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
+252
@@ -0,0 +1,252 @@
|
||||
import { cache as cs3DCache, Enums, volumeLoader } from '@cornerstonejs/core';
|
||||
import { utils } from '@ohif/core';
|
||||
|
||||
import getCornerstoneViewportType from '../../utils/getCornerstoneViewportType';
|
||||
import {
|
||||
StackViewportData,
|
||||
VolumeViewportData,
|
||||
} from '../../types/CornerstoneCacheService';
|
||||
|
||||
const VOLUME_IMAGE_LOADER_SCHEME = 'streaming-wadors';
|
||||
const VOLUME_LOADER_SCHEME = 'cornerstoneStreamingImageVolume';
|
||||
|
||||
class CornerstoneCacheService {
|
||||
stackImageIds: Map<string, string[]> = new Map();
|
||||
volumeImageIds: Map<string, string[]> = new Map();
|
||||
|
||||
constructor(servicesManager) {
|
||||
this.servicesManager = servicesManager;
|
||||
}
|
||||
|
||||
public getCacheSize() {
|
||||
return cs3DCache.getCacheSize();
|
||||
}
|
||||
|
||||
public getCacheFreeSpace() {
|
||||
return cs3DCache.getBytesAvailable();
|
||||
}
|
||||
|
||||
public async createViewportData(
|
||||
displaySets: unknown[],
|
||||
viewportOptions: Record<string, unknown>,
|
||||
dataSource: unknown,
|
||||
initialImageIndex?: number
|
||||
): Promise<StackViewportData | VolumeViewportData> {
|
||||
let viewportType = viewportOptions.viewportType as string;
|
||||
|
||||
// Todo: Since Cornerstone 3D currently doesn't support segmentation
|
||||
// on stack viewport, we should check if whether the the displaySets
|
||||
// that are about to be displayed are referenced in a segmentation
|
||||
// as a reference volume, if so, we should hang a volume viewport
|
||||
// instead of a stack viewport
|
||||
if (this._shouldRenderSegmentation(displaySets)) {
|
||||
viewportType = 'volume';
|
||||
|
||||
// update viewportOptions to reflect the new viewport type
|
||||
viewportOptions.viewportType = viewportType;
|
||||
}
|
||||
|
||||
const cs3DViewportType = getCornerstoneViewportType(viewportType);
|
||||
let viewportData: StackViewportData | VolumeViewportData;
|
||||
|
||||
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;
|
||||
|
||||
return viewportData;
|
||||
}
|
||||
|
||||
public async invalidateViewportData(
|
||||
viewportData: VolumeViewportData,
|
||||
invalidatedDisplaySetInstanceUID: string,
|
||||
dataSource,
|
||||
DisplaySetService
|
||||
) {
|
||||
if (viewportData.viewportType === Enums.ViewportType.STACK) {
|
||||
throw new Error('Invalidation of StackViewport is not supported yet');
|
||||
}
|
||||
|
||||
// Todo: grab the volume and get the id from the viewport itself
|
||||
const volumeId = `${VOLUME_LOADER_SCHEME}:${invalidatedDisplaySetInstanceUID}`;
|
||||
|
||||
const volume = cs3DCache.getVolume(volumeId);
|
||||
|
||||
if (volume) {
|
||||
cs3DCache.removeVolumeLoadObject(volumeId);
|
||||
}
|
||||
|
||||
const displaySets = viewportData.data.map(({ displaySetInstanceUID }) =>
|
||||
DisplaySetService.getDisplaySetByUID(displaySetInstanceUID)
|
||||
);
|
||||
|
||||
const newViewportData = await this._getVolumeViewportData(
|
||||
dataSource,
|
||||
displaySets
|
||||
);
|
||||
|
||||
return newViewportData;
|
||||
}
|
||||
|
||||
private _getStackViewportData(
|
||||
dataSource,
|
||||
displaySets,
|
||||
initialImageIndex
|
||||
): StackViewportData {
|
||||
// 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 StackViewportData: StackViewportData = {
|
||||
viewportType: Enums.ViewportType.STACK,
|
||||
data: {
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
imageIds: stackImageIds,
|
||||
},
|
||||
};
|
||||
|
||||
if (typeof initialImageIndex === 'number') {
|
||||
StackViewportData.data.initialImageIndex = initialImageIndex;
|
||||
}
|
||||
|
||||
return StackViewportData;
|
||||
}
|
||||
|
||||
private async _getVolumeViewportData(
|
||||
dataSource,
|
||||
displaySets
|
||||
): Promise<VolumeViewportData> {
|
||||
// Todo: Check the cache for multiple scenarios to see if we need to
|
||||
// decache the volume data from other viewports or not
|
||||
|
||||
const volumeData = [];
|
||||
|
||||
for (const displaySet of displaySets) {
|
||||
// Don't create volumes for the displaySets that have custom load
|
||||
// function (e.g., SEG, RT, since they rely on the reference volumes
|
||||
// and they take care of their own loading after they are created in their
|
||||
// getSOPClassHandler method
|
||||
|
||||
if (displaySet.load && displaySet.load instanceof Function) {
|
||||
const { UserAuthenticationService } = this.servicesManager.services;
|
||||
const headers = UserAuthenticationService.getAuthorizationHeader();
|
||||
await displaySet.load({ headers });
|
||||
|
||||
volumeData.push({
|
||||
studyInstanceUID: displaySet.StudyInstanceUID,
|
||||
displaySetInstanceUID: displaySet.displaySetInstanceUID,
|
||||
});
|
||||
|
||||
// Todo: do some cache check and empty the cache if needed
|
||||
continue;
|
||||
}
|
||||
|
||||
const volumeLoaderSchema =
|
||||
displaySet.volumeLoaderSchema ?? VOLUME_LOADER_SCHEME;
|
||||
|
||||
const volumeId = `${volumeLoaderSchema}:${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
|
||||
);
|
||||
}
|
||||
|
||||
volumeData.push({
|
||||
StudyInstanceUID: displaySet.StudyInstanceUID,
|
||||
displaySetInstanceUID: displaySet.displaySetInstanceUID,
|
||||
volume,
|
||||
volumeId,
|
||||
imageIds: volumeImageIds,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
viewportType: Enums.ViewportType.ORTHOGRAPHIC,
|
||||
data: volumeData,
|
||||
};
|
||||
}
|
||||
|
||||
private _shouldRenderSegmentation(displaySets) {
|
||||
const { SegmentationService } = this.servicesManager.services;
|
||||
|
||||
const viewportDisplaySetInstanceUIDs = displaySets.map(
|
||||
({ displaySetInstanceUID }) => displaySetInstanceUID
|
||||
);
|
||||
|
||||
// check inside segmentations if any of them are referencing the displaySets
|
||||
// that are about to be displayed
|
||||
const segmentations = SegmentationService.getSegmentations();
|
||||
|
||||
for (const segmentation of segmentations) {
|
||||
const segDisplaySetInstanceUID = segmentation.displaySetInstanceUID;
|
||||
|
||||
const shouldDisplaySeg = SegmentationService.shouldRenderSegmentation(
|
||||
viewportDisplaySetInstanceUIDs,
|
||||
segDisplaySetInstanceUID
|
||||
);
|
||||
|
||||
if (shouldDisplaySeg) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _getCornerstoneStackImageIds(displaySet, dataSource): string[] {
|
||||
return dataSource.getImageIdsForDisplaySet(displaySet);
|
||||
}
|
||||
|
||||
private _getCornerstoneVolumeImageIds(displaySet, dataSource): string[] {
|
||||
const stackImageIds = this._getCornerstoneStackImageIds(
|
||||
displaySet,
|
||||
dataSource
|
||||
);
|
||||
|
||||
if (stackImageIds[0].startsWith('dicomfile')) {
|
||||
return stackImageIds;
|
||||
}
|
||||
|
||||
return stackImageIds.map(imageId => {
|
||||
const imageURI = utils.imageIdToURI(imageId);
|
||||
return `${VOLUME_IMAGE_LOADER_SCHEME}:${imageURI}`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default CornerstoneCacheService;
|
||||
@@ -0,0 +1,10 @@
|
||||
import CornerstoneCacheService from './CornerstoneCacheService';
|
||||
|
||||
export default function ExtendedCornerstoneCacheService(serviceManager) {
|
||||
return {
|
||||
name: 'CornerstoneCacheService',
|
||||
create: ({ configuration = {} }) => {
|
||||
return new CornerstoneCacheService(serviceManager);
|
||||
},
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,88 @@
|
||||
import { Enums as csToolsEnums, Types as cstTypes } from '@cornerstonejs/tools';
|
||||
import { Types } from '@cornerstonejs/core';
|
||||
|
||||
type SegmentationConfig = cstTypes.LabelmapTypes.LabelmapConfig & {
|
||||
renderInactiveSegmentations: boolean;
|
||||
brushSize: number;
|
||||
brushThresholdGate: number;
|
||||
};
|
||||
|
||||
type Segment = {
|
||||
// the label for the segment
|
||||
label: string;
|
||||
// the index of the segment in the segmentation
|
||||
segmentIndex: number;
|
||||
// the color of the segment
|
||||
color: Types.Point3;
|
||||
// the opacity of the segment
|
||||
opacity: number;
|
||||
// whether the segment is visible
|
||||
isVisible: boolean;
|
||||
// whether the segment is locked
|
||||
isLocked: boolean;
|
||||
};
|
||||
|
||||
type Segmentation = {
|
||||
// active segment index is the index of the segment that is currently being edited.
|
||||
activeSegmentIndex: number;
|
||||
// colorLUTIndex is the index of the color LUT that is currently being used.
|
||||
colorLUTIndex: number;
|
||||
// if segmentation contains any data (often calculated from labelmap)
|
||||
cachedStats: Record<string, any>;
|
||||
// displayText is the text that is displayed on the segmentation panel (often derived from the data)
|
||||
displayText?: string[];
|
||||
// the id of the segmentation
|
||||
id: string;
|
||||
// if the segmentation is the active segmentation being used in the viewer
|
||||
isActive: boolean;
|
||||
// if the segmentation is visible in the viewer
|
||||
isVisible: boolean;
|
||||
// the label of the segmentation
|
||||
label: string;
|
||||
// the number of segments in the segmentation
|
||||
segmentCount: number;
|
||||
// the array of segments with their details, [null, segment1, segment2, ...]
|
||||
segments: Array<Segment>;
|
||||
// the set of segments that are locked
|
||||
segmentsLocked: Array<number>;
|
||||
// the segmentation representation type
|
||||
type: csToolsEnums.SegmentationRepresentations;
|
||||
// if labelmap, the id of the volume that the labelmap is associated with
|
||||
volumeId?: string;
|
||||
// whether the segmentation is hydrated or not (non-hydrated SEG -> temporary segmentation for display in SEG Viewport
|
||||
// but hydrated SEG -> segmentation that is persisted in the store)
|
||||
hydrated: boolean;
|
||||
};
|
||||
|
||||
// Schema to generate a segmentation
|
||||
type SegmentationSchema = {
|
||||
// active segment index for the segmentation
|
||||
activeSegmentIndex: number;
|
||||
// statistics that are derived from the segmentation
|
||||
cachedStats: Record<string, any>;
|
||||
// the displayText for the segmentation in the panels
|
||||
displayText?: string[];
|
||||
// segmentation id
|
||||
id: string;
|
||||
// displaySetInstanceUID
|
||||
displaySetInstanceUID: string;
|
||||
// segmentation label
|
||||
label: string;
|
||||
// segment indices that are locked for the segmentation
|
||||
segmentsLocked: Array<number>;
|
||||
// the type of the segmentation (e.g., Labelmap etc.)
|
||||
type: csToolsEnums.SegmentationRepresentations;
|
||||
// the volume id of the volume that the labelmap is associated with, this only exists for the labelmap representation
|
||||
volumeId: string;
|
||||
// the referenced volumeURI for the segmentation
|
||||
referencedVolumeURI: string;
|
||||
// whether the segmentation is hydrated or not (non-hydrated SEG -> temporary segmentation for display in SEG Viewport
|
||||
// but hydrated SEG -> segmentation that is persisted in the store)
|
||||
hydrated: boolean;
|
||||
// the number of segments in the segmentation
|
||||
segmentCount: number;
|
||||
// the array of segments with their details
|
||||
segments: Array<Segment>;
|
||||
};
|
||||
|
||||
export { SegmentationConfig, Segment, Segmentation, SegmentationSchema };
|
||||
@@ -0,0 +1,10 @@
|
||||
import SegmentationService from './SegmentationService';
|
||||
|
||||
export default function ExtendedSegmentationService(servicesManager) {
|
||||
return {
|
||||
name: 'SegmentationService',
|
||||
create: ({ configuration = {} }) => {
|
||||
return new SegmentationService({ servicesManager });
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -31,6 +31,7 @@ export type SyncGroup = {
|
||||
const POSITION = 'cameraposition';
|
||||
const VOI = 'voi';
|
||||
const ZOOMPAN = 'zoompan';
|
||||
const STACKIMAGE = 'stackimage';
|
||||
|
||||
const asSyncGroup = (syncGroup: string | SyncGroup): SyncGroup =>
|
||||
typeof syncGroup === 'string' ? { type: syncGroup } : syncGroup;
|
||||
@@ -43,6 +44,7 @@ export default class SyncGroupService {
|
||||
[POSITION]: synchronizers.createCameraPositionSynchronizer,
|
||||
[VOI]: synchronizers.createVOISynchronizer,
|
||||
[ZOOMPAN]: synchronizers.createZoomPanSynchronizer,
|
||||
[STACKIMAGE]: synchronizers.createStackImageSynchronizer,
|
||||
};
|
||||
|
||||
constructor(serviceManager) {
|
||||
@@ -72,7 +74,7 @@ export default class SyncGroupService {
|
||||
* @param creator
|
||||
*/
|
||||
public setSynchronizer(type: string, creator: SyncCreator): void {
|
||||
this.synchronizerCreators[type] = creator;
|
||||
this.synchronizerCreators[type.toLowerCase()] = creator;
|
||||
}
|
||||
|
||||
protected _getOrCreateSynchronizer(
|
||||
@@ -91,20 +93,29 @@ export default class SyncGroupService {
|
||||
public addViewportToSyncGroup(
|
||||
viewportId: string,
|
||||
renderingEngineId: string,
|
||||
syncGroups?: (SyncGroup | string)[]
|
||||
syncGroups?: SyncGroup | string | SyncGroup[] | string[]
|
||||
): void {
|
||||
if (!syncGroups || !syncGroups.length) {
|
||||
if (!syncGroups) {
|
||||
return;
|
||||
}
|
||||
|
||||
syncGroups.forEach(syncGroup => {
|
||||
const syncGroupsArray = Array.isArray(syncGroups)
|
||||
? syncGroups
|
||||
: [syncGroups];
|
||||
|
||||
syncGroupsArray.forEach(syncGroup => {
|
||||
const syncGroupObj = asSyncGroup(syncGroup);
|
||||
const { type, target = true, source = true, options = {} } = syncGroupObj;
|
||||
const { id = type } = syncGroupObj;
|
||||
const {
|
||||
type,
|
||||
target = true,
|
||||
source = true,
|
||||
options = {},
|
||||
id = type,
|
||||
} = syncGroupObj;
|
||||
|
||||
const synchronizer = this._getOrCreateSynchronizer(type, id, options);
|
||||
|
||||
synchronizer.setOptions(viewportId, options);
|
||||
|
||||
const viewportInfo = { viewportId, renderingEngineId };
|
||||
if (target && source) {
|
||||
synchronizer.add(viewportInfo);
|
||||
@@ -123,11 +134,16 @@ export default class SyncGroupService {
|
||||
|
||||
public removeViewportFromSyncGroup(
|
||||
viewportId: string,
|
||||
renderingEngineId: string
|
||||
renderingEngineId: string,
|
||||
syncGroupId?: string
|
||||
): void {
|
||||
const synchronizers = SynchronizerManager.getAllSynchronizers();
|
||||
|
||||
synchronizers.forEach(synchronizer => {
|
||||
const filteredSynchronizers = syncGroupId
|
||||
? synchronizers.filter(s => s.id === syncGroupId)
|
||||
: synchronizers;
|
||||
|
||||
filteredSynchronizers.forEach(synchronizer => {
|
||||
if (!synchronizer) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -72,7 +72,16 @@ export default class ToolGroupService {
|
||||
this.toolGroupIds = new Set();
|
||||
}
|
||||
|
||||
public disable(viewportId: string, renderingEngineId: string): void {
|
||||
public destroyToolGroup(toolGroupId: string) {
|
||||
ToolGroupManager.destroyToolGroup(toolGroupId);
|
||||
this.toolGroupIds.delete(toolGroupId);
|
||||
}
|
||||
|
||||
public removeViewportFromToolGroup(
|
||||
viewportId: string,
|
||||
renderingEngineId: string,
|
||||
deleteToolGroupIfEmpty?: boolean
|
||||
): void {
|
||||
const toolGroup = ToolGroupManager.getToolGroupForViewport(
|
||||
viewportId,
|
||||
renderingEngineId
|
||||
@@ -85,9 +94,10 @@ export default class ToolGroupService {
|
||||
toolGroup.removeViewports(renderingEngineId, viewportId);
|
||||
|
||||
const viewportIds = toolGroup.getViewportIds();
|
||||
// if (viewportIds.length === 0) {
|
||||
// ToolGroupManager.destroyToolGroup(toolGroup.id);
|
||||
// }
|
||||
|
||||
if (viewportIds.length === 0 && deleteToolGroupIfEmpty) {
|
||||
ToolGroupManager.destroyToolGroup(toolGroup.id);
|
||||
}
|
||||
}
|
||||
|
||||
public addViewportToToolGroup(
|
||||
@@ -110,7 +120,10 @@ export default class ToolGroupService {
|
||||
toolGroup.addViewport(viewportId, renderingEngineId);
|
||||
}
|
||||
|
||||
this._broadcastEvent(EVENTS.VIEWPORT_ADDED, { viewportId, toolGroupId });
|
||||
this._broadcastEvent(EVENTS.VIEWPORT_ADDED, {
|
||||
viewportId,
|
||||
toolGroupId,
|
||||
});
|
||||
}
|
||||
|
||||
public createToolGroup(toolGroupId: string): Types.IToolGroup {
|
||||
@@ -122,7 +135,9 @@ export default class ToolGroupService {
|
||||
const toolGroup = ToolGroupManager.createToolGroup(toolGroupId);
|
||||
this.toolGroupIds.add(toolGroupId);
|
||||
|
||||
this._broadcastEvent(EVENTS.TOOLGROUP_CREATED, { toolGroupId });
|
||||
this._broadcastEvent(EVENTS.TOOLGROUP_CREATED, {
|
||||
toolGroupId,
|
||||
});
|
||||
|
||||
return toolGroup;
|
||||
}
|
||||
@@ -195,9 +210,11 @@ export default class ToolGroupService {
|
||||
|
||||
private _getToolNames(toolGroupTools: Tools): string[] {
|
||||
const toolNames = [];
|
||||
toolGroupTools.active.forEach(tool => {
|
||||
toolNames.push(tool.toolName);
|
||||
});
|
||||
if (toolGroupTools.active) {
|
||||
toolGroupTools.active.forEach(tool => {
|
||||
toolNames.push(tool.toolName);
|
||||
});
|
||||
}
|
||||
if (toolGroupTools.passive) {
|
||||
toolGroupTools.passive.forEach(tool => {
|
||||
toolNames.push(tool.toolName);
|
||||
@@ -221,9 +238,12 @@ export default class ToolGroupService {
|
||||
|
||||
private _setToolsMode(toolGroup, tools) {
|
||||
const { active, passive, enabled, disabled } = tools;
|
||||
active.forEach(({ toolName, bindings }) => {
|
||||
toolGroup.setToolActive(toolName, { bindings });
|
||||
});
|
||||
|
||||
if (active) {
|
||||
active.forEach(({ toolName, bindings }) => {
|
||||
toolGroup.setToolActive(toolName, { bindings });
|
||||
});
|
||||
}
|
||||
|
||||
if (passive) {
|
||||
passive.forEach(({ toolName }) => {
|
||||
|
||||
@@ -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