OHIF-27: PlayClipController
This commit is contained in:
parent
ec333118d3
commit
72c08147d4
@ -76,6 +76,10 @@ export class MetadataProvider {
|
|||||||
// the imagePlane object for easier use in the Viewer
|
// the imagePlane object for easier use in the Viewer
|
||||||
metadata.imagePlane = this.getImagePlane(instanceMetadata);
|
metadata.imagePlane = this.getImagePlane(instanceMetadata);
|
||||||
|
|
||||||
|
if (metadata.instance && !metadata.instance.multiframeMetadata) {
|
||||||
|
metadata.instance.multiframeMetadata = this.getMultiframeModuleMetadata(data);
|
||||||
|
}
|
||||||
|
|
||||||
// Add the metadata to the imageId lookup object
|
// Add the metadata to the imageId lookup object
|
||||||
this.metadataLookup.set(imageId, metadata);
|
this.metadataLookup.set(imageId, metadata);
|
||||||
}
|
}
|
||||||
@ -110,8 +114,10 @@ export class MetadataProvider {
|
|||||||
|
|
||||||
if(image.data) {
|
if(image.data) {
|
||||||
value = this.getFromDataSet(image.data, type, tag);
|
value = this.getFromDataSet(image.data, type, tag);
|
||||||
} else {
|
} else if(image.instance) {
|
||||||
value = image.instance[attrName];
|
value = image.instance[attrName];
|
||||||
|
} else {
|
||||||
|
value = image[attrName];
|
||||||
}
|
}
|
||||||
|
|
||||||
return value == null ? defaultValue : value;
|
return value == null ? defaultValue : value;
|
||||||
@ -208,7 +214,7 @@ export class MetadataProvider {
|
|||||||
imageMetadata.instance.frameTime = imageMetadata.instance.frameTime || this.getFromDataSet(image.data, 'string', 'x00181063');
|
imageMetadata.instance.frameTime = imageMetadata.instance.frameTime || this.getFromDataSet(image.data, 'string', 'x00181063');
|
||||||
imageMetadata.instance.frameTimeVector = imageMetadata.instance.frameTimeVector || this.getFromDataSet(image.data, 'string', 'x00181065');
|
imageMetadata.instance.frameTimeVector = imageMetadata.instance.frameTimeVector || this.getFromDataSet(image.data, 'string', 'x00181065');
|
||||||
|
|
||||||
if ((image.data || image.instance) && !imageMetadata.instance.multiframeMetadata) {
|
if (!imageMetadata.instance.multiframeMetadata) {
|
||||||
imageMetadata.instance.multiframeMetadata = this.getMultiframeModuleMetadata(image);
|
imageMetadata.instance.multiframeMetadata = this.getMultiframeModuleMetadata(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -118,7 +118,7 @@ Template.imageThumbnail.helpers({
|
|||||||
const instance = Template.instance();
|
const instance = Template.instance();
|
||||||
const stack = instance.data.thumbnail.stack;
|
const stack = instance.data.thumbnail.stack;
|
||||||
const displaySetInstanceUid = stack.displaySetInstanceUid;
|
const displaySetInstanceUid = stack.displaySetInstanceUid;
|
||||||
const progress = Session.get('DisplaySetProgress:' + displaySetInstanceUid);
|
const progress = Session.get('StackProgress:' + displaySetInstanceUid);
|
||||||
const percentComplete = progress && progress.percentComplete;
|
const percentComplete = progress && progress.percentComplete;
|
||||||
|
|
||||||
return percentComplete;
|
return percentComplete;
|
||||||
|
|||||||
@ -274,6 +274,7 @@ Template.cineDialog.events({
|
|||||||
|
|
||||||
Template.cineDialog.helpers({
|
Template.cineDialog.helpers({
|
||||||
isPlaying() {
|
isPlaying() {
|
||||||
|
Session.get('UpdateCINE');
|
||||||
return viewportUtils.isPlaying();
|
return viewportUtils.isPlaying();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import { updateCrosshairsSynchronizer } from '../../../lib/updateCrosshairsSynch
|
|||||||
import { toolManager } from '../../../lib/toolManager';
|
import { toolManager } from '../../../lib/toolManager';
|
||||||
import { updateOrientationMarkers } from '../../../lib/updateOrientationMarkers';
|
import { updateOrientationMarkers } from '../../../lib/updateOrientationMarkers';
|
||||||
import { getInstanceClassDefaultViewport } from '../../../lib/instanceClassSpecificViewport';
|
import { getInstanceClassDefaultViewport } from '../../../lib/instanceClassSpecificViewport';
|
||||||
|
import { PlayClipManager } from '../../../lib/classes/PlayClipManager';
|
||||||
import { OHIFError } from '../../../lib/classes/OHIFError';
|
import { OHIFError } from '../../../lib/classes/OHIFError';
|
||||||
|
|
||||||
const allCornerstoneEvents = 'CornerstoneToolsMouseDown CornerstoneToolsMouseDownActivate ' +
|
const allCornerstoneEvents = 'CornerstoneToolsMouseDown CornerstoneToolsMouseDownActivate ' +
|
||||||
@ -259,9 +260,12 @@ const loadDisplaySetIntoViewport = (data, templateData) => {
|
|||||||
|
|
||||||
cornerstoneTools.addToolState(element, 'playClip', cineToolData);
|
cornerstoneTools.addToolState(element, 'playClip', cineToolData);
|
||||||
|
|
||||||
|
const playClipManager = PlayClipManager.getInstance();
|
||||||
|
const playClipController = playClipManager.add(element, displaySet);
|
||||||
|
|
||||||
// Autoplay datasets that have framerates set
|
// Autoplay datasets that have framerates set
|
||||||
if (multiframeMetadata && multiframeMetadata.isMultiframeImage && multiframeMetadata.averageFrameRate > 0) {
|
if (multiframeMetadata && multiframeMetadata.isMultiframeImage && multiframeMetadata.averageFrameRate > 0) {
|
||||||
cornerstoneTools.playClip(element);
|
playClipController.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable mouse, mouseWheel, touch, and keyboard input on the element
|
// Enable mouse, mouseWheel, touch, and keyboard input on the element
|
||||||
@ -587,6 +591,9 @@ Template.imageViewerViewport.onDestroyed(function() {
|
|||||||
// for compatibility with other systems
|
// for compatibility with other systems
|
||||||
$element.trigger('OHIFDestroyedViewport');
|
$element.trigger('OHIFDestroyedViewport');
|
||||||
|
|
||||||
|
// Disable the PlayClipController for the current element
|
||||||
|
PlayClipManager.getInstance().remove(element);
|
||||||
|
|
||||||
// Disable the viewport element with Cornerstone
|
// Disable the viewport element with Cornerstone
|
||||||
// This also triggers the removal of the element from all available
|
// This also triggers the removal of the element from all available
|
||||||
// synchronizers, such as the one used for reference lines.
|
// synchronizers, such as the one used for reference lines.
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import { hotkeyUtils } from '../../../lib/hotkeyUtils';
|
|||||||
import { ResizeViewportManager } from '../../../lib/classes/ResizeViewportManager';
|
import { ResizeViewportManager } from '../../../lib/classes/ResizeViewportManager';
|
||||||
import { LayoutManager } from '../../../lib/classes/LayoutManager';
|
import { LayoutManager } from '../../../lib/classes/LayoutManager';
|
||||||
import { StudyPrefetcher } from '../../../lib/classes/StudyPrefetcher';
|
import { StudyPrefetcher } from '../../../lib/classes/StudyPrefetcher';
|
||||||
|
import { StudyLoadingListener } from '../../../lib/classes/StudyLoadingListener';
|
||||||
|
import { PlayClipManager } from '../../../lib/classes/PlayClipManager';
|
||||||
|
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
window.ResizeViewportManager = window.ResizeViewportManager || new ResizeViewportManager();
|
window.ResizeViewportManager = window.ResizeViewportManager || new ResizeViewportManager();
|
||||||
@ -36,9 +38,13 @@ Template.viewerMain.onRendered(() => {
|
|||||||
const { studies } = instance.data;
|
const { studies } = instance.data;
|
||||||
const parentElement = instance.$('#layoutManagerTarget').get(0);
|
const parentElement = instance.$('#layoutManagerTarget').get(0);
|
||||||
const studyPrefetcher = StudyPrefetcher.getInstance();
|
const studyPrefetcher = StudyPrefetcher.getInstance();
|
||||||
|
const studyLoadingListener = StudyLoadingListener.getInstance();
|
||||||
|
|
||||||
OHIF.viewerbase.studyLoadingListener.clear();
|
studyLoadingListener.clear();
|
||||||
OHIF.viewerbase.studyLoadingListener.addStudies(studies);
|
studyLoadingListener.addStudies(studies);
|
||||||
|
|
||||||
|
// Remove all PlayClipControllers from PlayClipManager
|
||||||
|
PlayClipManager.getInstance().clear();
|
||||||
|
|
||||||
OHIF.viewerbase.layoutManager = new LayoutManager(parentElement, studies);
|
OHIF.viewerbase.layoutManager = new LayoutManager(parentElement, studies);
|
||||||
studyPrefetcher.setStudies(studies);
|
studyPrefetcher.setStudies(studies);
|
||||||
|
|||||||
@ -157,10 +157,6 @@ Viewerbase.createStacks = createStacks;
|
|||||||
import { StackManager } from './lib/StackManager';
|
import { StackManager } from './lib/StackManager';
|
||||||
Viewerbase.stackManager = StackManager;
|
Viewerbase.stackManager = StackManager;
|
||||||
|
|
||||||
// studyLoadingListener
|
|
||||||
import { studyLoadingListener } from './lib/studyLoadingListener';
|
|
||||||
Viewerbase.studyLoadingListener = studyLoadingListener;
|
|
||||||
|
|
||||||
// toolManager
|
// toolManager
|
||||||
import { toolManager } from './lib/toolManager';
|
import { toolManager } from './lib/toolManager';
|
||||||
Viewerbase.toolManager = toolManager;
|
Viewerbase.toolManager = toolManager;
|
||||||
@ -204,6 +200,14 @@ Viewerbase.StudyPrefetcher = StudyPrefetcher;
|
|||||||
import { ResizeViewportManager } from './lib/classes/ResizeViewportManager';
|
import { ResizeViewportManager } from './lib/classes/ResizeViewportManager';
|
||||||
Viewerbase.ResizeViewportManager = ResizeViewportManager;
|
Viewerbase.ResizeViewportManager = ResizeViewportManager;
|
||||||
|
|
||||||
|
// StudyLoadingListener
|
||||||
|
import { StudyLoadingListener } from './lib/classes/StudyLoadingListener';
|
||||||
|
Viewerbase.StudyLoadingListener = StudyLoadingListener;
|
||||||
|
|
||||||
|
// PlayClipManager
|
||||||
|
import { PlayClipManager } from './lib/classes/PlayClipManager';
|
||||||
|
Viewerbase.PlayClipManager = PlayClipManager;
|
||||||
|
|
||||||
// StudyMetadata, SeriesMetadata, InstanceMetadata
|
// StudyMetadata, SeriesMetadata, InstanceMetadata
|
||||||
import { StudyMetadata } from './lib/classes/metadata/StudyMetadata';
|
import { StudyMetadata } from './lib/classes/metadata/StudyMetadata';
|
||||||
import { SeriesMetadata } from './lib/classes/metadata/SeriesMetadata';
|
import { SeriesMetadata } from './lib/classes/metadata/SeriesMetadata';
|
||||||
|
|||||||
306
Packages/ohif-viewerbase/client/lib/classes/PlayClipManager.js
Normal file
306
Packages/ohif-viewerbase/client/lib/classes/PlayClipManager.js
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
import { $ } from 'meteor/jquery';
|
||||||
|
import { Tracker } from 'meteor/tracker';
|
||||||
|
import { Session } from 'meteor/session';
|
||||||
|
import { OHIF } from 'meteor/ohif:core';
|
||||||
|
|
||||||
|
class PlayClipController {
|
||||||
|
constructor(element, displaySet) {
|
||||||
|
this._isPlaying = false;
|
||||||
|
this.element = element;
|
||||||
|
this.displaySet = displaySet;
|
||||||
|
this._playComputation = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getStackProgress() {
|
||||||
|
return Session.get('StackProgress:' + this.displaySet.displaySetInstanceUid);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getToolState(toolType) {
|
||||||
|
const toolState = cornerstoneTools.getToolState(this.element, toolType);
|
||||||
|
if (toolState && toolState.data) {
|
||||||
|
return toolState.data[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_countFramesByStatus(stackProgress, startIndex, endIndex, status) {
|
||||||
|
const framesStatus = stackProgress.framesStatus;
|
||||||
|
const framesCount = framesStatus.length;
|
||||||
|
let currentIndex = startIndex;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (framesStatus[currentIndex] === status) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(currentIndex === endIndex) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentIndex = (currentIndex + 1) % framesCount;
|
||||||
|
} while (true)
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isLoopEnabled() {
|
||||||
|
const playClipToolState = this._getToolState('playClip');
|
||||||
|
return playClipToolState && playClipToolState.loop;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getCurrentIndex() {
|
||||||
|
const stackToolState = this._getToolState('stack');
|
||||||
|
return stackToolState.currentImageIdIndex || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isLastFrame() {
|
||||||
|
const stackToolState = this._getToolState('stack');
|
||||||
|
const currentIndex = (stackToolState && stackToolState.currentImageIdIndex) || 0;
|
||||||
|
const imagesCount = stackToolState ? stackToolState.imageIds.length : 0;
|
||||||
|
|
||||||
|
return (imagesCount === 0) || (currentIndex === imagesCount - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getFrameDistance(startIndex, endIndex) {
|
||||||
|
const framesCount = this.displaySet.numImageFrames;
|
||||||
|
return startIndex <= endIndex ? endIndex - startIndex : framesCount - (startIndex - endIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getLastRelativeFrame(startFrame) {
|
||||||
|
const numImageFrames = this.displaySet.numImageFrames;
|
||||||
|
|
||||||
|
if (this._isLoopEnabled()) {
|
||||||
|
// Easiest way to get the previous index without having to check if it's negative
|
||||||
|
return (startFrame + numImageFrames - 1) % numImageFrames;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the last index of the array
|
||||||
|
return numImageFrames - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
isPlaying() {
|
||||||
|
return this._isPlaying;
|
||||||
|
}
|
||||||
|
|
||||||
|
play() {
|
||||||
|
if (this._isPlaying) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._isPlaying = true;
|
||||||
|
|
||||||
|
const playClipToolState = this._getToolState('playClip');
|
||||||
|
const startIndex = this._getCurrentIndex();
|
||||||
|
const endIndex = this._getLastRelativeFrame(startIndex);
|
||||||
|
const framesToPlay = this._getFrameDistance(startIndex, endIndex);
|
||||||
|
const playTimeRemaining = framesToPlay / playClipToolState.framesPerSecond;
|
||||||
|
|
||||||
|
const play = playComputation => {
|
||||||
|
playComputation.stop();
|
||||||
|
|
||||||
|
try {
|
||||||
|
cornerstoneTools.playClip(this.element);
|
||||||
|
} catch(e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._playComputation = Tracker.autorun(playComputation => {
|
||||||
|
const stackProgress = this._getStackProgress();
|
||||||
|
|
||||||
|
if (!stackProgress) {
|
||||||
|
return;
|
||||||
|
} else if (!stackProgress.multiFrame) {
|
||||||
|
play(playComputation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const framesToLoad = this._countFramesByStatus(stackProgress, startIndex, endIndex, false);
|
||||||
|
const loadTimeRemaining = framesToLoad / stackProgress.framesPerSecond;
|
||||||
|
|
||||||
|
if ((stackProgress.percentComplete === 100) ||
|
||||||
|
(loadTimeRemaining < playTimeRemaining)) {
|
||||||
|
play(playComputation);
|
||||||
|
this._attachNewImageListener();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the UpdateCINE session property
|
||||||
|
Session.set('UpdateCINE', Random.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
if (!this._isPlaying) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._playComputation) {
|
||||||
|
this._playComputation.stop();
|
||||||
|
this._playComputation = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
cornerstoneTools.stopClip(this.element);
|
||||||
|
} catch(e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._isPlaying = false;
|
||||||
|
this._detachNewImageListener();
|
||||||
|
|
||||||
|
// Update the UpdateCINE session property
|
||||||
|
Session.set('UpdateCINE', Random.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
togglePlay() {
|
||||||
|
if(this._isPlaying) {
|
||||||
|
this.stop();
|
||||||
|
} else {
|
||||||
|
this.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_isNextFrameLoaded() {
|
||||||
|
const stackProgress = this._getStackProgress();
|
||||||
|
|
||||||
|
if (!stackProgress || !stackProgress.framesStatus) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stackToolState = this._getToolState('stack');
|
||||||
|
const currentImageIdIndex = stackToolState.currentImageIdIndex || 0;
|
||||||
|
const isLoopEnabled = this._isLoopEnabled();
|
||||||
|
|
||||||
|
let nextImageIndex = currentImageIdIndex + 1;
|
||||||
|
|
||||||
|
if ((nextImageIndex >= stackToolState.imageIds.length) && isLoopEnabled) {
|
||||||
|
nextImageIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stackProgress.framesStatus && stackProgress.framesStatus[nextImageIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
_newImageEventHandler(e, eventData) {
|
||||||
|
if (this._isLastFrame() && !this._isLoopEnabled()) {
|
||||||
|
return this.stop();
|
||||||
|
} else if (!this._isNextFrameLoaded()) {
|
||||||
|
// Stop the clip and play it again in order to wait for the next frames (buffer)
|
||||||
|
this.stop();
|
||||||
|
this.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_attachNewImageListener() {
|
||||||
|
this._detachNewImageListener();
|
||||||
|
$(this.element).on('CornerstoneNewImage.PlayClipManager', this._newImageEventHandler.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
_detachNewImageListener() {
|
||||||
|
$(this.element).off('CornerstoneNewImage.PlayClipManager');
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PlayClipManager {
|
||||||
|
constructor() {
|
||||||
|
this.elementControllers = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
add(element, displaySet) {
|
||||||
|
const playClipController = new PlayClipController(element, displaySet);
|
||||||
|
|
||||||
|
// Remove any other PlayClipController for this `element`
|
||||||
|
this.remove(element);
|
||||||
|
|
||||||
|
// Add the new PlayClipController for this `element`
|
||||||
|
this.elementControllers.push({
|
||||||
|
element,
|
||||||
|
playClipController
|
||||||
|
});
|
||||||
|
|
||||||
|
return playClipController;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove (element) {
|
||||||
|
const index = this._indexOf(element);
|
||||||
|
|
||||||
|
if (index === -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const elementController = this.elementControllers[index];
|
||||||
|
const playClipController = elementController.playClipController;
|
||||||
|
|
||||||
|
playClipController.destroy();
|
||||||
|
this.elementControllers.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
get (element) {
|
||||||
|
const index = this._indexOf(element);
|
||||||
|
|
||||||
|
if (index >= 0) {
|
||||||
|
const elementController = this.elementControllers[index];
|
||||||
|
return elementController.playClipController;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_indexOf(element) {
|
||||||
|
const elementControllers = this.elementControllers;
|
||||||
|
const length = elementControllers.length;
|
||||||
|
|
||||||
|
for(let i = 0; i < length; i++) {
|
||||||
|
if (elementControllers[i].element === element) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
_forEach(callback) {
|
||||||
|
const elementControllers = this.elementControllers;
|
||||||
|
const length = elementControllers.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const elementControllers = this.elementControllers[i];
|
||||||
|
const element = elementControllers.element;
|
||||||
|
const playClipController = elementControllers.playClipController;
|
||||||
|
|
||||||
|
callback(element, playClipController);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
playAll() {
|
||||||
|
this._forEach((element, playClipController) => {
|
||||||
|
playClipController.play();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stopAll() {
|
||||||
|
this._forEach((element, playClipController) => {
|
||||||
|
playClipController.stop();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this._forEach((element, playClipController) => {
|
||||||
|
playClipController.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.elementControllers = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Singleton
|
||||||
|
static getInstance() {
|
||||||
|
if (!PlayClipManager._instance) {
|
||||||
|
PlayClipManager._instance = new PlayClipManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlayClipManager._instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { PlayClipManager };
|
||||||
@ -2,15 +2,59 @@ import { $ } from 'meteor/jquery';
|
|||||||
import { Session } from 'meteor/session';
|
import { Session } from 'meteor/session';
|
||||||
|
|
||||||
class BaseLoadingListener {
|
class BaseLoadingListener {
|
||||||
constructor(stack) {
|
constructor(stack, options) {
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
this.id = BaseLoadingListener.getNewId();
|
this.id = BaseLoadingListener.getNewId();
|
||||||
this.stack = stack;
|
this.stack = stack;
|
||||||
this.startListening();
|
this.startListening();
|
||||||
|
this.statsItemsLimit = options.statsItemsLimit || 2;
|
||||||
|
this.stats = {
|
||||||
|
items: [],
|
||||||
|
total: 0,
|
||||||
|
elapsedTime: 0,
|
||||||
|
speed: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register the start point to make it possible to calculate
|
||||||
|
// bytes/s or frames/s when the first byte or frame is received
|
||||||
|
this._addStatsData(0);
|
||||||
|
|
||||||
|
// Update the progress before starting the download
|
||||||
|
// to make it possible to update the UI
|
||||||
|
this._updateProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
_addStatsData(value) {
|
||||||
|
const date = new Date();
|
||||||
|
const stats = this.stats;
|
||||||
|
const items = stats.items;
|
||||||
|
const newItem = {
|
||||||
|
value,
|
||||||
|
date
|
||||||
|
};
|
||||||
|
|
||||||
|
items.push(newItem);
|
||||||
|
stats.total += newItem.value;
|
||||||
|
|
||||||
|
// Remove items until it gets below the limit
|
||||||
|
while (items.length > this.statsItemsLimit) {
|
||||||
|
const item = items.shift();
|
||||||
|
stats.total -= item.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the elapsedTime (seconds) based on first and last
|
||||||
|
// elements and recalculate the speed (bytes/s or frames/s)
|
||||||
|
if (items.length > 1) {
|
||||||
|
const oldestItem = items[0];
|
||||||
|
stats.elapsedTime = (newItem.date.getTime() - oldestItem.date.getTime()) / 1000;
|
||||||
|
stats.speed = (stats.total - oldestItem.value) / stats.elapsedTime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_getProgressSessionId() {
|
_getProgressSessionId() {
|
||||||
const displaySetInstanceUid = this.stack.displaySetInstanceUid;
|
const displaySetInstanceUid = this.stack.displaySetInstanceUid;
|
||||||
return 'DisplaySetProgress:' + displaySetInstanceUid;
|
return 'StackProgress:' + displaySetInstanceUid;
|
||||||
}
|
}
|
||||||
|
|
||||||
_clearSession() {
|
_clearSession() {
|
||||||
@ -44,6 +88,7 @@ class DICOMFileLoadingListener extends BaseLoadingListener {
|
|||||||
constructor(stack) {
|
constructor(stack) {
|
||||||
super(stack);
|
super(stack);
|
||||||
this.dataSetUrls = this._getDataSetUrls(stack);
|
this.dataSetUrls = this._getDataSetUrls(stack);
|
||||||
|
this._lastLoaded = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getImageLoadProgressEventName() {
|
_getImageLoadProgressEventName() {
|
||||||
@ -66,21 +111,32 @@ class DICOMFileLoadingListener extends BaseLoadingListener {
|
|||||||
|
|
||||||
_imageLoadProgressEventHandle(e, eventData) {
|
_imageLoadProgressEventHandle(e, eventData) {
|
||||||
const dataSetUrl = this._getDataSetUrl(eventData.imageId);
|
const dataSetUrl = this._getDataSetUrl(eventData.imageId);
|
||||||
|
const bytesDiff = eventData.loaded - this._lastLoaded;
|
||||||
|
|
||||||
if (!this.dataSetUrls.has(dataSetUrl)) {
|
if (!this.dataSetUrls.has(dataSetUrl)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._triggerProgress(eventData);
|
// Add the bytes downloaded to the stats
|
||||||
|
this._addStatsData(bytesDiff);
|
||||||
|
|
||||||
|
// Update the download progress
|
||||||
|
this._updateProgress(eventData);
|
||||||
|
|
||||||
|
// Cache the last eventData.loaded value
|
||||||
|
this._lastLoaded = eventData.loaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
_triggerProgress(eventData) {
|
_updateProgress(eventData) {
|
||||||
const progressSessionId = this._getProgressSessionId();
|
const progressSessionId = this._getProgressSessionId();
|
||||||
|
eventData = eventData || {};
|
||||||
|
|
||||||
Session.set(progressSessionId, {
|
Session.set(progressSessionId, {
|
||||||
|
multiFrame: false,
|
||||||
percentComplete: eventData.percentComplete,
|
percentComplete: eventData.percentComplete,
|
||||||
bytesLoaded: eventData.loaded,
|
bytesLoaded: eventData.loaded,
|
||||||
bytesTotal: eventData.total
|
bytesTotal: eventData.total,
|
||||||
|
bytesPerSecond: this.stats.speed
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,9 +166,35 @@ class DICOMFileLoadingListener extends BaseLoadingListener {
|
|||||||
|
|
||||||
class StackLoadingListener extends BaseLoadingListener {
|
class StackLoadingListener extends BaseLoadingListener {
|
||||||
constructor(stack) {
|
constructor(stack) {
|
||||||
super(stack);
|
super(stack, { statsItemsLimit: 20 });
|
||||||
this.remainingImageIds = stack.imageIds.slice();
|
this.imageDataMap = this._convertImageIdsArrayToMap(stack.imageIds);
|
||||||
this.loadedImageIds = [];
|
this.framesStatus = this._createArray(stack.imageIds.length, false);
|
||||||
|
this.loadedCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_convertImageIdsArrayToMap(imageIds) {
|
||||||
|
const imageIdsMap = new Map();
|
||||||
|
|
||||||
|
for (let i = 0; i < imageIds.length; i++) {
|
||||||
|
imageIdsMap.set(imageIds[i], {
|
||||||
|
index: i,
|
||||||
|
loaded: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageIdsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
_createArray(length, defaultValue) {
|
||||||
|
// `new Array(length)` is an anti-pattern in javascript because its
|
||||||
|
// funny API. Otherwise I would go for `new Array(length).fill(false)`
|
||||||
|
const array = new Array();
|
||||||
|
|
||||||
|
for(let i = 0; i < length; i++) {
|
||||||
|
array[i] = defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getImageLoadedEventName() {
|
_getImageLoadedEventName() {
|
||||||
@ -143,42 +225,65 @@ class StackLoadingListener extends BaseLoadingListener {
|
|||||||
$(cornerstone).off(imageCachePromiseRemovedEventName);
|
$(cornerstone).off(imageCachePromiseRemovedEventName);
|
||||||
}
|
}
|
||||||
|
|
||||||
_moveImageId(imageId, srcImageIds, dstImageIds) {
|
_updateImageStatus(imageId, loaded) {
|
||||||
const imageIdIndex = srcImageIds.indexOf(imageId);
|
const imageData = this.imageDataMap.get(imageId);
|
||||||
|
|
||||||
if (imageIdIndex === -1) {
|
if (!imageData || (imageData.loaded === loaded)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dstImageIds.push(imageId);
|
// Add one more frame to the stats
|
||||||
srcImageIds.splice(imageIdIndex, 1);
|
if(loaded) {
|
||||||
this._triggerProgress();
|
this._addStatsData(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
imageData.loaded = loaded;
|
||||||
|
this.framesStatus[imageData.index] = loaded;
|
||||||
|
this.loadedCount += loaded ? 1 : -1;
|
||||||
|
this._updateProgress();
|
||||||
}
|
}
|
||||||
|
|
||||||
_imageLoadedEventHandle(e, eventData) {
|
_imageLoadedEventHandle(e, eventData) {
|
||||||
const imageId = eventData.image.imageId;
|
const imageId = eventData.image.imageId;
|
||||||
this._moveImageId(imageId, this.remainingImageIds, this.loadedImageIds);
|
this._updateImageStatus(imageId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
_imageCachePromiseRemovedEventHandle(e, eventData) {
|
_imageCachePromiseRemovedEventHandle(e, eventData) {
|
||||||
const imageId = eventData.imageId;
|
const imageId = eventData.imageId;
|
||||||
this._moveImageId(imageId, this.loadedImageIds, this.remainingImageIds);
|
this._updateImageStatus(imageId, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_triggerProgress() {
|
_updateProgress() {
|
||||||
const loadedImagesCount = this.loadedImageIds.length;
|
const totalFramesCount = this.stack.imageIds.length;
|
||||||
const remainingImagesCount = this.remainingImageIds.length;
|
const loadedFramesCount = this.loadedCount;
|
||||||
const totalImagesCount = loadedImagesCount + remainingImagesCount;
|
const loadingFramesCount = totalFramesCount - loadedFramesCount;
|
||||||
const percentComplete = Math.round(loadedImagesCount / totalImagesCount * 100);
|
const percentComplete = Math.round(loadedFramesCount / totalFramesCount * 100);
|
||||||
const progressSessionId = this._getProgressSessionId();
|
const progressSessionId = this._getProgressSessionId();
|
||||||
|
|
||||||
Session.set(progressSessionId, {
|
Session.set(progressSessionId, {
|
||||||
totalImagesCount,
|
multiFrame: true,
|
||||||
loadedImagesCount,
|
totalFramesCount,
|
||||||
remainingImagesCount,
|
loadedFramesCount,
|
||||||
percentComplete
|
loadingFramesCount,
|
||||||
|
percentComplete,
|
||||||
|
framesPerSecond: this.stats.speed,
|
||||||
|
framesStatus: this.framesStatus
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_logProgress() {
|
||||||
|
const totalFramesCount = this.stack.imageIds.length;
|
||||||
|
const displaySetInstanceUid = this.stack.displaySetInstanceUid;
|
||||||
|
let progressBar = '[';
|
||||||
|
|
||||||
|
for(let i = 0; i < totalFramesCount; i++) {
|
||||||
|
const ch = this.framesStatus[i] ? '|' : '.';
|
||||||
|
progressBar += `${ch}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
progressBar += ']';
|
||||||
|
console.log(`${displaySetInstanceUid}: ${progressBar}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StudyLoadingListener {
|
class StudyLoadingListener {
|
||||||
@ -223,6 +328,7 @@ class StudyLoadingListener {
|
|||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
const displaySetInstanceUid = displaySetInstanceUids[i];
|
const displaySetInstanceUid = displaySetInstanceUids[i];
|
||||||
const displaySet = this.listeners[displaySetInstanceUid];
|
const displaySet = this.listeners[displaySetInstanceUid];
|
||||||
|
|
||||||
displaySet.destroy();
|
displaySet.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,9 +350,15 @@ class StudyLoadingListener {
|
|||||||
const colonIndex = imageId.indexOf(':');
|
const colonIndex = imageId.indexOf(':');
|
||||||
return imageId.substring(0, colonIndex);
|
return imageId.substring(0, colonIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Singleton
|
||||||
|
static getInstance() {
|
||||||
|
if(!StudyLoadingListener._instance) {
|
||||||
|
StudyLoadingListener._instance = new StudyLoadingListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
return StudyLoadingListener._instance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Singleton
|
export { StudyLoadingListener };
|
||||||
const studyLoadingListener = new StudyLoadingListener();
|
|
||||||
|
|
||||||
export { studyLoadingListener };
|
|
||||||
@ -6,6 +6,7 @@ import { _ } from 'meteor/underscore';
|
|||||||
import { OHIF } from 'meteor/ohif:core';
|
import { OHIF } from 'meteor/ohif:core';
|
||||||
import { updateOrientationMarkers } from './updateOrientationMarkers';
|
import { updateOrientationMarkers } from './updateOrientationMarkers';
|
||||||
import { getInstanceClassDefaultViewport } from './instanceClassSpecificViewport';
|
import { getInstanceClassDefaultViewport } from './instanceClassSpecificViewport';
|
||||||
|
import { PlayClipManager } from './classes/PlayClipManager';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a cornerstone enabledElement for a DOM Element
|
* Get a cornerstone enabledElement for a DOM Element
|
||||||
@ -195,16 +196,12 @@ const toggleDialog = element => {
|
|||||||
const toggleCinePlay = () => {
|
const toggleCinePlay = () => {
|
||||||
// Get the active viewport element
|
// Get the active viewport element
|
||||||
const element = getActiveViewportElement();
|
const element = getActiveViewportElement();
|
||||||
|
const playClipManager = PlayClipManager.getInstance();
|
||||||
|
const playClipController = playClipManager.get(element);
|
||||||
|
|
||||||
// Check if it's playing the clip to toggle it
|
if (playClipController) {
|
||||||
if (isPlaying()) {
|
playClipController.togglePlay();
|
||||||
cornerstoneTools.stopClip(element);
|
|
||||||
} else {
|
|
||||||
cornerstoneTools.playClip(element);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the UpdateCINE session property
|
|
||||||
Session.set('UpdateCINE', Random.id());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Show/hide the CINE dialog
|
// Show/hide the CINE dialog
|
||||||
@ -219,29 +216,11 @@ const isPlaying = () => {
|
|||||||
Session.get('UpdateCINE');
|
Session.get('UpdateCINE');
|
||||||
Session.get('LayoutManagerUpdated');
|
Session.get('LayoutManagerUpdated');
|
||||||
|
|
||||||
// Get the viewport element and its current playClip tool state
|
|
||||||
const element = getActiveViewportElement();
|
const element = getActiveViewportElement();
|
||||||
// Empty Elements throws cornerstore exception
|
const playClipManager = PlayClipManager.getInstance();
|
||||||
if (!element || !$(element).find('canvas').length) {
|
const playClipController = playClipManager.get(element);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const toolState = cornerstoneTools.getToolState(element, 'playClip');
|
return playClipController && playClipController.isPlaying();
|
||||||
|
|
||||||
// Stop here if the tool state is not defined yet
|
|
||||||
if (!toolState) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the clip state
|
|
||||||
const clipState = toolState.data[0];
|
|
||||||
|
|
||||||
if(clipState) {
|
|
||||||
// Return true if the clip is playing
|
|
||||||
return !_.isUndefined(clipState.intervalId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if a study has multiple frames
|
// Check if a study has multiple frames
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user