diff --git a/.gitignore b/.gitignore
index 2e15e6d39..0448892b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
.idea/
+.vscode/
.meteor/local
.meteor/meteorite
.meteor/dev_bundle
diff --git a/OHIFViewer/.meteor/packages b/OHIFViewer/.meteor/packages
index a229e05c5..6b44cb394 100644
--- a/OHIFViewer/.meteor/packages
+++ b/OHIFViewer/.meteor/packages
@@ -39,6 +39,8 @@ ohif:viewerbase
ohif:study-list
ohif:hanging-protocols
ohif:metadata
+ohif:google-cloud
+ohif:demo-mode
ohif:user-oidc
ohif:measurement-table
@@ -50,4 +52,4 @@ johdirr:meteor-git-rev
cultofcoders:persistent-session
shell-server@0.3.1
underscore
-meteortesting:mocha
+meteortesting:mocha
\ No newline at end of file
diff --git a/OHIFViewer/.meteor/versions b/OHIFViewer/.meteor/versions
index e061f8b60..a0c2399a3 100644
--- a/OHIFViewer/.meteor/versions
+++ b/OHIFViewer/.meteor/versions
@@ -81,7 +81,9 @@ ohif:commands@0.0.1
ohif:core@0.0.1
ohif:cornerstone@0.0.1
ohif:cornerstone-settings@0.0.1
+ohif:demo-mode@0.0.1
ohif:design@0.0.1
+ohif:google-cloud@0.0.1
ohif:hanging-protocols@0.0.1
ohif:header@0.0.1
ohif:hotkeys@0.0.1
@@ -128,4 +130,4 @@ ui@1.0.13
underscore@1.0.10
url@1.2.0
webapp@1.6.2
-webapp-hashing@1.0.9
+webapp-hashing@1.0.9
\ No newline at end of file
diff --git a/OHIFViewer/client/components/ohifViewer/ohifViewer.html b/OHIFViewer/client/components/ohifViewer/ohifViewer.html
index 2750cd2db..8bce9d23d 100644
--- a/OHIFViewer/client/components/ohifViewer/ohifViewer.html
+++ b/OHIFViewer/client/components/ohifViewer/ohifViewer.html
@@ -23,6 +23,8 @@
{{/section}}
{{/header}}
-
- {{>Template.dynamic template=(choose this.template 'studylist') data=this}}
+
+ {{#if isStudyListReady}}
+ {{>Template.dynamic template=(choose this.template 'studylist') data=this}}
+ {{/if}}
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 @@
+
+ {{#dialogForm (extend this
+ id='structuredReportModal'
+ dialogClass='themed modal-lg'
+ title='Structured report'
+ hideCancel=true
+ hideConfirm=true
+ )}}
+
+ {{/dialogForm}}
+
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 @@
+
+
+
+
+
+
Open Health Imaging Foundation
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+ {{>UI.contentBlock}}
+
+
+
+
+
+
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 @@
+
+ {{#gcloudDialog (extend this
+ id='dicomStorePicker' dialogClass='themed modal-lg'
+ )}}
+
+
+
+ {{/gcloudDialog}}
+
\ 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 @@
+
+ {{#gcloudDialog (extend this
+ id='dicomFilesUploader' dialogClass='themed modal-lg'
+ )}}
+
+
+
+ {{/gcloudDialog}}
+
\ 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 @@
@@ -56,7 +59,7 @@
class="form-control studylist-search"
id="modality">
-
+
|
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 d6f545048..b4cd83ccf 100644
--- a/Packages/ohif-study-list/client/components/studylist/studylistResult/studylistResult.js
+++ b/Packages/ohif-study-list/client/components/studylist/studylistResult/studylistResult.js
@@ -6,6 +6,7 @@ import { ReactiveDict } from 'meteor/reactive-dict';
import { moment } from 'meteor/momentjs:moment';
import { OHIF } from 'meteor/ohif:core';
+
Session.setDefault('showLoadingText', true);
Session.setDefault('serverError', false);
@@ -39,6 +40,13 @@ Template.studylistResult.helpers({
return studies;
},
+ dicomSource() {
+ const config = Session.get('GCP_HEALTHCARE_CONFIG');;
+ if (!config)
+ return '';
+ return config.project + ' / ' + config.location + ' / ' + config.dataset + ' / ' + config.dicomStore
+ },
+
numberOfStudies() {
return OHIF.studylist.collections.Studies.find().count();
},
@@ -75,8 +83,11 @@ let filter;
* @returns {*}
*/
function getFilter(filter) {
- if (filter && filter.length && filter.substr(filter.length - 1) !== '*') {
- filter += '*';
+ const server = OHIF.servers.getCurrentServer();
+ if (!server.isCloud) {
+ if (filter && filter.length && filter.substr(filter.length - 1) !== '*') {
+ filter += '*';
+ }
}
return filter;
@@ -100,6 +111,9 @@ function replaceUndefinedColumnValue(text) {
function search(instance) {
OHIF.log.info('search()');
+ // Clear all current studies
+ OHIF.studylist.collections.Studies.remove({});
+
// Show loading message
Session.set('showLoadingText', true);
@@ -111,10 +125,9 @@ function search(instance) {
const currentPage = instance.paginationData.currentPage.get();
// Create the filters to be used for the StudyList Search
+ const reverseFormatPN = Blaze._getGlobalHelper('reverseFormatPN');
filter = {
- offset: rowsPerPage * currentPage,
- limit: rowsPerPage,
- patientName: getFilter($('input#patientName').val()),
+ patientName: reverseFormatPN(getFilter($('input#patientName').val())),
patientId: getFilter($('input#patientId').val()),
accessionNumber: getFilter($('input#accessionNumber').val()),
studyDescription: getFilter($('input#studyDescription').val()),
@@ -122,7 +135,11 @@ function search(instance) {
studyDateTo,
modalitiesInStudy: $('input#modality').val() ? $('input#modality').val() : ''
};
-
+ 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());
@@ -143,10 +160,7 @@ function search(instance) {
// Loop through all identified studies
studies.forEach(study => {
- // TODO: Why is this Modality filter different from QIDO?
- if (!modality && !study.modalities.includes(modality)) {
- return;
- }
+
// Sometimes DICOM studies have incorrect Date entries with
// periods such as '1990.10.04'
@@ -247,16 +261,30 @@ Template.studylistResult.onRendered(() => {
}
instance.datePicker = $studyDate.daterangepicker({
- maxDate: today,
- autoUpdateInput: true,
- startDate: startDate,
- endDate: endDate,
+ maxDate: false,
+ autoUpdateInput: false,
+ // startDate: startDate,
+ // endDate: endDate,
ranges: {
+ Empty: [],
Today: [today, today],
'Last 7 Days': [lastWeek, today],
'Last 30 Days': [lastMonth, today]
- }
+ },
+ locale: { cancelLabel: 'Clear' }
}).data('daterangepicker');
+ instance.datePicker.autoUpdateInput = true;
+ $studyDate.on('apply.daterangepicker', function(ev, picker) {
+ if (picker.chosenLabel === 'Empty')
+ $(this).val('');
+ else
+ $(this).val(picker.startDate.format('MM/DD/YYYY') + ' - ' + picker.endDate.format('MM/DD/YYYY'));
+ $studyDate.trigger('change');
+ });
+ $studyDate.on('cancel.daterangepicker', function(ev, picker) {
+ $(this).val('');
+ $studyDate.trigger('change');
+ });
search(instance);
@@ -307,9 +335,15 @@ Template.studylistResult.events({
dateRange = dateRange.replace(/ /g, '');
// Split dateRange into subdates
- const dates = dateRange.split('-');
- studyDateFrom = dates[0];
- studyDateTo = dates[1];
+ if (dateRange) {
+ // Split dateRange into subdates
+ const dates = dateRange.split('-');
+ studyDateFrom = dates[0];
+ studyDateTo = dates[1];
+ } else {
+ studyDateFrom = '';
+ studyDateTo = '';
+ }
if (dateRange !== '') {
search(instance);
diff --git a/Packages/ohif-study-list/client/components/studylist/studylistResult/studylistResult.styl b/Packages/ohif-study-list/client/components/studylist/studylistResult/studylistResult.styl
index 2273e6521..4f5bd6c79 100644
--- a/Packages/ohif-study-list/client/components/studylist/studylistResult/studylistResult.styl
+++ b/Packages/ohif-study-list/client/components/studylist/studylistResult/studylistResult.styl
@@ -25,6 +25,11 @@ $bodyCellHeight = 40px
color: $tableTextSecondaryColor
line-height: $studyListToolbarHeight
+ .dicom-source
+ font-size: 18px;
+ font-weight: 400;
+ margin: 0 30px;
+
.studylistToolbar
height: $studyListToolbarHeight
line-height: $studyListToolbarHeight
diff --git a/Packages/ohif-study-list/client/components/studylist/studylistToolbar/studylistToolbar.html b/Packages/ohif-study-list/client/components/studylist/studylistToolbar/studylistToolbar.html
index f62fbc760..b5c47191e 100644
--- a/Packages/ohif-study-list/client/components/studylist/studylistToolbar/studylistToolbar.html
+++ b/Packages/ohif-study-list/client/components/studylist/studylistToolbar/studylistToolbar.html
@@ -8,5 +8,11 @@
{{/if}}
+ {{#if changeDicomStoreSupported}}
+ {{#button class='btn btn-primary changeDicomStoreBtn'}}Change dicom store{{/button}}
+ {{/if}}
+ {{#if uploadSupported}}
+ {{#button class='btn btn-primary uploadStudiesBtn'}}Upload studies{{/button}}
+ {{/if}}
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
}