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:
1 parent
c511edd419
commit
ab4d4c9008
537 files changed
+4990
-3413
No files matched your search
@@ -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
|
||||||
+7
-7
@@ -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>
|
||||||
+8
-8
@@ -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');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
+1
-1
@@ -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
|
||||||
+3
-3
@@ -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>
|
||||||
+28
-30
@@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
File renamed without changes.
@@ -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>
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
File renamed without changes.
@@ -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>
|
||||||
+3
-11
@@ -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');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
File renamed without changes.
@@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
|
||||||
+13
-11
@@ -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'
|
||||||
@@ -31,16 +20,6 @@ Router.route('/viewer/:_id', {
|
|||||||
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() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
local
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
dev_bundle
|
||||||
|
local
|
||||||
@@ -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
|
||||||
@@ -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": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
server
|
||||||
|
browser
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
METEOR@1.4.1.1
|
||||||
@@ -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
|
||||||
@@ -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';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*/
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
body
|
||||||
|
background-color: black
|
||||||
@@ -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>
|
||||||
@@ -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.');
|
||||||
@@ -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
-6
@@ -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}}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -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
|
||||||
@@ -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>
|
||||||
@@ -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);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// Create application logger using loglevel
|
||||||
|
// https://atmospherejs.com/spacejamio/loglevel
|
||||||
|
log = loglevel.createAppLogger('PETSUVMeasurements', defaultLevel = 'info');
|
||||||
@@ -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']
|
||||||
|
});
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -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({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -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')
|
|
||||||
-7
@@ -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>
|
|
||||||
-32
@@ -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([]);
|
|
||||||
});
|
|
||||||
-19
@@ -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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
-24
@@ -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>
|
|
||||||
-11
@@ -1,11 +0,0 @@
|
|||||||
<template name="lesionTableTimepointCell">
|
|
||||||
{{#if hasDataAtThisTimepoint}}
|
|
||||||
<div class="lesionTableTimepointCell" tabindex="1">
|
|
||||||
{{displayData}}
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<div class="lesionTableTimepointCell empty">
|
|
||||||
...
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</template>
|
|
||||||
-39
@@ -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');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
-42
@@ -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>
|
|
||||||
-103
@@ -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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
File renamed without changes.
File renamed without changes.
+88
-39
@@ -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();
|
||||||
@@ -3461,6 +3497,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
});
|
});
|
||||||
|
|
||||||
})($, 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);
|
||||||
}
|
}
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loaded 100 of 537 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user