Skip to content

Commit

Permalink
feat: handle large image volumes
Browse files Browse the repository at this point in the history
  • Loading branch information
Ouwen committed Mar 13, 2023
1 parent eab28b7 commit b161039
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 11 deletions.
2 changes: 2 additions & 0 deletions extensions/cornerstone/src/init.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,26 @@ class CornerstoneCacheService {

stackImageIds: Map<string, string[]> = new Map();
volumeImageIds: Map<string, string[]> = 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();
}
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -238,6 +266,7 @@ class CornerstoneCacheService {
}

private _getCornerstoneStackImageIds(displaySet, dataSource): string[] {
displaySet.sortByImagePositionPatient();
return dataSource.getImageIdsForDisplaySet(displaySet);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
}
Expand All @@ -48,26 +68,26 @@ 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];
}
}
}
}

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(
Expand Down Expand Up @@ -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;
3 changes: 3 additions & 0 deletions platform/viewer/public/config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit b161039

Please sign in to comment.