From 947a5f470d832e76a1d3fcc4ddc7390340d8071a Mon Sep 17 00:00:00 2001 From: nicolasvandooren Date: Wed, 12 Dec 2018 18:01:46 +0100 Subject: [PATCH 01/66] feat(standalone-viewer): Add authorization header (#322) --- StandaloneViewer/StandaloneViewer/client/config.js | 11 +++++++++++ StandaloneViewer/StandaloneViewer/routes.js | 8 ++++++++ docs/standalone-viewer/usage.md | 10 ++++++++++ 3 files changed, 29 insertions(+) diff --git a/StandaloneViewer/StandaloneViewer/client/config.js b/StandaloneViewer/StandaloneViewer/client/config.js index db0df14ba..8a81ef8f8 100644 --- a/StandaloneViewer/StandaloneViewer/client/config.js +++ b/StandaloneViewer/StandaloneViewer/client/config.js @@ -21,4 +21,15 @@ Meteor.startup(function() { cornerstoneWADOImageLoader.webWorkerManager.initialize(config); cornerstoneWebImageLoader.external.cornerstone = cornerstone; + + let configureAuthorization = { + beforeSend: function(xhr){ + if (OHIF.viewer.authorizationToken) { + xhr.setRequestHeader('Authorization', OHIF.viewer.authorizationToken); + } + } + }; + + cornerstoneWADOImageLoader.configure(configureAuthorization); + cornerstoneWebImageLoader.configure(configureAuthorization); }); diff --git a/StandaloneViewer/StandaloneViewer/routes.js b/StandaloneViewer/StandaloneViewer/routes.js index 725805107..e47bd1d7d 100644 --- a/StandaloneViewer/StandaloneViewer/routes.js +++ b/StandaloneViewer/StandaloneViewer/routes.js @@ -94,6 +94,14 @@ if (Meteor.isClient) { oReq.open('GET', url); oReq.setRequestHeader('Accept', 'application/json') + // Add token in the request authorization header + // if a token fragment parameter is present + const tokenParam = this.params.hash ? this.params.hash.match(/(?:token)=(.*?)(?:&|$)/) : null; + if (tokenParam) { + OHIF.viewer.authorizationToken = "Bearer " + tokenParam[1]; + oReq.setRequestHeader('Authorization', OHIF.viewer.authorizationToken); + } + // Fire the request to the server oReq.send(); }, diff --git a/docs/standalone-viewer/usage.md b/docs/standalone-viewer/usage.md index 367e0302b..3b487b06c 100644 --- a/docs/standalone-viewer/usage.md +++ b/docs/standalone-viewer/usage.md @@ -70,6 +70,16 @@ It is possible to build this standalone viewer to run as a client-only bundle of Open your web browser and navigate to http://localhost:3000/sampleJPEG.json or http://localhost:3000/sampleDICOM.json + ## Authorization Header + + A ```token``` fragment parameter can be specified. If present this value will be used for http bearer authorization when making requests for the above JSON, and when retrieving images using the Cornerstone Image Loaders. + + Example : + + ``` + http://localhost:3000/sampleDICOM.json#token=1a2b3c4d + ``` + ### Testing the Sample client-only build For the sake of simplicity we have also included a pre-built client-only version of the standalone viewer, which can be found in the SampleClientOnlyBuild folder. From 7643a384b44ceec32e6afb81e7f33bc54fd53cca Mon Sep 17 00:00:00 2001 From: Zaid Safadi Date: Mon, 17 Dec 2018 17:33:30 -0500 Subject: [PATCH 02/66] Update cornerstone libs (wado) to latest and fix Incorrect ultrasound measurement #319 (#323) --- Packages/ohif-cornerstone/package.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Packages/ohif-cornerstone/package.js b/Packages/ohif-cornerstone/package.js index cbe68e3a7..d2a8c0057 100644 --- a/Packages/ohif-cornerstone/package.js +++ b/Packages/ohif-cornerstone/package.js @@ -6,11 +6,11 @@ Package.describe({ Npm.depends({ hammerjs: '2.0.8', - 'cornerstone-core': '2.2.7', + 'cornerstone-core': '2.2.8', 'cornerstone-tools': '2.4.0', - 'cornerstone-math': '0.1.6', - 'dicom-parser': '1.8.0', - 'cornerstone-wado-image-loader': '2.1.4', + 'cornerstone-math': '0.1.7', + 'dicom-parser': '1.8.3', + 'cornerstone-wado-image-loader': '2.2.3', 'dcmjs': '0.2.1' }); From e508a59ee350f04e08932c61ce14360dd98d6142 Mon Sep 17 00:00:00 2001 From: Evren Ozkan Date: Wed, 19 Dec 2018 10:29:45 -0500 Subject: [PATCH 03/66] fix(thumbnails): Prevent caching for thumbnail images by using canvas element (thanks @jasonklotzer) --- .../imageThumbnail/imageThumbnail.html | 2 +- .../imageThumbnail/imageThumbnail.js | 20 ++++++------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/Packages/ohif-viewerbase/client/components/studyBrowser/imageThumbnail/imageThumbnail.html b/Packages/ohif-viewerbase/client/components/studyBrowser/imageThumbnail/imageThumbnail.html index a64431dcb..5f9386a58 100644 --- a/Packages/ohif-viewerbase/client/components/studyBrowser/imageThumbnail/imageThumbnail.html +++ b/Packages/ohif-viewerbase/client/components/studyBrowser/imageThumbnail/imageThumbnail.html @@ -1,7 +1,7 @@ diff --git a/OHIFViewer/client/components/ohifViewer/ohifViewer.js b/OHIFViewer/client/components/ohifViewer/ohifViewer.js index 3e2be61ab..ce9eb71e5 100644 --- a/OHIFViewer/client/components/ohifViewer/ohifViewer.js +++ b/OHIFViewer/client/components/ohifViewer/ohifViewer.js @@ -8,6 +8,7 @@ import { OHIF } from 'meteor/ohif:core'; Template.ohifViewer.onCreated(() => { const instance = Template.instance(); instance.headerClasses = new ReactiveVar(''); + Session.set("IsStudyListReady", true);; const headerItems = [{ action: () => OHIF.ui.showDialog('serverInformationModal'), @@ -31,9 +32,11 @@ Template.ohifViewer.onCreated(() => { icon: 'fa fa-info' }]; - if (OHIF.user.userLoggedIn() === true) { + const isUserLoggedIn = OHIF.user.userLoggedIn(); + const isDemoUserLoggedIn = OHIF.demoMode && OHIF.demoMode.userLoggedIn(); + if (isUserLoggedIn || isDemoUserLoggedIn) { headerItems.push({ - action: OHIF.user.logout, + action: isDemoUserLoggedIn ? OHIF.demoMode.logout : OHIF.user.logout, text: 'Logout', iconClasses: 'logout', iconSvgUse: 'packages/ohif_viewerbase/assets/user-menu-icons.svg#logout' @@ -57,6 +60,24 @@ Template.ohifViewer.onCreated(() => { // Set the viewer open state on session Session.set('ViewerOpened', isViewer); }); + + if (OHIF.demoMode && OHIF.demoMode.userLoggedIn()) + OHIF.demoMode.setDemoServerConfig(); + else if (OHIF.gcloud && OHIF.gcloud.isEnabled()) { + const server = OHIF.servers.getCurrentServer(); + if (!server || !server.isCloud) { + Session.set("IsStudyListReady", false); + OHIF.gcloud.showDicomStorePicker({canClose: OHIF.demoMode}).then(config => { + if (!config) { + if (OHIF.demoMode) + Router.go('/demo-signin'); + return; + } + OHIF.servers.applyCloudServerConfig(config); + Session.set("IsStudyListReady", true); + }); + } + } }); Template.ohifViewer.events({ @@ -72,7 +93,8 @@ Template.ohifViewer.events({ Router.go('viewerStudies', { studyInstanceUids }); } } - } + }, + }); Template.ohifViewer.helpers({ @@ -86,5 +108,8 @@ Template.ohifViewer.helpers({ } return instance.hasViewerData ? 'Back to viewer' : ''; + }, + isStudyListReady() { + return !!Session.get('IsStudyListReady'); } }); diff --git a/OHIFViewer/client/components/ohifViewer/structuredReportModal/structureReportModal.html b/OHIFViewer/client/components/ohifViewer/structuredReportModal/structureReportModal.html new file mode 100644 index 000000000..5c52d5a15 --- /dev/null +++ b/OHIFViewer/client/components/ohifViewer/structuredReportModal/structureReportModal.html @@ -0,0 +1,11 @@ + diff --git a/OHIFViewer/client/components/ohifViewer/structuredReportModal/structuredReportModal.js b/OHIFViewer/client/components/ohifViewer/structuredReportModal/structuredReportModal.js new file mode 100644 index 000000000..90c948007 --- /dev/null +++ b/OHIFViewer/client/components/ohifViewer/structuredReportModal/structuredReportModal.js @@ -0,0 +1,91 @@ +import { $ } from 'meteor/jquery'; +import { OHIF } from 'meteor/ohif:core'; + +Template.structuredReportModal.onRendered(() => { + const structuredReport = getStructureReport(); + + render(structuredReport); +}); + +// FIXME: we use just 1st SR for current study for now +function getStructureReport() { + let structuredReport; + + OHIF.viewer.StudyMetadataList.find(studyMetadata => { + + structuredReport = studyMetadata.findInstance(instance => instance.getData().modality === 'SR'); + + // If SR is found stop the search + return !!structuredReport; + }); + + return structuredReport; +} + +function render(structureReport) { + const root = $('#root'); + + if (structureReport) { + renderStructuredReport(root, structureReport.getData()); + } else { + renderNoData(root); + } + +} + +function renderStructuredReport(root, data) { + root.append(getMainDataHtml(data)); + root.append(getContentSequenceHtml(data.contentSequence)); +} + +function renderNoData(root) { + root.append('
No structured report found
'); +} + +function getMainDataHtml(data) { + const root = $('
'); + + const { completionFlag, verificationFlag, manufacturer, contentDateTime } = data; + + if (completionFlag) { + root.append(getMainDataItemHtml('Completion flag', completionFlag)); + } + + if (verificationFlag) { + root.append(getMainDataItemHtml('Verification flag', verificationFlag)); + } + + if (manufacturer) { + root.append(getMainDataItemHtml('Manufacturer', manufacturer)); + } + + if (contentDateTime) { + root.append(getMainDataItemHtml('Content Date/Time', contentDateTime)); + } + + return root; +} + +const getContentSequenceHtml = (data, level = 1) => { + const root = $('
'); + const header = data.header; + const items = data.items || []; + + if (header) { + root.append(`${header}`); + } + + items.forEach(item => { + root.append( + item instanceof Object + ? getContentSequenceHtml(item, level + 1) + : `
${item}
` + ); + }); + + return root; +} + +function getMainDataItemHtml(key, value) { + return $(`
${key}: ${value}
`); +} \ No newline at end of file diff --git a/OHIFViewer/client/components/toolbarSection/toolbarSection.js b/OHIFViewer/client/components/toolbarSection/toolbarSection.js index 9d0eb3601..8062334fe 100644 --- a/OHIFViewer/client/components/toolbarSection/toolbarSection.js +++ b/OHIFViewer/client/components/toolbarSection/toolbarSection.js @@ -261,6 +261,22 @@ Template.toolbarSection.helpers({ buttonTemplateName: 'layoutButton' }); + buttonData.push({ + id: 'sr', + title: 'SR', + classes: 'imageViewerTool', + svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-sr' + }); + + buttonData.push({ + id: 'ps', + title: 'PS', + iconClasses: 'fa fa-th-large', + classes: 'imageViewerTool', + svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-ps' + }); + + buttonData.push({ id: 'toggleMore', title: 'More', @@ -318,4 +334,4 @@ Template.toolbarSection.onRendered(function() { } } } -}); +}); \ No newline at end of file diff --git a/Packages/ohif-demo-mode/client/components/demoSignin/demoSignin.html b/Packages/ohif-demo-mode/client/components/demoSignin/demoSignin.html new file mode 100644 index 000000000..b5934b22e --- /dev/null +++ b/Packages/ohif-demo-mode/client/components/demoSignin/demoSignin.html @@ -0,0 +1,25 @@ + diff --git a/Packages/ohif-demo-mode/client/components/demoSignin/demoSignin.js b/Packages/ohif-demo-mode/client/components/demoSignin/demoSignin.js new file mode 100644 index 000000000..dfa2cfb39 --- /dev/null +++ b/Packages/ohif-demo-mode/client/components/demoSignin/demoSignin.js @@ -0,0 +1,15 @@ +import { OHIF } from 'meteor/ohif:core'; +import { Router } from 'meteor/clinical:router'; + +Template.demoSignin.events({ + 'click #google-login-button'() { + OHIF.gcloud.setEnabled(true); + OHIF.user.login(); + }, + 'click #anonymous-login-button'() { + OHIF.gcloud.setEnabled(false); + OHIF.demoMode.login(); + Router.go('/studylist', {}, { replaceState: true }); + } +}); + diff --git a/Packages/ohif-demo-mode/client/components/demoSignin/demoSignin.styl b/Packages/ohif-demo-mode/client/components/demoSignin/demoSignin.styl new file mode 100644 index 000000000..75cf61132 --- /dev/null +++ b/Packages/ohif-demo-mode/client/components/demoSignin/demoSignin.styl @@ -0,0 +1,93 @@ +@require '{ohif:design}/app' + +.demoSignin + display flex + flex 1 + flex-flow row nowrap + align-items stretch + height 100vh + min-height 600px + width 100% + background-color rgba(21, 25, 30, 0.7) + +.demo-content + margin auto + width 500px + position relative + top -150px + +.demoSigninButtons + margin auto + width 270px + font-weight 500 + +#google-login-button + display flex + justify-content center + height 40px + width 100% + color #fff + border-color #4285F4 + background #4285F4 + border-radius 3px + box-shadow 0 0 1px 0 rgba(0, 0, 0, 0.12), 0 1px 1px 0 rgba(0, 0, 0, 0.24) + cursor pointer + position relative + align-items center + +#anonymous-login-button + display flex + justify-content center + height 40px + width 100% + color #757575 + border-color white + background white + border-radius 3px + box-shadow 0 0 1px 0 rgba(0, 0, 0, 0.12), 0 1px 1px 0 rgba(0, 0, 0, 0.24) + cursor pointer + position relative + align-items center + +#google-login-button .sign-in-with-google-icon-tile + position absolute + top 1px + left 1px + background #fff + border-radius 2px + height 32px + width 32px + display flex + align-items center + justify-content center + font-weight 500 + +svg.google-icon + width 24px + height 24px + +hr.tint + border-color hsla(0, 0%, 100%, 0.3) + +.demo-brand + height 60px + display inline-block + text-decoration none + margin-bottom 30px + + .logo-image + display inline-block + fill transparent + float left + height 100% + margin 0 8px 0 0 + width 60px + margin-right 20px + + .logo-text + display inline-block + font-family Roboto, Arial, Helvetica, sans-serif + font-size 26px + font-weight 400 + theme('color', '$textPrimaryColor') + line-height 60px \ No newline at end of file diff --git a/Packages/ohif-demo-mode/client/components/index.js b/Packages/ohif-demo-mode/client/components/index.js new file mode 100644 index 000000000..16ec46a8d --- /dev/null +++ b/Packages/ohif-demo-mode/client/components/index.js @@ -0,0 +1,3 @@ +import './demoSignin/demoSignin.html'; +import './demoSignin/demoSignin.js'; +import './demoSignin/demoSignin.styl'; diff --git a/Packages/ohif-demo-mode/client/demoModeMediator.js b/Packages/ohif-demo-mode/client/demoModeMediator.js new file mode 100644 index 000000000..d4338c5df --- /dev/null +++ b/Packages/ohif-demo-mode/client/demoModeMediator.js @@ -0,0 +1,29 @@ +import { OHIF } from 'meteor/ohif:core'; +import { Router } from 'meteor/clinical:router'; +import { Servers, CurrentServer } from 'meteor/ohif:servers/both/collections'; + +const devModeMediator = {}; +export default devModeMediator; + +const DEMO_SERVER_NAME = "demo-dcm4chee"; + +devModeMediator.login = () => sessionStorage.setItem('isDemoUserSignedIn', true); + +devModeMediator.logout = () => { + if (OHIF.user.userLoggedIn()) + OHIF.user.logout(); + sessionStorage.removeItem('isDemoUserSignedIn'); + Router.go('/'); +} + +devModeMediator.userLoggedIn = () => sessionStorage.getItem('isDemoUserSignedIn'); + +devModeMediator.setDemoServerConfig = () => { + CurrentServer.remove({}); + const demoServer = Servers.findOne({ name: DEMO_SERVER_NAME }); + if (!demoServer) + throw new Error("demoServer is not found"); + CurrentServer.insert({ + serverId: demoServer._id + }); +}; \ No newline at end of file diff --git a/Packages/ohif-demo-mode/client/index.js b/Packages/ohif-demo-mode/client/index.js new file mode 100644 index 000000000..b561f9934 --- /dev/null +++ b/Packages/ohif-demo-mode/client/index.js @@ -0,0 +1,2 @@ +import './components'; +import './routes.js'; diff --git a/Packages/ohif-demo-mode/client/routes.js b/Packages/ohif-demo-mode/client/routes.js new file mode 100644 index 000000000..b449f14bc --- /dev/null +++ b/Packages/ohif-demo-mode/client/routes.js @@ -0,0 +1,26 @@ +import { Meteor } from 'meteor/meteor'; +import { Router } from 'meteor/clinical:router'; +import { OHIF } from 'meteor/ohif:core'; + +const DEMO_SIGN_IN_PAGE = '/demo-signin'; + +Router.onRun(function() { + if (!OHIF.demoMode) + this.next(); + else if (this.url === DEMO_SIGN_IN_PAGE) + this.next(); + else if (OHIF.demoMode.userLoggedIn() || OHIF.user.userLoggedIn()) { + // user is logged in whether in demo or in oidc mode + this.next(); + } else if (this.url === OHIF.user.getOidcRedirectUri()) { + // allow oidc to sign in + this.next(); + } else { + // redirect to demo login page + Router.go(DEMO_SIGN_IN_PAGE, {}, { replaceState: true }); + } +}); + +Router.route(DEMO_SIGN_IN_PAGE, function() { + this.render('demoSignin'); +}, { name: 'demo' }); \ No newline at end of file diff --git a/Packages/ohif-demo-mode/main.js b/Packages/ohif-demo-mode/main.js new file mode 100644 index 000000000..6f891fbb2 --- /dev/null +++ b/Packages/ohif-demo-mode/main.js @@ -0,0 +1,9 @@ +import { OHIF } from 'meteor/ohif:core'; +import './client'; +import devModeMediator from './client/demoModeMediator.js'; + +const demoMode = Meteor.settings && Meteor.settings.public && Meteor.settings.public.demoMode; + +if (demoMode) { + OHIF.demoMode = devModeMediator; +} \ No newline at end of file diff --git a/Packages/ohif-demo-mode/package.js b/Packages/ohif-demo-mode/package.js new file mode 100644 index 000000000..b9013a37b --- /dev/null +++ b/Packages/ohif-demo-mode/package.js @@ -0,0 +1,20 @@ +Package.describe({ + name: 'ohif:demo-mode', + summary: 'demo mode', + version: '0.0.1', +}); + +Package.onUse(function(api) { + api.versionsFrom('1.4'); + + api.use('http'); + api.use('ecmascript'); + api.use(['templating', 'stylus'], 'client'); + + // OHIF dependencies + api.use('ohif:core', 'client'); + + // Main module + api.mainModule('main.js', 'client'); + +}); diff --git a/Packages/ohif-google-cloud/.meteorignore b/Packages/ohif-google-cloud/.meteorignore new file mode 100644 index 000000000..8cd000bc2 --- /dev/null +++ b/Packages/ohif-google-cloud/.meteorignore @@ -0,0 +1,3 @@ +healthcare-api-adapter/node_modules/ +healthcare-api-adapter/public/ +healthcare-api-adapter/src/ \ No newline at end of file diff --git a/Packages/ohif-google-cloud/client/components/dialogs/gcloudDialog.html b/Packages/ohif-google-cloud/client/components/dialogs/gcloudDialog.html new file mode 100644 index 000000000..98bd592c2 --- /dev/null +++ b/Packages/ohif-google-cloud/client/components/dialogs/gcloudDialog.html @@ -0,0 +1,12 @@ + diff --git a/Packages/ohif-google-cloud/client/components/dialogs/gcloudDialog.js b/Packages/ohif-google-cloud/client/components/dialogs/gcloudDialog.js new file mode 100644 index 000000000..180160a6c --- /dev/null +++ b/Packages/ohif-google-cloud/client/components/dialogs/gcloudDialog.js @@ -0,0 +1,22 @@ +import { Meteor } from 'meteor/meteor'; +import { Template } from 'meteor/templating'; +import { OHIF } from 'meteor/ohif:core'; + +Template.gcloudDialog.onRendered(() => { + const instance = Template.instance(); + + // Allow options ovewrite + const modalOptions = _.extend( + { + backdrop: 'static', + keyboard: false, + width: 650, + }, + instance.data.modalOptions + ); + + const $modal = instance.$('.modal'); + + // Create the bootstrap modal + $modal.modal(modalOptions); +}); diff --git a/Packages/ohif-google-cloud/client/components/dialogs/gcloudDialog.styl b/Packages/ohif-google-cloud/client/components/dialogs/gcloudDialog.styl new file mode 100644 index 000000000..ae7de0a87 --- /dev/null +++ b/Packages/ohif-google-cloud/client/components/dialogs/gcloudDialog.styl @@ -0,0 +1,6 @@ +@import '{ohif:design}/app' + +.gcloud-dialog + display flex + .modal-dialog + margin auto \ No newline at end of file diff --git a/Packages/ohif-google-cloud/client/components/dicomStorePicker/dicomStorePicker.html b/Packages/ohif-google-cloud/client/components/dicomStorePicker/dicomStorePicker.html new file mode 100644 index 000000000..ba23a4016 --- /dev/null +++ b/Packages/ohif-google-cloud/client/components/dicomStorePicker/dicomStorePicker.html @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Packages/ohif-google-cloud/client/components/dicomStorePicker/dicomStorePicker.js b/Packages/ohif-google-cloud/client/components/dicomStorePicker/dicomStorePicker.js new file mode 100644 index 000000000..57f12070c --- /dev/null +++ b/Packages/ohif-google-cloud/client/components/dicomStorePicker/dicomStorePicker.js @@ -0,0 +1,28 @@ +import { Meteor } from 'meteor/meteor'; + +const DATASET_PICKER_ID = 'gcp-dataset-picker'; +const EVENT_NAME = 'onSelect'; + +Template.dicomStorePicker.onRendered(() => { + const instance = Template.instance(); + instance.$('#' + DATASET_PICKER_ID).on(EVENT_NAME, (event, data) => { + instance + .$('.modal') + .one('hidden.bs.modal', event => { + instance.data.promiseResolve(data); + }) + .modal('hide'); + }); +}); + +Template.dicomStorePicker.helpers({ + datasetPickerId() { + return DATASET_PICKER_ID; + }, + eventName() { + return EVENT_NAME; + }, + oidcStorageKey() { + return OHIF.user.getOidcStorageKey(); + }, +}); diff --git a/Packages/ohif-google-cloud/client/components/dicomStorePicker/dicomStorePicker.styl b/Packages/ohif-google-cloud/client/components/dicomStorePicker/dicomStorePicker.styl new file mode 100644 index 000000000..f669618c4 --- /dev/null +++ b/Packages/ohif-google-cloud/client/components/dicomStorePicker/dicomStorePicker.styl @@ -0,0 +1,5 @@ +@import '{ohif:design}/app' + +#dicomStorePicker + .modal-dialog + width 694px \ No newline at end of file diff --git a/Packages/ohif-google-cloud/client/components/index.js b/Packages/ohif-google-cloud/client/components/index.js new file mode 100644 index 000000000..25d25ebe4 --- /dev/null +++ b/Packages/ohif-google-cloud/client/components/index.js @@ -0,0 +1,12 @@ +import './dialogs/gcloudDialog.html'; +import './dialogs/gcloudDialog.js'; +import './dialogs/gcloudDialog.styl'; + + +import './dicomStorePicker/dicomStorePicker.html'; +import './dicomStorePicker/dicomStorePicker.js'; +import './dicomStorePicker/dicomStorePicker.styl'; + +import './uploadStudiesDialog/uploadStudiesDialog.html'; +import './uploadStudiesDialog/uploadStudiesDialog.js'; +import './uploadStudiesDialog/uploadStudiesDialog.styl'; \ No newline at end of file diff --git a/Packages/ohif-google-cloud/client/components/uploadStudiesDialog/uploadStudiesDialog.html b/Packages/ohif-google-cloud/client/components/uploadStudiesDialog/uploadStudiesDialog.html new file mode 100644 index 000000000..7373c1c7f --- /dev/null +++ b/Packages/ohif-google-cloud/client/components/uploadStudiesDialog/uploadStudiesDialog.html @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/Packages/ohif-google-cloud/client/components/uploadStudiesDialog/uploadStudiesDialog.js b/Packages/ohif-google-cloud/client/components/uploadStudiesDialog/uploadStudiesDialog.js new file mode 100644 index 000000000..110f428cc --- /dev/null +++ b/Packages/ohif-google-cloud/client/components/uploadStudiesDialog/uploadStudiesDialog.js @@ -0,0 +1,31 @@ +import { Meteor } from 'meteor/meteor'; + +const DICOM_FILE_UPLOADER_ID = 'gcp-dicom-uploader'; +const EVENT_NAME = 'onClose'; + +Template.uploadStudiesDialog.onRendered(() => { + const instance = Template.instance(); + instance.$('#' + DICOM_FILE_UPLOADER_ID).on(EVENT_NAME, (event, data) => { + instance + .$('.modal') + .one('hidden.bs.modal', event => { + instance.data.promiseResolve(data); + }) + .modal('hide'); + }); +}); + +Template.uploadStudiesDialog.helpers({ + dicomFilesUploaderId() { + return DICOM_FILE_UPLOADER_ID; + }, + eventName() { + return EVENT_NAME; + }, + oidcStorageKey() { + return OHIF.user.getOidcStorageKey(); + }, + url() { + return OHIF.gcloud.getConfig().qidoRoot; // FIXME: not QIDO + } +}); diff --git a/Packages/ohif-google-cloud/client/components/uploadStudiesDialog/uploadStudiesDialog.styl b/Packages/ohif-google-cloud/client/components/uploadStudiesDialog/uploadStudiesDialog.styl new file mode 100644 index 000000000..ea3005d96 --- /dev/null +++ b/Packages/ohif-google-cloud/client/components/uploadStudiesDialog/uploadStudiesDialog.styl @@ -0,0 +1,8 @@ +@import '{ohif:design}/app' + +#dicomFilesUploader + .modal-dialog + .modal-body + padding 0 + width 536px + max-height 563px \ No newline at end of file diff --git a/Packages/ohif-google-cloud/client/index.js b/Packages/ohif-google-cloud/client/index.js new file mode 100644 index 000000000..095e70eb8 --- /dev/null +++ b/Packages/ohif-google-cloud/client/index.js @@ -0,0 +1,8 @@ +import { loadScript } from "../lib/utils" + +loadScript("/packages/ohif_google-cloud/.npm/package/node_modules/healthcare-api-adapter/dist/vue.js", () => { + loadScript("/packages/ohif_google-cloud/.npm/package/node_modules/healthcare-api-adapter/dist/gcp.min.js"); +}); + + +import './components'; diff --git a/Packages/ohif-google-cloud/lib/GCloudAdapter.js b/Packages/ohif-google-cloud/lib/GCloudAdapter.js new file mode 100644 index 000000000..17aab1a38 --- /dev/null +++ b/Packages/ohif-google-cloud/lib/GCloudAdapter.js @@ -0,0 +1,41 @@ +const GCloudAdapter = {}; + +const GCP_HEALTHCARE_CONFIG = 'GCP_HEALTHCARE_CONFIG'; +let isEnabled = true; + +GCloudAdapter.getConfig = function() { + const configStr = sessionStorage.getItem(GCP_HEALTHCARE_CONFIG); + if (configStr) return JSON.parse(configStr); + return null; +}; + +GCloudAdapter.setConfig = function(config) { + if (config) sessionStorage.setItem(GCP_HEALTHCARE_CONFIG, JSON.stringify(config)); + else sessionStorage.removeItem(GCP_HEALTHCARE_CONFIG); +}; + +GCloudAdapter.showDicomStorePicker = function(options) { + return OHIF.ui.showDialog('dicomStorePicker', options).then(config => { + if (config) OHIF.gcloud.setConfig(config); + return config; + }); +}; + +GCloudAdapter.showUploadStudiesDialog = function() { + return OHIF.ui.showDialog('uploadStudiesDialog') +}; + +const gcpConfig = GCloudAdapter.getConfig(); +if (gcpConfig) { + OHIF.servers.applyCloudServerConfig(gcpConfig); +} + +GCloudAdapter.setEnabled = function(value) { + isEnabled = value; +} + +GCloudAdapter.isEnabled = function() { + return isEnabled; +}; + +export default GCloudAdapter; diff --git a/Packages/ohif-google-cloud/lib/utils.js b/Packages/ohif-google-cloud/lib/utils.js new file mode 100644 index 000000000..a6084eb99 --- /dev/null +++ b/Packages/ohif-google-cloud/lib/utils.js @@ -0,0 +1,10 @@ +export function loadScript(url, callback=undefined) +{ + var head = document.getElementsByTagName('head')[0]; + var script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = url; + script.onreadystatechange = callback; + script.onload = callback; + head.appendChild(script); +} \ No newline at end of file diff --git a/Packages/ohif-google-cloud/main.js b/Packages/ohif-google-cloud/main.js new file mode 100644 index 000000000..937a20b37 --- /dev/null +++ b/Packages/ohif-google-cloud/main.js @@ -0,0 +1,9 @@ +import { OHIF } from 'meteor/ohif:core'; +import GCloudAdapter from './lib/GCloudAdapter'; + +if (Meteor.settings.public.googleCloud) { + const gcloud = GCloudAdapter; + OHIF.gcloud = gcloud; +} + + diff --git a/Packages/ohif-google-cloud/package.js b/Packages/ohif-google-cloud/package.js new file mode 100644 index 000000000..3a3b34177 --- /dev/null +++ b/Packages/ohif-google-cloud/package.js @@ -0,0 +1,44 @@ +Package.describe({ + name: 'ohif:google-cloud', + summary: 'DICOM Services: Google Cloud Healthcare API integration', + version: '0.0.1', + documentation: 'README.md', +}); + +Npm.depends({ + 'healthcare-api-adapter': "git+https://github.com/quantumsoftgroup/healthcare-api-adapter#v0.2.1" +}); + + +Package.onUse(function(api) { + api.versionsFrom('1.4'); + + api.use('http'); + api.use('ecmascript'); + + api.use(['templating', 'stylus'], 'client'); + + // Main module + api.mainModule('main.js', ['client']); + + // Client imports and routes + api.addFiles('client/index.js', 'client'); + + var assets = [ + '.npm/package/node_modules/healthcare-api-adapter/dist/gcp.min.js', + '.npm/package/node_modules/healthcare-api-adapter/dist/gcp.0.min.js', + '.npm/package/node_modules/healthcare-api-adapter/dist/gcp.2.min.js', + '.npm/package/node_modules/healthcare-api-adapter/dist/gcp.3.min.js', + '.npm/package/node_modules/healthcare-api-adapter/dist/gcp.4.min.js', + '.npm/package/node_modules/healthcare-api-adapter/dist/vue.js', + '.npm/package/node_modules/healthcare-api-adapter/dist/img/Button_File.473e74a7.svg', + '.npm/package/node_modules/healthcare-api-adapter/dist/img/Button_Folder.271da60b.svg', + '.npm/package/node_modules/healthcare-api-adapter/dist/img/Icon-24px-Close.d1a4d6d2.svg', + '.npm/package/node_modules/healthcare-api-adapter/dist/img/Icon-Arrow.e493b444.svg', + '.npm/package/node_modules/healthcare-api-adapter/dist/img/Icon-Warn.f3b4b640.svg', + '.npm/package/node_modules/healthcare-api-adapter/dist/img/arrow_right.d8a5b209.svg', + ]; + + api.addAssets(assets, 'client'); +}); + diff --git a/Packages/ohif-measurement-table/client/index.js b/Packages/ohif-measurement-table/client/index.js index bf641764e..6e8095097 100644 --- a/Packages/ohif-measurement-table/client/index.js +++ b/Packages/ohif-measurement-table/client/index.js @@ -31,21 +31,29 @@ class MeasurementTable { Session.set('TimepointsReady', true); await measurementApi.retrieveMeasurements(patientId, [OHIF.viewer.data.currentTimepointId]); - Session.set('MeasurementsReady', true); + Session.set('MeasurementsReady', false); measurementApi.syncMeasurementsAndToolData(); + this.jumpToFirstMeasurement(); + const viewportUtils = OHIF.viewerbase.viewportUtils; this.firstMeasurementActivated = false; - + this.dataIsavalible = false; instance.autorun(() => { if (!Session.get('TimepointsReady') || !Session.get('MeasurementsReady') || !Session.get('ViewerReady') || this.firstMeasurementActivated) { + if (this.dataIsavalible) { + viewportUtils.hideTools(); + this.dataIsavalible = false; + } return; } - - this.jumpToFirstMeasurement(); + if(!this.dataIsavalible){ + viewportUtils.unhideTools(); + this.dataIsavalible = true; + } }); diff --git a/Packages/ohif-measurement-table/client/schema/length.js b/Packages/ohif-measurement-table/client/schema/length.js index 20c17a98d..46ffa90ea 100644 --- a/Packages/ohif-measurement-table/client/schema/length.js +++ b/Packages/ohif-measurement-table/client/schema/length.js @@ -47,6 +47,11 @@ const toolSchema = new SimpleSchema([MeasurementSchemaTypes.CornerstoneToolMeasu label: 'Length', optional: true, decimal: true + }, + dashed: { + type: String, + label: 'dashed', + optional: true } }]); diff --git a/Packages/ohif-measurement-table/client/utils/srUtils.js b/Packages/ohif-measurement-table/client/utils/srUtils.js index 8805993b6..2d2462059 100644 --- a/Packages/ohif-measurement-table/client/utils/srUtils.js +++ b/Packages/ohif-measurement-table/client/utils/srUtils.js @@ -1,6 +1,16 @@ import { dcmjs } from 'meteor/ohif:cornerstone'; -const supportedSopClassUIDs = ['1.2.840.10008.5.1.4.1.1.88.22']; +const supportedSopClassUIDs = ['1.2.840.10008.5.1.4.1.1.88.22', '1.2.840.10008.5.1.4.1.1.11.1']; + +const toArray = function(x) { + return (x.constructor.name === "Array" ? x : [x]); +}; + +const codeMeaningEquals = (codeMeaningName) => { + return (contentItem) => { + return contentItem.ConceptNameCodeSequence.CodeMeaning === codeMeaningName; + }; +}; const getAllDisplaySets = () => { const allStudies = OHIF.viewer.Studies.all(); @@ -59,5 +69,5 @@ const getLatestSRSeries = () => { export { getAllDisplaySets, getInstanceMetadata, - getLatestSRSeries, -} + getLatestSRSeries +} \ No newline at end of file diff --git a/Packages/ohif-servers/both/lib/applyCloudServerConfig.js b/Packages/ohif-servers/both/lib/applyCloudServerConfig.js new file mode 100644 index 000000000..8341c5257 --- /dev/null +++ b/Packages/ohif-servers/both/lib/applyCloudServerConfig.js @@ -0,0 +1,28 @@ +import { OHIF } from 'meteor/ohif:core'; +import { Session } from 'meteor/session'; +import { Servers, CurrentServer } from 'meteor/ohif:servers/both/collections'; + +/** + * Recreates a current server with GCloud config + */ +OHIF.servers.applyCloudServerConfig = (config) => { + Session.set('GCP_HEALTHCARE_CONFIG', config); + CurrentServer.remove({}); + if (!config) + return; + config.name = "gcs"; + config.imageRendering = "wadors"; + config.origin = "json"; + config.thumbnailRendering = "wadors"; + config.qidoSupportsIncludeField = false; + config.type = "dicomweb"; + config.requestOptions = {}; + config.requestOptions.requestFromBrowser = true; + config.origin = 'json'; + config.type = 'dicomWeb'; + config.isCloud = true; + const serverId = Servers.insert(config); + CurrentServer.insert({ + serverId + }); +}; \ No newline at end of file diff --git a/Packages/ohif-servers/both/lib/index.js b/Packages/ohif-servers/both/lib/index.js index 0df54741b..73ccf0759 100644 --- a/Packages/ohif-servers/both/lib/index.js +++ b/Packages/ohif-servers/both/lib/index.js @@ -1 +1,2 @@ import './getCurrentServer.js'; +import './applyCloudServerConfig.js'; diff --git a/Packages/ohif-studies/imports/both/services/index.js b/Packages/ohif-studies/imports/both/services/index.js index 8808a8ee6..11aa91bb9 100644 --- a/Packages/ohif-studies/imports/both/services/index.js +++ b/Packages/ohif-studies/imports/both/services/index.js @@ -3,4 +3,5 @@ import './namespace'; // DICOMWeb instance, study, and metadata retrieval import './qido/instances.js'; import './qido/studies.js'; +import './qido/retrieveMetadata.js'; import './wado/retrieveMetadata.js'; diff --git a/Packages/ohif-studies/imports/both/services/qido/retrieveMetadata.js b/Packages/ohif-studies/imports/both/services/qido/retrieveMetadata.js new file mode 100644 index 000000000..71b29ee05 --- /dev/null +++ b/Packages/ohif-studies/imports/both/services/qido/retrieveMetadata.js @@ -0,0 +1,110 @@ + +import { OHIF } from 'meteor/ohif:core'; +import DICOMwebClient from 'dicomweb-client'; +const { DICOMWeb } = OHIF; + +/** + * Creates a URL for a WADO search + * + * @param server + * @param studyInstanceUid + * @returns {string} + */ +function buildUrl(server, studyInstanceUid) { + return server.wadoRoot + '/studies?includefield=all&StudyInstanceUID=' + studyInstanceUid; +} + +function buildInstanceWadoRsUri(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid) { + return `${server.wadoRoot}/studies/${studyInstanceUid}/series/${seriesInstanceUid}/instances/${sopInstanceUid}` +} + +function buildInstanceFrameWadoRsUri(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid, frame) { + const baseWadoRsUri = buildInstanceWadoRsUri(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid); + frame = frame != null || 1; + + return `${baseWadoRsUri}/frames/${frame}` +} +/** + * Parses result data from a QIDO search into Study MetaData + * Returns an object populated with study metadata, including the + * series list. + * + * @param server + * @param studyInstanceUid + * @param resultData + * @returns {{seriesList: Array, patientName: *, patientId: *, accessionNumber: *, studyDate: *, modalities: *, studyDescription: *, imageCount: *, studyInstanceUid: *}} + */ +async function resultDataToStudyMetadata(server, studyInstanceUid, resultData, instancesIn) { + + const seriesList = []; + + if (!resultData.length) { + return; + } + + const anInstance = resultData[0]; + if (!anInstance) { + return; + } + + const studyData = { + seriesList, + patientName: DICOMWeb.getName(anInstance['00100010']), + patientId: DICOMWeb.getString(anInstance['00100020']), + patientAge: DICOMWeb.getNumber(anInstance['00101010']), + patientSize: DICOMWeb.getNumber(anInstance['00101020']), + patientWeight: DICOMWeb.getNumber(anInstance['00101030']), + accessionNumber: DICOMWeb.getString(anInstance['00080050']), + studyDate: DICOMWeb.getString(anInstance['00080020']), + modalities: DICOMWeb.getString(anInstance['00080061']), + studyDescription: DICOMWeb.getString(anInstance['00081030']), + imageCount: DICOMWeb.getString(anInstance['00201208']), + studyInstanceUid: DICOMWeb.getString(anInstance['0020000D']), + institutionName: DICOMWeb.getString(anInstance['00080080']) + }; + await Promise.all(instancesIn.seriesList.map(async function(seriesMap) { + var instance = seriesMap.instances[0]; + var seriesInstanceUid = instance.seriesInstanceUid; + var series = seriesMap[seriesInstanceUid]; + if (!series) { + series = seriesMap; + series.instances = []; + seriesMap[seriesInstanceUid] = series; + seriesList.push(series); + } + const sopInstanceUid = instance.sopInstanceUid; + const wadouri = buildInstanceWadoRsUri(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid); + const baseWadoRsUri = buildInstanceWadoRsUri(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid); + const wadorsuri = buildInstanceFrameWadoRsUri(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid); + + const instanceSummary = instance; + instanceSummary.baseWadoRsUri=baseWadoRsUri; + instanceSummary.wadouri=WADOProxy.convertURL(wadouri, server); + instanceSummary.wadorsuri=WADOProxy.convertURL(wadorsuri, server); + instanceSummary.imageRendering=server.imageRendering; + instanceSummary.thumbnailRendering=server.thumbnailRendering; + series.instances.push(instanceSummary); + })); + return studyData; +} + +/** + * Retrieved Study MetaData from a DICOM server using a WADO call + * @param server + * @param studyInstanceUid + * @returns {Promise} + */ +OHIF.studies.services.QIDO.RetrieveMetadata = async function(server, studyInstanceUid) { + const url = buildUrl(server, studyInstanceUid); + return new Promise((resolve, reject) => { + DICOMWeb.getJSON(url, server.requestOptions).then(result => { + OHIF.studies.services.QIDO.Instances(server, studyInstanceUid).then(instances => { + resultDataToStudyMetadata(server, studyInstanceUid, result, instances).then((study) => { + study.wadoUriRoot = server.wadoUriRoot; + study.studyInstanceUid = studyInstanceUid; + resolve(study); + }, reject) + }, reject); + }, reject); + }); +}; diff --git a/Packages/ohif-studies/imports/both/services/qido/studies.js b/Packages/ohif-studies/imports/both/services/qido/studies.js index 830bd9302..1d1be4501 100644 --- a/Packages/ohif-studies/imports/both/services/qido/studies.js +++ b/Packages/ohif-studies/imports/both/services/qido/studies.js @@ -38,8 +38,8 @@ function dateToString(date) { */ function getQIDOQueryParams(filter, serverSupportsQIDOIncludeField) { const commaSeparatedFields = [ - '00081030', // Study Description - '00080060' //Modality + // this is temp '00081030', // Study Description + // '00080060' //Modality // Add more fields here if you want them in the result ].join(','); diff --git a/Packages/ohif-studies/imports/both/services/wado/retrieveMetadata.js b/Packages/ohif-studies/imports/both/services/wado/retrieveMetadata.js index a4c9f8904..9f0a228a5 100644 --- a/Packages/ohif-studies/imports/both/services/wado/retrieveMetadata.js +++ b/Packages/ohif-studies/imports/both/services/wado/retrieveMetadata.js @@ -214,6 +214,112 @@ function getRadiopharmaceuticalInfo(instance) { }; } +function getRelationshipString (data) { + const relationshipType = DICOMWeb.getString(data['0040A010']); + + switch (relationshipType) { + case 'HAS CONCEPT MOD': + return 'Concept modifier: '; + case 'HAS OBS CONTEXT': + return 'Observation context: '; + default: + return ''; + } +} + +const getNestedObject = (data) => data.Value[0] || {}; + +const getMeaningString = (data) => (data['0040A043'] && `${DICOMWeb.getString(data['0040A043'].Value[0]['00080104'])} = `) || ''; + +function getValueString (data) { + switch (DICOMWeb.getString(data['0040A040'])) { // ValueType + case 'CODE': + const conceptCode = getNestedObject(data['0040A168']); + const conceptCodeValue = DICOMWeb.getString(conceptCode['00080100']); + const conceptCodeMeaning = DICOMWeb.getString(conceptCode['00080104']); + const schemeDesignator = DICOMWeb.getString(conceptCode['00080102']); + return `${conceptCodeMeaning} (${conceptCodeValue}, ${schemeDesignator})`; + + case 'PNAME': + return DICOMWeb.getName(data['0040A123']); + + case 'TEXT': + return DICOMWeb.getString(data['0040A160']); + + case 'UIDREF': + return DICOMWeb.getString(data['0040A124']); + + case 'NUM': + const numValue = DICOMWeb.getString(getNestedObject(data['0040A300'])['0040A30A']); + const codeValue = DICOMWeb.getString(getNestedObject(getNestedObject(data['0040A300'])['004008EA'])['00080100']); + return `${numValue} ${codeValue}`; + } +} + +function constructPlainValue(data) { + const value = getValueString(data); + + if (value) { + return getRelationshipString(data) + getMeaningString(data) + value; + } +} + +function constructContentSequence(data, header) { + if (!data['0040A730'].Value) { + return; + } + + const items = data['0040A730'].Value.map(item => parseContent(item)).filter(item => item); + + if (items.length) { + const result = { + items + }; + + if (header) { + result.header = header; + } + + return result; + } +} + +/** + * Recursively parses content sequence for structured reports + * + * @param instance The instance + * @returns {Array} Series List + */ + +function parseContent(instance) { + if (instance['0040A040']) { // ValueType + if (DICOMWeb.getString(instance['0040A040']) === 'CONTAINER') { + const header = DICOMWeb.getString(getNestedObject(instance['0040A043'])['00080104']); // TODO: check with real data + return constructContentSequence(instance, header); + } + + return constructPlainValue(instance); + } + + if (instance['0040A730']) { //ContentSequence + return constructContentSequence(instance); + } +} + +function getModality(instance) { + const modality = DICOMWeb.getString(instance['00080060']); + return modality || (!!instance['0040A730'] && 'SR') || undefined; // FIXME: dirty, dirty hack, we use +} + +function getContentDateTime(instance) { + const date = DICOMWeb.getString(instance['00080023']); + const time = DICOMWeb.getString(instance['00080033']); + + if (date && time) { + return `${date.substr(0, 4)}-${date.substr(4, 2)}-${date.substr(6, 2)} ${time.substr(0, 2)}:${time.substr(2, 2)}:${time.substr(4, 2)}`; + } +} + /** * Parses result data from a WADO search into Study MetaData * Returns an object populated with study metadata, including the @@ -257,11 +363,12 @@ async function resultDataToStudyMetadata(server, studyInstanceUid, resultData) { await Promise.all(resultData.map(async function(instance) { const seriesInstanceUid = DICOMWeb.getString(instance['0020000E']); let series = seriesMap[seriesInstanceUid]; + const modality = getModality(instance); if (!series) { series = { seriesDescription: DICOMWeb.getString(instance['0008103E']), - modality: DICOMWeb.getString(instance['00080060']), + modality, seriesInstanceUid: seriesInstanceUid, seriesNumber: DICOMWeb.getNumber(instance['00200011']), seriesDate: DICOMWeb.getString(instance['00080021']), @@ -276,11 +383,15 @@ async function resultDataToStudyMetadata(server, studyInstanceUid, resultData) { const wadouri = buildInstanceWadoUrl(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid); const baseWadoRsUri = buildInstanceWadoRsUri(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid); const wadorsuri = buildInstanceFrameWadoRsUri(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid); - const instanceSummary = { + contentSequence: parseContent(instance), + completionFlag: DICOMWeb.getString(instance['0040A491']), + manufacturer: DICOMWeb.getString(instance['00080070']), + verificationFlag: DICOMWeb.getString(instance['0040A493']), + contentDateTime: getContentDateTime(instance), imageType: DICOMWeb.getString(instance['00080008']), sopClassUid: DICOMWeb.getString(instance['00080016']), - modality: DICOMWeb.getString(instance['00080060']), + modality, sopInstanceUid, instanceNumber: DICOMWeb.getNumber(instance['00200013']), imagePositionPatient: DICOMWeb.getString(instance['00200032']), @@ -322,8 +433,8 @@ async function resultDataToStudyMetadata(server, studyInstanceUid, resultData) { contrastBolusAgent: DICOMWeb.getString(instance['00180010']), radiopharmaceuticalInfo: getRadiopharmaceuticalInfo(instance), baseWadoRsUri: baseWadoRsUri, - wadouri: WADOProxy.convertURL(wadouri, server), - wadorsuri: WADOProxy.convertURL(wadorsuri, server), + wadouri: wadouri, + wadorsuri: wadorsuri, imageRendering: server.imageRendering, thumbnailRendering: server.thumbnailRendering }; @@ -351,7 +462,6 @@ async function resultDataToStudyMetadata(server, studyInstanceUid, resultData) { series.instances.push(instanceSummary); })); - return studyData; } diff --git a/Packages/ohif-studies/imports/client/lib/retrieveStudyMetadata.js b/Packages/ohif-studies/imports/client/lib/retrieveStudyMetadata.js index 79c99afdb..b8a503c93 100644 --- a/Packages/ohif-studies/imports/client/lib/retrieveStudyMetadata.js +++ b/Packages/ohif-studies/imports/client/lib/retrieveStudyMetadata.js @@ -49,9 +49,15 @@ OHIF.studies.retrieveStudyMetadata = (studyInstanceUid, seriesInstanceUids) => { // If no study metadata is in the cache variable, we need to retrieve it from // the server with a call. if (server.type === 'dicomWeb' && server.requestOptions.requestFromBrowser === true) { - OHIF.studies.services.WADO.RetrieveMetadata(server, studyInstanceUid).then(function (data) { - resolve(data) - }, reject); + if(server.metadataSource ==='qido') { + OHIF.studies.services.QIDO.RetrieveMetadata(server, studyInstanceUid).then(function (data) { + resolve(data) + }, reject); + } else { + OHIF.studies.services.WADO.RetrieveMetadata(server, studyInstanceUid).then(function (data) { + resolve(data) + }, reject); + } } else { Meteor.call('GetStudyMetadata', studyInstanceUid, function (error, study) { OHIF.log.timeEnd(timingKey); diff --git a/Packages/ohif-studies/imports/client/lib/searchStudies.js b/Packages/ohif-studies/imports/client/lib/searchStudies.js index 8fe6b6949..723109a02 100644 --- a/Packages/ohif-studies/imports/client/lib/searchStudies.js +++ b/Packages/ohif-studies/imports/client/lib/searchStudies.js @@ -10,13 +10,12 @@ const studySearchPromises = new Map(); * @returns {Promise} resolved with an array of studies information or rejected with an error */ OHIF.studies.searchStudies = filter => { - const promiseKey = JSON.stringify(filter); + const server = OHIF.servers.getCurrentServer(); + const promiseKey = server.qidoRoot + " - " + JSON.stringify(filter); if (studySearchPromises.has(promiseKey)) { return studySearchPromises.get(promiseKey); } else { const promise = new Promise((resolve, reject) => { - const server = OHIF.servers.getCurrentServer(); - if (server.type === 'dicomWeb' && server.requestOptions.requestFromBrowser === true) { OHIF.studies.services.QIDO.Studies(server, filter).then(resolve, reject); } else { diff --git a/Packages/ohif-studies/imports/server/methods/getStudyMetadata.js b/Packages/ohif-studies/imports/server/methods/getStudyMetadata.js index b97d04316..e22323453 100644 --- a/Packages/ohif-studies/imports/server/methods/getStudyMetadata.js +++ b/Packages/ohif-studies/imports/server/methods/getStudyMetadata.js @@ -12,14 +12,18 @@ Meteor.methods({ // Get the server data. This is user-defined in the config.json files or through servers // configuration modal const server = OHIF.servers.getCurrentServer(); - if (!server) { throw new Meteor.Error('improper-server-config', 'No properly configured server was available over DICOMWeb or DIMSE.'); } try { if (server.type === 'dicomWeb') { - return OHIF.studies.services.WADO.RetrieveMetadata(server, studyInstanceUid); + if(server.metadataSource ==='qido') { + return OHIF.studies.services.QIDO.RetrieveMetadata(server, studyInstanceUid); + } + else{ + return OHIF.studies.services.WADO.RetrieveMetadata(server, studyInstanceUid); + } } else if (server.type === 'dimse') { return OHIF.studies.services.DIMSE.RetrieveMetadata(studyInstanceUid); } diff --git a/Packages/ohif-study-list/client/components/studylist/studylistResult/studylistResult.html b/Packages/ohif-study-list/client/components/studylist/studylistResult/studylistResult.html index c6ebb2f6b..c5c2600f7 100644 --- a/Packages/ohif-study-list/client/components/studylist/studylistResult/studylistResult.html +++ b/Packages/ohif-study-list/client/components/studylist/studylistResult/studylistResult.html @@ -1,6 +1,9 @@ diff --git a/Packages/ohif-study-list/client/components/studylist/studylistToolbar/studylistToolbar.js b/Packages/ohif-study-list/client/components/studylist/studylistToolbar/studylistToolbar.js index a68b49885..dc0ee6f73 100644 --- a/Packages/ohif-study-list/client/components/studylist/studylistToolbar/studylistToolbar.js +++ b/Packages/ohif-study-list/client/components/studylist/studylistToolbar/studylistToolbar.js @@ -1,5 +1,6 @@ import { OHIF } from 'meteor/ohif:core'; import { Template } from 'meteor/templating'; +import { Router } from 'meteor/clinical:router'; import { Meteor } from 'meteor/meteor'; import { Session } from 'meteor/session'; import { $ } from 'meteor/jquery'; @@ -25,6 +26,21 @@ Template.studylistToolbar.events({ 'click .js-import-files'(event) { // Reset file input $(event.currentTarget).val(''); + }, + + 'click .uploadStudiesBtn'() { + OHIF.gcloud.showUploadStudiesDialog(); + }, + + 'click .changeDicomStoreBtn'() { + OHIF.gcloud.showDicomStorePicker({canClose:true}).then(config => { + if (!config) + return; + Session.set("IsStudyListReady", false); + OHIF.studylist.collections.Studies.remove({}); + OHIF.servers.applyCloudServerConfig(config); + setImmediate(() => Session.set("IsStudyListReady", true)); + }); } }); @@ -32,5 +48,11 @@ Template.studylistToolbar.helpers({ importSupported() { const importSupported = Session.get('importSupported'); return (importSupported && OHIF.uiSettings.studyListFunctionsEnabled); + }, + uploadSupported() { + return !!(OHIF.gcloud && OHIF.gcloud.isEnabled()); + }, + changeDicomStoreSupported() { + return !!(OHIF.gcloud && OHIF.gcloud.isEnabled()); } }); diff --git a/Packages/ohif-study-list/client/components/studylist/studylistToolbar/studylistToolbar.styl b/Packages/ohif-study-list/client/components/studylist/studylistToolbar/studylistToolbar.styl index 23944cbad..6c464b475 100644 --- a/Packages/ohif-study-list/client/components/studylist/studylistToolbar/studylistToolbar.styl +++ b/Packages/ohif-study-list/client/components/studylist/studylistToolbar/studylistToolbar.styl @@ -25,3 +25,6 @@ overflow: hidden position: absolute z-index: -1 + .uploadStudiesBtn + margin 0 20px + diff --git a/Packages/ohif-user-oidc/imports/client/oidcUserManager.js b/Packages/ohif-user-oidc/imports/client/oidcUserManager.js index e3f73887d..6f409cb18 100644 --- a/Packages/ohif-user-oidc/imports/client/oidcUserManager.js +++ b/Packages/ohif-user-oidc/imports/client/oidcUserManager.js @@ -16,20 +16,29 @@ const oidcClient = oidc[0]; const redirect_uri = Meteor.absoluteUrl(oidcClient.authRedirectUri); const silent_redirect_uri = Meteor.absoluteUrl('/packages/ohif_user-oidc/public/silent-refresh.html'); +function httpGetSync(theUrl) +{ + // TODO: consider making it async somehow + var xmlHttp = new XMLHttpRequest(); + xmlHttp.open( "GET", theUrl, false ); // false for synchronous request + xmlHttp.send( null ); + return xmlHttp.responseText; +} +const id = oidcClient.clientId || httpGetSync( Meteor.absoluteUrl('/gcloud-client-id')); const settings = { authority: oidcClient.authServerUrl, - client_id: oidcClient.clientId, + client_id: id, redirect_uri, silent_redirect_uri, post_logout_redirect_uri: Meteor.absoluteUrl(oidcClient.postLogoutRedirectUri), response_type: oidcClient.responseType || 'id_token token', - scope: oidc.scope || 'email profile openid', // Note: Request must have scope 'openid' to be considered an OpenID Connect request + scope: oidcClient.scope || 'email profile openid https://www.googleapis.com/auth/cloud-platform.read-only https://www.googleapis.com/auth/cloud-healthcare', // seems like scope is not loaded from settings Note: Request must have scope 'openid' to be considered an OpenID Connect request automaticSilentRenew: true, revokeAccessTokenOnSignout: true, }; -const itemName = `oidc.user:${oidcClient.authServerUrl}:${oidcClient.clientId}`; +const itemName = `oidc.user:${oidcClient.authServerUrl}:${id}`; function getTokenFromStorage() { const userDataJSON = sessionStorage.getItem(itemName); @@ -46,12 +55,34 @@ OHIF.user.getAccessToken = function oidcGetAccessToken() { if (!OHIF.user.userLoggedIn) { throw new Error('User is not logged in.'); } - return getTokenFromStorage(); }; +OHIF.user.getOidcStorageKey = function () { + return itemName; +} + +OHIF.user.getOidcRedirectUri = function () { + return oidcClient.authRedirectUri; +} + +OHIF.user.login = function oidcLogin() { + oidcUserManager.signinRedirect({redirect_uri}); +} + OHIF.user.logout = function oidcLogout() { - oidcUserManager.signoutRedirect(); + const config = JSON.parse(sessionStorage.getItem(itemName) || null); + if (oidcClient.revokeUrl && config && config.access_token) { + // OIDC from Google doesn't support signing out for some reason + // so we revoke the token manually + sessionStorage.removeItem(itemName); + const revokeUrl = oidcClient.revokeUrl + config.access_token; + fetch(revokeUrl).catch(()=>{}).then(() => location.assign(oidcClient.postLogoutRedirectUri || '/')); + + } else { + // simple oidc signout behavior + oidcUserManager.signoutRedirect(); + } } OHIF.user.userLoggedIn = () => !!getTokenFromStorage(); @@ -70,7 +101,7 @@ function handleSilentRenewError(error) { } oidcUserManager.events.addSilentRenewError(handleSilentRenewError); - +oidcUserManager.startSilentRenew(); oidcUserManager.events.addAccessTokenExpired(function(){ OHIF.user.logout(); }); diff --git a/Packages/ohif-user-oidc/imports/client/routes.js b/Packages/ohif-user-oidc/imports/client/routes.js index 19652efba..7c702cb5d 100644 --- a/Packages/ohif-user-oidc/imports/client/routes.js +++ b/Packages/ohif-user-oidc/imports/client/routes.js @@ -66,14 +66,21 @@ function urlHasSignInResponse() { } Router.onRun(function() { - const next = this.next; - const redirect_uri = Meteor.absoluteUrl(this.request.url); + const isSignedIn = OHIF.user.userLoggedIn() || OHIF.demoMode && OHIF.demoMode.userLoggedIn(); + const isDemoPage = OHIF.demoMode && this.url === "/demo-signin"; - if (OHIF.user.userLoggedIn()) { - next() - } else if (urlHasSignInResponse() === true) { - processSignInResponse().then(next); - } else { + if (isSignedIn || isDemoPage) + this.next(); + else if (urlHasSignInResponse() === true) + processSignInResponse().then(this.next); + else { + const redirect_uri = Meteor.absoluteUrl(OHIF.user.getOidcRedirectUri()); signIn({ redirect_uri }); } }); + +Router.route(OHIF.user.getOidcRedirectUri(), function() { + Router.go('/', {}, { replaceState: true }); +}, { name: 'oidc_redirect' }); + + diff --git a/Packages/ohif-viewerbase/assets/icons.svg b/Packages/ohif-viewerbase/assets/icons.svg index b15421f67..9b935e9bc 100644 --- a/Packages/ohif-viewerbase/assets/icons.svg +++ b/Packages/ohif-viewerbase/assets/icons.svg @@ -234,4 +234,41 @@ Password - + + Structured Report + + + + + + + + + + + Presentation State + + + + + + + + + + + + + + Google icon + + + + + + + + + + + \ No newline at end of file diff --git a/Packages/ohif-viewerbase/client/lib/getWADORSImageUrl.js b/Packages/ohif-viewerbase/client/lib/getWADORSImageUrl.js index 4f7b82dfb..bc40b549a 100644 --- a/Packages/ohif-viewerbase/client/lib/getWADORSImageUrl.js +++ b/Packages/ohif-viewerbase/client/lib/getWADORSImageUrl.js @@ -9,7 +9,7 @@ export function getWADORSImageUrl(instance, frame) { frame = (frame || 0) + 1; // Replaces /frame/1 by /frame/{frame} - wadorsuri = wadorsuri.replace(/(%2Fframes%2F)(\d+)/, `$1${frame}`); + wadorsuri = wadorsuri.replace(/(\/frames\/)(\d+)/, `$1${frame}`); return wadorsuri; } diff --git a/Packages/ohif-viewerbase/client/lib/helpers/formatPN.js b/Packages/ohif-viewerbase/client/lib/helpers/formatPN.js index e7ee7c7b9..b32378114 100644 --- a/Packages/ohif-viewerbase/client/lib/helpers/formatPN.js +++ b/Packages/ohif-viewerbase/client/lib/helpers/formatPN.js @@ -9,10 +9,28 @@ const formatPN = context => { // Convert the first ^ to a ', '. String.replace() only affects // the first appearance of the character. - const commaBetweenFirstAndLast = context.replace('^', ', '); + //const commaBetweenFirstAndLast = context.replace('^', ', '); // Replace any remaining '^' characters with spaces - const cleaned = commaBetweenFirstAndLast.replace(/\^/g, ' '); + //const cleaned = commaBetweenFirstAndLast.replace(/\^/g, ' '); + + // for cloud healthcare replace all ^ with ', ' + const cleaned = context.replace(/\^/g, ', '); + + // Trim any extraneous whitespace + return cleaned.trim(); +}; + +/** + * Formats a patient name for display purposes + */ +const reverseFormatPN = context => { + if (!context) { + context; + } + + // Replace any remaining '^' characters with spaces + const cleaned = context.replace(/, /g, '^'); // Trim any extraneous whitespace return cleaned.trim(); @@ -27,4 +45,8 @@ if (!Blaze._getGlobalHelper('formatPN')) { Blaze.registerHelper('formatPN', formatPN); } -export { formatPN }; +if (!Blaze._getGlobalHelper('reverseFormatPN')) { + Blaze.registerHelper('reverseFormatPN', reverseFormatPN); +} + +export { formatPN, reverseFormatPN }; diff --git a/Packages/ohif-viewerbase/client/lib/helpers/index.js b/Packages/ohif-viewerbase/client/lib/helpers/index.js index 867832cc5..f2ef53ea8 100644 --- a/Packages/ohif-viewerbase/client/lib/helpers/index.js +++ b/Packages/ohif-viewerbase/client/lib/helpers/index.js @@ -3,7 +3,7 @@ */ import { isTouchDevice } from './isTouchDevice'; -import { formatPN } from './formatPN'; +import { formatPN, reverseFormatPN } from './formatPN'; import { formatDA } from './formatDA'; import { formatTM } from './formatTM'; @@ -33,7 +33,8 @@ const helpers = { isTouchDevice, formatPN, formatDA, - formatTM + formatTM, + reverseFormatPN }; export { helpers }; diff --git a/Packages/ohif-viewerbase/client/lib/hotkeyUtils.js b/Packages/ohif-viewerbase/client/lib/hotkeyUtils.js index fdfd08157..e31c034a0 100644 --- a/Packages/ohif-viewerbase/client/lib/hotkeyUtils.js +++ b/Packages/ohif-viewerbase/client/lib/hotkeyUtils.js @@ -225,6 +225,16 @@ Meteor.startup(function() { name: 'Show/Hide Download Dialog', action: viewportUtils.toggleDownloadDialog, disabled: () => !viewportUtils.isDownloadEnabled() + }, + ps: { + name: 'Show/Hide Presentation State', + action: () => Session.set('MeasurementsReady', !Session.get('MeasurementsReady')), + disabled: () => false + }, + sr: { + name: 'Show/Hide Structured Report', + action: () => OHIF.ui.showDialog('structuredReportModal'), + disabled: () => false } }, true); diff --git a/Packages/ohif-viewerbase/client/lib/viewportUtils.js b/Packages/ohif-viewerbase/client/lib/viewportUtils.js index 86e719da3..f0507925e 100644 --- a/Packages/ohif-viewerbase/client/lib/viewportUtils.js +++ b/Packages/ohif-viewerbase/client/lib/viewportUtils.js @@ -167,6 +167,18 @@ const clearTools = () => { toolStateManager.clear(element); cornerstone.updateImage(element); }; +const hideTools = () => { + const element = getActiveViewportElement(); + const toolStateManager = cornerstoneTools.globalImageIdSpecificToolStateManager; + toolStateManager.hide(element); + cornerstone.updateImage(element); +}; +const unhideTools = () => { + const element = getActiveViewportElement(); + const toolStateManager = cornerstoneTools.globalImageIdSpecificToolStateManager; + toolStateManager.unhide(element); + cornerstone.updateImage(element); +}; const linkStackScroll = () => { const synchronizer = OHIF.viewer.stackImagePositionOffsetSynchronizer; @@ -383,6 +395,8 @@ const viewportUtils = { flipH, resetViewport, clearTools, + hideTools, + unhideTools, linkStackScroll, toggleDialog, toggleCinePlay, diff --git a/clientImage/Dockerfile b/clientImage/Dockerfile new file mode 100644 index 000000000..9b925ad81 --- /dev/null +++ b/clientImage/Dockerfile @@ -0,0 +1,3 @@ +FROM nginx:stable-alpine-perl +COPY nginx.conf /etc/nginx/nginx.conf +COPY build /usr/share/nginx/html/ diff --git a/clientImage/howTo.md b/clientImage/howTo.md new file mode 100644 index 000000000..918286ced --- /dev/null +++ b/clientImage/howTo.md @@ -0,0 +1,17 @@ +## environment +[install meteor](https://www.meteor.com/install) +## download +```bash +git clone https://github.com/quantumsoftgroup/ViewersClone.git +cd ViewersClone +git checkout dev +``` +## build +```bash +cd OHIFViewer/ +METEOR_PACKAGE_DIRS="../Packages" meteor-build-client ../clientImage/build -s ../config/oidc.json +cd ../clientImage +docker build -t ohif-ghc . +docker run --env CLIENT_ID={$someID}.apps.googleusercontent.com --publish 3000:80 ohif-ghc + +``` diff --git a/clientImage/nginx.conf b/clientImage/nginx.conf new file mode 100644 index 000000000..db5518a7f --- /dev/null +++ b/clientImage/nginx.conf @@ -0,0 +1,44 @@ +worker_processes 1; +load_module modules/ngx_http_perl_module.so; +events { + worker_connections 1024; +} +env CLIENT_ID; +http { + include mime.types; + default_type application/octet-stream; + perl_set $client_id 'sub { return $ENV{"CLIENT_ID"}; }'; + + sendfile on; + keepalive_timeout 65; + + access_log off; + error_log off; + + server { + listen 80; + root /usr/share/nginx/html; + + location / { + try_files $uri @index; + } + + location @index { + add_header Cache-Control no-cache; + expires 0; + try_files /index.html =404; + } + + location ~ /(favicon.ico|favicon.png|robots.txt)$ { + expires 1y; + access_log off; + log_not_found off; + } + + location /gcloud-client-id { + return 200 '${client_id}'; + } + } +} + + diff --git a/config/oidc-demo.json b/config/oidc-demo.json new file mode 100644 index 000000000..710cdc771 --- /dev/null +++ b/config/oidc-demo.json @@ -0,0 +1,53 @@ +{ + "public": { + "clientOnly": true, + "googleCloud": true, + "ui": { + "studyListFunctionsEnabled": true, + "studyListDateFilterNumDays": false + }, + "servers": { + "dicomWeb": [{ + "name": "dcm4chee-oidc-Client", + "qidoSupportsIncludeField": false, + "imageRendering": "wadors", + "thumbnailRendering": "wadors", + "metadataSource": "wado", + "requestOptions": { + "requestFromBrowser": true, + "logRequests": true, + "logResponses": false, + "logTiming": true + } + }, + { + "name": "demo-dcm4chee", + "wadoUriRoot": "https://dcm4che.ohif.club/dcm4chee-arc/aets/DCM4CHEE/wado", + "qidoRoot": "https://dcm4che.ohif.club/dcm4chee-arc/aets/DCM4CHEE/rs", + "wadoRoot": "https://dcm4che.ohif.club/dcm4chee-arc/aets/DCM4CHEE/rs", + "qidoSupportsIncludeField": false, + "imageRendering": "wadouri", + "thumbnailRendering": "wadors", + "requestOptions": { + "auth": "cloud:healthcare", + "requestFromBrowser": true, + "logRequests": true, + "logResponses": false, + "logTiming": true + } + }] + }, + "custom": { + "oidc": [{ + "authServerUrl": "https://accounts.google.com", + "authRedirectUri": "/_oauth/google", + "postLogoutRedirectUri": "/", + "clientId": "570420945968-pmtd0sjm7mmf3i5m7ld09aos1op3qva1.apps.googleusercontent.com", + "scope": "email profile openid https://www.googleapis.com/auth/cloud-platform.read-only https://www.googleapis.com/auth/cloud-healthcare", + "revokeUrl": "https://accounts.google.com/o/oauth2/revoke?token=" + }] + }, + "userAuthenticationRoutesEnabled": true, + "demoMode": true + } +} \ No newline at end of file diff --git a/config/oidc-dev.json b/config/oidc-dev.json new file mode 100644 index 000000000..710cdc771 --- /dev/null +++ b/config/oidc-dev.json @@ -0,0 +1,53 @@ +{ + "public": { + "clientOnly": true, + "googleCloud": true, + "ui": { + "studyListFunctionsEnabled": true, + "studyListDateFilterNumDays": false + }, + "servers": { + "dicomWeb": [{ + "name": "dcm4chee-oidc-Client", + "qidoSupportsIncludeField": false, + "imageRendering": "wadors", + "thumbnailRendering": "wadors", + "metadataSource": "wado", + "requestOptions": { + "requestFromBrowser": true, + "logRequests": true, + "logResponses": false, + "logTiming": true + } + }, + { + "name": "demo-dcm4chee", + "wadoUriRoot": "https://dcm4che.ohif.club/dcm4chee-arc/aets/DCM4CHEE/wado", + "qidoRoot": "https://dcm4che.ohif.club/dcm4chee-arc/aets/DCM4CHEE/rs", + "wadoRoot": "https://dcm4che.ohif.club/dcm4chee-arc/aets/DCM4CHEE/rs", + "qidoSupportsIncludeField": false, + "imageRendering": "wadouri", + "thumbnailRendering": "wadors", + "requestOptions": { + "auth": "cloud:healthcare", + "requestFromBrowser": true, + "logRequests": true, + "logResponses": false, + "logTiming": true + } + }] + }, + "custom": { + "oidc": [{ + "authServerUrl": "https://accounts.google.com", + "authRedirectUri": "/_oauth/google", + "postLogoutRedirectUri": "/", + "clientId": "570420945968-pmtd0sjm7mmf3i5m7ld09aos1op3qva1.apps.googleusercontent.com", + "scope": "email profile openid https://www.googleapis.com/auth/cloud-platform.read-only https://www.googleapis.com/auth/cloud-healthcare", + "revokeUrl": "https://accounts.google.com/o/oauth2/revoke?token=" + }] + }, + "userAuthenticationRoutesEnabled": true, + "demoMode": true + } +} \ No newline at end of file diff --git a/config/oidc.json b/config/oidc.json new file mode 100644 index 000000000..53ad5b5da --- /dev/null +++ b/config/oidc.json @@ -0,0 +1,36 @@ +{ + "public": { + "clientOnly": true, + "googleCloud": true, + "ui": { + "studyListFunctionsEnabled": true, + "studyListDateFilterNumDays": false + }, + "servers": { + "dicomWeb": [{ + "name": "dcm4chee-oidc-Client", + "qidoSupportsIncludeField": false, + "imageRendering": "wadors", + "thumbnailRendering": "wadors", + "metadataSource": "wado", + "requestOptions": { + "requestFromBrowser": true, + "logRequests": true, + "logResponses": false, + "logTiming": true + } + }] + }, + "custom": { + "oidc": [{ + "authServerUrl": "https://accounts.google.com", + "authRedirectUri": "/_oauth/google", + "postLogoutRedirectUri": "/", + "scope": "email profile openid https://www.googleapis.com/auth/cloud-platform.read-only https://www.googleapis.com/auth/cloud-healthcare", + "revokeUrl": "https://accounts.google.com/o/oauth2/revoke?token=" + }] + }, + "userAuthenticationRoutesEnabled": true, + "demoMode": false + } +} \ No newline at end of file diff --git a/config/orthancDIMSE.json b/config/orthancDIMSE.json index eeaafd64f..cd4acb29d 100644 --- a/config/orthancDIMSE.json +++ b/config/orthancDIMSE.json @@ -20,7 +20,7 @@ { "host": "0.0.0.0", "port": 11112, - "aeTitle": "OHIFDCM", + "aeTitle": "ORTHANC", "default": true, "server": true } diff --git a/ghc-dockerfile b/ghc-dockerfile new file mode 100644 index 000000000..a3d112119 --- /dev/null +++ b/ghc-dockerfile @@ -0,0 +1,36 @@ +# First stage of multi-stage build +# This stage is named 'builder' +# The data for this intermediary image is not included +# in the final image. +FROM node:8.10.0-slim as builder + +RUN apt-get update && apt-get install -y \ + curl \ + g++ \ + git \ + python \ + build-essential + +RUN curl https://install.meteor.com/ | sh +RUN npm install -g meteor-build-client-fixed2 +# Create a non-root user +RUN useradd -ms /bin/bash user +USER user +RUN mkdir /home/user/Viewers +COPY OHIFViewer/package.json /home/user/Viewers/OHIFViewer/ +ADD --chown=user:user . /home/user/Viewers + +WORKDIR /home/user/Viewers/OHIFViewer + +ENV METEOR_PACKAGE_DIRS=../Packages +ENV METEOR_PROFILE=1 +ARG OHIF_CONFIG_FILE=../config/oidc.json +RUN meteor npm install +RUN meteor-build-client-fixed2 /home/user/app/ -s $OHIF_CONFIG_FILE +RUN ls /home/user/app/ + +# Second stage of multi-stage build +FROM nginx:stable-alpine-perl +COPY ./clientImage/nginx.conf /etc/nginx/nginx.conf +COPY --from=builder /home/user/app /usr/share/nginx/html/ + diff --git a/package-lock.json b/package-lock.json index 5688b2b54..851cb319a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,445 +1,5 @@ { "name": "ohif-viewers-meta", "version": "0.0.1", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "bcrypt": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-2.0.1.tgz", - "integrity": "sha512-DwB7WgJPdskbR+9Y3OTJtwRq09Lmm7Na6b+4ewvXjkD0nfNRi1OozxljHm5ETlDCBq9DTy04lQz+rj+T2ztIJg==", - "requires": { - "nan": "2.10.0", - "node-pre-gyp": "0.9.1" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true - }, - "are-we-there-yet": { - "version": "1.1.4", - "bundled": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.0.1", - "bundled": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true - }, - "debug": { - "version": "2.6.9", - "bundled": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.4.2", - "bundled": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.2", - "bundled": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true - }, - "iconv-lite": { - "version": "0.4.21", - "bundled": true, - "requires": { - "safer-buffer": "^2.1.0" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true - }, - "ini": { - "version": "1.3.5", - "bundled": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true - }, - "minipass": { - "version": "2.2.4", - "bundled": true, - "requires": { - "safe-buffer": "^5.1.1", - "yallist": "^3.0.0" - }, - "dependencies": { - "yallist": { - "version": "3.0.2", - "bundled": true - } - } - }, - "minizlib": { - "version": "1.1.0", - "bundled": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true - }, - "needle": { - "version": "2.2.0", - "bundled": true, - "requires": { - "debug": "^2.1.2", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.9.1", - "bundled": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.0", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.1.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.3", - "bundled": true - }, - "npm-packlist": { - "version": "1.1.10", - "bundled": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true - }, - "rc": { - "version": "1.2.6", - "bundled": true, - "requires": { - "deep-extend": "~0.4.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true - } - } - }, - "readable-stream": { - "version": "2.3.5", - "bundled": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.6.2", - "bundled": true, - "requires": { - "glob": "^7.0.5" - } - }, - "safe-buffer": { - "version": "5.1.1", - "bundled": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true - }, - "sax": { - "version": "1.2.4", - "bundled": true - }, - "semver": { - "version": "5.5.0", - "bundled": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.0.3", - "bundled": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true - }, - "tar": { - "version": "4.4.1", - "bundled": true, - "requires": { - "chownr": "^1.0.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.2.4", - "minizlib": "^1.1.0", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.1", - "yallist": "^3.0.2" - }, - "dependencies": { - "yallist": { - "version": "3.0.2", - "bundled": true - } - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true - }, - "wide-align": { - "version": "1.1.2", - "bundled": true, - "requires": { - "string-width": "^1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true - } - } - }, - "gitbook-plugin-edit-link": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/gitbook-plugin-edit-link/-/gitbook-plugin-edit-link-2.0.2.tgz", - "integrity": "sha1-2PzZJ+ztgeemYqctWdtgnq/X5y8=" - }, - "gitbook-plugin-github": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/gitbook-plugin-github/-/gitbook-plugin-github-3.0.0.tgz", - "integrity": "sha1-Z0V9+YpX6o75slGLiDQNs3ClMXs=" - }, - "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" - } - } + "lockfileVersion": 1 } From 46828dc5cec751073c34b0e0bfe99b7833200b51 Mon Sep 17 00:00:00 2001 From: Erik Ziegler Date: Fri, 22 Mar 2019 16:16:02 +0100 Subject: [PATCH 58/66] fix(google-cloud): Minor cleanup to prevent Google Cloud packages from being added unnecessarily --- .../client/components/ohifViewer/ohifViewer.js | 9 +++++---- Packages/ohif-demo-mode/client/index.js | 2 -- .../client/components/demoSignin/demoSignin.html | 0 .../client/components/demoSignin/demoSignin.js | 0 .../client/components/demoSignin/demoSignin.styl | 0 .../{ => imports}/client/components/index.js | 0 .../{ => imports}/client/demoModeMediator.js | 0 Packages/ohif-demo-mode/imports/client/index.js | 7 +++++++ .../ohif-demo-mode/{ => imports}/client/routes.js | 0 Packages/ohif-demo-mode/main.js | 12 +++--------- Packages/ohif-demo-mode/package.js | 1 - Packages/ohif-google-cloud/client/main.js | 6 ++++++ .../client/components/dialogs/gcloudDialog.html | 0 .../client/components/dialogs/gcloudDialog.js | 0 .../client/components/dialogs/gcloudDialog.styl | 0 .../dicomStorePicker/dicomStorePicker.html | 0 .../dicomStorePicker/dicomStorePicker.js | 0 .../dicomStorePicker/dicomStorePicker.styl | 0 .../{ => imports}/client/components/index.js | 0 .../uploadStudiesDialog/uploadStudiesDialog.html | 0 .../uploadStudiesDialog/uploadStudiesDialog.js | 0 .../uploadStudiesDialog/uploadStudiesDialog.styl | 0 .../{ => imports}/client/index.js | 0 Packages/ohif-google-cloud/imports/index.js | 8 ++++++++ .../{ => imports}/lib/GCloudAdapter.js | 6 +++++- .../ohif-google-cloud/{ => imports}/lib/utils.js | 5 ++--- Packages/ohif-google-cloud/main.js | 9 --------- Packages/ohif-google-cloud/package.js | 7 ++----- Packages/ohif-servers/client/startup.js | 3 +++ .../imports/server/methods/getStudyMetadata.js | 3 +-- .../studylist/studylistResult/studylistResult.js | 15 ++++++--------- 31 files changed, 48 insertions(+), 45 deletions(-) delete mode 100644 Packages/ohif-demo-mode/client/index.js rename Packages/ohif-demo-mode/{ => imports}/client/components/demoSignin/demoSignin.html (100%) rename Packages/ohif-demo-mode/{ => imports}/client/components/demoSignin/demoSignin.js (100%) rename Packages/ohif-demo-mode/{ => imports}/client/components/demoSignin/demoSignin.styl (100%) rename Packages/ohif-demo-mode/{ => imports}/client/components/index.js (100%) rename Packages/ohif-demo-mode/{ => imports}/client/demoModeMediator.js (100%) create mode 100644 Packages/ohif-demo-mode/imports/client/index.js rename Packages/ohif-demo-mode/{ => imports}/client/routes.js (100%) create mode 100644 Packages/ohif-google-cloud/client/main.js rename Packages/ohif-google-cloud/{ => imports}/client/components/dialogs/gcloudDialog.html (100%) rename Packages/ohif-google-cloud/{ => imports}/client/components/dialogs/gcloudDialog.js (100%) rename Packages/ohif-google-cloud/{ => imports}/client/components/dialogs/gcloudDialog.styl (100%) rename Packages/ohif-google-cloud/{ => imports}/client/components/dicomStorePicker/dicomStorePicker.html (100%) rename Packages/ohif-google-cloud/{ => imports}/client/components/dicomStorePicker/dicomStorePicker.js (100%) rename Packages/ohif-google-cloud/{ => imports}/client/components/dicomStorePicker/dicomStorePicker.styl (100%) rename Packages/ohif-google-cloud/{ => imports}/client/components/index.js (100%) rename Packages/ohif-google-cloud/{ => imports}/client/components/uploadStudiesDialog/uploadStudiesDialog.html (100%) rename Packages/ohif-google-cloud/{ => imports}/client/components/uploadStudiesDialog/uploadStudiesDialog.js (100%) rename Packages/ohif-google-cloud/{ => imports}/client/components/uploadStudiesDialog/uploadStudiesDialog.styl (100%) rename Packages/ohif-google-cloud/{ => imports}/client/index.js (100%) create mode 100644 Packages/ohif-google-cloud/imports/index.js rename Packages/ohif-google-cloud/{ => imports}/lib/GCloudAdapter.js (90%) rename Packages/ohif-google-cloud/{ => imports}/lib/utils.js (83%) delete mode 100644 Packages/ohif-google-cloud/main.js diff --git a/OHIFViewer/client/components/ohifViewer/ohifViewer.js b/OHIFViewer/client/components/ohifViewer/ohifViewer.js index ce9eb71e5..4e8d7e961 100644 --- a/OHIFViewer/client/components/ohifViewer/ohifViewer.js +++ b/OHIFViewer/client/components/ohifViewer/ohifViewer.js @@ -61,10 +61,11 @@ Template.ohifViewer.onCreated(() => { Session.set('ViewerOpened', isViewer); }); - if (OHIF.demoMode && OHIF.demoMode.userLoggedIn()) - OHIF.demoMode.setDemoServerConfig(); - else if (OHIF.gcloud && OHIF.gcloud.isEnabled()) { + if (OHIF.demoMode && OHIF.demoMode.userLoggedIn()) { + OHIF.demoMode.setDemoServerConfig(); + } else if (OHIF.gcloud && OHIF.gcloud.isEnabled()) { const server = OHIF.servers.getCurrentServer(); + if (!server || !server.isCloud) { Session.set("IsStudyListReady", false); OHIF.gcloud.showDicomStorePicker({canClose: OHIF.demoMode}).then(config => { @@ -94,7 +95,7 @@ Template.ohifViewer.events({ } } }, - + }); Template.ohifViewer.helpers({ diff --git a/Packages/ohif-demo-mode/client/index.js b/Packages/ohif-demo-mode/client/index.js deleted file mode 100644 index b561f9934..000000000 --- a/Packages/ohif-demo-mode/client/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import './components'; -import './routes.js'; diff --git a/Packages/ohif-demo-mode/client/components/demoSignin/demoSignin.html b/Packages/ohif-demo-mode/imports/client/components/demoSignin/demoSignin.html similarity index 100% rename from Packages/ohif-demo-mode/client/components/demoSignin/demoSignin.html rename to Packages/ohif-demo-mode/imports/client/components/demoSignin/demoSignin.html diff --git a/Packages/ohif-demo-mode/client/components/demoSignin/demoSignin.js b/Packages/ohif-demo-mode/imports/client/components/demoSignin/demoSignin.js similarity index 100% rename from Packages/ohif-demo-mode/client/components/demoSignin/demoSignin.js rename to Packages/ohif-demo-mode/imports/client/components/demoSignin/demoSignin.js diff --git a/Packages/ohif-demo-mode/client/components/demoSignin/demoSignin.styl b/Packages/ohif-demo-mode/imports/client/components/demoSignin/demoSignin.styl similarity index 100% rename from Packages/ohif-demo-mode/client/components/demoSignin/demoSignin.styl rename to Packages/ohif-demo-mode/imports/client/components/demoSignin/demoSignin.styl diff --git a/Packages/ohif-demo-mode/client/components/index.js b/Packages/ohif-demo-mode/imports/client/components/index.js similarity index 100% rename from Packages/ohif-demo-mode/client/components/index.js rename to Packages/ohif-demo-mode/imports/client/components/index.js diff --git a/Packages/ohif-demo-mode/client/demoModeMediator.js b/Packages/ohif-demo-mode/imports/client/demoModeMediator.js similarity index 100% rename from Packages/ohif-demo-mode/client/demoModeMediator.js rename to Packages/ohif-demo-mode/imports/client/demoModeMediator.js diff --git a/Packages/ohif-demo-mode/imports/client/index.js b/Packages/ohif-demo-mode/imports/client/index.js new file mode 100644 index 000000000..0dc08b954 --- /dev/null +++ b/Packages/ohif-demo-mode/imports/client/index.js @@ -0,0 +1,7 @@ +import { OHIF } from 'meteor/ohif:core'; +import devModeMediator from './demoModeMediator.js'; +import './components'; +import './routes.js'; + +OHIF.demoMode = devModeMediator; + diff --git a/Packages/ohif-demo-mode/client/routes.js b/Packages/ohif-demo-mode/imports/client/routes.js similarity index 100% rename from Packages/ohif-demo-mode/client/routes.js rename to Packages/ohif-demo-mode/imports/client/routes.js diff --git a/Packages/ohif-demo-mode/main.js b/Packages/ohif-demo-mode/main.js index 6f891fbb2..6e8e8f5a6 100644 --- a/Packages/ohif-demo-mode/main.js +++ b/Packages/ohif-demo-mode/main.js @@ -1,9 +1,3 @@ -import { OHIF } from 'meteor/ohif:core'; -import './client'; -import devModeMediator from './client/demoModeMediator.js'; - -const demoMode = Meteor.settings && Meteor.settings.public && Meteor.settings.public.demoMode; - -if (demoMode) { - OHIF.demoMode = devModeMediator; -} \ No newline at end of file +if (Meteor.settings.public.demoMode) { + import './imports/client/index.js'; +} diff --git a/Packages/ohif-demo-mode/package.js b/Packages/ohif-demo-mode/package.js index b9013a37b..2626bf1da 100644 --- a/Packages/ohif-demo-mode/package.js +++ b/Packages/ohif-demo-mode/package.js @@ -16,5 +16,4 @@ Package.onUse(function(api) { // Main module api.mainModule('main.js', 'client'); - }); diff --git a/Packages/ohif-google-cloud/client/main.js b/Packages/ohif-google-cloud/client/main.js new file mode 100644 index 000000000..9b559f487 --- /dev/null +++ b/Packages/ohif-google-cloud/client/main.js @@ -0,0 +1,6 @@ +import { Meteor } from 'meteor/meteor'; + +if (Meteor.settings.public.googleCloud) { + import '../imports/index.js'; + import '../imports/client/index.js'; +} diff --git a/Packages/ohif-google-cloud/client/components/dialogs/gcloudDialog.html b/Packages/ohif-google-cloud/imports/client/components/dialogs/gcloudDialog.html similarity index 100% rename from Packages/ohif-google-cloud/client/components/dialogs/gcloudDialog.html rename to Packages/ohif-google-cloud/imports/client/components/dialogs/gcloudDialog.html diff --git a/Packages/ohif-google-cloud/client/components/dialogs/gcloudDialog.js b/Packages/ohif-google-cloud/imports/client/components/dialogs/gcloudDialog.js similarity index 100% rename from Packages/ohif-google-cloud/client/components/dialogs/gcloudDialog.js rename to Packages/ohif-google-cloud/imports/client/components/dialogs/gcloudDialog.js diff --git a/Packages/ohif-google-cloud/client/components/dialogs/gcloudDialog.styl b/Packages/ohif-google-cloud/imports/client/components/dialogs/gcloudDialog.styl similarity index 100% rename from Packages/ohif-google-cloud/client/components/dialogs/gcloudDialog.styl rename to Packages/ohif-google-cloud/imports/client/components/dialogs/gcloudDialog.styl diff --git a/Packages/ohif-google-cloud/client/components/dicomStorePicker/dicomStorePicker.html b/Packages/ohif-google-cloud/imports/client/components/dicomStorePicker/dicomStorePicker.html similarity index 100% rename from Packages/ohif-google-cloud/client/components/dicomStorePicker/dicomStorePicker.html rename to Packages/ohif-google-cloud/imports/client/components/dicomStorePicker/dicomStorePicker.html diff --git a/Packages/ohif-google-cloud/client/components/dicomStorePicker/dicomStorePicker.js b/Packages/ohif-google-cloud/imports/client/components/dicomStorePicker/dicomStorePicker.js similarity index 100% rename from Packages/ohif-google-cloud/client/components/dicomStorePicker/dicomStorePicker.js rename to Packages/ohif-google-cloud/imports/client/components/dicomStorePicker/dicomStorePicker.js diff --git a/Packages/ohif-google-cloud/client/components/dicomStorePicker/dicomStorePicker.styl b/Packages/ohif-google-cloud/imports/client/components/dicomStorePicker/dicomStorePicker.styl similarity index 100% rename from Packages/ohif-google-cloud/client/components/dicomStorePicker/dicomStorePicker.styl rename to Packages/ohif-google-cloud/imports/client/components/dicomStorePicker/dicomStorePicker.styl diff --git a/Packages/ohif-google-cloud/client/components/index.js b/Packages/ohif-google-cloud/imports/client/components/index.js similarity index 100% rename from Packages/ohif-google-cloud/client/components/index.js rename to Packages/ohif-google-cloud/imports/client/components/index.js diff --git a/Packages/ohif-google-cloud/client/components/uploadStudiesDialog/uploadStudiesDialog.html b/Packages/ohif-google-cloud/imports/client/components/uploadStudiesDialog/uploadStudiesDialog.html similarity index 100% rename from Packages/ohif-google-cloud/client/components/uploadStudiesDialog/uploadStudiesDialog.html rename to Packages/ohif-google-cloud/imports/client/components/uploadStudiesDialog/uploadStudiesDialog.html diff --git a/Packages/ohif-google-cloud/client/components/uploadStudiesDialog/uploadStudiesDialog.js b/Packages/ohif-google-cloud/imports/client/components/uploadStudiesDialog/uploadStudiesDialog.js similarity index 100% rename from Packages/ohif-google-cloud/client/components/uploadStudiesDialog/uploadStudiesDialog.js rename to Packages/ohif-google-cloud/imports/client/components/uploadStudiesDialog/uploadStudiesDialog.js diff --git a/Packages/ohif-google-cloud/client/components/uploadStudiesDialog/uploadStudiesDialog.styl b/Packages/ohif-google-cloud/imports/client/components/uploadStudiesDialog/uploadStudiesDialog.styl similarity index 100% rename from Packages/ohif-google-cloud/client/components/uploadStudiesDialog/uploadStudiesDialog.styl rename to Packages/ohif-google-cloud/imports/client/components/uploadStudiesDialog/uploadStudiesDialog.styl diff --git a/Packages/ohif-google-cloud/client/index.js b/Packages/ohif-google-cloud/imports/client/index.js similarity index 100% rename from Packages/ohif-google-cloud/client/index.js rename to Packages/ohif-google-cloud/imports/client/index.js diff --git a/Packages/ohif-google-cloud/imports/index.js b/Packages/ohif-google-cloud/imports/index.js new file mode 100644 index 000000000..9949a65aa --- /dev/null +++ b/Packages/ohif-google-cloud/imports/index.js @@ -0,0 +1,8 @@ +import { OHIF } from 'meteor/ohif:core'; +import GCloudAdapter from './lib/GCloudAdapter'; + +const gcloud = GCloudAdapter; + +OHIF.gcloud = gcloud; + + diff --git a/Packages/ohif-google-cloud/lib/GCloudAdapter.js b/Packages/ohif-google-cloud/imports/lib/GCloudAdapter.js similarity index 90% rename from Packages/ohif-google-cloud/lib/GCloudAdapter.js rename to Packages/ohif-google-cloud/imports/lib/GCloudAdapter.js index 17aab1a38..c7373621b 100644 --- a/Packages/ohif-google-cloud/lib/GCloudAdapter.js +++ b/Packages/ohif-google-cloud/imports/lib/GCloudAdapter.js @@ -1,3 +1,5 @@ +import { OHIF } from 'meteor/ohif:core'; + const GCloudAdapter = {}; const GCP_HEALTHCARE_CONFIG = 'GCP_HEALTHCARE_CONFIG'; @@ -16,7 +18,9 @@ GCloudAdapter.setConfig = function(config) { GCloudAdapter.showDicomStorePicker = function(options) { return OHIF.ui.showDialog('dicomStorePicker', options).then(config => { - if (config) OHIF.gcloud.setConfig(config); + if (config) { + OHIF.gcloud.setConfig(config); + } return config; }); }; diff --git a/Packages/ohif-google-cloud/lib/utils.js b/Packages/ohif-google-cloud/imports/lib/utils.js similarity index 83% rename from Packages/ohif-google-cloud/lib/utils.js rename to Packages/ohif-google-cloud/imports/lib/utils.js index a6084eb99..b75523691 100644 --- a/Packages/ohif-google-cloud/lib/utils.js +++ b/Packages/ohif-google-cloud/imports/lib/utils.js @@ -1,5 +1,4 @@ -export function loadScript(url, callback=undefined) -{ +export function loadScript(url, callback) { var head = document.getElementsByTagName('head')[0]; var script = document.createElement('script'); script.type = 'text/javascript'; @@ -7,4 +6,4 @@ export function loadScript(url, callback=undefined) script.onreadystatechange = callback; script.onload = callback; head.appendChild(script); -} \ No newline at end of file +} diff --git a/Packages/ohif-google-cloud/main.js b/Packages/ohif-google-cloud/main.js deleted file mode 100644 index 937a20b37..000000000 --- a/Packages/ohif-google-cloud/main.js +++ /dev/null @@ -1,9 +0,0 @@ -import { OHIF } from 'meteor/ohif:core'; -import GCloudAdapter from './lib/GCloudAdapter'; - -if (Meteor.settings.public.googleCloud) { - const gcloud = GCloudAdapter; - OHIF.gcloud = gcloud; -} - - diff --git a/Packages/ohif-google-cloud/package.js b/Packages/ohif-google-cloud/package.js index 3a3b34177..6ba8daed6 100644 --- a/Packages/ohif-google-cloud/package.js +++ b/Packages/ohif-google-cloud/package.js @@ -19,12 +19,9 @@ Package.onUse(function(api) { api.use(['templating', 'stylus'], 'client'); // Main module - api.mainModule('main.js', ['client']); + api.mainModule('client/main.js', ['client']); - // Client imports and routes - api.addFiles('client/index.js', 'client'); - - var assets = [ + const assets = [ '.npm/package/node_modules/healthcare-api-adapter/dist/gcp.min.js', '.npm/package/node_modules/healthcare-api-adapter/dist/gcp.0.min.js', '.npm/package/node_modules/healthcare-api-adapter/dist/gcp.2.min.js', diff --git a/Packages/ohif-servers/client/startup.js b/Packages/ohif-servers/client/startup.js index d8c3d2f47..437668c8a 100644 --- a/Packages/ohif-servers/client/startup.js +++ b/Packages/ohif-servers/client/startup.js @@ -21,6 +21,9 @@ if (Meteor.settings && }); const newServer = Servers.findOne(); + if (!newServer) { + return; + } CurrentServer.insert({ serverId: newServer._id diff --git a/Packages/ohif-studies/imports/server/methods/getStudyMetadata.js b/Packages/ohif-studies/imports/server/methods/getStudyMetadata.js index e22323453..95afe37ee 100644 --- a/Packages/ohif-studies/imports/server/methods/getStudyMetadata.js +++ b/Packages/ohif-studies/imports/server/methods/getStudyMetadata.js @@ -20,8 +20,7 @@ Meteor.methods({ if (server.type === 'dicomWeb') { if(server.metadataSource ==='qido') { return OHIF.studies.services.QIDO.RetrieveMetadata(server, studyInstanceUid); - } - else{ + } else { return OHIF.studies.services.WADO.RetrieveMetadata(server, studyInstanceUid); } } else if (server.type === 'dimse') { diff --git a/Packages/ohif-study-list/client/components/studylist/studylistResult/studylistResult.js b/Packages/ohif-study-list/client/components/studylist/studylistResult/studylistResult.js index b4cd83ccf..680f0cf6a 100644 --- a/Packages/ohif-study-list/client/components/studylist/studylistResult/studylistResult.js +++ b/Packages/ohif-study-list/client/components/studylist/studylistResult/studylistResult.js @@ -84,10 +84,8 @@ let filter; */ function getFilter(filter) { const server = OHIF.servers.getCurrentServer(); - if (!server.isCloud) { - if (filter && filter.length && filter.substr(filter.length - 1) !== '*') { - filter += '*'; - } + if (filter && filter.length && filter.substr(filter.length - 1) !== '*') { + filter += '*'; } return filter; @@ -133,13 +131,12 @@ function search(instance) { studyDescription: getFilter($('input#studyDescription').val()), studyDateFrom, studyDateTo, - modalitiesInStudy: $('input#modality').val() ? $('input#modality').val() : '' + modalitiesInStudy: $('input#modality').val() ? $('input#modality').val() : '', + offset: rowsPerPage * currentPage, + limit: rowsPerPage }; const server = OHIF.servers.getCurrentServer(); - if (!server.isCloud) { - filter.offset=rowsPerPage * currentPage; - filter.limit=rowsPerPage; - } + // Make sure that modality has a reasonable value, since it is occasionally // returned as 'undefined' const modality = replaceUndefinedColumnValue($('input#modality').val()); From c0549967c62cf111d7a2ae1cd4486e3ca0a71f14 Mon Sep 17 00:00:00 2001 From: Erik Ziegler Date: Fri, 22 Mar 2019 16:16:56 +0100 Subject: [PATCH 59/66] fix(toolbar): Remove unnecessary Presentation State button --- .../toolbarSection/toolbarSection.js | 13 +----- .../ohif-viewerbase/client/lib/hotkeyUtils.js | 5 --- config/publicOrthancDICOMWeb.json | 43 +++++++++---------- 3 files changed, 22 insertions(+), 39 deletions(-) diff --git a/OHIFViewer/client/components/toolbarSection/toolbarSection.js b/OHIFViewer/client/components/toolbarSection/toolbarSection.js index 8062334fe..455eb4156 100644 --- a/OHIFViewer/client/components/toolbarSection/toolbarSection.js +++ b/OHIFViewer/client/components/toolbarSection/toolbarSection.js @@ -59,7 +59,7 @@ Template.toolbarSection.helpers({ // bottomLabel: 'Hanging' // }] // }; - + return { toggleable: true, key: 'rightSidebar', @@ -268,15 +268,6 @@ Template.toolbarSection.helpers({ svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-sr' }); - buttonData.push({ - id: 'ps', - title: 'PS', - iconClasses: 'fa fa-th-large', - classes: 'imageViewerTool', - svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-ps' - }); - - buttonData.push({ id: 'toggleMore', title: 'More', @@ -334,4 +325,4 @@ Template.toolbarSection.onRendered(function() { } } } -}); \ No newline at end of file +}); diff --git a/Packages/ohif-viewerbase/client/lib/hotkeyUtils.js b/Packages/ohif-viewerbase/client/lib/hotkeyUtils.js index e31c034a0..ed9abe3e1 100644 --- a/Packages/ohif-viewerbase/client/lib/hotkeyUtils.js +++ b/Packages/ohif-viewerbase/client/lib/hotkeyUtils.js @@ -226,11 +226,6 @@ Meteor.startup(function() { action: viewportUtils.toggleDownloadDialog, disabled: () => !viewportUtils.isDownloadEnabled() }, - ps: { - name: 'Show/Hide Presentation State', - action: () => Session.set('MeasurementsReady', !Session.get('MeasurementsReady')), - disabled: () => false - }, sr: { name: 'Show/Hide Structured Report', action: () => OHIF.ui.showDialog('structuredReportModal'), diff --git a/config/publicOrthancDICOMWeb.json b/config/publicOrthancDICOMWeb.json index c187fdad1..8b78e604e 100644 --- a/config/publicOrthancDICOMWeb.json +++ b/config/publicOrthancDICOMWeb.json @@ -1,26 +1,7 @@ { - "servers": { - "dicomWeb": [ - { - "name": "Orthanc", - "wadoUriRoot": "http://dicomweb.ohif.org/wado", - "qidoRoot": "http://dicomweb.ohif.org/dicom-web", - "wadoRoot": "http://dicomweb.ohif.org/dicom-web", - "qidoSupportsIncludeField": false, - "imageRendering": "wadouri", - "thumbnailRendering": "wadouri", - "requestOptions": { - "auth": "orthanc:orthanc", - "logRequests": true, - "logResponses": false, - "logTiming": true - } - } - ] - }, - "defaultServiceType": "dicomWeb", "dropCollections": true, "public": { + "clientOnly": true, "verifyEmail": false, "ui": { "studyListFunctionsEnabled": true, @@ -29,9 +10,25 @@ "displaySetNavigationMultipleViewports": true, "autoPositionMeasurementsTextCallOuts": "TRLB", "studyListDateFilterNumDays": 1 + }, + "servers": { + "dicomWeb": [ + { + "name": "Orthanc", + "wadoUriRoot": "https://server.dcmjs.org//dcm4chee-arc/aets/DCM4CHEE/wado", + "qidoRoot": "https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs", + "wadoRoot": "https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs", + "qidoSupportsIncludeField": true, + "imageRendering": "wadors", + "thumbnailRendering": "wadors", + "requestOptions": { + "requestFromBrowser": true, + "logRequests": true, + "logResponses": false, + "logTiming": true + } + } + ] } - }, - "proxy": { - "enabled": true } } From 416b4ade381e34a30a3e5f8336376c86dc88e1e6 Mon Sep 17 00:00:00 2001 From: Erik Ziegler Date: Fri, 22 Mar 2019 16:47:22 +0100 Subject: [PATCH 60/66] feat(google-cloud): Add an example configuration for Google Cloud Healthcare (you may need to enable the Cloud Resource Manager API) --- config/oidc-googleCloud.json | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 config/oidc-googleCloud.json diff --git a/config/oidc-googleCloud.json b/config/oidc-googleCloud.json new file mode 100644 index 000000000..d8e2acc84 --- /dev/null +++ b/config/oidc-googleCloud.json @@ -0,0 +1,25 @@ +{ + "public": { + "clientOnly": true, + "googleCloud": true, + "ui": { + "studyListFunctionsEnabled": true, + "studyListDateFilterNumDays": false + }, + "servers": { + "dicomWeb": [] + }, + "custom": { + "oidc": [{ + "authServerUrl": "https://accounts.google.com", + "authRedirectUri": "/_oauth/google", + "postLogoutRedirectUri": "/", + "clientId": "YOURCLIENTID.apps.googleusercontent.com", + "scope": "email profile openid https://www.googleapis.com/auth/cloud-platform.read-only https://www.googleapis.com/auth/cloud-healthcare", + "revokeUrl": "https://accounts.google.com/o/oauth2/revoke?token=" + }] + }, + "userAuthenticationRoutesEnabled": false, + "demoMode": false + } +} From 6586c03af13853552d679477a5c42454e67c3646 Mon Sep 17 00:00:00 2001 From: Erik Ziegler Date: Wed, 3 Apr 2019 18:17:38 +0200 Subject: [PATCH 61/66] feat(google-cloud): Add docs for how to use OHIF against Google Cloud Healthcare --- clientImage/howTo.md | 17 ----- .../viewer-google-cloud}/Dockerfile | 0 dockersupport/viewer-google-cloud/README.md | 34 +++++++++ .../viewer-google-cloud}/nginx.conf | 0 docs/SUMMARY.md | 1 + .../google-cloud-healthcare.md | 72 +++++++++++++++++++ 6 files changed, 107 insertions(+), 17 deletions(-) delete mode 100644 clientImage/howTo.md rename {clientImage => dockersupport/viewer-google-cloud}/Dockerfile (100%) create mode 100644 dockersupport/viewer-google-cloud/README.md rename {clientImage => dockersupport/viewer-google-cloud}/nginx.conf (100%) create mode 100644 docs/connecting-to-image-archives/google-cloud-healthcare.md diff --git a/clientImage/howTo.md b/clientImage/howTo.md deleted file mode 100644 index 918286ced..000000000 --- a/clientImage/howTo.md +++ /dev/null @@ -1,17 +0,0 @@ -## environment -[install meteor](https://www.meteor.com/install) -## download -```bash -git clone https://github.com/quantumsoftgroup/ViewersClone.git -cd ViewersClone -git checkout dev -``` -## build -```bash -cd OHIFViewer/ -METEOR_PACKAGE_DIRS="../Packages" meteor-build-client ../clientImage/build -s ../config/oidc.json -cd ../clientImage -docker build -t ohif-ghc . -docker run --env CLIENT_ID={$someID}.apps.googleusercontent.com --publish 3000:80 ohif-ghc - -``` diff --git a/clientImage/Dockerfile b/dockersupport/viewer-google-cloud/Dockerfile similarity index 100% rename from clientImage/Dockerfile rename to dockersupport/viewer-google-cloud/Dockerfile diff --git a/dockersupport/viewer-google-cloud/README.md b/dockersupport/viewer-google-cloud/README.md new file mode 100644 index 000000000..0180ca4d9 --- /dev/null +++ b/dockersupport/viewer-google-cloud/README.md @@ -0,0 +1,34 @@ +This folder contains the instructions for building the ohif/viewer-google-cloud Docker container. + +1. [Install Meteor](https://www.meteor.com/install) +1. Clone the repository +```bash +git clone https://github.com/OHIF/Viewers.git +cd Viewers +``` + +1. Install meteor-build-client-fixed2 so you can build the Standalone Viewer + +```bash +npm install -g meteor-build-client-fixed2 +``` + +1. Build the Standalone client-only OHIF Viewer + +```bash +cd OHIFViewer/ +METEOR_PACKAGE_DIRS="../Packages" meteor-build-client ../dockersupport/viewer-google-cloud/build -s ../config/oidc.json +``` + +1. Build the Docker image + +```bash +cd ../dockersupport/viewer-google-cloud +docker build -t ohif/viewer-google-cloud . +``` + +1. Run the Docker image using an OAuth Client ID + +```bash +docker run --env CLIENT_ID={$someID}.apps.googleusercontent.com --publish 3000:80 ohif/viewer-google-cloud +``` diff --git a/clientImage/nginx.conf b/dockersupport/viewer-google-cloud/nginx.conf similarity index 100% rename from clientImage/nginx.conf rename to dockersupport/viewer-google-cloud/nginx.conf diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 2dcf5bcee..d8996639f 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -13,6 +13,7 @@ - Examples - [dcm4chee w/ Docker](connecting-to-image-archives/dcm4chee-with-docker.md) - [Orthanc w/ Docker](connecting-to-image-archives/orthanc-with-docker.md) + - [Google Cloud Healthcare](connecting-to-image-archives/google-cloud-healthcare.md) - [Osirix](connecting-to-image-archives/osirix.md) - Data Organization - [Data Hierarchy](data/data-hierarchy.md) diff --git a/docs/connecting-to-image-archives/google-cloud-healthcare.md b/docs/connecting-to-image-archives/google-cloud-healthcare.md new file mode 100644 index 000000000..22b1d372a --- /dev/null +++ b/docs/connecting-to-image-archives/google-cloud-healthcare.md @@ -0,0 +1,72 @@ +# Google Cloud Healthcare + +> The [Google Cloud Healthcare API](https://cloud.google.com/healthcare/) is a powerful option for storing medical imaging data in the cloud. + +An alternative to deploying your own PACS is to use a software-as-a-service provider such as Google Cloud. The Cloud Healthcare API promises to be a scalable, secure, cost effective image storage solution for those willing to store their data in the cloud. It offers an [almost-entirely complete DICOMWeb API](https://cloud.google.com/healthcare/docs/dicom) which requires tokens generated via the [OAuth 2.0 Sign In flow](https://developers.google.com/identity/sign-in/web/sign-in). Images can even be transcoded on the fly if this is desired. The Cloud Healthcare API is a very attractive option because it allows us to avoid deploying the Meteor server entirely. We can just deploy OHIF as a client-only static site application. + +## Setup a Google Cloud Healthcare Project + +1. Create a Google Cloud account +1. Create a project in Google Cloud +1. Enable the [Cloud Healthcare API](https://cloud.google.com/healthcare/) for your project. +1. (Optional): Create a Dataset and Data Store for storing your DICOM data +1. Enable the [Cloud Resource Manager API](https://cloud.google.com/resource-manager/) for your project. +1. Go to APIs & Services > Credentials > Credentials to set up an [OAuth 2.0 Client ID](https://support.google.com/cloud/answer/6158849?hl=en) +1. Add your domain (e.g. `http://localhost:3000`) to Authorized JavaScript origins. Add your domain, plus `_oauth/google` (e.g. `http://localhost:3000/_oauth/google`) to Authorized redirect URIs. Save your Client ID for later. +1. Go to APIs & Services > Credentials > OAuth consent screen and fill in your application details. + + Under Scopes for Google APIs, add `../auth/cloudplatformprojects.readonly` and `../auth/cloud-healthcare` in addition to `email`, `profile`, and `openid`. + +## Run the viewer with your OAuth Client ID + +1. Open the `config/oidc-googleCloud.json` file and change `YOURCLIENTID` to your Client ID value. +1. Run the OHIF Viewer using the oidc-googleCloud.json configuration file + +````bash +cd OHIFViewer +METEOR_PACKAGE_DIRS="../Packages" meteor --settings ../config/oidc-googleCloud.json +```` + +## Running via Docker + +OHIF is also providing a Docker container which can connect to Google Cloud Healthcare with a Client ID which is provided at runtime. This is a very simple method to get up and running. Internally, the container is running [Nginx](https://nginx.org/) to serve the [Standalone Viewer](../standalone-viewer/usage.md). + +1. Install Docker (https://www.docker.com/) +1. Run the Docker container, providing a Client ID as an environment variable + +````bash +docker run --env CLIENT_ID={$CLIENTID}.apps.googleusercontent.com --publish 3000:80 ohif/viewer-google-cloud:latest +```` + +## Building the ohif/viewer-google-cloud Docker Image + +The [ohif/viewer-google-cloud](https://cloud.docker.com/u/ohif/repository/docker/ohif/viewer-google-cloud) Docker image is built as follows. The Dockerfile and nginx.conf are in the `/dockersupport/viewer-google-cloud` folder. + +1. [Install Meteor](https://www.meteor.com/install) +1. Clone the repository +```bash +git clone https://github.com/OHIF/Viewers.git +cd Viewers +``` + +1. Install meteor-build-client-fixed2 so you can build the Standalone Viewer +```bash +npm install -g meteor-build-client-fixed2 +``` + +1. Build the Standalone client-only OHIF Viewer +```bash +cd OHIFViewer/ +METEOR_PACKAGE_DIRS="../Packages" meteor-build-client-fixed2 ../dockersupport/viewer-google-cloud/build -s ../config/oidc.json +``` + +1. Build the Docker image +```bash +cd ../dockersupport/viewer-google-cloud +docker build -t ohif/viewer-google-cloud . +``` + +1. Run the Docker image using an OAuth Client ID +```bash +docker run --env CLIENT_ID={$someID}.apps.googleusercontent.com --publish 3000:80 ohif/viewer-google-cloud +``` \ No newline at end of file From 6c9270b3de6d0dfd0304591ec3541abebce53831 Mon Sep 17 00:00:00 2001 From: Erik Ziegler Date: Thu, 4 Apr 2019 09:02:15 +0200 Subject: [PATCH 62/66] fix(dockerfile): Fix jessie-updates issues with node:8.10.0-slim image --- development.Dockerfile | 2 ++ dockerfile | 2 ++ 2 files changed, 4 insertions(+) diff --git a/development.Dockerfile b/development.Dockerfile index 6c5457cae..95987fe87 100644 --- a/development.Dockerfile +++ b/development.Dockerfile @@ -5,6 +5,8 @@ # in the final image. FROM node:8.10.0-slim as builder +# Fix build now that jessie-updates has been archived +RUN sed -i '/jessie-updates/d' /etc/apt/sources.list RUN apt-get update && apt-get install -y \ curl \ g++ \ diff --git a/dockerfile b/dockerfile index 201092b54..90be9c616 100644 --- a/dockerfile +++ b/dockerfile @@ -5,6 +5,8 @@ # in the final image. FROM node:8.10.0-slim as builder +# Fix build now that jessie-updates has been archived +RUN sed -i '/jessie-updates/d' /etc/apt/sources.list RUN apt-get update && apt-get install -y \ curl \ g++ \ From 9d83a1e6cce02e7983a6bda00fbf76988b455cbc Mon Sep 17 00:00:00 2001 From: Dusan Zelembaba Date: Fri, 5 Apr 2019 10:13:14 -0400 Subject: [PATCH 63/66] Restrict Scope for Cloud Healthcare API Example Config (#378) The integration only needs read access to projects not all cloud platform resources. --- config/oidc.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/oidc.json b/config/oidc.json index 53ad5b5da..cf0c42f5e 100644 --- a/config/oidc.json +++ b/config/oidc.json @@ -26,11 +26,11 @@ "authServerUrl": "https://accounts.google.com", "authRedirectUri": "/_oauth/google", "postLogoutRedirectUri": "/", - "scope": "email profile openid https://www.googleapis.com/auth/cloud-platform.read-only https://www.googleapis.com/auth/cloud-healthcare", + "scope": "email profile openid https://www.googleapis.com/auth/cloudplatformprojects.readonly https://www.googleapis.com/auth/cloud-healthcare", "revokeUrl": "https://accounts.google.com/o/oauth2/revoke?token=" }] }, "userAuthenticationRoutesEnabled": true, "demoMode": false } -} \ No newline at end of file +} From 9270fbf78d5be9d55d4c03bcfeba92f3283b9ee9 Mon Sep 17 00:00:00 2001 From: Erik Ziegler Date: Fri, 5 Apr 2019 17:23:50 +0200 Subject: [PATCH 64/66] docs(google-cloud): Update Google Cloud docs to clarify instructions --- .../google-cloud-healthcare.md | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/docs/connecting-to-image-archives/google-cloud-healthcare.md b/docs/connecting-to-image-archives/google-cloud-healthcare.md index 22b1d372a..1ed4e98cd 100644 --- a/docs/connecting-to-image-archives/google-cloud-healthcare.md +++ b/docs/connecting-to-image-archives/google-cloud-healthcare.md @@ -11,11 +11,24 @@ An alternative to deploying your own PACS is to use a software-as-a-service prov 1. Enable the [Cloud Healthcare API](https://cloud.google.com/healthcare/) for your project. 1. (Optional): Create a Dataset and Data Store for storing your DICOM data 1. Enable the [Cloud Resource Manager API](https://cloud.google.com/resource-manager/) for your project. -1. Go to APIs & Services > Credentials > Credentials to set up an [OAuth 2.0 Client ID](https://support.google.com/cloud/answer/6158849?hl=en) -1. Add your domain (e.g. `http://localhost:3000`) to Authorized JavaScript origins. Add your domain, plus `_oauth/google` (e.g. `http://localhost:3000/_oauth/google`) to Authorized redirect URIs. Save your Client ID for later. -1. Go to APIs & Services > Credentials > OAuth consent screen and fill in your application details. - Under Scopes for Google APIs, add `../auth/cloudplatformprojects.readonly` and `../auth/cloud-healthcare` in addition to `email`, `profile`, and `openid`. + *Note:* If you are having trouble finding the APIs, use the search box at the top of the Cloud console. + +1. Go to APIs & Services > Credentials to create an OAuth Consent screen and fill in your application details. + + - Under Scopes for Google APIs, click "manually paste scopes". + - Add the following scopes: + - https://www.googleapis.com/auth/cloudplatformprojects.readonly + - https://www.googleapis.com/auth/cloud-healthcare + +1. Go to APIs & Services > Credentials to create a new set of credentials: + - Choose the "Web Application" type + - Set up an [OAuth 2.0 Client ID](https://support.google.com/cloud/answer/6158849?hl=en) + + - Add your domain (e.g. ```http://localhost:3000```) to Authorized JavaScript origins. + - Add your domain, plus `_oauth/google` (e.g. ```http://localhost:3000/_oauth/google```) to Authorized Redirect URIs. + - Save your Client ID for later. +1. (Optional): Enable Public Datasets that are being hosted by Google: https://cloud.google.com/healthcare/docs/resources/public-datasets/ ## Run the viewer with your OAuth Client ID @@ -24,6 +37,7 @@ An alternative to deploying your own PACS is to use a software-as-a-service prov ````bash cd OHIFViewer +METEOR_PACKAGE_DIRS="../Packages" meteor npm install METEOR_PACKAGE_DIRS="../Packages" meteor --settings ../config/oidc-googleCloud.json ```` @@ -32,10 +46,10 @@ METEOR_PACKAGE_DIRS="../Packages" meteor --settings ../config/oidc-googleCloud.j OHIF is also providing a Docker container which can connect to Google Cloud Healthcare with a Client ID which is provided at runtime. This is a very simple method to get up and running. Internally, the container is running [Nginx](https://nginx.org/) to serve the [Standalone Viewer](../standalone-viewer/usage.md). 1. Install Docker (https://www.docker.com/) -1. Run the Docker container, providing a Client ID as an environment variable +1. Run the Docker container, providing a Client ID as an environment variable. Client IDs look like `xyz.apps.googleusercontent.com`. ````bash -docker run --env CLIENT_ID={$CLIENTID}.apps.googleusercontent.com --publish 3000:80 ohif/viewer-google-cloud:latest +docker run --env CLIENT_ID=$CLIENT_ID --publish 3000:80 ohif/viewer-google-cloud:latest ```` ## Building the ohif/viewer-google-cloud Docker Image @@ -57,6 +71,7 @@ npm install -g meteor-build-client-fixed2 1. Build the Standalone client-only OHIF Viewer ```bash cd OHIFViewer/ +METEOR_PACKAGE_DIRS="../Packages" meteor npm install METEOR_PACKAGE_DIRS="../Packages" meteor-build-client-fixed2 ../dockersupport/viewer-google-cloud/build -s ../config/oidc.json ``` @@ -69,4 +84,4 @@ docker build -t ohif/viewer-google-cloud . 1. Run the Docker image using an OAuth Client ID ```bash docker run --env CLIENT_ID={$someID}.apps.googleusercontent.com --publish 3000:80 ohif/viewer-google-cloud -``` \ No newline at end of file +``` From 46ed3395790e35c02004d5228505a3dc2cf2b8ae Mon Sep 17 00:00:00 2001 From: Egor Lezhnin <878990+pavertomato@users.noreply.github.com> Date: Mon, 8 Apr 2019 18:10:30 +0700 Subject: [PATCH 65/66] fix(google-cloud): Update healthcare-api-adapter to use v1beta1 URLs (#379) --- Packages/ohif-google-cloud/package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Packages/ohif-google-cloud/package.js b/Packages/ohif-google-cloud/package.js index 6ba8daed6..dfacc7491 100644 --- a/Packages/ohif-google-cloud/package.js +++ b/Packages/ohif-google-cloud/package.js @@ -6,7 +6,7 @@ Package.describe({ }); Npm.depends({ - 'healthcare-api-adapter': "git+https://github.com/quantumsoftgroup/healthcare-api-adapter#v0.2.1" + 'healthcare-api-adapter': "git+https://github.com/quantumsoftgroup/healthcare-api-adapter#v0.2.2" }); From 7ff9a87b6e68f4fb8e9a527e292821c7ec9f966f Mon Sep 17 00:00:00 2001 From: Evren Ozkan Date: Wed, 1 May 2019 11:27:53 -0400 Subject: [PATCH 66/66] fix(wado-proxy): Use wado proxy to retrieve images if enabled in configuration --- .../imports/both/services/wado/retrieveMetadata.js | 4 ++-- Packages/ohif-wadoproxy/both/convertURL.js | 4 ++++ config/dcm4cheeDICOMWeb.json | 3 +++ config/publicOrthancDICOMWeb.json | 3 +++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Packages/ohif-studies/imports/both/services/wado/retrieveMetadata.js b/Packages/ohif-studies/imports/both/services/wado/retrieveMetadata.js index 9f0a228a5..c5c037658 100644 --- a/Packages/ohif-studies/imports/both/services/wado/retrieveMetadata.js +++ b/Packages/ohif-studies/imports/both/services/wado/retrieveMetadata.js @@ -433,8 +433,8 @@ async function resultDataToStudyMetadata(server, studyInstanceUid, resultData) { contrastBolusAgent: DICOMWeb.getString(instance['00180010']), radiopharmaceuticalInfo: getRadiopharmaceuticalInfo(instance), baseWadoRsUri: baseWadoRsUri, - wadouri: wadouri, - wadorsuri: wadorsuri, + wadouri: WADOProxy.convertURL(wadouri, server), + wadorsuri: WADOProxy.convertURL(wadorsuri, server), imageRendering: server.imageRendering, thumbnailRendering: server.thumbnailRendering }; diff --git a/Packages/ohif-wadoproxy/both/convertURL.js b/Packages/ohif-wadoproxy/both/convertURL.js index 781ba1d42..50e105da1 100644 --- a/Packages/ohif-wadoproxy/both/convertURL.js +++ b/Packages/ohif-wadoproxy/both/convertURL.js @@ -11,6 +11,10 @@ WADOProxy.convertURL = (url, serverConfiguration) => { } const { settings } = WADOProxy; + if (!settings.enabled) { + return url; + } + const serverId = serverConfiguration._id; const query = queryString.stringify({url, serverId}); diff --git a/config/dcm4cheeDICOMWeb.json b/config/dcm4cheeDICOMWeb.json index 70cfdf39c..43b8ecb27 100644 --- a/config/dcm4cheeDICOMWeb.json +++ b/config/dcm4cheeDICOMWeb.json @@ -50,5 +50,8 @@ "studyListFunctionsEnabled": true, "studyListDateFilterNumDays": 1 } + }, + "proxy": { + "enabled": true } } diff --git a/config/publicOrthancDICOMWeb.json b/config/publicOrthancDICOMWeb.json index 8b78e604e..50faf31ab 100644 --- a/config/publicOrthancDICOMWeb.json +++ b/config/publicOrthancDICOMWeb.json @@ -30,5 +30,8 @@ } ] } + }, + "proxy": { + "enabled": true } }