ohif-viewer/extensions/cornerstone/src/utils/segmentationHandlers.ts
Joe Boccanfuso 35bfc9b0cc
feature(segmentation): Add contour tools and utilities in a side panel (#5517)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Dan Rukas <dan.rukas@gmail.com>
2025-10-29 14:15:52 -04:00

161 lines
5.4 KiB
TypeScript

import * as cornerstoneTools from '@cornerstonejs/tools';
import { updateSegmentationStats } from './updateSegmentationStats';
import { useSelectedSegmentationsForViewportStore } from '../stores';
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
/**
* Sets up the handler for segmentation data modification events
*/
export function setupSegmentationDataModifiedHandler({
segmentationService,
customizationService,
commandsManager,
}) {
// A flag to indicate if the event is unsubscribed to. This is important because
// the debounced callback does an await and in that period of time the event may have
// been unsubscribed.
let isUnsubscribed = false;
const { unsubscribe: debouncedUnsubscribe } = segmentationService.subscribeDebounced(
segmentationService.EVENTS.SEGMENTATION_DATA_MODIFIED,
async ({ segmentationId }) => {
const disableUpdateSegmentationStats = customizationService.getCustomization(
'panelSegmentation.disableUpdateSegmentationStats'
);
const segmentation = segmentationService.getSegmentation(segmentationId);
if (!segmentation || disableUpdateSegmentationStats) {
return;
}
const readableText = customizationService.getCustomization('panelSegmentation.readableText');
// Check for segments with bidirectional measurements and update them
const segmentIndices = Object.keys(segmentation.segments)
.map(index => parseInt(index))
.filter(index => index > 0);
for (const segmentIndex of segmentIndices) {
const segment = segmentation.segments[segmentIndex];
if (segment?.cachedStats?.namedStats?.bidirectional) {
// Run the command to update the bidirectional measurement
commandsManager.runCommand('runSegmentBidirectional', {
segmentationId,
segmentIndex,
});
}
}
const updatedSegmentation = await updateSegmentationStats({
segmentation,
segmentationId,
readableText,
});
if (!isUnsubscribed && updatedSegmentation) {
segmentationService.addOrUpdateSegmentation({
segmentationId,
segments: updatedSegmentation.segments,
});
}
},
1000
);
const unsubscribe = () => {
isUnsubscribed = true;
debouncedUnsubscribe();
};
return { unsubscribe };
}
/**
* Sets up the handler for segmentation modification events
*/
export function setupSegmentationModifiedHandler({ segmentationService }) {
const { unsubscribe } = segmentationService.subscribe(
segmentationService.EVENTS.SEGMENTATION_MODIFIED,
async ({ segmentationId }) => {
const segmentation = segmentationService.getSegmentation(segmentationId);
if (!segmentation) {
return;
}
const annotationState = cornerstoneTools.annotation.state.getAllAnnotations();
const bidirectionalAnnotations = annotationState.filter(
annotation =>
annotation.metadata.toolName === cornerstoneTools.SegmentBidirectionalTool.toolName
);
const segmentIndices = Object.keys(segmentation.segments)
.map(index => parseInt(index))
.filter(index => index > 0);
// check if there is a bidirectional data that exists but the segment
// does not exists anymore we need to remove the bidirectional data
const bidirectionalAnnotationsToRemove = bidirectionalAnnotations.filter(
annotation =>
annotation.metadata.segmentationId === segmentationId &&
!segmentIndices.includes(annotation.metadata.segmentIndex)
);
const toRemoveUIDs = bidirectionalAnnotationsToRemove.map(
annotation => annotation.annotationUID
);
toRemoveUIDs.forEach(uid => {
cornerstoneTools.annotation.state.removeAnnotation(uid);
});
}
);
return { unsubscribe };
}
/**
* Sets up auto tab switching for when the first segmentation is added into the viewer.
*/
export function setUpSelectedSegmentationsForViewportHandler({ segmentationService }) {
const selectedSegmentationsForViewportEvents = [
segmentationService.EVENTS.SEGMENTATION_MODIFIED,
segmentationService.EVENTS.SEGMENTATION_REPRESENTATION_MODIFIED,
];
const unsubscribeSelectedSegmentationsForViewportEvents = selectedSegmentationsForViewportEvents
.map(eventName =>
segmentationService.subscribe(eventName, event => {
const { viewportId } = event;
if (!viewportId) {
return;
}
const { selectedSegmentationsForViewport, setSelectedSegmentationsForViewport } =
useSelectedSegmentationsForViewportStore.getState();
const representations = segmentationService.getSegmentationRepresentations(viewportId);
const activeRepresentation = representations.find(representation => representation.active);
const typeToSegmentationIdMap =
selectedSegmentationsForViewport[viewportId] ??
new Map<SegmentationRepresentations, string>();
if (activeRepresentation) {
typeToSegmentationIdMap.set(
activeRepresentation.type,
activeRepresentation.segmentationId
);
} else {
typeToSegmentationIdMap.clear();
}
setSelectedSegmentationsForViewport(viewportId, typeToSegmentationIdMap);
})
)
.map(subscription => subscription.unsubscribe);
return { unsubscribeSelectedSegmentationsForViewportEvents };
}