2022-11-16 14:19:52 -05:00
import {
cache ,
2023-04-26 12:21:08 -04:00
Enums as csEnums ,
2023-03-22 12:44:52 -04:00
eventTarget ,
2024-11-06 07:15:27 -05:00
geometryLoader ,
getEnabledElementByViewportId ,
imageLoader ,
Types as csTypes ,
2022-11-16 14:19:52 -05:00
utilities as csUtils ,
2024-12-20 08:12:58 -05:00
metaData ,
2022-11-16 14:19:52 -05:00
} from '@cornerstonejs/core' ;
2025-09-30 14:49:13 -03:00
import { ViewportType } from '@cornerstonejs/core/enums' ;
2023-03-22 12:44:52 -04:00
import {
Enums as csToolsEnums ,
segmentation as cstSegmentation ,
Types as cstTypes ,
} from '@cornerstonejs/tools' ;
2025-09-30 14:49:13 -03:00
2024-11-06 07:15:27 -05:00
import { PubSubService , Types as OHIFTypes } from '@ohif/core' ;
2025-01-03 21:08:34 +05:30
import i18n from '@ohif/i18n' ;
2025-09-30 14:49:13 -03:00
import { VOLUME_LOADER_SCHEME } from '../../constants' ;
2023-04-26 12:21:08 -04:00
import { mapROIContoursToRTStructData } from './RTSTRUCT/mapROIContoursToRTStructData' ;
2024-11-06 07:15:27 -05:00
import { SegmentationPresentation , SegmentationPresentationItem } from '../../types/Presentation' ;
2025-09-30 14:49:13 -03:00
import { EasingFunctionEnum , EasingFunctionMap } from '../../utils/transitions' ;
2022-11-16 14:19:52 -05:00
2025-09-30 14:49:13 -03:00
const {
Labelmap : LABELMAP ,
Contour : CONTOUR ,
Surface : SURFACE ,
} = csToolsEnums . SegmentationRepresentations ;
const {
getLabelmapImageIds ,
helpers : { convertStackToVolumeLabelmap },
state : { addColorLUT , updateLabelmapSegmentationImageReferences },
triggerSegmentationEvents : { triggerSegmentationRepresentationModified },
} = cstSegmentation ;
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
export type SegmentRepresentation = {
segmentIndex : number ;
color : csTypes.Color ;
opacity : number ;
visible : boolean ;
};
export type SegmentationData = cstTypes . Segmentation ;
export type SegmentationRepresentation = cstTypes . SegmentationRepresentation & {
viewportId : string ;
id : string ;
label : string ;
styles : cstTypes.RepresentationStyle ;
segments : {
[ key : number ] : SegmentRepresentation ;
};
};
export type SegmentationInfo = {
segmentation : SegmentationData ;
representation? : SegmentationRepresentation ;
};
2022-11-16 14:19:52 -05:00
const EVENTS = {
2024-11-06 07:15:27 -05:00
SEGMENTATION_MODIFIED : 'event::segmentation_modified' ,
// fired when the segmentation is added
2022-11-16 14:19:52 -05:00
SEGMENTATION_ADDED : 'event::segmentation_added' ,
2024-11-06 07:15:27 -05:00
//
SEGMENTATION_DATA_MODIFIED : 'event::segmentation_data_modified' ,
2022-11-16 14:19:52 -05:00
// fired when the segmentation is removed
SEGMENTATION_REMOVED : 'event::segmentation_removed' ,
2024-11-06 07:15:27 -05:00
//
// fired when segmentation representation is added
SEGMENTATION_REPRESENTATION_MODIFIED : 'event::segmentation_representation_modified' ,
// fired when segmentation representation is removed
SEGMENTATION_REPRESENTATION_REMOVED : 'event::segmentation_representation_removed' ,
//
// LOADING EVENTS
2023-04-26 12:21:08 -04:00
// fired when the active segment is loaded in SEG or RTSTRUCT
SEGMENT_LOADING_COMPLETE : 'event::segment_loading_complete' ,
2023-10-12 17:09:44 -04:00
// loading completed for all segments
2023-04-26 12:21:08 -04:00
SEGMENTATION_LOADING_COMPLETE : 'event::segmentation_loading_complete' ,
2025-10-29 14:15:52 -04:00
// fired when a contour annotation cut merge process is completed
2025-11-11 08:19:45 -05:00
SEGMENTATION_ANNOTATION_CUT_MERGE_PROCESS_COMPLETED :
'event::annotation_cut_merge_process_completed' ,
2025-11-19 17:55:53 -05:00
SEGMENTATION_STYLE_MODIFIED : 'event::segmentation_style_modified' ,
2022-11-16 14:19:52 -05:00
};
const VALUE_TYPES = {};
2023-04-18 11:17:50 -04:00
class SegmentationService extends PubSubService {
static REGISTRATION = {
name : 'segmentationService' ,
altName : 'SegmentationService' ,
2024-11-06 07:15:27 -05:00
create : ({ servicesManager } : OHIFTypes . Extensions . ExtensionParams ) : SegmentationService => {
2023-04-18 11:17:50 -04:00
return new SegmentationService ({ servicesManager });
},
};
2024-11-06 07:15:27 -05:00
private _segmentationIdToColorLUTIndexMap : Map < string , number >;
2025-03-17 22:34:34 -04:00
private _segmentationGroupStatsMap : Map < string , any >;
2024-05-08 12:12:49 -04:00
readonly servicesManager : AppTypes.ServicesManager ;
2022-11-16 14:19:52 -05:00
highlightIntervalId = null ;
readonly EVENTS = EVENTS ;
constructor ({ servicesManager }) {
2023-04-18 11:17:50 -04:00
super ( EVENTS );
2024-11-06 07:15:27 -05:00
this . _segmentationIdToColorLUTIndexMap = new Map ();
2022-11-16 14:19:52 -05:00
this . servicesManager = servicesManager ;
2025-03-17 22:34:34 -04:00
this . _segmentationGroupStatsMap = new Map ();
2024-11-11 14:28:34 -05:00
}
2022-11-16 14:19:52 -05:00
2024-11-11 14:28:34 -05:00
public onModeEnter () : void {
2022-11-16 14:19:52 -05:00
this . _initSegmentationService ();
}
2024-11-11 14:28:34 -05:00
public onModeExit () : void {
this . destroy ();
}
2022-11-16 14:19:52 -05:00
/**
2024-11-06 07:15:27 -05:00
* Retrieves a segmentation by its ID.
*
* @param segmentationId - The unique identifier of the segmentation to retrieve.
* @returns The segmentation object if found, or undefined if not found.
*
* @remarks
* This method directly accesses the cornerstone tools segmentation state to fetch
* the segmentation data. It's useful when you need to access specific properties
* or perform operations on a particular segmentation.
2022-11-16 14:19:52 -05:00
*/
2024-11-06 07:15:27 -05:00
public getSegmentation ( segmentationId : string ) : cstTypes . Segmentation | undefined {
return cstSegmentation . state . getSegmentation ( segmentationId );
2022-11-16 14:19:52 -05:00
}
2024-11-06 07:15:27 -05:00
/**
* Retrieves all segmentations from the cornerstone tools segmentation state.
*
* @returns An array of all segmentations currently stored in the state
*
* @remarks
* This is a convenience method that directly accesses the cornerstone tools
* segmentation state to get all available segmentations. It returns the raw
* segmentation objects without any additional processing or filtering.
*/
public getSegmentations () : cstTypes . Segmentation [] | [] {
return cstSegmentation . state . getSegmentations ();
}
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
public getPresentation ( viewportId : string ) : SegmentationPresentation {
const segmentationPresentations : SegmentationPresentation = [];
const segmentationsMap = new Map < string , SegmentationPresentationItem >();
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
const representations = this . getSegmentationRepresentations ( viewportId );
for ( const representation of representations ) {
if ( ! representation ) {
continue ;
2022-11-16 14:19:52 -05:00
}
2025-09-30 14:49:13 -03:00
const { segmentationId , type } = representation ;
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
segmentationsMap . set ( segmentationId , {
segmentationId ,
type ,
hydrated : true ,
config : representation.config || {},
});
}
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
// Check inside the removedDisplaySetAndRepresentationMaps to see if any of the representations are not hydrated
// const hydrationMap = this._segmentationRepresentationHydrationMaps.get(presentationId);
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
// if (hydrationMap) {
// hydrationMap.forEach(rep => {
// segmentationsMap.set(rep.segmentationId, {
// segmentationId: rep.segmentationId,
// type: rep.type,
// hydrated: rep.hydrated,
// config: rep.config || {},
// });
// });
// }
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
// // Convert the Map to an array
segmentationPresentations . push (... segmentationsMap . values ());
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
return segmentationPresentations ;
2022-11-16 14:19:52 -05:00
}
2024-11-06 07:15:27 -05:00
public getRepresentationsForSegmentation (
segmentationId : string
) : { viewportId : string ; representations : any [] }[] {
const representations =
cstSegmentation . state . getSegmentationRepresentationsBySegmentationId ( segmentationId );
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
return representations ;
2022-11-16 14:19:52 -05:00
}
2023-09-22 10:23:44 -04:00
/**
2024-11-06 07:15:27 -05:00
* Retrieves segmentation representations (labelmap, contour, surface) based on specified criteria.
*
* @param viewportId - The ID of the viewport.
* @param specifier - An object containing optional `segmentationId` and `type` to filter the representations.
* @returns An array of `SegmentationRepresentation` matching the criteria, or an empty array if none are found.
*
* @remarks
* This method filters the segmentation representations according to the provided `specifier`:
* - **No `segmentationId` or `type` provided**: Returns all representations associated with the given `viewportId`.
* - **Only `segmentationId` provided**: Returns all representations with that `segmentationId`, regardless of `viewportId`.
* - **Only `type` provided**: Returns all representations of that `type` associated with the given `viewportId`.
* - **Both `segmentationId` and `type` provided**: Returns representations matching both criteria, regardless of `viewportId`.
2023-09-22 10:23:44 -04:00
*/
2024-11-06 07:15:27 -05:00
public getSegmentationRepresentations (
viewportId : string ,
specifier : {
segmentationId? : string ;
2025-09-30 14:49:13 -03:00
type ?: csToolsEnums . SegmentationRepresentations ;
2024-11-06 07:15:27 -05:00
} = {}
) : SegmentationRepresentation [] {
// Get all representations for the viewportId
const representations = cstSegmentation . state . getSegmentationRepresentations (
viewportId ,
specifier
2022-11-16 14:19:52 -05:00
);
2024-11-06 07:15:27 -05:00
// Map to our SegmentationRepresentation type
const ohifRepresentations = representations . map ( repr =>
this . _toOHIFSegmentationRepresentation ( viewportId , repr )
2022-11-16 14:19:52 -05:00
);
2024-11-06 07:15:27 -05:00
return ohifRepresentations ;
2022-11-16 14:19:52 -05:00
}
2024-11-06 07:15:27 -05:00
public destroy = () => {
eventTarget . removeEventListener (
csToolsEnums . Events . SEGMENTATION_MODIFIED ,
this . _onSegmentationModifiedFromSource
);
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
eventTarget . removeEventListener (
csToolsEnums . Events . SEGMENTATION_REMOVED ,
this . _onSegmentationModifiedFromSource
);
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
eventTarget . removeEventListener (
csToolsEnums . Events . SEGMENTATION_DATA_MODIFIED ,
this . _onSegmentationDataModifiedFromSource
);
2022-11-16 14:19:52 -05:00
2025-09-30 14:49:13 -03:00
eventTarget . removeEventListener (
csToolsEnums . Events . SEGMENTATION_REPRESENTATION_MODIFIED ,
this . _onSegmentationRepresentationModifiedFromSource
);
2024-11-06 07:15:27 -05:00
eventTarget . removeEventListener (
csToolsEnums . Events . SEGMENTATION_REPRESENTATION_ADDED ,
2025-09-30 14:49:13 -03:00
this . _onSegmentationRepresentationModifiedFromSource
);
eventTarget . removeEventListener (
csToolsEnums . Events . SEGMENTATION_REPRESENTATION_REMOVED ,
this . _onSegmentationRepresentationModifiedFromSource
2024-11-06 07:15:27 -05:00
);
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
eventTarget . removeEventListener (
csToolsEnums . Events . SEGMENTATION_ADDED ,
this . _onSegmentationAddedFromSource
2022-11-16 14:19:52 -05:00
);
2025-06-02 12:12:31 -04:00
this . reset ();
2024-11-06 07:15:27 -05:00
};
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
public async addSegmentationRepresentation (
viewportId : string ,
{
segmentationId ,
2025-11-21 13:07:26 -05:00
predecessorImageId ,
2024-11-06 07:15:27 -05:00
type ,
2025-03-19 18:24:33 -04:00
config ,
2024-11-06 07:15:27 -05:00
suppressEvents = false ,
} : {
segmentationId : string ;
2025-11-21 13:07:26 -05:00
predecessorImageId? : string ;
2024-11-06 07:15:27 -05:00
type ?: csToolsEnums . SegmentationRepresentations ;
2025-03-19 18:24:33 -04:00
config ?: {
blendMode? : csEnums.BlendModes ;
};
2024-11-06 07:15:27 -05:00
suppressEvents? : boolean ;
}
) : Promise < void > {
const segmentation = this . getSegmentation ( segmentationId );
2025-11-21 13:07:26 -05:00
if ( segmentation && ! segmentation . predecessorImageId && predecessorImageId ) {
segmentation . predecessorImageId = predecessorImageId ;
}
2024-11-06 07:15:27 -05:00
const csViewport = this . getAndValidateViewport ( viewportId );
2025-04-07 16:45:29 -04:00
if ( ! csViewport ) {
return ;
}
2024-11-06 07:15:27 -05:00
const colorLUTIndex = this . _segmentationIdToColorLUTIndexMap . get ( segmentationId );
2023-10-10 13:21:17 -04:00
2024-11-06 07:15:27 -05:00
let isConverted = false ;
2023-10-10 13:21:17 -04:00
2025-10-08 03:10:32 +05:30
const defaultRepresentationType : csToolsEnums.SegmentationRepresentations =
csViewport . type === ViewportType . VOLUME_3D ? SURFACE : LABELMAP ;
let representationTypeToUse = type || defaultRepresentationType ;
2025-09-30 14:49:13 -03:00
if ( representationTypeToUse === LABELMAP ) {
2024-11-06 07:15:27 -05:00
const { isVolumeViewport , isVolumeSegmentation } = this . determineViewportAndSegmentationType (
csViewport ,
segmentation
2025-05-15 12:08:12 -04:00
) || { isVolumeViewport : false , isVolumeSegmentation : false };
2023-10-10 13:21:17 -04:00
2024-11-06 07:15:27 -05:00
({ representationTypeToUse , isConverted } = await this . handleViewportConversion (
isVolumeViewport ,
isVolumeSegmentation ,
csViewport ,
segmentation ,
viewportId ,
segmentationId ,
representationTypeToUse
));
2023-10-10 13:21:17 -04:00
}
2024-11-06 07:15:27 -05:00
await this . _addSegmentationRepresentation (
viewportId ,
segmentationId ,
representationTypeToUse ,
colorLUTIndex ,
2025-03-19 18:24:33 -04:00
isConverted ,
config
2024-11-06 07:15:27 -05:00
);
if ( ! suppressEvents ) {
this . _broadcastEvent ( this . EVENTS . SEGMENTATION_REPRESENTATION_MODIFIED , { segmentationId });
}
2023-10-10 13:21:17 -04:00
}
2022-11-16 14:19:52 -05:00
/**
2024-11-06 07:15:27 -05:00
* Creates an labelmap segmentation for a given display set
2022-11-16 14:19:52 -05:00
*
2024-11-06 07:15:27 -05:00
* @param displaySet - The display set to create the segmentation for.
* @param options - Optional parameters for creating the segmentation.
* @param options.segmentationId - Custom segmentation ID. If not provided, a UUID will be generated.
* @param options.FrameOfReferenceUID - Frame of reference UID for the segmentation.
* @param options.label - Label for the segmentation.
* @returns A promise that resolves to the created segmentation ID.
2022-11-16 14:19:52 -05:00
*/
2024-11-06 07:15:27 -05:00
public async createLabelmapForDisplaySet (
displaySet : AppTypes.DisplaySet ,
options ?: {
segmentationId? : string ;
segments ?: { [ segmentIndex : number ] : Partial < cstTypes.Segment > };
FrameOfReferenceUID? : string ;
label? : string ;
}
2025-10-29 14:15:52 -04:00
) : Promise < string > {
return this . _createSegmentationForDisplaySet ( displaySet , LABELMAP , options );
}
public async createContourForDisplaySet (
displaySet : AppTypes.DisplaySet ,
options ?: {
segmentationId? : string ;
segments ?: { [ segmentIndex : number ] : Partial < cstTypes.Segment > };
FrameOfReferenceUID? : string ;
label? : string ;
}
) : Promise < string > {
return this . _createSegmentationForDisplaySet ( displaySet , CONTOUR , options );
}
/**
* Private method to create segmentation for a display set with the specified type
*
* @param displaySet - The display set to create the segmentation for
* @param segmentationType - The type of segmentation (SegmentationRepresentations enum)
* @param options - Optional parameters for creating the segmentation
* @returns A promise that resolves to the created segmentation ID
*/
private async _createSegmentationForDisplaySet (
displaySet : AppTypes.DisplaySet ,
segmentationType : SegmentationRepresentations ,
options ?: {
segmentationId? : string ;
segments ?: { [ segmentIndex : number ] : Partial < cstTypes.Segment > };
FrameOfReferenceUID? : string ;
label? : string ;
}
2024-11-06 07:15:27 -05:00
) : Promise < string > {
// Todo: random does not makes sense, make this better, like
// labelmap 1, 2, 3 etc
const segmentationId = options ? . segmentationId ?? ` ${ csUtils . uuidv4 () } ` ;
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
const isDynamicVolume = displaySet . isDynamicVolume ;
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
let referenceImageIds = displaySet . imageIds ;
if ( isDynamicVolume ) {
// get the middle timepoint for referenceImageIds
const timePoints = displaySet . dynamicVolumeInfo . timePoints ;
const middleTimePoint = timePoints [ Math . floor ( timePoints . length / 2 )];
referenceImageIds = middleTimePoint ;
2022-11-16 14:19:52 -05:00
}
2024-11-06 07:15:27 -05:00
const derivedImages = await imageLoader . createAndCacheDerivedLabelmapImages ( referenceImageIds );
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
const segs = this . getSegmentations ();
2025-09-30 14:49:13 -03:00
const label = options ? . label || `Segmentation ${ segs . length + 1 } ` ;
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
const segImageIds = derivedImages . map ( image => image . imageId );
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
const segmentationPublicInput : cstTypes.SegmentationPublicInput = {
2022-11-16 14:19:52 -05:00
segmentationId ,
2024-11-06 07:15:27 -05:00
representation : {
2025-10-29 14:15:52 -04:00
type : segmentationType ,
2024-11-06 07:15:27 -05:00
data : {
imageIds : segImageIds ,
2025-04-01 14:19:03 -04:00
// referencedVolumeId: this._getVolumeIdForDisplaySet(displaySet),
2024-11-06 07:15:27 -05:00
referencedImageIds : referenceImageIds ,
},
},
config : {
label ,
segments :
2025-09-30 14:49:13 -03:00
options?.segments && Object . keys ( options . segments ). length > 0
2024-11-06 07:15:27 -05:00
? options . segments
: {
2025-01-03 21:08:34 +05:30
1 : {
label : ` ${ i18n . t ( 'Segment' ) } 1` ,
active : true ,
},
2024-11-06 07:15:27 -05:00
},
cachedStats : {
info : `S ${ displaySet . SeriesNumber } : ${ displaySet . SeriesDescription } ` ,
},
},
};
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
this . addOrUpdateSegmentation ( segmentationPublicInput );
return segmentationId ;
2022-11-16 14:19:52 -05:00
}
public async createSegmentationForSEGDisplaySet (
segDisplaySet ,
2024-11-06 07:15:27 -05:00
options : {
segmentationId? : string ;
2025-09-30 14:49:13 -03:00
type : csToolsEnums . SegmentationRepresentations ;
2024-11-06 07:15:27 -05:00
} = {
2025-01-03 21:08:34 +05:30
type : LABELMAP ,
}
2022-11-16 14:19:52 -05:00
) : Promise < string > {
2024-11-06 07:15:27 -05:00
const { type } = options ;
let { segmentationId } = options ;
2025-03-18 18:07:13 -03:00
const { labelMapImages } = segDisplaySet ;
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
if ( type !== LABELMAP ) {
throw new Error ( 'Only labelmap type is supported for SEG display sets right now' );
}
2022-11-16 14:19:52 -05:00
2025-03-18 18:07:13 -03:00
if ( ! labelMapImages || ! labelMapImages . length ) {
2024-11-06 07:15:27 -05:00
throw new Error ( 'SEG reading failed' );
2022-11-16 14:19:52 -05:00
}
2024-11-06 07:15:27 -05:00
segmentationId = segmentationId ?? segDisplaySet . displaySetInstanceUID ;
const referencedDisplaySetInstanceUID = segDisplaySet . referencedDisplaySetInstanceUID ;
const referencedDisplaySet = this . servicesManager . services . displaySetService . getDisplaySetByUID (
referencedDisplaySetInstanceUID
);
2023-04-26 12:21:08 -04:00
2024-11-06 07:15:27 -05:00
const images = referencedDisplaySet . instances ;
if ( ! images . length ) {
throw new Error ( 'No instances were provided for the referenced display set of the SEG' );
2023-04-26 12:21:08 -04:00
}
2024-11-06 07:15:27 -05:00
const imageIds = images . map ( image => image . imageId );
2025-03-18 18:07:13 -03:00
const derivedImages = labelMapImages ? . flat ();
const derivedImageIds = derivedImages . map ( image => image . imageId );
2022-11-16 14:19:52 -05:00
2025-03-18 18:07:13 -03:00
segDisplaySet . images = derivedImages . map ( image => ({
2024-12-20 08:12:58 -05:00
... image ,
... metaData . get ( 'instance' , image . referencedImageId ),
}));
2022-11-16 14:19:52 -05:00
2025-04-04 12:11:29 -04:00
segDisplaySet . imageIds = derivedImageIds ;
2025-03-18 18:07:13 -03:00
// We should parse the segmentation as separate slices to support overlapping segments.
// This parsing should occur in the CornerstoneJS library adapters.
// For now, we use the volume returned from the library and chop it here.
let firstSegmentedSliceImageId = null ;
for ( let i = 0 ; i < derivedImages . length ; i ++ ) {
const voxelManager = derivedImages [ i ]. voxelManager as csTypes . IVoxelManager < number >;
const scalarData = voxelManager . getScalarData ();
voxelManager . setScalarData ( scalarData );
// Check if this slice has any non-zero voxels and we haven't found one yet
if ( ! firstSegmentedSliceImageId && scalarData . some ( value => value !== 0 )) {
firstSegmentedSliceImageId = derivedImages [ i ]. referencedImageId ;
}
}
// assign the first non zero voxel image id to the segDisplaySet
segDisplaySet . firstSegmentedSliceImageId = firstSegmentedSliceImageId ;
2023-08-08 14:39:31 -04:00
const segmentsInfo = segDisplaySet . segMetadata . data ;
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
const segments : { [ segmentIndex : string ] : cstTypes . Segment } = {};
const colorLUT = [];
segmentsInfo . forEach (( segmentInfo , index ) => {
if ( index === 0 ) {
colorLUT . push ([ 0 , 0 , 0 , 0 ]);
2023-08-08 14:39:31 -04:00
return ;
2022-11-16 14:19:52 -05:00
}
2023-08-08 14:39:31 -04:00
const {
SegmentedPropertyCategoryCodeSequence ,
SegmentNumber ,
SegmentLabel ,
SegmentAlgorithmType ,
SegmentAlgorithmName ,
SegmentedPropertyTypeCodeSequence ,
rgba ,
} = segmentInfo ;
2024-11-06 07:15:27 -05:00
colorLUT . push ( rgba );
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
const segmentIndex = Number ( SegmentNumber );
2024-12-05 21:39:13 -05:00
const centroid = segDisplaySet . centroids ? . get ( index );
const imageCentroidXYZ = centroid ? . image || { x : 0 , y : 0 , z : 0 };
const worldCentroidXYZ = centroid ? . world || { x : 0 , y : 0 , z : 0 };
2024-11-06 07:15:27 -05:00
segments [ segmentIndex ] = {
segmentIndex ,
2023-08-08 14:39:31 -04:00
label : SegmentLabel || `Segment ${ SegmentNumber } ` ,
2024-11-06 07:15:27 -05:00
locked : false ,
active : false ,
cachedStats : {
center : {
image : [ imageCentroidXYZ . x , imageCentroidXYZ . y , imageCentroidXYZ . z ],
world : [ worldCentroidXYZ . x , worldCentroidXYZ . y , worldCentroidXYZ . z ],
},
modifiedTime : segDisplaySet.SeriesDate ,
category : SegmentedPropertyCategoryCodeSequence
? SegmentedPropertyCategoryCodeSequence . CodeMeaning
: '' ,
type : SegmentedPropertyTypeCodeSequence
? SegmentedPropertyTypeCodeSequence . CodeMeaning
: '' ,
algorithmType : SegmentAlgorithmType ,
algorithmName : SegmentAlgorithmName ,
},
2022-11-16 14:19:52 -05:00
};
});
2025-09-30 14:49:13 -03:00
const colorLUTIndex = addColorLUT ( colorLUT );
2024-11-06 07:15:27 -05:00
this . _segmentationIdToColorLUTIndexMap . set ( segmentationId , colorLUTIndex );
2023-08-08 14:39:31 -04:00
2023-04-26 12:21:08 -04:00
this . _broadcastEvent ( EVENTS . SEGMENTATION_LOADING_COMPLETE , {
2022-11-16 14:19:52 -05:00
segmentationId ,
segDisplaySet ,
});
2024-11-06 07:15:27 -05:00
const seg : cstTypes.SegmentationPublicInput = {
segmentationId ,
representation : {
type : LABELMAP ,
data : {
2025-03-18 18:07:13 -03:00
imageIds : derivedImageIds ,
2025-04-04 12:11:29 -04:00
// referencedVolumeId: this._getVolumeIdForDisplaySet(referencedDisplaySet),
2024-11-06 07:15:27 -05:00
referencedImageIds : imageIds as string [],
},
},
config : {
label : segDisplaySet.SeriesDescription ,
segments ,
},
};
segDisplaySet . isLoaded = true ;
this . addOrUpdateSegmentation ( seg );
return segmentationId ;
2023-04-26 12:21:08 -04:00
}
public async createSegmentationForRTDisplaySet (
rtDisplaySet ,
2024-11-06 07:15:27 -05:00
options : {
segmentationId? : string ;
2025-09-30 14:49:13 -03:00
type : csToolsEnums . SegmentationRepresentations ;
2024-11-06 07:15:27 -05:00
} = {
2025-01-03 21:08:34 +05:30
type : CONTOUR ,
}
2023-04-26 12:21:08 -04:00
) : Promise < string > {
2024-11-06 07:15:27 -05:00
const { type } = options ;
let { segmentationId } = options ;
// Currently, only contour representation is supported for RT display
if ( type !== CONTOUR ) {
throw new Error ( 'Only contour type is supported for RT display sets right now' );
}
// Assign segmentationId if not provided
2023-04-26 12:21:08 -04:00
segmentationId = segmentationId ?? rtDisplaySet . displaySetInstanceUID ;
const { structureSet } = rtDisplaySet ;
if ( ! structureSet ) {
throw new Error (
2024-11-06 07:15:27 -05:00
'To create the contours from RT displaySet, the displaySet should be loaded first. You can perform rtDisplaySet.load() before calling this method.'
2023-04-26 12:21:08 -04:00
);
}
const rtDisplaySetUID = rtDisplaySet . displaySetInstanceUID ;
2024-12-17 12:34:13 -05:00
const referencedDisplaySet = this . servicesManager . services . displaySetService . getDisplaySetByUID (
rtDisplaySet . referencedDisplaySetInstanceUID
);
const referencedImageIdsWithGeometry = Array . from ( structureSet . ReferencedSOPInstanceUIDsSet );
2025-03-27 16:52:22 -03:00
const referencedImageIds = referencedDisplaySet . imageIds ;
2024-12-17 12:34:13 -05:00
// find the first image id that contains a referenced SOP instance UID
2025-04-04 12:11:29 -04:00
const firstSegmentedSliceImageId =
referencedImageIds ? . find ( imageId =>
2025-09-05 09:35:16 -03:00
referencedImageIdsWithGeometry . some ( referencedId =>
imageId . includes ( referencedId as string )
)
2025-04-04 12:11:29 -04:00
) || null ;
2023-04-26 12:21:08 -04:00
2024-12-17 12:34:13 -05:00
rtDisplaySet . firstSegmentedSliceImageId = firstSegmentedSliceImageId ;
2025-09-05 09:35:16 -03:00
if ( ! structureSet . ROIContours ? . length ) {
throw new Error (
'The structureSet does not contain any ROIContours. Please ensure the structureSet is loaded first.'
);
}
2024-11-06 07:15:27 -05:00
// Map ROI contours to RT Struct Data
2023-09-01 16:19:39 -04:00
const allRTStructData = mapROIContoursToRTStructData ( structureSet , rtDisplaySetUID );
2023-04-26 12:21:08 -04:00
2024-11-06 07:15:27 -05:00
// Sort by segmentIndex for consistency
2023-04-26 12:21:08 -04:00
allRTStructData . sort (( a , b ) => a . segmentIndex - b . segmentIndex );
const geometryIds = allRTStructData . map (({ geometryId }) => geometryId );
2024-11-06 07:15:27 -05:00
// Initialize SegmentationPublicInput similar to SEG function
const segmentation : cstTypes.SegmentationPublicInput = {
segmentationId ,
representation : {
type : CONTOUR ,
data : {
2023-04-26 12:21:08 -04:00
geometryIds ,
},
},
2024-11-06 07:15:27 -05:00
config : {
label : rtDisplaySet.SeriesDescription ,
},
2023-04-26 12:21:08 -04:00
};
2024-11-06 07:15:27 -05:00
const segments : { [ segmentIndex : string ] : cstTypes . Segment } = {};
let segmentsCachedStats = {};
2025-04-02 18:10:47 -04:00
// Create colorLUT array for RT structures
const colorLUT = [[ 0 , 0 , 0 , 0 ]]; // First entry is transparent for index 0
2024-11-06 07:15:27 -05:00
// Process each segment similarly to the SEG function
2025-09-30 14:49:13 -03:00
for ( let i = 0 ; i < allRTStructData . length ; i ++ ) {
const rtStructData = allRTStructData [ i ];
2025-05-05 16:35:05 -04:00
const { data , id , color , segmentIndex , geometryId , group } = rtStructData ;
2023-04-26 12:21:08 -04:00
2025-04-02 18:10:47 -04:00
// Add the color to the colorLUT array
colorLUT . push ( color );
2023-06-19 09:01:23 -04:00
try {
2023-09-01 16:19:39 -04:00
const geometry = await geometryLoader . createAndCacheGeometry ( geometryId , {
geometryData : {
data ,
id ,
color ,
frameOfReferenceUID : structureSet.frameOfReferenceUID ,
segmentIndex ,
},
type : csEnums . GeometryType . CONTOUR ,
});
2023-06-19 09:01:23 -04:00
2024-11-06 07:15:27 -05:00
const contourSet = geometry . data as csTypes . IContourSet ;
const centroid = contourSet . centroid ;
2023-04-26 12:21:08 -04:00
2024-11-06 07:15:27 -05:00
segmentsCachedStats = {
2023-06-19 09:01:23 -04:00
center : { world : centroid },
2024-11-06 07:15:27 -05:00
modifiedTime : rtDisplaySet.SeriesDate , // Using SeriesDate as modifiedTime
2023-06-19 09:01:23 -04:00
};
2023-04-26 12:21:08 -04:00
2024-11-06 07:15:27 -05:00
segments [ segmentIndex ] = {
2023-06-19 09:01:23 -04:00
label : id ,
segmentIndex ,
2024-11-06 07:15:27 -05:00
cachedStats : segmentsCachedStats ,
locked : false ,
active : false ,
2025-05-05 16:35:05 -04:00
group ,
2023-06-19 09:01:23 -04:00
};
2023-04-26 12:21:08 -04:00
2024-11-06 07:15:27 -05:00
// Broadcast segment loading progress
2023-06-19 09:01:23 -04:00
const numInitialized = Object . keys ( segmentsCachedStats ). length ;
2023-09-01 16:19:39 -04:00
const percentComplete = Math . round (( numInitialized / allRTStructData . length ) * 100 );
2023-06-19 09:01:23 -04:00
this . _broadcastEvent ( EVENTS . SEGMENT_LOADING_COMPLETE , {
percentComplete ,
numSegments : allRTStructData.length ,
});
} catch ( e ) {
2024-11-06 07:15:27 -05:00
console . warn ( `Error initializing contour for segment ${ segmentIndex } :` , e );
continue ; // Continue processing other segments even if one fails
2023-06-19 09:01:23 -04:00
}
2023-04-26 12:21:08 -04:00
}
2025-04-02 18:10:47 -04:00
// Create and register the colorLUT
2025-09-30 14:49:13 -03:00
const colorLUTIndex = addColorLUT ( colorLUT );
2025-04-02 18:10:47 -04:00
this . _segmentationIdToColorLUTIndexMap . set ( segmentationId , colorLUTIndex );
2024-11-06 07:15:27 -05:00
// Assign processed segments to segmentation config
segmentation . config . segments = segments ;
2023-04-26 12:21:08 -04:00
2024-11-06 07:15:27 -05:00
// Broadcast segmentation loading complete event
2023-04-26 12:21:08 -04:00
this . _broadcastEvent ( EVENTS . SEGMENTATION_LOADING_COMPLETE , {
segmentationId ,
rtDisplaySet ,
});
2024-11-06 07:15:27 -05:00
// Mark the RT display set as loaded
rtDisplaySet . isLoaded = true ;
// Add or update the segmentation in the state
this . addOrUpdateSegmentation ( segmentation );
return segmentationId ;
2022-11-16 14:19:52 -05:00
}
2024-11-06 07:15:27 -05:00
/**
* Adds or updates a segmentation in the state
* @param segmentationId - The ID of the segmentation to add or update
* @param data - The data to add or update the segmentation with
*
* @remarks
* This method handles the addition or update of a segmentation in the state.
* If the segmentation already exists, it updates the existing segmentation.
* If the segmentation does not exist, it adds a new segmentation.
*/
public addOrUpdateSegmentation (
data : cstTypes.SegmentationPublicInput | Partial < cstTypes.Segmentation >
) {
const segmentationId = data . segmentationId ;
const existingSegmentation = cstSegmentation . state . getSegmentation ( segmentationId );
2023-09-22 10:23:44 -04:00
2024-11-06 07:15:27 -05:00
if ( existingSegmentation ) {
// Update the existing segmentation
this . updateSegmentationInSource ( segmentationId , data as Partial < cstTypes.Segmentation >);
} else {
// Add a new segmentation
this . addSegmentationToSource ( data as cstTypes . SegmentationPublicInput );
2023-09-22 10:23:44 -04:00
}
2024-11-06 07:15:27 -05:00
}
2023-09-22 10:23:44 -04:00
2024-11-06 07:15:27 -05:00
public setActiveSegmentation ( viewportId : string , segmentationId : string ) : void {
cstSegmentation . activeSegmentation . setActiveSegmentation ( viewportId , segmentationId );
}
2023-09-22 10:23:44 -04:00
2024-11-06 07:15:27 -05:00
/**
* Gets the active segmentation for a viewport
* @param viewportId - The ID of the viewport to get the active segmentation for
* @returns The active segmentation object, or null if no segmentation is active
*
* @remarks
* This method retrieves the currently active segmentation for the specified viewport.
* The active segmentation is the one that is currently selected for editing operations.
* Returns null if no segmentation is active in the viewport.
*/
public getActiveSegmentation ( viewportId : string ) : cstTypes . Segmentation | null {
return cstSegmentation . activeSegmentation . getActiveSegmentation ( viewportId );
}
2023-09-22 10:23:44 -04:00
2024-11-06 07:15:27 -05:00
/**
* Gets the active segment from the active segmentation in a viewport
* @param viewportId - The ID of the viewport to get the active segment from
* @returns The active segment object, or undefined if no segment is active
*
* @remarks
* This method retrieves the currently active segment from the active segmentation
* in the specified viewport. The active segment is the one that is currently
* selected for editing operations. Returns undefined if no segment is active or
* if there is no active segmentation.
*/
public getActiveSegment ( viewportId : string ) : cstTypes . Segment | undefined {
const activeSegmentation = this . getActiveSegmentation ( viewportId );
2023-09-22 10:23:44 -04:00
2024-11-06 07:15:27 -05:00
if ( ! activeSegmentation ) {
return ;
2023-09-22 10:23:44 -04:00
}
2024-11-06 07:15:27 -05:00
const { segments } = activeSegmentation ;
2023-09-22 10:23:44 -04:00
2024-11-06 07:15:27 -05:00
let activeSegment ;
for ( const segment of Object . values ( segments )) {
if ( segment . active ) {
activeSegment = segment ;
break ;
2023-09-22 10:23:44 -04:00
}
}
2024-11-06 07:15:27 -05:00
return activeSegment ;
}
public hasCustomStyles ( specifier : {
viewportId : string ;
segmentationId : string ;
2025-09-30 14:49:13 -03:00
type : csToolsEnums . SegmentationRepresentations ;
2024-11-06 07:15:27 -05:00
}) : boolean {
return cstSegmentation . config . style . hasCustomStyle ( specifier );
}
public getStyle = ( specifier : {
viewportId : string ;
segmentationId : string ;
2025-09-30 14:49:13 -03:00
type : csToolsEnums . SegmentationRepresentations ;
2024-11-06 07:15:27 -05:00
segmentIndex? : number ;
}) => {
const style = cstSegmentation . config . style . getStyle ( specifier );
return style ;
2023-09-22 10:23:44 -04:00
};
2024-11-06 07:15:27 -05:00
public setStyle = (
specifier : {
2025-09-30 14:49:13 -03:00
type : csToolsEnums . SegmentationRepresentations ;
2024-11-06 07:15:27 -05:00
viewportId? : string ;
segmentationId? : string ;
segmentIndex? : number ;
},
2025-11-19 17:55:53 -05:00
style : cstTypes.LabelmapStyle | cstTypes . ContourStyle | cstTypes . SurfaceStyle ,
merge : boolean = true
2024-11-06 07:15:27 -05:00
) => {
2025-11-19 17:55:53 -05:00
cstSegmentation . config . style . setStyle ( specifier , style , merge );
this . _broadcastEvent ( EVENTS . SEGMENTATION_STYLE_MODIFIED , {
specifier ,
style ,
merge ,
});
2024-11-06 07:15:27 -05:00
};
public resetToGlobalStyle = () => {
cstSegmentation . config . style . resetToGlobalStyle ();
};
2025-10-29 14:15:52 -04:00
public getNextAvailableSegmentIndex ( segmentationId : string ) : number {
const csSegmentation = this . getCornerstoneSegmentation ( segmentationId );
// grab the next available segment index based on the object keys,
// so basically get the highest segment index value + 1
const segmentKeys = Object . keys ( csSegmentation . segments );
return segmentKeys . length === 0 ? 1 : Math.max (... segmentKeys . map ( Number )) + 1 ;
}
2024-11-06 07:15:27 -05:00
/**
* Adds a new segment to the specified segmentation.
* @param segmentationId - The ID of the segmentation to add the segment to.
* @param viewportId: The ID of the viewport to add the segment to, it is used to get the representation, if it is not
* provided, the first available representation for the segmentationId will be used.
* @param config - An object containing the configuration options for the new segment.
* - segmentIndex: (optional) The index of the segment to add. If not provided, the next available index will be used.
* - properties: (optional) An object containing the properties of the new segment.
* - label: (optional) The label of the new segment. If not provided, a default label will be used.
* - color: (optional) The color of the new segment in RGB format. If not provided, a default color will be used.
* - visibility: (optional) Whether the new segment should be visible. If not provided, the segment will be visible by default.
* - isLocked: (optional) Whether the new segment should be locked for editing. If not provided, the segment will not be locked by default.
* - active: (optional) Whether the new segment should be the active segment to be edited. If not provided, the segment will not be active by default.
*/
public addSegment (
2022-11-16 14:19:52 -05:00
segmentationId : string ,
2024-11-06 07:15:27 -05:00
config : {
segmentIndex? : number ;
label? : string ;
isLocked? : boolean ;
active? : boolean ;
color? : csTypes.Color ; // Add color type
visibility? : boolean ; // Add visibility option
} = {}
2022-11-16 14:19:52 -05:00
) : void {
2024-11-06 07:15:27 -05:00
if ( config ? . segmentIndex === 0 ) {
2025-01-03 21:08:34 +05:30
throw new Error ( i18n . t ( 'Segment' ) + ' index 0 is reserved for "no label"' );
2024-11-06 07:15:27 -05:00
}
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
const csSegmentation = this . getCornerstoneSegmentation ( segmentationId );
let segmentIndex = config . segmentIndex ;
if ( ! segmentIndex ) {
2025-10-29 14:15:52 -04:00
segmentIndex = this . getNextAvailableSegmentIndex ( segmentationId );
2024-11-06 07:15:27 -05:00
}
// update the segmentation
if ( ! config . label ) {
2025-01-03 21:08:34 +05:30
config . label = ` ${ i18n . t ( 'Segment' ) } ${ segmentIndex } ` ;
2023-09-22 10:23:44 -04:00
}
2024-11-06 07:15:27 -05:00
const currentSegments = csSegmentation . segments ;
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
cstSegmentation . updateSegmentations ([
{
segmentationId ,
payload : {
segments : {
... currentSegments ,
[ segmentIndex ] : {
... currentSegments [ segmentIndex ],
segmentIndex ,
cachedStats : {},
locked : false ,
... config ,
},
},
},
},
]);
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
this . setActiveSegment ( segmentationId , segmentIndex );
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
// Apply additional configurations
if ( config . isLocked !== undefined ) {
this . _setSegmentLockedStatus ( segmentationId , segmentIndex , config . isLocked );
2022-11-16 14:19:52 -05:00
}
2024-11-06 07:15:27 -05:00
// Get all viewports that have this segmentation
const viewportIds = this . getViewportIdsWithSegmentation ( segmentationId );
viewportIds . forEach ( viewportId => {
// Set color if provided
if ( config . color !== undefined ) {
this . setSegmentColor ( viewportId , segmentationId , segmentIndex , config . color );
2022-11-16 14:19:52 -05:00
}
2024-11-06 07:15:27 -05:00
// Set visibility if provided
if ( config . visibility !== undefined ) {
this . setSegmentVisibility ( viewportId , segmentationId , segmentIndex , config . visibility );
2022-11-16 14:19:52 -05:00
}
});
}
2024-11-06 07:15:27 -05:00
/**
* Removes a segment from a segmentation and updates the active segment index if necessary.
*
* @param segmentationId - The ID of the segmentation containing the segment to remove.
* @param segmentIndex - The index of the segment to remove.
*
* @remarks
* This method performs the following actions:
* 1. Clears the segment value in the Cornerstone segmentation.
* 2. Updates all related segmentation representations to remove the segment.
* 3. If the removed segment was the active segment, it updates the active segment index.
*
*/
public removeSegment ( segmentationId : string , segmentIndex : number ) : void {
cstSegmentation . removeSegment ( segmentationId , segmentIndex );
}
public setSegmentVisibility (
viewportId : string ,
2022-11-16 14:19:52 -05:00
segmentationId : string ,
segmentIndex : number ,
2024-11-06 07:15:27 -05:00
isVisible : boolean ,
2025-09-30 14:49:13 -03:00
type ?: csToolsEnums . SegmentationRepresentations
2022-11-16 14:19:52 -05:00
) : void {
2024-11-06 07:15:27 -05:00
this . _setSegmentVisibility ( viewportId , segmentationId , segmentIndex , isVisible , type );
}
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
/**
* Sets the locked status of a segment in a segmentation.
*
* @param segmentationId - The ID of the segmentation containing the segment.
* @param segmentIndex - The index of the segment to set the locked status for.
* @param isLocked - The new locked status of the segment.
*
* @remarks
* This method updates the locked status of a specific segment within a segmentation.
* A locked segment cannot be modified or edited.
*/
public setSegmentLocked ( segmentationId : string , segmentIndex : number , isLocked : boolean ) : void {
this . _setSegmentLockedStatus ( segmentationId , segmentIndex , isLocked );
}
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
/**
* Toggles the locked state of a segment in a segmentation.
* @param segmentationId - The ID of the segmentation.
* @param segmentIndex - The index of the segment to toggle.
*/
public toggleSegmentLocked ( segmentationId : string , segmentIndex : number ) : void {
const isLocked = cstSegmentation . segmentLocking . isSegmentIndexLocked (
2022-11-16 14:19:52 -05:00
segmentationId ,
2024-11-06 07:15:27 -05:00
segmentIndex
2022-11-16 14:19:52 -05:00
);
2024-11-06 07:15:27 -05:00
this . _setSegmentLockedStatus ( segmentationId , segmentIndex , ! isLocked );
}
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
public toggleSegmentVisibility (
viewportId : string ,
segmentationId : string ,
segmentIndex : number ,
2025-09-30 14:49:13 -03:00
type : csToolsEnums . SegmentationRepresentations
2024-11-06 07:15:27 -05:00
) : void {
const isVisible = cstSegmentation . config . visibility . getSegmentIndexVisibility (
viewportId ,
{
segmentationId ,
type ,
},
segmentIndex
);
this . _setSegmentVisibility ( viewportId , segmentationId , segmentIndex , ! isVisible , type );
}
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
/**
* Sets the color of a specific segment in a segmentation.
*
* @param viewportId - The ID of the viewport containing the segmentation
* @param segmentationId - The ID of the segmentation containing the segment
* @param segmentIndex - The index of the segment to set the color for
* @param color - The new color to apply to the segment as an array of RGBA values
*
* @remarks
* This method updates the color of a specific segment within a segmentation.
* The color parameter should be an array of 4 numbers representing RGBA values.
*/
public setSegmentColor (
viewportId : string ,
segmentationId : string ,
segmentIndex : number ,
color : csTypes.Color
) : void {
2025-10-07 14:23:26 -03:00
const segmentationRepresentations = this . getSegmentationRepresentations ( viewportId , {
segmentationId ,
});
const { colorLUTIndex } = segmentationRepresentations [ 0 ];
this . _segmentationIdToColorLUTIndexMap . set ( segmentationId , colorLUTIndex );
2024-11-06 07:15:27 -05:00
cstSegmentation . config . color . setSegmentIndexColor (
viewportId ,
segmentationId ,
2023-04-26 12:21:08 -04:00
segmentIndex ,
2024-11-06 07:15:27 -05:00
color
2023-04-26 12:21:08 -04:00
);
2022-11-16 14:19:52 -05:00
}
2024-11-06 07:15:27 -05:00
/**
* Gets the current color of a specific segment in a segmentation.
*
* @param viewportId - The ID of the viewport containing the segmentation
* @param segmentationId - The ID of the segmentation containing the segment
* @param segmentIndex - The index of the segment to get the color for
* @returns An array of 4 numbers representing the RGBA color values of the segment
*
* @remarks
* This method retrieves the current color of a specific segment within a segmentation.
* The returned color is an array of 4 numbers representing RGBA values.
*/
public getSegmentColor ( viewportId : string , segmentationId : string , segmentIndex : number ) {
return cstSegmentation . config . color . getSegmentIndexColor (
viewportId ,
segmentationId ,
segmentIndex
);
}
2023-05-12 15:04:19 -04:00
2024-11-06 07:15:27 -05:00
/**
* Gets the labelmap volume for a segmentation
* @param segmentationId - The ID of the segmentation to get the labelmap volume for
* @returns The labelmap volume for the segmentation, or null if not found
*
* @remarks
* This method retrieves the labelmap volume data for a specific segmentation.
* The labelmap volume contains the actual segmentation data in the form of a 3D volume.
* Returns null if the segmentation does not have valid labelmap volume data.
*/
public getLabelmapVolume ( segmentationId : string ) {
const csSegmentation = cstSegmentation . state . getSegmentation ( segmentationId );
const labelmapData = csSegmentation . representationData [
2025-09-30 14:49:13 -03:00
LABELMAP
2024-11-06 07:15:27 -05:00
] as cstTypes . LabelmapToolOperationDataVolume ;
2023-04-26 12:21:08 -04:00
2024-11-06 07:15:27 -05:00
if ( ! labelmapData || ! labelmapData . volumeId ) {
return null ;
}
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
const { volumeId } = labelmapData ;
const labelmapVolume = cache . getVolume ( volumeId );
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
return labelmapVolume ;
}
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
/**
* Sets the label for a specific segment in a segmentation
* @param segmentationId - The ID of the segmentation containing the segment
* @param segmentIndex - The index of the segment to set the label for
* @param label - The new label to apply to the segment
*
* @remarks
* This method updates the text label of a specific segment within a segmentation.
* The label is used to identify and describe the segment in the UI.
*/
public setSegmentLabel ( segmentationId : string , segmentIndex : number , label : string ) {
this . _setSegmentLabel ( segmentationId , segmentIndex , label );
}
2023-04-26 12:21:08 -04:00
2024-11-06 07:15:27 -05:00
/**
* Sets the active segment for a segmentation
* @param segmentationId - The ID of the segmentation containing the segment
* @param segmentIndex - The index of the segment to set as active
*
* @remarks
* This method updates which segment is considered "active" within a segmentation.
* The active segment is typically highlighted and available for editing operations.
*/
public setActiveSegment ( segmentationId : string , segmentIndex : number ) : void {
this . _setActiveSegment ( segmentationId , segmentIndex );
}
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
/**
* Controls whether inactive segmentations should be rendered in a viewport
* @param viewportId - The ID of the viewport to update
* @param renderInactive - Whether inactive segmentations should be rendered
*
* @remarks
* This method configures if segmentations that are not currently active
* should still be visible in the specified viewport. This can be useful
* for comparing or viewing multiple segmentations simultaneously.
*/
public setRenderInactiveSegmentations ( viewportId : string , renderInactive : boolean ) : void {
cstSegmentation . config . style . setRenderInactiveSegmentations ( viewportId , renderInactive );
}
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
/**
* Gets whether inactive segmentations are being rendered for a viewport
* @param viewportId - The ID of the viewport to check
* @returns boolean indicating if inactive segmentations are rendered
*
* @remarks
* This method retrieves the current rendering state for inactive segmentations
* in the specified viewport. Returns true if inactive segmentations are visible.
*/
public getRenderInactiveSegmentations ( viewportId : string ) : boolean {
return cstSegmentation . config . style . getRenderInactiveSegmentations ( viewportId );
}
2025-03-17 22:34:34 -04:00
/**
* Sets statistics for a group of segmentations
* @param segmentationIds - Array of segmentation IDs that form the group
* @param stats - Statistics object containing metrics for the segmentation group
*
* @remarks
* This method stores statistical data for a group of related segmentations.
* The stats are stored using a composite key created from the sorted and joined
*/
public setSegmentationGroupStats ( segmentationIds : string [], stats : any ) {
const groupId = this . getGroupId ( segmentationIds );
this . _segmentationGroupStatsMap . set ( groupId , stats );
}
/**
* Gets statistics for a group of segmentations
* @param segmentationIds - Array of segmentation IDs that form the group
* @returns The stored statistics object for the segmentation group if found, undefined otherwise
*/
public getSegmentationGroupStats ( segmentationIds : string []) {
const groupId = this . getGroupId ( segmentationIds );
return this . _segmentationGroupStatsMap . get ( groupId );
}
private getGroupId ( segmentationIds : string []) {
return segmentationIds . sort (). join ( ',' );
}
2022-11-16 14:19:52 -05:00
/**
* Toggles the visibility of a segmentation in the state, and broadcasts the event.
* Note: this method does not update the segmentation state in the source. It only
* updates the state, and there should be separate listeners for that.
* @param ids segmentation ids
*/
2024-11-06 07:15:27 -05:00
public toggleSegmentationRepresentationVisibility = (
viewportId : string ,
2025-09-30 14:49:13 -03:00
{
segmentationId ,
type ,
} : { segmentationId : string ; type : csToolsEnums . SegmentationRepresentations }
2024-11-06 07:15:27 -05:00
) : void => {
this . _toggleSegmentationRepresentationVisibility ( viewportId , segmentationId , type );
2022-11-16 14:19:52 -05:00
};
2024-11-06 07:15:27 -05:00
public getViewportIdsWithSegmentation = ( segmentationId : string ) : string [] => {
const viewportIds = cstSegmentation . state . getViewportIdsWithSegmentation ( segmentationId );
return viewportIds ;
};
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
/**
* Clears segmentation representations from the viewport.
* Unlike removeSegmentationRepresentations, this doesn't update
* removed display set and representation maps.
* We track removed segmentations manually to avoid re-adding them
* when the display set is added again.
* @param viewportId - The viewport ID to clear segmentation representations from.
*/
public clearSegmentationRepresentations ( viewportId : string ) : void {
this . removeSegmentationRepresentations ( viewportId );
}
2024-05-21 16:43:47 -04:00
2024-11-06 07:15:27 -05:00
/**
* Completely removes a segmentation from the state
* @param segmentationId - The ID of the segmentation to remove.
*/
public remove ( segmentationId : string ) : void {
cstSegmentation . state . removeSegmentation ( segmentationId );
}
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
public removeAllSegmentations () : void {
cstSegmentation . state . removeAllSegmentations ();
}
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
/**
* It removes the segmentation representations from the viewport.
* @param viewportId - The viewport id to remove the segmentation representations from.
* @param specifier - The specifier to remove the segmentation representations.
*
* @remarks
* If no specifier is provided, all segmentation representations for the viewport are removed.
* If a segmentationId specifier is provided, only the segmentation representation with the specified segmentationId and type are removed.
* If a type specifier is provided, only the segmentation representation with the specified type are removed.
* If both a segmentationId and type specifier are provided, only the segmentation representation with the specified segmentationId and type are removed.
*/
public removeSegmentationRepresentations (
viewportId : string ,
specifier : {
segmentationId? : string ;
2025-09-30 14:49:13 -03:00
type ?: csToolsEnums . SegmentationRepresentations ;
2024-11-06 07:15:27 -05:00
} = {}
) : void {
cstSegmentation . removeSegmentationRepresentations ( viewportId , specifier );
}
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
public jumpToSegmentCenter (
segmentationId : string ,
segmentIndex : number ,
viewportId? : string ,
highlightAlpha = 0.9 ,
highlightSegment = true ,
animationLength = 750 ,
highlightHideOthers = false ,
2025-09-21 23:48:35 -03:00
animationFunctionType : EasingFunctionEnum = EasingFunctionEnum . EASE_IN_OUT
2024-11-06 07:15:27 -05:00
) : void {
const center = this . _getSegmentCenter ( segmentationId , segmentIndex );
if ( ! center ) {
2025-03-18 18:07:13 -03:00
console . warn ( 'No center found for segmentation' , segmentationId , segmentIndex );
2024-11-06 07:15:27 -05:00
return ;
}
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
const { world } = center as { world : csTypes.Point3 };
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
// need to find which viewports are displaying the segmentation
const viewportIds = viewportId
? [ viewportId ]
: this . getViewportIdsWithSegmentation ( segmentationId );
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
viewportIds . forEach ( viewportId => {
const { viewport } = getEnabledElementByViewportId ( viewportId );
2026-01-13 15:57:44 -05:00
if ( ! viewport ? . jumpToWorld ) {
return ;
}
2024-11-06 07:15:27 -05:00
viewport . jumpToWorld ( world );
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
highlightSegment &&
this . highlightSegment (
2022-11-16 14:19:52 -05:00
segmentationId ,
segmentIndex ,
2024-11-06 07:15:27 -05:00
viewportId ,
highlightAlpha ,
animationLength ,
2025-09-21 23:48:35 -03:00
highlightHideOthers ,
animationFunctionType
2022-11-16 14:19:52 -05:00
);
2024-11-06 07:15:27 -05:00
});
}
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
public highlightSegment (
segmentationId : string ,
segmentIndex : number ,
viewportId? : string ,
alpha = 0.9 ,
animationLength = 750 ,
hideOthers = true ,
2025-09-21 23:48:35 -03:00
animationFunctionType : EasingFunctionEnum = EasingFunctionEnum . EASE_IN_OUT
2024-11-06 07:15:27 -05:00
) : void {
if ( this . highlightIntervalId ) {
clearInterval ( this . highlightIntervalId );
2022-11-16 14:19:52 -05:00
}
2023-09-22 10:23:44 -04:00
2024-11-06 07:15:27 -05:00
const csSegmentation = this . getCornerstoneSegmentation ( segmentationId );
const viewportIds = viewportId
? [ viewportId ]
: this . getViewportIdsWithSegmentation ( segmentationId );
viewportIds . forEach ( viewportId => {
const segmentationRepresentation = this . getSegmentationRepresentations ( viewportId , {
segmentationId ,
2023-09-22 10:23:44 -04:00
});
2024-11-06 07:15:27 -05:00
const representation = segmentationRepresentation [ 0 ];
const { type } = representation ;
const segments = csSegmentation . segments ;
const highlightFn =
type === LABELMAP ? this . _highlightLabelmap . bind ( this ) : this . _highlightContour . bind ( this );
const adjustedAlpha = type === LABELMAP ? alpha : 1 - alpha ;
highlightFn (
segmentIndex ,
adjustedAlpha ,
hideOthers ,
segments ,
viewportId ,
animationLength ,
2025-09-21 23:48:35 -03:00
representation ,
animationFunctionType
2024-11-06 07:15:27 -05:00
);
});
}
private getAndValidateViewport ( viewportId : string ) {
const csViewport =
this . servicesManager . services . cornerstoneViewportService . getCornerstoneViewport ( viewportId );
if ( ! csViewport ) {
2025-04-07 16:45:29 -04:00
console . warn ( `Viewport with id ${ viewportId } not found.` );
return null ;
2023-09-22 10:23:44 -04:00
}
2024-11-06 07:15:27 -05:00
return csViewport ;
}
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
/**
* Sets the visibility of a segmentation representation.
*
* @param viewportId - The ID of the viewport.
* @param segmentationId - The ID of the segmentation.
* @param isVisible - The new visibility state.
*/
private _setSegmentationRepresentationVisibility (
viewportId : string ,
2022-11-16 14:19:52 -05:00
segmentationId : string ,
2025-09-30 14:49:13 -03:00
type : csToolsEnums . SegmentationRepresentations ,
2024-11-06 07:15:27 -05:00
isVisible : boolean
) : void {
const representations = this . getSegmentationRepresentations ( viewportId , {
segmentationId ,
type ,
});
const representation = representations [ 0 ];
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
if ( ! representation ) {
console . debug (
'No segmentation representation found for the given viewportId and segmentationId'
);
return ;
2022-11-16 14:19:52 -05:00
}
2024-11-06 07:15:27 -05:00
cstSegmentation . config . visibility . setSegmentationRepresentationVisibility (
viewportId ,
{
segmentationId ,
type ,
},
isVisible
);
}
private determineViewportAndSegmentationType ( csViewport , segmentation ) {
const isVolumeViewport =
csViewport . type === ViewportType . ORTHOGRAPHIC || csViewport . type === ViewportType . VOLUME_3D ;
const isVolumeSegmentation = 'volumeId' in segmentation . representationData [ LABELMAP ];
return { isVolumeViewport , isVolumeSegmentation };
}
private async handleViewportConversion (
isVolumeViewport : boolean ,
isVolumeSegmentation : boolean ,
csViewport : csTypes.IViewport ,
segmentation : cstTypes.Segmentation ,
viewportId : string ,
segmentationId : string ,
representationType : csToolsEnums.SegmentationRepresentations
) {
let representationTypeToUse = representationType ;
let isConverted = false ;
const handler = isVolumeViewport ? this . handleVolumeViewportCase : this.handleStackViewportCase ;
({ representationTypeToUse , isConverted } = await handler . apply ( this , [
csViewport ,
segmentation ,
isVolumeSegmentation ,
viewportId ,
2022-11-16 14:19:52 -05:00
segmentationId ,
2024-11-06 07:15:27 -05:00
]));
return { representationTypeToUse , isConverted };
}
private async handleVolumeViewportCase ( csViewport , segmentation , isVolumeSegmentation ) {
if ( csViewport . type === ViewportType . VOLUME_3D ) {
2025-09-30 14:49:13 -03:00
return {
representationTypeToUse : SURFACE ,
isConverted : false ,
};
2024-11-06 07:15:27 -05:00
} else {
await this . handleVolumeViewport (
csViewport as csTypes . IVolumeViewport ,
segmentation ,
isVolumeSegmentation
);
2025-09-30 14:49:13 -03:00
return { representationTypeToUse : LABELMAP , isConverted : false };
2024-11-06 07:15:27 -05:00
}
}
private async handleStackViewportCase (
csViewport : csTypes.IViewport ,
segmentation : cstTypes.Segmentation ,
isVolumeSegmentation : boolean ,
viewportId : string ,
segmentationId : string
2025-09-30 14:49:13 -03:00
) : Promise < {
representationTypeToUse : csToolsEnums.SegmentationRepresentations ;
isConverted : boolean ;
} > {
2024-11-06 07:15:27 -05:00
if ( isVolumeSegmentation ) {
const isConverted = await this . convertStackToVolumeViewport ( csViewport );
2025-09-30 14:49:13 -03:00
return { representationTypeToUse : LABELMAP , isConverted };
2024-11-06 07:15:27 -05:00
}
if ( updateLabelmapSegmentationImageReferences ( viewportId , segmentationId )) {
2025-09-30 14:49:13 -03:00
return { representationTypeToUse : LABELMAP , isConverted : false };
2024-11-06 07:15:27 -05:00
}
const isConverted = await this . attemptStackToVolumeConversion (
csViewport as csTypes . IStackViewport ,
segmentation ,
viewportId ,
segmentationId
2022-11-16 14:19:52 -05:00
);
2024-11-06 07:15:27 -05:00
2025-09-30 14:49:13 -03:00
return { representationTypeToUse : LABELMAP , isConverted };
2024-11-06 07:15:27 -05:00
}
private async _addSegmentationRepresentation (
viewportId : string ,
segmentationId : string ,
representationType : csToolsEnums.SegmentationRepresentations ,
colorLUTIndex : number ,
2025-03-19 18:24:33 -04:00
isConverted : boolean ,
config ?: {
blendMode? : csEnums.BlendModes ;
}
2024-11-06 07:15:27 -05:00
) : Promise < void > {
const representation = {
type : representationType ,
2022-11-16 14:19:52 -05:00
segmentationId ,
2025-03-19 18:24:33 -04:00
config : { colorLUTOrIndex : colorLUTIndex , ... config },
2024-11-06 07:15:27 -05:00
};
const addRepresentation = () =>
cstSegmentation . addSegmentationRepresentations ( viewportId , [ representation ]);
if ( isConverted ) {
const { viewportGridService } = this . servicesManager . services ;
await new Promise < void >( resolve => {
const { unsubscribe } = viewportGridService . subscribe (
viewportGridService . EVENTS . GRID_STATE_CHANGED ,
() => {
addRepresentation ();
unsubscribe ();
resolve ();
}
);
});
} else {
addRepresentation ();
}
}
private async handleVolumeViewport (
viewport : csTypes.IVolumeViewport ,
segmentation : SegmentationData ,
isVolumeSegmentation : boolean
) : Promise < void > {
if ( isVolumeSegmentation ) {
return ; // Volume Labelmap on Volume Viewport is natively supported
}
const frameOfReferenceUID = viewport . getFrameOfReferenceUID ();
const imageIds = getLabelmapImageIds ( segmentation . segmentationId );
const segImage = cache . getImage ( imageIds [ 0 ]);
if ( segImage ? . FrameOfReferenceUID === frameOfReferenceUID ) {
await convertStackToVolumeLabelmap ( segmentation );
}
}
private async convertStackToVolumeViewport ( viewport : csTypes.IViewport ) : Promise < boolean > {
const { viewportGridService , cornerstoneViewportService } = this . servicesManager . services ;
const state = viewportGridService . getState ();
const gridViewport = state . viewports . get ( viewport . id );
const prevViewPresentation = viewport . getViewPresentation ();
const prevViewReference = viewport . getViewReference ();
const stackViewport = cornerstoneViewportService . getCornerstoneViewport ( viewport . id );
const { element } = stackViewport ;
const volumeViewportNewVolumeHandler = () => {
const volumeViewport = cornerstoneViewportService . getCornerstoneViewport ( viewport . id );
volumeViewport . setViewPresentation ( prevViewPresentation );
volumeViewport . setViewReference ( prevViewReference );
volumeViewport . render ();
element . removeEventListener (
csEnums . Events . VOLUME_VIEWPORT_NEW_VOLUME ,
volumeViewportNewVolumeHandler
);
};
element . addEventListener (
csEnums . Events . VOLUME_VIEWPORT_NEW_VOLUME ,
volumeViewportNewVolumeHandler
2022-11-16 14:19:52 -05:00
);
2024-11-06 07:15:27 -05:00
viewportGridService . setDisplaySetsForViewport ({
viewportId : viewport.id ,
displaySetInstanceUIDs : gridViewport.displaySetInstanceUIDs ,
viewportOptions : {
... gridViewport . viewportOptions ,
viewportType : ViewportType.ORTHOGRAPHIC ,
},
2022-11-16 14:19:52 -05:00
});
2024-11-06 07:15:27 -05:00
return true ;
}
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
private async attemptStackToVolumeConversion (
viewport : csTypes.IStackViewport ,
segmentation : SegmentationData ,
viewportId : string ,
segmentationId : string
) : Promise < boolean > {
const imageIds = getLabelmapImageIds ( segmentation . segmentationId );
const frameOfReferenceUID = viewport . getFrameOfReferenceUID ();
const segImage = cache . getImage ( imageIds [ 0 ]);
2024-11-11 14:28:34 -05:00
if (
segImage ? . FrameOfReferenceUID &&
frameOfReferenceUID &&
segImage . FrameOfReferenceUID === frameOfReferenceUID
) {
2024-11-06 07:15:27 -05:00
const isConverted = await this . convertStackToVolumeViewport ( viewport );
2025-09-30 14:49:13 -03:00
triggerSegmentationRepresentationModified ( viewportId , segmentationId , LABELMAP );
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
return isConverted ;
2022-11-16 14:19:52 -05:00
}
2024-11-06 07:15:27 -05:00
}
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
private addSegmentationToSource ( segmentationPublicInput : cstTypes.SegmentationPublicInput ) {
cstSegmentation . addSegmentations ([ segmentationPublicInput ]);
}
2023-08-16 12:51:26 -04:00
2024-11-06 07:15:27 -05:00
private updateSegmentationInSource (
segmentationId : string ,
payload : Partial < cstTypes.Segmentation >
) {
cstSegmentation . updateSegmentations ([{ segmentationId , payload }]);
}
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
private _toOHIFSegmentationRepresentation (
viewportId : string ,
csRepresentation : cstTypes.SegmentationRepresentation
) : SegmentationRepresentation {
const { segmentationId , type , active , visible } = csRepresentation ;
const { colorLUTIndex } = csRepresentation ;
2023-09-22 10:23:44 -04:00
2024-11-06 07:15:27 -05:00
const segmentsRepresentations : { [ segmentIndex : number ] : SegmentRepresentation } = {};
const segmentation = cstSegmentation . state . getSegmentation ( segmentationId );
if ( ! segmentation ) {
throw new Error ( `Segmentation with ID ${ segmentationId } not found.` );
2023-09-22 10:23:44 -04:00
}
2024-11-06 07:15:27 -05:00
const segmentIds = Object . keys ( segmentation . segments );
for ( const segmentId of segmentIds ) {
const segmentIndex = parseInt ( segmentId , 10 );
const color = cstSegmentation . config . color . getSegmentIndexColor (
viewportId ,
segmentationId ,
segmentIndex
);
const isVisible = cstSegmentation . config . visibility . getSegmentIndexVisibility (
viewportId ,
{
segmentationId ,
type ,
},
segmentIndex
);
2024-04-17 21:59:47 -04:00
2024-11-06 07:15:27 -05:00
segmentsRepresentations [ segmentIndex ] = {
color ,
segmentIndex ,
opacity : color [ 3 ],
visible : isVisible ,
};
}
const styles = cstSegmentation . config . style . getStyle ({
viewportId ,
segmentationId ,
type ,
2024-04-17 21:59:47 -04:00
});
2024-11-06 07:15:27 -05:00
const id = ` ${ segmentationId } - ${ type } - ${ viewportId } ` ;
return {
id : id ,
segmentationId ,
label : segmentation.label ,
active ,
type ,
visible ,
segments : segmentsRepresentations ,
styles ,
viewportId ,
colorLUTIndex ,
config : {},
};
}
private _initSegmentationService() {
eventTarget . addEventListener (
csToolsEnums . Events . SEGMENTATION_MODIFIED ,
this . _onSegmentationModifiedFromSource
);
eventTarget . addEventListener (
csToolsEnums . Events . SEGMENTATION_REMOVED ,
this . _onSegmentationModifiedFromSource
);
eventTarget . addEventListener (
csToolsEnums . Events . SEGMENTATION_DATA_MODIFIED ,
this . _onSegmentationDataModifiedFromSource
);
eventTarget . addEventListener (
csToolsEnums . Events . SEGMENTATION_REPRESENTATION_MODIFIED ,
this . _onSegmentationRepresentationModifiedFromSource
);
eventTarget . addEventListener (
csToolsEnums . Events . SEGMENTATION_REPRESENTATION_ADDED ,
this . _onSegmentationRepresentationModifiedFromSource
);
eventTarget . addEventListener (
csToolsEnums . Events . SEGMENTATION_REPRESENTATION_REMOVED ,
this . _onSegmentationRepresentationModifiedFromSource
);
eventTarget . addEventListener (
csToolsEnums . Events . SEGMENTATION_ADDED ,
this . _onSegmentationAddedFromSource
);
2025-10-29 14:15:52 -04:00
eventTarget . addEventListener (
csToolsEnums . Events . ANNOTATION_CUT_MERGE_PROCESS_COMPLETED ,
this . _onAnnotationCutMergeProcessCompletedFromSource
);
2024-11-06 07:15:27 -05:00
}
private getCornerstoneSegmentation ( segmentationId : string ) {
return cstSegmentation . state . getSegmentation ( segmentationId );
2023-08-16 12:51:26 -04:00
}
2023-04-26 12:21:08 -04:00
private _highlightLabelmap (
segmentIndex : number ,
alpha : number ,
hideOthers : boolean ,
2025-09-30 14:49:13 -03:00
segments : cstTypes.Segment [],
2024-11-06 07:15:27 -05:00
viewportId : string ,
2023-04-26 12:21:08 -04:00
animationLength : number ,
2025-09-21 23:48:35 -03:00
representation : cstTypes.SegmentationRepresentation ,
animationFunctionType : EasingFunctionEnum
2023-04-26 12:21:08 -04:00
) {
2024-11-06 07:15:27 -05:00
const { segmentationId } = representation ;
2023-04-26 12:21:08 -04:00
const newSegmentSpecificConfig = {
2024-11-06 07:15:27 -05:00
fillAlpha : alpha ,
2023-04-26 12:21:08 -04:00
};
if ( hideOthers ) {
2024-11-06 07:15:27 -05:00
throw new Error ( 'hideOthers is not working right now' );
2023-04-26 12:21:08 -04:00
}
2024-11-06 07:15:27 -05:00
const { fillAlpha } = this . getStyle ({
viewportId ,
segmentationId ,
type : LABELMAP ,
segmentIndex ,
}) as cstTypes . LabelmapStyle ;
2023-04-26 12:21:08 -04:00
2023-05-15 08:23:13 -04:00
let startTime : number = null ;
const animation = ( timestamp : number ) => {
if ( startTime === null ) {
startTime = timestamp ;
}
const elapsed = timestamp - startTime ;
const progress = Math . min ( elapsed / animationLength , 1 );
2023-04-26 12:21:08 -04:00
2025-09-21 23:48:35 -03:00
const easingFunction = EasingFunctionMap . get ( animationFunctionType );
2024-11-06 07:15:27 -05:00
cstSegmentation . config . style . setStyle (
2023-04-26 12:21:08 -04:00
{
2024-11-06 07:15:27 -05:00
segmentationId ,
segmentIndex ,
type : LABELMAP ,
},
{
2025-09-21 23:48:35 -03:00
fillAlpha : easingFunction ( progress , fillAlpha ),
2023-04-26 12:21:08 -04:00
}
);
2023-05-15 08:23:13 -04:00
if ( progress < 1 ) {
requestAnimationFrame ( animation );
} else {
2024-11-06 07:15:27 -05:00
cstSegmentation . config . style . setStyle (
{
segmentationId ,
segmentIndex ,
type : LABELMAP ,
},
2025-11-19 17:55:53 -05:00
{},
false
2023-04-26 12:21:08 -04:00
);
}
2023-05-15 08:23:13 -04:00
};
requestAnimationFrame ( animation );
2023-04-26 12:21:08 -04:00
}
private _highlightContour (
segmentIndex : number ,
alpha : number ,
hideOthers : boolean ,
2025-09-30 14:49:13 -03:00
segments : cstTypes.Segment [],
2024-11-06 07:15:27 -05:00
viewportId : string ,
2023-04-26 12:21:08 -04:00
animationLength : number ,
2025-09-21 23:48:35 -03:00
representation : cstTypes.SegmentationRepresentation ,
animationFunctionType : EasingFunctionEnum
2023-04-26 12:21:08 -04:00
) {
2024-11-06 07:15:27 -05:00
const { segmentationId } = representation ;
2023-04-26 12:21:08 -04:00
const startTime = performance . now ();
2024-11-06 07:15:27 -05:00
const prevStyle = cstSegmentation . config . style . getStyle ({
type : CONTOUR ,
2025-09-30 14:49:13 -03:00
}) as cstTypes . ContourStyle ;
2024-11-06 07:15:27 -05:00
const prevOutlineWidth = prevStyle . outlineWidth ;
2023-04-26 12:21:08 -04:00
const animate = ( currentTime : number ) => {
const progress = ( currentTime - startTime ) / animationLength ;
if ( progress >= 1 ) {
2025-02-25 11:49:16 -05:00
cstSegmentation . config . style . resetToGlobalStyle ();
2023-04-26 12:21:08 -04:00
return ;
}
2025-09-21 23:48:35 -03:00
const OUTLINE_ANIMATION_SCALE_FACTOR = 5 ;
const easingFunction = EasingFunctionMap . get ( animationFunctionType );
2024-11-06 07:15:27 -05:00
cstSegmentation . config . style . setStyle (
2023-04-26 12:21:08 -04:00
{
2024-11-06 07:15:27 -05:00
segmentationId ,
segmentIndex ,
type : CONTOUR ,
},
{
2025-09-21 23:48:35 -03:00
outlineWidth : easingFunction ( progress , prevOutlineWidth , OUTLINE_ANIMATION_SCALE_FACTOR ),
2023-04-26 12:21:08 -04:00
}
);
requestAnimationFrame ( animate );
};
requestAnimationFrame ( animate );
}
2024-11-06 07:15:27 -05:00
private _toggleSegmentationRepresentationVisibility = (
viewportId : string ,
segmentationId : string ,
2025-09-30 14:49:13 -03:00
type : csToolsEnums . SegmentationRepresentations
2024-11-06 07:15:27 -05:00
) : void => {
const segmentsHidden = cstSegmentation . config . visibility . getHiddenSegmentIndices ( viewportId , {
segmentationId ,
2025-10-15 08:32:27 -03:00
type ,
2024-11-06 07:15:27 -05:00
});
2023-04-26 12:21:08 -04:00
2024-11-06 07:15:27 -05:00
const currentVisibility = segmentsHidden . size === 0 ;
this . _setSegmentationRepresentationVisibility (
viewportId ,
segmentationId ,
2025-10-15 08:32:27 -03:00
type ,
2024-11-06 07:15:27 -05:00
! currentVisibility
);
};
2023-04-26 12:21:08 -04:00
2024-11-06 07:15:27 -05:00
private _setActiveSegment ( segmentationId : string , segmentIndex : number ) {
cstSegmentation . segmentIndex . setActiveSegmentIndex ( segmentationId , segmentIndex );
2022-11-16 14:19:52 -05:00
}
2024-11-06 07:15:27 -05:00
private _getVolumeIdForDisplaySet ( displaySet ) {
const volumeLoaderSchema = displaySet . volumeLoaderSchema ?? VOLUME_LOADER_SCHEME ;
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
return ` ${ volumeLoaderSchema } : ${ displaySet . displaySetInstanceUID } ` ;
}
2022-11-16 14:19:52 -05:00
2025-09-19 10:25:32 -03:00
private _getSegmentCenter (
segmentationId : string ,
segmentIndex : number
) : { image? : csTypes.Point3 ; world : csTypes.Point3 } | undefined {
2022-11-16 14:19:52 -05:00
const segmentation = this . getSegmentation ( segmentationId );
if ( ! segmentation ) {
return ;
}
2024-11-06 07:15:27 -05:00
const { segments } = segmentation ;
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
const { cachedStats } = segments [ segmentIndex ];
2022-11-16 14:19:52 -05:00
2025-09-19 10:25:32 -03:00
if ( cachedStats ? . center ) {
const { center } = cachedStats ;
2022-11-16 14:19:52 -05:00
2025-09-19 10:25:32 -03:00
return center as { image : csTypes.Point3 ; world : csTypes.Point3 };
}
2022-11-16 14:19:52 -05:00
2025-09-19 10:25:32 -03:00
if ( cachedStats ? . namedStats ? . center ) {
2025-04-07 16:45:29 -04:00
return {
world : cachedStats.namedStats.center.value ,
};
}
2022-11-16 14:19:52 -05:00
}
2024-11-06 07:15:27 -05:00
private _setSegmentLockedStatus ( segmentationId : string , segmentIndex : number , isLocked : boolean ) {
2023-09-01 16:19:39 -04:00
cstSegmentation . segmentLocking . setSegmentIndexLocked ( segmentationId , segmentIndex , isLocked );
2022-11-16 14:19:52 -05:00
}
private _setSegmentVisibility (
2024-11-06 07:15:27 -05:00
viewportId : string ,
2022-11-16 14:19:52 -05:00
segmentationId : string ,
segmentIndex : number ,
isVisible : boolean ,
2025-09-30 14:49:13 -03:00
type ?: csToolsEnums . SegmentationRepresentations
2022-11-16 14:19:52 -05:00
) {
2024-11-06 07:15:27 -05:00
cstSegmentation . config . visibility . setSegmentIndexVisibility (
viewportId ,
{
segmentationId ,
type ,
},
2022-11-16 14:19:52 -05:00
segmentIndex ,
isVisible
);
}
2024-11-06 07:15:27 -05:00
private _setSegmentLabel ( segmentationId : string , segmentIndex : number , segmentLabel : string ) {
const segmentation = this . getCornerstoneSegmentation ( segmentationId );
const { segments } = segmentation ;
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
segments [ segmentIndex ]. label = segmentLabel ;
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
cstSegmentation . updateSegmentations ([
{
segmentationId ,
payload : {
segments ,
},
},
]);
2022-11-16 14:19:52 -05:00
}
2024-11-06 07:15:27 -05:00
private _onSegmentationDataModifiedFromSource = evt => {
2022-11-16 14:19:52 -05:00
const { segmentationId } = evt . detail ;
this . _broadcastEvent ( this . EVENTS . SEGMENTATION_DATA_MODIFIED , {
2024-11-06 07:15:27 -05:00
segmentationId ,
2022-11-16 14:19:52 -05:00
});
};
2024-11-06 07:15:27 -05:00
private _onSegmentationRepresentationModifiedFromSource = evt => {
const { segmentationId , viewportId } = evt . detail ;
this . _broadcastEvent ( this . EVENTS . SEGMENTATION_REPRESENTATION_MODIFIED , {
2022-11-16 14:19:52 -05:00
segmentationId ,
2024-11-06 07:15:27 -05:00
viewportId ,
2022-11-16 14:19:52 -05:00
});
2024-11-06 07:15:27 -05:00
};
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
private _onSegmentationModifiedFromSource = (
evt : cstTypes.EventTypes.SegmentationModifiedEventType
) => {
const { segmentationId } = evt . detail ;
2023-03-22 12:44:52 -04:00
2024-11-06 07:15:27 -05:00
this . _broadcastEvent ( this . EVENTS . SEGMENTATION_MODIFIED , {
segmentationId ,
2022-11-16 14:19:52 -05:00
});
};
2024-11-06 07:15:27 -05:00
private _onSegmentationAddedFromSource = (
evt : cstTypes.EventTypes.SegmentationAddedEventType
) => {
const { segmentationId } = evt . detail ;
2022-11-16 14:19:52 -05:00
2024-11-06 07:15:27 -05:00
this . _broadcastEvent ( this . EVENTS . SEGMENTATION_ADDED , {
segmentationId ,
});
2022-11-16 14:19:52 -05:00
};
2025-10-29 14:15:52 -04:00
private _onAnnotationCutMergeProcessCompletedFromSource = evt => {
const { segmentationId } = evt . detail ;
2025-11-11 08:19:45 -05:00
this . _broadcastEvent ( this . EVENTS . SEGMENTATION_ANNOTATION_CUT_MERGE_PROCESS_COMPLETED , {
2025-10-29 14:15:52 -04:00
segmentationId ,
});
};
2022-11-16 14:19:52 -05:00
}
export default SegmentationService ;
export { EVENTS , VALUE_TYPES };