feat(google-cloud): Add support for Google Cloud Healthcare API DICOM Store Switch (#325)
* NOTASK: PoC for loading data from Healthcare API * NOTASK: PoC for loading data from Healthcare API fix * Add sample dialog for loading test dicom-picker * Fix incorrect promise resolving * NOTASK: PoC for loading dicom store * Add web-components as git submodules. (Probably will be changed later) * Add rough dicomStore picker * Add dataset selector * NOTASK: QIDO metadata first draft * NOTASK: Not Implemented error for color images * NOTASK: clean-up * Dicom files uploader intermediate version * NOTASK: Accept header * NOTASK: docker configs * NOTASK: fix * NOTASK: howTo * Add draft of DICOM uploader * Add missing files * Fix error with missing clientId * Update submodule * NOTASK: HowTo * NOTASK: config for dev * NOTASK: new docker file * clean-up * Update GCP web-components * Add integration with async web components * Fix errors in nginx.conf * Structured reports views basic implementation * NOTASK: qido -> wado * NOTASK: config fix * NOTASK: dirty copy-paste implementation * Add "Change dicom store" button. Add an ability to clear date. Fix bug, when OAuth dosn't work if URL is not / * Fix studylist filtering * Fix dockerfile * NOTASK: meteor update * NOTASK: fix of package structure * NOTASK: merge fix * improvements Move check for now SR into SR modal Use simple button to open SR modal Remove button styling cleanup * copy SR data retrieval to wado * make search of SR simpler * Codestyle fix * NOTASK: palette color error message * Fix date filter. Fix server settings on first loading. Add SR and PS buttons * NOTASK: PS is now hidden by default * NOTASK: dirty solution * NOTASK: new wadoimage lib version * Fix PS and SR buttons * Make modal dialogs vertically centered * Update web components. Quick UI fixes * Add missing files to the previous commit * NOTASK: show-hide for PS * NOTASK: show-hide for PS * NOTASK: fix of min-max PixelValue bug * Add "sign out" functionality for Google OAuth. Update web-components. * intermediate commit * Add demo signin page * Beautifying demo sign in page * NOTASK: merge * NOTASK: clean-up * Use npm for getting healthcare-api-adapter instead of git submodule. * NOTASK: clean-up * meteor update * clean-up * clean-up * Clean up code * Update healthcare-api-adapter version. Add location to dicom store path. Fix buttons style. * Downgrade meteor and packages to master version. Use meteor-build-client-fixed2
This commit is contained in:
parent
1783351f57
commit
9486522552
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
.idea/
|
||||
.vscode/
|
||||
.meteor/local
|
||||
.meteor/meteorite
|
||||
.meteor/dev_bundle
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -23,6 +23,8 @@
|
||||
</div>
|
||||
{{/section}}
|
||||
{{/header}}
|
||||
|
||||
{{>Template.dynamic template=(choose this.template 'studylist') data=this}}
|
||||
|
||||
{{#if isStudyListReady}}
|
||||
{{>Template.dynamic template=(choose this.template 'studylist') data=this}}
|
||||
{{/if}}
|
||||
</template>
|
||||
|
||||
@ -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');
|
||||
}
|
||||
});
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
<template name="structuredReportModal">
|
||||
{{#dialogForm (extend this
|
||||
id='structuredReportModal'
|
||||
dialogClass='themed modal-lg'
|
||||
title='Structured report'
|
||||
hideCancel=true
|
||||
hideConfirm=true
|
||||
)}}
|
||||
<div id="root"></div>
|
||||
{{/dialogForm}}
|
||||
</template>
|
||||
@ -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('<div>No structured report found</div>');
|
||||
}
|
||||
|
||||
function getMainDataHtml(data) {
|
||||
const root = $('<div></div>');
|
||||
|
||||
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 = $('<div></div>');
|
||||
const header = data.header;
|
||||
const items = data.items || [];
|
||||
|
||||
if (header) {
|
||||
root.append(`<h${level}>${header}</h${level}>`);
|
||||
}
|
||||
|
||||
items.forEach(item => {
|
||||
root.append(
|
||||
item instanceof Object
|
||||
? getContentSequenceHtml(item, level + 1)
|
||||
: `<div>${item}</div>`
|
||||
);
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
function getMainDataItemHtml(key, value) {
|
||||
return $(`<div><b>${key}</b>: ${value}</div>`);
|
||||
}
|
||||
@ -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() {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,25 @@
|
||||
<template name="demoSignin">
|
||||
<div class="demoSignin">
|
||||
<div class="demo-content">
|
||||
<div class="demo-brand">
|
||||
<svg class="logo-image">
|
||||
<use xlink:href={{absoluteUrl "packages/ohif_viewerbase/assets/icons.svg#icon-ohif-logo"}}></use>
|
||||
</svg>
|
||||
<div class="logo-text">Open Health Imaging Foundation</div>
|
||||
</div>
|
||||
<div class="demoSigninButtons">
|
||||
<button type="submit" id="google-login-button" class="button button--nostyle">
|
||||
<span class="sign-in-with-google-icon-tile">
|
||||
<svg class="google-icon">
|
||||
<use xlink:href={{absoluteUrl "packages/ohif_viewerbase/assets/icons.svg#google-icon"}}></use>
|
||||
</svg></span>
|
||||
<span>Sign in with Google</span>
|
||||
</button>
|
||||
<hr class="tint">
|
||||
<button id="anonymous-login-button">
|
||||
<span>Anonymous Sign in</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -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 });
|
||||
}
|
||||
});
|
||||
|
||||
@ -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
|
||||
3
Packages/ohif-demo-mode/client/components/index.js
Normal file
3
Packages/ohif-demo-mode/client/components/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import './demoSignin/demoSignin.html';
|
||||
import './demoSignin/demoSignin.js';
|
||||
import './demoSignin/demoSignin.styl';
|
||||
29
Packages/ohif-demo-mode/client/demoModeMediator.js
Normal file
29
Packages/ohif-demo-mode/client/demoModeMediator.js
Normal file
@ -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
|
||||
});
|
||||
};
|
||||
2
Packages/ohif-demo-mode/client/index.js
Normal file
2
Packages/ohif-demo-mode/client/index.js
Normal file
@ -0,0 +1,2 @@
|
||||
import './components';
|
||||
import './routes.js';
|
||||
26
Packages/ohif-demo-mode/client/routes.js
Normal file
26
Packages/ohif-demo-mode/client/routes.js
Normal file
@ -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' });
|
||||
9
Packages/ohif-demo-mode/main.js
Normal file
9
Packages/ohif-demo-mode/main.js
Normal file
@ -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;
|
||||
}
|
||||
20
Packages/ohif-demo-mode/package.js
Normal file
20
Packages/ohif-demo-mode/package.js
Normal file
@ -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');
|
||||
|
||||
});
|
||||
3
Packages/ohif-google-cloud/.meteorignore
Normal file
3
Packages/ohif-google-cloud/.meteorignore
Normal file
@ -0,0 +1,3 @@
|
||||
healthcare-api-adapter/node_modules/
|
||||
healthcare-api-adapter/public/
|
||||
healthcare-api-adapter/src/
|
||||
@ -0,0 +1,12 @@
|
||||
<template name="gcloudDialog">
|
||||
<div id="{{this.id}}" class="modal fade gcloud-dialog {{this.class}}" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog {{this.dialogClass}}" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
{{>UI.contentBlock}}
|
||||
</div>
|
||||
<div class="modal-footer">{{>section 'dialogFooter'}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -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);
|
||||
});
|
||||
@ -0,0 +1,6 @@
|
||||
@import '{ohif:design}/app'
|
||||
|
||||
.gcloud-dialog
|
||||
display flex
|
||||
.modal-dialog
|
||||
margin auto
|
||||
@ -0,0 +1,9 @@
|
||||
<template name="dicomStorePicker">
|
||||
{{#gcloudDialog (extend this
|
||||
id='dicomStorePicker' dialogClass='themed modal-lg'
|
||||
)}}
|
||||
<div class="dicom-store-picker">
|
||||
<gcp-dataset-selector id={{datasetPickerId}} event={{eventName}} oidc-key={{oidcStorageKey}} can-close={{this.canClose}}></gcp-dataset-selector>
|
||||
</div>
|
||||
{{/gcloudDialog}}
|
||||
</template>
|
||||
@ -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();
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,5 @@
|
||||
@import '{ohif:design}/app'
|
||||
|
||||
#dicomStorePicker
|
||||
.modal-dialog
|
||||
width 694px
|
||||
12
Packages/ohif-google-cloud/client/components/index.js
Normal file
12
Packages/ohif-google-cloud/client/components/index.js
Normal file
@ -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';
|
||||
@ -0,0 +1,9 @@
|
||||
<template name="uploadStudiesDialog">
|
||||
{{#gcloudDialog (extend this
|
||||
id='dicomFilesUploader' dialogClass='themed modal-lg'
|
||||
)}}
|
||||
<div class="dicom-files-uploader">
|
||||
<gcp-dicom-uploader id={{dicomFilesUploaderId}} event={{eventName}} oidc-key={{oidcStorageKey}} url={{url}}></gcp-dicom-uploader>
|
||||
</div>
|
||||
{{/gcloudDialog}}
|
||||
</template>
|
||||
@ -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
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,8 @@
|
||||
@import '{ohif:design}/app'
|
||||
|
||||
#dicomFilesUploader
|
||||
.modal-dialog
|
||||
.modal-body
|
||||
padding 0
|
||||
width 536px
|
||||
max-height 563px
|
||||
8
Packages/ohif-google-cloud/client/index.js
Normal file
8
Packages/ohif-google-cloud/client/index.js
Normal file
@ -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';
|
||||
41
Packages/ohif-google-cloud/lib/GCloudAdapter.js
Normal file
41
Packages/ohif-google-cloud/lib/GCloudAdapter.js
Normal file
@ -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;
|
||||
10
Packages/ohif-google-cloud/lib/utils.js
Normal file
10
Packages/ohif-google-cloud/lib/utils.js
Normal file
@ -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);
|
||||
}
|
||||
9
Packages/ohif-google-cloud/main.js
Normal file
9
Packages/ohif-google-cloud/main.js
Normal file
@ -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;
|
||||
}
|
||||
|
||||
|
||||
44
Packages/ohif-google-cloud/package.js
Normal file
44
Packages/ohif-google-cloud/package.js
Normal file
@ -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');
|
||||
});
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
@ -47,6 +47,11 @@ const toolSchema = new SimpleSchema([MeasurementSchemaTypes.CornerstoneToolMeasu
|
||||
label: 'Length',
|
||||
optional: true,
|
||||
decimal: true
|
||||
},
|
||||
dashed: {
|
||||
type: String,
|
||||
label: 'dashed',
|
||||
optional: true
|
||||
}
|
||||
}]);
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
28
Packages/ohif-servers/both/lib/applyCloudServerConfig.js
Normal file
28
Packages/ohif-servers/both/lib/applyCloudServerConfig.js
Normal file
@ -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
|
||||
});
|
||||
};
|
||||
@ -1 +1,2 @@
|
||||
import './getCurrentServer.js';
|
||||
import './applyCloudServerConfig.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';
|
||||
|
||||
@ -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);
|
||||
});
|
||||
};
|
||||
@ -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(',');
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
<template name="studylistResult">
|
||||
<div class="studyListToolbar clearfix">
|
||||
<div class="header pull-left">Study List</div>
|
||||
<div class="header pull-left">
|
||||
<span>Study List</span>
|
||||
<span class="dicom-source">{{dicomSource}}</span>
|
||||
</div>
|
||||
<div class="studyCount pull-right">{{numberOfStudies}}</div>
|
||||
<div class="pull-right">{{>studylistToolbar}}</div>
|
||||
</div>
|
||||
@ -56,7 +59,7 @@
|
||||
class="form-control studylist-search"
|
||||
id="modality">
|
||||
</th>
|
||||
<th class="studyDescription">
|
||||
<!-- <th class="studyDescription">
|
||||
<div id="_studyDescription" class="sortingCell">
|
||||
<span>Study Description</span>
|
||||
<i class="{{sortingColumnsIcons.studyDescription}}"> </i>
|
||||
@ -64,7 +67,7 @@
|
||||
<input type="text"
|
||||
class="form-control studylist-search"
|
||||
id="studyDescription">
|
||||
</th>
|
||||
</th> -->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="studyListData">
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -8,5 +8,11 @@
|
||||
<input id="btnImport" class='js-import-files' type="file" webkitdirectory directory multiple>
|
||||
</div>
|
||||
{{/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}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -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());
|
||||
}
|
||||
});
|
||||
|
||||
@ -25,3 +25,6 @@
|
||||
overflow: hidden
|
||||
position: absolute
|
||||
z-index: -1
|
||||
.uploadStudiesBtn
|
||||
margin 0 20px
|
||||
|
||||
|
||||
@ -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();
|
||||
});
|
||||
|
||||
@ -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' });
|
||||
|
||||
|
||||
|
||||
@ -234,4 +234,41 @@
|
||||
<title>Password</title>
|
||||
<path d="M5 12h8v-3c0-2.203-1.797-4-4-4s-4 1.797-4 4v3zM18 13.5v9c0 0.828-0.672 1.5-1.5 1.5h-15c-0.828 0-1.5-0.672-1.5-1.5v-9c0-0.828 0.672-1.5 1.5-1.5h0.5v-3c0-3.844 3.156-7 7-7s7 3.156 7 7v3h0.5c0.828 0 1.5 0.672 1.5 1.5z"></path>
|
||||
</symbol>
|
||||
</svg>
|
||||
<symbol id="icon-sr" viewBox="0 0 21 21">
|
||||
<title>Structured Report</title>
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g transform="translate(-24.000000, -24.000000)">
|
||||
<g transform="translate(25.000000, 25.000000)">
|
||||
<path d="M16,17 L16,18 C16,18.5522847 15.5522847,19 15,19 L1,19 C0.44771525,19 0,18.5522847 0,18 L0,1 C0,0.44771525 0.44771525,1.59872116e-14 1,1.59872116e-14 L12.5857864,1.59872116e-14 C12.8510029,1.59872116e-14 13.1053568,0.10535684 13.2928932,0.292893219 L15.7071068,2.70710678 C15.8946432,2.89464316 16,3.14899707 16,3.41421356 L16,5" stroke="#91B9CD" stroke-width="2" stroke-linecap="round"></path>
|
||||
<path d="M8.421,8.642 C8.13499857,8.642 7.90950083,8.70983265 7.7445,8.8455 C7.57949918,8.98116734 7.497,9.16083221 7.497,9.3845 C7.497,9.60816778 7.59783233,9.78599934 7.7995,9.918 C8.00116768,10.0500007 8.46682968,10.2058324 9.1965,10.3855 C9.92617032,10.5651676 10.4926647,10.8346649 10.896,11.194 C11.2993353,11.5533351 11.501,12.0776632 11.501,12.767 C11.501,13.4563368 11.2425026,14.0154979 10.7255,14.4445 C10.2084974,14.8735021 9.52833755,15.088 8.685,15.088 C7.46766058,15.088 6.37133821,14.6370045 5.396,13.735 L6.419,12.481 C7.24767081,13.2070036 8.01399648,13.57 8.718,13.57 C9.03333491,13.57 9.28083243,13.5021673 9.4605,13.3665 C9.64016756,13.2308327 9.73,13.0475012 9.73,12.8165 C9.73,12.5854988 9.63466762,12.4021673 9.444,12.2665 C9.25333238,12.1308327 8.87566949,11.993334 8.311,11.854 C7.41632886,11.6413323 6.76183541,11.3645017 6.3475,11.0235 C5.93316459,10.6824983 5.726,10.1471703 5.726,9.4175 C5.726,8.68782968 5.98816404,8.12500198 6.5125,7.729 C7.03683596,7.33299802 7.69132941,7.135 8.476,7.135 C8.9893359,7.135 9.5026641,7.22299912 10.016,7.399 C10.5293359,7.57500088 10.9766648,7.82433172 11.358,8.147 L10.489,9.401 C9.82166333,8.89499747 9.13233689,8.642 8.421,8.642 Z M19.201,9.863 C19.201,11.0950062 18.7133382,11.8906649 17.738,12.25 L19.685,15 L17.573,15 L15.868,12.547 L14.68,12.547 L14.68,15 L12.964,15 L12.964,7.311 L15.879,7.311 C17.0743393,7.311 17.9268308,7.51266465 18.4365,7.916 C18.9461692,8.31933535 19.201,8.96832886 19.201,9.863 Z M15.934,11.062 C16.5206696,11.062 16.9203323,10.9666676 17.133,10.776 C17.3456677,10.5853324 17.452,10.2828354 17.452,9.8685 C17.452,9.45416459 17.3420011,9.17000077 17.122,9.016 C16.9019989,8.86199923 16.5170027,8.785 15.967,8.785 L14.68,8.785 L14.68,11.062 L15.934,11.062 Z" fill="#91B9CD"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</symbol>
|
||||
<symbol id="icon-ps" viewBox="0 0 22 21">
|
||||
<title>Presentation State</title>
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g transform="translate(-22.000000, -64.000000)">
|
||||
<g transform="translate(22.000000, 65.000000)">
|
||||
<path d="M3,3.55271368e-14 L19,3.55271368e-14 C20.6568542,3.66943598e-14 22,1.34314575 22,3 L22,12.4553925 C22,14.1122467 20.6568542,15.4553925 19,15.4553925 L3,15.4553925 C1.34314575,15.4553925 2.02906125e-16,14.1122467 0,12.4553925 L0,3 C-2.02906125e-16,1.34314575 1.34314575,3.5831496e-14 3,3.55271368e-14 Z M7.4305,4.311 L4.7135,4.311 L4.7135,12 L6.4295,12 L6.4295,9.855 L7.4525,9.855 C8.58917235,9.855 9.43433057,9.6350022 9.988,9.195 C10.5416694,8.7549978 10.8185,8.06017141 10.8185,7.1105 C10.8185,6.16082858 10.5490027,5.45683562 10.01,4.9985 C9.47099731,4.54016437 8.61117257,4.311 7.4305,4.311 Z M14.6355,5.642 C15.3468369,5.642 16.0361633,5.89499747 16.7035,6.401 L17.5725,5.147 C17.1911648,4.82433172 16.7438359,4.57500088 16.2305,4.399 C15.7171641,4.22299912 15.2038359,4.135 14.6905,4.135 C13.9058294,4.135 13.251336,4.33299802 12.727,4.729 C12.202664,5.12500198 11.9405,5.68782969 11.9405,6.4175 C11.9405,7.14717032 12.1476646,7.6824983 12.562,8.0235 C12.9763354,8.36450171 13.6308289,8.64133227 14.5255,8.854 C15.0901695,8.99333403 15.4678324,9.13083266 15.6585,9.2665 C15.8491676,9.40216735 15.9445,9.58549885 15.9445,9.8165 C15.9445,10.0475012 15.8546676,10.2308327 15.675,10.3665 C15.4953324,10.5021673 15.2478349,10.57 14.9325,10.57 C14.2284965,10.57 13.4621708,10.2070036 12.6335,9.481 L11.6105,10.735 C12.5858382,11.6370045 13.6821606,12.088 14.8995,12.088 C15.7428376,12.088 16.4229974,11.8735021 16.94,11.4445 C17.4570026,11.0154979 17.7155,10.4563368 17.7155,9.767 C17.7155,9.07766322 17.5138354,8.55333513 17.1105,8.194 C16.7071647,7.83466487 16.1406703,7.56516757 15.411,7.3855 C14.6813297,7.20583244 14.2156677,7.05000066 14.014,6.918 C13.8123323,6.78599934 13.7115,6.60816779 13.7115,6.3845 C13.7115,6.16083222 13.7939992,5.98116735 13.959,5.8455 C14.1240008,5.70983266 14.3494986,5.642 14.6355,5.642 Z M7.5845,8.37 L6.4295,8.37 L6.4295,5.785 L7.4085,5.785 C7.97316949,5.785 8.389332,5.87849906 8.657,6.0655 C8.92466801,6.25250093 9.0585,6.56783111 9.0585,7.0115 C9.0585,7.45516888 8.95583436,7.79249884 8.7505,8.0235 C8.54516564,8.25450115 8.15650286,8.37 7.5845,8.37 Z" fill="#91B9CD"></path>
|
||||
<path d="M11,15 L11,18" stroke="#91B9CD" stroke-width="2" stroke-linecap="round"></path>
|
||||
<path d="M11,-1.11022302e-15 L11,1" stroke="#91B9CD" stroke-width="2" stroke-linecap="round"></path>
|
||||
<path d="M7,19 L11,18" stroke="#91B9CD" stroke-width="2" stroke-linecap="round"></path>
|
||||
<path d="M11,19 L15,18" stroke="#91B9CD" stroke-width="2" stroke-linecap="round" transform="translate(13.000000, 18.000000) scale(-1, 1) translate(-13.000000, -18.000000) "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</symbol>
|
||||
<symbol id="google-icon" viewBox="0 0 24 24">
|
||||
<desc>Google icon</desc>
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g transform="translate(3.000000, 3.000000)">
|
||||
<path d="M17.64,9.20454545 C17.64,8.56636364 17.5827273,7.95272727 17.4763636,7.36363636 L9,7.36363636 L9,10.845 L13.8436364,10.845 C13.635,11.97 13.0009091,12.9231818 12.0477273,13.5613636 L12.0477273,15.8195455 L14.9563636,15.8195455 C16.6581818,14.2527273 17.64,11.9454545 17.64,9.20454545 L17.64,9.20454545 Z" id="Shape" fill="#4285F4"></path>
|
||||
<path d="M9,18 C11.43,18 13.4672727,17.1940909 14.9563636,15.8195455 L12.0477273,13.5613636 C11.2418182,14.1013636 10.2109091,14.4204545 9,14.4204545 C6.65590909,14.4204545 4.67181818,12.8372727 3.96409091,10.71 L0.957272727,10.71 L0.957272727,13.0418182 C2.43818182,15.9831818 5.48181818,18 9,18 L9,18 Z" id="Shape" fill="#34A853"></path>
|
||||
<path d="M3.96409091,10.71 C3.78409091,10.17 3.68181818,9.59318182 3.68181818,9 C3.68181818,8.40681818 3.78409091,7.83 3.96409091,7.29 L3.96409091,4.95818182 L0.957272727,4.95818182 C0.347727273,6.17318182 0,7.54772727 0,9 C0,10.4522727 0.347727273,11.8268182 0.957272727,13.0418182 L3.96409091,10.71 L3.96409091,10.71 Z" id="Shape" fill="#FBBC05"></path>
|
||||
<path d="M9,3.57954545 C10.3213636,3.57954545 11.5077273,4.03363636 12.4404545,4.92545455 L15.0218182,2.34409091 C13.4631818,0.891818182 11.4259091,0 9,0 C5.48181818,0 2.43818182,2.01681818 0.957272727,4.95818182 L3.96409091,7.29 C4.67181818,5.16272727 6.65590909,3.57954545 9,3.57954545 L9,3.57954545 Z" id="Shape" fill="#EA4335"></path>
|
||||
<polygon id="Shape" points="0 0 18 0 18 18 0 18"></polygon>
|
||||
</g>
|
||||
</g>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 27 KiB |
@ -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;
|
||||
}
|
||||
|
||||
@ -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 };
|
||||
|
||||
@ -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 };
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
3
clientImage/Dockerfile
Normal file
3
clientImage/Dockerfile
Normal file
@ -0,0 +1,3 @@
|
||||
FROM nginx:stable-alpine-perl
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
COPY build /usr/share/nginx/html/
|
||||
17
clientImage/howTo.md
Normal file
17
clientImage/howTo.md
Normal file
@ -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
|
||||
|
||||
```
|
||||
44
clientImage/nginx.conf
Normal file
44
clientImage/nginx.conf
Normal file
@ -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}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
53
config/oidc-demo.json
Normal file
53
config/oidc-demo.json
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
53
config/oidc-dev.json
Normal file
53
config/oidc-dev.json
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
36
config/oidc.json
Normal file
36
config/oidc.json
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
@ -20,7 +20,7 @@
|
||||
{
|
||||
"host": "0.0.0.0",
|
||||
"port": 11112,
|
||||
"aeTitle": "OHIFDCM",
|
||||
"aeTitle": "ORTHANC",
|
||||
"default": true,
|
||||
"server": true
|
||||
}
|
||||
|
||||
36
ghc-dockerfile
Normal file
36
ghc-dockerfile
Normal file
@ -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/
|
||||
|
||||
442
package-lock.json
generated
442
package-lock.json
generated
@ -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
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user