diff --git a/extensions/cornerstone/src/services/SegmentationService/SegmentationService.test.ts b/extensions/cornerstone/src/services/SegmentationService/SegmentationService.test.ts index 754c4aaf0a7..d5530dc9654 100644 --- a/extensions/cornerstone/src/services/SegmentationService/SegmentationService.test.ts +++ b/extensions/cornerstone/src/services/SegmentationService/SegmentationService.test.ts @@ -1331,6 +1331,51 @@ describe('SegmentationService', () => { expect(retrievedSegmentationId).toEqual(segmentationId); }); + + it('should create a dedicated color LUT and remember its index so every viewport representation reuses it', async () => { + const displaySet = { + imageIds: ['imageId'], + isDynamicVolume: false, + SeriesNumber: 1, + SeriesDescription: 'Series Description', + } as unknown as AppTypes.DisplaySet; + + jest + .spyOn(imageLoader, 'createAndCacheDerivedLabelmapImages') + .mockReturnValue([{ imageId: 'imageId' }] as csTypes.IImage[]); + jest.spyOn(cstSegmentation.state, 'getSegmentations').mockReturnValue([]); + jest.spyOn(service, 'addOrUpdateSegmentation').mockReturnValue(undefined); + jest.mocked(cstSegmentation.state.addColorLUT).mockReturnValue(7); + + const segmentationId = await service.createLabelmapForDisplaySet(displaySet); + + expect(cstSegmentation.state.addColorLUT).toHaveBeenCalledWith([[0, 0, 0, 0]]); + expect(service['_segmentationIdToColorLUTIndexMap'].get(segmentationId)).toBe(7); + }); + + it('should keep the existing color LUT when called again with the same segmentation id', async () => { + const displaySet = { + imageIds: ['imageId'], + isDynamicVolume: false, + SeriesNumber: 1, + SeriesDescription: 'Series Description', + } as unknown as AppTypes.DisplaySet; + + jest + .spyOn(imageLoader, 'createAndCacheDerivedLabelmapImages') + .mockReturnValue([{ imageId: 'imageId' }] as csTypes.IImage[]); + jest.spyOn(cstSegmentation.state, 'getSegmentations').mockReturnValue([]); + jest.spyOn(service, 'addOrUpdateSegmentation').mockReturnValue(undefined); + jest.mocked(cstSegmentation.state.addColorLUT).mockReturnValueOnce(7).mockReturnValueOnce(8); + + const segmentationId = await service.createLabelmapForDisplaySet(displaySet); + await service.createLabelmapForDisplaySet(displaySet, { segmentationId }); + + // Updating an existing segmentation must not strand the representations + // already rendering it on the previous LUT. + expect(cstSegmentation.state.addColorLUT).toHaveBeenCalledTimes(1); + expect(service['_segmentationIdToColorLUTIndexMap'].get(segmentationId)).toBe(7); + }); }); describe('createSegmentationForSEGDisplaySet', () => { diff --git a/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts b/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts index a1dd3226449..e0a54fe37e7 100644 --- a/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts +++ b/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts @@ -512,6 +512,19 @@ class SegmentationService extends PubSubService implements ISegmentationServiceI }, }; + // Create a dedicated color LUT up front and remember its index so that every + // representation of this segmentation (one per viewport) reuses the same LUT. + // Otherwise each viewport would get its own default LUT copy and editing a + // segment color on one viewport would not be reflected on the others (the + // segment color appears to revert to the default when interacting elsewhere). + // The caller may pass the id of an existing segmentation, which this method + // updates rather than replaces; keep its LUT so representations already + // rendering it don't diverge from the ones created afterwards. + if (!this._segmentationIdToColorLUTIndexMap.has(segmentationId)) { + const colorLUTIndex = addColorLUT([[0, 0, 0, 0]] as csTypes.ColorLUT); + this._segmentationIdToColorLUTIndexMap.set(segmentationId, colorLUTIndex); + } + this.addOrUpdateSegmentation(segmentationPublicInput); return segmentationId; }