diff --git a/docs/latest/viewer/configuration.md b/docs/latest/viewer/configuration.md
index 838e5f5a4..cbdea6a41 100644
--- a/docs/latest/viewer/configuration.md
+++ b/docs/latest/viewer/configuration.md
@@ -67,6 +67,33 @@ window.config = {
extensions: [],
showStudyList: true,
filterQueryParam: false,
+ /**
+ * Disable caching of servers configuration.
+ *
+ * There will be no effect if you update the servers property of this config
+ * while your application is running because this property is cached in local storage.
+ */
+ disableServersCache: false,
+ /**
+ * OHIF's study prefetcher configuration.
+ *
+ * @param {boolean} enabled Whether to enable/disable OHIF's study prefetcher
+ * @param {('all'|'closest'|'downward'|'upward'|'topdown')} order Fetching order: all display sets, the closest ones, downward or top down fashion based on the currently selected display set
+ * @param {number} displaySetCount How much display sets should be prefetched at once (note: this attribute is ignored if order was set to 'all')
+ * @param {boolean} preventCache Prevent images to be cached in Cornerstone Tools's request pool manager
+ * @param {number} prefetchDisplaySetsTimeout Prefetch timeout
+ * @param {boolean} displayProgress Whether to display or not the progress bar in the display set
+ * @param {boolean} includeActiveDisplaySet Include or not the active display set while prefetching
+ */
+ studyPrefetcher: {
+ enabled: true,
+ order: 'all',
+ displaySetCount: 1,
+ preventCache: false,
+ prefetchDisplaySetsTimeout: 300,
+ displayProgress: false,
+ includeActiveDisplaySet: true,
+ },
servers: {
dicomWeb: [
{
@@ -81,7 +108,14 @@ window.config = {
},
],
},
- // Supported Keys: https://craig.is/killing/mice
+ /**
+ * Hotkey definitions.
+ * Supported Keys: https://craig.is/killing/mice
+ *
+ * @param {string} commandName
+ * @param {string} label
+ * @param {array} keys
+ */
hotkeys: [
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
@@ -97,14 +131,16 @@ window.config = {
keys: ['v'],
},
],
- /* Configuration passed to the bundled cornerstone extension
+ /**
+ * Configuration passed to the bundled cornerstone extension
*
* The cornerstone extension is currently tightly coupled to the platform.
* Until we're able to decouple it, this key will serve as a workaround to
* pass it configuration.
+ *
+ * @param {boolean} hideHandles Whether to show/hide annotation "handles"
*/
cornerstoneExtensionConfig: {
- /* Whether to show/hide annotation "handles" */
hideHandles: true,
},
};
diff --git a/docs/latest/viewer/studyPrefetcher.md b/docs/latest/viewer/studyPrefetcher.md
new file mode 100644
index 000000000..2470561a3
--- /dev/null
+++ b/docs/latest/viewer/studyPrefetcher.md
@@ -0,0 +1,37 @@
+# Viewer: Study Prefetcher & Stack Prefetch
+
+Prefetching the stack of images of the active viewport is enabled by default for
+cornerstone viewports via cornerstone tools's stack prefetch functionality.
+
+In order to have a more customizable alternative for prefetching, OHIF provides
+a built-in study prefetcher functionality which allows the user to set the order
+in which the display sets are prefetched, the granularity, the timeout and
+whether or not display a progress bar in the display set (close to the thumbnail
+image) in the study browser.
+
+You can customize these options via the viewer's configuration:
+
+```js
+window.config = {
+ /**
+ * OHIF's study prefetcher configuration.
+ *
+ * @param {boolean} enabled Whether to enable/disable OHIF's study prefetcher
+ * @param {('all'|'closest'|'downward'|'upward'|'topdown')} order Fetching order: all display sets, the closest ones, downward or top down fashion based on the currently selected display set
+ * @param {number} displaySetCount How much display sets should be prefetched at once (note: this attribute is ignored if order was set to 'all')
+ * @param {boolean} preventCache Prevent images to be cached in Cornerstone Tools's request pool manager
+ * @param {number} prefetchDisplaySetsTimeout Prefetch timeout
+ * @param {boolean} displayProgress Whether to display or not the progress bar in the display set
+ * @param {boolean} includeActiveDisplaySet Include or not the active display set while prefetching
+ */
+ studyPrefetcher: {
+ enabled: true,
+ order: 'all',
+ displaySetCount: 1,
+ preventCache: false,
+ prefetchDisplaySetsTimeout: 300,
+ displayProgress: false,
+ includeActiveDisplaySet: true,
+ },
+};
+```
diff --git a/extensions/cornerstone/README.md b/extensions/cornerstone/README.md
index 9c6e86015..514857020 100644
--- a/extensions/cornerstone/README.md
+++ b/extensions/cornerstone/README.md
@@ -85,12 +85,32 @@ Tools can be configured through extension configuration using the tools key:
...
```
+## Stack prefetch configuration
+
+Stack prefetch can be configured through extension configuration using the
+stackPrefetch key:
+
+```js
+ ...
+ cornerstoneExtensionConfig: {
+ stackPrefetch: {
+ enabled: true,
+ maxImagesToPrefetch: Infinity,
+ preserveExistingPool: false,
+ maxSimultaneousRequests: 20,
+ },
+ },
+ ...
+```
+
## Annotate Tools Configuration
-*We currently support one property for annotation tools.*
+_We currently support one property for annotation tools._
### Hide handles
-This extension configuration allows you to toggle on/off handle rendering for all annotate tools:
+
+This extension configuration allows you to toggle on/off handle rendering for
+all annotate tools:
```js
...
@@ -119,3 +139,4 @@ This extension configuration allows you to toggle on/off handle rendering for al
[cornerstone-tools]: https://github.com/cornerstonejs/cornerstoneTools
[cornerstone]: https://github.com/cornerstonejs/cornerstone
+```
diff --git a/extensions/cornerstone/src/ConnectedCornerstoneViewport.js b/extensions/cornerstone/src/ConnectedCornerstoneViewport.js
index 2ea40f70a..6a6e837c2 100644
--- a/extensions/cornerstone/src/ConnectedCornerstoneViewport.js
+++ b/extensions/cornerstone/src/ConnectedCornerstoneViewport.js
@@ -53,7 +53,11 @@ const mapStateToProps = (state, ownProps) => {
// Currently justing using escape hatch + commands
// activeTool: activeButton && activeButton.command,
...dataFromStore,
- isStackPrefetchEnabled: isActive,
+ isStackPrefetchEnabled: ownProps.hasOwnProperty('isStackPrefetchEnabled')
+ ? ownProps.isStackPrefetchEnabled
+ : ownProps.stackPrefetch
+ ? ownProps.stackPrefetch.enabled
+ : isActive,
isPlaying,
frameRate,
//stack: viewportSpecificData.stack,
diff --git a/extensions/cornerstone/src/OHIFCornerstoneViewport.js b/extensions/cornerstone/src/OHIFCornerstoneViewport.js
index d0b14c7e3..f7875c44c 100644
--- a/extensions/cornerstone/src/OHIFCornerstoneViewport.js
+++ b/extensions/cornerstone/src/OHIFCornerstoneViewport.js
@@ -1,6 +1,6 @@
import React, { Component } from 'react';
-import OHIFCornerstoneViewportOverlay from './components/OHIFCornerstoneViewportOverlay'
+import OHIFCornerstoneViewportOverlay from './components/OHIFCornerstoneViewportOverlay';
import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport';
import OHIF from '@ohif/core';
import PropTypes from 'prop-types';
@@ -15,6 +15,7 @@ class OHIFCornerstoneViewport extends Component {
static defaultProps = {
customProps: {},
+ isStackPrefetchEnabled: true,
};
static propTypes = {
@@ -23,6 +24,8 @@ class OHIFCornerstoneViewport extends Component {
viewportIndex: PropTypes.number,
children: PropTypes.node,
customProps: PropTypes.object,
+ stackPrefetch: PropTypes.object,
+ isStackPrefetchEnabled: PropTypes.bool,
};
static id = 'OHIFCornerstoneViewport';
@@ -180,7 +183,7 @@ class OHIFCornerstoneViewport extends Component {
if (
displaySet.displaySetInstanceUID !==
- prevDisplaySet.displaySetInstanceUID ||
+ prevDisplaySet.displaySetInstanceUID ||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
displaySet.frameIndex !== prevDisplaySet.frameIndex
) {
@@ -232,7 +235,12 @@ class OHIFCornerstoneViewport extends Component {
};
const warningsOverlay = props => {
- return
+ return (
+
+ );
};
return (
@@ -244,10 +252,11 @@ class OHIFCornerstoneViewport extends Component {
onNewImageDebounced={newImageHandler}
onNewImageDebounceTime={300}
viewportOverlayComponent={warningsOverlay}
+ stackPrefetch={this.props.stackPrefetch}
+ isStackPrefetchEnabled={this.props.isStackPrefetchEnabled}
// ~~ Connected (From REDUX)
// frameRate={frameRate}
// isPlaying={false}
- // isStackPrefetchEnabled={true}
// onElementEnabled={() => {}}
// setViewportActive{() => {}}
{...this.props.customProps}
diff --git a/extensions/cornerstone/src/index.js b/extensions/cornerstone/src/index.js
index e78f7bd6b..8ed69d2ad 100644
--- a/extensions/cornerstone/src/index.js
+++ b/extensions/cornerstone/src/index.js
@@ -36,7 +36,7 @@ export default {
preRegistration({ servicesManager, configuration = {} }) {
init({ servicesManager, configuration });
},
- getViewportModule({ commandsManager }) {
+ getViewportModule({ commandsManager, appConfig }) {
const ExtendedOHIFCornerstoneViewport = props => {
/**
* TODO: This appears to be used to set the redux parameters for
@@ -48,8 +48,17 @@ export default {
jumpData.refreshViewports = false;
commandsManager.runCommand('jumpToImage', jumpData);
};
+
+ const { studyPrefetcher } = appConfig;
+ const isStackPrefetchEnabled =
+ studyPrefetcher && !studyPrefetcher.enabled;
+
return (
-
+
);
};
diff --git a/extensions/cornerstone/src/init.js b/extensions/cornerstone/src/init.js
index 27e962b80..fb0f55436 100644
--- a/extensions/cornerstone/src/init.js
+++ b/extensions/cornerstone/src/init.js
@@ -37,7 +37,14 @@ export default function init({ servicesManager, configuration }) {
}
};
- const { csToolsConfig } = configuration;
+ const {
+ csToolsConfig,
+ stackPrefetch = {
+ maxImagesToPrefetch: Infinity,
+ preserveExistingPool: false,
+ maxSimultaneousRequests: 20,
+ },
+ } = configuration;
const metadataProvider = OHIF.cornerstone.metadataProvider;
cornerstone.metaData.addProvider(
@@ -52,7 +59,7 @@ export default function init({ servicesManager, configuration }) {
autoResizeViewports: false,
};
- initCornerstoneTools(defaultCsToolsConfig);
+ initCornerstoneTools({ ...defaultCsToolsConfig, ...stackPrefetch });
const toolsGroupedByType = {
touch: [csTools.PanMultiTouchTool, csTools.ZoomTouchPinchTool],
diff --git a/extensions/cornerstone/src/initCornerstoneTools.js b/extensions/cornerstone/src/initCornerstoneTools.js
index 54b9fb6f6..8a513fa8f 100644
--- a/extensions/cornerstone/src/initCornerstoneTools.js
+++ b/extensions/cornerstone/src/initCornerstoneTools.js
@@ -37,8 +37,8 @@ export default function(configuration = {}) {
// Configure stack prefetch
cornerstoneTools.stackPrefetch.setConfiguration({
- maxImagesToPrefetch: Infinity,
- preserveExistingPool: false,
- maxSimultaneousRequests: 20,
+ maxImagesToPrefetch: configuration.maxImagesToPrefetch,
+ preserveExistingPool: configuration.preserveExistingPool,
+ maxSimultaneousRequests: configuration.maxSimultaneousRequests,
});
}
diff --git a/platform/core/src/classes/StudyLoadingListener.js b/platform/core/src/classes/StudyLoadingListener.js
index c655250bc..42414ab38 100644
--- a/platform/core/src/classes/StudyLoadingListener.js
+++ b/platform/core/src/classes/StudyLoadingListener.js
@@ -1,16 +1,14 @@
import cornerstone from 'cornerstone-core';
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
-import {
- clearStudyLoadingProgress,
- setStudyLoadingProgress,
-} from '../redux/actions';
+import debounce from 'lodash.debounce';
+
import StackManager from '../utils/StackManager';
+import { StudyPrefetcher } from './StudyPrefetcher';
class BaseLoadingListener {
constructor(stack, options = {}) {
this.id = BaseLoadingListener.getNewId();
this.stack = stack;
- this.startListening();
this.statsItemsLimit = options.statsItemsLimit || 2;
this.stats = {
items: [],
@@ -60,6 +58,9 @@ class BaseLoadingListener {
}
_getProgressId() {
+ /**
+ * TODO: The id key should be configurable.
+ */
const displaySetInstanceUID = this.stack.displaySetInstanceUID;
return 'StackProgress:' + displaySetInstanceUID;
}
@@ -96,11 +97,18 @@ class BaseLoadingListener {
class DICOMFileLoadingListener extends BaseLoadingListener {
constructor(stack, options) {
super(stack, options);
+
+ this.imageLoadProgressEventHandler = this._imageLoadProgressEventHandle.bind(
+ this
+ );
+
this._dataSetUrl = this._getDataSetUrl(stack);
this._lastLoaded = 0;
// Check how many instances has already been download (cached)
this._checkCachedData();
+
+ this.startListening();
}
_checkCachedData() {
@@ -127,10 +135,6 @@ class DICOMFileLoadingListener extends BaseLoadingListener {
startListening() {
const imageLoadProgressEventName = this._getImageLoadProgressEventName();
- this.imageLoadProgressEventHandler = this._imageLoadProgressEventHandle.bind(
- this
- );
-
this.stopListening();
cornerstone.events.addEventListener(
@@ -200,17 +204,57 @@ class DICOMFileLoadingListener extends BaseLoadingListener {
}
}
+const StudyLoadingListenerEvents = {
+ OnProgress: 'StudyLoadingListenerEvents.OnProgress',
+};
+
+function promiseState(promise, callback) {
+ // Symbols and RegExps are never content-equal
+ var uniqueValue = window['Symbol'] ? Symbol('unique') : /unique/;
+
+ function notifyPendingOrResolved(value) {
+ if (value === uniqueValue) {
+ return callback('pending');
+ } else {
+ return callback('fulfilled');
+ }
+ }
+
+ function notifyRejected(reason) {
+ return callback('rejected');
+ }
+
+ var race = [promise, Promise.resolve(uniqueValue)];
+ Promise.race(race).then(notifyPendingOrResolved, notifyRejected);
+}
+
class StackLoadingListener extends BaseLoadingListener {
constructor(stack, options = {}) {
options.statsItemsLimit = 20;
+
super(stack, options);
+ this.imageLoadedEventHandler = this._imageLoadedEventHandler.bind(this);
+ this.imageCachePromiseRemovedEventHandler = this._imageCachePromiseRemovedEventHandler.bind(
+ this
+ );
+
this.imageDataMap = this._convertImageIdsArrayToMap(stack.imageIds);
this.framesStatus = this._createArray(stack.imageIds.length, false);
this.loadedCount = 0;
// Check how many instances has already been download (cached)
- this._checkCachedData();
+ this._debouncedSetProgressData = debounce((...args) => {
+ this._setProgressData(...args);
+
+ /** After checking cache, continue prefetch */
+ const studyPrefetcher = StudyPrefetcher.getInstance();
+ studyPrefetcher.prefetch(studyPrefetcher.getElement());
+ }, 300);
+ const debounced = true;
+ this._checkCachedData(debounced);
+
+ this.startListening();
}
_convertImageIdsArrayToMap(imageIds) {
@@ -238,18 +282,37 @@ class StackLoadingListener extends BaseLoadingListener {
return array;
}
- _checkCachedData() {
- // const imageIds = this.stack.imageIds;
- // TODO: No way to check status of Promise.
- /*for(let i = 0; i < imageIds.length; i++) {
- const imageId = imageIds[i];
+ /**
+ * Check if image id is cached in cornerstone.
+ *
+ * @param {string} imageId
+ * @returns
+ */
+ isImageCached(imageId) {
+ const image = cornerstone.imageCache.imageCache[imageId];
+ return image && image.sizeInBytes;
+ }
- const imagePromise = cornerstone.imageCache.getImageLoadObject(imageId).promise;
+ _checkCachedData(debounced = false) {
+ const imageIds = this.stack.imageIds;
- if (imagePromise && (imagePromise.state() === 'resolved')) {
- this._updateFrameStatus(imageId, true);
- }
- }*/
+ for (let i = 0; i < imageIds.length; i++) {
+ const imageId = imageIds[i];
+
+ const imageObject = cornerstone.imageCache.getImageLoadObject(imageId);
+
+ if (this.isImageCached(imageId)) {
+ this._updateFrameStatus(imageId, true, debounced);
+ }
+
+ if (imageObject && imageObject.promise) {
+ promiseState(imageObject.promise, state => {
+ if (state === 'fulfilled') {
+ this._updateFrameStatus(imageId, true, debounced);
+ }
+ });
+ }
+ }
}
_getImageLoadedEventName() {
@@ -272,11 +335,6 @@ class StackLoadingListener extends BaseLoadingListener {
const imageLoadedEventName = this._getImageLoadedEventName();
const imageCachePromiseRemovedEventName = this._getImageCachePromiseRemoveEventName();
- this.imageLoadedEventHandler = this._imageLoadedEventHandler.bind(this);
- this.imageCachePromiseRemovedEventHandler = this._imageCachePromiseRemovedEventHandler.bind(
- this
- );
-
this.stopListening();
cornerstone.events.addEventListener(
@@ -303,7 +361,7 @@ class StackLoadingListener extends BaseLoadingListener {
);
}
- _updateFrameStatus(imageId, loaded) {
+ _updateFrameStatus(imageId, loaded, debounced) {
const imageData = this.imageDataMap.get(imageId);
if (!imageData || imageData.loaded === loaded) {
@@ -318,7 +376,7 @@ class StackLoadingListener extends BaseLoadingListener {
imageData.loaded = loaded;
this.framesStatus[imageData.index] = loaded;
this.loadedCount += loaded ? 1 : -1;
- this._updateProgress();
+ this._updateProgress(debounced);
}
_setProgressData(progressId, progressData) {
@@ -339,7 +397,7 @@ class StackLoadingListener extends BaseLoadingListener {
);
}
- _updateProgress() {
+ _updateProgress(debounced) {
const totalFramesCount = this.stack.imageIds.length;
const loadedFramesCount = this.loadedCount;
const loadingFramesCount = totalFramesCount - loadedFramesCount;
@@ -357,6 +415,11 @@ class StackLoadingListener extends BaseLoadingListener {
framesStatus: this.framesStatus,
};
+ if (debounced) {
+ this._debouncedSetProgressData(progressId, progressData);
+ return;
+ }
+
this._setProgressData(progressId, progressData);
}
@@ -371,11 +434,13 @@ class StackLoadingListener extends BaseLoadingListener {
}
progressBar += ']';
- log.info(`${displaySetInstanceUID}: ${progressBar}`);
+ console.info(`${displaySetInstanceUID}: ${progressBar}`);
}
}
class StudyLoadingListener {
+ static events = StudyLoadingListenerEvents;
+
constructor(options) {
this.listeners = {};
this.options = options;
@@ -454,14 +519,36 @@ class StudyLoadingListener {
_getSchema(stack) {
const imageId = stack.imageIds[0];
+ if (!imageId) {
+ return;
+ }
const colonIndex = imageId.indexOf(':');
return imageId.substring(0, colonIndex);
}
- // Singleton
static getInstance(options) {
+ /**
+ * TODO: Use a different alternative without the use of events.
+ */
+ const DEFAULT_OPTIONS = {
+ _setProgressData: (progressId, progressData) => {
+ const event = new CustomEvent(StudyLoadingListenerEvents.OnProgress, {
+ detail: { progressId, progressData },
+ });
+ document.dispatchEvent(event);
+ },
+ _clearProgressById: progressId => {
+ const event = new CustomEvent(StudyLoadingListenerEvents.OnProgress, {
+ detail: { progressId, percentComplete: 0 },
+ });
+ document.dispatchEvent(event);
+ },
+ };
+
if (!StudyLoadingListener._instance) {
- StudyLoadingListener._instance = new StudyLoadingListener(options);
+ StudyLoadingListener._instance = new StudyLoadingListener(
+ options || DEFAULT_OPTIONS
+ );
}
return StudyLoadingListener._instance;
diff --git a/platform/core/src/classes/StudyPrefetcher.js b/platform/core/src/classes/StudyPrefetcher.js
index 5f384ad4c..72ccc96eb 100644
--- a/platform/core/src/classes/StudyPrefetcher.js
+++ b/platform/core/src/classes/StudyPrefetcher.js
@@ -1,14 +1,28 @@
-import log from '../log.js';
-import OHIFError from './OHIFError';
import cornerstone from 'cornerstone-core';
import cornerstoneTools from 'cornerstone-tools';
+
import getImageId from '../utils/getImageId.js';
+const noop = () => {};
+
export class StudyPrefetcher {
- constructor(studies) {
+ options = {
+ order: 'closest',
+ displaySetCount: 1,
+ onImageCached: noop,
+ requestType: 'prefetch',
+ preventCache: false,
+ prefetchDisplaySetsTimeout: 300,
+ includeActiveDisplaySet: false,
+ };
+
+ constructor(studies, options) {
this.studies = studies || [];
- this.prefetchDisplaySetsTimeout = 300;
- this.lastActiveViewportElement = null;
+
+ if (options) {
+ this.options = { ...this.options, ...options };
+ this.options.requestType = 'prefetch';
+ }
cornerstone.events.addEventListener(
'cornerstoneimagecachefull.StudyPrefetcher',
@@ -16,6 +30,9 @@ export class StudyPrefetcher {
);
}
+ /**
+ * Remove previously set event listeners and stop prefetching.
+ */
destroy() {
this.stopPrefetching();
cornerstone.events.removeEventListener(
@@ -24,68 +41,117 @@ export class StudyPrefetcher {
);
}
- static getInstance() {
+ /**
+ * Get StudyPrefetcher singleton instance.
+ *
+ * @param {array} studies
+ * @param {object} options
+ * @returns
+ */
+ static getInstance(studies = [], options) {
if (!StudyPrefetcher.instance) {
- StudyPrefetcher.instance = new StudyPrefetcher([]);
+ StudyPrefetcher.instance = new StudyPrefetcher(studies, options);
+ }
+
+ if (options) {
+ this.options = { ...this.options, ...options };
+ this.options.requestType = 'prefetch';
}
return StudyPrefetcher.instance;
}
+ /**
+ * OHIF study metadata instances.
+ *
+ * @param {array} studies
+ */
setStudies(studies) {
this.stopPrefetching();
this.studies = studies;
}
- prefetch() {
+ /**
+ * Get previously prefetched element.
+ *
+ * @returns {HTMLElement} Previously prefetched element.
+ */
+ getElement() {
+ return this.element;
+ }
+
+ /**
+ * Prefetch related display sets based on cornerstone viewport element
+ * with previously set options.
+ *
+ * @param {*} element
+ * @param {string} displaySetInstanceUID the display set instance uid
+ * @returns
+ */
+ prefetch(element, displaySetInstanceUID) {
if (!this.studies || !this.studies.length) {
return;
}
+ this.element = element;
+ this.enabledElement = cornerstone.getEnabledElement(element);
+
this.stopPrefetching();
- this.prefetchDisplaySets();
+ this.prefetchDisplaySets(displaySetInstanceUID);
}
+ /**
+ * Stop prefetching images.
+ */
stopPrefetching() {
cornerstoneTools.requestPoolManager.clearRequestStack('prefetch');
}
- prefetchDisplaySetsAsync(timeout) {
- timeout = timeout || this.prefetchDisplaySetsTimeout;
-
+ /**
+ * Prefetch display sets async.
+ *
+ * @param {HTMLElement} element cornerstone viewport element
+ * @param {number} timeout
+ */
+ prefetchDisplaySetsAsync(element, timeout) {
+ this.enabledElement = cornerstone.getEnabledElement(element);
+ timeout = timeout || this.options.prefetchDisplaySetsTimeout;
clearTimeout(this.prefetchDisplaySetsHandler);
this.prefetchDisplaySetsHandler = setTimeout(() => {
- this.prefetchDisplaySets();
+ this.prefetchDisplaySets(element);
}, timeout);
}
- prefetchDisplaySets() {
- // TODO: Allow passing in config
- let config = {
- order: 'closest',
- displaySetCount: 1,
- };
-
- const displaySetsToPrefetch = this.getDisplaySetsToPrefetch(config);
+ /**
+ * Extract all image ids from all display sets to be fetched and
+ * call method to add images to request pool manager.
+ *
+ * @param {string} displaySetInstanceUID the display set instance uid
+ */
+ prefetchDisplaySets(displaySetInstanceUID) {
+ const displaySetsToPrefetch = this.getDisplaySetsToPrefetch(
+ displaySetInstanceUID
+ );
const imageIds = this.getImageIdsFromDisplaySets(displaySetsToPrefetch);
-
this.prefetchImageIds(imageIds);
}
+ /**
+ * Add image ids to request pool manager.
+ *
+ * @param {array} imageIds
+ */
prefetchImageIds(imageIds) {
const nonCachedImageIds = this.filterCachedImageIds(imageIds);
const requestPoolManager = cornerstoneTools.requestPoolManager;
- const requestType = 'prefetch';
- const preventCache = false;
- const noop = () => {};
nonCachedImageIds.forEach(imageId => {
requestPoolManager.addRequest(
{},
imageId,
- requestType,
- preventCache,
- noop,
+ this.options.requestType,
+ this.options.preventCache,
+ () => {},
noop
);
});
@@ -93,26 +159,45 @@ export class StudyPrefetcher {
requestPoolManager.startGrabbing();
}
+ /**
+ * Get study by cornerstone image instance.
+ *
+ * @param {object} image
+ * @returns
+ */
getStudy(image) {
const StudyInstanceUID = cornerstone.metaData.get(
'StudyInstanceUID',
image.imageId
);
- return OHIF.viewer.Studies.find(
- study => study.StudyInstanceUID === StudyInstanceUID
+ const studies = this.studies;
+ return studies.find(
+ study => study.getData().StudyInstanceUID === StudyInstanceUID
);
}
+ /**
+ * Get study series by cornerstone image instance.
+ *
+ * @param {object} study OHIF study instance
+ * @param {object} image cornerstone image instance object
+ * @returns
+ */
getSeries(study, image) {
const SeriesInstanceUID = cornerstone.metaData.get(
'SeriesInstanceUID',
image.imageId
);
- const studyMetadata = OHIF.viewerbase.getStudyMetadata(study);
-
- return studyMetadata.getSeriesByUID(SeriesInstanceUID);
+ return study.getSeriesByUID(SeriesInstanceUID);
}
+ /**
+ * Get sop instance by cornerstone image instance.
+ *
+ * @param {array} series
+ * @param {object} image
+ * @returns
+ */
getInstance(series, image) {
const instanceMetadata = cornerstone.metaData.get(
'instance',
@@ -121,7 +206,33 @@ export class StudyPrefetcher {
return series.getInstanceByUID(instanceMetadata.SOPInstanceUID);
}
- getActiveDisplaySet(displaySets, instance) {
+ /**
+ * Returns the display set with given uid.
+ *
+ * @param {string} displaySetInstanceUID the display set instance uid
+ * @returns {object} displaySet
+ */
+ getDisplaySetByUID(displaySetInstanceUID) {
+ let displaySet;
+ this.studies.forEach(study => {
+ const ds = study.displaySets.find(
+ ds => ds.displaySetInstanceUID === displaySetInstanceUID
+ );
+ if (ds) {
+ displaySet = ds;
+ }
+ });
+ return displaySet;
+ }
+
+ /**
+ * Get display set by SOPInstanceUID.
+ *
+ * @param {array} displaySets
+ * @param {object} instance
+ * @returns
+ */
+ getDisplaySetBySOPInstanceUID(displaySets, instance) {
return displaySets.find(displaySet => {
return displaySet.images.some(displaySetImage => {
return displaySetImage.SOPInstanceUID === instance.SOPInstanceUID;
@@ -129,25 +240,48 @@ export class StudyPrefetcher {
});
}
- getDisplaySetsToPrefetch(config) {
+ /**
+ * Get active viewport image based on cornerstone viewport element.
+ * @returns
+ */
+ getActiveViewportImage() {
+ if (!this.enabledElement) {
+ return;
+ }
+
+ return this.enabledElement.image;
+ }
+
+ /**
+ * Prefetch display sets based on cornerstone viewport element image.
+ *
+ * @param {string} displaySetInstanceUID the display set instance uid
+ * @returns {array} displaySets
+ */
+ getDisplaySetsToPrefetch(displaySetInstanceUID) {
const image = this.getActiveViewportImage();
- if (!image || !config || !config.displaySetCount) {
+ if (!image) {
return [];
}
- /*const study = this.getStudy(image);
+ const study = this.getStudy(image);
const series = this.getSeries(study, image);
- const instance = this.getInstance(series, image);*/
+ const instance = this.getInstance(series, image);
const displaySets = study.displaySets;
- const activeDisplaySet = null; //this.getActiveDisplaySet(displaySets, instance);
+ const activeDisplaySet = displaySetInstanceUID
+ ? this.getDisplaySetByUID(displaySetInstanceUID)
+ : this.getDisplaySetBySOPInstanceUID(displaySets, instance);
+
const prefetchMethodMap = {
topdown: 'getFirstDisplaySets',
downward: 'getNextDisplaySets',
+ upward: 'getPreviousDisplaySets',
closest: 'getClosestDisplaySets',
+ all: 'getAllDisplaySets',
};
- const prefetchOrder = config.order;
+ const prefetchOrder = this.options.order;
const methodName = prefetchMethodMap[prefetchOrder];
const getDisplaySets = this[methodName];
@@ -163,18 +297,59 @@ export class StudyPrefetcher {
this,
displaySets,
activeDisplaySet,
- config.displaySetCount
+ this.options.displaySetCount,
+ this.options.includeActiveDisplaySet
);
}
- getFirstDisplaySets(displaySets, activeDisplaySet, displaySetCount) {
+ /**
+ * Get all display sets.
+ *
+ * @param {array} displaySets
+ * @param {object} activeDisplaySet
+ * @param {number} displaySetCount
+ * @param {boolean} includeActiveDisplaySet
+ * @returns
+ */
+ getAllDisplaySets(
+ displaySets,
+ activeDisplaySet,
+ displaySetCount,
+ includeActiveDisplaySet
+ ) {
+ const length = displaySets.length;
+ const selectedDisplaySets = [];
+
+ for (let i = 0; i < length; i++) {
+ const displaySet = displaySets[i];
+ selectedDisplaySets.push(displaySet);
+ }
+
+ return selectedDisplaySets;
+ }
+
+ /**
+ * Get all display sets in order after the active display set.
+ *
+ * @param {array} displaySets
+ * @param {object} activeDisplaySet
+ * @param {number} displaySetCount
+ * @param {boolean} includeActiveDisplaySet
+ * @returns
+ */
+ getFirstDisplaySets(
+ displaySets,
+ activeDisplaySet,
+ displaySetCount,
+ includeActiveDisplaySet
+ ) {
const length = displaySets.length;
const selectedDisplaySets = [];
for (let i = 0; i < length && displaySetCount; i++) {
const displaySet = displaySets[i];
- if (displaySet !== activeDisplaySet) {
+ if (includeActiveDisplaySet || displaySet !== activeDisplaySet) {
selectedDisplaySets.push(displaySet);
displaySetCount--;
}
@@ -183,21 +358,78 @@ export class StudyPrefetcher {
return selectedDisplaySets;
}
- getNextDisplaySets(displaySets, activeDisplaySet, displaySetCount) {
+ /**
+ * Get all display sets before the active display set.
+ *
+ * @param {array} displaySets
+ * @param {object} activeDisplaySet
+ * @param {number} displaySetCount
+ * @param {boolean} includeActiveDisplaySet
+ * @returns
+ */
+ getPreviousDisplaySets(
+ displaySets,
+ activeDisplaySet,
+ displaySetCount,
+ includeActiveDisplaySet
+ ) {
const activeDisplaySetIndex = displaySets.indexOf(activeDisplaySet);
- const begin = activeDisplaySetIndex + 1;
- const end = Math.min(begin + displaySetCount, displaySets.length);
+ const end = includeActiveDisplaySet
+ ? activeDisplaySetIndex + 1
+ : activeDisplaySetIndex;
+ const previousDisplaySets = displaySets.slice(0, end);
+ return previousDisplaySets.reverse().slice(0, displaySetCount);
+ }
+ /**
+ * Get all display sets after the active display set.
+ *
+ * @param {array} displaySets
+ * @param {object} activeDisplaySet
+ * @param {number} displaySetCount
+ * @param {boolean} includeActiveDisplaySet
+ * @returns
+ */
+ getNextDisplaySets(
+ displaySets,
+ activeDisplaySet,
+ displaySetCount,
+ includeActiveDisplaySet
+ ) {
+ const activeDisplaySetIndex = displaySets.indexOf(activeDisplaySet);
+ const begin = includeActiveDisplaySet
+ ? activeDisplaySetIndex
+ : activeDisplaySetIndex + 1;
+ const end = Math.min(begin + displaySetCount, displaySets.length);
return displaySets.slice(begin, end);
}
- getClosestDisplaySets(displaySets, activeDisplaySet, displaySetCount) {
+ /**
+ * Get all display set closest to the active display set.
+ *
+ * @param {array} displaySets
+ * @param {object} activeDisplaySet
+ * @param {number} displaySetCount
+ * @param {boolean} includeActiveDisplaySet
+ * @returns
+ */
+ getClosestDisplaySets(
+ displaySets,
+ activeDisplaySet,
+ displaySetCount,
+ includeActiveDisplaySet
+ ) {
const activeDisplaySetIndex = displaySets.indexOf(activeDisplaySet);
const length = displaySets.length;
const selectedDisplaySets = [];
let left = activeDisplaySetIndex - 1;
let right = activeDisplaySetIndex + 1;
+ if (includeActiveDisplaySet) {
+ selectedDisplaySets.push(displaySets[activeDisplaySetIndex]);
+ displaySetCount--;
+ }
+
while ((left >= 0 || right < length) && displaySetCount) {
if (left >= 0) {
selectedDisplaySets.push(displaySets[left]);
@@ -215,6 +447,12 @@ export class StudyPrefetcher {
return selectedDisplaySets;
}
+ /**
+ * Get all image ids from display sets.
+ *
+ * @param {array} displaySets
+ * @returns {array} image ids
+ */
getImageIdsFromDisplaySets(displaySets) {
let imageIds = [];
@@ -225,9 +463,19 @@ export class StudyPrefetcher {
return imageIds;
}
+ /**
+ * Get all image ids from a given display set.
+ *
+ * @param {array} displaySet
+ * @returns
+ */
getImageIdsFromDisplaySet(displaySet) {
const imageIds = [];
+ if (!displaySet.images || displaySet.images.length < 1) {
+ return [];
+ }
+
// TODO: This duplicates work done by the stack manager
displaySet.images.forEach(image => {
const numFrames = image.numFrames;
@@ -245,15 +493,30 @@ export class StudyPrefetcher {
return imageIds;
}
+ /**
+ * Filter cached image ids from a set of image ids.
+ *
+ * @param {array} imageIds
+ * @returns {array} images not cached
+ */
filterCachedImageIds(imageIds) {
return imageIds.filter(imageId => !this.isImageCached(imageId));
}
+ /**
+ * Check if image id is cached in cornerstone.
+ *
+ * @param {string} imageId
+ * @returns
+ */
isImageCached(imageId) {
const image = cornerstone.imageCache.imageCache[imageId];
return image && image.sizeInBytes;
}
+ /**
+ * Warns that cache is full and stops prefetching.
+ */
cacheFullHandler = () => {
log.warn('Cache full');
this.stopPrefetching();
diff --git a/platform/core/src/classes/metadata/StudyMetadata.js b/platform/core/src/classes/metadata/StudyMetadata.js
index e2e682cb2..78d0bf202 100644
--- a/platform/core/src/classes/metadata/StudyMetadata.js
+++ b/platform/core/src/classes/metadata/StudyMetadata.js
@@ -497,6 +497,12 @@ class StudyMetadata extends Metadata {
}
}
+ if (this._displaySets.some(ds =>
+ ds.displaySetInstanceUID === displaySet.displaySetInstanceUID)
+ ) {
+ return;
+ }
+
this._displaySets.splice(insertIndex, 0, displaySet);
this.displaySets = this._displaySets;
}
diff --git a/platform/core/src/redux/actions.js b/platform/core/src/redux/actions.js
index 1e46a9c4d..5f1b8559e 100644
--- a/platform/core/src/redux/actions.js
+++ b/platform/core/src/redux/actions.js
@@ -78,17 +78,6 @@ export const setActiveViewportSpecificData = viewportSpecificData => ({
/**
* NOT-VIEWPORT
*/
-export const setStudyLoadingProgress = (progressId, progressData) => ({
- type: 'SET_STUDY_LOADING_PROGRESS',
- progressId,
- progressData,
-});
-
-export const clearStudyLoadingProgress = progressId => ({
- type: 'CLEAR_STUDY_LOADING_PROGRESS',
- progressId,
-});
-
export const setUserPreferences = state => ({
type: SET_USER_PREFERENCES,
state,
@@ -134,8 +123,6 @@ const actions = {
/**
* NOT-VIEWPORT
*/
- setStudyLoadingProgress,
- clearStudyLoadingProgress,
setUserPreferences,
setExtensionData,
setTimepoints,
diff --git a/platform/core/src/redux/actions.test.js b/platform/core/src/redux/actions.test.js
index e80297a66..cb07bf6da 100644
--- a/platform/core/src/redux/actions.test.js
+++ b/platform/core/src/redux/actions.test.js
@@ -11,8 +11,6 @@ describe('actions', () => {
'setLayout',
'clearViewportSpecificData',
'setActiveViewportSpecificData',
- 'setStudyLoadingProgress',
- 'clearStudyLoadingProgress',
'setUserPreferences',
'setExtensionData',
'setTimepoints',
diff --git a/platform/core/src/redux/reducers/index.js b/platform/core/src/redux/reducers/index.js
index 44bf26755..9be45870c 100644
--- a/platform/core/src/redux/reducers/index.js
+++ b/platform/core/src/redux/reducers/index.js
@@ -1,5 +1,4 @@
import extensions from './extensions';
-import loading from './loading';
import preferences from './preferences';
import servers from './servers';
import studies from './studies';
@@ -8,7 +7,6 @@ import viewports from './viewports';
const reducers = {
extensions,
- loading,
preferences,
servers,
studies,
diff --git a/platform/core/src/redux/reducers/loading.js b/platform/core/src/redux/reducers/loading.js
deleted file mode 100644
index b82a57d27..000000000
--- a/platform/core/src/redux/reducers/loading.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import cloneDeep from 'lodash.clonedeep';
-
-const defaultState = {
- progress: {},
- lastUpdated: null,
-};
-
-const loading = (state = defaultState, action) => {
- let progress;
- let lastUpdated;
- switch (action.type) {
- case 'SET_STUDY_LOADING_PROGRESS':
- progress = cloneDeep(state).progress;
- progress[action.progressId] = action.progressData;
-
- // This is a workaround so we can easily identify changes
- // to the progress object without doing deep comparison.
- lastUpdated = new Date().getTime();
-
- return Object.assign({}, state, { progress, lastUpdated });
- case 'CLEAR_STUDY_LOADING_PROGRESS':
- progress = cloneDeep(state).progress;
- delete progress[action.progressId];
-
- lastUpdated = new Date().getTime();
-
- return Object.assign({}, state, { progress, lastUpdated });
- default:
- return state;
- }
-};
-
-export default loading;
diff --git a/platform/ui/src/components/studyBrowser/ImageThumbnail.js b/platform/ui/src/components/studyBrowser/ImageThumbnail.js
index 5f83d7348..2fb260ced 100644
--- a/platform/ui/src/components/studyBrowser/ImageThumbnail.js
+++ b/platform/ui/src/components/studyBrowser/ImageThumbnail.js
@@ -2,7 +2,7 @@
import './ImageThumbnail.styl';
import { utils } from '@ohif/core';
-import React, { useState, useEffect, createRef } from 'react';
+import React, { useState, useEffect, createRef, useCallback } from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
@@ -23,6 +23,7 @@ function ImageThumbnail(props) {
imageId,
stackPercentComplete,
error: propsError,
+ showProgressBar,
} = props;
const [isLoading, setLoading] = useState(false);
@@ -39,7 +40,8 @@ function ImageThumbnail(props) {
loadingOrError = ;
}
- const showStackLoadingProgressBar = stackPercentComplete !== undefined;
+ const showStackLoadingProgressBar =
+ showProgressBar && stackPercentComplete !== undefined;
const shouldRenderToCanvas = () => {
return imageId && !imageSrc;
@@ -71,11 +73,11 @@ function ImageThumbnail(props) {
}
};
- const purgeCancelablePromise = () => {
+ const purgeCancelablePromise = useCallback(() => {
if (cancelablePromise) {
cancelablePromise.cancel();
}
- };
+ });
useEffect(() => {
return () => {
@@ -96,7 +98,13 @@ function ImageThumbnail(props) {
setImagePromise();
fetchImagePromise();
}
- }, [fetchImagePromise, image.imageId, imageId, purgeCancelablePromise, setImagePromise]);
+ }, [
+ fetchImagePromise,
+ image.imageId,
+ imageId,
+ purgeCancelablePromise,
+ setImagePromise,
+ ]);
return (
@@ -104,14 +112,14 @@ function ImageThumbnail(props) {
{shouldRenderToCanvas() ? (
) : (
-

- )}
+

+ )}
{loadingOrError}
{showStackLoadingProgressBar && (
@@ -135,6 +143,7 @@ ImageThumbnail.propTypes = {
width: PropTypes.number,
height: PropTypes.number,
stackPercentComplete: PropTypes.number.isRequired,
+ showProgressBar: PropTypes.bool,
};
ImageThumbnail.defaultProps = {
@@ -143,6 +152,7 @@ ImageThumbnail.defaultProps = {
stackPercentComplete: 0,
width: 217,
height: 123,
+ showProgressBar: true,
};
export default ImageThumbnail;
diff --git a/platform/ui/src/components/studyBrowser/StudyBrowser.js b/platform/ui/src/components/studyBrowser/StudyBrowser.js
index 0bb75058a..a745c02fb 100644
--- a/platform/ui/src/components/studyBrowser/StudyBrowser.js
+++ b/platform/ui/src/components/studyBrowser/StudyBrowser.js
@@ -9,6 +9,7 @@ function StudyBrowser(props) {
onThumbnailClick,
onThumbnailDoubleClick,
supportsDrag,
+ showThumbnailProgressBar,
} = props;
return (
@@ -28,7 +29,6 @@ function StudyBrowser(props) {
numImageFrames,
SeriesDescription,
SeriesNumber,
- stackPercentComplete,
hasWarnings,
} = thumb;
@@ -54,13 +54,13 @@ function StudyBrowser(props) {
SeriesDescription={SeriesDescription}
SeriesNumber={SeriesNumber}
hasWarnings={hasWarnings}
- stackPercentComplete={stackPercentComplete}
// Events
onClick={onThumbnailClick.bind(
undefined,
displaySetInstanceUID
)}
onDoubleClick={onThumbnailDoubleClick}
+ showProgressBar={showThumbnailProgressBar}
/>
);
@@ -95,6 +95,7 @@ StudyBrowser.propTypes = {
supportsDrag: PropTypes.bool,
onThumbnailClick: PropTypes.func,
onThumbnailDoubleClick: PropTypes.func,
+ showThumbnailProgressBar: PropTypes.bool,
};
StudyBrowser.defaultProps = {
@@ -102,6 +103,7 @@ StudyBrowser.defaultProps = {
supportsDrag: true,
onThumbnailClick: noop,
onThumbnailDoubleClick: noop,
+ showThumbnailProgressBar: true,
};
export { StudyBrowser };
diff --git a/platform/ui/src/components/studyBrowser/Thumbnail.js b/platform/ui/src/components/studyBrowser/Thumbnail.js
index 94e76ec4d..36bb5f68c 100644
--- a/platform/ui/src/components/studyBrowser/Thumbnail.js
+++ b/platform/ui/src/components/studyBrowser/Thumbnail.js
@@ -1,34 +1,36 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { useDrag } from 'react-dnd';
+import { classes } from '@ohif/core';
import ImageThumbnail from './ImageThumbnail';
import classNames from 'classnames';
import { Icon } from './../../elements/Icon';
import { Tooltip } from './../tooltip';
import { OverlayTrigger } from './../overlayTrigger';
-
import './Thumbnail.styl';
+const StudyLoadingListener = classes.StudyLoadingListener;
+
function ThumbnailFooter({
SeriesDescription,
SeriesNumber,
InstanceNumber,
numImageFrames,
- hasWarnings
+ hasWarnings,
}) {
const [inconsistencyWarnings, inconsistencyWarningsSet] = useState([]);
useEffect(() => {
- let unmounted = false
+ let unmounted = false;
hasWarnings.then(response => {
if (!unmounted) {
- inconsistencyWarningsSet(response)
+ inconsistencyWarningsSet(response);
}
- })
+ });
return () => {
- unmounted = true
- }
- }, [])
+ unmounted = true;
+ };
+ }, [hasWarnings]);
const infoOnly = !SeriesDescription;
@@ -41,7 +43,7 @@ function ThumbnailFooter({
);
};
- const getWarningContent = (inconsistencyWarnings) => {
+ const getWarningContent = inconsistencyWarnings => {
if (Array.isArray(inconsistencyWarnings)) {
const listedWarnings = inconsistencyWarnings.map((warn, index) => {
return {warn};
@@ -54,8 +56,8 @@ function ThumbnailFooter({
};
const getWarningInfo = (SeriesNumber, inconsistencyWarnings) => {
- return(
-
+ return (
+
{inconsistencyWarnings && inconsistencyWarnings.length != 0 ? (
Series Inconsistencies
- {getWarningContent(inconsistencyWarnings)}
+
+ {getWarningContent(inconsistencyWarnings)}
+
}
>
@@ -79,9 +83,9 @@ function ThumbnailFooter({
) : (
- )}
+ )}
- );
+ );
};
const getSeriesInformation = (
SeriesNumber,
@@ -92,21 +96,27 @@ function ThumbnailFooter({
if (!SeriesNumber && !InstanceNumber && !numImageFrames) {
return;
}
- const seriesInformation =
+ const seriesInformation = (
{getInfo(SeriesNumber, 'S:')}
{getInfo(InstanceNumber, 'I:')}
{getInfo(numImageFrames, '', 'image-frames')}
{getWarningInfo(SeriesNumber, inconsistencyWarnings)}
+ );
- return (seriesInformation);
+ return seriesInformation;
};
return (
{SeriesDescription}
- {getSeriesInformation(SeriesNumber, InstanceNumber, numImageFrames, inconsistencyWarnings)}
+ {getSeriesInformation(
+ SeriesNumber,
+ InstanceNumber,
+ numImageFrames,
+ inconsistencyWarnings
+ )}
);
}
@@ -124,14 +134,37 @@ function Thumbnail(props) {
SeriesDescription,
SeriesNumber,
hasWarnings,
- stackPercentComplete,
StudyInstanceUID,
onClick,
onDoubleClick,
onMouseDown,
supportsDrag,
+ showProgressBar,
} = props;
+ const [stackPercentComplete, setStackPercentComplete] = useState(0);
+ useEffect(() => {
+ const onProgressChange = ({ detail }) => {
+ const { progressId, progressData } = detail;
+ if (`StackProgress:${displaySetInstanceUID}` === progressId) {
+ const percent = progressData ? progressData.percentComplete : 0;
+ setStackPercentComplete(percent);
+ }
+ };
+
+ document.addEventListener(
+ StudyLoadingListener.events.OnProgress,
+ onProgressChange
+ );
+
+ return () => {
+ document.removeEventListener(
+ StudyLoadingListener.events.OnProgress,
+ onProgressChange
+ );
+ };
+ }, [displaySetInstanceUID]);
+
const [collectedProps, drag, dragPreview] = useDrag({
// `droppedItem` in `dropTarget`
// The only data it will have access to
@@ -164,6 +197,7 @@ function Thumbnail(props) {
imageId={imageId}
error={error}
stackPercentComplete={stackPercentComplete}
+ showProgressBar={showProgressBar}
/>
)}
{/* SHOW TEXT ALTERNATIVE */}
@@ -203,6 +237,7 @@ Thumbnail.propTypes = {
onDoubleClick: PropTypes.func,
onClick: PropTypes.func,
onMouseDown: PropTypes.func,
+ showProgressBar: PropTypes.bool,
};
Thumbnail.defaultProps = {
diff --git a/platform/viewer/public/config/default.js b/platform/viewer/public/config/default.js
index 7bc3bc78e..9ca1920db 100644
--- a/platform/viewer/public/config/default.js
+++ b/platform/viewer/public/config/default.js
@@ -5,6 +5,15 @@ window.config = {
showStudyList: true,
filterQueryParam: false,
disableServersCache: false,
+ studyPrefetcher: {
+ enabled: true,
+ order: 'closest',
+ displaySetCount: 3,
+ preventCache: false,
+ prefetchDisplaySetsTimeout: 300,
+ displayProgress: true,
+ includeActiveDisplaySet: true,
+ },
servers: {
dicomWeb: [
{
diff --git a/platform/viewer/public/config/netlify.js b/platform/viewer/public/config/netlify.js
index b85f5b0ac..497d1135b 100644
--- a/platform/viewer/public/config/netlify.js
+++ b/platform/viewer/public/config/netlify.js
@@ -1,6 +1,15 @@
window.config = {
routerBasename: '/pwa',
showStudyList: true,
+ studyPrefetcher: {
+ enabled: true,
+ order: 'closest',
+ displaySetCount: 3,
+ preventCache: false,
+ prefetchDisplaySetsTimeout: 300,
+ displayProgress: true,
+ includeActiveDisplaySet: true,
+ },
servers: {
dicomWeb: [
{
diff --git a/platform/viewer/src/components/StudyLoadingMonitor.js b/platform/viewer/src/components/StudyLoadingMonitor.js
index 75698dc24..88a9b0a6b 100644
--- a/platform/viewer/src/components/StudyLoadingMonitor.js
+++ b/platform/viewer/src/components/StudyLoadingMonitor.js
@@ -1,42 +1,23 @@
-import { Component } from "react";
-import PropTypes from "prop-types";
-import OHIF from "@ohif/core";
+import { useEffect } from 'react';
+import OHIF from '@ohif/core';
-class StudyLoadingMonitor extends Component {
- static propTypes = {
- studies: PropTypes.array.isRequired,
- setStudyLoadingProgress: PropTypes.func.isRequired,
- clearStudyLoadingProgress: PropTypes.func.isRequired
- };
+const { StudyLoadingListener } = OHIF.classes;
- componentDidMount() {
- // TODO: This is pretty ugly. The thing is that the StudyLoadingListener
- // needs to update the Redux store, but shouldn't know that it exists.
- // I am therefore passing in some functions to update the store instead,
- // but this should definitely be cleaned up somehow.
- const options = {
- _setProgressData: (progressId, progressData) => {
- this.props.setStudyLoadingProgress(progressId, progressData);
- },
- _clearProgressById: progressId => {
- this.props.clearStudyLoadingProgress(progressId);
- }
+const StudyLoadingMonitor = ({ studies }) => {
+ useEffect(() => {
+ const studyLoadingListener = StudyLoadingListener.getInstance();
+
+ if (studies && studies.length > 0) {
+ studyLoadingListener.clear();
+ studyLoadingListener.addStudies(studies);
+ }
+
+ return () => {
+ studyLoadingListener.clear();
};
+ }, [studies]);
- const { StudyLoadingListener } = OHIF.classes;
- this.studyLoadingListener = StudyLoadingListener.getInstance(options);
- this.studyLoadingListener.clear();
- this.studyLoadingListener.addStudies(this.props.studies);
- }
-
- render() {
- return null;
- }
-
- componentWillUnmount() {
- // Destroy stack loading listeners when we close the viewer
- this.studyLoadingListener.clear();
- }
-}
+ return null;
+};
export default StudyLoadingMonitor;
diff --git a/platform/viewer/src/components/StudyPrefetcher.css b/platform/viewer/src/components/StudyPrefetcher.css
new file mode 100644
index 000000000..fe9acbfef
--- /dev/null
+++ b/platform/viewer/src/components/StudyPrefetcher.css
@@ -0,0 +1,15 @@
+.StudyPrefetcher {
+ height: 5px;
+ width: 100%;
+ display: flex;
+ flex-direction: row;
+ background-color: var(--default-color);
+}
+
+.StudyPrefetcher .item {
+ height: 5px;
+}
+
+.StudyPrefetcher .item.cached {
+ background-color: black;
+}
diff --git a/platform/viewer/src/components/StudyPrefetcher.js b/platform/viewer/src/components/StudyPrefetcher.js
index f6500776b..2a0bb4763 100644
--- a/platform/viewer/src/components/StudyPrefetcher.js
+++ b/platform/viewer/src/components/StudyPrefetcher.js
@@ -1,29 +1,93 @@
-import { Component } from "react";
-import PropTypes from "prop-types";
-import OHIF from "@ohif/core";
+import React, { useEffect } from 'react';
+import { classes, utils } from '@ohif/core';
+import PropTypes from 'prop-types';
+import cs from 'cornerstone-core';
-const { StudyPrefetcher } = OHIF.classes;
+import './StudyPrefetcher.css';
-class StudyPrefetcherComponent extends Component {
- static propTypes = {
- studies: PropTypes.array
- };
+const StudyPrefetcher = ({ studies, options }) => {
+ useEffect(() => {
+ const studyPrefetcher = classes.StudyPrefetcher.getInstance(
+ studies,
+ options
+ );
+ const studiesMetadata = studies.map(s =>
+ utils.studyMetadataManager.get(s.StudyInstanceUID)
+ );
+ studyPrefetcher.setStudies(studiesMetadata);
- componentDidMount() {
- const { studies } = this.props;
+ const onNewImage = ({ detail }) => {
+ /**
+ * When images are cached the viewport will load instantly and
+ * the display sets will not be available at this point in time.
+ *
+ * This code add display sets and updates the study prefetcher metadata.
+ */
+ const studiesMetadata = studies.map(s => {
+ const studyMetadata = utils.studyMetadataManager.get(
+ s.StudyInstanceUID
+ );
+ const displaySets = studyMetadata.getDisplaySets();
+ if (!displaySets || displaySets.length < 1) {
+ s.displaySets.forEach(ds => studyMetadata.addDisplaySet(ds));
+ }
+ return studyMetadata;
+ });
+ studyPrefetcher.setStudies(studiesMetadata);
- this.studyPrefetcher = StudyPrefetcher.getInstance();
- this.studyPrefetcher.setStudies(studies);
- }
+ const study = studyPrefetcher.getStudy(detail.image);
+ const series = studyPrefetcher.getSeries(study, detail.image);
+ const instance = studyPrefetcher.getInstance(series, detail.image);
- render() {
- return null;
- }
+ if (study.displaySets && study.displaySets.length > 0) {
+ const {
+ displaySetInstanceUID,
+ } = studyPrefetcher.getDisplaySetBySOPInstanceUID(
+ study.displaySets,
+ instance
+ );
+ studyPrefetcher.prefetch(detail.element, displaySetInstanceUID);
+ }
+ };
- componentWillUnmount() {
- // Stop prefetching when we close the viewer
- this.studyPrefetcher.destroy();
- }
-}
+ const onElementEnabled = ({ detail }) => {
+ detail.element.addEventListener(cs.EVENTS.NEW_IMAGE, onNewImage);
+ };
-export default StudyPrefetcherComponent;
+ cs.events.addEventListener(cs.EVENTS.ELEMENT_ENABLED, onElementEnabled);
+
+ return () => {
+ cs.events.removeEventListener(
+ cs.EVENTS.ELEMENT_ENABLED,
+ onElementEnabled
+ );
+ studyPrefetcher.destroy();
+ };
+ }, [options, studies]);
+
+ return null;
+};
+
+StudyPrefetcher.propTypes = {
+ studies: PropTypes.array.isRequired,
+ options: PropTypes.shape({
+ enabled: PropTypes.bool,
+ order: PropTypes.string,
+ displaySetCount: PropTypes.number,
+ preventCache: PropTypes.bool,
+ prefetchDisplaySetsTimeout: PropTypes.number,
+ includeActiveDisplaySet: PropTypes.bool,
+ }),
+};
+
+StudyPrefetcher.defaultProps = {
+ options: {
+ order: 'closest',
+ displaySetCount: 1,
+ preventCache: false,
+ prefetchDisplaySetsTimeout: 300,
+ includeActiveDisplaySet: false,
+ },
+};
+
+export default StudyPrefetcher;
diff --git a/platform/viewer/src/connectedComponents/ConnectedStudyBrowser.js b/platform/viewer/src/connectedComponents/ConnectedStudyBrowser.js
index 22efaeb12..0f6a129ce 100644
--- a/platform/viewer/src/connectedComponents/ConnectedStudyBrowser.js
+++ b/platform/viewer/src/connectedComponents/ConnectedStudyBrowser.js
@@ -1,44 +1,11 @@
import OHIF from '@ohif/core';
import { connect } from 'react-redux';
-import { StudyBrowser } from '@ohif/ui';
-import cloneDeep from 'lodash.clonedeep';
import findDisplaySetByUID from './findDisplaySetByUID';
import { servicesManager } from './../App.js';
-
-const { studyMetadataManager } = OHIF.utils;
+import { StudyBrowser } from '../../../ui/src/components/studyBrowser/StudyBrowser';
const { setActiveViewportSpecificData } = OHIF.redux.actions;
-// TODO
-// - Determine in which display set is active from Redux (activeViewportIndex and layout viewportData)
-// - Pass in errors and stack loading progress from Redux
-const mapStateToProps = (state, ownProps) => {
- // If we know that the stack loading progress details have changed,
- // we can try to update the component state so that the thumbnail
- // progress bar is updated
- const stackLoadingProgressMap = state.loading.progress;
- const studiesWithLoadingData = cloneDeep(ownProps.studies);
-
- studiesWithLoadingData.forEach(study => {
- study.thumbnails.forEach(data => {
- const { displaySetInstanceUID } = data;
- const stackId = `StackProgress:${displaySetInstanceUID}`;
- const stackProgressData = stackLoadingProgressMap[stackId];
-
- let stackPercentComplete = 0;
- if (stackProgressData) {
- stackPercentComplete = stackProgressData.percentComplete;
- }
-
- data.stackPercentComplete = stackPercentComplete;
- });
- });
-
- return {
- studies: studiesWithLoadingData,
- };
-};
-
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onThumbnailClick: displaySetInstanceUID => {
@@ -50,7 +17,10 @@ const mapDispatchToProps = (dispatch, ownProps) => {
if (displaySet.isDerived) {
const { Modality } = displaySet;
if (Modality === 'SEG' && servicesManager) {
- const {LoggerService, UINotificationService} = servicesManager.services;
+ const {
+ LoggerService,
+ UINotificationService,
+ } = servicesManager.services;
const onDisplaySetLoadFailureHandler = error => {
LoggerService.error({ error, message: error.message });
UINotificationService.show({
@@ -61,20 +31,25 @@ const mapDispatchToProps = (dispatch, ownProps) => {
});
};
- const {referencedDisplaySet, activatedLabelmapPromise} = displaySet.getSourceDisplaySet(
+ const {
+ referencedDisplaySet,
+ activatedLabelmapPromise,
+ } = displaySet.getSourceDisplaySet(
ownProps.studyMetadata,
true,
onDisplaySetLoadFailureHandler
);
displaySet = referencedDisplaySet;
- activatedLabelmapPromise.then((activatedLabelmapIndex) => {
- const selectionFired = new CustomEvent("extensiondicomsegmentationsegselected", {
- "detail": {"activatedLabelmapIndex":activatedLabelmapIndex}
- });
+ activatedLabelmapPromise.then(activatedLabelmapIndex => {
+ const selectionFired = new CustomEvent(
+ 'extensiondicomsegmentationsegselected',
+ {
+ detail: { activatedLabelmapIndex: activatedLabelmapIndex },
+ }
+ );
document.dispatchEvent(selectionFired);
});
-
} else {
displaySet = displaySet.getSourceDisplaySet(ownProps.studyMetadata);
}
@@ -96,7 +71,7 @@ const mapDispatchToProps = (dispatch, ownProps) => {
};
const ConnectedStudyBrowser = connect(
- mapStateToProps,
+ null,
mapDispatchToProps
)(StudyBrowser);
diff --git a/platform/viewer/src/connectedComponents/ConnectedStudyLoadingMonitor.js b/platform/viewer/src/connectedComponents/ConnectedStudyLoadingMonitor.js
deleted file mode 100644
index 8bfffe88a..000000000
--- a/platform/viewer/src/connectedComponents/ConnectedStudyLoadingMonitor.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import { connect } from "react-redux";
-import StudyLoadingMonitor from "../components/StudyLoadingMonitor.js";
-import OHIF from "@ohif/core";
-
-const {
- setStudyLoadingProgress,
- clearStudyLoadingProgress
-} = OHIF.redux.actions;
-
-const mapDispatchToProps = dispatch => {
- return {
- setStudyLoadingProgress: (progressId, progressData) => {
- dispatch(setStudyLoadingProgress(progressId, progressData));
- },
- clearStudyLoadingProgress: progressId => {
- dispatch(clearStudyLoadingProgress(progressId));
- }
- };
-};
-
-const ConnectedStudyLoadingMonitor = connect(
- null,
- mapDispatchToProps
-)(StudyLoadingMonitor);
-
-export default ConnectedStudyLoadingMonitor;
diff --git a/platform/viewer/src/connectedComponents/Viewer.js b/platform/viewer/src/connectedComponents/Viewer.js
index c4e2b25d2..e6152c872 100644
--- a/platform/viewer/src/connectedComponents/Viewer.js
+++ b/platform/viewer/src/connectedComponents/Viewer.js
@@ -1,11 +1,10 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
-import { useLogger } from '@ohif/ui';
-
import OHIF, { MODULE_TYPES, DICOMSR } from '@ohif/core';
import { withDialog } from '@ohif/ui';
import moment from 'moment';
+
import ConnectedHeader from './ConnectedHeader.js';
import ToolbarRow from './ToolbarRow.js';
import ConnectedStudyBrowser from './ConnectedStudyBrowser.js';
@@ -14,7 +13,6 @@ import SidePanel from './../components/SidePanel.js';
import ErrorBoundaryDialog from './../components/ErrorBoundaryDialog';
import { extensionManager } from './../App.js';
import { ReconstructionIssues } from './../../../core/src/enums.js';
-import dcmjs from 'dcmjs';
// Contexts
import WhiteLabelingContext from '../context/WhiteLabelingContext.js';
@@ -22,8 +20,8 @@ import UserManagerContext from '../context/UserManagerContext';
import AppContext from '../context/AppContext';
import './Viewer.css';
-import { finished } from 'stream';
-import { cornerstoneWADOImageLoader } from 'cornerstone-wado-image-loader';
+import StudyPrefetcher from '../components/StudyPrefetcher.js';
+import StudyLoadingMonitor from '../components/StudyLoadingMonitor';
class Viewer extends Component {
static propTypes = {
@@ -197,11 +195,17 @@ class Viewer extends Component {
]);
}
- const activeViewport = this.props.viewports[this.props.activeViewportIndex];
- const activeDisplaySetInstanceUID =
- activeViewport ? activeViewport.displaySetInstanceUID : undefined;
+ const activeViewport = this.props.viewports[
+ this.props.activeViewportIndex
+ ];
+ const activeDisplaySetInstanceUID = activeViewport
+ ? activeViewport.displaySetInstanceUID
+ : undefined;
this.setState({
- thumbnails: _mapStudiesToThumbnails(studies, activeDisplaySetInstanceUID),
+ thumbnails: _mapStudiesToThumbnails(
+ studies,
+ activeDisplaySetInstanceUID
+ ),
});
}
}
@@ -211,24 +215,31 @@ class Viewer extends Component {
studies,
isStudyLoaded,
activeViewportIndex,
- viewports
+ viewports,
} = this.props;
const activeViewport = viewports[activeViewportIndex];
- const activeDisplaySetInstanceUID =
- activeViewport ? activeViewport.displaySetInstanceUID : undefined;
+ const activeDisplaySetInstanceUID = activeViewport
+ ? activeViewport.displaySetInstanceUID
+ : undefined;
- const prevActiveViewport = prevProps.viewports[prevProps.activeViewportIndex];
- const prevActiveDisplaySetInstanceUID =
- prevActiveViewport ? prevActiveViewport.displaySetInstanceUID : undefined;
+ const prevActiveViewport =
+ prevProps.viewports[prevProps.activeViewportIndex];
+ const prevActiveDisplaySetInstanceUID = prevActiveViewport
+ ? prevActiveViewport.displaySetInstanceUID
+ : undefined;
- if (studies !== prevProps.studies ||
+ if (
+ studies !== prevProps.studies ||
activeViewportIndex !== prevProps.activeViewportIndex ||
activeDisplaySetInstanceUID !== prevActiveDisplaySetInstanceUID
- ) {
-
+ ) {
this.setState({
- thumbnails: _mapStudiesToThumbnails(studies, activeDisplaySetInstanceUID),
+ thumbnails: _mapStudiesToThumbnails(
+ studies,
+ activeDisplaySetInstanceUID
+ ),
+ activeDisplaySetInstanceUID,
});
}
if (isStudyLoaded && isStudyLoaded !== prevProps.isStudyLoaded) {
@@ -288,14 +299,12 @@ class Viewer extends Component {
)}
-
{/* TOOLBAR */}
-
- {/**/}
- {/**/}
-
+
+ {appContext => }
+
{/* VIEWPORTS + SIDEPANELS */}
{/* LEFT */}
@@ -348,10 +356,24 @@ class Viewer extends Component {
activeIndex={this.props.activeViewportIndex}
/>
) : (
-
+
+ {appContext => {
+ const { appConfig } = appContext;
+ const { studyPrefetcher } = appConfig;
+ const { thumbnails } = this.state;
+ return (
+
+ );
+ }}
+
)}
@@ -359,6 +381,22 @@ class Viewer extends Component {
{/* MAIN */}
+
+ {appContext => {
+ const { appConfig } = appContext;
+ const { studyPrefetcher } = appConfig;
+ const { studies } = this.props;
+ return (
+ studyPrefetcher &&
+ studyPrefetcher.enabled && (
+
+ )
+ );
+ }}
+
{
switch (warning) {
case ReconstructionIssues.DATASET_4D:
inconsistencyWarnings.push('The dataset is 4D.');
break;
case ReconstructionIssues.VARYING_IMAGESDIMENSIONS:
- inconsistencyWarnings.push('The dataset frames have different dimensions (rows, columns).');
+ inconsistencyWarnings.push(
+ 'The dataset frames have different dimensions (rows, columns).'
+ );
break;
case ReconstructionIssues.VARYING_IMAGESCOMPONENTS:
- inconsistencyWarnings.push('The dataset frames have different components (Sample per pixel).');
+ inconsistencyWarnings.push(
+ 'The dataset frames have different components (Sample per pixel).'
+ );
break;
case ReconstructionIssues.VARYING_IMAGESORIENTATION:
- inconsistencyWarnings.push('The dataset frames have different orientation.');
+ inconsistencyWarnings.push(
+ 'The dataset frames have different orientation.'
+ );
break;
case ReconstructionIssues.IRREGULAR_SPACING:
- inconsistencyWarnings.push('The dataset frames have different pixel spacing.');
+ inconsistencyWarnings.push(
+ 'The dataset frames have different pixel spacing.'
+ );
break;
case ReconstructionIssues.MULTIFFRAMES:
inconsistencyWarnings.push('The dataset is a multiframes.');
@@ -443,13 +495,22 @@ const _checkForSeriesInconsistencesWarnings = async function (displaySet, studie
break;
}
});
- inconsistencyWarnings.push('The datasets is not a reconstructable 3D volume. MPR mode is not available.');
+ inconsistencyWarnings.push(
+ 'The datasets is not a reconstructable 3D volume. MPR mode is not available.'
+ );
}
- if (displaySet.missingFrames &&
+ if (
+ displaySet.missingFrames &&
(!displaySet.reconstructionIssues ||
- (displaySet.reconstructionIssues && !displaySet.reconstructionIssues.find(warn => warn === ReconstructionIssues.DATASET_4D)))) {
- inconsistencyWarnings.push('The datasets is missing frames: ' + displaySet.missingFrames + '.');
+ (displaySet.reconstructionIssues &&
+ !displaySet.reconstructionIssues.find(
+ warn => warn === ReconstructionIssues.DATASET_4D
+ )))
+ ) {
+ inconsistencyWarnings.push(
+ 'The datasets is missing frames: ' + displaySet.missingFrames + '.'
+ );
}
} else {
const segMetadata = displaySet.metadata;
@@ -458,24 +519,31 @@ const _checkForSeriesInconsistencesWarnings = async function (displaySet, studie
return inconsistencyWarnings;
}
- const { referencedDisplaySet } = displaySet.getSourceDisplaySet(studies, false);
+ const { referencedDisplaySet } = displaySet.getSourceDisplaySet(
+ studies,
+ false
+ );
if (!referencedDisplaySet) {
displaySet.inconsistencyWarnings = inconsistencyWarnings;
return inconsistencyWarnings;
}
- const imageIds = referencedDisplaySet.images.map(image => image.getImageId());
+ const imageIds = referencedDisplaySet.images.map(image =>
+ image.getImageId()
+ );
if (!imageIds || imageIds.length === 0) {
displaySet.inconsistencyWarnings = inconsistencyWarnings;
return inconsistencyWarnings;
}
for (
- let i = 0, groupsLen = segMetadata.PerFrameFunctionalGroupsSequence.length;
+ let i = 0,
+ groupsLen = segMetadata.PerFrameFunctionalGroupsSequence.length;
i < groupsLen;
++i
) {
- const PerFrameFunctionalGroups = segMetadata.PerFrameFunctionalGroupsSequence[i];
+ const PerFrameFunctionalGroups =
+ segMetadata.PerFrameFunctionalGroupsSequence[i];
if (!PerFrameFunctionalGroups) {
continue;
}
@@ -485,29 +553,27 @@ const _checkForSeriesInconsistencesWarnings = async function (displaySet, studie
SourceImageSequence = segMetadata.SourceImageSequence[i];
} else if (PerFrameFunctionalGroups.DerivationImageSequence) {
SourceImageSequence =
- PerFrameFunctionalGroups.DerivationImageSequence
- .SourceImageSequence;
+ PerFrameFunctionalGroups.DerivationImageSequence.SourceImageSequence;
}
if (!SourceImageSequence) {
if (inconsistencyWarnings.length === 0) {
- const warningMessage = 'The segmentation ' +
+ const warningMessage =
+ 'The segmentation ' +
'has frames out of plane respect to the source images.';
- inconsistencyWarnings.push(warningMessage);
+ inconsistencyWarnings.push(warningMessage);
}
continue;
}
- const {
- ReferencedSOPInstanceUID,
- } = SourceImageSequence;
+ const { ReferencedSOPInstanceUID } = SourceImageSequence;
const imageId = imageIds.find(imageId => {
const sopCommonModule = cornerstone.metaData.get(
- "sopCommonModule",
- imageId
+ 'sopCommonModule',
+ imageId
);
if (!sopCommonModule) {
- return;
+ return;
}
return sopCommonModule.sopInstanceUID === ReferencedSOPInstanceUID;
@@ -517,33 +583,32 @@ const _checkForSeriesInconsistencesWarnings = async function (displaySet, studie
continue;
}
- const sourceImageMetadata = cornerstone.metaData.get(
- "instance",
- imageId
- );
+ const sourceImageMetadata = cornerstone.metaData.get('instance', imageId);
if (
segMetadata.Rows !== sourceImageMetadata.Rows ||
segMetadata.Columns !== sourceImageMetadata.Columns
) {
- const warningMessage = 'The segmentation ' +
+ const warningMessage =
+ 'The segmentation ' +
'has frames with different geometry ' +
'dimensions (Rows and Columns) respect to the source images.';
- inconsistencyWarnings.push(warningMessage);
+ inconsistencyWarnings.push(warningMessage);
break;
}
}
if (inconsistencyWarnings.length !== 0) {
- const warningMessage = 'The segmentation format is not supported yet. ' +
+ const warningMessage =
+ 'The segmentation format is not supported yet. ' +
'The segmentation data (segments) could not be loaded.';
- inconsistencyWarnings.push(warningMessage);
+ inconsistencyWarnings.push(warningMessage);
}
}
// cache the warnings
displaySet.inconsistencyWarnings = inconsistencyWarnings;
return inconsistencyWarnings;
-}
+};
/**
* Checks if display set is active, i.e. if the series is currently shown
@@ -562,30 +627,42 @@ const _checkForSeriesInconsistencesWarnings = async function (displaySet, studie
* @param {string} activeDisplaySetInstanceUID
* @returns {boolean} is active.
*/
- const _isDisplaySetActive = function(displaySet, studies, activeDisplaySetInstanceUID) {
+const _isDisplaySetActive = function(
+ displaySet,
+ studies,
+ activeDisplaySetInstanceUID
+) {
let active = false;
- const {
- displaySetInstanceUID,
- } = displaySet;
+ const { displaySetInstanceUID } = displaySet;
// TO DO: in the future, we could possibly support new modalities
// we should have a list of all modalities here, instead of having hard coded checks
- if (displaySet.Modality !== 'SEG' &&
+ if (
+ displaySet.Modality !== 'SEG' &&
displaySet.Modality !== 'RTSTRUCT' &&
- displaySet.Modality !== 'RTDOSE') {
+ displaySet.Modality !== 'RTDOSE'
+ ) {
active = activeDisplaySetInstanceUID === displaySetInstanceUID;
- } else if (displaySet.getSourceDisplaySet){
+ } else if (displaySet.getSourceDisplaySet) {
if (displaySet.Modality === 'SEG') {
- const { referencedDisplaySet } = displaySet.getSourceDisplaySet(studies, false);
- active = referencedDisplaySet ?
- activeDisplaySetInstanceUID === referencedDisplaySet.displaySetInstanceUID :
- false;
+ const { referencedDisplaySet } = displaySet.getSourceDisplaySet(
+ studies,
+ false
+ );
+ active = referencedDisplaySet
+ ? activeDisplaySetInstanceUID ===
+ referencedDisplaySet.displaySetInstanceUID
+ : false;
} else {
- const referencedDisplaySet = displaySet.getSourceDisplaySet(studies, false);
- active = referencedDisplaySet ?
- activeDisplaySetInstanceUID === referencedDisplaySet.displaySetInstanceUID :
- false;
+ const referencedDisplaySet = displaySet.getSourceDisplaySet(
+ studies,
+ false
+ );
+ active = referencedDisplaySet
+ ? activeDisplaySetInstanceUID ===
+ referencedDisplaySet.displaySetInstanceUID
+ : false;
}
}
@@ -629,11 +706,17 @@ const _mapStudiesToThumbnails = function(studies, activeDisplaySetInstanceUID) {
altImageText = displaySet.Modality ? displaySet.Modality : 'UN';
}
- const hasWarnings = _checkForSeriesInconsistencesWarnings(displaySet, studies);
- const active = _isDisplaySetActive(displaySet, studies, activeDisplaySetInstanceUID)
+ const hasWarnings = _checkForSeriesInconsistencesWarnings(
+ displaySet,
+ studies
+ );
return {
- active,
+ active: _isDisplaySetActive(
+ displaySet,
+ studies,
+ activeDisplaySetInstanceUID
+ ),
imageId,
altImageText,
displaySetInstanceUID,