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
+5122
-3545
No files matched your search
@@ -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,27 +0,0 @@
|
||||
Template.flexboxLayout.events({
|
||||
'transitionend .sidebarMenu'(event) {
|
||||
if (!event.target.classList.contains('sidebarMenu')) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleResize();
|
||||
}
|
||||
});
|
||||
|
||||
Template.flexboxLayout.helpers({
|
||||
leftSidebarOpen() {
|
||||
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() {
|
||||
return Template.instance().data.state.get('rightSidebar') !== null;
|
||||
}
|
||||
});
|
||||
@@ -1,78 +0,0 @@
|
||||
@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
|
||||
@@ -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');
|
||||
}
|
||||
});
|
||||
@@ -1,42 +0,0 @@
|
||||
<template name="lesionTracker">
|
||||
<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/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="worklistTabs" class="tab-content">
|
||||
<div class="tab-pane active" id="worklistTab">
|
||||
<div class="worklistContainer">
|
||||
{{> worklistResult }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" id="viewerTab">
|
||||
</div>
|
||||
</div>
|
||||
{{ >associationModal }}
|
||||
{{ >optionsModal }}
|
||||
{{ >serverInformationModal }}
|
||||
{{ >confirmRemoveTimepointAssociation }}
|
||||
{{ >lastLoginModal }}
|
||||
{{ >studyContextMenu }}
|
||||
{{ >progressDialog}}
|
||||
{{ >viewSeriesDetailsModal }}
|
||||
</template>
|
||||
@@ -1,46 +0,0 @@
|
||||
const worklistContentId = 'worklistTab';
|
||||
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.lesionTracker.onCreated(() => {
|
||||
ViewerData = Session.get('ViewerData') || {};
|
||||
});
|
||||
|
||||
Template.lesionTracker.events({
|
||||
'click .js-toggle-studyList'() {
|
||||
const contentId = Session.get('activeContentId');
|
||||
|
||||
if (contentId !== worklistContentId) {
|
||||
switchToTab(worklistContentId);
|
||||
} else {
|
||||
switchToTab(viewerContentId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Template.lesionTracker.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 === worklistContentId) {
|
||||
return 'Back to viewer';
|
||||
} else if (contentId === viewerContentId) {
|
||||
return 'Study list';
|
||||
}
|
||||
},
|
||||
|
||||
onStudyList() {
|
||||
return (Session.get('activeContentId') === 'worklistTab');
|
||||
}
|
||||
});
|
||||
|
||||
Session.set('defaultSignInMessage', 'Tumor tracking in your browser.');
|
||||
@@ -1,79 +0,0 @@
|
||||
@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
|
||||
|
||||
#worklistTabs
|
||||
height: "calc(100% - %s)" % $topBarHeight
|
||||
|
||||
.tab-pane
|
||||
.viewerContainer
|
||||
.loadingTextDiv
|
||||
theme('color', '$textSecondaryColor')
|
||||
font-size: 30px
|
||||
-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,41 +0,0 @@
|
||||
<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 'lesionTableHudOpen'}}active{{/if}}">
|
||||
<div class="svgContainer">
|
||||
<svg>
|
||||
<use xlink:href="/packages/viewerbase/assets/icons.svg#icon-hud"></use>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="buttonLabel">HUD</div>
|
||||
</div>
|
||||
<div class="toolbarSectionButton pull-right rp-x-1 m-t-1 rm-r-1" data-toggle="modal" data-target="#optionsModal">
|
||||
<div class="svgContainer">
|
||||
<svg>
|
||||
<use xlink:href="/packages/viewerbase/assets/icons.svg#icon-trial-info"></use>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="buttonLabel">Trial</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,171 +0,0 @@
|
||||
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/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: 'lesions',
|
||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-measurements-lesions',
|
||||
svgWidth: 18,
|
||||
svgHeight: 10,
|
||||
bottomLabel: 'Lesions'
|
||||
}, {
|
||||
value: 'additional',
|
||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-measurements-additional',
|
||||
svgWidth: 14,
|
||||
svgHeight: 13,
|
||||
bottomLabel: 'Additional'
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
toolbarButtons() {
|
||||
// Check if the measure tools shall be disabled
|
||||
const isMesureDisabled = !Template.instance().data.timepointApi.currentTimepointId;
|
||||
const buttonData = [];
|
||||
|
||||
buttonData.push({
|
||||
id: 'zoom',
|
||||
title: 'Zoom',
|
||||
classes: 'imageViewerTool',
|
||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-zoom'
|
||||
});
|
||||
|
||||
buttonData.push({
|
||||
id: 'wwwc',
|
||||
title: 'Levels',
|
||||
classes: 'imageViewerTool',
|
||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-levels'
|
||||
});
|
||||
|
||||
buttonData.push({
|
||||
id: 'pan',
|
||||
title: 'Pan',
|
||||
classes: 'imageViewerTool',
|
||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-pan'
|
||||
});
|
||||
|
||||
buttonData.push({
|
||||
id: 'link',
|
||||
title: 'Link',
|
||||
classes: 'imageViewerCommand toolbarSectionButton',
|
||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-link'
|
||||
});
|
||||
|
||||
buttonData.push({
|
||||
id: 'bidirectional',
|
||||
title: 'Target',
|
||||
classes: 'imageViewerTool rm-l-3',
|
||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-target',
|
||||
disabled: isMesureDisabled
|
||||
});
|
||||
|
||||
buttonData.push({
|
||||
id: 'nonTarget',
|
||||
title: 'Non-Target',
|
||||
classes: 'imageViewerTool toolbarSectionButton',
|
||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-non-target',
|
||||
disabled: isMesureDisabled
|
||||
});
|
||||
|
||||
buttonData.push({
|
||||
id: 'length',
|
||||
title: 'Temp',
|
||||
classes: 'imageViewerTool toolbarSectionButton',
|
||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-temp'
|
||||
});
|
||||
|
||||
return buttonData;
|
||||
},
|
||||
|
||||
extraToolbarButtons() {
|
||||
// Check if the measure tools shall be disabled
|
||||
const isMesureDisabled = !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/viewerbase/assets/icons.svg#icon-tools-measure-temp',
|
||||
disabled: isMesureDisabled
|
||||
});
|
||||
|
||||
buttonData.push({
|
||||
id: 'unTool',
|
||||
title: 'UN Tool',
|
||||
classes: 'imageViewerTool toolbarSectionButton',
|
||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-temp',
|
||||
disabled: isMesureDisabled
|
||||
});
|
||||
|
||||
buttonData.push({
|
||||
id: 'exTool',
|
||||
title: 'EX Tool',
|
||||
classes: 'imageViewerTool toolbarSectionButton',
|
||||
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-temp',
|
||||
disabled: isMesureDisabled
|
||||
});
|
||||
|
||||
return buttonData;
|
||||
}
|
||||
});
|
||||
|
||||
Template.toolbarSection.events({
|
||||
'click #toggleHUD'() {
|
||||
const state = Session.get('lesionTableHudOpen');
|
||||
Session.set('lesionTableHudOpen', !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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
@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
|
||||
@@ -1,18 +0,0 @@
|
||||
<template name="viewer">
|
||||
{{#if Template.subscriptionsReady}}
|
||||
<div class="viewerDialogs">
|
||||
{{>confirmDeleteDialog}}
|
||||
{{>lesionLocationDialog}}
|
||||
{{>nonTargetLesionDialog}}
|
||||
{{>nonTargetResponseDialog}}
|
||||
{{>lesionTableHUD (clone this)}}
|
||||
{{! >conformanceCheckFeedback}}
|
||||
</div>
|
||||
<div id="viewer">
|
||||
{{>toolbarSection (clone this)}}
|
||||
{{>flexboxLayout (clone this)}}
|
||||
</div>
|
||||
{{else}}
|
||||
{{>loadingText}}
|
||||
{{/if}}
|
||||
</template>
|
||||
@@ -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,22 +0,0 @@
|
||||
@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)
|
||||
@@ -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.
+90
-41
@@ -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
|
||||
if (typeof cornerstone === 'undefined') {
|
||||
cornerstone = {};
|
||||
@@ -22,39 +22,64 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
|
||||
'use strict';
|
||||
|
||||
var scrollTimeout;
|
||||
var scrollTimeoutDelay = 1;
|
||||
|
||||
function mouseWheel(e) {
|
||||
// !!!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;
|
||||
}
|
||||
clearTimeout(scrollTimeout);
|
||||
|
||||
var element = e.currentTarget;
|
||||
var startingCoords = cornerstone.pageToPixel(element, e.pageX || e.originalEvent.pageX, e.pageY || e.originalEvent.pageY);
|
||||
scrollTimeout = setTimeout(function() {
|
||||
// !!!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 wheelDelta = e.wheelDelta || -e.detail || -e.originalEvent.detail;
|
||||
var direction = Math.max(-1, Math.min(1, (wheelDelta)));
|
||||
var element = e.currentTarget;
|
||||
|
||||
var mouseWheelData = {
|
||||
element: element,
|
||||
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
|
||||
};
|
||||
var x;
|
||||
var 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';
|
||||
@@ -798,6 +823,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
// we want to detect both the same time
|
||||
pinch.recognizeWith(pan);
|
||||
pinch.recognizeWith(rotate);
|
||||
rotate.recognizeWith(pan);
|
||||
|
||||
var doubleTap = new Hammer.Tap({
|
||||
event: 'doubletap',
|
||||
@@ -2113,6 +2139,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
$(mouseEventData.element).on('CornerstoneToolsMouseMove', eventData, cornerstoneTools.arrowAnnotate.mouseMoveCallback);
|
||||
$(mouseEventData.element).on('CornerstoneToolsMouseDown', eventData, cornerstoneTools.arrowAnnotate.mouseDownCallback);
|
||||
$(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
|
||||
@@ -2123,6 +2150,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
$(mouseEventData.element).off('CornerstoneToolsMouseMove', cornerstoneTools.arrowAnnotate.mouseMoveCallback);
|
||||
$(mouseEventData.element).off('CornerstoneToolsMouseDown', cornerstoneTools.arrowAnnotate.mouseDownCallback);
|
||||
$(mouseEventData.element).off('CornerstoneToolsMouseDownActivate', cornerstoneTools.arrowAnnotate.mouseDownActivateCallback);
|
||||
$(mouseEventData.element).off('CornerstoneToolsMouseDoubleClick', cornerstoneTools.arrowAnnotate.mouseDoubleClickCallback);
|
||||
|
||||
cornerstone.updateImage(mouseEventData.element);
|
||||
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
|
||||
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
|
||||
@@ -3386,7 +3422,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
};
|
||||
|
||||
// First we calculate the ellipse points (top, left, right, and bottom)
|
||||
var ellipsePoints = [{
|
||||
var ellipsePoints = [ {
|
||||
// Top middle point of ellipse
|
||||
x: leftCanvas + widthCanvas / 2,
|
||||
y: topCanvas
|
||||
@@ -3402,14 +3438,14 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
// Right middle point of ellipse
|
||||
x: leftCanvas + widthCanvas,
|
||||
y: topCanvas + heightCanvas / 2
|
||||
}];
|
||||
} ];
|
||||
|
||||
// We obtain the link starting point by finding the closest point on the ellipse to the
|
||||
// center of the textbox
|
||||
link.start = cornerstoneMath.point.findClosestPoint(ellipsePoints, link.end);
|
||||
|
||||
// Next we calculate the corners of the textbox bounding box
|
||||
var boundingBoxPoints = [{
|
||||
var boundingBoxPoints = [ {
|
||||
// Top middle point of bounding box
|
||||
x: boundingBox.left + boundingBox.width / 2,
|
||||
y: boundingBox.top
|
||||
@@ -3435,7 +3471,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
context.beginPath();
|
||||
context.strokeStyle = color;
|
||||
context.lineWidth = lineWidth;
|
||||
context.setLineDash([2, 3]);
|
||||
context.setLineDash([ 2, 3 ]);
|
||||
context.moveTo(link.start.x, link.start.y);
|
||||
context.lineTo(link.end.x, link.end.y);
|
||||
context.stroke();
|
||||
@@ -3460,7 +3496,8 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
toolType: toolType
|
||||
});
|
||||
|
||||
})($, cornerstone, cornerstoneMath, cornerstoneTools);
|
||||
})($, cornerstone, cornerstoneMath, cornerstoneTools);
|
||||
|
||||
// End Source; src/imageTools/ellipticalRoi.js
|
||||
|
||||
// Begin Source: src/imageTools/freehand.js
|
||||
@@ -4325,6 +4362,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
function mouseUpCallback(e, eventData) {
|
||||
$(eventData.element).off('CornerstoneToolsMouseDrag', dragCallback);
|
||||
$(eventData.element).off('CornerstoneToolsMouseUp', mouseUpCallback);
|
||||
$(eventData.element).off('CornerstoneToolsMouseClick', mouseUpCallback);
|
||||
hideTool(eventData);
|
||||
}
|
||||
|
||||
@@ -4339,6 +4377,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
if (cornerstoneTools.isMouseButtonEnabled(eventData.which, e.data.mouseButtonMask)) {
|
||||
$(eventData.element).on('CornerstoneToolsMouseDrag', eventData, dragCallback);
|
||||
$(eventData.element).on('CornerstoneToolsMouseUp', eventData, mouseUpCallback);
|
||||
$(eventData.element).on('CornerstoneToolsMouseClick', eventData, mouseUpCallback);
|
||||
drawMagnificationTool(eventData);
|
||||
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 font = cornerstoneTools.textStyle.getFont();
|
||||
var fontHeight = cornerstoneTools.textStyle.getFontSize();
|
||||
var config = cornerstoneTools.rectangleRoi.getConfiguration();
|
||||
|
||||
for (var i = 0; i < toolData.data.length; i++) {
|
||||
context.save();
|
||||
|
||||
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
|
||||
if (data.active) {
|
||||
color = cornerstoneTools.toolColors.getActiveColor();
|
||||
@@ -5606,7 +5653,8 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
x: mouseEventData.currentPoints.image.x,
|
||||
y: mouseEventData.currentPoints.image.y,
|
||||
highlight: true,
|
||||
active: true
|
||||
active: true,
|
||||
hasBoundingBox: true
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -5655,12 +5703,13 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
|
||||
///////// BEGIN IMAGE RENDERING ///////
|
||||
function pointNearTool(element, data, coords) {
|
||||
if (!data.textBoundingBox) {
|
||||
if (!data.handles.end.boundingBox) {
|
||||
return;
|
||||
}
|
||||
|
||||
var distanceToPoint = cornerstoneMath.rect.distanceToPoint(data.textBoundingBox, coords);
|
||||
return (distanceToPoint < 10);
|
||||
var distanceToPoint = cornerstoneMath.rect.distanceToPoint(data.handles.end.boundingBox, coords);
|
||||
var insideBoundingBox = cornerstoneTools.pointInsideBoundingBox(data.handles.end, coords);
|
||||
return (distanceToPoint < 10) || insideBoundingBox;
|
||||
}
|
||||
|
||||
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);
|
||||
data.textBoundingBox = boundingBox;
|
||||
data.handles.end.boundingBox = boundingBox;
|
||||
|
||||
context.restore();
|
||||
}
|
||||
@@ -8423,7 +8472,7 @@ Display scroll progress bar across bottom of image.
|
||||
// When the user has scrolled to a new image
|
||||
clearTimeout(resetPrefetchTimeout);
|
||||
resetPrefetchTimeout = setTimeout(function() {
|
||||
var element = e.currentTarget;
|
||||
var element = e.target;
|
||||
prefetch(element);
|
||||
}, resetPrefetchDelay);
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,14 +1,13 @@
|
||||
Package.describe({
|
||||
name: 'cornerstone',
|
||||
name: 'ohif:cornerstone',
|
||||
summary: 'Cornerstone Web-based Medical Imaging libraries',
|
||||
version: '0.0.1'
|
||||
});
|
||||
|
||||
Package.onUse(function(api) {
|
||||
api.versionsFrom('1.3.5.1');
|
||||
api.versionsFrom('1.4');
|
||||
|
||||
api.use('jquery');
|
||||
api.use('dicomweb');
|
||||
|
||||
api.addFiles('client/cornerstone.js', 'client', {
|
||||
bare: true
|
||||
File renamed without changes.
+1
-1
@@ -1,7 +1,7 @@
|
||||
<template name="roundedButtonGroup">
|
||||
<div class="{{class}} roundedButtonGroup clearfix center-table">
|
||||
{{#each item in this.options}}
|
||||
<a class="roundedButtonWrapper noselect {{inlineIf getValue item.value 'active'}}" data-value="{{item.value}}">
|
||||
<a class="roundedButtonWrapper noselect {{#if eq getValue item.value}}active{{/if}}" data-value="{{item.value}}">
|
||||
<div class="roundedButton">
|
||||
{{#if item.svgLink}}
|
||||
<svg style="width: {{item.svgWidth}}px; height: {{item.svgHeight}}px">
|
||||
File renamed without changes.
File renamed without changes.
@@ -48,5 +48,4 @@ Package.onUse(function(api) {
|
||||
'components/roundedButtonGroup/roundedButtonGroup.styl',
|
||||
'components/roundedButtonGroup/roundedButtonGroup.js'
|
||||
], 'client');
|
||||
|
||||
});
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Executable
+38
@@ -0,0 +1,38 @@
|
||||
Package.describe({
|
||||
name: 'ohif:dicom-services',
|
||||
summary: 'DICOM Services: DICOMWeb and DIMSE C-Service functions',
|
||||
version: '0.0.1'
|
||||
});
|
||||
|
||||
Package.onUse(function(api) {
|
||||
api.versionsFrom('1.4');
|
||||
|
||||
api.use('http');
|
||||
api.use('ecmascript');
|
||||
|
||||
// DICOMWeb functions
|
||||
api.addFiles('server/DICOMWeb/namespace.js', 'server');
|
||||
api.addFiles('server/DICOMWeb/getJSON.js', 'server');
|
||||
api.addFiles('server/DICOMWeb/getName.js', 'server');
|
||||
api.addFiles('server/DICOMWeb/getNumber.js', 'server');
|
||||
api.addFiles('server/DICOMWeb/getString.js', 'server');
|
||||
api.addFiles('server/DICOMWeb/getModalities.js', 'server');
|
||||
api.addFiles('server/DICOMWeb/getAttribute.js', 'server');
|
||||
|
||||
api.export('DICOMWeb', 'server');
|
||||
|
||||
// DIMSE functions
|
||||
api.addFiles('server/DIMSE/require.js', 'server');
|
||||
api.addFiles('server/DIMSE/constants.js', 'server');
|
||||
api.addFiles('server/DIMSE/elements_data.js', 'server');
|
||||
api.addFiles('server/DIMSE/Field.js', 'server');
|
||||
api.addFiles('server/DIMSE/RWStream.js', 'server');
|
||||
api.addFiles('server/DIMSE/Data.js', 'server');
|
||||
api.addFiles('server/DIMSE/Message.js', 'server');
|
||||
api.addFiles('server/DIMSE/PDU.js', 'server');
|
||||
api.addFiles('server/DIMSE/CSocket.js', 'server');
|
||||
api.addFiles('server/DIMSE/Connection.js', 'server');
|
||||
api.addFiles('server/DIMSE/DIMSE.js', 'server');
|
||||
|
||||
api.export('DIMSE', 'server');
|
||||
});
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
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