fix(HistoryMemo): Segmentation delete wasn't remembered (#5775)
Will address a few more comments in a separate PR released after this one into 3.13 * fix(HistoryMemo): Segmentation delete wasn't remembered * Temporary add of package.json linking * fix: Use newer dcmjs version and delete segmentation with history cs3d
This commit is contained in:
1 parent
3b9d3babf9
commit
48f2f6fb13
22 files changed
+363
-275
No files matched your search
@@ -46,8 +46,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@cornerstonejs/adapters": "4.15.28",
|
||||
"@cornerstonejs/core": "4.15.28",
|
||||
"@cornerstonejs/adapters": "4.15.29",
|
||||
"@cornerstonejs/core": "4.15.29",
|
||||
"@kitware/vtk.js": "34.15.1",
|
||||
"react-color": "2.19.3"
|
||||
}
|
||||
|
||||
@@ -46,8 +46,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@cornerstonejs/adapters": "4.15.28",
|
||||
"@cornerstonejs/core": "4.15.28",
|
||||
"@cornerstonejs/adapters": "4.15.29",
|
||||
"@cornerstonejs/core": "4.15.29",
|
||||
"@kitware/vtk.js": "34.15.1",
|
||||
"react-color": "2.19.3"
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"@ohif/extension-cornerstone": "3.12.0-beta.133",
|
||||
"@ohif/extension-measurement-tracking": "3.12.0-beta.133",
|
||||
"@ohif/ui": "3.12.0-beta.133",
|
||||
"dcmjs": "0.48.0",
|
||||
"dcmjs": "0.49.4",
|
||||
"dicom-parser": "1.8.21",
|
||||
"hammerjs": "2.0.8",
|
||||
"prop-types": "15.8.1",
|
||||
@@ -46,9 +46,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@cornerstonejs/adapters": "4.15.28",
|
||||
"@cornerstonejs/core": "4.15.28",
|
||||
"@cornerstonejs/tools": "4.15.28",
|
||||
"@cornerstonejs/adapters": "4.15.29",
|
||||
"@cornerstonejs/core": "4.15.29",
|
||||
"@cornerstonejs/tools": "4.15.29",
|
||||
"classnames": "2.5.1"
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@
|
||||
"@ohif/extension-default": "3.12.0-beta.133",
|
||||
"@ohif/i18n": "3.12.0-beta.133",
|
||||
"@ohif/ui": "3.12.0-beta.133",
|
||||
"dcmjs": "0.48.0",
|
||||
"dcmjs": "0.49.4",
|
||||
"dicom-parser": "1.8.21",
|
||||
"hammerjs": "2.0.8",
|
||||
"prop-types": "15.8.1",
|
||||
@@ -42,8 +42,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@cornerstonejs/core": "4.15.28",
|
||||
"@cornerstonejs/tools": "4.15.28",
|
||||
"@cornerstonejs/core": "4.15.29",
|
||||
"@cornerstonejs/tools": "4.15.29",
|
||||
"classnames": "2.5.1"
|
||||
}
|
||||
}
|
||||
@@ -40,10 +40,10 @@
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "1.3.0",
|
||||
"@cornerstonejs/codec-openjph": "2.4.7",
|
||||
"@cornerstonejs/dicom-image-loader": "4.15.28",
|
||||
"@cornerstonejs/dicom-image-loader": "4.15.29",
|
||||
"@ohif/core": "3.12.0-beta.133",
|
||||
"@ohif/ui": "3.12.0-beta.133",
|
||||
"dcmjs": "0.48.0",
|
||||
"dcmjs": "0.49.4",
|
||||
"dicom-parser": "1.8.21",
|
||||
"hammerjs": "2.0.8",
|
||||
"prop-types": "15.8.1",
|
||||
@@ -53,12 +53,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@cornerstonejs/adapters": "4.15.28",
|
||||
"@cornerstonejs/ai": "4.15.28",
|
||||
"@cornerstonejs/core": "4.15.28",
|
||||
"@cornerstonejs/labelmap-interpolation": "4.15.28",
|
||||
"@cornerstonejs/polymorphic-segmentation": "4.15.28",
|
||||
"@cornerstonejs/tools": "4.15.28",
|
||||
"@cornerstonejs/adapters": "4.15.29",
|
||||
"@cornerstonejs/ai": "4.15.29",
|
||||
"@cornerstonejs/core": "4.15.29",
|
||||
"@cornerstonejs/labelmap-interpolation": "4.15.29",
|
||||
"@cornerstonejs/polymorphic-segmentation": "4.15.29",
|
||||
"@cornerstonejs/tools": "4.15.29",
|
||||
"@icr/polyseg-wasm": "0.4.0",
|
||||
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
|
||||
"@kitware/vtk.js": "34.15.1",
|
||||
|
||||
@@ -2117,7 +2117,9 @@ describe('SegmentationService', () => {
|
||||
service.removeSegment(segmentationId, segmentIndex);
|
||||
|
||||
expect(cstSegmentation.removeSegment).toHaveBeenCalledTimes(1);
|
||||
expect(cstSegmentation.removeSegment).toHaveBeenCalledWith(segmentationId, segmentIndex);
|
||||
expect(cstSegmentation.removeSegment).toHaveBeenCalledWith(segmentationId, segmentIndex, {
|
||||
recordHistory: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ import { SegmentationPresentation, SegmentationPresentationItem } from '../../ty
|
||||
import { EasingFunctionEnum, EasingFunctionMap } from '../../utils/transitions';
|
||||
import { ViewReference } from '@cornerstonejs/core/types';
|
||||
|
||||
const { DefaultHistoryMemo } = csUtils.HistoryMemo;
|
||||
|
||||
const {
|
||||
Labelmap: LABELMAP,
|
||||
Contour: CONTOUR,
|
||||
@@ -951,11 +953,75 @@ class SegmentationService extends PubSubService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a memo that records the current state of a segment (segmentationId/segmentIndex)
|
||||
* so that undo can restore it via addSegment and redo can call removeSegment again
|
||||
* without recording history.
|
||||
*
|
||||
* @param segmentationId - The ID of the segmentation.
|
||||
* @param segmentIndex - The index of the segment (must still exist when called).
|
||||
* @param _options - Reserved (e.g. deleting) for future use.
|
||||
* @returns A Memo with restoreMemo(undo): undo => addSegment, redo => removeSegment (skipRecordingHistory).
|
||||
*/
|
||||
public createSegmentIndexMemo(
|
||||
segmentationId: string,
|
||||
segmentIndex: number,
|
||||
_options?: { deleting?: boolean }
|
||||
): csTypes.Memo | null {
|
||||
const csSegmentation = this.getCornerstoneSegmentation(segmentationId);
|
||||
const segment = csSegmentation?.segments?.[segmentIndex];
|
||||
if (!segment) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let color: csTypes.Color | undefined;
|
||||
let visibility: boolean | undefined;
|
||||
const viewportIds = this.getViewportIdsWithSegmentation(segmentationId);
|
||||
if (viewportIds.length > 0) {
|
||||
const firstViewportId = viewportIds[0];
|
||||
const representations = this.getSegmentationRepresentations(firstViewportId, {
|
||||
segmentationId,
|
||||
});
|
||||
const repType = representations[0]?.type ?? LABELMAP;
|
||||
color = this.getSegmentColor(firstViewportId, segmentationId, segmentIndex);
|
||||
visibility = cstSegmentation.config.visibility.getSegmentIndexVisibility(
|
||||
firstViewportId,
|
||||
{ segmentationId, type: repType },
|
||||
segmentIndex
|
||||
);
|
||||
}
|
||||
|
||||
const segmentState = {
|
||||
segmentIndex,
|
||||
label: segment.label,
|
||||
isLocked: segment.locked,
|
||||
active: segment.active,
|
||||
color,
|
||||
visibility,
|
||||
};
|
||||
|
||||
const service = this;
|
||||
const memo: csTypes.Memo = {
|
||||
id: csUtils.uuidv4(),
|
||||
operationType: 'segmentIndex',
|
||||
restoreMemo(undo?: boolean) {
|
||||
if (undo === true) {
|
||||
service.addSegment(segmentationId, segmentState);
|
||||
} else {
|
||||
// Redo: remove the segment via cornerstone without recording history
|
||||
cstSegmentation.removeSegment(segmentationId, segmentIndex, { recordHistory: false });
|
||||
}
|
||||
},
|
||||
};
|
||||
return memo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param options - Optional. skipRecordingHistory: if true, do not push undo memo (used when redoing).
|
||||
*
|
||||
* @remarks
|
||||
* This method performs the following actions:
|
||||
@@ -964,8 +1030,21 @@ class SegmentationService extends PubSubService {
|
||||
* 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 removeSegment(
|
||||
segmentationId: string,
|
||||
segmentIndex: number,
|
||||
options?: { skipRecordingHistory?: boolean }
|
||||
): void {
|
||||
let memo;
|
||||
if (!options?.skipRecordingHistory) {
|
||||
memo = this.createSegmentIndexMemo(segmentationId, segmentIndex, { deleting: true });
|
||||
DefaultHistoryMemo.startGroupRecording();
|
||||
cstSegmentation.removeSegment(segmentationId, segmentIndex, { recordHistory: true });
|
||||
DefaultHistoryMemo.push(memo);
|
||||
DefaultHistoryMemo.endGroupRecording();
|
||||
} else {
|
||||
cstSegmentation.removeSegment(segmentationId, segmentIndex, { recordHistory: false });
|
||||
}
|
||||
}
|
||||
|
||||
public setSegmentVisibility(
|
||||
|
||||
@@ -59,6 +59,12 @@ export async function updateSegmentationStats({
|
||||
Object.entries(stats).forEach(([segmentIndex, segmentStats]) => {
|
||||
const index = parseInt(segmentIndex);
|
||||
|
||||
if (!updatedSegmentation.segments[index]) {
|
||||
// This happens when a segment is being restored
|
||||
console.warn('Segment not found to update cached stats:', index);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!updatedSegmentation.segments[index].cachedStats) {
|
||||
updatedSegmentation.segments[index].cachedStats = {};
|
||||
hasUpdates = true;
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.12.0-beta.133",
|
||||
"@ohif/i18n": "3.12.0-beta.133",
|
||||
"dcmjs": "0.48.0",
|
||||
"dcmjs": "0.49.4",
|
||||
"dicomweb-client": "0.10.4",
|
||||
"prop-types": "15.8.1",
|
||||
"react": "18.3.1",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.12.0-beta.133",
|
||||
"@ohif/ui": "3.12.0-beta.133",
|
||||
"dcmjs": "0.48.0",
|
||||
"dcmjs": "0.49.4",
|
||||
"dicom-parser": "1.8.21",
|
||||
"hammerjs": "2.0.8",
|
||||
"prop-types": "15.8.1",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.12.0-beta.133",
|
||||
"@ohif/ui": "3.12.0-beta.133",
|
||||
"dcmjs": "0.48.0",
|
||||
"dcmjs": "0.49.4",
|
||||
"dicom-parser": "1.8.21",
|
||||
"hammerjs": "2.0.8",
|
||||
"prop-types": "15.8.1",
|
||||
|
||||
@@ -32,14 +32,14 @@
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@cornerstonejs/core": "4.15.28",
|
||||
"@cornerstonejs/tools": "4.15.28",
|
||||
"@cornerstonejs/core": "4.15.29",
|
||||
"@cornerstonejs/tools": "4.15.29",
|
||||
"@ohif/core": "3.12.0-beta.133",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.133",
|
||||
"@ohif/extension-default": "3.12.0-beta.133",
|
||||
"@ohif/ui": "3.12.0-beta.133",
|
||||
"classnames": "2.5.1",
|
||||
"dcmjs": "0.48.0",
|
||||
"dcmjs": "0.49.4",
|
||||
"lodash.debounce": "4.0.8",
|
||||
"prop-types": "15.8.1",
|
||||
"react": "18.3.1",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.12.0-beta.133",
|
||||
"@ohif/ui": "3.12.0-beta.133",
|
||||
"dcmjs": "0.48.0",
|
||||
"dcmjs": "0.49.4",
|
||||
"dicom-parser": "1.8.21",
|
||||
"hammerjs": "2.0.8",
|
||||
"prop-types": "15.8.1",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.12.0-beta.133",
|
||||
"@ohif/ui": "3.12.0-beta.133",
|
||||
"dcmjs": "0.48.0",
|
||||
"dcmjs": "0.49.4",
|
||||
"dicom-parser": "1.8.21",
|
||||
"hammerjs": "2.0.8",
|
||||
"prop-types": "15.8.1",
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@cornerstonejs/core": "4.15.28",
|
||||
"@cornerstonejs/tools": "4.15.28",
|
||||
"@cornerstonejs/core": "4.15.29",
|
||||
"@cornerstonejs/tools": "4.15.29",
|
||||
"@ohif/core": "3.12.0-beta.133",
|
||||
"@ohif/extension-cornerstone": "3.12.0-beta.133",
|
||||
"@ohif/extension-default": "3.12.0-beta.133",
|
||||
|
||||
Reference in new issue
Block a user