From b161039e59f0e3d5acd688c82aec02a3e6603046 Mon Sep 17 00:00:00 2001 From: Ouwen Huang Date: Mon, 13 Mar 2023 03:30:09 +0000 Subject: [PATCH] feat: handle large image volumes --- extensions/cornerstone/src/init.tsx | 2 + .../CornerstoneCacheService.ts | 33 +++++++++++++++- .../src/classes/{ImageSet.js => ImageSet.ts} | 38 ++++++++++++++----- platform/viewer/public/config/default.js | 3 ++ 4 files changed, 65 insertions(+), 11 deletions(-) rename platform/core/src/classes/{ImageSet.js => ImageSet.ts} (72%) diff --git a/extensions/cornerstone/src/init.tsx b/extensions/cornerstone/src/init.tsx index fa70d7a567..6cbcd2212c 100644 --- a/extensions/cornerstone/src/init.tsx +++ b/extensions/cornerstone/src/init.tsx @@ -71,12 +71,14 @@ export default async function init({ uiNotificationService, cineService, cornerstoneViewportService, + cornerstoneCacheService, hangingProtocolService, toolGroupService, viewportGridService, } = servicesManager.services; window.services = servicesManager.services; + cornerstoneCacheService.setMaxFramesInVolume(appConfig?.maxFramesInVolume); if ( appConfig.showWarningMessageForCrossOrigin && diff --git a/extensions/cornerstone/src/services/CornerstoneCacheService/CornerstoneCacheService.ts b/extensions/cornerstone/src/services/CornerstoneCacheService/CornerstoneCacheService.ts index 01a359368d..0aaac703c7 100644 --- a/extensions/cornerstone/src/services/CornerstoneCacheService/CornerstoneCacheService.ts +++ b/extensions/cornerstone/src/services/CornerstoneCacheService/CornerstoneCacheService.ts @@ -22,11 +22,26 @@ class CornerstoneCacheService { stackImageIds: Map = new Map(); volumeImageIds: Map = new Map(); + maxFramesInVolume = Infinity; + servicesManager: ServicesManager; - constructor(servicesManager) { + constructor(servicesManager: ServicesManager) { this.servicesManager = servicesManager; } + public setMaxFramesInVolume(numFrames: number): void { + this.maxFramesInVolume = numFrames || Infinity; + const { displaySetService } = this.servicesManager.services; + const displaySets = displaySetService.getActiveDisplaySets(); + displaySets.forEach(displaySet => + displaySetService.setDisplaySetMetadataInvalidated(displaySet.getUID()) + ); + } + + public getMaxFramesInVolume(): number { + return this.maxFramesInVolume; + } + public getCacheSize() { return cs3DCache.getCacheSize(); } @@ -186,9 +201,22 @@ class CornerstoneCacheService { displaySet, dataSource ); + const distributedCopy = (items, n) => { + const elements = [items[0]]; + const totalItems = items.length - 2; + const interval = totalItems / (n - 2); + for (let i = 1; i < n - 1; i++) { + elements.push(items[Math.floor(i * interval)]); + } + elements.push(items[items.length - 1]); + return elements; + }; volume = await volumeLoader.createAndCacheVolume(volumeId, { - imageIds: volumeImageIds, + imageIds: + volumeImageIds.length > this.maxFramesInVolume + ? distributedCopy(volumeImageIds, this.maxFramesInVolume) + : volumeImageIds, }); this.volumeImageIds.set( @@ -238,6 +266,7 @@ class CornerstoneCacheService { } private _getCornerstoneStackImageIds(displaySet, dataSource): string[] { + displaySet.sortByImagePositionPatient(); return dataSource.getImageIdsForDisplaySet(displaySet); } diff --git a/platform/core/src/classes/ImageSet.js b/platform/core/src/classes/ImageSet.ts similarity index 72% rename from platform/core/src/classes/ImageSet.js rename to platform/core/src/classes/ImageSet.ts index 9f4ba70741..6f813fabaa 100644 --- a/platform/core/src/classes/ImageSet.js +++ b/platform/core/src/classes/ImageSet.ts @@ -10,13 +10,15 @@ const OBJECT = 'object'; * indiscriminately, but this should be changed). */ class ImageSet { + private rawImages; + constructor(images) { if (Array.isArray(images) !== true) { throw new Error('ImageSet expects an array of images'); } - // @property "images" - Object.defineProperty(this, 'images', { + // @property "rawImages" + Object.defineProperty(this, 'rawImages', { enumerable: false, configurable: false, writable: false, @@ -32,6 +34,24 @@ class ImageSet { }); } + get images(): any { + /** + * Extensions can create arbitrary imagesMapper functions to filter/map + * these metadata. For example to only return the first 300 slices + * of a large volume, the below can be run in a command. + * + * const ds = services.DisplaySetService.getDisplaySetByUID(displaySetUID) + * ds.sortByImagePositionPatient() + * ds.setAttribute('imagesMapper', (ds)=> ds.slice(0, 300)) + * displaySetService.setDisplaySetMetadataInvalidated(displaySetUID) + */ + const imagesMapper = this.getAttribute('imagesMapper'); + if (imagesMapper && imagesMapper instanceof Function) { + return imagesMapper(this.rawImages); + } + return this.rawImages; + } + getUID() { return this.uid; } @@ -48,7 +68,7 @@ class ImageSet { if (typeof attributes === OBJECT && attributes !== null) { const imageSet = this, hasOwn = Object.prototype.hasOwnProperty; - for (let attribute in attributes) { + for (const attribute in attributes) { if (hasOwn.call(attributes, attribute)) { imageSet[attribute] = attributes[attribute]; } @@ -56,18 +76,18 @@ class ImageSet { } } - getNumImages = () => this.images.length + getNumImages = () => this.rawImages.length; getImage(index) { - return this.images[index]; + return this.rawImages[index]; } sortBy(sortingCallback) { - return this.images.sort(sortingCallback); + return this.rawImages.sort(sortingCallback); } sortByImagePositionPatient() { - const images = this.images; + const images = this.rawImages; const referenceImagePositionPatient = _getImagePositionPatient(images[0]); const refIppVec = new Vector3( @@ -114,11 +134,11 @@ class ImageSet { } function _getImagePositionPatient(image) { - return image.getData().metadata.ImagePositionPatient; + return image.ImagePositionPatient; } function _getImageOrientationPatient(image) { - return image.getData().metadata.ImageOrientationPatient; + return image.ImageOrientationPatient; } export default ImageSet; diff --git a/platform/viewer/public/config/default.js b/platform/viewer/public/config/default.js index c0de3708a9..b4d6af7b4b 100644 --- a/platform/viewer/public/config/default.js +++ b/platform/viewer/public/config/default.js @@ -10,6 +10,9 @@ window.config = { showStudyList: true, // some windows systems have issues with more than 3 web workers maxNumberOfWebWorkers: 3, + maxFramesInVolume: 400, + maxCacheSize: 1073741824, + // below flag is for performance reasons, but it might not work for all servers omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true,