diff --git a/OHIFViewer/client/components/viewer/viewer.html b/OHIFViewer/client/components/viewer/viewer.html index 2aadaded7..eabbfd0de 100644 --- a/OHIFViewer/client/components/viewer/viewer.html +++ b/OHIFViewer/client/components/viewer/viewer.html @@ -2,6 +2,7 @@ {{#if Template.subscriptionsReady}}
{{> studyBrowser }} + {{> cineDialog }}
{{> toolbar }} {{> imageViewerViewports }} diff --git a/OHIFViewer/client/components/viewer/viewer.js b/OHIFViewer/client/components/viewer/viewer.js index 32ffb23c4..f9940eecd 100644 --- a/OHIFViewer/client/components/viewer/viewer.js +++ b/OHIFViewer/client/components/viewer/viewer.js @@ -12,6 +12,10 @@ Template.viewer.onCreated(function() { OHIF.viewer.defaultTool = 'wwwc'; OHIF.viewer.refLinesEnabled = true; OHIF.viewer.isPlaying = {}; + OHIF.viewer.cine = { + framesPerSecond: 24, + loop: true + }; OHIF.viewer.functionList = { invert: function(element) { diff --git a/Packages/cornerstone/client/cornerstoneTools.js b/Packages/cornerstone/client/cornerstoneTools.js index 369ef0549..b398ebe0e 100644 --- a/Packages/cornerstone/client/cornerstoneTools.js +++ b/Packages/cornerstone/client/cornerstoneTools.js @@ -1,4 +1,4 @@ -/*! cornerstoneTools - v0.7.8 - 2016-02-13 | (c) 2014 Chris Hafey | https://github.com/chafey/cornerstoneTools */ +/*! cornerstoneTools - v0.7.8 - 2016-03-17 | (c) 2014 Chris Hafey | https://github.com/chafey/cornerstoneTools */ // Begin Source: src/header.js if (typeof cornerstone === 'undefined') { cornerstone = {}; @@ -596,31 +596,33 @@ if (typeof cornerstoneTools === 'undefined') { isPress = false; clearTimeout(pressTimeout); - startPoints = { - page: cornerstoneMath.point.pageToPoint(e.originalEvent.changedTouches[0]), - image: cornerstone.pageToPixel(element, e.originalEvent.changedTouches[0].pageX, e.originalEvent.changedTouches[0].pageY), - client: { - x: e.originalEvent.changedTouches[0].clientX, - y: e.originalEvent.changedTouches[0].clientY - } - }; - startPoints.canvas = cornerstone.pixelToCanvas(element, startPoints.image); + setTimeout(function() { + startPoints = { + page: cornerstoneMath.point.pageToPoint(e.originalEvent.changedTouches[0]), + image: cornerstone.pageToPixel(element, e.originalEvent.changedTouches[0].pageX, e.originalEvent.changedTouches[0].pageY), + client: { + x: e.originalEvent.changedTouches[0].clientX, + y: e.originalEvent.changedTouches[0].clientY + } + }; + startPoints.canvas = cornerstone.pixelToCanvas(element, startPoints.image); - eventType = 'CornerstoneToolsTouchEnd'; + eventType = 'CornerstoneToolsTouchEnd'; - eventData = { - event: e, - viewport: cornerstone.getViewport(element), - image: cornerstone.getEnabledElement(element).image, - element: element, - startPoints: startPoints, - currentPoints: startPoints, - type: eventType, - isTouchEvent: true - }; + eventData = { + event: e, + viewport: cornerstone.getViewport(element), + image: cornerstone.getEnabledElement(element).image, + element: element, + startPoints: startPoints, + currentPoints: startPoints, + type: eventType, + isTouchEvent: true + }; - event = $.Event(eventType, eventData); - $(element).trigger(event, eventData); + event = $.Event(eventType, eventData); + $(element).trigger(event, eventData); + }, 50); break; case 'panmove': @@ -793,8 +795,18 @@ if (typeof cornerstoneTools === 'undefined') { pinch.recognizeWith(pan); pinch.recognizeWith(rotate); + var doubleTap = new Hammer.Tap({ + event: 'doubletap', + taps: 2, + interval: 1500, + threshold: 50, + posThreshold: 50 + }); + + doubleTap.recognizeWith(pan); + // add to the Manager - mc.add([ pan, rotate, pinch ]); + mc.add([ doubleTap, pan, rotate, pinch ]); mc.on('tap doubletap panstart panmove panend pinchstart pinchmove rotatemove', onTouch); cornerstoneTools.preventGhostClick.enable(element); @@ -1114,6 +1126,16 @@ if (typeof cornerstoneTools === 'undefined') { mouseButtonMask: mouseButtonMask }; + var eventType = 'CornerstoneToolsToolDeactivated'; + var statusChangeEventData = { + mouseButtonMask: mouseButtonMask, + toolType: mouseToolInterface.toolType, + type: eventType + }; + + var event = $.Event(eventType, statusChangeEventData); + $(element).trigger(event, statusChangeEventData); + $(element).off('CornerstoneImageRendered', mouseToolInterface.onImageRendered); $(element).off('CornerstoneToolsMouseMove', mouseToolInterface.mouseMoveCallback || mouseMoveCallback); $(element).off('CornerstoneToolsMouseDown', mouseToolInterface.mouseDownCallback || mouseDownCallback); @@ -1557,6 +1579,7 @@ if (typeof cornerstoneTools === 'undefined') { $(element).off('CornerstoneToolsTouchStartActive', touchToolInterface.touchDownActivateCallback || touchDownActivateCallback); $(element).off('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback); + $(element).off('CornerstoneToolsTouchStart', touchToolInterface.touchStartCallback || touchStartCallback); cornerstone.updateImage(element); cornerstoneTools.moveNewHandleTouch(touchEventData, touchToolInterface.toolType, measurementData, measurementData.handles.end, function() { @@ -1569,6 +1592,7 @@ if (typeof cornerstoneTools === 'undefined') { $(element).on('CornerstoneToolsTouchStartActive', touchToolInterface.touchDownActivateCallback || touchDownActivateCallback); $(element).on('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback); + $(element).on('CornerstoneToolsTouchStart', touchToolInterface.touchStartCallback || touchStartCallback); cornerstone.updateImage(element); }); } @@ -1804,8 +1828,15 @@ if (typeof cornerstoneTools === 'undefined') { // visible, interactive function deactivate(element) { - //console.log('deactivate touchTool'); - + var eventType = 'CornerstoneToolsToolDeactivated'; + var statusChangeEventData = { + toolType: touchToolInterface.toolType, + type: eventType + }; + + var event = $.Event(eventType, statusChangeEventData); + $(element).trigger(event, statusChangeEventData); + $(element).off('CornerstoneImageRendered', touchToolInterface.onImageRendered); $(element).off('CornerstoneToolsTouchStart', touchToolInterface.touchStartCallback || touchStartCallback); $(element).off('CornerstoneToolsTouchStartActive', touchToolInterface.touchDownActivateCallback || touchDownActivateCallback); @@ -2707,6 +2738,56 @@ if (typeof cornerstoneTools === 'undefined') { // End Source; src/imageTools/displayTool.js +// Begin Source: src/imageTools/doubleTapTool.js +(function($, cornerstone, cornerstoneTools) { + + 'use strict'; + + function doubleTapTool(doubleTapCallback) { + var toolInterface = { + activate: function(element) { + $(element).off('CornerstoneToolsDoubleTap', doubleTapCallback); + var eventData = {}; + $(element).on('CornerstoneToolsDoubleTap', eventData, doubleTapCallback); + }, + disable: function(element) {$(element).off('CornerstoneToolsDoubleTap', doubleTapCallback);}, + enable: function(element) {$(element).off('CornerstoneToolsDoubleTap', doubleTapCallback);}, + deactivate: function(element) {$(element).off('CornerstoneToolsDoubleTap', doubleTapCallback);} + }; + return toolInterface; + } + + // module exports + cornerstoneTools.doubleTapTool = doubleTapTool; + +})($, cornerstone, cornerstoneTools); + +// End Source; src/imageTools/doubleTapTool.js + +// Begin Source: src/imageTools/doubleTapZoom.js +(function($, cornerstone, cornerstoneTools) { + + 'use strict'; + + function fitToWindowStrategy(eventData) { + cornerstone.fitToWindow(eventData.element); + } + + function doubleTapCallback(e, eventData) { + cornerstoneTools.doubleTapZoom.strategy(eventData); + return false; // false = causes jquery to preventDefault() and stopPropagation() this event + } + + cornerstoneTools.doubleTapZoom = cornerstoneTools.doubleTapTool(doubleTapCallback); + cornerstoneTools.doubleTapZoom.strategies = { + default: fitToWindowStrategy + }; + cornerstoneTools.doubleTapZoom.strategy = fitToWindowStrategy; + +})($, cornerstone, cornerstoneTools); + +// End Source; src/imageTools/doubleTapZoom.js + // Begin Source: src/imageTools/dragProbe.js (function($, cornerstone, cornerstoneTools) { @@ -5288,6 +5369,13 @@ if (typeof cornerstoneTools === 'undefined') { if (cornerstoneTools.anyHandlesOutsideImage(mouseEventData, measurementData.handles)) { // delete the measurement cornerstoneTools.removeToolState(element, toolType, measurementData); + + $(element).on('CornerstoneToolsMouseMove', cornerstoneTools.simpleAngle.mouseMoveCallback); + $(element).on('CornerstoneToolsMouseDrag', cornerstoneTools.simpleAngle.mouseMoveCallback); + $(element).on('CornerstoneToolsMouseDown', eventData, cornerstoneTools.simpleAngle.mouseDownCallback); + $(element).on('CornerstoneToolsMouseDownActivate', eventData, cornerstoneTools.simpleAngle.mouseDownActivateCallback); + cornerstone.updateImage(element); + return; } measurementData.handles.end.active = true; @@ -5328,7 +5416,12 @@ if (typeof cornerstoneTools === 'undefined') { if (cornerstoneTools.anyHandlesOutsideImage(touchEventData, measurementData.handles)) { // delete the measurement cornerstoneTools.removeToolState(element, toolType, measurementData); + $(element).on('CornerstoneToolsTouchDrag', cornerstoneTools.simpleAngleTouch.touchMoveCallback); + $(element).on('CornerstoneToolsTouchStart', cornerstoneTools.simpleAngleTouch.touchStartCallback); + $(element).on('CornerstoneToolsTouchStartActive', cornerstoneTools.simpleAngleTouch.touchDownActivateCallback); + $(element).on('CornerstoneToolsTap', cornerstoneTools.simpleAngleTouch.tapCallback); cornerstone.updateImage(element); + return; } cornerstoneTools.moveNewHandleTouch(touchEventData, toolType, measurementData, measurementData.handles.end, function() { @@ -5643,7 +5736,7 @@ if (typeof cornerstoneTools === 'undefined') { $(eventData.element).on('CornerstoneToolsMouseDrag', mouseDragCallback); $(eventData.element).on('CornerstoneToolsMouseUp', mouseUpCallback); $(eventData.element).on('CornerstoneToolsMouseClick', mouseUpCallback); - return false; // false = cases jquery to preventDefault() and stopPropagation() this event + return false; // false = causes jquery to preventDefault() and stopPropagation() this event } } @@ -6789,16 +6882,32 @@ if (typeof cornerstoneTools === 'undefined') { } } + function toolDeactivatedCallback(e, eventData) { + if (eventData.toolType === toolType) { + $(element).off('CornerstoneToolsMouseMove', moveCallback); + $(element).off('CornerstoneToolsMouseDrag', moveCallback); + $(element).off('CornerstoneToolsMouseClick', moveEndCallback); + $(element).off('CornerstoneToolsMouseUp', moveEndCallback); + $(element).off('CornerstoneToolsMeasurementRemoved', measurementRemovedCallback); + $(element).off('CornerstoneToolsToolDeactivated', toolDeactivatedCallback); + + handle.active = false; + cornerstone.updateImage(element); + } + } + $(element).on('CornerstoneToolsMouseDrag', whichMovement); $(element).on('CornerstoneToolsMouseMove', whichMovement); $(element).on('CornerstoneToolsMeasurementRemoved', measurementRemovedCallback); - + $(element).on('CornerstoneToolsToolDeactivated', toolDeactivatedCallback); + function moveEndCallback() { $(element).off('CornerstoneToolsMouseMove', moveCallback); $(element).off('CornerstoneToolsMouseDrag', moveCallback); $(element).off('CornerstoneToolsMouseClick', moveEndCallback); $(element).off('CornerstoneToolsMouseUp', moveEndCallback); $(element).off('CornerstoneToolsMeasurementRemoved', measurementRemovedCallback); + $(element).off('CornerstoneToolsToolDeactivated', toolDeactivatedCallback); handle.active = false; cornerstone.updateImage(element); @@ -6822,7 +6931,7 @@ if (typeof cornerstoneTools === 'undefined') { 'use strict'; function moveNewHandleTouch(eventData, toolType, data, handle, doneMovingCallback, preventHandleOutsideImage) { - console.log('moveNewHandleTouch'); + //console.log('moveNewHandleTouch'); var element = eventData.element; var imageCoords = cornerstone.pageToPixel(element, eventData.currentPoints.page.x, eventData.currentPoints.page.y + 50); var distanceFromTouch = { @@ -6857,12 +6966,12 @@ if (typeof cornerstoneTools === 'undefined') { } function moveEndCallback(e, eventData) { - console.log(e); $(element).off('CornerstoneToolsTouchDrag', moveCallback); $(element).off('CornerstoneToolsTouchPinch', moveEndCallback); $(element).off('CornerstoneToolsTouchEnd', moveEndCallback); - //$(element).off('CornerstoneToolsDragEnd', moveEndCallback); $(element).off('CornerstoneToolsTap', moveEndCallback); + $(element).off('CornerstoneToolsTouchStart', stopImmediatePropagation); + $(element).off('CornerstoneToolsToolDeactivated', toolDeactivatedCallback); if (e.type === 'CornerstoneToolsTouchPinch' || e.type === 'CornerstoneToolsTouchPress') { handle.active = false; @@ -6891,11 +7000,45 @@ if (typeof cornerstoneTools === 'undefined') { } } + function stopImmediatePropagation(e) { + // Stop the CornerstoneToolsTouchStart event from + // become a CornerstoneToolsTouchStartActive event when + // moveNewHandleTouch ends + e.stopImmediatePropagation(); + return false; + } + $(element).on('CornerstoneToolsTouchDrag', moveCallback); $(element).on('CornerstoneToolsTouchPinch', moveEndCallback); $(element).on('CornerstoneToolsTouchEnd', moveEndCallback); - //$(element).on('CornerstoneToolsDragEnd', moveEndCallback); $(element).on('CornerstoneToolsTap', moveEndCallback); + $(element).on('CornerstoneToolsTouchStart', stopImmediatePropagation); + + function toolDeactivatedCallback() { + $(element).off('CornerstoneToolsTouchDrag', moveCallback); + $(element).off('CornerstoneToolsTouchPinch', moveEndCallback); + $(element).off('CornerstoneToolsTouchEnd', moveEndCallback); + $(element).off('CornerstoneToolsTap', moveEndCallback); + $(element).off('CornerstoneToolsTouchStart', stopImmediatePropagation); + $(element).off('CornerstoneToolsToolDeactivated', toolDeactivatedCallback); + + handle.active = false; + data.active = false; + handle.x = eventData.currentPoints.image.x + distanceFromTouch.x; + handle.y = eventData.currentPoints.image.y + distanceFromTouch.y; + + if (preventHandleOutsideImage) { + handle.x = Math.max(handle.x, 0); + handle.x = Math.min(handle.x, eventData.image.width); + + handle.y = Math.max(handle.y, 0); + handle.y = Math.min(handle.y, eventData.image.height); + } + + cornerstone.updateImage(element); + } + + $(element).on('CornerstoneToolsToolDeactivated', toolDeactivatedCallback); } // module/private exports @@ -7619,9 +7762,9 @@ if (typeof cornerstoneTools === 'undefined') { var maxSimultaneousRequests = cornerstoneTools.getMaxSimultaneousRequests(); maxNumRequests = { - interaction: maxSimultaneousRequests, - thumbnail: maxSimultaneousRequests - 2, - prefetch: maxSimultaneousRequests - 1 + interaction: Math.max(maxSimultaneousRequests, 1), + thumbnail: Math.max(maxSimultaneousRequests - 2, 1), + prefetch: Math.max(maxSimultaneousRequests - 1, 1) }; for (var i = 0; i < maxSimultaneousRequests; i++) { @@ -7697,12 +7840,8 @@ if (typeof cornerstoneTools === 'undefined') { throw 'playClip: element must not be undefined'; } - if (framesPerSecond === undefined) { - framesPerSecond = 30; - } - var stackToolData = cornerstoneTools.getToolState(element, 'stack'); - if (stackToolData === undefined || stackToolData.data === undefined || stackToolData.data.length === 0) { + if (!stackToolData || !stackToolData.data || !stackToolData.data.length) { return; } @@ -7711,10 +7850,10 @@ if (typeof cornerstoneTools === 'undefined') { var playClipToolData = cornerstoneTools.getToolState(element, toolType); var playClipData; - if (playClipToolData === undefined || playClipToolData.data.length === 0) { + if (!playClipToolData || !playClipToolData.data || !playClipToolData.data.length) { playClipData = { intervalId: undefined, - framesPerSecond: framesPerSecond, + framesPerSecond: framesPerSecond || 30, lastFrameTimeStamp: undefined, frameRate: 0, loop: true @@ -7722,7 +7861,6 @@ if (typeof cornerstoneTools === 'undefined') { cornerstoneTools.addToolState(element, toolType, playClipData); } else { playClipData = playClipToolData.data[0]; - playClipData.framesPerSecond = framesPerSecond; } // if already playing, do not set a new interval @@ -7731,7 +7869,6 @@ if (typeof cornerstoneTools === 'undefined') { } playClipData.intervalId = setInterval(function() { - var newImageIdIndex = stackData.currentImageIdIndex; if (playClipData.framesPerSecond > 0) { @@ -7741,10 +7878,10 @@ if (typeof cornerstoneTools === 'undefined') { } if (!playClipData.loop && (newImageIdIndex >= stackData.imageIds.length || newImageIdIndex < 0)) { - var eventDetail = { element: element }; + var event = $.Event('CornerstoneToolsClipStopped', eventDetail); $(element).trigger(event, eventDetail); @@ -7802,13 +7939,12 @@ if (typeof cornerstoneTools === 'undefined') { */ function stopClip(element) { var playClipToolData = cornerstoneTools.getToolState(element, toolType); - var playClipData; - if (playClipToolData === undefined || playClipToolData.data.length === 0) { + if (!playClipToolData || !playClipToolData.data || !playClipToolData.data.length) { return; - } else { - playClipData = playClipToolData.data[0]; } + var playClipData = playClipToolData.data[0]; + clearInterval(playClipData.intervalId); playClipData.intervalId = undefined; } @@ -8169,7 +8305,7 @@ Display scroll progress bar across bottom of image. } } - function getConfiguration () { + function getConfiguration() { return configuration; } @@ -10520,9 +10656,15 @@ Display scroll progress bar across bottom of image. return browserData[browserVersion]; } + function isMobileDevice() { + var pattern = new RegExp('Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini'); + return !!pattern.test(navigator.userAgent); + } + // module exports cornerstoneTools.getMaxSimultaneousRequests = getMaxSimultaneousRequests; cornerstoneTools.getBrowserInfo = getBrowserInfo; + cornerstoneTools.isMobileDevice = isMobileDevice; })(cornerstone, cornerstoneTools); diff --git a/Packages/dimseservice/server/DIMSE.js b/Packages/dimseservice/server/DIMSE.js index e70313782..dff41ccdf 100755 --- a/Packages/dimseservice/server/DIMSE.js +++ b/Packages/dimseservice/server/DIMSE.js @@ -8,11 +8,11 @@ var conn = new Connection({ } }); -Meteor.startup(function(){ +Meteor.startup(function() { var peers = Meteor.settings.dimse; console.log('Adding DIMSE peers'); if (peers && peers.length) { - peers.forEach(function(peer){ + peers.forEach(function(peer) { conn.addPeer(peer); }); } @@ -36,12 +36,12 @@ DIMSE.retrievePatients = function(params, options) { var future = new Future; DIMSE.associate([C.SOP_PATIENT_ROOT_FIND], function(pdu) { var defaultParams = { - 0x00100010: "", - 0x00100020: "", - 0x00100030: "", - 0x00100040: "", - 0x00101010: "", - 0x00101040: "" + 0x00100010: '', + 0x00100020: '', + 0x00100030: '', + 0x00100040: '', + 0x00101010: '', + 0x00101040: '' }; var result = this.findPatients(Object.assign(defaultParams, params)), @@ -69,16 +69,16 @@ DIMSE.retrieveStudies = function(params, options) { var future = new Future; DIMSE.associate([C.SOP_STUDY_ROOT_FIND], function(pdu) { var defaultParams = { - 0x0020000D: "", - 0x00080060: "", - 0x00080005: "", - 0x00080020: "", - 0x00080030: "", - 0x00080090: "", - 0x00100010: "", - 0x00100020: "", - 0x00200010: "", - 0x00100030: "" + 0x0020000D: '', + 0x00080060: '', + 0x00080005: '', + 0x00080020: '', + 0x00080030: '', + 0x00080090: '', + 0x00100010: '', + 0x00100020: '', + 0x00200010: '', + 0x00100030: '' }; var result = this.findStudies(Object.assign(defaultParams, params)), @@ -105,17 +105,17 @@ DIMSE.retrieveSeries = function(studyInstanceUID, params, options) { var future = new Future; DIMSE.associate([C.SOP_STUDY_ROOT_FIND], function(pdu) { var defaultParams = { - 0x0020000D: studyInstanceUID ? studyInstanceUID : "", - 0x00080005: "", - 0x00080020: "", - 0x00080030: "", - 0x00080090: "", - 0x00100010: "", - 0x00100020: "", - 0x00200010: "", - 0x0008103E: "", - 0x0020000E: "", - 0x00200011: "" + 0x0020000D: studyInstanceUID ? studyInstanceUID : '', + 0x00080005: '', + 0x00080020: '', + 0x00080030: '', + 0x00080090: '', + 0x00100010: '', + 0x00100020: '', + 0x00200010: '', + 0x0008103E: '', + 0x0020000E: '', + 0x00200011: '' }; var result = this.findSeries(Object.assign(defaultParams, params)), @@ -141,24 +141,46 @@ DIMSE.retrieveInstances = function(studyInstanceUID, seriesInstanceUID, params, var future = new Future; DIMSE.associate([C.SOP_STUDY_ROOT_FIND], function(pdu) { var defaultParams = { - 0x0020000D: studyInstanceUID ? studyInstanceUID : "", - 0x0020000E: (studyInstanceUID && seriesInstanceUID) ? seriesInstanceUID : "", - 0x00080005: "", - 0x00080020: "", - 0x00080030: "", - 0x00080090: "", - 0x00100010: "", - 0x00100020: "", - 0x00200010: "", - 0x0008103E: "", - 0x00200011: "", - 0x00080016: "", - 0x00080018: "", - 0x00200013: "", - 0x00280010: "", - 0x00280011: "", - 0x00280100: "", - 0x00280103: "" + 0x0020000D: studyInstanceUID ? studyInstanceUID : '', + 0x0020000E: (studyInstanceUID && seriesInstanceUID) ? seriesInstanceUID : '', + 0x00080005: '', + 0x00080020: '', + 0x00080030: '', + 0x00080090: '', + 0x00100010: '', + 0x00100020: '', + 0x00200010: '', + 0x0008103E: '', + 0x00200011: '', + 0x00080016: '', // sopClassUid + 0x00080018: '', // sopInstanceUid + 0x00200013: '', // instanceNumber + 0x00280010: '', // rows + 0x00280011: '', // columns + 0x00280100: '', // bitsAllocated + 0x00280101: '', // bitsStored + 0x00280102: '', // highBit + 0x00280103: '', // pixelRepresentation + 0x00280004: '', // photometricInterpretation + 0x00280008: '', // numFrames + 0x00181063: '', // frameTime + 0x00281052: '', // rescaleIntercept + 0x00281053: '', // rescaleSlope + 0x00280002: '', // samplesPerPixel + 0x00201041: '', // sliceLocation + 0x00281050: '', // windowCenter + 0x00281051: '', // windowWidth + 0x00280030: '', // pixelSpacing + 0x00200062: '', // laterality + 0x00185101: '', // viewPosition + 0x00080008: '', // imageType + 0x00200032: '', // imagePositionPatient + 0x00200037: '', // imageOrientationPatient + 0x00200052: '', // frameOfReferenceUID + + // Orthanc has a bug here so we can't retrieve sequences at the moment + // https://groups.google.com/forum/#!topic/orthanc-users/ghKJfvtnK8Y + //0x00082112: '' // sourceImageSequence }; var result = this.findInstances(Object.assign(defaultParams, params)), o = this; @@ -183,16 +205,16 @@ DIMSE.storeInstances = function(fileList) { var handle = conn.storeInstances(fileList); handle.on('file', function(err, file) { console.log(err, file); - }) + }); }; DIMSE.moveInstances = function(studyInstanceUID, seriesInstanceUID, sopInstanceUID, sopClassUID, params) { DIMSE.associate([C.SOP_STUDY_ROOT_MOVE, sopClassUID], function() { var defaultParams = { - 0x0020000D: studyInstanceUID ? studyInstanceUID : "", - 0x0020000E: seriesInstanceUID ? seriesInstanceUID : "", - 0x00080018: sopInstanceUID ? sopInstanceUID : "" - } - this.moveInstances("OHIFDCM", Object.assign(defaultParams, params)); + 0x0020000D: studyInstanceUID ? studyInstanceUID : '', + 0x0020000E: seriesInstanceUID ? seriesInstanceUID : '', + 0x00080018: sopInstanceUID ? sopInstanceUID : '' + }; + this.moveInstances('OHIFDCM', Object.assign(defaultParams, params)); }); -} \ No newline at end of file +}; diff --git a/Packages/viewerbase/client/components/viewer/cineDialog/cineDialog.html b/Packages/viewerbase/client/components/viewer/cineDialog/cineDialog.html new file mode 100644 index 000000000..562617b8c --- /dev/null +++ b/Packages/viewerbase/client/components/viewer/cineDialog/cineDialog.html @@ -0,0 +1,42 @@ + diff --git a/Packages/viewerbase/client/components/viewer/cineDialog/cineDialog.js b/Packages/viewerbase/client/components/viewer/cineDialog/cineDialog.js new file mode 100644 index 000000000..b7723d912 --- /dev/null +++ b/Packages/viewerbase/client/components/viewer/cineDialog/cineDialog.js @@ -0,0 +1,78 @@ +toggleCinePlay = function() { + var element = getActiveViewportElement(); + var playClipToolData = cornerstoneTools.getToolState(element, 'playClip'); + + if (isPlaying()) { + cornerstoneTools.stopClip(element); + } else { + cornerstoneTools.playClip(element); + } +}; + +function updateFramerate(rate) { + OHIF.viewer.cine.framesPerSecond = rate; + + // Update playClip toolData for this imageId + var element = getActiveViewportElement(); + var playClipToolData = cornerstoneTools.getToolState(element, 'playClip'); + playClipToolData.data[0].framesPerSecond = OHIF.viewer.cine.framesPerSecond; + + // If the movie is playing, stop/start to update the framerate + if (isPlaying()) { + var element = getActiveViewportElement(); + cornerstoneTools.stopClip(element); + cornerstoneTools.playClip(element); + } + + Session.set('UpdateCine', Random.id()); +} + +Template.cineDialog.helpers({ + isPlaying: function() { + return isPlaying(); + }, + framerate: function() { + Session.get('UpdateCine'); + return OHIF.viewer.cine.framesPerSecond.toFixed(1); + } +}); + +Template.cineDialog.events({ + 'click #cineFirstButton': function() { + switchToImageByIndex(0); + }, + 'click #cineBackButton': function() { + switchToImageRelative(-1); + }, + 'click #cineSlowPlaybackButton': function() { + updateFramerate(OHIF.viewer.cine.framesPerSecond - 1); + }, + 'click #cinePlayButton': function() { + toggleCinePlay(); + }, + 'click #cineNextButton': function() { + switchToImageRelative(1); + }, + 'click #cineFastForwardButton': function() { + updateFramerate(OHIF.viewer.cine.framesPerSecond + 1); + }, + 'click #cineLastButton': function() { + switchToImageByIndex(-1); + }, + 'change #cineLoopCheckbox': function(e) { + var element = getActiveViewportElement(); + var playClipToolData = cornerstoneTools.getToolState(element, 'playClip'); + playClipToolData.data[0].loop = $(e.currentTarget).is(':checked'); + OHIF.viewer.cine.loop = playClipToolData.data[0].loop; + }, + 'input #cineSlider': function(e) { + // Update the FPS text onscreen + var rate = parseFloat($(e.currentTarget).val()); + updateFramerate(rate); + } +}); + +Template.cineDialog.onRendered(function() { + var dialog = $('#cineDialog'); + dialog.draggable(); +}); diff --git a/Packages/viewerbase/client/components/viewer/cineDialog/cineDialog.styl b/Packages/viewerbase/client/components/viewer/cineDialog/cineDialog.styl new file mode 100644 index 000000000..afd3a7424 --- /dev/null +++ b/Packages/viewerbase/client/components/viewer/cineDialog/cineDialog.styl @@ -0,0 +1,44 @@ +#cineDialog + display: none + z-index: 1000 + position: absolute + top: auto + left: auto + bottom: 3px + right: 3px + + overflow: hidden + padding: 10px + + width: 250px + height: 175px + + border: 1px black solid + border-radius: 3px + background: white + color: black + + box-sizing: border-box + + -webkit-user-select: none + -moz-user-select: none + -o-user-select: none + -ms-user-select: none + -khtml-user-select: none + user-select: none + + #cineButtons + padding-bottom: 10px + overflow: auto + + .cineButton + cursor: pointer + margin: 0 6px + text-decoration: none + + #cineOptions + padding: 5px 0 + overflow: auto + + #loopSection + padding-right: 12px \ No newline at end of file diff --git a/Packages/viewerbase/client/components/viewer/imageViewerViewport/imageViewerViewport.js b/Packages/viewerbase/client/components/viewer/imageViewerViewport/imageViewerViewport.js index a80462407..26b1bbd1e 100644 --- a/Packages/viewerbase/client/components/viewer/imageViewerViewport/imageViewerViewport.js +++ b/Packages/viewerbase/client/components/viewer/imageViewerViewport/imageViewerViewport.js @@ -180,6 +180,17 @@ function loadSeriesIntoViewport(data, templateData) { cornerstoneTools.clearToolState(element, 'stack'); cornerstoneTools.addToolState(element, 'stack', stack); + // Set the default CINE settings + var frameRate = 1000 / series.instances[0].frameTime; + var cineToolData = { + loop: OHIF.viewer.cine.loop, + framesPerSecond: frameRate || OHIF.viewer.cine.framesPerSecond + }; + cornerstoneTools.addToolState(element, 'playClip', cineToolData); + + // Autoplay datasets that have framerates set + cornerstoneTools.playClip(element); + // Enable mouse, mouseWheel, touch, and keyboard input on the element cornerstoneTools.mouseInput.enable(element); cornerstoneTools.touchInput.enable(element); @@ -224,6 +235,20 @@ function loadSeriesIntoViewport(data, templateData) { templateData.imageId = eventData.enabledElement.image.imageId; Session.set('CornerstoneNewImage' + viewportIndex, Random.id()); + // Get the element and stack data + var element = e.target; + var toolData = cornerstoneTools.getToolState(element, 'stack'); + if (!toolData || !toolData.data || !toolData.data.length) { + return; + } + + var stack = toolData.data[0]; + + // Update the imageSlider value + var currentOverlay = $(element).siblings('.imageViewerViewportOverlay'); + var currentImageSlider = currentOverlay.find('#imageSlider'); + currentImageSlider.val(stack.currentImageIdIndex + 1); + // If this viewport is displaying a stack of images, save the current image // index in the stack to the global ViewerData object, as well as the Meteor Session. var stack = cornerstoneTools.getToolState(element, 'stack'); diff --git a/Packages/viewerbase/client/components/viewer/playClipButton/playClipButton.html b/Packages/viewerbase/client/components/viewer/playClipButton/playClipButton.html index 6c5b8ac27..115f5369a 100644 --- a/Packages/viewerbase/client/components/viewer/playClipButton/playClipButton.html +++ b/Packages/viewerbase/client/components/viewer/playClipButton/playClipButton.html @@ -1,9 +1,14 @@ \ No newline at end of file diff --git a/Packages/viewerbase/client/components/viewer/playClipButton/playClipButton.js b/Packages/viewerbase/client/components/viewer/playClipButton/playClipButton.js index 4671e7cef..2a50a14db 100644 --- a/Packages/viewerbase/client/components/viewer/playClipButton/playClipButton.js +++ b/Packages/viewerbase/client/components/viewer/playClipButton/playClipButton.js @@ -1,41 +1,52 @@ toggleCinePlay = function(element) { + var viewports = $('.imageViewerViewport'); + if (!element) { - var activeViewport = Session.get('activeViewport'); - element = $('.imageViewerViewport').get(activeViewport); + var viewportIndex = Session.get('activeViewport'); + element = viewports.get(viewportIndex); } if (!element) { return; } - var viewportIndex = $('.imageViewerViewport').index(element); var isPlaying = OHIF.viewer.isPlaying[viewportIndex] || false; - if (isPlaying === true) { + if (isPlaying) { cornerstoneTools.stopClip(element); } else { cornerstoneTools.playClip(element); } + OHIF.viewer.isPlaying[viewportIndex] = !OHIF.viewer.isPlaying[viewportIndex]; Session.set('UpdateCINE', Random.id()); }; -Template.playClipButton.helpers({ - 'isPlaying': function() { - Session.get('UpdateCINE'); - var activeViewport = Session.get('activeViewport'); +isPlaying = function() { + Session.get('UpdateCINE'); + var activeViewport = Session.get('activeViewport'); - // TODO=Check best way to make sure this is always defined - // Right now it is initialized in enableHotkeys AND in - // imageViewer onCreated, but this appears to break some things - if (!OHIF.viewer.isPlaying) { - return; - } - return !!OHIF.viewer.isPlaying[activeViewport]; + // TODO=Check best way to make sure this is always defined + // Right now it is initialized in enableHotkeys AND in + // imageViewer onCreated, but this appears to break some things + if (!OHIF.viewer.isPlaying) { + return; + } + + return !!OHIF.viewer.isPlaying[activeViewport]; +}; + +Template.playClipButton.helpers({ + isPlaying: function() { + return isPlaying(); } }); Template.playClipButton.events({ 'click #playClip': function() { toggleCinePlay(); + }, + 'click #toggleCineDialog': function() { + var cineDialog = document.getElementById('cineDialog'); + toggleDialog(cineDialog); } -}); \ No newline at end of file +}); diff --git a/Packages/viewerbase/client/components/viewer/toolbar/toolbar.js b/Packages/viewerbase/client/components/viewer/toolbar/toolbar.js index 12539ff96..1d9711edc 100644 --- a/Packages/viewerbase/client/components/viewer/toolbar/toolbar.js +++ b/Packages/viewerbase/client/components/viewer/toolbar/toolbar.js @@ -133,6 +133,7 @@ Template.toolbar.events({ if (!OHIF.viewer.functionList.hasOwnProperty(command)) { return; } + var activeViewport = Session.get('activeViewport'); var element = $('.imageViewerViewport').get(activeViewport); OHIF.viewer.functionList[command](element); @@ -150,48 +151,53 @@ Template.toolbar.onRendered(function() { // Set disabled/enabled tool buttons that are set in toolManager var states = toolManager.getToolDefaultStates(); var disabledToolButtons = states.disabledToolButtons; - var allToolbarButtons = $("#toolbar").find("button"); + var allToolbarButtons = $('#toolbar').find('button'); if (disabledToolButtons.length > 0) { - for (var i=0; i < allToolbarButtons.length; i++) { + for (var i = 0; i < allToolbarButtons.length; i++) { var toolbarButton = allToolbarButtons[i]; - $(toolbarButton).prop("disabled", false); - var index = disabledToolButtons.indexOf($(toolbarButton).attr("id")); + $(toolbarButton).prop('disabled', false); + var index = disabledToolButtons.indexOf($(toolbarButton).attr('id')); if (index !== -1) { - $(toolbarButton).prop("disabled", true); + $(toolbarButton).prop('disabled', true); } } } }); Template.toolbar.helpers({ - 'button': function() { + button: function() { if (this.toolbarOptions && this.toolbarOptions.buttonData) { return this.toolbarOptions.buttonData; } + return getDefaultButtonData(); }, - 'includePlayClipButton': function() { + includePlayClipButton: function() { if (this.toolbarOptions && this.toolbarOptions.includePlayClipButton !== undefined) { return this.toolbarOptions.includePlayClipButton; } + return true; }, - 'includeLayoutButton': function() { + includeLayoutButton: function() { if (this.toolbarOptions && this.toolbarOptions.includeLayoutButton !== undefined) { return this.toolbarOptions.includeLayoutButton; } + return true; }, - 'includeHangingProtocolButtons': function() { + includeHangingProtocolButtons: function() { if (this.toolbarOptions && this.toolbarOptions.includeHangingProtocolButtons !== undefined) { return this.toolbarOptions.includeHangingProtocolButtons; } - return true; + + return false; }, - 'btnGroup': function() { + btnGroup: function() { if (this.toolbarOptions && this.toolbarOptions.btnGroup !== undefined) { return this.toolbarOptions.btnGroup; } + return []; } -}); \ No newline at end of file +}); diff --git a/Packages/viewerbase/client/components/viewer/viewportOverlay/viewportOverlay.html b/Packages/viewerbase/client/components/viewer/viewportOverlay/viewportOverlay.html index 52548f3a8..9a4095f6a 100644 --- a/Packages/viewerbase/client/components/viewer/viewportOverlay/viewportOverlay.html +++ b/Packages/viewerbase/client/components/viewer/viewportOverlay/viewportOverlay.html @@ -17,7 +17,7 @@
Ser: {{seriesNumber}}
Img: {{imageNumber}} ({{imageIndex}}/{{numImages}})
-
{{frameRate}}
+
{{#if frameRate}}{{frameRate}} FPS{{/if}}
{{imageDimensions}}
{{seriesDescription}}
diff --git a/Packages/viewerbase/client/components/viewer/viewportOverlay/viewportOverlay.js b/Packages/viewerbase/client/components/viewer/viewportOverlay/viewportOverlay.js index 4e7ee7657..48bb05c0f 100644 --- a/Packages/viewerbase/client/components/viewer/viewportOverlay/viewportOverlay.js +++ b/Packages/viewerbase/client/components/viewer/viewportOverlay/viewportOverlay.js @@ -157,6 +157,11 @@ Template.viewportOverlay.helpers({ seriesDescription: function() { return getSeries.call(this, 'seriesDescription'); }, + frameRate: function() { + var frameTime = getInstance.call(this, 'frameTime'); + var frameRate = 1000 / frameTime; + return frameRate.toFixed(1); + }, seriesNumber: function() { return getSeries.call(this, 'seriesNumber'); }, diff --git a/Packages/viewerbase/lib/draggable.js b/Packages/viewerbase/lib/draggable.js new file mode 100644 index 000000000..eaf0c70f2 --- /dev/null +++ b/Packages/viewerbase/lib/draggable.js @@ -0,0 +1,109 @@ +// Allow attaching to jQuery selectors +$.fn.draggable = function() { + makeDraggable(this); +}; + +/** + * This function makes an element movable around the page. + * It supports mouse and touch input and allows whichever element + * is specified to be moved to any arbitrary position. + * + * @param element + */ +makeDraggable = function(element) { + var container = $(window); + var diffX, + diffY; + + function getCursorCoords(e) { + var cursor = { + x: e.clientX, + y: e.clientY + }; + + // Handle touchMove cases + if (cursor.x === undefined) { + cursor.x = e.originalEvent.touches[0].pageX; + } + if (cursor.y === undefined) { + cursor.y = e.originalEvent.touches[0].pageY; + } + return cursor; + } + + function startMoving(e) { + // Prevent dragging dialog by clicking on slider + // (could be extended for buttons, not sure it's necessary + if (e.target.type && e.target.type === 'range') { + return; + } + + var elementTop = parseFloat(element.offset().top), + elementLeft = parseFloat(element.offset().left); + + var cursor = getCursorCoords(e); + diffX = cursor.x - elementLeft; + diffY = cursor.y - elementTop; + + container.css('cursor', 'move'); + element.css('cursor', 'move'); + + element.css({ + cursor: 'move', + left: elementLeft + 'px', + top: elementTop + 'px', + bottom: 'auto', // Setting these to empty doesn't seem to work in Firefox or Safari + right: 'auto' + }); + + $(document).on('mousemove', moveHandler); + $(document).on('mouseup', stopMoving); + + $(document).on('touchmove', moveHandler); + $(document).on('touchend', stopMoving); + } + + function stopMoving() { + container.css('cursor', 'default'); + element.css('cursor', 'default'); + + $(document).off('mousemove', moveHandler); + $(document).off('touchmove', moveHandler); + } + + function moveHandler(e) { + // Prevent dialog box dragging whole page in iOS + e.preventDefault(); + + var elementWidth = parseFloat(element.outerWidth()), + elementHeight = parseFloat(element.outerHeight()), + containerWidth = parseFloat(container.width()), + containerHeight = parseFloat(container.height()); + + var cursor = getCursorCoords(e); + + var elementLeft = cursor.x - diffX; + var elementTop = cursor.y - diffY; + + elementLeft = Math.max(elementLeft, 0); + elementTop = Math.max(elementTop, 0); + + if (elementLeft + elementWidth > containerWidth) { + elementLeft = containerWidth - elementWidth; + } + + if (elementTop + elementHeight > containerHeight) { + elementTop = containerHeight - elementHeight; + } + + element.css({ + left: elementLeft + 'px', + top: elementTop + 'px', + bottom: 'auto', // Setting these to empty doesn't seem to work in Firefox or Safari + right: 'auto' + }); + } + + element.on('mousedown', startMoving); + element.on('touchstart', startMoving); +}; \ No newline at end of file diff --git a/Packages/viewerbase/lib/metaDataProvider.js b/Packages/viewerbase/lib/metaDataProvider.js index 29103573b..2056707eb 100644 --- a/Packages/viewerbase/lib/metaDataProvider.js +++ b/Packages/viewerbase/lib/metaDataProvider.js @@ -47,6 +47,7 @@ addMetaData = function(imageId, data) { laterality: instanceMetaData.laterality, viewPosition: instanceMetaData.viewPosition, sliceThickness: instanceMetaData.sliceThickness, + frameTime: instanceMetaData.frameTime, index: imageIndex }; diff --git a/Packages/viewerbase/lib/toggleDialog.js b/Packages/viewerbase/lib/toggleDialog.js new file mode 100644 index 000000000..3d68a00f6 --- /dev/null +++ b/Packages/viewerbase/lib/toggleDialog.js @@ -0,0 +1,8 @@ +toggleDialog = function(element) { + var elem = $(element); + if (elem.css('display') === 'none') { + elem.css('display', 'block'); + } else { + elem.css('display', 'none'); + } +}; \ No newline at end of file diff --git a/Packages/viewerbase/package.js b/Packages/viewerbase/package.js index 12baa7486..181cd5173 100644 --- a/Packages/viewerbase/package.js +++ b/Packages/viewerbase/package.js @@ -96,6 +96,10 @@ Package.onUse(function(api) { api.addFiles('client/components/viewer/layoutChooser/layoutChooser.js', 'client'); api.addFiles('client/components/viewer/layoutChooser/layoutChooser.styl', 'client'); + api.addFiles('client/components/viewer/cineDialog/cineDialog.html', 'client'); + api.addFiles('client/components/viewer/cineDialog/cineDialog.js', 'client'); + api.addFiles('client/components/viewer/cineDialog/cineDialog.styl', 'client'); + api.addFiles('client/components/viewer/simpleToolbarButton/simpleToolbarButton.html', 'client'); api.addFiles('client/components/viewer/playClipButton/playClipButton.html', 'client'); @@ -131,6 +135,8 @@ Package.onUse(function(api) { api.addFiles('lib/windowManager.js', 'client'); api.addFiles('lib/enablePrefetchOnElement.js', 'client'); api.addFiles('lib/displayReferenceLines.js', 'client'); + api.addFiles('lib/draggable.js', 'client'); + api.addFiles('lib/toggleDialog.js', 'client'); api.addFiles('lib/setActiveViewport.js', 'client'); api.addFiles('lib/switchToImageByIndex.js', 'client'); api.addFiles('lib/switchToImageRelative.js', 'client'); @@ -160,6 +166,7 @@ Package.onUse(function(api) { api.export('setFocusToActiveViewport', 'client'); api.export('updateAllViewports', 'client'); api.export('exportStudies', 'client'); + api.export('getActiveViewportElement', 'client'); api.export('encodeQueryData', 'server'); // Viewer management objects diff --git a/Packages/viewerbase/server/services/dimse/retrieveMetadata.js b/Packages/viewerbase/server/services/dimse/retrieveMetadata.js index 9a2520f6b..6fdb4c637 100755 --- a/Packages/viewerbase/server/services/dimse/retrieveMetadata.js +++ b/Packages/viewerbase/server/services/dimse/retrieveMetadata.js @@ -69,6 +69,8 @@ function resultDataToStudyMetadata(studyInstanceUid, resultData) { var sopInstanceUid = instance[0x00080018]; + console.log(instance); + var instanceSummary = { imageType: instance[0x00080008], sopClassUid: instance[0x00080016], @@ -95,7 +97,7 @@ function resultDataToStudyMetadata(studyInstanceUid, resultData) { laterality: instance[0x00200062], viewPosition: instance[0x00185101], numFrames: parseFloat(instance[0x00280008]), - frameRate: parseFloat(instance[0x00181063]) + frameTime: parseFloat(instance[0x00181063]) }; // Retrieve the actual data over WADO-URI diff --git a/Packages/viewerbase/server/services/dimse/studies.js b/Packages/viewerbase/server/services/dimse/studies.js index 4ebb3d204..f3940c8c2 100755 --- a/Packages/viewerbase/server/services/dimse/studies.js +++ b/Packages/viewerbase/server/services/dimse/studies.js @@ -30,7 +30,6 @@ function resultDataToStudies(resultData) { return studies; } - Services.DIMSE.Studies = function(filter) { log.info('Services.DIMSE.Studies'); var parameters = { @@ -38,11 +37,11 @@ Services.DIMSE.Studies = function(filter) { 0x00100020: filter.patientId, 0x00080050: filter.accessionNumber, 0x00081030: filter.studyDescription, - 0x00100040: "", - 0x00201208: "", - 0x00080061: "" + 0x00100040: '', + 0x00201208: '', + 0x00080061: '' }; var results = DIMSE.retrieveStudies(parameters); return resultDataToStudies(results); -}; \ No newline at end of file +}; diff --git a/Packages/viewerbase/server/services/remote/retrieveMetadata.js b/Packages/viewerbase/server/services/remote/retrieveMetadata.js index 3acc10184..abcb14310 100755 --- a/Packages/viewerbase/server/services/remote/retrieveMetadata.js +++ b/Packages/viewerbase/server/services/remote/retrieveMetadata.js @@ -94,7 +94,7 @@ function resultDataToStudyMetadata(server, studyInstanceUid, resultData) { laterality: remoteGetValue(instance['0020,0062']), viewPosition: remoteGetValue(instance['0018,5101']), numFrames: parseFloat(remoteGetValue(instance['0028,0008'])), - frameRate: parseFloat(remoteGetValue(instance['0018,1063'])) + frameTime: parseFloat(remoteGetValue(instance['0018,1063'])) }; var iid = instance['xxxx,0001'].Value; diff --git a/Packages/viewerbase/server/services/wado/retrieveMetadata.js b/Packages/viewerbase/server/services/wado/retrieveMetadata.js index 11508ebfd..2b64afbb5 100644 --- a/Packages/viewerbase/server/services/wado/retrieveMetadata.js +++ b/Packages/viewerbase/server/services/wado/retrieveMetadata.js @@ -105,7 +105,7 @@ function resultDataToStudyMetadata(server, studyInstanceUid, resultData) { laterality: DICOMWeb.getString(instance['00200062']), viewPosition: DICOMWeb.getString(instance['00185101']), numFrames: DICOMWeb.getNumber(instance['00280008']), - frameRate: DICOMWeb.getNumber(instance['00181063']), + frameTime: DICOMWeb.getNumber(instance['00181063']), sliceThickness: DICOMWeb.getNumber(instance['00180050']) };