feat: Add DICOM SEG support and MPR, referenceLines and StackSync and more (#3015)
* feat: add initial sop class handler for SEG * feat: move segmentation service * update segmentation service methods * fix: viewport data structure * feat: Add initial render for the DICOM SEG for each displaySet * fix: various wrong architectural dependencies between services * feat: initial separate SEG display in each viewport * feat: refactore viewport action bar * initial work for SEG hydration * fix: various bugs regarding drag and dropping different viewports * fix: rendering issues for multiple seg displaysets * fix: bugs for thumbnail and hydration * feat: fix the initial segment color * update after rebase * feat: initial design for the segmentation group table * feat: initial new design for the segmentation panel * feat: segmentation panel * feat: make segmentation appear on all related viewports * fix: segmentation load bug based on functional groups * initial work for segmentation crosshairs * fix: various stylings and functionality * fix: various stylings for the seg panel * fix: overflow styles * feat: add more ui components * feat: added segmentation config * feat: add jump to segment * feat: add jump to segment for DICOM SEG viewport * fix: bugs after rebase * feat: jump in segmentation viewport * fix: mpr support for seg * feat: add more icons * feat: new icons * feat: add new side panel * feat: add segmentation config * feat: add loading indicator to ohif * feat: make hanging protocols follow matching rules for viewports * feat: enhance drag and drop to be hanging protocol aware * fix: mpr restore previous layout * fix: crosshairs toggle * fix: add auth headers to the dicom loader via dicomwebclient * fix: bug for crosshairs toggle in mpr * fix: seg viewport reusing old toolGroup * feat: add loading animation with lottie * fix: various bugs for Segmentation hydration in mpr * feat: change outline alpha to outline opacity * feat: loading indicator for seg viewport * fix: various segmentation group styles * fix: local mode for seg * feat: add animation for the highlight * fix: loading indicatro to show segment indices * feat: enhance modality drop down ui * fix: panels * fix: download form * fix: layout shift in loading indicator * fix: loading indicator to have correct values * fix: update software number * fix: image jump between MPR and default * fix: segmentation cleanup and cine service cleanup * fix: segmentation toolgroup clena up * fix: highlight interval should not trigger again * fix: issue with multiframe sorting * rename: change onSeriesChange to onArrowsClick * fix: buttons for tmtv and layout shift * fix: various bugs wrt crosshairs * fix: crosshairs re init on reset camera * fix: middle slice calculation different from cs middle reset camera * fix: reset camera should reset viewport camera * fix: loading segments in MPR mode * fix: tmtv hp back to before * fix: layout shift * feat: orientation markers for volume viewport * fix: capture for volume viewports * fix: memory leak for back to worklist * fix: loading bar bg color * fix: side panel for only one panel * fix: react select style in production * fix: various styling for segmentation groups * fix: various ui styles * feat: add new segmentation config styles * fix: hover state for segmentation item * fix: side panel layout shift * temp add panels * try to fix scrollbar for thimbnail * fix: scroll not appearing for side panels * feat: changed styles for scrollbar * feat: add cpu fallback warning * fix: webworker destroy * fix: select styles * fix: orientation marker color and position * fix: capture screenshot for volume viewports * fix: hide segment visibility on mpr * fix: reloading an already loaded seg displayset * feat: add is equal * fix: mpr jump to measurements * apply review comments * fix: optimization for the segmentation load * fix: remove unnecessary context menu and hp service reset * fix: segmentation service wrt brush settings * feat: initial work for the stack synchronization * fix: add validateDisplaySetSelectorsForNewDisplaySets to make sure drag and drop can follow requirements * fix: various react proptypes * fix: thumbnails for the tmtv and change default props to default params * feat: make showing loading indicator configurable and add docs * bump versions and add more docs * fix demo * update cornerstone versions * apply review comments * feat: add reference lines * fix build * fix unit tests * add reference lines icon * fix: e2e tests * fix static wado config * docs: add segmentation service docs * fix: docker build * fix: keep camera and bump versions * Try re-enabling minification to fix deploy previews Co-authored-by: Erik Ziegler <erik.sweed@gmail.com>
This commit is contained in:
1 parent
08c58b0634
commit
83ea228d86
254 files changed
+12856
-3141
No files matched your search
@@ -1,231 +0,0 @@
|
||||
import {
|
||||
cache as cs3DCache,
|
||||
Enums,
|
||||
Types,
|
||||
volumeLoader,
|
||||
} from '@cornerstonejs/core';
|
||||
import { utils, pubSubServiceInterface } from '@ohif/core';
|
||||
|
||||
import getCornerstoneViewportType from '../../utils/getCornerstoneViewportType';
|
||||
|
||||
export type StackData = {
|
||||
StudyInstanceUID: string;
|
||||
displaySetInstanceUID: string;
|
||||
imageIds: string[];
|
||||
frameRate?: number;
|
||||
isClip?: boolean;
|
||||
initialImageIndex?: number | string | null;
|
||||
viewportType: Enums.ViewportType;
|
||||
};
|
||||
|
||||
export type VolumeData = {
|
||||
StudyInstanceUID: string;
|
||||
displaySetInstanceUIDs: string[]; // can have more than one displaySet (fusion)
|
||||
imageIds: string[][]; // can have more than one imageId list (fusion)
|
||||
volumes: Types.IVolume[];
|
||||
viewportType: Enums.ViewportType;
|
||||
};
|
||||
|
||||
const VOLUME_LOADER_SCHEME = 'streaming-wadors';
|
||||
|
||||
const EVENTS = {
|
||||
VIEWPORT_DATA_CHANGED: 'event::cornerstone::viewportdatachanged',
|
||||
};
|
||||
|
||||
export type IViewportData = StackData | VolumeData;
|
||||
|
||||
class CornerstoneCacheService {
|
||||
stackImageIds: Map<string, string[]> = new Map();
|
||||
volumeImageIds: Map<string, string[]> = new Map();
|
||||
listeners: { [key: string]: (...args: any[]) => void } = {};
|
||||
EVENTS: { [key: string]: string };
|
||||
|
||||
constructor() {
|
||||
this.listeners = {};
|
||||
this.EVENTS = EVENTS;
|
||||
Object.assign(this, pubSubServiceInterface);
|
||||
}
|
||||
|
||||
public getCacheSize() {
|
||||
return cs3DCache.getCacheSize();
|
||||
}
|
||||
|
||||
public getCacheFreeSpace() {
|
||||
return cs3DCache.getBytesAvailable();
|
||||
}
|
||||
|
||||
public async getViewportData(
|
||||
viewportIndex: number,
|
||||
displaySets: unknown[],
|
||||
viewportType: string,
|
||||
dataSource: unknown,
|
||||
callback: (val: IViewportData) => unknown,
|
||||
initialImageIndex?: number
|
||||
): Promise<StackData | VolumeData> {
|
||||
const cs3DViewportType = getCornerstoneViewportType(viewportType);
|
||||
let viewportData: IViewportData;
|
||||
|
||||
if (cs3DViewportType === Enums.ViewportType.STACK) {
|
||||
viewportData = await this._getStackViewportData(
|
||||
dataSource,
|
||||
displaySets,
|
||||
initialImageIndex
|
||||
);
|
||||
}
|
||||
|
||||
if (cs3DViewportType === Enums.ViewportType.ORTHOGRAPHIC) {
|
||||
viewportData = await this._getVolumeViewportData(dataSource, displaySets);
|
||||
}
|
||||
|
||||
viewportData.viewportType = cs3DViewportType;
|
||||
|
||||
await callback(viewportData);
|
||||
|
||||
this._broadcastEvent(this.EVENTS.VIEWPORT_DATA_CHANGED, {
|
||||
viewportData,
|
||||
viewportIndex,
|
||||
});
|
||||
|
||||
return viewportData;
|
||||
}
|
||||
public async invalidateViewportData(
|
||||
viewportData: VolumeData,
|
||||
invalidatedDisplaySetInstanceUID: string,
|
||||
dataSource,
|
||||
DisplaySetService
|
||||
) {
|
||||
if (viewportData.viewportType === Enums.ViewportType.STACK) {
|
||||
throw new Error('Invalidation of StackViewport is not supported yet');
|
||||
}
|
||||
|
||||
const volumeId = invalidatedDisplaySetInstanceUID;
|
||||
const volume = cs3DCache.getVolume(volumeId);
|
||||
|
||||
if (volume) {
|
||||
cs3DCache.removeVolumeLoadObject(volumeId);
|
||||
}
|
||||
|
||||
const displaySets = viewportData.displaySetInstanceUIDs.map(
|
||||
DisplaySetService.getDisplaySetByUID
|
||||
);
|
||||
|
||||
const newViewportData = await this._getVolumeViewportData(
|
||||
dataSource,
|
||||
displaySets
|
||||
);
|
||||
|
||||
return newViewportData;
|
||||
}
|
||||
|
||||
private _getStackViewportData(
|
||||
dataSource,
|
||||
displaySets,
|
||||
initialImageIndex
|
||||
): StackData {
|
||||
// For Stack Viewport we don't have fusion currently
|
||||
const displaySet = displaySets[0];
|
||||
|
||||
let stackImageIds = this.stackImageIds.get(
|
||||
displaySet.displaySetInstanceUID
|
||||
);
|
||||
|
||||
if (!stackImageIds) {
|
||||
stackImageIds = this._getCornerstoneStackImageIds(displaySet, dataSource);
|
||||
this.stackImageIds.set(displaySet.displaySetInstanceUID, stackImageIds);
|
||||
}
|
||||
|
||||
const { displaySetInstanceUID, StudyInstanceUID } = displaySet;
|
||||
|
||||
const stackData: StackData = {
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
viewportType: Enums.ViewportType.STACK,
|
||||
imageIds: stackImageIds,
|
||||
};
|
||||
|
||||
if (typeof initialImageIndex === 'number') {
|
||||
stackData.initialImageIndex = initialImageIndex;
|
||||
}
|
||||
|
||||
return stackData;
|
||||
}
|
||||
|
||||
private async _getVolumeViewportData(
|
||||
dataSource,
|
||||
displaySets
|
||||
): Promise<VolumeData> {
|
||||
// Check the cache for multiple scenarios to see if we need to
|
||||
// decache the volume data from other viewports or not
|
||||
|
||||
const volumeImageIdsArray = [];
|
||||
const volumes = [];
|
||||
|
||||
for (const displaySet of displaySets) {
|
||||
const volumeId = displaySet.displaySetInstanceUID;
|
||||
|
||||
let volumeImageIds = this.volumeImageIds.get(
|
||||
displaySet.displaySetInstanceUID
|
||||
);
|
||||
|
||||
let volume = cs3DCache.getVolume(volumeId);
|
||||
|
||||
if (!volumeImageIds || !volume) {
|
||||
volumeImageIds = this._getCornerstoneVolumeImageIds(
|
||||
displaySet,
|
||||
dataSource
|
||||
);
|
||||
|
||||
volume = await volumeLoader.createAndCacheVolume(volumeId, {
|
||||
imageIds: volumeImageIds,
|
||||
});
|
||||
|
||||
this.volumeImageIds.set(
|
||||
displaySet.displaySetInstanceUID,
|
||||
volumeImageIds
|
||||
);
|
||||
}
|
||||
|
||||
volumeImageIdsArray.push(volumeImageIds);
|
||||
volumes.push(volume);
|
||||
}
|
||||
|
||||
// assert displaySets are from the same study
|
||||
const { StudyInstanceUID } = displaySets[0];
|
||||
const displaySetInstanceUIDs = [];
|
||||
|
||||
displaySets.forEach(displaySet => {
|
||||
if (displaySet.StudyInstanceUID !== StudyInstanceUID) {
|
||||
throw new Error('Display sets are not from the same study');
|
||||
}
|
||||
|
||||
displaySetInstanceUIDs.push(displaySet.displaySetInstanceUID);
|
||||
});
|
||||
|
||||
return {
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUIDs,
|
||||
imageIds: volumeImageIdsArray,
|
||||
viewportType: Enums.ViewportType.ORTHOGRAPHIC,
|
||||
volumes,
|
||||
};
|
||||
}
|
||||
|
||||
private _getCornerstoneStackImageIds(displaySet, dataSource): string[] {
|
||||
return dataSource.getImageIdsForDisplaySet(displaySet);
|
||||
}
|
||||
|
||||
private _getCornerstoneVolumeImageIds(displaySet, dataSource): string[] {
|
||||
const stackImageIds = this._getCornerstoneStackImageIds(
|
||||
displaySet,
|
||||
dataSource
|
||||
);
|
||||
|
||||
return stackImageIds.map(imageId => {
|
||||
const imageURI = utils.imageIdToURI(imageId);
|
||||
return `${VOLUME_LOADER_SCHEME}:${imageURI}`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const CacheService = new CornerstoneCacheService();
|
||||
export default CacheService;
|
||||
+201
-44
@@ -2,7 +2,6 @@ import { pubSubServiceInterface } from '@ohif/core';
|
||||
import {
|
||||
RenderingEngine,
|
||||
StackViewport,
|
||||
Enums,
|
||||
Types,
|
||||
getRenderingEngine,
|
||||
utilities as csUtils,
|
||||
@@ -18,7 +17,10 @@ import ViewportInfo, {
|
||||
DisplaySetOptions,
|
||||
PublicViewportOptions,
|
||||
} from './Viewport';
|
||||
import { StackData, VolumeData } from './CornerstoneCacheService';
|
||||
import {
|
||||
StackViewportData,
|
||||
VolumeViewportData,
|
||||
} from '../../types/CornerstoneCacheService';
|
||||
import {
|
||||
setColormap,
|
||||
setLowerUpperColorTransferFunction,
|
||||
@@ -27,8 +29,8 @@ import {
|
||||
import JumpPresets from '../../utils/JumpPresets';
|
||||
|
||||
const EVENTS = {
|
||||
VIEWPORT_INFO_CREATED:
|
||||
'event::cornerstone::viewportservice:viewportinfocreated',
|
||||
VIEWPORT_DATA_CHANGED:
|
||||
'event::cornerstoneViewportService:viewportDataChanged',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -39,8 +41,7 @@ class CornerstoneViewportService implements IViewportService {
|
||||
renderingEngine: Types.IRenderingEngine | null;
|
||||
viewportsInfo: Map<number, ViewportInfo>;
|
||||
viewportGridResizeObserver: ResizeObserver | null;
|
||||
// TODO - get the right type here.
|
||||
hangingProtocolService: object;
|
||||
viewportsDisplaySets: Map<string, string[]> = new Map();
|
||||
|
||||
/**
|
||||
* Service-specific
|
||||
@@ -52,6 +53,7 @@ class CornerstoneViewportService implements IViewportService {
|
||||
enableResizeDetector: true;
|
||||
resizeRefreshRateMs: 200;
|
||||
resizeRefreshMode: 'debounce';
|
||||
servicesManager = null;
|
||||
|
||||
constructor(servicesManager) {
|
||||
this.renderingEngine = null;
|
||||
@@ -60,8 +62,7 @@ class CornerstoneViewportService implements IViewportService {
|
||||
//
|
||||
this.listeners = {};
|
||||
this.EVENTS = EVENTS;
|
||||
const { HangingProtocolService } = servicesManager.services;
|
||||
this.hangingProtocolService = HangingProtocolService;
|
||||
this.servicesManager = servicesManager;
|
||||
Object.assign(this, pubSubServiceInterface);
|
||||
//
|
||||
}
|
||||
@@ -71,7 +72,7 @@ class CornerstoneViewportService implements IViewportService {
|
||||
* @param {*} viewportIndex
|
||||
* @param {*} elementRef
|
||||
*/
|
||||
public enableElement(
|
||||
public enableViewport(
|
||||
viewportIndex: number,
|
||||
viewportOptions: PublicViewportOptions,
|
||||
elementRef: HTMLDivElement
|
||||
@@ -87,6 +88,16 @@ class CornerstoneViewportService implements IViewportService {
|
||||
return `viewport-${viewportIndex}`;
|
||||
}
|
||||
|
||||
public getViewportIds(): string[] {
|
||||
const viewportIds = [];
|
||||
|
||||
this.viewportsInfo.forEach(viewportInfo => {
|
||||
viewportIds.push(viewportInfo.getViewportId());
|
||||
});
|
||||
|
||||
return viewportIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* It retrieves the renderingEngine if it does exist, or creates one otherwise
|
||||
* @returns {RenderingEngine} rendering engine
|
||||
@@ -112,10 +123,9 @@ class CornerstoneViewportService implements IViewportService {
|
||||
*/
|
||||
public resize() {
|
||||
const immediate = true;
|
||||
const resetPan = false;
|
||||
const resetZoom = false;
|
||||
const keepCamera = true;
|
||||
|
||||
this.renderingEngine.resize(immediate, resetPan, resetZoom);
|
||||
this.renderingEngine.resize(immediate, keepCamera);
|
||||
this.renderingEngine.render();
|
||||
}
|
||||
|
||||
@@ -126,8 +136,9 @@ class CornerstoneViewportService implements IViewportService {
|
||||
this._removeResizeObserver();
|
||||
this.viewportGridResizeObserver = null;
|
||||
this.renderingEngine.destroy();
|
||||
this.viewportsDisplaySets.clear();
|
||||
this.renderingEngine = null;
|
||||
cache.purgeVolumeCache();
|
||||
cache.purgeCache();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,12 +153,11 @@ class CornerstoneViewportService implements IViewportService {
|
||||
}
|
||||
|
||||
const viewportId = viewportInfo.getViewportId();
|
||||
this.renderingEngine.disableElement(viewportId);
|
||||
this.viewportsInfo.delete(viewportIndex);
|
||||
|
||||
if (this.viewportsInfo.size === 0) {
|
||||
this.destroy();
|
||||
}
|
||||
this.renderingEngine && this.renderingEngine.disableElement(viewportId);
|
||||
|
||||
this.viewportsInfo.get(viewportIndex).destroy();
|
||||
this.viewportsInfo.delete(viewportIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,9 +168,9 @@ class CornerstoneViewportService implements IViewportService {
|
||||
* @param {*} dataSource
|
||||
* @returns
|
||||
*/
|
||||
public setViewportDisplaySets(
|
||||
public setViewportData(
|
||||
viewportIndex: number,
|
||||
viewportData: StackData | VolumeData,
|
||||
viewportData: StackViewportData | VolumeViewportData,
|
||||
publicViewportOptions: PublicViewportOptions,
|
||||
publicDisplaySetOptions: DisplaySetOptions[]
|
||||
): void {
|
||||
@@ -179,8 +189,12 @@ class CornerstoneViewportService implements IViewportService {
|
||||
|
||||
viewportInfo.setViewportOptions(viewportOptions);
|
||||
viewportInfo.setDisplaySetOptions(displaySetOptions);
|
||||
viewportInfo.setViewportData(viewportData);
|
||||
|
||||
this._broadcastEvent(EVENTS.VIEWPORT_INFO_CREATED, viewportInfo);
|
||||
this._broadcastEvent(this.EVENTS.VIEWPORT_DATA_CHANGED, {
|
||||
viewportData,
|
||||
viewportIndex,
|
||||
});
|
||||
|
||||
const viewportId = viewportInfo.getViewportId();
|
||||
const element = viewportInfo.getElement();
|
||||
@@ -203,6 +217,7 @@ class CornerstoneViewportService implements IViewportService {
|
||||
// renderingEngine is designed to be used like this. This will trigger
|
||||
// ENABLED_ELEMENT again and again, which will run onEnableElement callbacks
|
||||
renderingEngine.enableElement(viewportInput);
|
||||
|
||||
this._setDisplaySets(viewportId, viewportData, viewportInfo);
|
||||
}
|
||||
|
||||
@@ -265,11 +280,18 @@ class CornerstoneViewportService implements IViewportService {
|
||||
|
||||
_setStackViewport(
|
||||
viewport: Types.IStackViewport,
|
||||
viewportData: StackData,
|
||||
viewportData: StackViewportData,
|
||||
viewportInfo: ViewportInfo
|
||||
) {
|
||||
const displaySetOptions = viewportInfo.getDisplaySetOptions();
|
||||
const { imageIds, initialImageIndex } = viewportData;
|
||||
|
||||
const {
|
||||
imageIds,
|
||||
initialImageIndex,
|
||||
displaySetInstanceUID,
|
||||
} = viewportData.data;
|
||||
|
||||
this.viewportsDisplaySets.set(viewport.id, [displaySetInstanceUID]);
|
||||
|
||||
let initialImageIndexToUse = initialImageIndex;
|
||||
|
||||
@@ -331,7 +353,15 @@ class CornerstoneViewportService implements IViewportService {
|
||||
}
|
||||
|
||||
if (preset === JumpPresets.Middle) {
|
||||
return Math.floor(lastSliceIndex / 2);
|
||||
// Note: this is a simple but yet very important formula.
|
||||
// since viewport reset works with the middle slice
|
||||
// if the below formula is not correct, on a viewport reset
|
||||
// it will jump to a different slice than the middle one which
|
||||
// was the initial slice, and we have some tools such as Crosshairs
|
||||
// which rely on a relative camera modifications and those will break.
|
||||
return lastSliceIndex % 2 === 0
|
||||
? lastSliceIndex / 2
|
||||
: (lastSliceIndex + 1) / 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -339,7 +369,7 @@ class CornerstoneViewportService implements IViewportService {
|
||||
|
||||
async _setVolumeViewport(
|
||||
viewport: Types.IVolumeViewport,
|
||||
viewportData: VolumeData,
|
||||
viewportData: VolumeViewportData,
|
||||
viewportInfo: ViewportInfo
|
||||
): Promise<void> {
|
||||
// TODO: We need to overhaul the way data sources work so requests can be made
|
||||
@@ -355,21 +385,24 @@ class CornerstoneViewportService implements IViewportService {
|
||||
// (This call may or may not create sub-requests for series metadata)
|
||||
const volumeInputArray = [];
|
||||
const displaySetOptionsArray = viewportInfo.getDisplaySetOptions();
|
||||
const { hangingProtocolService } = this;
|
||||
const { HangingProtocolService } = this.servicesManager.services;
|
||||
|
||||
for (let i = 0; i < viewportData.imageIds.length; i++) {
|
||||
const imageIds = viewportData.imageIds[i];
|
||||
const displaySetInstanceUID = viewportData.displaySetInstanceUIDs[i];
|
||||
const displaySetOptions = displaySetOptionsArray[i];
|
||||
const volumeToLoad = [];
|
||||
const displaySetInstanceUIDs = [];
|
||||
|
||||
const volumeId = displaySetInstanceUID;
|
||||
for (const [index, data] of viewportData.data.entries()) {
|
||||
const { volume, imageIds, displaySetInstanceUID } = data;
|
||||
|
||||
// if (displaySet.needsRerendering) {
|
||||
// console.warn('Removing volume from cache', volumeId);
|
||||
// cache.removeVolumeLoadObject(volumeId);
|
||||
// displaySet.needsRerendering = false;
|
||||
// this.displaySetsNeedRerendering.add(displaySet.displaySetInstanceUID);
|
||||
// }
|
||||
displaySetInstanceUIDs.push(displaySetInstanceUID);
|
||||
|
||||
if (!volume) {
|
||||
continue;
|
||||
}
|
||||
|
||||
volumeToLoad.push(volume);
|
||||
|
||||
const displaySetOptions = displaySetOptionsArray[index];
|
||||
const { volumeId } = volume;
|
||||
|
||||
const voiCallbacks = this._getVOICallbacks(volumeId, displaySetOptions);
|
||||
|
||||
@@ -386,18 +419,20 @@ class CornerstoneViewportService implements IViewportService {
|
||||
});
|
||||
}
|
||||
|
||||
this.viewportsDisplaySets.set(viewport.id, displaySetInstanceUIDs);
|
||||
|
||||
if (
|
||||
hangingProtocolService.hasCustomImageLoadStrategy() &&
|
||||
!hangingProtocolService.customImageLoadPerformed
|
||||
HangingProtocolService.hasCustomImageLoadStrategy() &&
|
||||
!HangingProtocolService.customImageLoadPerformed
|
||||
) {
|
||||
// delegate the volume loading to the hanging protocol service if it has a custom image load strategy
|
||||
return hangingProtocolService.runImageLoadStrategy({
|
||||
return HangingProtocolService.runImageLoadStrategy({
|
||||
viewportId: viewport.id,
|
||||
volumeInputArray,
|
||||
});
|
||||
}
|
||||
|
||||
viewportData.volumes.forEach(volume => {
|
||||
volumeToLoad.forEach(volume => {
|
||||
volume.load();
|
||||
});
|
||||
|
||||
@@ -406,9 +441,98 @@ class CornerstoneViewportService implements IViewportService {
|
||||
}
|
||||
|
||||
public async setVolumesForViewport(viewport, volumeInputArray) {
|
||||
const {
|
||||
DisplaySetService,
|
||||
SegmentationService,
|
||||
ToolGroupService,
|
||||
} = this.servicesManager.services;
|
||||
|
||||
await viewport.setVolumes(volumeInputArray);
|
||||
|
||||
// load any secondary displaySets
|
||||
const displaySetInstanceUIDs = this.viewportsDisplaySets.get(viewport.id);
|
||||
|
||||
const segDisplaySet = displaySetInstanceUIDs
|
||||
.map(DisplaySetService.getDisplaySetByUID)
|
||||
.find(displaySet => displaySet && displaySet.Modality === 'SEG');
|
||||
|
||||
if (segDisplaySet) {
|
||||
const { referencedVolumeId } = segDisplaySet;
|
||||
const referencedVolume = cache.getVolume(referencedVolumeId);
|
||||
const segmentationId = segDisplaySet.displaySetInstanceUID;
|
||||
|
||||
const toolGroup = ToolGroupService.getToolGroupForViewport(viewport.id);
|
||||
|
||||
if (referencedVolume) {
|
||||
SegmentationService.addSegmentationRepresentationToToolGroup(
|
||||
toolGroup.id,
|
||||
segmentationId
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const toolGroup = ToolGroupService.getToolGroupForViewport(viewport.id);
|
||||
const toolGroupSegmentationRepresentations =
|
||||
SegmentationService.getSegmentationRepresentationsForToolGroup(
|
||||
toolGroup.id
|
||||
) || [];
|
||||
|
||||
// csToolsUtils.segmentation.triggerSegmentationRender(toolGroup.id);
|
||||
// If the displaySet is not a SEG displaySet we assume it is a primary displaySet
|
||||
// and we can look into hydrated segmentations to check if any of them are
|
||||
// associated with the primary displaySet
|
||||
// get segmentations only returns the hydrated segmentations
|
||||
const segmentations = SegmentationService.getSegmentations();
|
||||
|
||||
for (const segmentation of segmentations) {
|
||||
// if there is already a segmentation representation for this segmentation
|
||||
// for this toolGroup, don't bother at all
|
||||
if (
|
||||
toolGroupSegmentationRepresentations.find(
|
||||
representation => representation.segmentationId === segmentation.id
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// otherwise, check if the hydrated segmentations are in the same FOR
|
||||
// as the primary displaySet, if so add the representation (since it was not there)
|
||||
const { id: segDisplaySetInstanceUID } = segmentation;
|
||||
|
||||
const segFrameOfReferenceUID = this._getFrameOfReferenceUID(
|
||||
segDisplaySetInstanceUID
|
||||
);
|
||||
|
||||
let shouldDisplaySeg = false;
|
||||
|
||||
for (const displaySetInstanceUID of displaySetInstanceUIDs) {
|
||||
const primaryFrameOfReferenceUID = this._getFrameOfReferenceUID(
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
if (segFrameOfReferenceUID === primaryFrameOfReferenceUID) {
|
||||
shouldDisplaySeg = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldDisplaySeg) {
|
||||
const toolGroup = ToolGroupService.getToolGroupForViewport(
|
||||
viewport.id
|
||||
);
|
||||
|
||||
SegmentationService.addSegmentationRepresentationToToolGroup(
|
||||
toolGroup.id,
|
||||
segmentation.id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const viewportInfo = this.getViewportInfo(viewport.id);
|
||||
|
||||
const toolGroup = ToolGroupService.getToolGroupForViewport(viewport.id);
|
||||
csToolsUtils.segmentation.triggerSegmentationRender(toolGroup.id);
|
||||
|
||||
const initialImageOptions = viewportInfo.getInitialImageOptions();
|
||||
|
||||
if (
|
||||
@@ -493,17 +617,21 @@ class CornerstoneViewportService implements IViewportService {
|
||||
|
||||
_setDisplaySets(
|
||||
viewportId: string,
|
||||
viewportData: StackData | VolumeData,
|
||||
viewportData: StackViewportData | VolumeViewportData,
|
||||
viewportInfo: ViewportInfo
|
||||
): void {
|
||||
const viewport = this.getCornerstoneViewport(viewportId);
|
||||
|
||||
if (viewport instanceof StackViewport) {
|
||||
this._setStackViewport(viewport, viewportData as StackData, viewportInfo);
|
||||
this._setStackViewport(
|
||||
viewport,
|
||||
viewportData as StackViewportData,
|
||||
viewportInfo
|
||||
);
|
||||
} else if (viewport instanceof VolumeViewport) {
|
||||
this._setVolumeViewport(
|
||||
viewport,
|
||||
viewportData as VolumeData,
|
||||
viewportData as VolumeViewportData,
|
||||
viewportInfo
|
||||
);
|
||||
} else {
|
||||
@@ -559,6 +687,10 @@ class CornerstoneViewportService implements IViewportService {
|
||||
} {
|
||||
const viewportIndex = viewportInfo.getViewportIndex();
|
||||
|
||||
if (!publicViewportOptions.viewportId) {
|
||||
publicViewportOptions.viewportId = this.getViewportId(viewportIndex);
|
||||
}
|
||||
|
||||
// Creating a temporary viewportInfo to handle defaults
|
||||
const newViewportInfo = new ViewportInfo(
|
||||
viewportIndex,
|
||||
@@ -578,6 +710,31 @@ class CornerstoneViewportService implements IViewportService {
|
||||
displaySetOptions: newDisplaySetOptions,
|
||||
};
|
||||
}
|
||||
|
||||
_getFrameOfReferenceUID(displaySetInstanceUID) {
|
||||
const { DisplaySetService } = this.servicesManager.services;
|
||||
const displaySet = DisplaySetService.getDisplaySetByUID(
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
if (!displaySet) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (displaySet.frameOfReferenceUID) {
|
||||
return displaySet.frameOfReferenceUID;
|
||||
}
|
||||
|
||||
if (displaySet.Modality === 'SEG') {
|
||||
const { instance } = displaySet;
|
||||
return instance.FrameOfReferenceUID;
|
||||
}
|
||||
|
||||
const { images } = displaySet;
|
||||
if (images && images.length) {
|
||||
return images[0].FrameOfReferenceUID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function ExtendedCornerstoneViewportService(serviceManager) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Types } from '@cornerstonejs/core';
|
||||
import { StackData, VolumeData } from './CornerstoneCacheService';
|
||||
import { StackData, VolumeData } from '../../types/CornerstoneCacheService';
|
||||
import {
|
||||
DisplaySetOptions,
|
||||
PublicViewportOptions,
|
||||
@@ -31,7 +31,7 @@ export interface IViewportService {
|
||||
* @param {*} viewportIndex
|
||||
* @param {*} elementRef
|
||||
*/
|
||||
enableElement(
|
||||
enableViewport(
|
||||
viewportIndex: number,
|
||||
viewportOptions: ViewportOptions,
|
||||
elementRef: HTMLDivElement
|
||||
@@ -65,7 +65,7 @@ export interface IViewportService {
|
||||
* @param {*} dataSource
|
||||
* @returns
|
||||
*/
|
||||
setViewportDisplaySets(
|
||||
setViewportData(
|
||||
viewportIndex: number,
|
||||
viewportData: StackData | VolumeData,
|
||||
publicViewportOptions: PublicViewportOptions,
|
||||
|
||||
@@ -4,6 +4,10 @@ import getCornerstoneOrientation from '../../utils/getCornerstoneOrientation';
|
||||
import getCornerstoneViewportType from '../../utils/getCornerstoneViewportType';
|
||||
import JumpPresets from '../../utils/JumpPresets';
|
||||
import { SyncGroup } from '../SyncGroupService/SyncGroupService';
|
||||
import {
|
||||
StackViewportData,
|
||||
VolumeViewportData,
|
||||
} from './CornerstoneCacheService';
|
||||
|
||||
export type InitialImageOptions = {
|
||||
index?: number;
|
||||
@@ -67,6 +71,7 @@ class ViewportInfo {
|
||||
private element: HTMLDivElement;
|
||||
private viewportOptions: ViewportOptions;
|
||||
private displaySetOptions: Array<DisplaySetOptions>;
|
||||
private viewportData: StackViewportData | VolumeViewportData;
|
||||
private renderingEngineId: string;
|
||||
|
||||
constructor(viewportIndex: number, viewportId: string) {
|
||||
@@ -76,6 +81,13 @@ class ViewportInfo {
|
||||
this.setPublicDisplaySetOptions([{}]);
|
||||
}
|
||||
|
||||
public destroy = (): void => {
|
||||
this.element = null;
|
||||
this.viewportData = null;
|
||||
this.viewportOptions = null;
|
||||
this.displaySetOptions = null;
|
||||
};
|
||||
|
||||
public setRenderingEngineId(renderingEngineId: string): void {
|
||||
this.renderingEngineId = renderingEngineId;
|
||||
}
|
||||
@@ -95,6 +107,16 @@ class ViewportInfo {
|
||||
this.element = element;
|
||||
}
|
||||
|
||||
public setViewportData(
|
||||
viewportData: StackViewportData | VolumeViewportData
|
||||
): void {
|
||||
this.viewportData = viewportData;
|
||||
}
|
||||
|
||||
public getViewportData(): StackViewportData | VolumeViewportData {
|
||||
return this.viewportData;
|
||||
}
|
||||
|
||||
public getViewportIndex(): number {
|
||||
return this.viewportIndex;
|
||||
}
|
||||
@@ -118,6 +140,23 @@ class ViewportInfo {
|
||||
this.setDisplaySetOptions(displaySetOptions);
|
||||
}
|
||||
|
||||
public hasDisplaySet(displaySetInstanceUID: string): boolean {
|
||||
// Todo: currently this does not work for non image & referenceImage displaySets.
|
||||
// Since SEG and other derived displaySets are loaded in a different way, and not
|
||||
// via cornerstoneViewportService
|
||||
let viewportData = this.getViewportData();
|
||||
|
||||
if (viewportData.viewportType === Enums.ViewportType.ORTHOGRAPHIC) {
|
||||
viewportData = viewportData as VolumeViewportData;
|
||||
return viewportData.data.some(
|
||||
({ displaySetInstanceUID: dsUID }) => dsUID === displaySetInstanceUID
|
||||
);
|
||||
}
|
||||
|
||||
viewportData = viewportData as StackViewportData;
|
||||
return viewportData.data.displaySetInstanceUID === displaySetInstanceUID;
|
||||
}
|
||||
|
||||
public setPublicViewportOptions(
|
||||
viewportOptionsEntry: PublicViewportOptions
|
||||
): void {
|
||||
@@ -201,12 +240,21 @@ class ViewportInfo {
|
||||
const displaySetOptions: Array<DisplaySetOptions> = [];
|
||||
|
||||
publicDisplaySetOptions.forEach(option => {
|
||||
if (!option) {
|
||||
option = {
|
||||
blendMode: undefined,
|
||||
slabThickness: undefined,
|
||||
colormap: undefined,
|
||||
voi: {},
|
||||
voiInverted: false,
|
||||
};
|
||||
}
|
||||
const blendMode = getCornerstoneBlendMode(option.blendMode);
|
||||
|
||||
displaySetOptions.push({
|
||||
voi: option.voi || ({} as VOI),
|
||||
voiInverted: option.voiInverted || false,
|
||||
colormap: option.colormap || undefined,
|
||||
voi: option.voi,
|
||||
voiInverted: option.voiInverted,
|
||||
colormap: option.colormap,
|
||||
slabThickness: option.slabThickness,
|
||||
blendMode,
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user