fix(seg): Enhance segmentation tools and UI, refactor code, and update dependencies (#4915)
This commit is contained in:
1 parent
1dc00d3030
commit
8432d5f633
37 files changed
+1063
-1122
No files matched your search
@@ -38,7 +38,7 @@
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.4",
|
||||
"@cornerstonejs/codec-openjph": "^2.4.5",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.8.2",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.8.4",
|
||||
"@ohif/core": "3.10.0-beta.144",
|
||||
"@ohif/ui": "3.10.0-beta.144",
|
||||
"dcmjs": "*",
|
||||
@@ -51,12 +51,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.8.2",
|
||||
"@cornerstonejs/ai": "^3.8.2",
|
||||
"@cornerstonejs/core": "^3.8.2",
|
||||
"@cornerstonejs/labelmap-interpolation": "^3.8.2",
|
||||
"@cornerstonejs/polymorphic-segmentation": "^3.8.2",
|
||||
"@cornerstonejs/tools": "^3.8.2",
|
||||
"@cornerstonejs/adapters": "^3.8.4",
|
||||
"@cornerstonejs/ai": "^3.8.4",
|
||||
"@cornerstonejs/core": "^3.8.4",
|
||||
"@cornerstonejs/labelmap-interpolation": "^3.8.4",
|
||||
"@cornerstonejs/polymorphic-segmentation": "^3.8.4",
|
||||
"@cornerstonejs/tools": "^3.8.4",
|
||||
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
ToolGroupManager,
|
||||
Enums,
|
||||
utilities as cstUtils,
|
||||
ReferenceLinesTool,
|
||||
annotation,
|
||||
Types as ToolTypes,
|
||||
} from '@cornerstonejs/tools';
|
||||
@@ -43,6 +42,8 @@ const toggleSyncFunctions = {
|
||||
voi: toggleVOISliceSync,
|
||||
};
|
||||
|
||||
const { segmentation: segmentationUtils } = cstUtils;
|
||||
|
||||
const getLabelmapTools = ({ toolGroupService }) => {
|
||||
const labelmapTools = [];
|
||||
const toolGroupIds = toolGroupService.getToolGroupIds();
|
||||
@@ -155,6 +156,8 @@ function commandsModule({
|
||||
}
|
||||
);
|
||||
|
||||
measurement.annotationUID = annotation.annotationUID;
|
||||
|
||||
// Update segmentation stats
|
||||
const updatedSegmentation = updateSegmentBidirectionalStats({
|
||||
segmentationId: targetId,
|
||||
@@ -172,6 +175,14 @@ function commandsModule({
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// get the active segmentIndex bidirectional annotation and jump to it
|
||||
const activeBidirectional = bidirectionalData.find(
|
||||
measurement => measurement.segmentIndex === targetIndex
|
||||
);
|
||||
commandsManager.run('jumpToMeasurement', {
|
||||
uid: activeBidirectional.annotationUID,
|
||||
});
|
||||
},
|
||||
interpolateLabelmap: () => {
|
||||
const { segmentationId, segmentIndex } = _getActiveSegmentationInfo();
|
||||
@@ -1630,6 +1641,72 @@ function commandsModule({
|
||||
segmentAI.initViewport(viewport);
|
||||
}
|
||||
},
|
||||
setBrushSize: ({ value, toolNames }) => {
|
||||
const brushSize = Number(value);
|
||||
|
||||
toolGroupService.getToolGroupIds()?.forEach(toolGroupId => {
|
||||
if (toolNames?.length === 0) {
|
||||
segmentationUtils.setBrushSizeForToolGroup(toolGroupId, brushSize);
|
||||
} else {
|
||||
toolNames?.forEach(toolName => {
|
||||
segmentationUtils.setBrushSizeForToolGroup(toolGroupId, brushSize, toolName);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
setThresholdRange: ({
|
||||
value,
|
||||
toolNames = [
|
||||
'ThresholdCircularBrush',
|
||||
'ThresholdSphereBrush',
|
||||
'ThresholdCircularBrushDynamic',
|
||||
'ThresholdSphereBrushDynamic',
|
||||
],
|
||||
}) => {
|
||||
const toolGroupIds = toolGroupService.getToolGroupIds();
|
||||
if (!toolGroupIds?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const toolGroupId of toolGroupIds) {
|
||||
const toolGroup = toolGroupService.getToolGroup(toolGroupId);
|
||||
toolNames?.forEach(toolName => {
|
||||
toolGroup.setToolConfiguration(toolName, {
|
||||
threshold: {
|
||||
range: value,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
increaseBrushSize: () => {
|
||||
const toolGroupIds = toolGroupService.getToolGroupIds();
|
||||
if (!toolGroupIds?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const toolGroupId of toolGroupIds) {
|
||||
const brushSize = segmentationUtils.getBrushSizeForToolGroup(toolGroupId);
|
||||
segmentationUtils.setBrushSizeForToolGroup(toolGroupId, brushSize + 3);
|
||||
}
|
||||
},
|
||||
decreaseBrushSize: () => {
|
||||
const toolGroupIds = toolGroupService.getToolGroupIds();
|
||||
if (!toolGroupIds?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const toolGroupId of toolGroupIds) {
|
||||
const brushSize = segmentationUtils.getBrushSizeForToolGroup(toolGroupId);
|
||||
segmentationUtils.setBrushSizeForToolGroup(toolGroupId, brushSize - 3);
|
||||
}
|
||||
},
|
||||
addNewSegment: () => {
|
||||
const { segmentationService } = servicesManager.services;
|
||||
const { activeViewportId } = viewportGridService.getState();
|
||||
const activeSegmentation = segmentationService.getActiveSegmentation(activeViewportId);
|
||||
segmentationService.addSegment(activeSegmentation.segmentationId);
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
@@ -1903,6 +1980,11 @@ function commandsModule({
|
||||
toggleLabelmapAssist: actions.toggleLabelmapAssist,
|
||||
interpolateScrollForMarkerLabelmap: actions.interpolateScrollForMarkerLabelmap,
|
||||
clearMarkersForMarkerLabelmap: actions.clearMarkersForMarkerLabelmap,
|
||||
setBrushSize: actions.setBrushSize,
|
||||
setThresholdRange: actions.setThresholdRange,
|
||||
increaseBrushSize: actions.increaseBrushSize,
|
||||
decreaseBrushSize: actions.decreaseBrushSize,
|
||||
addNewSegment: actions.addNewSegment,
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -95,24 +95,53 @@ export const CustomDropdownMenuContent = () => {
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger className="pl-1">
|
||||
<Icons.Export className="text-foreground" />
|
||||
<span className="pl-2">{t('Export & Download')}</span>
|
||||
<span className="pl-2">{t('Download & Export')}</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuItem onClick={() => actions.downloadCSVSegmentationReport(segmentationId)}>
|
||||
{t('Download CSV Report')}
|
||||
<DropdownMenuLabel className="flex items-center pl-0">
|
||||
<Icons.Download className="h-5 w-5" />
|
||||
<span className="pl-1">{t('Download')}</span>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
actions.downloadCSVSegmentationReport(segmentationId);
|
||||
}}
|
||||
>
|
||||
{t('CSV Report')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => actions.storeSegmentation(segmentationId)}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
actions.onSegmentationDownload(segmentationId);
|
||||
}}
|
||||
disabled={!allowExport}
|
||||
>
|
||||
{t('Export DICOM SEG')}
|
||||
{t('DICOM SEG')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => actions.onSegmentationDownload(segmentationId)}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
actions.onSegmentationDownloadRTSS(segmentationId);
|
||||
}}
|
||||
disabled={!allowExport}
|
||||
>
|
||||
{t('Download DICOM SEG')}
|
||||
{t('DICOM RTSS')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel className="flex items-center pl-0">
|
||||
<Icons.Export className="h-5 w-5" />
|
||||
<span className="pl-1 pt-1">{t('Export')}</span>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
actions.storeSegmentation(segmentationId);
|
||||
}}
|
||||
disabled={!allowExport}
|
||||
>
|
||||
{t('DICOM SEG')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
|
||||
@@ -39,7 +39,10 @@ import { useLutPresentationStore } from './stores/useLutPresentationStore';
|
||||
import { usePositionPresentationStore } from './stores/usePositionPresentationStore';
|
||||
import { useSegmentationPresentationStore } from './stores/useSegmentationPresentationStore';
|
||||
import { imageRetrieveMetadataProvider } from '@cornerstonejs/core/utilities';
|
||||
import { updateSegmentationStats } from './utils/updateSegmentationStats';
|
||||
import {
|
||||
setupSegmentationDataModifiedHandler,
|
||||
setupSegmentationModifiedHandler,
|
||||
} from './utils/segmentationHandlers';
|
||||
|
||||
const { registerColormap } = csUtilities.colormap;
|
||||
|
||||
@@ -189,44 +192,16 @@ export default async function init({
|
||||
});
|
||||
});
|
||||
|
||||
segmentationService.subscribeDebounced(
|
||||
segmentationService.EVENTS.SEGMENTATION_DATA_MODIFIED,
|
||||
async ({ segmentationId }) => {
|
||||
const segmentation = segmentationService.getSegmentation(segmentationId);
|
||||
const readableText = customizationService.getCustomization('panelSegmentation.readableText');
|
||||
const updatedSegmentation = await updateSegmentationStats({
|
||||
segmentation,
|
||||
segmentationId,
|
||||
readableText,
|
||||
});
|
||||
// Setup segmentation event handlers
|
||||
setupSegmentationDataModifiedHandler({
|
||||
segmentationService,
|
||||
customizationService,
|
||||
commandsManager,
|
||||
});
|
||||
|
||||
if (updatedSegmentation) {
|
||||
segmentationService.addOrUpdateSegmentation({
|
||||
segmentationId,
|
||||
segments: updatedSegmentation.segments,
|
||||
});
|
||||
}
|
||||
|
||||
// Check for segments with bidirectional measurements and update them
|
||||
if (segmentation) {
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
1000
|
||||
);
|
||||
setupSegmentationModifiedHandler({
|
||||
segmentationService,
|
||||
});
|
||||
|
||||
// When a custom image load is performed, update the relevant viewports
|
||||
hangingProtocolService.subscribe(
|
||||
|
||||
@@ -602,10 +602,16 @@ class SegmentationService extends PubSubService {
|
||||
const segments: { [segmentIndex: string]: cstTypes.Segment } = {};
|
||||
let segmentsCachedStats = {};
|
||||
|
||||
// Create colorLUT array for RT structures
|
||||
const colorLUT = [[0, 0, 0, 0]]; // First entry is transparent for index 0
|
||||
|
||||
// Process each segment similarly to the SEG function
|
||||
for (const rtStructData of allRTStructData) {
|
||||
const { data, id, color, segmentIndex, geometryId } = rtStructData;
|
||||
|
||||
// Add the color to the colorLUT array
|
||||
colorLUT.push(color);
|
||||
|
||||
try {
|
||||
const geometry = await geometryLoader.createAndCacheGeometry(geometryId, {
|
||||
geometryData: {
|
||||
@@ -647,6 +653,11 @@ class SegmentationService extends PubSubService {
|
||||
}
|
||||
}
|
||||
|
||||
// Create and register the colorLUT
|
||||
const colorLUTIndex = getNextColorLUTIndex();
|
||||
addColorLUT(colorLUT, colorLUTIndex);
|
||||
this._segmentationIdToColorLUTIndexMap.set(segmentationId, colorLUTIndex);
|
||||
|
||||
// Assign processed segments to segmentation config
|
||||
segmentation.config.segments = segments;
|
||||
|
||||
|
||||
-1
@@ -53,7 +53,6 @@ const segmentationRepresentationModifiedCallback = async (
|
||||
const targetFrameOfReferenceUID = viewport.getFrameOfReferenceUID();
|
||||
|
||||
if (!targetFrameOfReferenceUID) {
|
||||
console.debug('No frame of reference UID found for the target viewport');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -846,13 +846,17 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
if (addOverlayFn) {
|
||||
addOverlayFn();
|
||||
}
|
||||
viewport.render();
|
||||
|
||||
volumesProperties.forEach(({ properties, volumeId }) => {
|
||||
viewport.setProperties(properties, volumeId);
|
||||
setTimeout(() => {
|
||||
// seems like a hack but we need the actor to be ready first before
|
||||
// we set the properties
|
||||
viewport.setProperties(properties, volumeId);
|
||||
viewport.render();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
viewport.render();
|
||||
|
||||
this.setPresentations(viewport.id, presentations, viewportInfo);
|
||||
|
||||
if (!presentations.positionPresentation) {
|
||||
@@ -865,8 +869,6 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
}
|
||||
}
|
||||
|
||||
viewport.render();
|
||||
|
||||
this._broadcastEvent(this.EVENTS.VIEWPORT_VOLUMES_CHANGED, {
|
||||
viewportInfo,
|
||||
});
|
||||
|
||||
@@ -2,7 +2,6 @@ const colormaps = [
|
||||
{
|
||||
ColorSpace: 'RGB',
|
||||
Name: 'Grayscale',
|
||||
name: 'Grayscale',
|
||||
NanColor: [1, 0, 0],
|
||||
RGBPoints: [0, 0, 0, 0, 1, 1, 1, 1],
|
||||
description: 'Grayscale',
|
||||
@@ -10,7 +9,6 @@ const colormaps = [
|
||||
{
|
||||
ColorSpace: 'RGB',
|
||||
Name: 'X Ray',
|
||||
name: 'X Ray',
|
||||
NanColor: [1, 0, 0],
|
||||
RGBPoints: [0, 1, 1, 1, 1, 0, 0, 0],
|
||||
description: 'X Ray',
|
||||
@@ -18,7 +16,6 @@ const colormaps = [
|
||||
{
|
||||
ColorSpace: 'RGB',
|
||||
Name: 'hsv',
|
||||
name: 'hsv',
|
||||
RGBPoints: [
|
||||
-1, 1, 0, 0, -0.666666, 1, 0, 1, -0.333333, 0, 0, 1, 0, 0, 1, 1, 0.33333, 0, 1, 0, 0.66666, 1,
|
||||
1, 0, 1, 1, 0, 0,
|
||||
@@ -28,7 +25,6 @@ const colormaps = [
|
||||
{
|
||||
ColorSpace: 'RGB',
|
||||
Name: 'hot_iron',
|
||||
name: 'hot_iron',
|
||||
RGBPoints: [
|
||||
0.0, 0.0039215686, 0.0039215686, 0.0156862745, 0.00392156862745098, 0.0039215686,
|
||||
0.0039215686, 0.0156862745, 0.00784313725490196, 0.0039215686, 0.0039215686, 0.031372549,
|
||||
@@ -224,7 +220,6 @@ const colormaps = [
|
||||
{
|
||||
ColorSpace: 'RGB',
|
||||
Name: 'red_hot',
|
||||
name: 'red_hot',
|
||||
RGBPoints: [
|
||||
0.0, 0.0, 0.0, 0.0, 0.00392156862745098, 0.0, 0.0, 0.0, 0.00784313725490196, 0.0, 0.0, 0.0,
|
||||
0.011764705882352941, 0.0, 0.0, 0.0, 0.01568627450980392, 0.0039215686, 0.0039215686,
|
||||
@@ -359,7 +354,6 @@ const colormaps = [
|
||||
{
|
||||
ColorSpace: 'RGB',
|
||||
Name: 's_pet',
|
||||
name: 's_pet',
|
||||
RGBPoints: [
|
||||
0.0, 0.0156862745, 0.0039215686, 0.0156862745, 0.00392156862745098, 0.0156862745,
|
||||
0.0039215686, 0.0156862745, 0.00784313725490196, 0.0274509804, 0.0039215686, 0.031372549,
|
||||
@@ -553,7 +547,6 @@ const colormaps = [
|
||||
{
|
||||
ColorSpace: 'RGB',
|
||||
Name: 'perfusion',
|
||||
name: 'perfusion',
|
||||
RGBPoints: [
|
||||
0.0, 0.0, 0.0, 0.0, 0.00392156862745098, 0.0078431373, 0.0235294118, 0.0235294118,
|
||||
0.00784313725490196, 0.0078431373, 0.031372549, 0.0470588235, 0.011764705882352941,
|
||||
@@ -733,7 +726,6 @@ const colormaps = [
|
||||
{
|
||||
ColorSpace: 'RGB',
|
||||
Name: 'rainbow_2',
|
||||
name: 'rainbow_2',
|
||||
RGBPoints: [
|
||||
0.0, 0.0, 0.0, 0.0, 0.00392156862745098, 0.0156862745, 0.0, 0.0117647059, 0.00784313725490196,
|
||||
0.0352941176, 0.0, 0.0274509804, 0.011764705882352941, 0.0509803922, 0.0, 0.0392156863,
|
||||
@@ -872,7 +864,6 @@ const colormaps = [
|
||||
{
|
||||
ColorSpace: 'RGB',
|
||||
Name: 'suv',
|
||||
name: 'suv',
|
||||
RGBPoints: [
|
||||
0.0, 1.0, 1.0, 1.0, 0.00392156862745098, 1.0, 1.0, 1.0, 0.00784313725490196, 1.0, 1.0, 1.0,
|
||||
0.011764705882352941, 1.0, 1.0, 1.0, 0.01568627450980392, 1.0, 1.0, 1.0, 0.0196078431372549,
|
||||
@@ -1019,7 +1010,6 @@ const colormaps = [
|
||||
{
|
||||
ColorSpace: 'RGB',
|
||||
Name: 'ge_256',
|
||||
name: 'ge_256',
|
||||
RGBPoints: [
|
||||
0.0, 0.0039215686, 0.0078431373, 0.0078431373, 0.00392156862745098, 0.0039215686,
|
||||
0.0078431373, 0.0078431373, 0.00784313725490196, 0.0039215686, 0.0078431373, 0.0117647059,
|
||||
@@ -1211,7 +1201,6 @@ const colormaps = [
|
||||
{
|
||||
ColorSpace: 'RGB',
|
||||
Name: 'ge',
|
||||
name: 'ge',
|
||||
RGBPoints: [
|
||||
0.0, 0.0078431373, 0.0078431373, 0.0078431373, 0.00392156862745098, 0.0078431373,
|
||||
0.0078431373, 0.0078431373, 0.00784313725490196, 0.0078431373, 0.0078431373, 0.0078431373,
|
||||
@@ -1404,7 +1393,6 @@ const colormaps = [
|
||||
{
|
||||
ColorSpace: 'RGB',
|
||||
Name: 'siemens',
|
||||
name: 'siemens',
|
||||
RGBPoints: [
|
||||
0.0, 0.0078431373, 0.0039215686, 0.1254901961, 0.00392156862745098, 0.0078431373,
|
||||
0.0039215686, 0.1254901961, 0.00784313725490196, 0.0078431373, 0.0039215686, 0.1882352941,
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { handleSegmentChange } from './segmentUtils';
|
||||
import { isReferenceViewable } from './isReferenceViewable';
|
||||
import {
|
||||
setupSegmentationDataModifiedHandler,
|
||||
setupSegmentationModifiedHandler,
|
||||
} from './segmentationHandlers';
|
||||
|
||||
const utils = {
|
||||
handleSegmentChange,
|
||||
isReferenceViewable,
|
||||
setupSegmentationDataModifiedHandler,
|
||||
setupSegmentationModifiedHandler,
|
||||
};
|
||||
|
||||
export default utils;
|
||||
@@ -84,7 +84,7 @@ const Bidirectional = {
|
||||
function getMappedAnnotations(annotation, displaySetService) {
|
||||
const { metadata, data } = annotation;
|
||||
const { cachedStats } = data;
|
||||
const { referencedImageId, referencedSeriesInstanceUID } = metadata;
|
||||
const { referencedImageId } = metadata;
|
||||
const targets = Object.keys(cachedStats);
|
||||
|
||||
if (!targets.length) {
|
||||
|
||||
@@ -93,6 +93,7 @@ function getMappedAnnotations(annotation, displaySetService) {
|
||||
}
|
||||
|
||||
const annotations = [];
|
||||
const addedModalities = new Set();
|
||||
Object.keys(cachedStats).forEach(targetId => {
|
||||
const targetStats = cachedStats[targetId];
|
||||
|
||||
@@ -108,6 +109,12 @@ function getMappedAnnotations(annotation, displaySetService) {
|
||||
const { mean, stdDev, max, area, Modality, areaUnit, modalityUnit, perimeter, radiusUnit } =
|
||||
targetStats;
|
||||
|
||||
if (Modality && addedModalities.has(Modality)) {
|
||||
return;
|
||||
}
|
||||
|
||||
addedModalities.add(Modality);
|
||||
|
||||
annotations.push({
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID,
|
||||
|
||||
@@ -93,6 +93,8 @@ function getMappedAnnotations(annotation, displaySetService) {
|
||||
}
|
||||
|
||||
const annotations = [];
|
||||
const addedModalities = new Set();
|
||||
|
||||
Object.keys(cachedStats).forEach(targetId => {
|
||||
const targetStats = cachedStats[targetId];
|
||||
|
||||
@@ -107,6 +109,16 @@ function getMappedAnnotations(annotation, displaySetService) {
|
||||
const { SeriesNumber } = displaySet;
|
||||
const { mean, stdDev, max, area, Modality, areaUnit, modalityUnit } = targetStats;
|
||||
|
||||
// Skip if we've already added this modality
|
||||
if (Modality && addedModalities.has(Modality)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add modality to the set if it exists
|
||||
if (Modality) {
|
||||
addedModalities.add(Modality);
|
||||
}
|
||||
|
||||
annotations.push({
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID,
|
||||
|
||||
@@ -92,6 +92,8 @@ function getMappedAnnotations(annotation, displaySetService) {
|
||||
}
|
||||
|
||||
const annotations = [];
|
||||
|
||||
const addedModalities = new Set();
|
||||
Object.keys(cachedStats).forEach(targetId => {
|
||||
const targetStats = cachedStats[targetId];
|
||||
|
||||
@@ -106,6 +108,16 @@ function getMappedAnnotations(annotation, displaySetService) {
|
||||
const { SeriesNumber } = displaySet;
|
||||
const { mean, stdDev, max, area, Modality, modalityUnit, areaUnit } = targetStats;
|
||||
|
||||
// Skip if we've already added this modality
|
||||
if (Modality && addedModalities.has(Modality)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add modality to the set if it exists
|
||||
if (Modality) {
|
||||
addedModalities.add(Modality);
|
||||
}
|
||||
|
||||
annotations.push({
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID,
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import * as cornerstoneTools from '@cornerstonejs/tools';
|
||||
import { updateSegmentationStats } from './updateSegmentationStats';
|
||||
|
||||
/**
|
||||
* Sets up the handler for segmentation data modification events
|
||||
*/
|
||||
export function setupSegmentationDataModifiedHandler({
|
||||
segmentationService,
|
||||
customizationService,
|
||||
commandsManager,
|
||||
}) {
|
||||
segmentationService.subscribeDebounced(
|
||||
segmentationService.EVENTS.SEGMENTATION_DATA_MODIFIED,
|
||||
async ({ segmentationId }) => {
|
||||
const segmentation = segmentationService.getSegmentation(segmentationId);
|
||||
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 (updatedSegmentation) {
|
||||
segmentationService.addOrUpdateSegmentation({
|
||||
segmentationId,
|
||||
segments: updatedSegmentation.segments,
|
||||
});
|
||||
}
|
||||
},
|
||||
1000
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the handler for segmentation modification events
|
||||
*/
|
||||
export function setupSegmentationModifiedHandler({ segmentationService }) {
|
||||
segmentationService.subscribe(
|
||||
segmentationService.EVENTS.SEGMENTATION_MODIFIED,
|
||||
async ({ segmentationId }) => {
|
||||
const segmentation = segmentationService.getSegmentation(segmentationId);
|
||||
|
||||
const annotationState = cornerstoneTools.annotation.state.getAllAnnotations();
|
||||
const bidirectionalAnnotations = annotationState.filter(
|
||||
annotation =>
|
||||
annotation.metadata.toolName === cornerstoneTools.SegmentBidirectionalTool.toolName
|
||||
);
|
||||
|
||||
let toRemoveUIDs = [];
|
||||
if (!segmentation) {
|
||||
toRemoveUIDs = bidirectionalAnnotations.map(
|
||||
annotation => annotation.metadata.segmentationId === segmentationId
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
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)
|
||||
);
|
||||
|
||||
toRemoveUIDs = bidirectionalAnnotationsToRemove.map(annotation => annotation.annotationUID);
|
||||
}
|
||||
|
||||
toRemoveUIDs.forEach(uid => {
|
||||
cornerstoneTools.annotation.state.removeAnnotation(uid);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user