Skip to content

Commit

Permalink
#103 v2.0.0 Does not work anymore (#105)
Browse files Browse the repository at this point in the history
- Use IAppManager to determine dicomviewer app path in filesystem
- Fix the issue with opening DICOM files in 2 or more level directories
- Add "Open with DICOM Viewer" option at folder level
- Support loading extensionless DICOM files when a folder is opened with "Open with DICOM Viewer" option
  • Loading branch information
ayselafsar authored Mar 26, 2024
1 parent f4fc3c4 commit e69cb86
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 47 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ You can build the source code with the following steps:

2. Change into the directory you have cloned this repository

3. Run `npm run install` command to build source code
3. Run `npm run build` command to build source code

4. Enable the DICOM Viewer app in Nextcloud

Expand Down
77 changes: 52 additions & 25 deletions lib/Controller/DisplayController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
include_once realpath(dirname(__FILE__)).DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'Nanodicom'.DIRECTORY_SEPARATOR.'nanodicom.php';

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 @@ -19,12 +21,14 @@
use OCP\ILogger;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserSession;
use OCP\Share\IManager;

class DisplayController extends Controller {

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

/**
* @param IRequest $request
Expand All @@ -36,30 +40,40 @@ public function __construct(IConfig $config,
ILogger $logger,
IMimeTypeDetector $mimeTypeDetector,
IRootFolder $rootFolder,
IManager $shareManager) {
IManager $shareManager,
IUserSession $userSession) {
parent::__construct(Application::APP_ID, $request);
$this->config = $config;
$this->urlGenerator = $urlGenerator;
$this->logger = $logger;
$this->mimeTypeDetector = $mimeTypeDetector;
$this->rootFolder = $rootFolder;
$this->shareManager = $shareManager;
$this->userSession = $userSession;

$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 @@ -107,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 All @@ -139,7 +153,7 @@ private function getContentSecurityPolicy() {
return $policy;
}

private function generateDICOMJson($dicomFilePaths, $selectedFileFullPath, $parentFullPath, $downloadUrlPrefix, $isPublic, $singlePublicFileDownload) {
private function generateDICOMJson($dicomFilePaths, $selectedFileFullPath, $parentFullPath, $currentUserPathToFile, $downloadUrlPrefix, $isPublic, $singlePublicFileDownload) {
$dicomJson = array('studies' => array());

foreach($dicomFilePaths as $dicomFilePath) {
Expand All @@ -153,6 +167,8 @@ private function generateDICOMJson($dicomFilePaths, $selectedFileFullPath, $pare
$urlParamFiles = substr($dicomFilePath, strrpos($dicomFilePath, '/') + 1);
$fileUrlPath = $downloadUrlPrefix.'?path='.$urlParamPath.'&files='.$urlParamFiles;
}
} else if ($currentUserPathToFile != null) {
$fileUrlPath = $downloadUrlPrefix.strstr($dicomFilePath, $currentUserPathToFile);
} else {
$fileUrlPath = $downloadUrlPrefix.$dicomFilePath;
}
Expand All @@ -161,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 @@ -425,23 +447,28 @@ public function getDICOMViewerAssetSub(string $assetpath): StreamResponse {
public function getDICOMJson(): JSONResponse {
$fileQueryParams = explode('|', $this->getQueryParam('file'));
$userId = $fileQueryParams[0];
$filepath = ltrim($fileQueryParams[1], '/');
$fileid = $fileQueryParams[1];
$isOpenNoExtension = count($fileQueryParams) > 2 && $fileQueryParams[2] == 1;

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

$parentPathToRemove = $dicomFolder->getParent()->getPath();
if ($userFolder->getPath() == $dicomFolder->getPath()) {
$parentPathToRemove = '/'.$userId.'/files';
}
// Find the file path by current user (e.g. file path in the shared folder)
$currentUser = $this->userSession->getUser();
$currentUserId = $currentUser->getUID();
$currentUserFolder = $this->rootFolder->getUserFolder($currentUserId);
$currentUserPathToFile = implode('', explode($currentUserFolder->getPath(), $currentUserFolder->getById((int)$fileid)[0]->getParent()->getPath(), 2));

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

$dicomParentFullPath = $this->dataFolder.'/'.$userId.'/files';
$downloadUrlPrefix = 'remote.php/dav/files/'.$userId;
$dicomJson = $this->generateDICOMJson($dicomFilePaths, $selectedFileFullPath, $dicomParentFullPath, $downloadUrlPrefix, false, false);
$downloadUrlPrefix = 'remote.php/dav/files/'.$currentUserId;
$dicomJson = $this->generateDICOMJson($dicomFilePaths, $selectedFileFullPath, $dicomParentFullPath, $currentUserPathToFile, $downloadUrlPrefix, false, false);
$response = new JSONResponse($dicomJson);
return $response;
}
Expand Down Expand Up @@ -476,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 All @@ -487,7 +514,7 @@ public function getPublicDICOMJson(): JSONResponse {
}

$downloadUrlPrefix = $this->getNextcloudBasePath().'/s/'.$shareToken.'/download';
$dicomJson = $this->generateDICOMJson($dicomFilePaths, $selectedFileFullPath, $dicomParentFullPath, $downloadUrlPrefix, true, $singlePublicFileDownload);
$dicomJson = $this->generateDICOMJson($dicomFilePaths, $selectedFileFullPath, $dicomParentFullPath, null, $downloadUrlPrefix, true, $singlePublicFileDownload);

$response = new JSONResponse($dicomJson);
return $response;
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);
};
11 changes: 5 additions & 6 deletions src/views/DICOMView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,13 @@ export default {
const shareToken = getPublicShareToken();
dicomUrl = shareToken && window.location.protocol + '//' + window.location.host + generateUrl(`/apps/dicomviewer/publicdicomjson?file=${shareToken}|${file.filename}`);
} else {
dicomUrl = window.location.protocol + '//' + window.location.host + generateUrl(`/apps/dicomviewer/dicomjson?file=${file.ownerId}|${file.filename}`);
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 e69cb86

Please sign in to comment.