Work on refactoring measurements out of Lesion Tracker package
- Removed AssociatedStudies Collection - Measurement API is generated from configuration files - Data exchange methods are defined in configuration
This commit is contained in:
parent
c511edd419
commit
ab4d4c9008
@ -28,17 +28,19 @@ clinical:hipaa-logger
|
|||||||
|
|
||||||
aldeed:simple-schema # Third party package to deal with schemas
|
aldeed:simple-schema # Third party package to deal with schemas
|
||||||
|
|
||||||
|
# OHIF Packages
|
||||||
design
|
design
|
||||||
ohif:core
|
ohif:core
|
||||||
|
ohif:cornerstone
|
||||||
|
ohif:viewerbase
|
||||||
|
ohif:study-list
|
||||||
|
ohif:dicom-services
|
||||||
|
ohif:hanging-protocols
|
||||||
|
ohif:user-management
|
||||||
|
ohif:lesiontracker
|
||||||
|
|
||||||
insecure@1.0.7 # Allow all DB writes from clients (for prototyping)
|
|
||||||
cornerstone
|
|
||||||
worklist
|
|
||||||
viewerbase
|
|
||||||
dicomweb
|
|
||||||
accounts-base@1.2.14
|
accounts-base@1.2.14
|
||||||
accounts-password@1.3.1
|
accounts-password@1.3.1
|
||||||
|
|
||||||
ian:accounts-ui-bootstrap-3
|
ian:accounts-ui-bootstrap-3
|
||||||
twbs:bootstrap
|
twbs:bootstrap
|
||||||
http@1.2.10
|
http@1.2.10
|
||||||
@ -49,7 +51,6 @@ fortawesome:fontawesome
|
|||||||
random@1.0.10
|
random@1.0.10
|
||||||
reactive-var@1.0.11
|
reactive-var@1.0.11
|
||||||
reactive-dict@1.1.8
|
reactive-dict@1.1.8
|
||||||
lesiontracker
|
|
||||||
practicalmeteor:loglevel
|
practicalmeteor:loglevel
|
||||||
momentjs:moment
|
momentjs:moment
|
||||||
|
|
||||||
@ -61,10 +62,10 @@ gilbertwat:bootstrap3-daterangepicker
|
|||||||
email@1.1.18
|
email@1.1.18
|
||||||
peppelg:bootstrap-3-modal
|
peppelg:bootstrap-3-modal
|
||||||
simple:reactive-method
|
simple:reactive-method
|
||||||
hangingprotocols
|
|
||||||
fastclick@1.0.13
|
fastclick@1.0.13
|
||||||
standard-minifier-css@1.3.2
|
standard-minifier-css@1.3.2
|
||||||
standard-minifier-js@1.2.1
|
standard-minifier-js@1.2.1
|
||||||
johdirr:meteor-git-rev
|
johdirr:meteor-git-rev
|
||||||
u2622:persistent-session
|
u2622:persistent-session
|
||||||
shell-server@0.2.1
|
shell-server@0.2.1
|
||||||
|
aldeed:collection2
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
accounts-base@1.2.14
|
accounts-base@1.2.14
|
||||||
accounts-password@1.3.1
|
accounts-password@1.3.1
|
||||||
|
aldeed:collection2@2.10.0
|
||||||
|
aldeed:collection2-core@1.2.0
|
||||||
|
aldeed:schema-deny@1.1.0
|
||||||
|
aldeed:schema-index@1.1.1
|
||||||
aldeed:simple-schema@1.5.3
|
aldeed:simple-schema@1.5.3
|
||||||
aldeed:template-extension@4.0.0
|
aldeed:template-extension@4.0.0
|
||||||
allow-deny@1.0.5
|
allow-deny@1.0.5
|
||||||
@ -9,10 +13,10 @@ anti:i18n@0.4.3
|
|||||||
arsnebula:reactive-promise@0.9.1
|
arsnebula:reactive-promise@0.9.1
|
||||||
autoupdate@1.3.12
|
autoupdate@1.3.12
|
||||||
babel-compiler@6.13.0
|
babel-compiler@6.13.0
|
||||||
babel-runtime@0.1.12
|
babel-runtime@0.1.13
|
||||||
base64@1.0.10
|
base64@1.0.10
|
||||||
binary-heap@1.0.10
|
binary-heap@1.0.10
|
||||||
blaze@2.1.9
|
blaze@2.2.0
|
||||||
blaze-html-templates@1.0.5
|
blaze-html-templates@1.0.5
|
||||||
blaze-tools@1.0.10
|
blaze-tools@1.0.10
|
||||||
boilerplate-generator@1.0.11
|
boilerplate-generator@1.0.11
|
||||||
@ -33,7 +37,6 @@ clinical:router-middleware-stack@2.1.2
|
|||||||
clinical:router-url@2.1.0
|
clinical:router-url@2.1.0
|
||||||
clinical:theming@0.4.10
|
clinical:theming@0.4.10
|
||||||
coffeescript@1.11.1_2
|
coffeescript@1.11.1_2
|
||||||
cornerstone@0.0.1
|
|
||||||
ddp@1.2.5
|
ddp@1.2.5
|
||||||
ddp-client@1.3.2
|
ddp-client@1.3.2
|
||||||
ddp-common@1.2.7
|
ddp-common@1.2.7
|
||||||
@ -41,9 +44,7 @@ ddp-rate-limiter@1.0.6
|
|||||||
ddp-server@1.3.11
|
ddp-server@1.3.11
|
||||||
deps@1.0.12
|
deps@1.0.12
|
||||||
design@0.0.1
|
design@0.0.1
|
||||||
dicomweb@0.0.1
|
|
||||||
diff-sequence@1.0.7
|
diff-sequence@1.0.7
|
||||||
dimseservice@0.0.1
|
|
||||||
ecmascript@0.5.9
|
ecmascript@0.5.9
|
||||||
ecmascript-runtime@0.3.15
|
ecmascript-runtime@0.3.15
|
||||||
ejson@1.0.13
|
ejson@1.0.13
|
||||||
@ -54,14 +55,12 @@ fortawesome:fontawesome@4.6.3
|
|||||||
geojson-utils@1.0.10
|
geojson-utils@1.0.10
|
||||||
gilbertwat:bootstrap3-daterangepicker@1.3.21_1
|
gilbertwat:bootstrap3-daterangepicker@1.3.21_1
|
||||||
grove:less@0.2.0
|
grove:less@0.2.0
|
||||||
hangingprotocols@0.0.1
|
|
||||||
hot-code-push@1.0.4
|
hot-code-push@1.0.4
|
||||||
html-tools@1.0.11
|
html-tools@1.0.11
|
||||||
htmljs@1.0.11
|
htmljs@1.0.11
|
||||||
http@1.2.10
|
http@1.2.10
|
||||||
ian:accounts-ui-bootstrap-3@1.2.89
|
ian:accounts-ui-bootstrap-3@1.2.89
|
||||||
id-map@1.0.9
|
id-map@1.0.9
|
||||||
insecure@1.0.7
|
|
||||||
iron:controller@1.0.12
|
iron:controller@1.0.12
|
||||||
iron:core@1.0.11
|
iron:core@1.0.11
|
||||||
iron:dynamic-template@1.0.12
|
iron:dynamic-template@1.0.12
|
||||||
@ -69,7 +68,6 @@ iron:layout@1.0.12
|
|||||||
johdirr:meteor-git-rev@0.0.4
|
johdirr:meteor-git-rev@0.0.4
|
||||||
jquery@1.11.10
|
jquery@1.11.10
|
||||||
launch-screen@1.1.0
|
launch-screen@1.1.0
|
||||||
lesiontracker@0.0.1
|
|
||||||
livedata@1.0.18
|
livedata@1.0.18
|
||||||
localstorage@1.0.12
|
localstorage@1.0.12
|
||||||
logging@1.1.16
|
logging@1.1.16
|
||||||
@ -93,12 +91,21 @@ npm-bcrypt@0.9.2
|
|||||||
npm-mongo@2.2.11_2
|
npm-mongo@2.2.11_2
|
||||||
observe-sequence@1.0.14
|
observe-sequence@1.0.14
|
||||||
ohif:core@0.0.1
|
ohif:core@0.0.1
|
||||||
|
ohif:cornerstone@0.0.1
|
||||||
|
ohif:dicom-services@0.0.1
|
||||||
|
ohif:hanging-protocols@0.0.1
|
||||||
|
ohif:lesiontracker@0.0.1
|
||||||
|
ohif:measurements@0.0.1
|
||||||
|
ohif:study-list@0.0.1
|
||||||
|
ohif:user-management@0.0.1
|
||||||
|
ohif:viewerbase@0.0.1
|
||||||
|
ohif:wadoproxy@0.0.1
|
||||||
ordered-dict@1.0.9
|
ordered-dict@1.0.9
|
||||||
orthanc-remote@0.0.1
|
|
||||||
peppelg:bootstrap-3-modal@1.0.4
|
peppelg:bootstrap-3-modal@1.0.4
|
||||||
practicalmeteor:chai@2.1.0_1
|
practicalmeteor:chai@2.1.0_1
|
||||||
practicalmeteor:loglevel@1.2.0_2
|
practicalmeteor:loglevel@1.2.0_2
|
||||||
promise@0.8.8
|
promise@0.8.8
|
||||||
|
raix:eventemitter@0.1.3
|
||||||
random@1.0.10
|
random@1.0.10
|
||||||
rate-limit@1.0.6
|
rate-limit@1.0.6
|
||||||
reactive-dict@1.1.8
|
reactive-dict@1.1.8
|
||||||
@ -106,7 +113,6 @@ reactive-var@1.0.11
|
|||||||
reload@1.1.11
|
reload@1.1.11
|
||||||
retry@1.0.9
|
retry@1.0.9
|
||||||
routepolicy@1.0.12
|
routepolicy@1.0.12
|
||||||
rwatts:uuid@0.1.0
|
|
||||||
service-configuration@1.0.11
|
service-configuration@1.0.11
|
||||||
session@1.1.7
|
session@1.1.7
|
||||||
sha@1.0.9
|
sha@1.0.9
|
||||||
@ -133,9 +139,6 @@ ui@1.0.12
|
|||||||
underscore@1.0.10
|
underscore@1.0.10
|
||||||
url@1.0.11
|
url@1.0.11
|
||||||
validatejs@0.0.1
|
validatejs@0.0.1
|
||||||
viewerbase@0.0.1
|
|
||||||
wadoproxy@0.0.1
|
|
||||||
webapp@1.3.12
|
webapp@1.3.12
|
||||||
webapp-hashing@1.0.9
|
webapp-hashing@1.0.9
|
||||||
worklist@0.0.1
|
|
||||||
zuuk:stale-session@1.0.8
|
zuuk:stale-session@1.0.8
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
<template name="lesionTracker">
|
<template name="app">
|
||||||
<div class="topBar noselect {{#if onStudyList}}studyList{{/if}}">
|
<div class="topBar noselect {{#if onStudyList}}studyList{{/if}}">
|
||||||
<div class="clearfix p-x-1 p-t-1">
|
<div class="clearfix p-x-1 p-t-1">
|
||||||
<a target='_blank'
|
<a target='_blank'
|
||||||
class="brandSection pull-left"
|
class="brandSection pull-left"
|
||||||
href="http://ohif.org">
|
href="http://ohif.org">
|
||||||
<svg class="logoImage">
|
<svg class="logoImage">
|
||||||
<use xlink:href="/packages/viewerbase/assets/icons.svg#icon-ohif-logo"></use>
|
<use xlink:href="/packages/ohif_viewerbase/assets/icons.svg#icon-ohif-logo"></use>
|
||||||
</svg>
|
</svg>
|
||||||
<div class="logoText">
|
<div class="logoText">
|
||||||
Open Health Imaging Foundation
|
Open Health Imaging Foundation
|
||||||
@ -22,10 +22,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ >timeoutCountdownDialog }}
|
{{ >timeoutCountdownDialog }}
|
||||||
<div id="worklistTabs" class="tab-content">
|
<div id="studylistTabs" class="tab-content">
|
||||||
<div class="tab-pane active" id="worklistTab">
|
<div class="tab-pane active" id="studylistTab">
|
||||||
<div class="worklistContainer">
|
<div class="studylistContainer">
|
||||||
{{> worklistResult }}
|
{{> studylistResult }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane" id="viewerTab">
|
<div class="tab-pane" id="viewerTab">
|
||||||
@ -37,6 +37,6 @@
|
|||||||
{{ >confirmRemoveTimepointAssociation }}
|
{{ >confirmRemoveTimepointAssociation }}
|
||||||
{{ >lastLoginModal }}
|
{{ >lastLoginModal }}
|
||||||
{{ >studyContextMenu }}
|
{{ >studyContextMenu }}
|
||||||
{{ >progressDialog}}
|
{{ >progressDialog }}
|
||||||
{{ >viewSeriesDetailsModal }}
|
{{ >viewSeriesDetailsModal }}
|
||||||
</template>
|
</template>
|
||||||
@ -1,26 +1,26 @@
|
|||||||
const worklistContentId = 'worklistTab';
|
const studylistContentId = 'studylistTab';
|
||||||
const viewerContentId = 'viewerTab';
|
const viewerContentId = 'viewerTab';
|
||||||
|
|
||||||
// Define the ViewerData global object
|
// Define the ViewerData global object
|
||||||
// If there is currently any Session data for this object,
|
// If there is currently any Session data for this object,
|
||||||
// use this to repopulate the variable
|
// use this to repopulate the variable
|
||||||
Template.lesionTracker.onCreated(() => {
|
Template.app.onCreated(() => {
|
||||||
ViewerData = Session.get('ViewerData') || {};
|
ViewerData = Session.get('ViewerData') || {};
|
||||||
});
|
});
|
||||||
|
|
||||||
Template.lesionTracker.events({
|
Template.app.events({
|
||||||
'click .js-toggle-studyList'() {
|
'click .js-toggle-studyList'() {
|
||||||
const contentId = Session.get('activeContentId');
|
const contentId = Session.get('activeContentId');
|
||||||
|
|
||||||
if (contentId !== worklistContentId) {
|
if (contentId !== studylistContentId) {
|
||||||
switchToTab(worklistContentId);
|
switchToTab(studylistContentId);
|
||||||
} else {
|
} else {
|
||||||
switchToTab(viewerContentId);
|
switchToTab(viewerContentId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Template.lesionTracker.helpers({
|
Template.app.helpers({
|
||||||
studyListToggleText() {
|
studyListToggleText() {
|
||||||
const contentId = Session.get('activeContentId');
|
const contentId = Session.get('activeContentId');
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ Template.lesionTracker.helpers({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contentId === worklistContentId) {
|
if (contentId === studylistContentId) {
|
||||||
return 'Back to viewer';
|
return 'Back to viewer';
|
||||||
} else if (contentId === viewerContentId) {
|
} else if (contentId === viewerContentId) {
|
||||||
return 'Study list';
|
return 'Study list';
|
||||||
@ -39,7 +39,7 @@ Template.lesionTracker.helpers({
|
|||||||
},
|
},
|
||||||
|
|
||||||
onStudyList() {
|
onStudyList() {
|
||||||
return (Session.get('activeContentId') === 'worklistTab');
|
return (Session.get('activeContentId') === 'studylistTab');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ $expandedHeight = 160px
|
|||||||
top: 5px
|
top: 5px
|
||||||
left: 5px
|
left: 5px
|
||||||
|
|
||||||
#worklistTabs
|
#studylistTabs
|
||||||
height: "calc(100% - %s)" % $topBarHeight
|
height: "calc(100% - %s)" % $topBarHeight
|
||||||
|
|
||||||
.tab-pane
|
.tab-pane
|
||||||
@ -20,10 +20,10 @@
|
|||||||
<div class="pull-right m-t-1 rm-x-1">
|
<div class="pull-right m-t-1 rm-x-1">
|
||||||
{{>roundedButtonGroup rightSidebarToggleButtonData}}
|
{{>roundedButtonGroup rightSidebarToggleButtonData}}
|
||||||
</div>
|
</div>
|
||||||
<div id="toggleHUD" class="toolbarSectionButton pull-right rp-x-1 m-t-1 rm-r-3 {{#if session 'lesionTableHudOpen'}}active{{/if}}">
|
<div id="toggleHUD" class="toolbarSectionButton pull-right rp-x-1 m-t-1 rm-r-3 {{#if session 'measurementTableHudOpen'}}active{{/if}}">
|
||||||
<div class="svgContainer">
|
<div class="svgContainer">
|
||||||
<svg>
|
<svg>
|
||||||
<use xlink:href="/packages/viewerbase/assets/icons.svg#icon-hud"></use>
|
<use xlink:href="/packages/ohif_viewerbase/assets/icons.svg#icon-hud"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttonLabel">HUD</div>
|
<div class="buttonLabel">HUD</div>
|
||||||
@ -31,7 +31,7 @@
|
|||||||
<div class="toolbarSectionButton pull-right rp-x-1 m-t-1 rm-r-1" data-toggle="modal" data-target="#optionsModal">
|
<div class="toolbarSectionButton pull-right rp-x-1 m-t-1 rm-r-1" data-toggle="modal" data-target="#optionsModal">
|
||||||
<div class="svgContainer">
|
<div class="svgContainer">
|
||||||
<svg>
|
<svg>
|
||||||
<use xlink:href="/packages/viewerbase/assets/icons.svg#icon-trial-info"></use>
|
<use xlink:href="/packages/ohif_viewerbase/assets/icons.svg#icon-trial-info"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttonLabel">Trial</div>
|
<div class="buttonLabel">Trial</div>
|
||||||
@ -22,7 +22,7 @@ Template.toolbarSection.helpers({
|
|||||||
value: instance.data.state,
|
value: instance.data.state,
|
||||||
options: [{
|
options: [{
|
||||||
value: 'studies',
|
value: 'studies',
|
||||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-studies',
|
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-studies',
|
||||||
svgWidth: 15,
|
svgWidth: 15,
|
||||||
svgHeight: 13,
|
svgHeight: 13,
|
||||||
bottomLabel: 'Studies'
|
bottomLabel: 'Studies'
|
||||||
@ -37,75 +37,73 @@ Template.toolbarSection.helpers({
|
|||||||
key: 'rightSidebar',
|
key: 'rightSidebar',
|
||||||
value: instance.data.state,
|
value: instance.data.state,
|
||||||
options: [{
|
options: [{
|
||||||
value: 'lesions',
|
value: 'measurements',
|
||||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-measurements-lesions',
|
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-measurements-lesions',
|
||||||
svgWidth: 18,
|
svgWidth: 18,
|
||||||
svgHeight: 10,
|
svgHeight: 10,
|
||||||
bottomLabel: 'Lesions'
|
bottomLabel: 'Measurements'
|
||||||
}, {
|
|
||||||
value: 'additional',
|
|
||||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-measurements-additional',
|
|
||||||
svgWidth: 14,
|
|
||||||
svgHeight: 13,
|
|
||||||
bottomLabel: 'Additional'
|
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
toolbarButtons() {
|
toolbarButtons() {
|
||||||
// Check if the measure tools shall be disabled
|
// Check if the measure tools shall be disabled
|
||||||
const isMesureDisabled = !Template.instance().data.timepointApi.currentTimepointId;
|
const isToolDisabled = false; //!Template.instance().data.timepointApi;
|
||||||
|
|
||||||
const buttonData = [];
|
const buttonData = [];
|
||||||
|
|
||||||
buttonData.push({
|
buttonData.push({
|
||||||
id: 'zoom',
|
id: 'zoom',
|
||||||
title: 'Zoom',
|
title: 'Zoom',
|
||||||
classes: 'imageViewerTool',
|
classes: 'imageViewerTool',
|
||||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-zoom'
|
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-zoom'
|
||||||
});
|
});
|
||||||
|
|
||||||
buttonData.push({
|
buttonData.push({
|
||||||
id: 'wwwc',
|
id: 'wwwc',
|
||||||
title: 'Levels',
|
title: 'Levels',
|
||||||
classes: 'imageViewerTool',
|
classes: 'imageViewerTool',
|
||||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-levels'
|
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-levels'
|
||||||
});
|
});
|
||||||
|
|
||||||
buttonData.push({
|
buttonData.push({
|
||||||
id: 'pan',
|
id: 'pan',
|
||||||
title: 'Pan',
|
title: 'Pan',
|
||||||
classes: 'imageViewerTool',
|
classes: 'imageViewerTool',
|
||||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-pan'
|
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-pan'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO: design the link functionality
|
||||||
|
Commenting this out until we build this tool
|
||||||
buttonData.push({
|
buttonData.push({
|
||||||
id: 'link',
|
id: 'link',
|
||||||
title: 'Link',
|
title: 'Link',
|
||||||
classes: 'imageViewerCommand toolbarSectionButton',
|
classes: 'imageViewerCommand toolbarSectionButton',
|
||||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-link'
|
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-link'
|
||||||
});
|
});*/
|
||||||
|
|
||||||
buttonData.push({
|
buttonData.push({
|
||||||
id: 'bidirectional',
|
id: 'bidirectional',
|
||||||
title: 'Target',
|
title: 'Target',
|
||||||
classes: 'imageViewerTool rm-l-3',
|
classes: 'imageViewerTool rm-l-3',
|
||||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-target',
|
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-measure-target',
|
||||||
disabled: isMesureDisabled
|
disabled: isToolDisabled
|
||||||
});
|
});
|
||||||
|
|
||||||
buttonData.push({
|
buttonData.push({
|
||||||
id: 'nonTarget',
|
id: 'nonTarget',
|
||||||
title: 'Non-Target',
|
title: 'Non-Target',
|
||||||
classes: 'imageViewerTool toolbarSectionButton',
|
classes: 'imageViewerTool toolbarSectionButton',
|
||||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-non-target',
|
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-measure-non-target',
|
||||||
disabled: isMesureDisabled
|
disabled: isToolDisabled
|
||||||
});
|
});
|
||||||
|
|
||||||
buttonData.push({
|
buttonData.push({
|
||||||
id: 'length',
|
id: 'length',
|
||||||
title: 'Temp',
|
title: 'Temp',
|
||||||
classes: 'imageViewerTool toolbarSectionButton',
|
classes: 'imageViewerTool toolbarSectionButton',
|
||||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-temp'
|
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-measure-temp'
|
||||||
});
|
});
|
||||||
|
|
||||||
return buttonData;
|
return buttonData;
|
||||||
@ -113,7 +111,7 @@ Template.toolbarSection.helpers({
|
|||||||
|
|
||||||
extraToolbarButtons() {
|
extraToolbarButtons() {
|
||||||
// Check if the measure tools shall be disabled
|
// Check if the measure tools shall be disabled
|
||||||
const isMesureDisabled = !Template.instance().data.timepointApi.currentTimepointId;
|
const isToolDisabled = false; //!Template.instance().data.timepointApi.currentTimepointId;
|
||||||
const buttonData = [];
|
const buttonData = [];
|
||||||
|
|
||||||
// TODO: Get real icons for CR / UN / EX
|
// TODO: Get real icons for CR / UN / EX
|
||||||
@ -121,24 +119,24 @@ Template.toolbarSection.helpers({
|
|||||||
id: 'crTool',
|
id: 'crTool',
|
||||||
title: 'CR Tool',
|
title: 'CR Tool',
|
||||||
classes: 'imageViewerTool toolbarSectionButton',
|
classes: 'imageViewerTool toolbarSectionButton',
|
||||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-temp',
|
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-measure-temp',
|
||||||
disabled: isMesureDisabled
|
disabled: isToolDisabled
|
||||||
});
|
});
|
||||||
|
|
||||||
buttonData.push({
|
buttonData.push({
|
||||||
id: 'unTool',
|
id: 'unTool',
|
||||||
title: 'UN Tool',
|
title: 'UN Tool',
|
||||||
classes: 'imageViewerTool toolbarSectionButton',
|
classes: 'imageViewerTool toolbarSectionButton',
|
||||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-temp',
|
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-measure-temp',
|
||||||
disabled: isMesureDisabled
|
disabled: isToolDisabled
|
||||||
});
|
});
|
||||||
|
|
||||||
buttonData.push({
|
buttonData.push({
|
||||||
id: 'exTool',
|
id: 'exTool',
|
||||||
title: 'EX Tool',
|
title: 'EX Tool',
|
||||||
classes: 'imageViewerTool toolbarSectionButton',
|
classes: 'imageViewerTool toolbarSectionButton',
|
||||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-temp',
|
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-measure-temp',
|
||||||
disabled: isMesureDisabled
|
disabled: isToolDisabled
|
||||||
});
|
});
|
||||||
|
|
||||||
return buttonData;
|
return buttonData;
|
||||||
@ -147,8 +145,8 @@ Template.toolbarSection.helpers({
|
|||||||
|
|
||||||
Template.toolbarSection.events({
|
Template.toolbarSection.events({
|
||||||
'click #toggleHUD'() {
|
'click #toggleHUD'() {
|
||||||
const state = Session.get('lesionTableHudOpen');
|
const state = Session.get('measurementTableHudOpen');
|
||||||
Session.set('lesionTableHudOpen', !state);
|
Session.set('measurementTableHudOpen', !state);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
17
LesionTracker/client/components/viewer/viewer.html
Normal file
17
LesionTracker/client/components/viewer/viewer.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<template name="viewer">
|
||||||
|
{{#if and Template.subscriptionsReady dataSourcesReady}}
|
||||||
|
<div class="viewerDialogs">
|
||||||
|
{{>confirmDeleteDialog}}
|
||||||
|
{{>measurementLocationDialog (clone this)}}
|
||||||
|
{{>nonTargetLesionDialog (clone this)}}
|
||||||
|
{{>nonTargetResponseDialog (clone this)}}
|
||||||
|
{{>measurementTableHUD (clone this)}}
|
||||||
|
</div>
|
||||||
|
<div id="viewer">
|
||||||
|
{{>toolbarSection (clone this)}}
|
||||||
|
{{>viewerSection (clone this)}}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
{{>loadingText}}
|
||||||
|
{{/if}}
|
||||||
|
</template>
|
||||||
113
LesionTracker/client/components/viewer/viewer.js
Normal file
113
LesionTracker/client/components/viewer/viewer.js
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import { OHIF } from 'meteor/ohif:core';
|
||||||
|
import { TimepointsConfiguration } from 'meteor/ohif:measurements/both/configuration/timepoints';
|
||||||
|
import { MeasurementsConfiguration } from 'meteor/ohif:measurements/both/configuration/measurements';
|
||||||
|
import { MeasurementHandlers } from 'meteor/ohif:measurements/client/lib/MeasurementHandlers';
|
||||||
|
|
||||||
|
Session.set('TimepointsReady', false);
|
||||||
|
Session.set('MeasurementsReady', false);
|
||||||
|
|
||||||
|
Template.viewer.onCreated(() => {
|
||||||
|
const TimepointApi = TimepointsConfiguration.getTimepointsApi();
|
||||||
|
const MeasurementApi = MeasurementsConfiguration.getMeasurementsApi();
|
||||||
|
|
||||||
|
OHIF.viewer = OHIF.viewer || {};
|
||||||
|
ViewerData = window.ViewerData || ViewerData;
|
||||||
|
|
||||||
|
const instance = Template.instance();
|
||||||
|
|
||||||
|
ValidationErrors.remove({});
|
||||||
|
|
||||||
|
instance.data.state = new ReactiveDict();
|
||||||
|
instance.data.state.set('leftSidebar', Session.get('leftSidebar'));
|
||||||
|
instance.data.state.set('rightSidebar', Session.get('rightSidebar'));
|
||||||
|
|
||||||
|
const contentId = instance.data.contentId;
|
||||||
|
|
||||||
|
OHIF.viewer.functionList = $.extend(OHIF.viewer.functionList, {
|
||||||
|
toggleLesionTrackerTools: toggleLesionTrackerTools,
|
||||||
|
clearTools: clearTools,
|
||||||
|
bidirectional: () => {
|
||||||
|
// Used for hotkeys
|
||||||
|
toolManager.setActiveTool('bidirectional');
|
||||||
|
},
|
||||||
|
nonTarget: () => {
|
||||||
|
// Used for hotkeys
|
||||||
|
toolManager.setActiveTool('nonTarget');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// The hotkey can also be an array (e.g. ["NUMPAD0", "0"])
|
||||||
|
OHIF.viewer.defaultHotkeys = OHIF.viewer.defaultHotkeys || {};
|
||||||
|
OHIF.viewer.defaultHotkeys.toggleLesionTrackerTools = 'O';
|
||||||
|
OHIF.viewer.defaultHotkeys.bidirectional = 'T'; // Target
|
||||||
|
OHIF.viewer.defaultHotkeys.nonTarget = 'N'; // Non-target
|
||||||
|
|
||||||
|
if (ViewerData[contentId].loadedSeriesData) {
|
||||||
|
log.info('Reloading previous loadedSeriesData');
|
||||||
|
OHIF.viewer.loadedSeriesData = ViewerData[contentId].loadedSeriesData;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
log.info('Setting default ViewerData');
|
||||||
|
OHIF.viewer.loadedSeriesData = {};
|
||||||
|
ViewerData[contentId].loadedSeriesData = {};
|
||||||
|
Session.set('ViewerData', ViewerData);
|
||||||
|
}
|
||||||
|
|
||||||
|
Session.set('activeViewport', ViewerData[contentId].activeViewport || false);
|
||||||
|
|
||||||
|
// Set lesion tool buttons as disabled if pixel spacing is not available for active element
|
||||||
|
instance.autorun(pixelSpacingAutorunCheck);
|
||||||
|
|
||||||
|
// Update the ViewerStudies collection with the loaded studies
|
||||||
|
ViewerStudies.remove({});
|
||||||
|
|
||||||
|
instance.data.studies.forEach(study => {
|
||||||
|
study.selected = true;
|
||||||
|
study.displaySets = createStacks(study);
|
||||||
|
ViewerStudies.insert(study);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (instance.data.currentTimepointId) {
|
||||||
|
instance.data.timepointApi = new TimepointApi(instance.data.currentTimepointId);
|
||||||
|
const timepointsPromise = instance.data.timepointApi.retrieveTimepoints();
|
||||||
|
timepointsPromise.then(() => {
|
||||||
|
Session.set('TimepointsReady', true);
|
||||||
|
})
|
||||||
|
|
||||||
|
instance.data.measurementApi = new MeasurementApi(instance.data.currentTimepointId);
|
||||||
|
const measurementsPromise = instance.data.measurementApi.retrieveMeasurements();
|
||||||
|
measurementsPromise.then(() => {
|
||||||
|
Session.set('MeasurementsReady', true);
|
||||||
|
|
||||||
|
instance.data.measurementApi.syncMeasurementsAndToolData();
|
||||||
|
})
|
||||||
|
|
||||||
|
// Provide the necessary data to the Measurement API and Timepoint API
|
||||||
|
const prior = instance.data.timepointApi.prior();
|
||||||
|
if (prior) {
|
||||||
|
instance.data.measurementApi.priorTimepointId = prior.timepointId;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('No current timepoint specified');
|
||||||
|
instance.data.measurementApi = new MeasurementApi();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.viewer.helpers({
|
||||||
|
dataSourcesReady() {
|
||||||
|
// TODO: Find a better way to do this
|
||||||
|
return Session.get('TimepointsReady') && Session.get('MeasurementsReady');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Template.viewer.events({
|
||||||
|
'CornerstoneToolsMeasurementAdded .imageViewerViewport'(event, instance, eventData) {
|
||||||
|
MeasurementHandlers.onAdded(event, instance, eventData);
|
||||||
|
},
|
||||||
|
'CornerstoneToolsMeasurementModified .imageViewerViewport'(event, instance, eventData) {
|
||||||
|
MeasurementHandlers.onModified(event, instance, eventData);
|
||||||
|
},
|
||||||
|
'CornerstoneToolsMeasurementRemoved .imageViewerViewport'(event, instance, eventData) {
|
||||||
|
MeasurementHandlers.onRemoved(event, instance, eventData);
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
<template name="viewerSection">
|
||||||
|
<div class="viewerSection">
|
||||||
|
<div class="sidebarMenu sidebar-left {{#if leftSidebarOpen}}sidebar-open{{/if}}">
|
||||||
|
{{>studyTimepointBrowser (clone this)}}
|
||||||
|
</div>
|
||||||
|
<div class="mainContent {{#if leftSidebarOpen}}sidebar-left-open{{/if}} {{#if rightSidebarOpen}}sidebar-right-open{{/if}}">
|
||||||
|
{{>viewerMain (clone this)}}
|
||||||
|
</div>
|
||||||
|
<div class="sidebarMenu sidebar-right {{#if rightSidebarOpen}}sidebar-open{{/if}}">
|
||||||
|
{{>measurementTable (clone this)}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -1,4 +1,4 @@
|
|||||||
Template.flexboxLayout.events({
|
Template.viewerSection.events({
|
||||||
'transitionend .sidebarMenu'(event) {
|
'transitionend .sidebarMenu'(event) {
|
||||||
if (!event.target.classList.contains('sidebarMenu')) {
|
if (!event.target.classList.contains('sidebarMenu')) {
|
||||||
return;
|
return;
|
||||||
@ -8,20 +8,12 @@ Template.flexboxLayout.events({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Template.flexboxLayout.helpers({
|
Template.viewerSection.helpers({
|
||||||
leftSidebarOpen() {
|
leftSidebarOpen() {
|
||||||
return Template.instance().data.state.get('leftSidebar');
|
return Template.instance().data.state.get('leftSidebar');
|
||||||
},
|
},
|
||||||
|
|
||||||
lesionSidebarOpen() {
|
|
||||||
return Template.instance().data.state.get('rightSidebar') === 'lesions';
|
|
||||||
},
|
|
||||||
|
|
||||||
additionalFindingsSidebarOpen() {
|
|
||||||
return Template.instance().data.state.get('rightSidebar') === 'additional';
|
|
||||||
},
|
|
||||||
|
|
||||||
rightSidebarOpen() {
|
rightSidebarOpen() {
|
||||||
return Template.instance().data.state.get('rightSidebar') !== null;
|
return Template.instance().data.state.get('rightSidebar');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -164,20 +164,4 @@ Meteor.call('removeHangingProtocolByID', HP.lesionTrackerFollowupProtocol.id, fu
|
|||||||
HangingProtocols.insert(HP.lesionTrackerFollowupProtocol);
|
HangingProtocols.insert(HP.lesionTrackerFollowupProtocol);
|
||||||
});
|
});
|
||||||
|
|
||||||
Meteor.startup(function() {
|
HangingProtocols.insert(HP.defaultProtocol);
|
||||||
getTimepointType = function(study) {
|
|
||||||
var timepoint = Timepoints.findOne({
|
|
||||||
studyInstanceUids: {
|
|
||||||
$in: [study.studyInstanceUid]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!timepoint) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return timepoint.timepointType;
|
|
||||||
};
|
|
||||||
|
|
||||||
HP.addCustomAttribute('timepointType', 'Timepoint Type', getTimepointType);
|
|
||||||
});
|
|
||||||
@ -31,8 +31,8 @@ Router.route('/', function() {
|
|||||||
if (verifyEmail && Meteor.user().emails && !Meteor.user().emails[0].verified) {
|
if (verifyEmail && Meteor.user().emails && !Meteor.user().emails[0].verified) {
|
||||||
this.render('emailVerification', routerOptions);
|
this.render('emailVerification', routerOptions);
|
||||||
} else {
|
} else {
|
||||||
Session.set('activeContentId', 'worklistTab');
|
Session.set('activeContentId', 'studylistTab');
|
||||||
this.render('lesionTracker', routerOptions);
|
this.render('app', routerOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -46,16 +46,7 @@ Router.route('/viewer/timepoints/:_id', {
|
|||||||
onBeforeAction: function() {
|
onBeforeAction: function() {
|
||||||
var timepointId = this.params._id;
|
var timepointId = this.params._id;
|
||||||
|
|
||||||
// Check if this study is already loaded in a tab
|
this.render('app', routerOptions);
|
||||||
// If it is, stop here so we don't keep adding tabs on hot-code reloads
|
|
||||||
var tab = WorklistTabs.findOne({
|
|
||||||
timepointId: timepointId
|
|
||||||
});
|
|
||||||
if (tab) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.render('lesionTracker', routerOptions);
|
|
||||||
openNewTabWithTimepoint(timepointId);
|
openNewTabWithTimepoint(timepointId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
14
LesionTracker/server/dropAllCollections.js
Normal file
14
LesionTracker/server/dropAllCollections.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Temporary fix to drop all Collections on server restart
|
||||||
|
// http://stackoverflow.com/questions/23891631/meteor-how-can-i-drop-all-mongo-collections-and-clear-all-data-on-startup
|
||||||
|
Meteor.startup(() => {
|
||||||
|
console.warn('Dropping all Collections!');
|
||||||
|
Object.keys(global).forEach((key) => {
|
||||||
|
const object = global[key];
|
||||||
|
if (object instanceof Meteor.Collection) {
|
||||||
|
if (!(/^server|currentServer$/).test(object._name)) {
|
||||||
|
console.warn(`Dropping: ${object._debugName}`);
|
||||||
|
object.remove({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -4,6 +4,16 @@
|
|||||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||||
# but you can also edit it by hand.
|
# but you can also edit it by hand.
|
||||||
|
|
||||||
|
# OHIF Packages
|
||||||
|
design
|
||||||
|
ohif:core
|
||||||
|
ohif:cornerstone
|
||||||
|
ohif:viewerbase
|
||||||
|
ohif:study-list
|
||||||
|
ohif:dicom-services
|
||||||
|
ohif:hanging-protocols
|
||||||
|
# ohif:user-management
|
||||||
|
|
||||||
twbs:bootstrap
|
twbs:bootstrap
|
||||||
http@1.2.10
|
http@1.2.10
|
||||||
arsnebula:reactive-promise
|
arsnebula:reactive-promise
|
||||||
@ -24,24 +34,17 @@ random@1.0.10
|
|||||||
ejson@1.0.13
|
ejson@1.0.13
|
||||||
spacebars@1.0.12
|
spacebars@1.0.12
|
||||||
check@1.2.4
|
check@1.2.4
|
||||||
cornerstone
|
|
||||||
dicomweb
|
|
||||||
aldeed:simple-schema # Third party package to deal with schemas
|
aldeed:simple-schema # Third party package to deal with schemas
|
||||||
design
|
|
||||||
ohif:core
|
|
||||||
ecmascript@0.5.9
|
ecmascript@0.5.9
|
||||||
viewerbase
|
|
||||||
worklist
|
|
||||||
reactive-var@1.0.11
|
reactive-var@1.0.11
|
||||||
|
|
||||||
practicalmeteor:loglevel
|
practicalmeteor:loglevel
|
||||||
hangingprotocols
|
|
||||||
reactive-dict@1.1.8
|
reactive-dict@1.1.8
|
||||||
clinical:router
|
clinical:router
|
||||||
fastclick@1.0.13
|
fastclick@1.0.13
|
||||||
standard-minifier-css@1.3.2
|
standard-minifier-css@1.3.2
|
||||||
standard-minifier-js@1.2.1
|
standard-minifier-js@1.2.1
|
||||||
johdirr:meteor-git-rev
|
johdirr:meteor-git-rev
|
||||||
wadoproxy
|
|
||||||
aldeed:template-extension
|
aldeed:template-extension
|
||||||
peppelg:bootstrap-3-modal
|
peppelg:bootstrap-3-modal
|
||||||
shell-server@0.2.1
|
shell-server@0.2.1
|
||||||
|
|||||||
@ -1,14 +1,18 @@
|
|||||||
accounts-base@1.2.14
|
accounts-base@1.2.14
|
||||||
|
aldeed:collection2@2.10.0
|
||||||
|
aldeed:collection2-core@1.2.0
|
||||||
|
aldeed:schema-deny@1.1.0
|
||||||
|
aldeed:schema-index@1.1.1
|
||||||
aldeed:simple-schema@1.5.3
|
aldeed:simple-schema@1.5.3
|
||||||
aldeed:template-extension@4.0.0
|
aldeed:template-extension@4.0.0
|
||||||
allow-deny@1.0.5
|
allow-deny@1.0.5
|
||||||
arsnebula:reactive-promise@0.9.1
|
arsnebula:reactive-promise@0.9.1
|
||||||
autoupdate@1.3.12
|
autoupdate@1.3.12
|
||||||
babel-compiler@6.13.0
|
babel-compiler@6.13.0
|
||||||
babel-runtime@0.1.12
|
babel-runtime@0.1.13
|
||||||
base64@1.0.10
|
base64@1.0.10
|
||||||
binary-heap@1.0.10
|
binary-heap@1.0.10
|
||||||
blaze@2.1.9
|
blaze@2.2.0
|
||||||
blaze-html-templates@1.0.5
|
blaze-html-templates@1.0.5
|
||||||
blaze-tools@1.0.10
|
blaze-tools@1.0.10
|
||||||
boilerplate-generator@1.0.11
|
boilerplate-generator@1.0.11
|
||||||
@ -21,7 +25,6 @@ clinical:router-location@2.1.0
|
|||||||
clinical:router-middleware-stack@2.1.2
|
clinical:router-middleware-stack@2.1.2
|
||||||
clinical:router-url@2.1.0
|
clinical:router-url@2.1.0
|
||||||
coffeescript@1.11.1_2
|
coffeescript@1.11.1_2
|
||||||
cornerstone@0.0.1
|
|
||||||
ddp@1.2.5
|
ddp@1.2.5
|
||||||
ddp-client@1.3.2
|
ddp-client@1.3.2
|
||||||
ddp-common@1.2.7
|
ddp-common@1.2.7
|
||||||
@ -29,9 +32,7 @@ ddp-rate-limiter@1.0.6
|
|||||||
ddp-server@1.3.11
|
ddp-server@1.3.11
|
||||||
deps@1.0.12
|
deps@1.0.12
|
||||||
design@0.0.1
|
design@0.0.1
|
||||||
dicomweb@0.0.1
|
|
||||||
diff-sequence@1.0.7
|
diff-sequence@1.0.7
|
||||||
dimseservice@0.0.1
|
|
||||||
ecmascript@0.5.9
|
ecmascript@0.5.9
|
||||||
ecmascript-runtime@0.3.15
|
ecmascript-runtime@0.3.15
|
||||||
ejson@1.0.13
|
ejson@1.0.13
|
||||||
@ -39,7 +40,6 @@ fastclick@1.0.13
|
|||||||
fortawesome:fontawesome@4.6.3
|
fortawesome:fontawesome@4.6.3
|
||||||
geojson-utils@1.0.10
|
geojson-utils@1.0.10
|
||||||
gilbertwat:bootstrap3-daterangepicker@1.3.21_1
|
gilbertwat:bootstrap3-daterangepicker@1.3.21_1
|
||||||
hangingprotocols@0.0.1
|
|
||||||
hot-code-push@1.0.4
|
hot-code-push@1.0.4
|
||||||
html-tools@1.0.11
|
html-tools@1.0.11
|
||||||
htmljs@1.0.11
|
htmljs@1.0.11
|
||||||
@ -74,12 +74,18 @@ natestrauser:select2@4.0.3
|
|||||||
npm-mongo@2.2.11_2
|
npm-mongo@2.2.11_2
|
||||||
observe-sequence@1.0.14
|
observe-sequence@1.0.14
|
||||||
ohif:core@0.0.1
|
ohif:core@0.0.1
|
||||||
|
ohif:cornerstone@0.0.1
|
||||||
|
ohif:dicom-services@0.0.1
|
||||||
|
ohif:hanging-protocols@0.0.1
|
||||||
|
ohif:study-list@0.0.1
|
||||||
|
ohif:viewerbase@0.0.1
|
||||||
|
ohif:wadoproxy@0.0.1
|
||||||
ordered-dict@1.0.9
|
ordered-dict@1.0.9
|
||||||
orthanc-remote@0.0.1
|
|
||||||
peppelg:bootstrap-3-modal@1.0.4
|
peppelg:bootstrap-3-modal@1.0.4
|
||||||
practicalmeteor:chai@2.1.0_1
|
practicalmeteor:chai@2.1.0_1
|
||||||
practicalmeteor:loglevel@1.2.0_2
|
practicalmeteor:loglevel@1.2.0_2
|
||||||
promise@0.8.8
|
promise@0.8.8
|
||||||
|
raix:eventemitter@0.1.3
|
||||||
random@1.0.10
|
random@1.0.10
|
||||||
rate-limit@1.0.6
|
rate-limit@1.0.6
|
||||||
reactive-dict@1.1.8
|
reactive-dict@1.1.8
|
||||||
@ -87,7 +93,6 @@ reactive-var@1.0.11
|
|||||||
reload@1.1.11
|
reload@1.1.11
|
||||||
retry@1.0.9
|
retry@1.0.9
|
||||||
routepolicy@1.0.12
|
routepolicy@1.0.12
|
||||||
rwatts:uuid@0.1.0
|
|
||||||
service-configuration@1.0.11
|
service-configuration@1.0.11
|
||||||
session@1.1.7
|
session@1.1.7
|
||||||
shell-server@0.2.1
|
shell-server@0.2.1
|
||||||
@ -108,8 +113,5 @@ ui@1.0.12
|
|||||||
underscore@1.0.10
|
underscore@1.0.10
|
||||||
url@1.0.11
|
url@1.0.11
|
||||||
validatejs@0.0.1
|
validatejs@0.0.1
|
||||||
viewerbase@0.0.1
|
|
||||||
wadoproxy@0.0.1
|
|
||||||
webapp@1.3.12
|
webapp@1.3.12
|
||||||
webapp-hashing@1.0.9
|
webapp-hashing@1.0.9
|
||||||
worklist@0.0.1
|
|
||||||
|
|||||||
@ -40,7 +40,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{>serverInformationModal}}
|
{{>serverInformationModal}}
|
||||||
{{>worklist}}
|
{{>studylist}}
|
||||||
{{>aboutModal}}
|
{{>aboutModal}}
|
||||||
{{>viewSeriesDetailsModal}}
|
{{>viewSeriesDetailsModal}}
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
@import "{design}/app"
|
@import "{design}/app"
|
||||||
|
|
||||||
|
body
|
||||||
|
background-color: black
|
||||||
|
|
||||||
#viewer
|
#viewer
|
||||||
background-color: black
|
background-color: black
|
||||||
height: 100%
|
height: 100%
|
||||||
|
|||||||
@ -1,16 +1,5 @@
|
|||||||
Session.setDefault('ViewerData', {});
|
Session.setDefault('ViewerData', {});
|
||||||
|
|
||||||
// Re-add any tab data saved in the Session
|
|
||||||
WorklistTabs.remove({});
|
|
||||||
Object.keys(ViewerData).forEach(function(contentId) {
|
|
||||||
var tabData = ViewerData[contentId];
|
|
||||||
var data = {
|
|
||||||
title: tabData.title,
|
|
||||||
contentid: tabData.contentid
|
|
||||||
};
|
|
||||||
WorklistTabs.insert(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
Router.configure({
|
Router.configure({
|
||||||
layoutTemplate: 'layout',
|
layoutTemplate: 'layout',
|
||||||
loadingTemplate: 'layout'
|
loadingTemplate: 'layout'
|
||||||
@ -30,16 +19,6 @@ Router.route('/viewer/:_id', {
|
|||||||
name: 'viewer',
|
name: 'viewer',
|
||||||
onBeforeAction: function() {
|
onBeforeAction: function() {
|
||||||
var studyInstanceUid = this.params._id;
|
var studyInstanceUid = this.params._id;
|
||||||
|
|
||||||
// Check if this study is already loaded in a tab
|
|
||||||
// If it is, stop here so we don't keep adding tabs on hot-code reloads
|
|
||||||
var tabs = WorklistTabs.find({
|
|
||||||
studyInstanceUid: studyInstanceUid
|
|
||||||
});
|
|
||||||
|
|
||||||
if (tabs.count()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.render('ohifViewer', {
|
this.render('ohifViewer', {
|
||||||
data: function() {
|
data: function() {
|
||||||
|
|||||||
1
PETSUVMeasurements/.gagarin/.gitignore
vendored
Normal file
1
PETSUVMeasurements/.gagarin/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
local
|
||||||
16
PETSUVMeasurements/.meteor/.finished-upgraders
Normal file
16
PETSUVMeasurements/.meteor/.finished-upgraders
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# This file contains information which helps Meteor properly upgrade your
|
||||||
|
# app when you run 'meteor update'. You should check it into version control
|
||||||
|
# with your project.
|
||||||
|
|
||||||
|
notices-for-0.9.0
|
||||||
|
notices-for-0.9.1
|
||||||
|
0.9.4-platform-file
|
||||||
|
notices-for-facebook-graph-api-2
|
||||||
|
1.2.0-standard-minifiers-package
|
||||||
|
1.2.0-meteor-platform-split
|
||||||
|
1.2.0-cordova-changes
|
||||||
|
1.2.0-breaking-changes
|
||||||
|
1.3.0-split-minifiers-package
|
||||||
|
1.3.5-remove-old-dev-bundle-link
|
||||||
|
1.4.0-remove-old-dev-bundle-link
|
||||||
|
1.4.1-add-shell-server-package
|
||||||
2
PETSUVMeasurements/.meteor/.gitignore
vendored
Normal file
2
PETSUVMeasurements/.meteor/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
dev_bundle
|
||||||
|
local
|
||||||
7
PETSUVMeasurements/.meteor/.id
Normal file
7
PETSUVMeasurements/.meteor/.id
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# This file contains a token that is unique to your project.
|
||||||
|
# Check it into your repository along with the rest of this directory.
|
||||||
|
# It can be used for purposes such as:
|
||||||
|
# - ensuring you don't accidentally deploy one app on top of another
|
||||||
|
# - providing package authors with aggregated statistics
|
||||||
|
|
||||||
|
1q3vtk41m2yk4k1rqj08l
|
||||||
136
PETSUVMeasurements/.meteor/nightwatch.json
Normal file
136
PETSUVMeasurements/.meteor/nightwatch.json
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
{
|
||||||
|
"starrynight": {
|
||||||
|
"version": "3.7.0"
|
||||||
|
},
|
||||||
|
"nightwatch": {
|
||||||
|
"version": "0.8.6"
|
||||||
|
},
|
||||||
|
"src_folders": [
|
||||||
|
"./tests/nightwatch/walkthroughs"
|
||||||
|
],
|
||||||
|
"output_folder": "./tests/nightwatch/reports",
|
||||||
|
"custom_commands_path": [
|
||||||
|
"./tests/nightwatch/commands",
|
||||||
|
"./tests/nightwatch/commands/api/meteor",
|
||||||
|
"./tests/nightwatch/commands/actions",
|
||||||
|
"./tests/nightwatch/commands/components",
|
||||||
|
"./tests/nightwatch/commands/methods"
|
||||||
|
],
|
||||||
|
"custom_assertions_path": [
|
||||||
|
"./tests/nightwatch/assertions"
|
||||||
|
],
|
||||||
|
"globals_path": "./tests/nightwatch/globals.json",
|
||||||
|
"selenium": {
|
||||||
|
"start_process": true,
|
||||||
|
"server_path": "${npm_config_prefix}/lib/node_modules/starrynight/node_modules/selenium-server-standalone-jar/jar/selenium-server-standalone-2.45.0.jar",
|
||||||
|
"log_path": "tests/nightwatch/logs",
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"port": 4444,
|
||||||
|
"cli_args": {
|
||||||
|
"webdriver.chrome.driver": "${npm_config_prefix}/lib/node_modules/starrynight/node_modules/chromedriver/bin/chromedriver"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test_settings": {
|
||||||
|
"default": {
|
||||||
|
"launch_url": "http://localhost:5000",
|
||||||
|
"selenium_host": "127.0.0.1",
|
||||||
|
"selenium_port": 4444,
|
||||||
|
"pathname": "/wd/hub",
|
||||||
|
"silent": true,
|
||||||
|
"disable_colors": false,
|
||||||
|
"firefox_profile": false,
|
||||||
|
"ie_driver": "",
|
||||||
|
"screenshots": {
|
||||||
|
"enabled": false,
|
||||||
|
"path": "./tests/nightwatch/screenshots"
|
||||||
|
},
|
||||||
|
"desiredCapabilities": {
|
||||||
|
"browserName": "chrome",
|
||||||
|
"javascriptEnabled": true,
|
||||||
|
"acceptSslCerts": true,
|
||||||
|
"loggingPrefs": {
|
||||||
|
"browser": "ALL"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": "./tests/nightwatch/unittests/*"
|
||||||
|
},
|
||||||
|
"phantomjs": {
|
||||||
|
"desiredCapabilities": {
|
||||||
|
"browserName": "phantomjs",
|
||||||
|
"javascriptEnabled": true,
|
||||||
|
"databaseEnabled": false,
|
||||||
|
"locationContextEnabled": false,
|
||||||
|
"applicationCacheEnabled": false,
|
||||||
|
"browserConnectionEnabled": false,
|
||||||
|
"webStorageEnabled": false,
|
||||||
|
"acceptSslCerts": true,
|
||||||
|
"rotatable": false,
|
||||||
|
"nativeEvents": false,
|
||||||
|
"phantomjs.binary.path": "${npm_config_prefix}/lib/node_modules/starrynight/node_modules/phantomjs/bin/phantomjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"travis": {
|
||||||
|
"launch_url": "http://localhost:3000",
|
||||||
|
"selenium_host": "127.0.0.1",
|
||||||
|
"selenium_port": 4444,
|
||||||
|
"pathname": "/wd/hub",
|
||||||
|
"silent": true,
|
||||||
|
"disable_colors": false,
|
||||||
|
"firefox_profile": false,
|
||||||
|
"screenshots": {
|
||||||
|
"enabled": false,
|
||||||
|
"path": "./tests/nightwatch/screenshots"
|
||||||
|
},
|
||||||
|
"desiredCapabilities": {
|
||||||
|
"browserName": "firefox",
|
||||||
|
"javascriptEnabled": true,
|
||||||
|
"databaseEnabled": true,
|
||||||
|
"locationContextEnabled": true,
|
||||||
|
"applicationCacheEnabled": true,
|
||||||
|
"browserConnectionEnabled": true,
|
||||||
|
"webStorageEnabled": true,
|
||||||
|
"acceptSslCerts": true,
|
||||||
|
"rotatable": true,
|
||||||
|
"nativeEvents": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"travischrome": {
|
||||||
|
"launch_url": "http://localhost:3000",
|
||||||
|
"selenium_host": "127.0.0.1",
|
||||||
|
"selenium_port": 4444,
|
||||||
|
"pathname": "/wd/hub",
|
||||||
|
"silent": true,
|
||||||
|
"disable_colors": false,
|
||||||
|
"firefox_profile": false,
|
||||||
|
"screenshots": {
|
||||||
|
"enabled": false,
|
||||||
|
"path": "./tests/nightwatch/screenshots"
|
||||||
|
},
|
||||||
|
"desiredCapabilities": {
|
||||||
|
"browserName": "chrome",
|
||||||
|
"javascriptEnabled": true,
|
||||||
|
"databaseEnabled": true,
|
||||||
|
"locationContextEnabled": true,
|
||||||
|
"applicationCacheEnabled": true,
|
||||||
|
"browserConnectionEnabled": true,
|
||||||
|
"webStorageEnabled": true,
|
||||||
|
"acceptSslCerts": true,
|
||||||
|
"rotatable": true,
|
||||||
|
"nativeEvents": true,
|
||||||
|
"chromeOptions": {
|
||||||
|
"args": [
|
||||||
|
"--no-sandbox"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"unittests": {
|
||||||
|
"selenium": {
|
||||||
|
"start_process": false,
|
||||||
|
"start_session": false
|
||||||
|
},
|
||||||
|
"filter": "./tests/nightwatch/unittests/*",
|
||||||
|
"exclude": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
74
PETSUVMeasurements/.meteor/packages
Normal file
74
PETSUVMeasurements/.meteor/packages
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# Meteor packages used by this project, one per line.
|
||||||
|
# Check this file (and the other files in this directory) into your repository.
|
||||||
|
#
|
||||||
|
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||||
|
# but you can also edit it by hand.
|
||||||
|
|
||||||
|
npm-bcrypt@0.9.1
|
||||||
|
|
||||||
|
meteor-base@1.0.4 # Packages every Meteor app needs to have
|
||||||
|
mobile-experience@1.0.4 # Packages for a great mobile UX
|
||||||
|
mongo@1.1.12 # The database Meteor supports right now
|
||||||
|
blaze-html-templates@1.0.4 # Compile .html files into Meteor Blaze views
|
||||||
|
session@1.1.6 # Client-side reactive dictionary for your app
|
||||||
|
jquery@1.11.9 # Helpful client-side library
|
||||||
|
tracker@1.1.0 # Meteor's client-side reactive programming library
|
||||||
|
|
||||||
|
es5-shim@4.6.14 # ECMAScript 5 compatibility for older browsers.
|
||||||
|
ecmascript@0.5.8 # Enable ECMAScript2015+ syntax in app code
|
||||||
|
|
||||||
|
insecure
|
||||||
|
|
||||||
|
clinical:router
|
||||||
|
clinical:extended-api
|
||||||
|
clinical:active-entry
|
||||||
|
clinical:error-pages
|
||||||
|
clinical:theming
|
||||||
|
clinical:fonts
|
||||||
|
clinical:hipaa-audit-log
|
||||||
|
clinical:hipaa-logger
|
||||||
|
|
||||||
|
aldeed:simple-schema # Third party package to deal with schemas
|
||||||
|
|
||||||
|
# OHIF Packages
|
||||||
|
design
|
||||||
|
ohif:core
|
||||||
|
ohif:cornerstone
|
||||||
|
ohif:viewerbase
|
||||||
|
ohif:study-list
|
||||||
|
ohif:dicom-services
|
||||||
|
ohif:hanging-protocols
|
||||||
|
ohif:user-management
|
||||||
|
ohif:petsuv
|
||||||
|
|
||||||
|
accounts-base@1.2.11
|
||||||
|
accounts-password@1.3.0
|
||||||
|
|
||||||
|
ian:accounts-ui-bootstrap-3
|
||||||
|
twbs:bootstrap
|
||||||
|
http@1.2.9
|
||||||
|
arsnebula:reactive-promise
|
||||||
|
promise@0.8.4
|
||||||
|
stylus@2.513.5
|
||||||
|
fortawesome:fontawesome
|
||||||
|
random@1.0.10
|
||||||
|
reactive-var@1.0.10
|
||||||
|
reactive-dict@1.1.8
|
||||||
|
practicalmeteor:loglevel
|
||||||
|
momentjs:moment
|
||||||
|
|
||||||
|
anti:gagarin@=0.4.11
|
||||||
|
check@1.2.3
|
||||||
|
aldeed:template-extension@4.0.0
|
||||||
|
zuuk:stale-session
|
||||||
|
gilbertwat:bootstrap3-daterangepicker
|
||||||
|
email@1.1.17
|
||||||
|
peppelg:bootstrap-3-modal
|
||||||
|
simple:reactive-method
|
||||||
|
fastclick@1.0.12
|
||||||
|
standard-minifier-css@1.2.0
|
||||||
|
standard-minifier-js@1.2.0
|
||||||
|
johdirr:meteor-git-rev
|
||||||
|
u2622:persistent-session
|
||||||
|
shell-server
|
||||||
|
aldeed:collection2
|
||||||
2
PETSUVMeasurements/.meteor/platforms
Normal file
2
PETSUVMeasurements/.meteor/platforms
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
server
|
||||||
|
browser
|
||||||
1
PETSUVMeasurements/.meteor/release
Normal file
1
PETSUVMeasurements/.meteor/release
Normal file
@ -0,0 +1 @@
|
|||||||
|
METEOR@1.4.1.1
|
||||||
143
PETSUVMeasurements/.meteor/versions
Normal file
143
PETSUVMeasurements/.meteor/versions
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
accounts-base@1.2.11
|
||||||
|
accounts-password@1.3.0
|
||||||
|
aldeed:collection2@2.9.1
|
||||||
|
aldeed:collection2-core@1.1.1
|
||||||
|
aldeed:schema-deny@1.0.1
|
||||||
|
aldeed:schema-index@1.0.1
|
||||||
|
aldeed:simple-schema@1.5.3
|
||||||
|
aldeed:template-extension@4.0.0
|
||||||
|
allow-deny@1.0.5
|
||||||
|
amplify@1.0.0
|
||||||
|
anti:gagarin@0.4.11
|
||||||
|
anti:i18n@0.4.3
|
||||||
|
arsnebula:reactive-promise@0.9.1
|
||||||
|
autoupdate@1.3.11
|
||||||
|
babel-compiler@6.9.1
|
||||||
|
babel-runtime@0.1.11
|
||||||
|
base64@1.0.9
|
||||||
|
binary-heap@1.0.9
|
||||||
|
blaze@2.1.8
|
||||||
|
blaze-html-templates@1.0.4
|
||||||
|
blaze-tools@1.0.9
|
||||||
|
boilerplate-generator@1.0.9
|
||||||
|
caching-compiler@1.1.7
|
||||||
|
caching-html-compiler@1.0.6
|
||||||
|
callback-hook@1.0.9
|
||||||
|
check@1.2.3
|
||||||
|
clinical:active-entry@1.5.16
|
||||||
|
clinical:auto-resizing@0.2.0
|
||||||
|
clinical:error-pages@0.2.0
|
||||||
|
clinical:extended-api@2.2.4
|
||||||
|
clinical:fonts@1.1.1
|
||||||
|
clinical:hipaa-audit-log@2.4.2
|
||||||
|
clinical:hipaa-logger@1.0.1
|
||||||
|
clinical:router@2.0.19
|
||||||
|
clinical:router-location@2.1.0
|
||||||
|
clinical:router-middleware-stack@2.1.2
|
||||||
|
clinical:router-url@2.1.0
|
||||||
|
clinical:theming@0.4.10
|
||||||
|
coffeescript@1.2.4_1
|
||||||
|
ddp@1.2.5
|
||||||
|
ddp-client@1.3.1
|
||||||
|
ddp-common@1.2.6
|
||||||
|
ddp-rate-limiter@1.0.5
|
||||||
|
ddp-server@1.3.10
|
||||||
|
deps@1.0.12
|
||||||
|
design@0.0.1
|
||||||
|
diff-sequence@1.0.6
|
||||||
|
ecmascript@0.5.8
|
||||||
|
ecmascript-runtime@0.3.14
|
||||||
|
ejson@1.0.12
|
||||||
|
email@1.1.17
|
||||||
|
es5-shim@4.6.14
|
||||||
|
fastclick@1.0.12
|
||||||
|
fortawesome:fontawesome@4.5.0
|
||||||
|
geojson-utils@1.0.9
|
||||||
|
gilbertwat:bootstrap3-daterangepicker@1.3.21_1
|
||||||
|
grove:less@0.2.0
|
||||||
|
hot-code-push@1.0.4
|
||||||
|
html-tools@1.0.10
|
||||||
|
htmljs@1.0.10
|
||||||
|
http@1.2.9
|
||||||
|
ian:accounts-ui-bootstrap-3@1.2.89
|
||||||
|
id-map@1.0.8
|
||||||
|
insecure@1.0.7
|
||||||
|
iron:controller@1.0.12
|
||||||
|
iron:core@1.0.11
|
||||||
|
iron:dynamic-template@1.0.12
|
||||||
|
iron:layout@1.0.12
|
||||||
|
johdirr:meteor-git-rev@0.0.4
|
||||||
|
jquery@1.11.9
|
||||||
|
launch-screen@1.0.12
|
||||||
|
livedata@1.0.18
|
||||||
|
localstorage@1.0.11
|
||||||
|
logging@1.1.15
|
||||||
|
mdg:validation-error@0.5.1
|
||||||
|
meteor@1.2.17
|
||||||
|
meteor-base@1.0.4
|
||||||
|
meteor-platform@1.2.6
|
||||||
|
minifier-css@1.2.14
|
||||||
|
minifier-js@1.2.14
|
||||||
|
minimongo@1.0.17
|
||||||
|
mobile-experience@1.0.4
|
||||||
|
mobile-status-bar@1.0.12
|
||||||
|
modules@0.7.6
|
||||||
|
modules-runtime@0.7.6
|
||||||
|
momentjs:moment@2.14.4
|
||||||
|
mongo@1.1.12
|
||||||
|
mongo-id@1.0.5
|
||||||
|
mrt:moment@2.8.1
|
||||||
|
natestrauser:select2@4.0.3
|
||||||
|
npm-bcrypt@0.9.1
|
||||||
|
npm-mongo@1.5.49
|
||||||
|
observe-sequence@1.0.12
|
||||||
|
ohif:core@0.0.1
|
||||||
|
ohif:cornerstone@0.0.1
|
||||||
|
ohif:dicom-services@0.0.1
|
||||||
|
ohif:hanging-protocols@0.0.1
|
||||||
|
ohif:measurements@0.0.1
|
||||||
|
ohif:petsuv@0.0.1
|
||||||
|
ohif:study-list@0.0.1
|
||||||
|
ohif:user-management@0.0.1
|
||||||
|
ohif:viewerbase@0.0.1
|
||||||
|
ohif:wadoproxy@0.0.1
|
||||||
|
ordered-dict@1.0.8
|
||||||
|
peppelg:bootstrap-3-modal@1.0.4
|
||||||
|
practicalmeteor:chai@2.1.0_1
|
||||||
|
practicalmeteor:loglevel@1.2.0_2
|
||||||
|
promise@0.8.4
|
||||||
|
raix:eventemitter@0.1.3
|
||||||
|
random@1.0.10
|
||||||
|
rate-limit@1.0.5
|
||||||
|
reactive-dict@1.1.8
|
||||||
|
reactive-var@1.0.10
|
||||||
|
reload@1.1.10
|
||||||
|
retry@1.0.8
|
||||||
|
routepolicy@1.0.11
|
||||||
|
service-configuration@1.0.10
|
||||||
|
session@1.1.6
|
||||||
|
sha@1.0.8
|
||||||
|
shell-server@0.2.1
|
||||||
|
silentcicero:jszip@0.0.4
|
||||||
|
simple:reactive-method@1.0.2
|
||||||
|
spacebars@1.0.12
|
||||||
|
spacebars-compiler@1.0.12
|
||||||
|
srp@1.0.9
|
||||||
|
standard-app-packages@1.0.9
|
||||||
|
standard-minifier-css@1.2.0
|
||||||
|
standard-minifier-js@1.2.0
|
||||||
|
stylus@2.513.5
|
||||||
|
templating@1.2.14
|
||||||
|
templating-tools@1.0.4
|
||||||
|
tracker@1.1.0
|
||||||
|
twbs:bootstrap@3.3.6
|
||||||
|
typ:accounts-ldap@1.0.1
|
||||||
|
typ:ldapjs@0.7.3
|
||||||
|
u2622:persistent-session@0.4.4
|
||||||
|
ui@1.0.11
|
||||||
|
underscore@1.0.9
|
||||||
|
url@1.0.10
|
||||||
|
validatejs@0.0.1
|
||||||
|
webapp@1.3.11
|
||||||
|
webapp-hashing@1.0.9
|
||||||
|
zuuk:stale-session@1.0.8
|
||||||
46
PETSUVMeasurements/activeEntry.js
Normal file
46
PETSUVMeasurements/activeEntry.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
if (Meteor.isClient){
|
||||||
|
ActiveEntry.configure({
|
||||||
|
logo: {
|
||||||
|
url: '/mini-circles.png',
|
||||||
|
displayed: true
|
||||||
|
},
|
||||||
|
signIn: {
|
||||||
|
displayFullName: true,
|
||||||
|
destination: '/worklist'
|
||||||
|
},
|
||||||
|
signUp: {
|
||||||
|
destination: '/worklist'
|
||||||
|
},
|
||||||
|
themeColors: {
|
||||||
|
primary: ""
|
||||||
|
},
|
||||||
|
passwordOptions: {
|
||||||
|
showPasswordStrengthIndicator: false,
|
||||||
|
requireRegexValidation: true,
|
||||||
|
//requireStrongPasswords: false
|
||||||
|
passwordHistoryCount: 6,
|
||||||
|
failedAttemptsLimit: 5
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (Meteor.isServer){
|
||||||
|
Accounts.emailTemplates.siteName = 'AwesomeSite';
|
||||||
|
Accounts.emailTemplates.from = 'AwesomeSite Admin <accounts@example.com>';
|
||||||
|
Accounts.emailTemplates.enrollAccount.subject = function(user) {
|
||||||
|
return 'Welcome to Awesome Town, ' + user.profile.name;
|
||||||
|
};
|
||||||
|
|
||||||
|
Accounts.emailTemplates.enrollAccount.text = function(user, url) {
|
||||||
|
return 'You have been selected to participate in building a better future!'
|
||||||
|
+ ' To activate your account, simply click the link below:\n\n'
|
||||||
|
+ url;
|
||||||
|
};
|
||||||
|
|
||||||
|
Meteor.startup(function() {
|
||||||
|
//process.env.MAIL_URL = 'smtp://sandboxid.mailgun.org:mypassword@smtp.mailgun.org:587';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*/
|
||||||
2
PETSUVMeasurements/client/body.styl
Normal file
2
PETSUVMeasurements/client/body.styl
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
body
|
||||||
|
background-color: black
|
||||||
40
PETSUVMeasurements/client/components/app/app.html
Normal file
40
PETSUVMeasurements/client/components/app/app.html
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<template name="app">
|
||||||
|
<div class="topBar noselect {{#if onStudyList}}studyList{{/if}}">
|
||||||
|
<div class="clearfix p-x-1 p-t-1">
|
||||||
|
<a target='_blank'
|
||||||
|
class="brandSection pull-left"
|
||||||
|
href="http://ohif.org">
|
||||||
|
<svg class="logoImage">
|
||||||
|
<use xlink:href="/packages/ohif_viewerbase/assets/icons.svg#icon-ohif-logo"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="logoText">
|
||||||
|
Open Health Imaging Foundation
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{{ #if studyListToggleText }}
|
||||||
|
<a class="btn studyListLinkSection pull-left m-x-1 p-x-2 p-y-0 js-toggle-studyList">
|
||||||
|
{{studyListToggleText}}
|
||||||
|
</a>
|
||||||
|
{{ /if }}
|
||||||
|
<div class="userAccountSection pull-right">
|
||||||
|
{{>userAccountMenu}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ >timeoutCountdownDialog }}
|
||||||
|
<div id="studylistTabs" class="tab-content">
|
||||||
|
<div class="tab-pane active" id="studylistTab">
|
||||||
|
<div class="studylistContainer">
|
||||||
|
{{> studylistResult }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane" id="viewerTab">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ >associationModal }}
|
||||||
|
{{ >serverInformationModal }}
|
||||||
|
{{ >lastLoginModal }}
|
||||||
|
{{ >studyContextMenu }}
|
||||||
|
{{ >progressDialog }}
|
||||||
|
{{ >viewSeriesDetailsModal }}
|
||||||
|
</template>
|
||||||
46
PETSUVMeasurements/client/components/app/app.js
Normal file
46
PETSUVMeasurements/client/components/app/app.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
const studylistContentId = 'studylistTab';
|
||||||
|
const viewerContentId = 'viewerTab';
|
||||||
|
|
||||||
|
// Define the ViewerData global object
|
||||||
|
// If there is currently any Session data for this object,
|
||||||
|
// use this to repopulate the variable
|
||||||
|
Template.app.onCreated(() => {
|
||||||
|
ViewerData = Session.get('ViewerData') || {};
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.app.events({
|
||||||
|
'click .js-toggle-studyList'() {
|
||||||
|
const contentId = Session.get('activeContentId');
|
||||||
|
|
||||||
|
if (contentId !== studylistContentId) {
|
||||||
|
switchToTab(studylistContentId);
|
||||||
|
} else {
|
||||||
|
switchToTab(viewerContentId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.app.helpers({
|
||||||
|
studyListToggleText() {
|
||||||
|
const contentId = Session.get('activeContentId');
|
||||||
|
|
||||||
|
// If the Viewer has not been opened yet, 'Back to viewer' should
|
||||||
|
// not be displayed
|
||||||
|
const viewerContentExists = !!Object.keys(ViewerData).length;
|
||||||
|
if (!viewerContentExists) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentId === studylistContentId) {
|
||||||
|
return 'Back to viewer';
|
||||||
|
} else if (contentId === viewerContentId) {
|
||||||
|
return 'Study list';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onStudyList() {
|
||||||
|
return (Session.get('activeContentId') === 'studylistTab');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Session.set('defaultSignInMessage', 'Tumor tracking in your browser.');
|
||||||
79
PETSUVMeasurements/client/components/app/app.styl
Normal file
79
PETSUVMeasurements/client/components/app/app.styl
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
@import "{design}/app"
|
||||||
|
|
||||||
|
$expandedHeight = 160px
|
||||||
|
|
||||||
|
.logoImage
|
||||||
|
display: inline-block
|
||||||
|
height: 100%
|
||||||
|
float: left
|
||||||
|
|
||||||
|
.topBar
|
||||||
|
height: $topBarHeight
|
||||||
|
theme('color', '$textPrimaryColor')
|
||||||
|
theme('background-color', '$primaryBackgroundColor')
|
||||||
|
transition(all 0.5s ease)
|
||||||
|
|
||||||
|
.brandSection
|
||||||
|
height: 30px
|
||||||
|
display: inline-block
|
||||||
|
text-decoration: none
|
||||||
|
|
||||||
|
.logoImage
|
||||||
|
margin: 0 8px
|
||||||
|
width: 30px
|
||||||
|
|
||||||
|
.logoText
|
||||||
|
display: inline-block
|
||||||
|
font-family: $logoFontFamily
|
||||||
|
font-weight: $logoFontWeight
|
||||||
|
theme('color', '$textPrimaryColor')
|
||||||
|
line-height: 30px
|
||||||
|
|
||||||
|
.studyListLinkSection
|
||||||
|
theme('border-left', '%s solid $uiBorderColor' % $uiBorderThickness)
|
||||||
|
|
||||||
|
.btn
|
||||||
|
theme('color', '$textSecondaryColor')
|
||||||
|
cursor: pointer
|
||||||
|
font-size: 13px
|
||||||
|
font-weight: 500
|
||||||
|
line-height: 26px
|
||||||
|
margin-top: 3px
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
theme('color', '$hoverColor')
|
||||||
|
|
||||||
|
&:active
|
||||||
|
theme('color', '$activeColor')
|
||||||
|
|
||||||
|
&.studyList
|
||||||
|
background-color: rgba(21, 25, 30, 0.7)
|
||||||
|
height: $expandedHeight
|
||||||
|
|
||||||
|
.brandSection
|
||||||
|
height: 100%
|
||||||
|
width: 80%
|
||||||
|
padding-left: 50px
|
||||||
|
line-height: $expandedHeight
|
||||||
|
|
||||||
|
.logoImage
|
||||||
|
width: 50px
|
||||||
|
margin: 0 20px
|
||||||
|
|
||||||
|
.logoText
|
||||||
|
font-size: 30px
|
||||||
|
|
||||||
|
.studyListLinkSection
|
||||||
|
position: absolute
|
||||||
|
border: none
|
||||||
|
top: 5px
|
||||||
|
left: 5px
|
||||||
|
|
||||||
|
#studylistTabs
|
||||||
|
height: "calc(100% - %s)" % $topBarHeight
|
||||||
|
|
||||||
|
.tab-pane
|
||||||
|
.viewerContainer
|
||||||
|
.loadingTextDiv
|
||||||
|
theme('color', '$textSecondaryColor')
|
||||||
|
font-size: 30px
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
<template name="toolbarSection">
|
||||||
|
<div class="toolbarSection noselect p-x-2">
|
||||||
|
<div class="clearfix">
|
||||||
|
<div class="pull-left m-t-1">
|
||||||
|
{{>roundedButtonGroup leftSidebarToggleButtonData}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{>toolbarSectionTools toolbarButtons=toolbarButtons extraToolbarButtons=extraToolbarButtons}}
|
||||||
|
|
||||||
|
{{#if splitView}}
|
||||||
|
{{>studySeriesQuickSwitch (clone this side="right" viewportIndex=1)}}
|
||||||
|
{{>studySeriesQuickSwitch (clone this side="left" viewportIndex=0)}}
|
||||||
|
{{else}}
|
||||||
|
{{>studySeriesQuickSwitch (clone this side="middle" viewportIndex=0)}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<div class="toolbarSectionEntry pull-right rm-l-1 p-x-1">
|
||||||
|
{{>caseProgress}}
|
||||||
|
</div>
|
||||||
|
<div class="pull-right m-t-1 rm-x-1">
|
||||||
|
{{>roundedButtonGroup rightSidebarToggleButtonData}}
|
||||||
|
</div>
|
||||||
|
<div id="toggleHUD" class="toolbarSectionButton pull-right rp-x-1 m-t-1 rm-r-3 {{#if session 'measurementTableHudOpen'}}active{{/if}}">
|
||||||
|
<div class="svgContainer">
|
||||||
|
<svg>
|
||||||
|
<use xlink:href="/packages/ohif_viewerbase/assets/icons.svg#icon-hud"></use>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="buttonLabel">HUD</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -0,0 +1,143 @@
|
|||||||
|
import { OHIF } from 'meteor/ohif:core';
|
||||||
|
|
||||||
|
Template.toolbarSection.helpers({
|
||||||
|
// Returns true if the view shall be split in two viewports
|
||||||
|
splitView() {
|
||||||
|
// Run this computation every time the viewports are updated
|
||||||
|
Session.get('LayoutManagerUpdated');
|
||||||
|
|
||||||
|
// Stops here if layout manager is not defined yet
|
||||||
|
if (!window.layoutManager) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return window.layoutManager.viewportData.length > 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
leftSidebarToggleButtonData() {
|
||||||
|
const instance = Template.instance();
|
||||||
|
return {
|
||||||
|
toggleable: true,
|
||||||
|
key: 'leftSidebar',
|
||||||
|
value: instance.data.state,
|
||||||
|
options: [{
|
||||||
|
value: 'studies',
|
||||||
|
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-studies',
|
||||||
|
svgWidth: 15,
|
||||||
|
svgHeight: 13,
|
||||||
|
bottomLabel: 'Studies'
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
rightSidebarToggleButtonData() {
|
||||||
|
const instance = Template.instance();
|
||||||
|
return {
|
||||||
|
toggleable: true,
|
||||||
|
key: 'rightSidebar',
|
||||||
|
value: instance.data.state,
|
||||||
|
options: [{
|
||||||
|
value: 'measurements',
|
||||||
|
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-measurements-lesions',
|
||||||
|
svgWidth: 18,
|
||||||
|
svgHeight: 10,
|
||||||
|
bottomLabel: 'Measurements'
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
toolbarButtons() {
|
||||||
|
// Check if the measure tools shall be disabled
|
||||||
|
const isToolDisabled = false; //!Template.instance().data.timepointApi;
|
||||||
|
|
||||||
|
const buttonData = [];
|
||||||
|
|
||||||
|
buttonData.push({
|
||||||
|
id: 'zoom',
|
||||||
|
title: 'Zoom',
|
||||||
|
classes: 'imageViewerTool',
|
||||||
|
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-zoom'
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonData.push({
|
||||||
|
id: 'wwwc',
|
||||||
|
title: 'Levels',
|
||||||
|
classes: 'imageViewerTool',
|
||||||
|
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-levels'
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonData.push({
|
||||||
|
id: 'pan',
|
||||||
|
title: 'Pan',
|
||||||
|
classes: 'imageViewerTool',
|
||||||
|
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-pan'
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonData.push({
|
||||||
|
id: 'ellipticalRoi',
|
||||||
|
title: 'Ellipse',
|
||||||
|
classes: 'imageViewerTool',
|
||||||
|
iconClasses: 'fa fa-circle-o'
|
||||||
|
});
|
||||||
|
|
||||||
|
return buttonData;
|
||||||
|
},
|
||||||
|
|
||||||
|
extraToolbarButtons() {
|
||||||
|
// Check if the measure tools shall be disabled
|
||||||
|
const isToolDisabled = false; //!Template.instance().data.timepointApi.currentTimepointId;
|
||||||
|
const buttonData = [];
|
||||||
|
|
||||||
|
// TODO: Get real icons for CR / UN / EX
|
||||||
|
buttonData.push({
|
||||||
|
id: 'crTool',
|
||||||
|
title: 'CR Tool',
|
||||||
|
classes: 'imageViewerTool toolbarSectionButton',
|
||||||
|
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-measure-temp',
|
||||||
|
disabled: isToolDisabled
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonData.push({
|
||||||
|
id: 'unTool',
|
||||||
|
title: 'UN Tool',
|
||||||
|
classes: 'imageViewerTool toolbarSectionButton',
|
||||||
|
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-measure-temp',
|
||||||
|
disabled: isToolDisabled
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonData.push({
|
||||||
|
id: 'exTool',
|
||||||
|
title: 'EX Tool',
|
||||||
|
classes: 'imageViewerTool toolbarSectionButton',
|
||||||
|
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-measure-temp',
|
||||||
|
disabled: isToolDisabled
|
||||||
|
});
|
||||||
|
|
||||||
|
return buttonData;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.toolbarSection.events({
|
||||||
|
'click #toggleHUD'() {
|
||||||
|
const state = Session.get('measurementTableHudOpen');
|
||||||
|
Session.set('measurementTableHudOpen', !state);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.toolbarSection.onRendered(function() {
|
||||||
|
// Set disabled/enabled tool buttons that are set in toolManager
|
||||||
|
const states = toolManager.getToolDefaultStates();
|
||||||
|
const disabledToolButtons = states.disabledToolButtons;
|
||||||
|
const allToolbarButtons = $('#toolbar').find('button');
|
||||||
|
if (disabledToolButtons && disabledToolButtons.length > 0) {
|
||||||
|
for (var i = 0; i < allToolbarButtons.length; i++) {
|
||||||
|
const toolbarButton = allToolbarButtons[i];
|
||||||
|
$(toolbarButton).prop('disabled', false);
|
||||||
|
|
||||||
|
const index = disabledToolButtons.indexOf($(toolbarButton).attr('id'));
|
||||||
|
if (index !== -1) {
|
||||||
|
$(toolbarButton).prop('disabled', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
@import "{design}/app"
|
||||||
|
|
||||||
|
.toolbarSection
|
||||||
|
theme('border-bottom', '%s solid $uiBorderColor' % $uiBorderThickness)
|
||||||
|
flex: 0 0 auto
|
||||||
|
height: $toolbarHeight
|
||||||
|
overflow: hidden
|
||||||
|
padding-top: 6px
|
||||||
|
position: relative
|
||||||
|
transition(height 300ms ease)
|
||||||
|
width: 100%
|
||||||
|
|
||||||
|
&.expanded
|
||||||
|
height: $toolbarHeight + $toolbarDrawerHeight
|
||||||
|
|
||||||
|
.toolbarSectionEntry
|
||||||
|
theme('color', '$defaultColor')
|
||||||
|
cursor: pointer
|
||||||
|
display: inline-block
|
||||||
|
theme('fill', '$defaultColor')
|
||||||
|
margin-top: 3px
|
||||||
|
min-width: 30px
|
||||||
|
theme('stroke', '$defaultColor')
|
||||||
|
text-align: center
|
||||||
@ -2,15 +2,11 @@
|
|||||||
{{#if Template.subscriptionsReady}}
|
{{#if Template.subscriptionsReady}}
|
||||||
<div class="viewerDialogs">
|
<div class="viewerDialogs">
|
||||||
{{>confirmDeleteDialog}}
|
{{>confirmDeleteDialog}}
|
||||||
{{>lesionLocationDialog}}
|
{{>measurementTableHUD (clone this)}}
|
||||||
{{>nonTargetLesionDialog}}
|
|
||||||
{{>nonTargetResponseDialog}}
|
|
||||||
{{>lesionTableHUD (clone this)}}
|
|
||||||
{{! >conformanceCheckFeedback}}
|
|
||||||
</div>
|
</div>
|
||||||
<div id="viewer">
|
<div id="viewer">
|
||||||
{{>toolbarSection (clone this)}}
|
{{>toolbarSection (clone this)}}
|
||||||
{{>flexboxLayout (clone this)}}
|
{{>viewerSection (clone this)}}
|
||||||
</div>
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
{{>loadingText}}
|
{{>loadingText}}
|
||||||
68
PETSUVMeasurements/client/components/viewer/viewer.js
Normal file
68
PETSUVMeasurements/client/components/viewer/viewer.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { OHIF } from 'meteor/ohif:core';
|
||||||
|
import { MeasurementsConfiguration } from 'meteor/ohif:measurements/both/configuration/measurements';
|
||||||
|
import { MeasurementHandlers } from 'meteor/ohif:measurements/client/lib/MeasurementHandlers';
|
||||||
|
|
||||||
|
Session.set('MeasurementsReady', false);
|
||||||
|
|
||||||
|
Template.viewer.onCreated(() => {
|
||||||
|
const MeasurementApi = MeasurementsConfiguration.getMeasurementsApi();
|
||||||
|
|
||||||
|
OHIF.viewer = OHIF.viewer || {};
|
||||||
|
|
||||||
|
const instance = Template.instance();
|
||||||
|
|
||||||
|
instance.data.state = new ReactiveDict();
|
||||||
|
instance.data.state.set('leftSidebar', Session.get('leftSidebar'));
|
||||||
|
instance.data.state.set('rightSidebar', Session.get('rightSidebar'));
|
||||||
|
|
||||||
|
const contentId = instance.data.contentId;
|
||||||
|
|
||||||
|
if (ViewerData[contentId].loadedSeriesData) {
|
||||||
|
log.info('Reloading previous loadedSeriesData');
|
||||||
|
OHIF.viewer.loadedSeriesData = ViewerData[contentId].loadedSeriesData;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
log.info('Setting default ViewerData');
|
||||||
|
OHIF.viewer.loadedSeriesData = {};
|
||||||
|
ViewerData[contentId].loadedSeriesData = {};
|
||||||
|
Session.set('ViewerData', ViewerData);
|
||||||
|
}
|
||||||
|
|
||||||
|
Session.set('activeViewport', ViewerData[contentId].activeViewport || false);
|
||||||
|
|
||||||
|
// Update the ViewerStudies collection with the loaded studies
|
||||||
|
ViewerStudies.remove({});
|
||||||
|
|
||||||
|
instance.data.studies.forEach(study => {
|
||||||
|
study.selected = true;
|
||||||
|
study.displaySets = createStacks(study);
|
||||||
|
ViewerStudies.insert(study);
|
||||||
|
});
|
||||||
|
|
||||||
|
instance.data.measurementApi = new MeasurementApi();
|
||||||
|
const measurementsPromise = instance.data.measurementApi.retrieveMeasurements();
|
||||||
|
measurementsPromise.then(() => {
|
||||||
|
Session.set('MeasurementsReady', true);
|
||||||
|
|
||||||
|
instance.data.measurementApi.syncMeasurementsAndToolData();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.viewer.helpers({
|
||||||
|
dataSourcesReady() {
|
||||||
|
// TODO: Find a better way to do this
|
||||||
|
return Session.get('MeasurementsReady');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Template.viewer.events({
|
||||||
|
'CornerstoneToolsMeasurementAdded .imageViewerViewport'(event, instance, eventData) {
|
||||||
|
MeasurementHandlers.onAdded(event, instance, eventData);
|
||||||
|
},
|
||||||
|
'CornerstoneToolsMeasurementModified .imageViewerViewport'(event, instance, eventData) {
|
||||||
|
MeasurementHandlers.onModified(event, instance, eventData);
|
||||||
|
},
|
||||||
|
'CornerstoneToolsMeasurementRemoved .imageViewerViewport'(event, instance, eventData) {
|
||||||
|
MeasurementHandlers.onRemoved(event, instance, eventData);
|
||||||
|
}
|
||||||
|
});
|
||||||
22
PETSUVMeasurements/client/components/viewer/viewer.styl
Normal file
22
PETSUVMeasurements/client/components/viewer/viewer.styl
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
@import "{design}/app"
|
||||||
|
|
||||||
|
#viewer
|
||||||
|
background-color: black
|
||||||
|
height: 100%
|
||||||
|
width: 100%
|
||||||
|
|
||||||
|
#imageViewerViewports
|
||||||
|
.viewportContainer
|
||||||
|
border: none !important
|
||||||
|
outline: 0 !important // Prevent blue outline in Chrome
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
&.active
|
||||||
|
&:hover.active
|
||||||
|
border: none !important
|
||||||
|
outline: 0 !important // Prevent blue outline in Chrome
|
||||||
|
|
||||||
|
&:not(:last-child)
|
||||||
|
.removable
|
||||||
|
.imageViewerViewport
|
||||||
|
theme('border-right', '%s solid $uiBorderColor' % $uiBorderThickness)
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
<template name="viewerSection">
|
||||||
|
<div class="viewerSection">
|
||||||
|
<div class="sidebarMenu sidebar-left {{#if leftSidebarOpen}}sidebar-open{{/if}}">
|
||||||
|
{{>studyTimepointBrowser (clone this)}}
|
||||||
|
</div>
|
||||||
|
<div class="mainContent {{#if leftSidebarOpen}}sidebar-left-open{{/if}} {{#if rightSidebarOpen}}sidebar-right-open{{/if}}">
|
||||||
|
{{>viewerMain (clone this)}}
|
||||||
|
</div>
|
||||||
|
<div class="sidebarMenu sidebar-right {{#if rightSidebarOpen}}sidebar-open{{/if}}">
|
||||||
|
{{>measurementTable (clone this)}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
Template.viewerSection.events({
|
||||||
|
'transitionend .sidebarMenu'(event) {
|
||||||
|
if (!event.target.classList.contains('sidebarMenu')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleResize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.viewerSection.helpers({
|
||||||
|
leftSidebarOpen() {
|
||||||
|
return Template.instance().data.state.get('leftSidebar');
|
||||||
|
},
|
||||||
|
|
||||||
|
rightSidebarOpen() {
|
||||||
|
return Template.instance().data.state.get('rightSidebar');
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,78 @@
|
|||||||
|
@import "{design}/app"
|
||||||
|
|
||||||
|
.viewerSection
|
||||||
|
display: flex
|
||||||
|
flex: 1
|
||||||
|
flex-flow: row nowrap
|
||||||
|
align-items: stretch
|
||||||
|
height: calc(100% - 78px);
|
||||||
|
width: 100%
|
||||||
|
|
||||||
|
.sidebarMenu
|
||||||
|
height: 100%
|
||||||
|
// required transformation to make inner fixed elements relative to this one
|
||||||
|
transform(scale(1))
|
||||||
|
transition($sidebarTransition)
|
||||||
|
|
||||||
|
.sidebar-option
|
||||||
|
height: 100%
|
||||||
|
position: fixed
|
||||||
|
transform(translateX(100%))
|
||||||
|
transition($sidebarTransition)
|
||||||
|
width: 100%
|
||||||
|
|
||||||
|
&.active
|
||||||
|
transform(translateX(0%))
|
||||||
|
|
||||||
|
.sidebar-left
|
||||||
|
theme('border-right', '%s solid $uiBorderColor' % $uiBorderThickness)
|
||||||
|
flex: 1
|
||||||
|
margin-left: - $studiesSidebarMenuWidth
|
||||||
|
max-width: $studiesSidebarMenuWidth
|
||||||
|
order: 1
|
||||||
|
|
||||||
|
&.sidebar-open
|
||||||
|
margin-left: 0
|
||||||
|
|
||||||
|
.mainContent
|
||||||
|
flex: 1
|
||||||
|
order: 2
|
||||||
|
width: 100%
|
||||||
|
height: 100%
|
||||||
|
overflow: hidden
|
||||||
|
transition($sidebarTransition)
|
||||||
|
|
||||||
|
.sidebar-right
|
||||||
|
flex: 1
|
||||||
|
margin-left: -12px
|
||||||
|
margin-right: - $lesionsSidebarMenuWidth
|
||||||
|
max-width: $lesionsSidebarMenuWidth
|
||||||
|
order: 3
|
||||||
|
padding-left: 6px
|
||||||
|
position: relative
|
||||||
|
|
||||||
|
&[data-timepoints="3"]
|
||||||
|
margin-right: - ($lesionsSidebarMenuWidth + 135.5px)
|
||||||
|
max-width: $lesionsSidebarMenuWidth + 135.5px
|
||||||
|
|
||||||
|
&[data-timepoints="4"]
|
||||||
|
margin-right: - ($lesionsSidebarMenuWidth + 270px)
|
||||||
|
max-width: $lesionsSidebarMenuWidth + 270px
|
||||||
|
|
||||||
|
&:before
|
||||||
|
theme('background-color', '$uiBorderColor')
|
||||||
|
content: ''
|
||||||
|
display: block
|
||||||
|
height: 100%
|
||||||
|
left: 5px
|
||||||
|
position: absolute
|
||||||
|
top: 0
|
||||||
|
width: $uiBorderThickness
|
||||||
|
|
||||||
|
&.sidebar-open
|
||||||
|
margin-right: 0
|
||||||
|
|
||||||
|
.studiesListedChanger
|
||||||
|
theme('border-bottom', '%s solid $uiBorderColor' % $uiBorderThickness)
|
||||||
|
padding: 20px 10px
|
||||||
|
text-align: center
|
||||||
20
PETSUVMeasurements/client/head.html
Normal file
20
PETSUVMeasurements/client/head.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Lesion Tracker</title>
|
||||||
|
<meta name="description" content="Prototype viewer application for oncology">
|
||||||
|
<!-- Prevents page zooming -->
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||||
|
|
||||||
|
<link rel="shortcut icon" type="image/png" href="images/logo.png" sizes="16x16 32x32 64x64">
|
||||||
|
<!--Best display for mobile devices-->
|
||||||
|
<meta http-equiv="cleartype" content="on">
|
||||||
|
<meta name="MobileOptimized" content="320">
|
||||||
|
<meta name="HandheldFriendly" content="True">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
|
||||||
|
<!-- Make SVG work on IE -->
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
|
||||||
|
<link href='http://fonts.googleapis.com/css?family=Roboto:400,100,100italic,300,300italic,400italic,500,500italic,700,700italic,900,900italic&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
|
||||||
|
<link href='http://fonts.googleapis.com/css?family=Sanchez:400,700&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
|
||||||
|
</head>
|
||||||
167
PETSUVMeasurements/client/lesionTrackerHangingProtocol.js
Normal file
167
PETSUVMeasurements/client/lesionTrackerHangingProtocol.js
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
// Define Baseline protocol
|
||||||
|
var proto = new HP.Protocol('LT_Baseline');
|
||||||
|
proto.locked = true;
|
||||||
|
|
||||||
|
var studyDescription = new HP.ProtocolMatchingRule();
|
||||||
|
studyDescription.required = true;
|
||||||
|
studyDescription.attribute = 'studyDescription';
|
||||||
|
studyDescription.constraint = {
|
||||||
|
contains: {
|
||||||
|
value: 'CT'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var isBaseline = new HP.ProtocolMatchingRule();
|
||||||
|
isBaseline.required = true;
|
||||||
|
isBaseline.attribute = 'timepointType';
|
||||||
|
isBaseline.constraint = {
|
||||||
|
equals: {
|
||||||
|
value: 'baseline'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
proto.addProtocolMatchingRule(studyDescription);
|
||||||
|
proto.addProtocolMatchingRule(isBaseline);
|
||||||
|
|
||||||
|
var oneByOne = new HP.ViewportStructure('grid', {
|
||||||
|
rows: 1,
|
||||||
|
columns: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stage 1
|
||||||
|
var single = new HP.Viewport();
|
||||||
|
|
||||||
|
var baseline = new HP.StudyMatchingRule(true);
|
||||||
|
baseline.required = true;
|
||||||
|
baseline.attribute = 'timepointType';
|
||||||
|
baseline.constraint = {
|
||||||
|
equals: {
|
||||||
|
value: 'baseline'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var body = new HP.SeriesMatchingRule();
|
||||||
|
body.attribute = 'seriesDescription';
|
||||||
|
body.weight = 5;
|
||||||
|
body.constraint = {
|
||||||
|
contains: {
|
||||||
|
value: 'Body'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var chest = new HP.SeriesMatchingRule();
|
||||||
|
chest.attribute = 'seriesDescription';
|
||||||
|
chest.constraint = {
|
||||||
|
contains: {
|
||||||
|
value: 'CHEST'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
single.studyMatchingRules.push(baseline);
|
||||||
|
single.seriesMatchingRules.push(body);
|
||||||
|
single.seriesMatchingRules.push(chest);
|
||||||
|
|
||||||
|
var first = new HP.Stage(oneByOne, 'oneByOne');
|
||||||
|
first.viewports.push(single);
|
||||||
|
|
||||||
|
proto.addStage(first);
|
||||||
|
|
||||||
|
HP.lesionTrackerBaselineProtocol = proto;
|
||||||
|
HP.lesionTrackerBaselineProtocol.id = 'lesionTrackerBaselineProtocol';
|
||||||
|
|
||||||
|
|
||||||
|
// Define Followup Protocol
|
||||||
|
var proto = new HP.Protocol('LT_BaselineFollowup');
|
||||||
|
proto.locked = true;
|
||||||
|
|
||||||
|
var studyDescription = new HP.ProtocolMatchingRule();
|
||||||
|
studyDescription.required = true;
|
||||||
|
studyDescription.attribute = 'studyDescription';
|
||||||
|
studyDescription.constraint = {
|
||||||
|
contains: {
|
||||||
|
value: 'CT'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var isFollowup = new HP.ProtocolMatchingRule();
|
||||||
|
isFollowup.required = true;
|
||||||
|
isFollowup.attribute = 'timepointType';
|
||||||
|
isFollowup.constraint = {
|
||||||
|
equals: {
|
||||||
|
value: 'followup'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
proto.addProtocolMatchingRule(studyDescription);
|
||||||
|
proto.addProtocolMatchingRule(isFollowup);
|
||||||
|
|
||||||
|
var oneByTwo = new HP.ViewportStructure('grid', {
|
||||||
|
rows: 1,
|
||||||
|
columns: 2
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stage 1
|
||||||
|
var left = new HP.Viewport();
|
||||||
|
var right = new HP.Viewport();
|
||||||
|
|
||||||
|
var baseline = new HP.StudyMatchingRule(true);
|
||||||
|
baseline.required = true;
|
||||||
|
baseline.attribute = 'timepointType';
|
||||||
|
baseline.constraint = {
|
||||||
|
equals: {
|
||||||
|
value: 'baseline'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var followup = new HP.StudyMatchingRule();
|
||||||
|
followup.required = true;
|
||||||
|
followup.attribute = 'timepointType';
|
||||||
|
followup.constraint = {
|
||||||
|
equals: {
|
||||||
|
value: 'followup'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var body = new HP.SeriesMatchingRule();
|
||||||
|
body.attribute = 'seriesDescription';
|
||||||
|
body.weight = 5;
|
||||||
|
body.constraint = {
|
||||||
|
contains: {
|
||||||
|
value: 'Body'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var chest = new HP.SeriesMatchingRule();
|
||||||
|
chest.attribute = 'seriesDescription';
|
||||||
|
chest.constraint = {
|
||||||
|
contains: {
|
||||||
|
value: 'CHEST'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
left.studyMatchingRules.push(baseline);
|
||||||
|
//left.seriesMatchingRules.push(body);
|
||||||
|
//left.seriesMatchingRules.push(chest);
|
||||||
|
|
||||||
|
right.studyMatchingRules.push(followup);
|
||||||
|
//right.seriesMatchingRules.push(body);
|
||||||
|
//right.seriesMatchingRules.push(chest);
|
||||||
|
|
||||||
|
var first = new HP.Stage(oneByTwo, 'oneByTwo');
|
||||||
|
first.viewports.push(left);
|
||||||
|
first.viewports.push(right);
|
||||||
|
|
||||||
|
proto.addStage(first);
|
||||||
|
|
||||||
|
HP.lesionTrackerFollowupProtocol = proto;
|
||||||
|
HP.lesionTrackerFollowupProtocol.id = 'lesionTrackerFollowupProtocol';
|
||||||
|
|
||||||
|
Meteor.call('removeHangingProtocolByID', HP.lesionTrackerBaselineProtocol.id, function() {
|
||||||
|
HangingProtocols.insert(HP.lesionTrackerBaselineProtocol);
|
||||||
|
});
|
||||||
|
|
||||||
|
Meteor.call('removeHangingProtocolByID', HP.lesionTrackerFollowupProtocol.id, function() {
|
||||||
|
HangingProtocols.insert(HP.lesionTrackerFollowupProtocol);
|
||||||
|
});
|
||||||
|
|
||||||
|
HangingProtocols.insert(HP.defaultProtocol);
|
||||||
3
PETSUVMeasurements/client/log.js
Normal file
3
PETSUVMeasurements/client/log.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
// Create application logger using loglevel
|
||||||
|
// https://atmospherejs.com/spacejamio/loglevel
|
||||||
|
log = loglevel.createAppLogger('PETSUVMeasurements', defaultLevel = 'info');
|
||||||
55
PETSUVMeasurements/client/routes.js
Normal file
55
PETSUVMeasurements/client/routes.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
Session.setDefault('ViewerData', {});
|
||||||
|
|
||||||
|
// verifyEmail controls whether emailVerification template will be rendered or not
|
||||||
|
var verifyEmail = Meteor.settings && Meteor.settings.public && Meteor.settings.public.verifyEmail || false;
|
||||||
|
|
||||||
|
Router.configure({
|
||||||
|
layoutTemplate: 'layout',
|
||||||
|
loadingTemplate: 'layout'
|
||||||
|
});
|
||||||
|
|
||||||
|
Router.onBeforeAction('loading');
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
additionalTemplates: []
|
||||||
|
};
|
||||||
|
|
||||||
|
var routerOptions = {
|
||||||
|
data: data
|
||||||
|
};
|
||||||
|
|
||||||
|
Router.route('/', function() {
|
||||||
|
// Check user is logged in
|
||||||
|
if (Meteor.user() && Meteor.userId()) {
|
||||||
|
if (verifyEmail && Meteor.user().emails && !Meteor.user().emails[0].verified) {
|
||||||
|
this.render('emailVerification', routerOptions);
|
||||||
|
} else {
|
||||||
|
Session.set('activeContentId', 'studylistTab');
|
||||||
|
this.render('app', routerOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.render('entrySignIn', routerOptions);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Router.route('/viewer/timepoints/:_id', {
|
||||||
|
layoutTemplate: 'layout',
|
||||||
|
name: 'viewer',
|
||||||
|
onBeforeAction: function() {
|
||||||
|
var timepointId = this.params._id;
|
||||||
|
|
||||||
|
this.render('app', routerOptions);
|
||||||
|
openNewTabWithTimepoint(timepointId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Router.onBeforeAction(function() {
|
||||||
|
if (!Meteor.userId() && !Meteor.loggingIn()) {
|
||||||
|
this.render('entrySignIn');
|
||||||
|
} else {
|
||||||
|
this.next();
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
except: ['entrySignIn', 'entrySignUp', 'forgotPassword', 'resetPassword']
|
||||||
|
});
|
||||||
BIN
PETSUVMeasurements/public/images/logo.png
Normal file
BIN
PETSUVMeasurements/public/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
12
PETSUVMeasurements/server/dropAllCollections.js
Normal file
12
PETSUVMeasurements/server/dropAllCollections.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Temporary fix to drop all Collections on server restart
|
||||||
|
// http://stackoverflow.com/questions/23891631/meteor-how-can-i-drop-all-mongo-collections-and-clear-all-data-on-startup
|
||||||
|
Meteor.startup(function() {
|
||||||
|
console.warn('Dropping all Collections!');
|
||||||
|
for (var property in global) {
|
||||||
|
var object = global[property];
|
||||||
|
if (object instanceof Meteor.Collection) {
|
||||||
|
console.warn('Dropping: ' + object._debugName);
|
||||||
|
object.remove({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
50
PETSUVMeasurements/server/email.js
Normal file
50
PETSUVMeasurements/server/email.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
Meteor.startup(function () {
|
||||||
|
|
||||||
|
// Mail server settings
|
||||||
|
var username = Meteor.settings && Meteor.settings.mailServerSettings && Meteor.settings.mailServerSettings.username || null;
|
||||||
|
var password = Meteor.settings && Meteor.settings.mailServerSettings && Meteor.settings.mailServerSettings.password || null;
|
||||||
|
var server = Meteor.settings && Meteor.settings.mailServerSettings && Meteor.settings.mailServerSettings.server || null;
|
||||||
|
var port = Meteor.settings && Meteor.settings.mailServerSettings && Meteor.settings.mailServerSettings.port || null;
|
||||||
|
var verifyEmail = Meteor.settings && Meteor.settings.public && Meteor.settings.public.verifyEmail || false;
|
||||||
|
var siteName = Meteor.settings && Meteor.settings.public && Meteor.settings.public.siteName || "Lesion Tracker";
|
||||||
|
|
||||||
|
if (username && password && server && port) {
|
||||||
|
Accounts.emailTemplates.siteName = siteName;
|
||||||
|
Accounts.emailTemplates.from = siteName+' Admin <'+username+'>';
|
||||||
|
|
||||||
|
process.env.MAIL_URL = 'smtp://' +
|
||||||
|
encodeURIComponent(username) + ':' +
|
||||||
|
encodeURIComponent(password) + '@' +
|
||||||
|
encodeURIComponent(server) + ':' + port;
|
||||||
|
|
||||||
|
// Subject line of the email.
|
||||||
|
Accounts.emailTemplates.verifyEmail.subject = function(user) {
|
||||||
|
return 'Confirm Your Email Address for '+siteName;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Email text
|
||||||
|
Accounts.emailTemplates.verifyEmail.text = function(user, url) {
|
||||||
|
return 'Thank you for registering. Please click on the following link to verify your email address: \r\n' + url;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reset password mail
|
||||||
|
Accounts.emailTemplates.resetPassword.subject = function() {
|
||||||
|
return 'Reset your '+siteName+' password'
|
||||||
|
};
|
||||||
|
|
||||||
|
Accounts.urls.resetPassword = function(token) {
|
||||||
|
return Meteor.absoluteUrl('resetPassword/' + token);
|
||||||
|
};
|
||||||
|
|
||||||
|
Accounts.emailTemplates.resetPassword.text = function(user, url) {
|
||||||
|
return "Hello " + user.profile.fullName + ",\n\n" +
|
||||||
|
"Click the following link to set your new password:\n" +
|
||||||
|
url + "\n\n";
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send email when account is created
|
||||||
|
Accounts.config({
|
||||||
|
sendVerificationEmail: verifyEmail
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -1,21 +0,0 @@
|
|||||||
Package.describe({
|
|
||||||
name: 'dicomweb',
|
|
||||||
summary: 'DICOM Web Helper Functions',
|
|
||||||
version: '0.0.1'
|
|
||||||
});
|
|
||||||
|
|
||||||
Package.onUse(function(api) {
|
|
||||||
api.use('http');
|
|
||||||
|
|
||||||
// DICOMWeb API functions
|
|
||||||
api.addFiles('server/namespace.js', 'server');
|
|
||||||
api.addFiles('server/getAttribute.js', 'server');
|
|
||||||
api.addFiles('server/getJSON.js', 'server');
|
|
||||||
api.addFiles('server/getName.js', 'server');
|
|
||||||
api.addFiles('server/getNumber.js', 'server');
|
|
||||||
api.addFiles('server/getString.js', 'server');
|
|
||||||
api.addFiles('server/getModalities.js', 'server');
|
|
||||||
|
|
||||||
api.export('DICOMWeb', ['client', 'server']);
|
|
||||||
});
|
|
||||||
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
Package.describe({
|
|
||||||
name: 'dimseservice',
|
|
||||||
summary: 'DICOM DIMSE C-Service',
|
|
||||||
version: '0.0.1'
|
|
||||||
});
|
|
||||||
|
|
||||||
Package.onUse(function(api) {
|
|
||||||
api.versionsFrom('1.4');
|
|
||||||
|
|
||||||
api.use('ecmascript');
|
|
||||||
api.addFiles('server/require.js', 'server');
|
|
||||||
api.addFiles('server/constants.js', 'server');
|
|
||||||
api.addFiles('server/elements_data.js', 'server');
|
|
||||||
api.addFiles('server/Field.js', 'server');
|
|
||||||
api.addFiles('server/RWStream.js', 'server');
|
|
||||||
api.addFiles('server/Data.js', 'server');
|
|
||||||
api.addFiles('server/Message.js', 'server');
|
|
||||||
api.addFiles('server/PDU.js', 'server');
|
|
||||||
api.addFiles('server/CSocket.js', 'server');
|
|
||||||
api.addFiles('server/Connection.js', 'server');
|
|
||||||
api.addFiles('server/DIMSE.js', 'server');
|
|
||||||
|
|
||||||
api.export('DIMSE', 'server');
|
|
||||||
});
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
import { Mongo } from 'meteor/mongo';
|
|
||||||
|
|
||||||
Timepoints = new Mongo.Collection('timepoints');
|
|
||||||
Studies = new Mongo.Collection('studies');
|
|
||||||
Measurements = new Mongo.Collection('measurements');
|
|
||||||
|
|
||||||
// ImageMeasurements describe temporary measurements that aren't
|
|
||||||
// specifically listed in the Lesion Table
|
|
||||||
ImageMeasurements = new Mongo.Collection('imageMeasurements');
|
|
||||||
|
|
||||||
// Additional Findings stores details from the Additional Findings
|
|
||||||
// panel, and represents items such as data quality,
|
|
||||||
AdditionalFindings = new Mongo.Collection('additionalFindings');
|
|
||||||
|
|
||||||
WorklistSubscriptions = ['studies', 'timepoints'];
|
|
||||||
Reviewers = new Mongo.Collection('reviewers');
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
import { SimpleSchema } from 'meteor/aldeed:simple-schema';
|
|
||||||
|
|
||||||
export const schema = new SimpleSchema({
|
|
||||||
measurableDisease: {
|
|
||||||
type: String,
|
|
||||||
label: 'Measurable disease',
|
|
||||||
allowedValues: ['Present', 'Absent'],
|
|
||||||
defaultValue: 'Absent',
|
|
||||||
optional: true
|
|
||||||
},
|
|
||||||
// TODO: Add check so that the value cannot be None & something other region
|
|
||||||
regionsOfMetastaticDisease: {
|
|
||||||
type: [String],
|
|
||||||
label: 'Regions of metastatic disease',
|
|
||||||
allowedValues: ['None', 'Lymph Node'],
|
|
||||||
defaultValue: ['None'],
|
|
||||||
optional: true
|
|
||||||
},
|
|
||||||
tracerRelatedToMetastaticDisease: {
|
|
||||||
type: String,
|
|
||||||
label: 'Tracer related to metastatic disease?',
|
|
||||||
allowedValues: ['Yes', 'No'],
|
|
||||||
defaultValue: 'Yes',
|
|
||||||
optional: true
|
|
||||||
},
|
|
||||||
numberOfBoneLesions: {
|
|
||||||
type: String,
|
|
||||||
label: 'Number of bone lesions',
|
|
||||||
allowedValues: ['0', '1-2', '2-4', '>5'],
|
|
||||||
defaultValue: '0',
|
|
||||||
optional: true
|
|
||||||
},
|
|
||||||
acceptableImageQuality: {
|
|
||||||
type: String,
|
|
||||||
label: 'Acceptable image quality',
|
|
||||||
allowedValues: ['Yes', 'No'],
|
|
||||||
defaultValue: 'Yes',
|
|
||||||
optional: true
|
|
||||||
},
|
|
||||||
adequateAnatomicalCoverage: {
|
|
||||||
type: String,
|
|
||||||
label: 'Adequate anatomical coverage',
|
|
||||||
allowedValues: ['Yes', 'No'],
|
|
||||||
defaultValue: 'Yes',
|
|
||||||
optional: true
|
|
||||||
},
|
|
||||||
presenceOfContrast: {
|
|
||||||
type: String,
|
|
||||||
label: 'Presence of contrast',
|
|
||||||
allowedValues: ['Yes', 'No'],
|
|
||||||
defaultValue: 'Yes',
|
|
||||||
optional: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1,102 +0,0 @@
|
|||||||
const hasValueAtTimepoint = timepointId => {
|
|
||||||
return measurement => {
|
|
||||||
if (measurement.timepoints[timepointId]) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const hasNoValueAtTimepoint = timepointId => {
|
|
||||||
return measurement => {
|
|
||||||
if (measurement.timepoints[timepointId] === undefined) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MeasurementApi = {
|
|
||||||
sortOptions: {
|
|
||||||
sort: {
|
|
||||||
lesionNumberAbsolute: 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Return all Measurements
|
|
||||||
all(withPriors=false) {
|
|
||||||
let data = Measurements.find({}, this.sortOptions).fetch();
|
|
||||||
|
|
||||||
// If we don't have a prior for this Timepoint,
|
|
||||||
// this filter, we should just return all of the
|
|
||||||
// available Non-Targets measurements
|
|
||||||
if (this.priorTimepointId && withPriors === true) {
|
|
||||||
return data.filter(hasValueAtTimepoint(this.priorTimepointId))
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
unmarked() {
|
|
||||||
const withPriors = true;
|
|
||||||
return this.all(withPriors).filter(hasNoValueAtTimepoint(this.currentTimepointId));;
|
|
||||||
},
|
|
||||||
|
|
||||||
unmarkedTargets() {
|
|
||||||
const withPriors = true;
|
|
||||||
return this.targets(withPriors).filter(hasNoValueAtTimepoint(this.currentTimepointId));;
|
|
||||||
},
|
|
||||||
|
|
||||||
unmarkedNonTargets() {
|
|
||||||
const withPriors = true;
|
|
||||||
return this.nonTargets(withPriors).filter(hasNoValueAtTimepoint(this.currentTimepointId));;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Return only Target Measurements
|
|
||||||
targets(withPriors=false) {
|
|
||||||
let data = Measurements.find({
|
|
||||||
isTarget: true
|
|
||||||
}, this.sortOptions).fetch();
|
|
||||||
|
|
||||||
// If we don't have a prior for this Timepoint,
|
|
||||||
// this filter, we should just return all of the
|
|
||||||
// available Non-Targets measurements
|
|
||||||
if (this.priorTimepointId && withPriors === true) {
|
|
||||||
return data.filter(hasValueAtTimepoint(this.priorTimepointId))
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Return only Non-Target Measurements
|
|
||||||
nonTargets(withPriors=false) {
|
|
||||||
let data = Measurements.find({
|
|
||||||
isTarget: false
|
|
||||||
}, this.sortOptions).fetch();
|
|
||||||
|
|
||||||
// If we don't have a prior for this Timepoint,
|
|
||||||
// this filter, we should just return all of the
|
|
||||||
// available Non-Targets measurements
|
|
||||||
if (this.priorTimepointId && withPriors === true) {
|
|
||||||
return data.filter(hasValueAtTimepoint(this.priorTimepointId))
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Return only New Lesions
|
|
||||||
newLesions() {
|
|
||||||
// If we are current editing a Baseline we won't have any priors, so newLesions
|
|
||||||
// should return an empty array.
|
|
||||||
if (!this.priorTimepointId) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find only lesions that have no value at the previous timepoint
|
|
||||||
return this.all().filter(hasNoValueAtTimepoint(this.priorTimepointId));
|
|
||||||
},
|
|
||||||
|
|
||||||
firstLesion() {
|
|
||||||
return Measurements.findOne({
|
|
||||||
target: true
|
|
||||||
}, this.sortOptions);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,130 +0,0 @@
|
|||||||
class TimepointApi {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
// Run this computation every time the timepoints are changed
|
|
||||||
Tracker.autorun(() => {
|
|
||||||
// Get all the timepoints and store it
|
|
||||||
this.timepoints = new Mongo.Collection(null);
|
|
||||||
const timepoints = Timepoints.find({}, {
|
|
||||||
sort: {
|
|
||||||
latestDate: -1
|
|
||||||
}
|
|
||||||
}).fetch();
|
|
||||||
_.each(timepoints, timepoint => this.timepoints.insert(timepoint));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return all timepoints
|
|
||||||
all() {
|
|
||||||
return this.timepoints.find().fetch();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return only the current timepoint
|
|
||||||
current() {
|
|
||||||
return this.timepoints.findOne({
|
|
||||||
timepointId: this.currentTimepointId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the prior timepoint
|
|
||||||
prior() {
|
|
||||||
const current = this.current();
|
|
||||||
if (!current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const latestDate = current.latestDate;
|
|
||||||
return this.timepoints.findOne({
|
|
||||||
latestDate: {
|
|
||||||
$lt: latestDate
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
sort: {
|
|
||||||
latestDate: -1
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return only the current and prior Timepoints
|
|
||||||
currentAndPrior() {
|
|
||||||
const timepoints = [];
|
|
||||||
|
|
||||||
const current = this.current();
|
|
||||||
if (current) {
|
|
||||||
timepoints.push(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
const prior = this.prior();
|
|
||||||
if (current && prior && prior._id !== current._id) {
|
|
||||||
timepoints.push(prior);
|
|
||||||
}
|
|
||||||
|
|
||||||
return timepoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return only the baseline timepoint
|
|
||||||
baseline() {
|
|
||||||
return this.timepoints.findOne({
|
|
||||||
timepointType: 'baseline'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return only the key timepoints (current, prior, nadir and baseline)
|
|
||||||
key() {
|
|
||||||
// Create a new Mini Mongo Collection to store the result
|
|
||||||
const result = new Mongo.Collection(null);
|
|
||||||
|
|
||||||
// Get all the timepoints
|
|
||||||
const all = this.all();
|
|
||||||
|
|
||||||
// Iterate over each timepoint and insert the key ones in the result
|
|
||||||
_.each(all, (timepoint, index) => {
|
|
||||||
if (index < 2 || index === (all.length - 1)) {
|
|
||||||
result.insert(timepoint);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Return the resulting timepoints
|
|
||||||
return result.find().fetch();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return only the timepoints for the given study
|
|
||||||
study(studyInstanceUid) {
|
|
||||||
// Create a new Mini Mongo Collection to store the result
|
|
||||||
const result = new Mongo.Collection(null);
|
|
||||||
|
|
||||||
// Iterate over each timepoint and insert the key ones in the result
|
|
||||||
_.each(this.all(), (timepoint, index) => {
|
|
||||||
if (_.contains(timepoint.studyInstanceUids, studyInstanceUid)) {
|
|
||||||
result.insert(timepoint);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Return the resulting timepoints
|
|
||||||
return result.find().fetch();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the timepoint title based on its date
|
|
||||||
title(timepoint) {
|
|
||||||
const timepointName = getTimepointName(timepoint);
|
|
||||||
|
|
||||||
const all = this.all();
|
|
||||||
let index = -1;
|
|
||||||
_.each(all, (currentTimepoint, currentIndex) => {
|
|
||||||
if (currentTimepoint.timepointId === timepoint.timepointId) {
|
|
||||||
index = currentIndex;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const states = {
|
|
||||||
0: '(Current)',
|
|
||||||
1: '(Prior)'
|
|
||||||
};
|
|
||||||
// TODO: [design] find out how to define the nadir timepoint
|
|
||||||
const parenthesis = states[index] || '';
|
|
||||||
return `${timepointName} ${parenthesis}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export { TimepointApi };
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
(function($, cornerstone, cornerstoneMath, cornerstoneTools) {
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var crToolInterface = cornerstoneTools.crunexTool('crTool');
|
|
||||||
cornerstoneTools.crTool = crToolInterface.crunex;
|
|
||||||
cornerstoneTools.crTool.setConfiguration(crToolInterface.defaultConfiguration);
|
|
||||||
cornerstoneTools.crToolTouch = crToolInterface.crunexTouch;
|
|
||||||
|
|
||||||
})($, cornerstone, cornerstoneMath, cornerstoneTools);
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
(function($, cornerstone, cornerstoneMath, cornerstoneTools) {
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
var exToolInterface = cornerstoneTools.crunexTool('exTool');
|
|
||||||
cornerstoneTools.exTool = exToolInterface.crunex;
|
|
||||||
cornerstoneTools.exTool.setConfiguration(exToolInterface.defaultConfiguration);
|
|
||||||
cornerstoneTools.exToolTouch = exToolInterface.crunexTouch;
|
|
||||||
|
|
||||||
})($, cornerstone, cornerstoneMath, cornerstoneTools);
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
(function($, cornerstone, cornerstoneMath, cornerstoneTools) {
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
var unToolInterface = cornerstoneTools.crunexTool('unTool');
|
|
||||||
cornerstoneTools.unTool = unToolInterface.crunex;
|
|
||||||
cornerstoneTools.unTool.setConfiguration(unToolInterface.defaultConfiguration);
|
|
||||||
cornerstoneTools.unToolTouch = unToolInterface.crunexTouch;
|
|
||||||
|
|
||||||
})($, cornerstone, cornerstoneMath, cornerstoneTools);
|
|
||||||
@ -1,55 +0,0 @@
|
|||||||
<template name="additionalFindings">
|
|
||||||
<div class="additionalFindings">
|
|
||||||
<div class="scrollArea">
|
|
||||||
{{#form schema=instance.currentSchema}}
|
|
||||||
<div class="row">
|
|
||||||
<div class="header">Additional Findings: Baseline</div>
|
|
||||||
{{>groupRadio labelClass='form-group' key='measurableDisease'}}
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="header">Supplementary Measurements</div>
|
|
||||||
{{>inputSelect class="bone-lesions" labelClass='form-group' key='numberOfBoneLesions' hideSearch=true}}
|
|
||||||
{{>inputSelect
|
|
||||||
class="disease-regions"
|
|
||||||
labelClass='form-group'
|
|
||||||
key='regionsOfMetastaticDisease'
|
|
||||||
multiple=true
|
|
||||||
options=(clone
|
|
||||||
placeholder='Choose a region'
|
|
||||||
allowClear=true
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
{{>groupRadio labelClass='form-group' key='tracerRelatedToMetastaticDisease'}}
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="header">Image Quality</div>
|
|
||||||
{{>groupRadio labelClass='form-group' key='acceptableImageQuality'}}
|
|
||||||
{{>groupRadio labelClass='form-group' key='adequateAnatomicalCoverage'}}
|
|
||||||
{{>groupRadio labelClass='form-group' key='presenceOfContrast'}}
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="header">Additional Screen Captures</div>
|
|
||||||
<div class="entrySection buttonSection">
|
|
||||||
<div class="svgContainer">
|
|
||||||
<svg>
|
|
||||||
<use xlink:href="/packages/viewerbase/assets/icons.svg#icon-create-screen-capture"></use>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="buttonLabel noselect">Add new screen capture</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="header">Comments</div>
|
|
||||||
<div class="entrySection buttonSection">
|
|
||||||
<div class="svgContainer">
|
|
||||||
<svg>
|
|
||||||
<use xlink:href="/packages/viewerbase/assets/icons.svg#icon-create-comment"></use>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="buttonLabel noselect">Add a comment</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/form}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
import { Template } from 'meteor/templating';
|
|
||||||
import { ReactiveDict } from 'meteor/reactive-dict';
|
|
||||||
import { schema as AdditionalFindingsSchema } from 'meteor/lesiontracker/both/schema/additionalFindings';
|
|
||||||
|
|
||||||
Template.additionalFindings.onCreated(() => {
|
|
||||||
const instance = Template.instance();
|
|
||||||
|
|
||||||
instance.currentSchema = AdditionalFindingsSchema;
|
|
||||||
});
|
|
||||||
|
|
||||||
Template.additionalFindings.onRendered(() => {
|
|
||||||
const instance = Template.instance();
|
|
||||||
|
|
||||||
// Get the form component
|
|
||||||
const form = instance.$('form:first').data('component');
|
|
||||||
|
|
||||||
const currentTimepointId = instance.data.currentTimepointId;
|
|
||||||
if (!currentTimepointId) {
|
|
||||||
console.warn('Case has no timepointId');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const additionalFindings = AdditionalFindings.findOne({
|
|
||||||
timepointId: currentTimepointId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (additionalFindings) {
|
|
||||||
instance.id = additionalFindings._id;
|
|
||||||
form.value(additionalFindings);
|
|
||||||
} else {
|
|
||||||
const defaultData = instance.currentSchema.clean({});
|
|
||||||
form.value(defaultData);
|
|
||||||
|
|
||||||
// Include patientId and timepointId
|
|
||||||
defaultData.patientId = Session.get('patientId');
|
|
||||||
defaultData.timepointId = currentTimepointId;
|
|
||||||
|
|
||||||
// TODO: [design] Turn this into a Meteor Call to insert it on the server
|
|
||||||
instance.id = AdditionalFindings.insert(defaultData);
|
|
||||||
}
|
|
||||||
|
|
||||||
instance.autorun(computation => {
|
|
||||||
// Run this computation everytime the form data is changed
|
|
||||||
form.depend();
|
|
||||||
|
|
||||||
// Stop here if it's the computation's first run
|
|
||||||
if (computation.firstRun) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the form data for AdditionalFindings
|
|
||||||
let formData = form.value();
|
|
||||||
|
|
||||||
// TODO: [design] Turn this into a Meteor Call to update it on the server
|
|
||||||
AdditionalFindings.update(instance.id, {
|
|
||||||
$set: formData
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,112 +0,0 @@
|
|||||||
@import "{design}/app"
|
|
||||||
|
|
||||||
.additionalFindings
|
|
||||||
width: 100%
|
|
||||||
height: 100%
|
|
||||||
background-color: #000000
|
|
||||||
|
|
||||||
.scrollArea
|
|
||||||
height: 100%
|
|
||||||
overflow-x: hidden
|
|
||||||
overflow-y: auto
|
|
||||||
margin-right: -16px
|
|
||||||
padding-right: 16px
|
|
||||||
|
|
||||||
-webkit-overflow-scrolling: touch
|
|
||||||
|
|
||||||
.row
|
|
||||||
margin-right: 0
|
|
||||||
margin-left: 0
|
|
||||||
|
|
||||||
&::-webkit-scrollbar
|
|
||||||
display: none
|
|
||||||
|
|
||||||
.header
|
|
||||||
theme('background-color', '$uiGray')
|
|
||||||
theme('color', '$textPrimaryColor')
|
|
||||||
height: 33px
|
|
||||||
line-height: 33px
|
|
||||||
font-weight: bold
|
|
||||||
font-size: 14px
|
|
||||||
padding: 1px 13px 0
|
|
||||||
|
|
||||||
.form-group
|
|
||||||
theme('color', '$textPrimaryColor')
|
|
||||||
margin-bottom: 0
|
|
||||||
padding: 7px 27px 7px 11px
|
|
||||||
width: 100%
|
|
||||||
|
|
||||||
&:not(:last-child)
|
|
||||||
theme('border-bottom', '%s solid $uiBorderColorDark' % $uiBorderThickness)
|
|
||||||
|
|
||||||
& > .wrapperText
|
|
||||||
display: block
|
|
||||||
font-weight: bold
|
|
||||||
line-height: 15px
|
|
||||||
margin: 10px 2px 8px
|
|
||||||
|
|
||||||
.control-label
|
|
||||||
font-size: 14px
|
|
||||||
font-weight: 100
|
|
||||||
display: inline-block
|
|
||||||
padding: 5px
|
|
||||||
|
|
||||||
.select2
|
|
||||||
margin-bottom: 8px
|
|
||||||
margin-left: 3px
|
|
||||||
margin-top: 5px
|
|
||||||
|
|
||||||
.group-radio
|
|
||||||
height: 30px
|
|
||||||
margin-bottom: 15px
|
|
||||||
|
|
||||||
label
|
|
||||||
display: inline-block
|
|
||||||
float: left
|
|
||||||
font-size: 14px
|
|
||||||
font-weight: 100
|
|
||||||
height: 30px
|
|
||||||
line-height: 30px
|
|
||||||
width: 109px
|
|
||||||
|
|
||||||
.disease-regions + .select2
|
|
||||||
width: 100% !important
|
|
||||||
|
|
||||||
.bone-lesions + .select2
|
|
||||||
width: auto !important
|
|
||||||
|
|
||||||
.buttonSection
|
|
||||||
cursor: pointer
|
|
||||||
height: 90px
|
|
||||||
theme('color', '$defaultColor')
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
theme('color', '$hoverColor')
|
|
||||||
|
|
||||||
.svgContainer
|
|
||||||
svg
|
|
||||||
theme('fill', '$hoverColor')
|
|
||||||
|
|
||||||
&:active
|
|
||||||
theme('color', '$activeColor')
|
|
||||||
|
|
||||||
.svgContainer
|
|
||||||
svg
|
|
||||||
theme('fill', '$activeColor')
|
|
||||||
|
|
||||||
div
|
|
||||||
float: left
|
|
||||||
display: inline-block
|
|
||||||
height: 92px
|
|
||||||
|
|
||||||
.buttonLabel
|
|
||||||
line-height: 92px
|
|
||||||
padding: 0 12px
|
|
||||||
|
|
||||||
.svgContainer
|
|
||||||
padding: 25px 14px
|
|
||||||
|
|
||||||
svg
|
|
||||||
width: 36px
|
|
||||||
height: 36px
|
|
||||||
theme('fill', '$textPrimaryColor')
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
<template name="conformanceCheckFeedback">
|
|
||||||
<div id="conformanceCheckFeedback">
|
|
||||||
{{ #each validationErrors }}
|
|
||||||
<p data-toggle=""><i class="fa fa-exclamation-triangle fa-lg"></i> {{prefix}}{{error}}</p>
|
|
||||||
{{ /each }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
Template.conformanceCheckFeedback.helpers({
|
|
||||||
validationErrors: function() {
|
|
||||||
// Return validation errors sorted by last added Target
|
|
||||||
var errors = ValidationErrors.find({}, {
|
|
||||||
sort: {
|
|
||||||
prefix: -1,
|
|
||||||
type: 1
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Template.instance().validationErrors.set(errors.fetch());
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Template.conformanceCheckFeedback.events({
|
|
||||||
'mouseenter #conformanceCheckFeedback': function(e, template) {
|
|
||||||
if (template.validationErrors.get().length > 0) {
|
|
||||||
$(e.currentTarget).addClass("conformanceHover");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'mouseleave #conformanceCheckFeedback': function(e, template) {
|
|
||||||
var conformanceDiv = e.currentTarget;
|
|
||||||
if ($(conformanceDiv).hasClass("conformanceHover")) {
|
|
||||||
$(conformanceDiv).removeClass("conformanceHover");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Template.conformanceCheckFeedback.onCreated(function() {
|
|
||||||
this.validationErrors = new ReactiveVar([]);
|
|
||||||
});
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
#conformanceCheckFeedback
|
|
||||||
position: absolute
|
|
||||||
right: 10px
|
|
||||||
color: darkorange
|
|
||||||
cursor: pointer
|
|
||||||
background: black
|
|
||||||
|
|
||||||
width: 30%
|
|
||||||
height: 30px
|
|
||||||
overflow: hidden
|
|
||||||
|
|
||||||
p
|
|
||||||
margin: 5px
|
|
||||||
text-align: center
|
|
||||||
|
|
||||||
.conformanceHover
|
|
||||||
overflow: auto !important
|
|
||||||
height: 200px !important
|
|
||||||
z-index: 200
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
<template name="flexboxLayout">
|
|
||||||
<div class="viewerSection">
|
|
||||||
<div class="sidebarMenu sidebar-left {{#if leftSidebarOpen}}sidebar-open{{/if}}">
|
|
||||||
{{>studyTimepointBrowser (clone this)}}
|
|
||||||
</div>
|
|
||||||
<div class="mainContent {{#if leftSidebarOpen}}sidebar-left-open{{/if}} {{#if rightSidebarOpen}}sidebar-right-open{{/if}}">
|
|
||||||
{{>viewerMain (clone this)}}
|
|
||||||
</div>
|
|
||||||
<div class="sidebarMenu sidebar-right {{#if or lesionSidebarOpen additionalFindingsSidebarOpen}}sidebar-open{{/if}}">
|
|
||||||
<div class="sidebar-option {{#if lesionSidebarOpen}}active{{/if}}">
|
|
||||||
{{>lesionTable (clone this)}}
|
|
||||||
</div>
|
|
||||||
<div class="sidebar-option {{#if additionalFindingsSidebarOpen}}active{{/if}}">
|
|
||||||
{{>additionalFindings (clone this)}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
<template name="lesionTable">
|
|
||||||
<div id="lesionTableContainer">
|
|
||||||
{{#if this.timepoints.get.length}}
|
|
||||||
<div class="lesionTableLayoutChanger viewerRoundedButtonGroup">
|
|
||||||
{{>roundedButtonGroup buttonGroupData}}
|
|
||||||
</div>
|
|
||||||
<div class="lesionTableTimepointHeaderRow">
|
|
||||||
{{#each this.timepoints.get}}
|
|
||||||
{{>lesionTableTimepointHeader}}
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
{{>lesionTableView (clone this)}}
|
|
||||||
{{else}}
|
|
||||||
<h3 class="p-x-1 text-center">No associated timepoint</h3>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
Template.lesionTableHUD.onCreated(() => {
|
|
||||||
const instance = Template.instance();
|
|
||||||
|
|
||||||
instance.data.timepoints = new ReactiveVar(instance.data.timepointApi.currentAndPrior());
|
|
||||||
});
|
|
||||||
|
|
||||||
Template.lesionTableHUD.onRendered(() => {
|
|
||||||
const instance = Template.instance();
|
|
||||||
instance.$('#lesionTableHUD').resizable().draggable();
|
|
||||||
});
|
|
||||||
|
|
||||||
Template.lesionTableHUD.events({
|
|
||||||
'click .buttonClose'(event, instance) {
|
|
||||||
Session.set('lesionTableHudOpen', false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Template.lesionTableHUD.helpers({
|
|
||||||
hudHidden() {
|
|
||||||
return Session.get('lesionTableHudOpen') ? 'dialog-open' : 'dialog-closed';
|
|
||||||
},
|
|
||||||
toolbarButtons() {
|
|
||||||
var buttonData = [];
|
|
||||||
|
|
||||||
buttonData.push({
|
|
||||||
id: 'bidirectional',
|
|
||||||
title: 'Target',
|
|
||||||
classes: 'imageViewerTool',
|
|
||||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-target'
|
|
||||||
});
|
|
||||||
|
|
||||||
buttonData.push({
|
|
||||||
id: 'nonTarget',
|
|
||||||
title: 'Non-Target',
|
|
||||||
classes: 'imageViewerTool toolbarSectionButton',
|
|
||||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-non-target'
|
|
||||||
});
|
|
||||||
|
|
||||||
buttonData.push({
|
|
||||||
id: 'length',
|
|
||||||
title: 'Temp',
|
|
||||||
classes: 'imageViewerTool toolbarSectionButton',
|
|
||||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-temp'
|
|
||||||
});
|
|
||||||
|
|
||||||
return buttonData;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
<template name="lesionTableHeaderRow">
|
|
||||||
<div class="lesionTableHeaderRow">
|
|
||||||
{{ #if anyUnmarkedLesionsLeft}}
|
|
||||||
<div class="add js-setTool">
|
|
||||||
<svg>
|
|
||||||
<use xlink:href=/packages/viewerbase/assets/icons.svg#icon-ui-add></use>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
{{ /if }}
|
|
||||||
<div class="type">
|
|
||||||
{{type}}
|
|
||||||
</div>
|
|
||||||
<div class="max {{ # if gt numberOfLesions maxNumLesions }}warning{{/if}}">
|
|
||||||
{{ #if maxNumLesions }}
|
|
||||||
<p class="maxNumLesions">
|
|
||||||
Max {{maxNumLesions}}
|
|
||||||
</p>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
<div class="numberOfLesions">
|
|
||||||
{{numberOfLesions}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
<template name="lesionTableTimepointCell">
|
|
||||||
{{#if hasDataAtThisTimepoint}}
|
|
||||||
<div class="lesionTableTimepointCell" tabindex="1">
|
|
||||||
{{displayData}}
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<div class="lesionTableTimepointCell empty">
|
|
||||||
...
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</template>
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
Template.lesionTableTimepointHeader.helpers({
|
|
||||||
'timepointName': function() {
|
|
||||||
var timepoint = this;
|
|
||||||
return getTimepointName(timepoint);
|
|
||||||
},
|
|
||||||
isLoaded: function() {
|
|
||||||
Session.get('NewSeriesLoaded');
|
|
||||||
|
|
||||||
log.info('ViewerData changed, check for displayed timepoints');
|
|
||||||
|
|
||||||
var timepoint = this;
|
|
||||||
|
|
||||||
var isLoaded = false;
|
|
||||||
var viewports = $('.imageViewerViewport').not('.empty');
|
|
||||||
viewports.each(function(index, element) {
|
|
||||||
var enabledElement = cornerstone.getEnabledElement(element);
|
|
||||||
if (!enabledElement || !enabledElement.image) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var study = cornerstoneTools.metaData.get('study', enabledElement.image.imageId);
|
|
||||||
|
|
||||||
var currentTimepoint = Timepoints.findOne({
|
|
||||||
studyInstanceUids: {
|
|
||||||
$in: [study.studyInstanceUid]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (currentTimepoint && currentTimepoint._id === timepoint._id) {
|
|
||||||
isLoaded = true;
|
|
||||||
|
|
||||||
// Break the loop
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return isLoaded;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
<template name="lesionTableView">
|
|
||||||
<div class="lesionTableView scrollArea">
|
|
||||||
{{>lesionTableHeaderRow id="target"
|
|
||||||
measurements=targets
|
|
||||||
currentTimepointId=currentTimepointId
|
|
||||||
timepointApi=timepointApi}}
|
|
||||||
{{#each target in targets}}
|
|
||||||
{{>lesionTableRow (clone this rowItem=target)}}
|
|
||||||
{{/each}}
|
|
||||||
|
|
||||||
{{>lesionTableHeaderRow id="nonTarget"
|
|
||||||
measurements=nonTargets
|
|
||||||
currentTimepointId=currentTimepointId
|
|
||||||
timepointApi=timepointApi}}
|
|
||||||
{{#each nonTarget in nonTargets}}
|
|
||||||
{{>lesionTableRow (clone this rowItem=nonTarget)}}
|
|
||||||
{{/each}}
|
|
||||||
|
|
||||||
{{#if gt this.timepoints.get.length 1}}
|
|
||||||
{{>lesionTableHeaderRow id="newLesion"
|
|
||||||
measurements=newLesions
|
|
||||||
currentTimepointId=currentTimepointId
|
|
||||||
timepointApi=timepointApi}}
|
|
||||||
{{#each newLesion in newLesions}}
|
|
||||||
{{>lesionTableRow (clone this rowItem=newLesion)}}
|
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import { MeasurementApi } from 'meteor/lesiontracker/client/api/measurement';
|
|
||||||
|
|
||||||
Template.lesionTableView.helpers({
|
|
||||||
targets() {
|
|
||||||
const withPriors = true;
|
|
||||||
return MeasurementApi.targets(withPriors);
|
|
||||||
},
|
|
||||||
|
|
||||||
nonTargets() {
|
|
||||||
const withPriors = true;
|
|
||||||
return MeasurementApi.nonTargets(withPriors);
|
|
||||||
},
|
|
||||||
|
|
||||||
newLesions() {
|
|
||||||
return MeasurementApi.newLesions();
|
|
||||||
},
|
|
||||||
|
|
||||||
isFollowup() {
|
|
||||||
const instance = Template.instance();
|
|
||||||
const current = instance.data.timepointApi.current();
|
|
||||||
return (current && current.timepointType === 'followup');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
<template name="lesionTrackerWorklistContextMenu">
|
|
||||||
{{#if uiSettings.studyListFunctionsEnabled}}
|
|
||||||
<div id="studyContextMenu" class="studyContextMenu noselect"
|
|
||||||
oncontextmenu='return false;'
|
|
||||||
unselectable='on'
|
|
||||||
onselectstart='return false;'
|
|
||||||
>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a id="viewStudies" type="button"
|
|
||||||
title="View Studies"
|
|
||||||
>View</a>
|
|
||||||
<hr/>
|
|
||||||
|
|
||||||
<a id="launchStudyAssociation" type="button"
|
|
||||||
data-toggle="modal"
|
|
||||||
data-target="#associationModal"
|
|
||||||
title="Launch Study Association"
|
|
||||||
>Associate</a>
|
|
||||||
<a id="launchRemoveAssociation" type="button"
|
|
||||||
data-toggle="modal"
|
|
||||||
data-target="#confirmRemoveTimepointAssociation"
|
|
||||||
title="Remove Timepoint Association"
|
|
||||||
>Remove Association</a>
|
|
||||||
<hr/>
|
|
||||||
|
|
||||||
<a id="viewSeriesDetails" type="button"
|
|
||||||
title="View Series Details"
|
|
||||||
>View Series Details</a>
|
|
||||||
<a class="disabled">Anonymize</a>
|
|
||||||
<a class="disabled">Send</a>
|
|
||||||
<hr/>
|
|
||||||
|
|
||||||
<a id="exportSelectedStudies"
|
|
||||||
title="Export Selected Studies"
|
|
||||||
>Export</a>
|
|
||||||
<a class="disabled">Delete</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</template>
|
|
||||||
@ -1,103 +0,0 @@
|
|||||||
// Use Aldeed's meteor-template-extension package to replace the
|
|
||||||
// default WorklistStudy template.
|
|
||||||
// See https://github.com/aldeed/meteor-template-extension
|
|
||||||
var defaultTemplate = 'worklistStudy';
|
|
||||||
Template.lesionTrackerWorklistStudy.replaces(defaultTemplate);
|
|
||||||
|
|
||||||
// Add the TimepointName helper to the default template. The
|
|
||||||
// HTML of this template is replaced with that of lesionTrackerWorklistStudy
|
|
||||||
Template[defaultTemplate].helpers({
|
|
||||||
timepointName: function() {
|
|
||||||
var data = this;
|
|
||||||
var study = Studies.findOne({
|
|
||||||
studyInstanceUid: data.studyInstanceUid
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!study) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var timepoint = Timepoints.findOne({
|
|
||||||
timepointId: study.timepointId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!timepoint) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return getTimepointName(timepoint);
|
|
||||||
},
|
|
||||||
reviewerTip: function() {
|
|
||||||
var data = this;
|
|
||||||
var study = Studies.findOne({
|
|
||||||
studyInstanceUid: data.studyInstanceUid
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!study) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var timepoint = Timepoints.findOne({
|
|
||||||
timepointId: study.timepointId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!timepoint) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var timepointReviewers = Reviewers.findOne({
|
|
||||||
timepointId: timepoint.timepointId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!timepointReviewers) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return getReviewerTipText(timepointReviewers.reviewers);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function getReviewerTipText(reviewers) {
|
|
||||||
if (!reviewers || reviewers.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var newReviewers = reviewers.filter(function(reviewer) {
|
|
||||||
return reviewer.userId !== Meteor.userId();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (newReviewers.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var tipText = 'The study is being reviewed by ';
|
|
||||||
newReviewers.forEach(function(reviewer, index) {
|
|
||||||
if (reviewer.userId === Meteor.userId()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index > 0) {
|
|
||||||
tipText += ',';
|
|
||||||
}
|
|
||||||
|
|
||||||
tipText += reviewer.userName;
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
return tipText;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Observe changes on WorklistTabs
|
|
||||||
// remove the current user from Reviewers if tab is removed
|
|
||||||
WorklistTabs.find().observe({
|
|
||||||
removed: function(tab) {
|
|
||||||
var contentId = tab.contentid;
|
|
||||||
var timepointIds = ViewerData[contentId].timepointIds;
|
|
||||||
if (timepointIds && timepointIds.length > 0) {
|
|
||||||
timepointIds.forEach(function(timepointId) {
|
|
||||||
// Remove the current user from Reviewers
|
|
||||||
Meteor.call('removeReviewer', timepointId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1,262 +0,0 @@
|
|||||||
import { OHIF } from 'meteor/ohif:core';
|
|
||||||
import { TimepointApi } from 'meteor/lesiontracker/client/api/timepoint';
|
|
||||||
import { MeasurementApi } from 'meteor/lesiontracker/client/api/measurement';
|
|
||||||
|
|
||||||
OHIF.viewer = OHIF.viewer || {};
|
|
||||||
OHIF.viewer.loadIndicatorDelay = 3000;
|
|
||||||
OHIF.viewer.defaultTool = 'wwwc';
|
|
||||||
OHIF.viewer.refLinesEnabled = true;
|
|
||||||
OHIF.viewer.cine = {
|
|
||||||
framesPerSecond: 24,
|
|
||||||
loop: true
|
|
||||||
};
|
|
||||||
|
|
||||||
Session.setDefault('activeViewport', false);
|
|
||||||
Session.setDefault('leftSidebar', null);
|
|
||||||
Session.setDefault('rightSidebar', null);
|
|
||||||
|
|
||||||
Template.viewer.onCreated(() => {
|
|
||||||
const instance = Template.instance();
|
|
||||||
|
|
||||||
ValidationErrors.remove({});
|
|
||||||
|
|
||||||
instance.data.state = new ReactiveDict();
|
|
||||||
instance.data.state.set('leftSidebar', Session.get('leftSidebar'));
|
|
||||||
instance.data.state.set('rightSidebar', Session.get('rightSidebar'));
|
|
||||||
|
|
||||||
instance.subscribe('hangingprotocols');
|
|
||||||
|
|
||||||
Session.set('currentTimepointId', instance.data.currentTimepointId);
|
|
||||||
|
|
||||||
const contentId = instance.data.contentId;
|
|
||||||
|
|
||||||
OHIF.viewer.functionList = $.extend(OHIF.viewer.functionList, {
|
|
||||||
toggleLesionTrackerTools: toggleLesionTrackerTools,
|
|
||||||
clearTools: clearTools,
|
|
||||||
bidirectional: () => {
|
|
||||||
// Used for hotkeys
|
|
||||||
toolManager.setActiveTool('bidirectional');
|
|
||||||
},
|
|
||||||
nonTarget: () => {
|
|
||||||
// Used for hotkeys
|
|
||||||
toolManager.setActiveTool('nonTarget');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// The hotkey can also be an array (e.g. ["NUMPAD0", "0"])
|
|
||||||
OHIF.viewer.defaultHotkeys = OHIF.viewer.defaultHotkeys || {};
|
|
||||||
OHIF.viewer.defaultHotkeys.toggleLesionTrackerTools = 'O';
|
|
||||||
OHIF.viewer.defaultHotkeys.bidirectional = 'T'; // Target
|
|
||||||
OHIF.viewer.defaultHotkeys.nonTarget = 'N'; // Non-target
|
|
||||||
|
|
||||||
if (ViewerData[contentId].loadedSeriesData) {
|
|
||||||
log.info('Reloading previous loadedSeriesData');
|
|
||||||
OHIF.viewer.loadedSeriesData = ViewerData[contentId].loadedSeriesData;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
log.info('Setting default ViewerData');
|
|
||||||
OHIF.viewer.loadedSeriesData = {};
|
|
||||||
ViewerData[contentId].loadedSeriesData = {};
|
|
||||||
Session.set('ViewerData', ViewerData);
|
|
||||||
}
|
|
||||||
|
|
||||||
Session.set('activeViewport', ViewerData[contentId].activeViewport || false);
|
|
||||||
|
|
||||||
// Set lesion tool buttons as disabled if pixel spacing is not available for active element
|
|
||||||
instance.autorun(pixelSpacingAutorunCheck);
|
|
||||||
|
|
||||||
// Update the ViewerStudies collection with the loaded studies
|
|
||||||
ViewerStudies.remove({});
|
|
||||||
|
|
||||||
instance.data.studies.forEach(study => {
|
|
||||||
study.selected = true;
|
|
||||||
study.displaySets = createStacks(study);
|
|
||||||
ViewerStudies.insert(study);
|
|
||||||
});
|
|
||||||
|
|
||||||
const patientId = instance.data.studies[0].patientId;
|
|
||||||
Session.set('patientId', patientId);
|
|
||||||
|
|
||||||
instance.autorun(() => {
|
|
||||||
const dataContext = Template.currentData();
|
|
||||||
instance.subscribe('singlePatientAssociatedStudies', dataContext.studies[0].patientId);
|
|
||||||
instance.subscribe('singlePatientTimepoints', dataContext.studies[0].patientId);
|
|
||||||
instance.subscribe('singlePatientMeasurements', dataContext.studies[0].patientId);
|
|
||||||
instance.subscribe('singlePatientImageMeasurements', dataContext.studies[0].patientId);
|
|
||||||
instance.subscribe('singlePatientAdditionalFindings', dataContext.studies[0].patientId);
|
|
||||||
|
|
||||||
if (instance.subscriptionsReady()) {
|
|
||||||
// Set buttons as enabled/disabled when Timepoints collection is ready
|
|
||||||
timepointAutoCheck(dataContext);
|
|
||||||
|
|
||||||
// Wait until the Timepoint subscription is ready to initialize the TimepointApi
|
|
||||||
instance.data.timepointApi = new TimepointApi();
|
|
||||||
instance.data.timepointApi.currentTimepointId = instance.data.currentTimepointId;
|
|
||||||
|
|
||||||
// Provide the necessary data to the Measurement API
|
|
||||||
MeasurementApi.currentTimepointId = instance.data.currentTimepointId;
|
|
||||||
const prior = instance.data.timepointApi.prior();
|
|
||||||
if (prior) {
|
|
||||||
MeasurementApi.priorTimepointId = prior.timepointId;
|
|
||||||
}
|
|
||||||
|
|
||||||
TrialResponseCriteria.validateAllDelayed();
|
|
||||||
|
|
||||||
ViewerStudies.find().observe({
|
|
||||||
added: study => {
|
|
||||||
// Find the relevant timepoint given the newly added study
|
|
||||||
const timepoint = Timepoints.findOne({
|
|
||||||
studyInstanceUids: {
|
|
||||||
$in: [study.studyInstanceUid]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!timepoint) {
|
|
||||||
log.warn('Study added to Viewer has not been associated!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the added document with its related timepointId
|
|
||||||
ViewerStudies.update(study._id, {
|
|
||||||
$set: {
|
|
||||||
timepointId: timepoint.timepointId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ImageMeasurements.find().observe({
|
|
||||||
added: data => {
|
|
||||||
if (data.clientId === ClientId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
syncImageMeasurementAndToolData(data);
|
|
||||||
|
|
||||||
// Update each displayed viewport
|
|
||||||
updateAllViewports();
|
|
||||||
},
|
|
||||||
changed: data => {
|
|
||||||
if (data.clientId === ClientId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
syncImageMeasurementAndToolData(data);
|
|
||||||
|
|
||||||
// Update each displayed viewport
|
|
||||||
updateAllViewports();
|
|
||||||
},
|
|
||||||
removed: data => {
|
|
||||||
if (data.clientId === ClientId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
removeToolDataWithMeasurementId(data.imageId, data.toolType, data.id);
|
|
||||||
|
|
||||||
// Update each displayed viewport
|
|
||||||
updateAllViewports();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Measurements.find().observe({
|
|
||||||
added: data => {
|
|
||||||
if (data.clientId === ClientId) {
|
|
||||||
TrialResponseCriteria.validateAllDelayed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info('Measurement added');
|
|
||||||
|
|
||||||
// This is used to re-add tools from the database into the
|
|
||||||
// Cornerstone ToolData structure
|
|
||||||
syncMeasurementAndToolData(data);
|
|
||||||
|
|
||||||
// Update each displayed viewport
|
|
||||||
updateAllViewports();
|
|
||||||
},
|
|
||||||
changed: data => {
|
|
||||||
if (data.clientId === ClientId) {
|
|
||||||
TrialResponseCriteria.validateAllDelayed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info('Measurement changed');
|
|
||||||
|
|
||||||
// This is used to update changed tools from the database
|
|
||||||
// in the Cornerstone ToolData structure
|
|
||||||
syncMeasurementAndToolData(data);
|
|
||||||
|
|
||||||
// Update each displayed viewport
|
|
||||||
updateAllViewports();
|
|
||||||
|
|
||||||
TrialResponseCriteria.validateAllDelayed();
|
|
||||||
},
|
|
||||||
removed: data => {
|
|
||||||
log.info('Measurement removed');
|
|
||||||
|
|
||||||
// Check that this Measurement actually contains timepoint data
|
|
||||||
if (!data || !data.timepoints) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the Measurement ID and relevant tool so we can remove
|
|
||||||
// tool data for this Measurement
|
|
||||||
const measurementId = data._id;
|
|
||||||
const toolType = data.toolType;
|
|
||||||
|
|
||||||
// Remove the measurement from all the imageIds on which it exists
|
|
||||||
// as toolData
|
|
||||||
Object.keys(data.timepoints).forEach(timepointId => {
|
|
||||||
// Clear the toolData for this timepoint
|
|
||||||
const imageId = data.timepoints[timepointId].imageId;
|
|
||||||
removeToolDataWithMeasurementId(imageId, toolType, measurementId);
|
|
||||||
|
|
||||||
// Set reviewer for this timepoint
|
|
||||||
if (data.timepoints[timepointId].studyInstanceUid) {
|
|
||||||
Meteor.call('setReviewer', data.timepoints[timepointId].studyInstanceUid);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sync database data with toolData for all the measurements
|
|
||||||
// that have just been updated
|
|
||||||
|
|
||||||
// Note that here we need to use greater than and equals to
|
|
||||||
// find the Measurements, whereas on the server it's
|
|
||||||
// only "greater than", since inside this callback the
|
|
||||||
// Measurements have already been decremented.
|
|
||||||
const measurements = Measurements.find({
|
|
||||||
patientId: data.patientId,
|
|
||||||
lesionNumberAbsolute: {
|
|
||||||
$gte: data.lesionNumberAbsolute
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
measurements.forEach(measurement => {
|
|
||||||
syncMeasurementAndToolData(measurement);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update each displayed viewport
|
|
||||||
updateAllViewports();
|
|
||||||
|
|
||||||
ValidationErrors.remove({
|
|
||||||
measurementId: data._id
|
|
||||||
});
|
|
||||||
|
|
||||||
TrialResponseCriteria.validateAll();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Template.viewer.events({
|
|
||||||
'CornerstoneToolsMeasurementAdded .imageViewerViewport'(event, instance, eventData) {
|
|
||||||
handleMeasurementAdded(event, eventData);
|
|
||||||
},
|
|
||||||
'CornerstoneToolsMeasurementModified .imageViewerViewport'(event, instance, eventData) {
|
|
||||||
handleMeasurementModified(event, eventData);
|
|
||||||
},
|
|
||||||
'CornerstoneToolsMeasurementRemoved .imageViewerViewport'(event, instance, eventData) {
|
|
||||||
handleMeasurementRemoved(event, eventData);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1,210 +0,0 @@
|
|||||||
measurementValuesByType = {
|
|
||||||
bidirectional: [
|
|
||||||
'shortestDiameter',
|
|
||||||
'longestDiameter'
|
|
||||||
],
|
|
||||||
nonTarget: ['response'],
|
|
||||||
crTool: ['response'],
|
|
||||||
exTool: ['response'],
|
|
||||||
unTool: ['response'],
|
|
||||||
length: ['length'],
|
|
||||||
ellipticalRoi: [
|
|
||||||
'area',
|
|
||||||
'mean',
|
|
||||||
'stdev'
|
|
||||||
]
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the Timepoint object for a specific Measurement.
|
|
||||||
* If no measurement exists yet, one will be created.
|
|
||||||
*
|
|
||||||
* Input is toolData from the lesion or nonTarget tool
|
|
||||||
*
|
|
||||||
* @param lesionData
|
|
||||||
*/
|
|
||||||
function updateLesionData(lesionData) {
|
|
||||||
var study = Studies.findOne({
|
|
||||||
studyInstanceUid: lesionData.studyInstanceUid
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!study) {
|
|
||||||
log.warn('Study is not associated with a timepoint');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var timepoint = Timepoints.findOne({
|
|
||||||
timepointId: study.timepointId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!timepoint) {
|
|
||||||
log.warn('Timepoint in an image is not present in the Timepoints Collection?');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the specific lesion to be updated
|
|
||||||
var existingMeasurement;
|
|
||||||
if (lesionData.id && lesionData.id !== 'notready') {
|
|
||||||
existingMeasurement = Measurements.findOne(lesionData.id);
|
|
||||||
} else {
|
|
||||||
existingMeasurement = Measurements.findOne({
|
|
||||||
lesionNumber: lesionData.lesionNumber,
|
|
||||||
isTarget: lesionData.isTarget
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a structure for the timepointData based
|
|
||||||
// on this Lesion's toolData
|
|
||||||
var timepointData = {
|
|
||||||
seriesInstanceUid: lesionData.seriesInstanceUid,
|
|
||||||
studyInstanceUid: lesionData.studyInstanceUid,
|
|
||||||
sopInstanceUid: lesionData.sopInstanceUid,
|
|
||||||
handles: lesionData.handles,
|
|
||||||
imageId: lesionData.imageId
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!lesionData.toolType) {
|
|
||||||
// For debugging, might want to switch to measurement types later
|
|
||||||
lesionData.toolType = lesionData.isTarget ? 'bidirectional' : 'nonTarget';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate this timepoint's data with whichever values
|
|
||||||
// are stored for this Measurement type
|
|
||||||
var values = measurementValuesByType[lesionData.toolType];
|
|
||||||
values.forEach(function(valueName) {
|
|
||||||
timepointData[valueName] = lesionData[valueName];
|
|
||||||
});
|
|
||||||
|
|
||||||
// If no such lesion exists, we need to add one
|
|
||||||
if (!existingMeasurement) {
|
|
||||||
// Create a data structure for the Measurement
|
|
||||||
// based on the current tool data
|
|
||||||
var measurement = {
|
|
||||||
lesionNumber: lesionData.lesionNumber,
|
|
||||||
isTarget: lesionData.isTarget,
|
|
||||||
patientId: lesionData.patientId,
|
|
||||||
id: lesionData.id,
|
|
||||||
toolType: lesionData.toolType
|
|
||||||
};
|
|
||||||
if (lesionData.toolType) {
|
|
||||||
measurement.toolType = lesionData.toolType;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the location name given the locationUID
|
|
||||||
if (lesionData.locationUID !== undefined) {
|
|
||||||
var locationObj = LesionLocations.findOne({
|
|
||||||
locationUID: lesionData.locationUID
|
|
||||||
});
|
|
||||||
|
|
||||||
measurement.location = locationObj.location;
|
|
||||||
measurement.isNodal = locationObj.isNodal;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add toolData parameters to the Measurement at this Timepoint
|
|
||||||
measurement.timepoints = {};
|
|
||||||
measurement.timepoints[timepoint.timepointId] = timepointData;
|
|
||||||
|
|
||||||
// Set a flag to prevent duplication of toolData
|
|
||||||
measurement.clientId = ClientId;
|
|
||||||
|
|
||||||
// Increment and store the absolute Lesion Number for this Measurement
|
|
||||||
measurement.lesionNumberAbsolute = Measurements.find().count() + 1;
|
|
||||||
|
|
||||||
// Insert this into the Measurements Collection
|
|
||||||
// Save the ID into the toolData (not sure if this works?)
|
|
||||||
log.info('LesionManager inserting Measurement');
|
|
||||||
measurement.id = Measurements.insert(measurement);
|
|
||||||
} else {
|
|
||||||
lesionData.id = existingMeasurement._id;
|
|
||||||
lesionData.isNodal = existingMeasurement.isNodal;
|
|
||||||
|
|
||||||
if (_.isEqual(existingMeasurement.timepoints[timepoint.timepointId], timepointData)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update timepoints from lesion data
|
|
||||||
existingMeasurement.timepoints[timepoint.timepointId] = timepointData;
|
|
||||||
|
|
||||||
log.info('LesionManager updating Measurement');
|
|
||||||
Measurements.update(existingMeasurement._id, {
|
|
||||||
$set: {
|
|
||||||
timepoints: existingMeasurement.timepoints,
|
|
||||||
clientId: ClientId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns new lesion number according to timepointId
|
|
||||||
* @param timepointId
|
|
||||||
* @param isTarget
|
|
||||||
* @returns {*}
|
|
||||||
*/
|
|
||||||
function getNewLesionNumber(timepointId, isTarget) {
|
|
||||||
// Get all current lesion measurements
|
|
||||||
var numMeasurements = Measurements.find({
|
|
||||||
isTarget: isTarget
|
|
||||||
}).count();
|
|
||||||
|
|
||||||
// If no measurements exist yet, start at 1
|
|
||||||
if (!numMeasurements) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find related measurements (i.e. target or non-target)
|
|
||||||
var measurements = Measurements.find({
|
|
||||||
isTarget: isTarget
|
|
||||||
}, {
|
|
||||||
sort: {
|
|
||||||
lesionNumber: 1
|
|
||||||
}
|
|
||||||
}).fetch();
|
|
||||||
|
|
||||||
// If measurements exist, find the last lesion number
|
|
||||||
// from the given timepoint
|
|
||||||
var lesionNumberCounter = 1;
|
|
||||||
|
|
||||||
// Search through every Measurement to see which ones
|
|
||||||
// already have data for this Timepoint, if we find one that
|
|
||||||
// doesn't have data, we will stop there and use that as the
|
|
||||||
// current Measurement
|
|
||||||
measurements.every(function(measurement) {
|
|
||||||
// If this measurement has no data for this Timepoint,
|
|
||||||
// use this as the current Measurement
|
|
||||||
if (!measurement.timepoints[timepointId]) {
|
|
||||||
lesionNumberCounter = measurement.lesionNumber;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
lesionNumberCounter++;
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
return lesionNumberCounter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the current Lesion Number already exists
|
|
||||||
* for any other timepoint, returns lesion locationUID
|
|
||||||
* @param lesionData
|
|
||||||
* @returns {*}
|
|
||||||
*/
|
|
||||||
function lesionNumberExists(lesionData) {
|
|
||||||
var measurement = Measurements.findOne({
|
|
||||||
lesionNumber: lesionData.lesionNumber,
|
|
||||||
isTarget: lesionData.isTarget
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!measurement) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return measurement.locationId;
|
|
||||||
}
|
|
||||||
|
|
||||||
LesionManager = {
|
|
||||||
updateLesionData: updateLesionData,
|
|
||||||
getNewLesionNumber: getNewLesionNumber,
|
|
||||||
lesionNumberExists: lesionNumberExists
|
|
||||||
};
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
/**
|
|
||||||
* Calculates total lesion burden given the Trial Criteria Type.
|
|
||||||
* Supports RECIST 1.1 and irRC at present, defaults to RECIST.
|
|
||||||
*
|
|
||||||
* @param criteriaType Either 'RECIST' or 'irRC'
|
|
||||||
* @returns {*}
|
|
||||||
*/
|
|
||||||
calculateTotalLesionBurden = function(criteriaType) {
|
|
||||||
var totalBurden;
|
|
||||||
var measurements = Measurements.find({
|
|
||||||
isTarget: true
|
|
||||||
});
|
|
||||||
|
|
||||||
switch (criteriaType) {
|
|
||||||
default:
|
|
||||||
case 'RECIST':
|
|
||||||
// - Time Point Measurement Total =
|
|
||||||
// Sum of long axis measurements for extranodal target lesion +
|
|
||||||
// short axis measurements for nodal lesions
|
|
||||||
var sumLongAxisExtranodal = 0,
|
|
||||||
sumShortAxisNodal = 0;
|
|
||||||
|
|
||||||
measurements.forEach(function(measurement) {
|
|
||||||
var LD = parseFloat(measurement.longestDiameter);
|
|
||||||
var SD = parseFloat(measurement.shortestDiameter);
|
|
||||||
|
|
||||||
if (measurement.nodal === true) {
|
|
||||||
sumShortAxisNodal += SD;
|
|
||||||
} else {
|
|
||||||
sumLongAxisExtranodal += LD;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
totalBurden = sumLongAxisExtranodal + sumShortAxisNodal;
|
|
||||||
break;
|
|
||||||
case 'irRC':
|
|
||||||
// - Time Point Measurement Total = SPD target lesions + SPD new lesions
|
|
||||||
// (SPD = sum of product of long axis and short axis diameters)
|
|
||||||
var sumProductLesions = 0,
|
|
||||||
sumProductNewLesions = 0;
|
|
||||||
|
|
||||||
measurements.forEach(function(measurement) {
|
|
||||||
var LD = parseFloat(measurement.longestDiameter);
|
|
||||||
var SD = parseFloat(measurement.shortestDiameter);
|
|
||||||
var product = LD * SD;
|
|
||||||
|
|
||||||
if (measurement.newLesion === true) {
|
|
||||||
sumProductNewLesions += product;
|
|
||||||
} else {
|
|
||||||
sumProductLesions += product;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
totalBurden = sumProductLesions + sumProductNewLesions;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return totalBurden;
|
|
||||||
};
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
clearMeasurementTimepointData = function(measurementId, timepointId) {
|
|
||||||
var data = Measurements.findOne(measurementId);
|
|
||||||
|
|
||||||
// Check that this Measurement actually contains data for this timepoint
|
|
||||||
if (!data || !data.timepoints[timepointId]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear the Measurement data for this timepoint
|
|
||||||
var imageId = data.timepoints[timepointId].imageId;
|
|
||||||
var toolType = data.toolType;
|
|
||||||
removeToolDataWithMeasurementId(imageId, toolType, measurementId);
|
|
||||||
|
|
||||||
// Update any viewports that are currently displaying this imageId
|
|
||||||
var enabledElements = cornerstone.getEnabledElementsByImageId(imageId);
|
|
||||||
enabledElements.forEach(function(enabledElement) {
|
|
||||||
cornerstone.updateImage(enabledElement.element);
|
|
||||||
});
|
|
||||||
|
|
||||||
delete data.timepoints[timepointId];
|
|
||||||
|
|
||||||
if (!Object.keys(data.timepoints).length) {
|
|
||||||
Meteor.call('removeMeasurement', measurementId, function(error, response) {
|
|
||||||
if (error) {
|
|
||||||
log.warn(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Update the Timepoint object of the Measurement document
|
|
||||||
Measurements.update(measurementId, {
|
|
||||||
$set: {
|
|
||||||
timepoints: data.timepoints
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
clearTools = function() {
|
|
||||||
var patientId = Session.get('patientId');
|
|
||||||
var toolTypes = [ 'bidirectional', 'nonTarget', 'length', 'ellipticalRoi', 'crTool', 'unTool', 'exTool' ];
|
|
||||||
var toolState = cornerstoneTools.globalImageIdSpecificToolStateManager.toolState;
|
|
||||||
var toolStateKeys = Object.keys(toolState).slice(0);
|
|
||||||
|
|
||||||
var viewportElements = $('.imageViewerViewport').not('.empty');
|
|
||||||
var seriesInstanceUids = []; // Holds seriesInstanceUid of imageViewerViewport elements
|
|
||||||
viewportElements.each(function(index, element) {
|
|
||||||
var enabledElement = cornerstone.getEnabledElement(element);
|
|
||||||
var study = cornerstoneTools.metaData.get('study', enabledElement.image.imageId);
|
|
||||||
if (!study) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Set reviewer for this timepoint
|
|
||||||
if (study.studyInstanceUid) {
|
|
||||||
Meteor.call('setReviewer',study.studyInstanceUid);
|
|
||||||
}
|
|
||||||
var series = cornerstoneTools.metaData.get('series', enabledElement.image.imageId);
|
|
||||||
if (!series) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
seriesInstanceUids.push(series.seriesInstanceUid);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set null array for toolState data found by imageId and toolType
|
|
||||||
toolStateKeys.forEach(function(imageId) {
|
|
||||||
toolTypes.forEach(function(toolType) {
|
|
||||||
var toolTypeData = toolState[imageId][toolType];
|
|
||||||
if (toolTypeData && toolTypeData.data.length > 0) {
|
|
||||||
var series = cornerstoneTools.metaData.get('series', imageId);
|
|
||||||
if (!series) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If seriesInstanceUid is found in seriesInstanceUids, set toolState data as null
|
|
||||||
if (seriesInstanceUids.indexOf(series.seriesInstanceUid) > -1) {
|
|
||||||
toolState[imageId][toolType] = {
|
|
||||||
data: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update imageViewerViewport elements to remove lesions on current image
|
|
||||||
viewportElements.each(function(index, element) {
|
|
||||||
cornerstone.updateImage(element);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove patient's measurements
|
|
||||||
Meteor.call('removeMeasurementsByPatientId', patientId);
|
|
||||||
Meteor.call('removeImageMeasurementsByPatientId', patientId);
|
|
||||||
|
|
||||||
// Clear all validation errors
|
|
||||||
ValidationErrors.remove({});
|
|
||||||
};
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
handleMeasurementAdded = function(e, eventData) {
|
|
||||||
var measurementData = eventData.measurementData;
|
|
||||||
switch (measurementData.toolType) {
|
|
||||||
case 'nonTarget':
|
|
||||||
case 'bidirectional':
|
|
||||||
case 'crTool':
|
|
||||||
case 'unTool':
|
|
||||||
case 'exTool':
|
|
||||||
log.info('CornerstoneToolsMeasurementAdded');
|
|
||||||
LesionManager.updateLesionData(measurementData);
|
|
||||||
TrialResponseCriteria.validateDelayed(measurementData);
|
|
||||||
// Set reviewer for this timepoint
|
|
||||||
if (measurementData.studyInstanceUid) {
|
|
||||||
Meteor.call('setReviewer', measurementData.studyInstanceUid);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'ellipticalRoi':
|
|
||||||
case 'length':
|
|
||||||
var enabledElement = cornerstone.getEnabledElement(eventData.element);
|
|
||||||
var imageId = enabledElement.image.imageId;
|
|
||||||
|
|
||||||
// Get the studyInstanceUid and series metaData
|
|
||||||
var study = cornerstoneTools.metaData.get('study', imageId);
|
|
||||||
var series = cornerstoneTools.metaData.get('series', imageId);
|
|
||||||
|
|
||||||
// Add the relevant metaData to this ImageMeasurement's toolData
|
|
||||||
measurementData.clientId = ClientId;
|
|
||||||
measurementData.imageId = imageId;
|
|
||||||
measurementData.toolType = eventData.toolType;
|
|
||||||
measurementData.patientId = study.patientId;
|
|
||||||
measurementData.studyInstanceUid = study.studyInstanceUid;
|
|
||||||
measurementData.seriesInstanceUid = series.seriesInstanceUid;
|
|
||||||
|
|
||||||
// Create the ImageMeasurement in the database
|
|
||||||
measurementData._id = ImageMeasurements.insert(measurementData);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
handleMeasurementModified = function(e, eventData) {
|
|
||||||
var measurementData = eventData.measurementData;
|
|
||||||
switch (measurementData.toolType) {
|
|
||||||
case 'nonTarget':
|
|
||||||
case 'bidirectional':
|
|
||||||
case 'crTool':
|
|
||||||
case 'unTool':
|
|
||||||
case 'exTool':
|
|
||||||
log.info('CornerstoneToolsMeasurementModified');
|
|
||||||
LesionManager.updateLesionData(measurementData);
|
|
||||||
TrialResponseCriteria.validateDelayed(measurementData);
|
|
||||||
// Set reviewer for this timepoint
|
|
||||||
if (measurementData.studyInstanceUid) {
|
|
||||||
Meteor.call('setReviewer',measurementData.studyInstanceUid);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'ellipticalRoi':
|
|
||||||
case 'length':
|
|
||||||
var enabledElement = cornerstone.getEnabledElement(eventData.element);
|
|
||||||
var imageId = enabledElement.image.imageId;
|
|
||||||
|
|
||||||
// Get the studyInstanceUid and series metaData
|
|
||||||
var study = cornerstoneTools.metaData.get('study', imageId);
|
|
||||||
var series = cornerstoneTools.metaData.get('series', imageId);
|
|
||||||
|
|
||||||
// Add the relevant metaData to this ImageMeasurement's toolData
|
|
||||||
measurementData.clientId = ClientId;
|
|
||||||
measurementData.imageId = imageId;
|
|
||||||
measurementData.toolType = eventData.toolType;
|
|
||||||
measurementData.patientId = study.patientId;
|
|
||||||
measurementData.studyInstanceUid = study.studyInstanceUid;
|
|
||||||
measurementData.seriesInstanceUid = series.seriesInstanceUid;
|
|
||||||
var toUpdate = $.extend({}, measurementData);
|
|
||||||
|
|
||||||
// Remove the Mongo _id otherwise it will complain when we try to 'mod' it.
|
|
||||||
delete toUpdate._id;
|
|
||||||
|
|
||||||
// Update the ImageMeasurement in the database
|
|
||||||
ImageMeasurements.update(measurementData._id, {
|
|
||||||
$set: toUpdate
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
handleMeasurementRemoved = function(e, eventData) {
|
|
||||||
var measurementData = eventData.measurementData;
|
|
||||||
|
|
||||||
switch (eventData.toolType) {
|
|
||||||
case 'nonTarget':
|
|
||||||
case 'bidirectional':
|
|
||||||
case 'crTool':
|
|
||||||
case 'unTool':
|
|
||||||
case 'exTool':
|
|
||||||
log.info('CornerstoneToolsMeasurementRemoved');
|
|
||||||
|
|
||||||
var measurement = Measurements.findOne(measurementData.id, {
|
|
||||||
reactive: false
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!measurement) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Set reviewer for this timepoint
|
|
||||||
if (measurementData.studyInstanceUid) {
|
|
||||||
Meteor.call('setReviewer', measurementData.studyInstanceUid);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearMeasurementTimepointData(measurement._id, measurementData.timepointId);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
syncImageMeasurementAndToolData = function(measurement) {
|
|
||||||
// TODO: Refactor this to merge it with syncMeasurementAndToolData somehow
|
|
||||||
log.info('syncImageMeasurementAndToolData');
|
|
||||||
|
|
||||||
var toolState = cornerstoneTools.globalImageIdSpecificToolStateManager.toolState;
|
|
||||||
|
|
||||||
var imageId = measurement.imageId;
|
|
||||||
var toolType = measurement.toolType;
|
|
||||||
|
|
||||||
// If no tool state exists for this imageId, create an empty object to store it
|
|
||||||
if (!toolState[imageId]) {
|
|
||||||
toolState[imageId] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we already have toolData for this imageId and toolType
|
|
||||||
if (toolState[imageId][toolType] &&
|
|
||||||
toolState[imageId][toolType].data &&
|
|
||||||
toolState[imageId][toolType].data.length) {
|
|
||||||
// If we have toolData, we should search it for any toolData
|
|
||||||
// related to the current ImageMeasurement
|
|
||||||
var toolData = toolState[imageId][toolType].data;
|
|
||||||
|
|
||||||
// Create a flag so we know if we have successfully updated
|
|
||||||
// this ImageMeasurement's timepoint data in the toolData
|
|
||||||
var alreadyExists = false;
|
|
||||||
|
|
||||||
// Loop through the toolData to search for this ImageMeasurement
|
|
||||||
toolData.forEach(function(tool) {
|
|
||||||
// Break the loop if this isn't the ImageMeasurement we are looking for
|
|
||||||
if (tool._id !== measurement._id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we find the ImageMeasurement, set the flag to True
|
|
||||||
alreadyExists = true;
|
|
||||||
|
|
||||||
// Update the toolData from the ImageMeasurement data
|
|
||||||
$.extend(tool, measurement);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// If we found the ImageMeasurement we intended to update, we can stop
|
|
||||||
// this function here
|
|
||||||
if (alreadyExists === true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If no toolData exists for this toolType, create an empty array to hold some
|
|
||||||
toolState[imageId][toolType] = {
|
|
||||||
data: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have reached this point, it means we haven't found the ImageMeasurement we are
|
|
||||||
// looking for in the current toolData. This means we need to add it.
|
|
||||||
|
|
||||||
// Add the ImageMeasurementData into the toolData for this imageId
|
|
||||||
toolState[imageId][toolType].data.push(measurement);
|
|
||||||
};
|
|
||||||
@ -1,90 +0,0 @@
|
|||||||
syncMeasurementAndToolData = function(measurement) {
|
|
||||||
log.info('syncMeasurementAndToolData');
|
|
||||||
|
|
||||||
// Check what toolType we should be adding this to, based on the isTarget value
|
|
||||||
// of the stored Measurement
|
|
||||||
var toolType = measurement.toolType;
|
|
||||||
|
|
||||||
// Loop through the timepoint data for this measurement
|
|
||||||
Object.keys(measurement.timepoints).forEach(function(key) {
|
|
||||||
var timepointData = measurement.timepoints[key];
|
|
||||||
var imageId = timepointData.imageId;
|
|
||||||
|
|
||||||
// Sync the Cornerstone ToolData with this Measurement's timepoint-specific data
|
|
||||||
syncTimepointDataWithToolData(measurement, timepointData, imageId, toolType);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function syncTimepointDataWithToolData(measurement, timepointData, imageId, toolType) {
|
|
||||||
// Get the global imageId-specific toolState from Cornerstone Tools
|
|
||||||
var toolState = cornerstoneTools.globalImageIdSpecificToolStateManager.toolState;
|
|
||||||
|
|
||||||
// If no tool state exists for this imageId, create an empty object to store it
|
|
||||||
if (!toolState[imageId]) {
|
|
||||||
toolState[imageId] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we already have toolData for this imageId and toolType
|
|
||||||
if (toolState[imageId][toolType] &&
|
|
||||||
toolState[imageId][toolType].data &&
|
|
||||||
toolState[imageId][toolType].data.length) {
|
|
||||||
|
|
||||||
// If we have toolData, we should search it for any toolData
|
|
||||||
// related to the current Measurement
|
|
||||||
var toolData = toolState[imageId][toolType].data;
|
|
||||||
|
|
||||||
// Create a flag so we know if we have successfully updated
|
|
||||||
// this Measurement's timepoint data in the toolData
|
|
||||||
var alreadyExists = false;
|
|
||||||
|
|
||||||
// Loop through the toolData to search for this Measurement's
|
|
||||||
// timepoint data
|
|
||||||
toolData.forEach(function(tool) {
|
|
||||||
// Break the loop if this isn't the Measurement we are looking for
|
|
||||||
if (tool.id !== measurement._id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we find the Measurement, set the flag to True
|
|
||||||
alreadyExists = true;
|
|
||||||
|
|
||||||
// Update the toolData from the Measurement data and
|
|
||||||
// timepoint-specific data from this Measurement
|
|
||||||
tool.lesionNumber = measurement.lesionNumber;
|
|
||||||
tool.isTarget = measurement.isTarget;
|
|
||||||
tool.active = timepointData.active;
|
|
||||||
tool.visible = timepointData.visible;
|
|
||||||
tool.isDeleted = timepointData.isDeleted;
|
|
||||||
tool.handles = timepointData.handles;
|
|
||||||
tool.toolType = measurement.toolType;
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// If we found the Measurement we intended to update, we can stop
|
|
||||||
// this function here
|
|
||||||
if (alreadyExists === true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If no toolData exists for this toolType, create an empty array to hold some
|
|
||||||
toolState[imageId][toolType] = {
|
|
||||||
data: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have reached this point, it means we haven't found the Measurement we are
|
|
||||||
// looking for in the current toolData. This means we need to add it.
|
|
||||||
|
|
||||||
// First, create the measurementData structure based on the lesion data at this timepoint.
|
|
||||||
var tool = timepointData;
|
|
||||||
tool.lesionNumber = measurement.lesionNumber;
|
|
||||||
tool.isTarget = measurement.isTarget;
|
|
||||||
tool.location = measurement.location;
|
|
||||||
tool.locationUID = measurement.locationUID;
|
|
||||||
tool.patientId = measurement.patientId;
|
|
||||||
tool.id = measurement._id;
|
|
||||||
tool.toolType = measurement.toolType;
|
|
||||||
|
|
||||||
// Add the measurementData into the toolData for this imageId
|
|
||||||
toolState[imageId][toolType].data.push(tool);
|
|
||||||
}
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
// If timepoint is baseline, sets lesion tool as active tool
|
|
||||||
setTimepointTools = function(timepoint) {
|
|
||||||
if (!timepoint) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var disabledBaselineTools = ["crunexTools"];
|
|
||||||
var states = toolManager.getToolDefaultStates();
|
|
||||||
var disabledToolButtons = states.disabledToolButtons;
|
|
||||||
|
|
||||||
if (timepoint.timepointType === "baseline") {
|
|
||||||
// Set active tool as lesion tool
|
|
||||||
toolManager.setActiveTool('bidirectional');
|
|
||||||
disabledBaselineTools.forEach(function(tool) {
|
|
||||||
var index = disabledToolButtons.indexOf(tool);
|
|
||||||
if (index === -1) {
|
|
||||||
disabledToolButtons.push(tool);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
toolManager.setActiveTool(toolManager.getDefaultTool());
|
|
||||||
// Remove disabled baseline tools
|
|
||||||
disabledBaselineTools.forEach(function(tool) {
|
|
||||||
var index = disabledToolButtons.indexOf(tool);
|
|
||||||
if (index !== -1) {
|
|
||||||
disabledToolButtons.splice(index, 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
states.disabledToolButtons = disabledToolButtons;
|
|
||||||
toolManager.setToolDefaultStates(states);
|
|
||||||
};
|
|
||||||
|
|
||||||
timepointAutoCheck = function(templateData) {
|
|
||||||
if (templateData && templateData.timepointIds) {
|
|
||||||
templateData.timepointIds.forEach(function(timepointId) {
|
|
||||||
var timepoint = Timepoints.findOne({timepointId: timepointId});
|
|
||||||
setTimepointTools(timepoint);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
updateRelatedElements = function(imageId) {
|
|
||||||
// Get all on-screen elements with this imageId
|
|
||||||
var enabledElements = cornerstone.getEnabledElementsByImageId(imageId);
|
|
||||||
|
|
||||||
// TODO=Check original event to prevent duplicate updateImage calls
|
|
||||||
|
|
||||||
// Loop through these elements
|
|
||||||
enabledElements.forEach(function(enabledElement) {
|
|
||||||
// Update the display so the tool is removed
|
|
||||||
var element = enabledElement.element;
|
|
||||||
cornerstone.updateImage(element);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
Meteor.startup(function() {
|
|
||||||
Worklist.subscriptions = ['studies', 'timepoints'];
|
|
||||||
Worklist.callbacks['dblClickOnStudy'] = dblClickOnStudy;
|
|
||||||
Worklist.callbacks['middleClickOnStudy'] = dblClickOnStudy;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lesion Tracker method including Timepoints / other studies
|
|
||||||
*/
|
|
||||||
function dblClickOnStudy(data) {
|
|
||||||
// Use the formatPN template helper to clean up the patient name
|
|
||||||
var title = formatPN(data.patientName);
|
|
||||||
var study = Studies.findOne({
|
|
||||||
studyInstanceUid: data.studyInstanceUid
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if the study has been associated, and if not, just open it on its own
|
|
||||||
if (!study) {
|
|
||||||
// Open a new tab with this study
|
|
||||||
open(data.studyInstanceUid, title);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the relevant timepoint given the clicked-on study
|
|
||||||
var timepoint = Timepoints.findOne({
|
|
||||||
studyInstanceUids: {
|
|
||||||
$in: [data.studyInstanceUid]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!timepoint) {
|
|
||||||
open(data.studyInstanceUid, title);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the Timepoint name to the Patient name to create the tab title
|
|
||||||
title += ' ' + getTimepointName(timepoint);
|
|
||||||
|
|
||||||
openNewTabWithTimepoint(timepoint.timepointId, title);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens a study
|
|
||||||
*
|
|
||||||
* @param studyInstanceUid The UID of the Study to be opened
|
|
||||||
* @param title The title to be used for the tab heading
|
|
||||||
*/
|
|
||||||
function open(studyInstanceUid, title) {
|
|
||||||
var contentid = 'viewerTab';
|
|
||||||
|
|
||||||
WorklistTabs.remove({});
|
|
||||||
|
|
||||||
// Create a new entry in the WorklistTabs Collection
|
|
||||||
WorklistTabs.insert({
|
|
||||||
title: title,
|
|
||||||
contentid: contentid,
|
|
||||||
studyInstanceUid: studyInstanceUid,
|
|
||||||
active: false
|
|
||||||
});
|
|
||||||
|
|
||||||
ViewerData = window.ViewerData || ViewerData;
|
|
||||||
|
|
||||||
// Update the ViewerData global object
|
|
||||||
ViewerData[contentid] = {
|
|
||||||
title: title,
|
|
||||||
contentid: contentid,
|
|
||||||
studyInstanceUids: [studyInstanceUid]
|
|
||||||
};
|
|
||||||
|
|
||||||
// Switch to the new tab
|
|
||||||
console.log('open');
|
|
||||||
switchToTab(contentid);
|
|
||||||
}
|
|
||||||
@ -1,293 +0,0 @@
|
|||||||
Package.describe({
|
|
||||||
name: 'lesiontracker',
|
|
||||||
summary: 'OHIF Lesion Tracker Tools',
|
|
||||||
version: '0.0.1'
|
|
||||||
});
|
|
||||||
|
|
||||||
Package.onUse(function(api) {
|
|
||||||
api.versionsFrom('1.4');
|
|
||||||
|
|
||||||
api.use('ecmascript');
|
|
||||||
api.use('standard-app-packages');
|
|
||||||
api.use('jquery');
|
|
||||||
api.use('stylus');
|
|
||||||
api.use('design');
|
|
||||||
|
|
||||||
api.use('validatejs');
|
|
||||||
|
|
||||||
// Schema for Data Models
|
|
||||||
api.use('aldeed:simple-schema');
|
|
||||||
|
|
||||||
// Control over logging
|
|
||||||
api.use('practicalmeteor:loglevel');
|
|
||||||
|
|
||||||
// Unique IDs
|
|
||||||
api.use('rwatts:uuid');
|
|
||||||
|
|
||||||
// Template overriding
|
|
||||||
api.use('aldeed:template-extension@4.0.0');
|
|
||||||
|
|
||||||
// Our custom packages
|
|
||||||
api.use('ohif:core');
|
|
||||||
api.use('worklist');
|
|
||||||
api.use('cornerstone');
|
|
||||||
|
|
||||||
api.addFiles('log.js', [ 'client', 'server' ]);
|
|
||||||
|
|
||||||
// Schema
|
|
||||||
api.addFiles('both/schema/additionalFindings.js', [ 'client', 'server' ]);
|
|
||||||
|
|
||||||
// Client-side collections
|
|
||||||
api.addFiles('client/collections/LesionLocations.js', 'client');
|
|
||||||
api.addFiles('client/collections/LocationResponses.js', 'client');
|
|
||||||
api.addFiles('client/collections/subscriptions.js', 'client');
|
|
||||||
|
|
||||||
// Additional Custom Cornerstone Tools for Lesion Tracker
|
|
||||||
api.addFiles([
|
|
||||||
'client/compatibility/bidirectionalTool.js',
|
|
||||||
'client/compatibility/nonTargetTool.js',
|
|
||||||
'client/compatibility/scaleOverlayTool.js',
|
|
||||||
'client/compatibility/deleteLesionKeyboardTool.js',
|
|
||||||
'client/compatibility/crunexTool.js',
|
|
||||||
'client/compatibility/crTool.js',
|
|
||||||
'client/compatibility/unTool.js',
|
|
||||||
'client/compatibility/exTool.js'
|
|
||||||
], 'client', {
|
|
||||||
bare: true
|
|
||||||
});
|
|
||||||
|
|
||||||
api.addFiles('client/tools.js', 'client');
|
|
||||||
|
|
||||||
// UI Components
|
|
||||||
api.addFiles('client/components/viewer/viewer.html', 'client');
|
|
||||||
api.addFiles('client/components/viewer/viewer.styl', 'client');
|
|
||||||
api.addFiles('client/components/viewer/viewer.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/flexboxLayout/flexboxLayout.html', 'client');
|
|
||||||
api.addFiles('client/components/flexboxLayout/flexboxLayout.styl', 'client');
|
|
||||||
api.addFiles('client/components/flexboxLayout/flexboxLayout.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/toolbarSection/toolbarSection.html', 'client');
|
|
||||||
api.addFiles('client/components/toolbarSection/toolbarSection.styl', 'client');
|
|
||||||
api.addFiles('client/components/toolbarSection/toolbarSection.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/radialProgressBar/radialProgressBar.html', 'client');
|
|
||||||
api.addFiles('client/components/radialProgressBar/radialProgressBar.styl', 'client');
|
|
||||||
api.addFiles('client/components/radialProgressBar/radialProgressBar.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/caseProgress/caseProgress.html', 'client');
|
|
||||||
api.addFiles('client/components/caseProgress/caseProgress.styl', 'client');
|
|
||||||
api.addFiles('client/components/caseProgress/caseProgress.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/lesionTracker/lesionTracker.html', 'client');
|
|
||||||
api.addFiles('client/components/lesionTracker/lesionTracker.styl', 'client');
|
|
||||||
api.addFiles('client/components/lesionTracker/lesionTracker.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/additionalFindings/additionalFindings.html', 'client');
|
|
||||||
api.addFiles('client/components/additionalFindings/additionalFindings.styl', 'client');
|
|
||||||
api.addFiles('client/components/additionalFindings/additionalFindings.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/studySeriesQuickSwitch/studySeriesQuickSwitch.html', 'client');
|
|
||||||
api.addFiles('client/components/studySeriesQuickSwitch/studySeriesQuickSwitch.styl', 'client');
|
|
||||||
api.addFiles('client/components/studySeriesQuickSwitch/studySeriesQuickSwitch.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/associationModal/associationModal.html', 'client');
|
|
||||||
api.addFiles('client/components/associationModal/associationModal.styl', 'client');
|
|
||||||
api.addFiles('client/components/associationModal/associationModal.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/activeEntry/activeEntry.styl', 'client');
|
|
||||||
api.addFiles('client/components/activeEntry/activeEntry.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/activeEntry/activeEntrySignIn/activeEntrySignIn.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/hipaaLogPage/hipaaLogPage.styl', 'client');
|
|
||||||
api.addFiles('client/components/hipaaLogPage/hipaaLogPage.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/optionsModal/optionsModal.html', 'client');
|
|
||||||
api.addFiles('client/components/optionsModal/optionsModal.styl', 'client');
|
|
||||||
api.addFiles('client/components/optionsModal/optionsModal.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/recistDescription/recistDescription.html', 'client');
|
|
||||||
api.addFiles('client/components/irRCDescription/irRCDescription.html', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/lesionLocationDialog/lesionLocationDialog.html', 'client');
|
|
||||||
api.addFiles('client/components/lesionLocationDialog/lesionLocationDialog.js', 'client');
|
|
||||||
api.addFiles('client/components/lesionLocationDialog/lesionLocationDialog.styl', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/lesionTableView/lesionTableView.html', 'client');
|
|
||||||
api.addFiles('client/components/lesionTableView/lesionTableView.styl', 'client');
|
|
||||||
api.addFiles('client/components/lesionTableView/lesionTableView.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/lesionTable/lesionTable.html', 'client');
|
|
||||||
api.addFiles('client/components/lesionTable/lesionTable.styl', 'client');
|
|
||||||
api.addFiles('client/components/lesionTable/lesionTable.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/lesionTableHUD/lesionTableHUD.html', 'client');
|
|
||||||
api.addFiles('client/components/lesionTableHUD/lesionTableHUD.styl', 'client');
|
|
||||||
api.addFiles('client/components/lesionTableHUD/lesionTableHUD.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/lesionTableRow/lesionTableRow.html', 'client');
|
|
||||||
api.addFiles('client/components/lesionTableRow/lesionTableRow.styl', 'client');
|
|
||||||
api.addFiles('client/components/lesionTableRow/lesionTableRow.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/lesionTableHeaderRow/lesionTableHeaderRow.html', 'client');
|
|
||||||
api.addFiles('client/components/lesionTableHeaderRow/lesionTableHeaderRow.styl', 'client');
|
|
||||||
api.addFiles('client/components/lesionTableHeaderRow/lesionTableHeaderRow.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/lesionTableTimepointCell/lesionTableTimepointCell.html', 'client');
|
|
||||||
api.addFiles('client/components/lesionTableTimepointCell/lesionTableTimepointCell.styl', 'client');
|
|
||||||
api.addFiles('client/components/lesionTableTimepointCell/lesionTableTimepointCell.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/lesionTableTimepointHeader/lesionTableTimepointHeader.html', 'client');
|
|
||||||
api.addFiles('client/components/lesionTableTimepointHeader/lesionTableTimepointHeader.styl', 'client');
|
|
||||||
api.addFiles('client/components/lesionTableTimepointHeader/lesionTableTimepointHeader.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/nonTargetLesionDialog/nonTargetLesionDialog.html', 'client');
|
|
||||||
api.addFiles('client/components/nonTargetLesionDialog/nonTargetLesionDialog.styl', 'client');
|
|
||||||
api.addFiles('client/components/nonTargetLesionDialog/nonTargetLesionDialog.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/studyTimepointBrowser/studyTimepoint.html', 'client');
|
|
||||||
api.addFiles('client/components/studyTimepointBrowser/studyTimepoint.styl', 'client');
|
|
||||||
api.addFiles('client/components/studyTimepointBrowser/studyTimepoint.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/studyTimepointBrowser/studyTimepointBrowser.html', 'client');
|
|
||||||
api.addFiles('client/components/studyTimepointBrowser/studyTimepointBrowser.styl', 'client');
|
|
||||||
api.addFiles('client/components/studyTimepointBrowser/studyTimepointBrowser.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/studyTimepointBrowser/studyTimepointStudy.html', 'client');
|
|
||||||
api.addFiles('client/components/studyTimepointBrowser/studyTimepointStudy.styl', 'client');
|
|
||||||
api.addFiles('client/components/studyTimepointBrowser/studyTimepointStudy.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/studyAssociationTable/studyAssociationTable.html', 'client');
|
|
||||||
api.addFiles('client/components/studyAssociationTable/studyAssociationTable.styl', 'client');
|
|
||||||
api.addFiles('client/components/studyAssociationTable/studyAssociationTable.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/conformanceCheckFeedback/conformanceCheckFeedback.html', 'client');
|
|
||||||
api.addFiles('client/components/conformanceCheckFeedback/conformanceCheckFeedback.styl', 'client');
|
|
||||||
api.addFiles('client/components/conformanceCheckFeedback/conformanceCheckFeedback.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/nonTargetResponseDialog/nonTargetResponseDialog.html', 'client');
|
|
||||||
api.addFiles('client/components/nonTargetResponseDialog/nonTargetResponseDialog.styl', 'client');
|
|
||||||
api.addFiles('client/components/nonTargetResponseDialog/nonTargetResponseDialog.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.html', 'client');
|
|
||||||
api.addFiles('client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.styl', 'client');
|
|
||||||
api.addFiles('client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/measureFlow/measureFlow.html', 'client');
|
|
||||||
api.addFiles('client/components/measureFlow/measureFlow.styl', 'client');
|
|
||||||
api.addFiles('client/components/measureFlow/measureFlow.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/timeoutCountdownDialog/timeoutCountdownDialog.html', 'client');
|
|
||||||
api.addFiles('client/components/timeoutCountdownDialog/timeoutCountdownDialog.styl', 'client');
|
|
||||||
api.addFiles('client/components/timeoutCountdownDialog/timeoutCountdownDialog.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/lesionTrackerWorklistContextMenu/lesionTrackerWorklistContextMenu.html', 'client');
|
|
||||||
api.addFiles('client/components/lesionTrackerWorklistContextMenu/lesionTrackerWorklistContextMenu.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/lesionTrackerViewportOverlay/lesionTrackerViewportOverlay.html', 'client');
|
|
||||||
api.addFiles('client/components/lesionTrackerViewportOverlay/lesionTrackerViewportOverlay.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/userAccountMenu/userAccountMenu.html', 'client');
|
|
||||||
api.addFiles('client/components/userAccountMenu/userAccountMenu.styl', 'client');
|
|
||||||
api.addFiles('client/components/userAccountMenu/userAccountMenu.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/confirmRemoveTimepointAssociation/confirmRemoveTimepointAssociation.html', 'client');
|
|
||||||
api.addFiles('client/components/confirmRemoveTimepointAssociation/confirmRemoveTimepointAssociation.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/lastLoginModal/lastLoginModal.html', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/emailVerification/emailVerification.html', 'client');
|
|
||||||
api.addFiles('client/components/emailVerification/emailVerification.styl', 'client');
|
|
||||||
api.addFiles('client/components/emailVerification/emailVerification.js', 'client');
|
|
||||||
|
|
||||||
// Server functions
|
|
||||||
api.addFiles('server/publications.js', 'server');
|
|
||||||
api.addFiles('server/methods.js', [ 'server' ]);
|
|
||||||
api.addFiles('server/reviewers.js', [ 'server' ]);
|
|
||||||
api.addFiles('server/createDemoUser.js', [ 'server' ]);
|
|
||||||
|
|
||||||
// Both client and server functions
|
|
||||||
api.addFiles('both/collections.js', [ 'client', 'server' ]);
|
|
||||||
|
|
||||||
// Worklist-related functions
|
|
||||||
api.addFiles('lib/worklist/openNewTabWithTimepoint.js', 'client');
|
|
||||||
api.addFiles('lib/worklist/worklistModification.js', 'client');
|
|
||||||
|
|
||||||
// Library functions
|
|
||||||
api.addFiles('lib/TrialCriteriaConstraints.js', 'client');
|
|
||||||
api.addFiles('lib/TrialResponseCriteria.js', 'client');
|
|
||||||
api.addFiles('lib/LesionManager.js', 'client');
|
|
||||||
api.addFiles('lib/pixelSpacingAutorunCheck.js', 'client');
|
|
||||||
api.addFiles('lib/toggleLesionTrackerTools.js', 'client');
|
|
||||||
api.addFiles('lib/clearMeasurementTimepointData.js', 'client');
|
|
||||||
api.addFiles('lib/removeToolDataWithMeasurementId.js', 'client');
|
|
||||||
api.addFiles('lib/getTimepointObject.js', 'client');
|
|
||||||
api.addFiles('lib/getTimepointName.js', 'client');
|
|
||||||
api.addFiles('lib/activateMeasurements.js', 'client');
|
|
||||||
api.addFiles('lib/activateLesion.js', 'client');
|
|
||||||
api.addFiles('lib/deactivateAllToolData.js', 'client');
|
|
||||||
api.addFiles('lib/clearTools.js', 'client');
|
|
||||||
api.addFiles('lib/calculateTotalLesionBurden.js', 'client');
|
|
||||||
api.addFiles('lib/convertToNonTarget.js', 'client');
|
|
||||||
api.addFiles('lib/convertNonTarget.js', 'client');
|
|
||||||
api.addFiles('lib/timepointAutoCheck.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('lib/syncMeasurementAndToolData.js', 'client');
|
|
||||||
api.addFiles('lib/syncImageMeasurementAndToolData.js', 'client');
|
|
||||||
api.addFiles('lib/updateRelatedElements.js', 'client');
|
|
||||||
|
|
||||||
api.addFiles('lib/handleMeasurementAdded.js', 'client');
|
|
||||||
api.addFiles('lib/handleMeasurementModified.js', 'client');
|
|
||||||
api.addFiles('lib/handleMeasurementRemoved.js', 'client');
|
|
||||||
|
|
||||||
// API classes
|
|
||||||
api.addFiles('client/api/timepoint.js');
|
|
||||||
api.addFiles('client/api/measurement.js');
|
|
||||||
|
|
||||||
// Export global functions
|
|
||||||
api.export('pixelSpacingAutorunCheck', 'client');
|
|
||||||
api.export('handleMeasurementAdded', 'client');
|
|
||||||
api.export('handleMeasurementModified', 'client');
|
|
||||||
api.export('handleMeasurementRemoved', 'client');
|
|
||||||
api.export('syncMeasurementAndToolData', 'client');
|
|
||||||
api.export('syncImageMeasurementAndToolData', 'client');
|
|
||||||
api.export('updateRelatedElements', 'client');
|
|
||||||
api.export('openNewTabWithTimepoint', 'client');
|
|
||||||
api.export('activateLesion', 'client');
|
|
||||||
api.export('activateMeasurements', 'client');
|
|
||||||
api.export('deactivateAllToolData', 'client');
|
|
||||||
api.export('toggleLesionTrackerTools', 'client');
|
|
||||||
api.export('clearMeasurementTimepointData', 'client');
|
|
||||||
api.export('removeToolDataWithMeasurementId', 'client');
|
|
||||||
api.export('getTimepointObject', 'client');
|
|
||||||
api.export('clearTools', 'client');
|
|
||||||
api.export('getTimepointName', 'client');
|
|
||||||
api.export('getTrialCriteriaConstraints', 'client');
|
|
||||||
api.export('calculateTotalLesionBurden', 'client');
|
|
||||||
api.export('convertToNonTarget', 'client');
|
|
||||||
api.export('convertNonTarget', 'client');
|
|
||||||
api.export('timepointAutoCheck', 'client');
|
|
||||||
|
|
||||||
// Export global objects
|
|
||||||
api.export('TrialResponseCriteria', 'client');
|
|
||||||
api.export('TrialCriteriaConstraints', 'client');
|
|
||||||
api.export('LesionManager', 'client');
|
|
||||||
|
|
||||||
// Export client-side collections
|
|
||||||
api.export('ValidationErrors', 'client');
|
|
||||||
api.export('LesionLocations', 'client');
|
|
||||||
api.export('LocationResponses', 'client');
|
|
||||||
api.export('TrialCriteriaTypes', 'client');
|
|
||||||
|
|
||||||
// Export collections spanning both client and server
|
|
||||||
api.export('AdditionalFindings', [ 'client', 'server' ]);
|
|
||||||
api.export('ImageMeasurements', [ 'client', 'server' ]);
|
|
||||||
api.export('Measurements', [ 'client', 'server' ]);
|
|
||||||
api.export('Studies', [ 'client', 'server' ]);
|
|
||||||
api.export('Timepoints', [ 'client', 'server' ]);
|
|
||||||
api.export('Reviewers', [ 'client', 'server' ]);
|
|
||||||
|
|
||||||
// Export utility classes
|
|
||||||
api.export('FormUtils', 'client');
|
|
||||||
});
|
|
||||||
@ -1,61 +0,0 @@
|
|||||||
Meteor.methods({
|
|
||||||
removeMeasurement: function(id) {
|
|
||||||
var lesionData = Measurements.findOne(id);
|
|
||||||
|
|
||||||
// Update all Measurements to decrement the lesion numbers for those
|
|
||||||
// that were created after the current lesion by 1
|
|
||||||
|
|
||||||
// TODO: Update this when we have more criteria than patientId
|
|
||||||
// Decrement the absolute lesion number of all lesions with
|
|
||||||
// absolute lesion numbers greater than the current lesion by 1
|
|
||||||
Measurements.update({
|
|
||||||
patientId: lesionData.patientId,
|
|
||||||
lesionNumberAbsolute: {
|
|
||||||
$gt: lesionData.lesionNumberAbsolute
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
$inc: {
|
|
||||||
lesionNumberAbsolute: -1
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
multi: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Decrement the lesion number of all related (i.e. target or non-target)
|
|
||||||
// lesions with lesion numbers greater than the current lesion by 1
|
|
||||||
Measurements.update({
|
|
||||||
patientId: lesionData.patientId,
|
|
||||||
lesionNumber: {
|
|
||||||
$gt: lesionData.lesionNumber
|
|
||||||
},
|
|
||||||
isTarget: lesionData.isTarget
|
|
||||||
}, {
|
|
||||||
$inc: {
|
|
||||||
lesionNumber: -1
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
multi: true
|
|
||||||
});
|
|
||||||
|
|
||||||
Measurements.remove(id);
|
|
||||||
},
|
|
||||||
removeMeasurementsByPatientId: function(patientId) {
|
|
||||||
Measurements.remove({
|
|
||||||
patientId: patientId
|
|
||||||
});
|
|
||||||
},
|
|
||||||
removeImageMeasurementsByPatientId: function(patientId) {
|
|
||||||
ImageMeasurements.remove({
|
|
||||||
patientId: patientId
|
|
||||||
});
|
|
||||||
},
|
|
||||||
clearAllTimepoints: function() {
|
|
||||||
Timepoints.remove({});
|
|
||||||
},
|
|
||||||
removeTimepoint: function(id) {
|
|
||||||
Timepoints.remove(id);
|
|
||||||
},
|
|
||||||
removeAssociatedStudy: function(id) {
|
|
||||||
Studies.remove(id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
/*! cornerstoneTools - v0.7.8 - 2016-08-05 | (c) 2014 Chris Hafey | https://github.com/chafey/cornerstoneTools */
|
/*! cornerstoneTools - v0.7.9 - 2016-09-18 | (c) 2014 Chris Hafey | https://github.com/chafey/cornerstoneTools */
|
||||||
// Begin Source: src/header.js
|
// Begin Source: src/header.js
|
||||||
if (typeof cornerstone === 'undefined') {
|
if (typeof cornerstone === 'undefined') {
|
||||||
cornerstone = {};
|
cornerstone = {};
|
||||||
@ -22,39 +22,64 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var scrollTimeout;
|
||||||
|
var scrollTimeoutDelay = 1;
|
||||||
|
|
||||||
function mouseWheel(e) {
|
function mouseWheel(e) {
|
||||||
// !!!HACK/NOTE/WARNING!!!
|
clearTimeout(scrollTimeout);
|
||||||
// for some reason I am getting mousewheel and DOMMouseScroll events on my
|
|
||||||
// mac os x mavericks system when middle mouse button dragging.
|
|
||||||
// I couldn't find any info about this so this might break other systems
|
|
||||||
// webkit hack
|
|
||||||
if (e.originalEvent.type === 'mousewheel' && e.originalEvent.wheelDeltaY === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// firefox hack
|
|
||||||
if (e.originalEvent.type === 'DOMMouseScroll' && e.originalEvent.axis === 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var element = e.currentTarget;
|
scrollTimeout = setTimeout(function() {
|
||||||
var startingCoords = cornerstone.pageToPixel(element, e.pageX || e.originalEvent.pageX, e.pageY || e.originalEvent.pageY);
|
// !!!HACK/NOTE/WARNING!!!
|
||||||
|
// for some reason I am getting mousewheel and DOMMouseScroll events on my
|
||||||
|
// mac os x mavericks system when middle mouse button dragging.
|
||||||
|
// I couldn't find any info about this so this might break other systems
|
||||||
|
// webkit hack
|
||||||
|
if (e.originalEvent.type === 'mousewheel' && e.originalEvent.wheelDeltaY === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// firefox hack
|
||||||
|
if (e.originalEvent.type === 'DOMMouseScroll' && e.originalEvent.axis === 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
e = window.event || e; // old IE support
|
var element = e.currentTarget;
|
||||||
var wheelDelta = e.wheelDelta || -e.detail || -e.originalEvent.detail;
|
|
||||||
var direction = Math.max(-1, Math.min(1, (wheelDelta)));
|
|
||||||
|
|
||||||
var mouseWheelData = {
|
var x;
|
||||||
element: element,
|
var y;
|
||||||
viewport: cornerstone.getViewport(element),
|
|
||||||
image: cornerstone.getEnabledElement(element).image,
|
|
||||||
direction: direction,
|
|
||||||
pageX: e.pageX || e.originalEvent.pageX,
|
|
||||||
pageY: e.pageY || e.originalEvent.pageY,
|
|
||||||
imageX: startingCoords.x,
|
|
||||||
imageY: startingCoords.y
|
|
||||||
};
|
|
||||||
|
|
||||||
$(element).trigger('CornerstoneToolsMouseWheel', mouseWheelData);
|
if (e.pageX !== undefined && e.pageY !== undefined) {
|
||||||
|
x = e.pageX;
|
||||||
|
y = e.pageY;
|
||||||
|
} else if (e.originalEvent &&
|
||||||
|
e.originalEvent.pageX !== undefined &&
|
||||||
|
e.originalEvent.pageY !== undefined) {
|
||||||
|
x = e.originalEvent.pageX;
|
||||||
|
y = e.originalEvent.pageY;
|
||||||
|
} else {
|
||||||
|
// IE9 & IE10
|
||||||
|
x = e.x;
|
||||||
|
y = e.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
var startingCoords = cornerstone.pageToPixel(element, x, y);
|
||||||
|
|
||||||
|
e = window.event || e; // old IE support
|
||||||
|
var wheelDelta = e.wheelDelta || -e.detail || -e.originalEvent.detail || -e.originalEvent.deltaY;
|
||||||
|
var direction = Math.max(-1, Math.min(1, (wheelDelta)));
|
||||||
|
|
||||||
|
var mouseWheelData = {
|
||||||
|
element: element,
|
||||||
|
viewport: cornerstone.getViewport(element),
|
||||||
|
image: cornerstone.getEnabledElement(element).image,
|
||||||
|
direction: direction,
|
||||||
|
pageX: x,
|
||||||
|
pageY: y,
|
||||||
|
imageX: startingCoords.x,
|
||||||
|
imageY: startingCoords.y
|
||||||
|
};
|
||||||
|
|
||||||
|
$(element).trigger('CornerstoneToolsMouseWheel', mouseWheelData);
|
||||||
|
}, scrollTimeoutDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
var mouseWheelEvents = 'mousewheel DOMMouseScroll';
|
var mouseWheelEvents = 'mousewheel DOMMouseScroll';
|
||||||
@ -798,6 +823,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
// we want to detect both the same time
|
// we want to detect both the same time
|
||||||
pinch.recognizeWith(pan);
|
pinch.recognizeWith(pan);
|
||||||
pinch.recognizeWith(rotate);
|
pinch.recognizeWith(rotate);
|
||||||
|
rotate.recognizeWith(pan);
|
||||||
|
|
||||||
var doubleTap = new Hammer.Tap({
|
var doubleTap = new Hammer.Tap({
|
||||||
event: 'doubletap',
|
event: 'doubletap',
|
||||||
@ -2113,6 +2139,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
$(mouseEventData.element).on('CornerstoneToolsMouseMove', eventData, cornerstoneTools.arrowAnnotate.mouseMoveCallback);
|
$(mouseEventData.element).on('CornerstoneToolsMouseMove', eventData, cornerstoneTools.arrowAnnotate.mouseMoveCallback);
|
||||||
$(mouseEventData.element).on('CornerstoneToolsMouseDown', eventData, cornerstoneTools.arrowAnnotate.mouseDownCallback);
|
$(mouseEventData.element).on('CornerstoneToolsMouseDown', eventData, cornerstoneTools.arrowAnnotate.mouseDownCallback);
|
||||||
$(mouseEventData.element).on('CornerstoneToolsMouseDownActivate', eventData, cornerstoneTools.arrowAnnotate.mouseDownActivateCallback);
|
$(mouseEventData.element).on('CornerstoneToolsMouseDownActivate', eventData, cornerstoneTools.arrowAnnotate.mouseDownActivateCallback);
|
||||||
|
$(mouseEventData.element).on('CornerstoneToolsMouseDoubleClick', eventData, cornerstoneTools.arrowAnnotate.mouseDoubleClickCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
// associate this data with this imageId so we can render it and manipulate it
|
// associate this data with this imageId so we can render it and manipulate it
|
||||||
@ -2123,6 +2150,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
$(mouseEventData.element).off('CornerstoneToolsMouseMove', cornerstoneTools.arrowAnnotate.mouseMoveCallback);
|
$(mouseEventData.element).off('CornerstoneToolsMouseMove', cornerstoneTools.arrowAnnotate.mouseMoveCallback);
|
||||||
$(mouseEventData.element).off('CornerstoneToolsMouseDown', cornerstoneTools.arrowAnnotate.mouseDownCallback);
|
$(mouseEventData.element).off('CornerstoneToolsMouseDown', cornerstoneTools.arrowAnnotate.mouseDownCallback);
|
||||||
$(mouseEventData.element).off('CornerstoneToolsMouseDownActivate', cornerstoneTools.arrowAnnotate.mouseDownActivateCallback);
|
$(mouseEventData.element).off('CornerstoneToolsMouseDownActivate', cornerstoneTools.arrowAnnotate.mouseDownActivateCallback);
|
||||||
|
$(mouseEventData.element).off('CornerstoneToolsMouseDoubleClick', cornerstoneTools.arrowAnnotate.mouseDoubleClickCallback);
|
||||||
|
|
||||||
cornerstone.updateImage(mouseEventData.element);
|
cornerstone.updateImage(mouseEventData.element);
|
||||||
cornerstoneTools.moveNewHandle(mouseEventData, toolType, measurementData, measurementData.handles.end, function() {
|
cornerstoneTools.moveNewHandle(mouseEventData, toolType, measurementData, measurementData.handles.end, function() {
|
||||||
@ -3295,6 +3323,14 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
|
|
||||||
// Set the invalidated flag to false so that this data won't automatically be recalculated
|
// Set the invalidated flag to false so that this data won't automatically be recalculated
|
||||||
data.invalidated = false;
|
data.invalidated = false;
|
||||||
|
|
||||||
|
var eventType = 'CornerstoneToolsMeasurementModified';
|
||||||
|
var modifiedEventData = {
|
||||||
|
toolType: toolType,
|
||||||
|
element: element,
|
||||||
|
measurementData: data
|
||||||
|
};
|
||||||
|
$(element).trigger(eventType, modifiedEventData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define an array to store the rows of text for the textbox
|
// Define an array to store the rows of text for the textbox
|
||||||
@ -3386,7 +3422,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// First we calculate the ellipse points (top, left, right, and bottom)
|
// First we calculate the ellipse points (top, left, right, and bottom)
|
||||||
var ellipsePoints = [{
|
var ellipsePoints = [ {
|
||||||
// Top middle point of ellipse
|
// Top middle point of ellipse
|
||||||
x: leftCanvas + widthCanvas / 2,
|
x: leftCanvas + widthCanvas / 2,
|
||||||
y: topCanvas
|
y: topCanvas
|
||||||
@ -3402,14 +3438,14 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
// Right middle point of ellipse
|
// Right middle point of ellipse
|
||||||
x: leftCanvas + widthCanvas,
|
x: leftCanvas + widthCanvas,
|
||||||
y: topCanvas + heightCanvas / 2
|
y: topCanvas + heightCanvas / 2
|
||||||
}];
|
} ];
|
||||||
|
|
||||||
// We obtain the link starting point by finding the closest point on the ellipse to the
|
// We obtain the link starting point by finding the closest point on the ellipse to the
|
||||||
// center of the textbox
|
// center of the textbox
|
||||||
link.start = cornerstoneMath.point.findClosestPoint(ellipsePoints, link.end);
|
link.start = cornerstoneMath.point.findClosestPoint(ellipsePoints, link.end);
|
||||||
|
|
||||||
// Next we calculate the corners of the textbox bounding box
|
// Next we calculate the corners of the textbox bounding box
|
||||||
var boundingBoxPoints = [{
|
var boundingBoxPoints = [ {
|
||||||
// Top middle point of bounding box
|
// Top middle point of bounding box
|
||||||
x: boundingBox.left + boundingBox.width / 2,
|
x: boundingBox.left + boundingBox.width / 2,
|
||||||
y: boundingBox.top
|
y: boundingBox.top
|
||||||
@ -3435,7 +3471,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
context.beginPath();
|
context.beginPath();
|
||||||
context.strokeStyle = color;
|
context.strokeStyle = color;
|
||||||
context.lineWidth = lineWidth;
|
context.lineWidth = lineWidth;
|
||||||
context.setLineDash([2, 3]);
|
context.setLineDash([ 2, 3 ]);
|
||||||
context.moveTo(link.start.x, link.start.y);
|
context.moveTo(link.start.x, link.start.y);
|
||||||
context.lineTo(link.end.x, link.end.y);
|
context.lineTo(link.end.x, link.end.y);
|
||||||
context.stroke();
|
context.stroke();
|
||||||
@ -3460,7 +3496,8 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
toolType: toolType
|
toolType: toolType
|
||||||
});
|
});
|
||||||
|
|
||||||
})($, cornerstone, cornerstoneMath, cornerstoneTools);
|
})($, cornerstone, cornerstoneMath, cornerstoneTools);
|
||||||
|
|
||||||
// End Source; src/imageTools/ellipticalRoi.js
|
// End Source; src/imageTools/ellipticalRoi.js
|
||||||
|
|
||||||
// Begin Source: src/imageTools/freehand.js
|
// Begin Source: src/imageTools/freehand.js
|
||||||
@ -4325,6 +4362,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
function mouseUpCallback(e, eventData) {
|
function mouseUpCallback(e, eventData) {
|
||||||
$(eventData.element).off('CornerstoneToolsMouseDrag', dragCallback);
|
$(eventData.element).off('CornerstoneToolsMouseDrag', dragCallback);
|
||||||
$(eventData.element).off('CornerstoneToolsMouseUp', mouseUpCallback);
|
$(eventData.element).off('CornerstoneToolsMouseUp', mouseUpCallback);
|
||||||
|
$(eventData.element).off('CornerstoneToolsMouseClick', mouseUpCallback);
|
||||||
hideTool(eventData);
|
hideTool(eventData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4339,6 +4377,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
if (cornerstoneTools.isMouseButtonEnabled(eventData.which, e.data.mouseButtonMask)) {
|
if (cornerstoneTools.isMouseButtonEnabled(eventData.which, e.data.mouseButtonMask)) {
|
||||||
$(eventData.element).on('CornerstoneToolsMouseDrag', eventData, dragCallback);
|
$(eventData.element).on('CornerstoneToolsMouseDrag', eventData, dragCallback);
|
||||||
$(eventData.element).on('CornerstoneToolsMouseUp', eventData, mouseUpCallback);
|
$(eventData.element).on('CornerstoneToolsMouseUp', eventData, mouseUpCallback);
|
||||||
|
$(eventData.element).on('CornerstoneToolsMouseClick', eventData, mouseUpCallback);
|
||||||
drawMagnificationTool(eventData);
|
drawMagnificationTool(eventData);
|
||||||
return false; // false = causes jquery to preventDefault() and stopPropagation() this event
|
return false; // false = causes jquery to preventDefault() and stopPropagation() this event
|
||||||
}
|
}
|
||||||
@ -4975,12 +5014,20 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
var lineWidth = cornerstoneTools.toolStyle.getToolWidth();
|
var lineWidth = cornerstoneTools.toolStyle.getToolWidth();
|
||||||
var font = cornerstoneTools.textStyle.getFont();
|
var font = cornerstoneTools.textStyle.getFont();
|
||||||
var fontHeight = cornerstoneTools.textStyle.getFontSize();
|
var fontHeight = cornerstoneTools.textStyle.getFontSize();
|
||||||
|
var config = cornerstoneTools.rectangleRoi.getConfiguration();
|
||||||
|
|
||||||
for (var i = 0; i < toolData.data.length; i++) {
|
for (var i = 0; i < toolData.data.length; i++) {
|
||||||
context.save();
|
context.save();
|
||||||
|
|
||||||
var data = toolData.data[i];
|
var data = toolData.data[i];
|
||||||
|
|
||||||
|
// Apply any shadow settings defined in the tool configuration
|
||||||
|
if (config && config.shadow) {
|
||||||
|
context.shadowColor = config.shadowColor || '#000000';
|
||||||
|
context.shadowOffsetX = config.shadowOffsetX || 1;
|
||||||
|
context.shadowOffsetY = config.shadowOffsetY || 1;
|
||||||
|
}
|
||||||
|
|
||||||
//differentiate the color of activation tool
|
//differentiate the color of activation tool
|
||||||
if (data.active) {
|
if (data.active) {
|
||||||
color = cornerstoneTools.toolColors.getActiveColor();
|
color = cornerstoneTools.toolColors.getActiveColor();
|
||||||
@ -5606,7 +5653,8 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
x: mouseEventData.currentPoints.image.x,
|
x: mouseEventData.currentPoints.image.x,
|
||||||
y: mouseEventData.currentPoints.image.y,
|
y: mouseEventData.currentPoints.image.y,
|
||||||
highlight: true,
|
highlight: true,
|
||||||
active: true
|
active: true,
|
||||||
|
hasBoundingBox: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -5655,12 +5703,13 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
|
|
||||||
///////// BEGIN IMAGE RENDERING ///////
|
///////// BEGIN IMAGE RENDERING ///////
|
||||||
function pointNearTool(element, data, coords) {
|
function pointNearTool(element, data, coords) {
|
||||||
if (!data.textBoundingBox) {
|
if (!data.handles.end.boundingBox) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var distanceToPoint = cornerstoneMath.rect.distanceToPoint(data.textBoundingBox, coords);
|
var distanceToPoint = cornerstoneMath.rect.distanceToPoint(data.handles.end.boundingBox, coords);
|
||||||
return (distanceToPoint < 10);
|
var insideBoundingBox = cornerstoneTools.pointInsideBoundingBox(data.handles.end, coords);
|
||||||
|
return (distanceToPoint < 10) || insideBoundingBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onImageRendered(e, eventData) {
|
function onImageRendered(e, eventData) {
|
||||||
@ -5707,7 +5756,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var boundingBox = cornerstoneTools.drawTextBox(context, data.text, textCoords.x, textCoords.y - 10, color, options);
|
var boundingBox = cornerstoneTools.drawTextBox(context, data.text, textCoords.x, textCoords.y - 10, color, options);
|
||||||
data.textBoundingBox = boundingBox;
|
data.handles.end.boundingBox = boundingBox;
|
||||||
|
|
||||||
context.restore();
|
context.restore();
|
||||||
}
|
}
|
||||||
@ -8423,7 +8472,7 @@ Display scroll progress bar across bottom of image.
|
|||||||
// When the user has scrolled to a new image
|
// When the user has scrolled to a new image
|
||||||
clearTimeout(resetPrefetchTimeout);
|
clearTimeout(resetPrefetchTimeout);
|
||||||
resetPrefetchTimeout = setTimeout(function() {
|
resetPrefetchTimeout = setTimeout(function() {
|
||||||
var element = e.currentTarget;
|
var element = e.target;
|
||||||
prefetch(element);
|
prefetch(element);
|
||||||
}, resetPrefetchDelay);
|
}, resetPrefetchDelay);
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user