Skip to content

Commit

Permalink
#103 Support opening DICOM files without extension in folder that is …
Browse files Browse the repository at this point in the history
…opened with file action menu "Open with DICOM Viewer"
  • Loading branch information
ayselafsar committed Mar 23, 2024
1 parent 8f165bb commit ee585f0
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 35 deletions.
47 changes: 32 additions & 15 deletions lib/Controller/DisplayController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Nanodicom;
use OC\Files\Filesystem;
use OCA\DICOMViewer\AppInfo\Application;
use OCP\App\IAppManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\EmptyContentSecurityPolicy;
use OCP\AppFramework\Http\TemplateResponse;
Expand All @@ -27,6 +28,7 @@ class DisplayController extends Controller {

/** @var IURLGenerator */
private $urlGenerator;
private ?IAppManager $appManager = null;

/**
* @param IRequest $request
Expand All @@ -51,19 +53,27 @@ public function __construct(IConfig $config,

$this->publicViewerFolderPath = null;
$this->publicViewerAssetsFolderPath = null;
$appsPaths = $this->config->getSystemValue('apps_paths');
foreach($appsPaths as $appsPath) {
$viewerFolder = $appsPath['path'] . '/dicomviewer/js/public/viewer';
if (file_exists($viewerFolder)) {
$this->publicViewerFolderPath = $viewerFolder;
$this->publicViewerAssetsFolderPath = $viewerFolder . '/assets';
break;
}

$app_path = $this->getAppManager()->getAppPath('dicomviewer');
$viewerFolder = $app_path . '/js/public/viewer';
if (file_exists($viewerFolder)) {
$this->publicViewerFolderPath = $viewerFolder;
$this->publicViewerAssetsFolderPath = $viewerFolder . '/assets';
} else {
$this->logger->error('Unable to find dicom viewer folder: ' . $viewerFolder);
}

$this->dataFolder = $this->config->getSystemValue('datadirectory');
}

private function getAppManager(): IAppManager {
if ($this->appManager !== null) {
return $this->appManager;
}
$this->appManager = \OCP\Server::get(IAppManager::class);
return $this->appManager;
}

private function getNextcloudBasePath() {
if ($this->config->getSystemValueBool('htaccess.IgnoreFrontController', false) || getenv('front_controller_active') === 'true') {
return $this->urlGenerator->getWebroot();
Expand Down Expand Up @@ -111,13 +121,13 @@ private function cleanDICOMTagValue($value) {
return $value;
}

private function getAllDICOMFilesInFolder($parentPathToRemove, $folderNode) {
private function getAllDICOMFilesInFolder($parentPathToRemove, $folderNode, $isOpenNoExtension) {
$filepaths = array();
$nodes = $folderNode->getDirectoryListing();
foreach($nodes as $node) {
if ($node->getType() == 'dir') {
$filepaths = array_merge($filepaths, $this->getAllDICOMFilesInFolder($parentPathToRemove, $node));
} else if ($node->getType() == 'file' && $node->getMimetype() == 'application/dicom') {
$filepaths = array_merge($filepaths, $this->getAllDICOMFilesInFolder($parentPathToRemove, $node, $isOpenNoExtension));
} else if ($node->getType() == 'file' && ($isOpenNoExtension || $node->getMimetype() == 'application/dicom')) {
array_push($filepaths, implode('', explode($parentPathToRemove, $node->getPath(), 2)));
}
}
Expand Down Expand Up @@ -167,6 +177,12 @@ private function generateDICOMJson($dicomFilePaths, $selectedFileFullPath, $pare

$fileFullPath = $parentFullPath.$dicomFilePath;
$dicom = Nanodicom::factory($fileFullPath);

if (!$dicom->is_dicom()) {
// Do not parse if it is not a DICOM file
continue;
}

$dicom->parse()->profiler_diff('parse');

$StudyInstanceUID = $this->cleanDICOMTagValue($dicom->value(0x0020, 0x000D));
Expand Down Expand Up @@ -432,11 +448,12 @@ public function getDICOMJson(): JSONResponse {
$fileQueryParams = explode('|', $this->getQueryParam('file'));
$userId = $fileQueryParams[0];
$fileid = $fileQueryParams[1];
$isOpenNoExtension = count($fileQueryParams) > 2 && $fileQueryParams[2] == 1;

// Find the file path located in the filesystem
$userFolder = $this->rootFolder->getUserFolder($userId);
$file = $userFolder->getById((int)$fileid)[0];
$selectedFileFullPath = $this->dataFolder.$file->getPath();
$selectedFileFullPath = $file->getType() == 'dir' ? null : $this->dataFolder.$file->getPath();

// Find the file path by current user (e.g. file path in the shared folder)
$currentUser = $this->userSession->getUser();
Expand All @@ -446,8 +463,8 @@ public function getDICOMJson(): JSONResponse {

// Get all DICOM files in the folder and sub folders
$parentPathToRemove = '/'.$userId.'/files';
$dicomFolder = $file->getParent();
$dicomFilePaths = $this->getAllDICOMFilesInFolder($parentPathToRemove, $dicomFolder);
$dicomFolder = $file->getType() == 'dir' ? $file : $file->getParent();
$dicomFilePaths = $this->getAllDICOMFilesInFolder($parentPathToRemove, $dicomFolder, $isOpenNoExtension);

$dicomParentFullPath = $this->dataFolder.'/'.$userId.'/files';
$downloadUrlPrefix = 'remote.php/dav/files/'.$currentUserId;
Expand Down Expand Up @@ -486,7 +503,7 @@ public function getPublicDICOMJson(): JSONResponse {

// Get all DICOM files in the share folder and sub folders
$parentPathToRemove = $shareNode->getPath();
$dicomFilePaths = $this->getAllDICOMFilesInFolder($parentPathToRemove, $shareNode);
$dicomFilePaths = $this->getAllDICOMFilesInFolder($parentPathToRemove, $shareNode, false);
} else {
$selectedFileFullPath = null;
$dicomParentFullPath = $this->dataFolder;
Expand Down
44 changes: 29 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
"@nextcloud/auth": "^2.2.1",
"@nextcloud/axios": "^2.4.0",
"@nextcloud/dialogs": "^5.0.3",
"@nextcloud/files": "^3.1.1",
"@nextcloud/logger": "^2.7.0",
"@nextcloud/l10n": "^2.2.0",
"@nextcloud/router": "^2.2.0",
"cornerstone-core": "^2.2.8",
"cornerstone-math": "^0.1.7",
Expand Down
3 changes: 3 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import DICOMView from './views/DICOMView.vue';
import generateFullUrl from './utils/generateFullUrl';
import registerFileActions from './utils/registerFileActions';
import './sidebar';

// Add MimeType Icon
Expand All @@ -17,3 +18,5 @@ OCA.Viewer.registerHandler({

canCompare: true,
});

registerFileActions();
18 changes: 18 additions & 0 deletions src/utils/AppIcon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const AppIcon = `
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
<style type="text/css">
.st0{fill:#969696;}
.st1{font-family:'MyriadPro-Bold';}
.st2{font-size:10.4869px;}
</style>
<path class="st0" d="M27,7.8c0.3,0.3,0.5,0.7,0.8,1.2c0.3,0.5,0.4,1,0.4,1.4v18.3c0,0.4-0.2,0.8-0.4,1.1c-0.4,0.2-0.7,0.4-1.2,0.4
H5.3c-0.4,0-0.8-0.2-1.1-0.4c-0.3-0.3-0.4-0.6-0.4-1.1V3.3c0-0.4,0.2-0.8,0.4-1.1s0.6-0.4,1.1-0.4h14.2c0.4,0,0.9,0.1,1.4,0.4
c0.5,0.3,0.9,0.4,1.2,0.8L27,7.8z M20,3.9v5.9H26c-0.1-0.3-0.2-0.5-0.4-0.6l-5-5C20.6,4.2,20.3,4,20,3.9z M26.1,28.1V11.9h-6.6
c-0.4,0-0.8-0.2-1.1-0.4c-0.3-0.3-0.4-0.6-0.4-1.1V3.9H5.9v24.3C5.9,28.1,26.1,28.1,26.1,28.1z"/>
<text transform="matrix(0.9036 0 0 1 6.9415 24.9971)" class="st0 st1 st2">dcm</text>
</svg>
`;

export default AppIcon;
39 changes: 39 additions & 0 deletions src/utils/registerFileActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { registerFileAction, FileAction, FileType, Permission } from '@nextcloud/files';
import { translate as t } from '@nextcloud/l10n';
import { generateUrl } from "@nextcloud/router";
import AppIcon from './AppIcon';

function openWithDICOMViewer(node) {
const dicomUrl = window.location.protocol + '//' + window.location.host + generateUrl(`/apps/dicomviewer/dicomjson?file=${node.owner}|${node.fileid}|1`);

// Open viewer in a new tab
const tab = window.open('about:blank');
tab.location = generateUrl(`/apps/dicomviewer/ncviewer/viewer/dicomjson?url=${dicomUrl}`);
tab.focus();
}

const fileAction = new FileAction({
id: 'dicomviewer',
order: -10000,
iconSvgInline() {
return AppIcon;
},
displayName() {
return t('dicomviewer', 'Open with DICOM Viewer');
},
enabled(nodes) {
return nodes.length === 1 && (nodes[0].permissions & Permission.READ) !== 0 && nodes[0].type === FileType.Folder;
},
async execBatch(nodes, view, dir) {
openWithDICOMViewer(nodes[0]);
return Promise.all([Promise.resolve(true)]);
},
async exec(node, view, dir) {
openWithDICOMViewer(node);
return true;
},
});

export default () => {
registerFileAction(fileAction);
};
9 changes: 4 additions & 5 deletions src/views/DICOMView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,10 @@ export default {
dicomUrl = window.location.protocol + '//' + window.location.host + generateUrl(`/apps/dicomviewer/dicomjson?file=${file.ownerId}|${file.fileid}`);
}
if (dicomUrl) {
const tab = window.open('about:blank');
tab.location = generateUrl(`/apps/dicomviewer/ncviewer/viewer/dicomjson?url=${dicomUrl}`);
tab.focus();
}
// Open viewer in a new tab
const tab = window.open('about:blank');
tab.location = generateUrl(`/apps/dicomviewer/ncviewer/viewer/dicomjson?url=${dicomUrl}`);
tab.focus();
// Close the loading modal
this.$parent.close();
Expand Down

0 comments on commit ee585f0

Please sign in to comment.