fix(3DSegmentation): [Bug] The viewports become blank when loading the seg file in advanced layout after closing the seg file from any other advanced layout (#5505)

This commit is contained in:
Vinícius Alves de Faria Resende authored and GitHub committed 2025-10-28 08:52:12 -04:00
1 parent afa9131da1
commit 76f7d4e23a
25 files changed
+189 -208

No files matched your search

@@ -46,8 +46,8 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.5.19",
"@cornerstonejs/core": "4.5.19",
"@cornerstonejs/adapters": "4.5.20",
"@cornerstonejs/core": "4.5.20",
"@kitware/vtk.js": "32.12.0",
"react-color": "2.19.3"
}
@@ -46,8 +46,8 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.5.19",
"@cornerstonejs/core": "4.5.19",
"@cornerstonejs/adapters": "4.5.20",
"@cornerstonejs/core": "4.5.20",
"@kitware/vtk.js": "32.12.0",
"react-color": "2.19.3"
}
+3 -3
View File
@@ -46,9 +46,9 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.5.19",
"@cornerstonejs/core": "4.5.19",
"@cornerstonejs/tools": "4.5.19",
"@cornerstonejs/adapters": "4.5.20",
"@cornerstonejs/core": "4.5.20",
"@cornerstonejs/tools": "4.5.20",
"classnames": "2.5.1"
}
}
@@ -42,8 +42,8 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/core": "4.5.19",
"@cornerstonejs/tools": "4.5.19",
"@cornerstonejs/core": "4.5.20",
"@cornerstonejs/tools": "4.5.20",
"classnames": "2.5.1"
}
}
+7 -7
View File
@@ -40,7 +40,7 @@
"@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.5.19",
"@cornerstonejs/dicom-image-loader": "4.5.20",
"@ohif/core": "3.12.0-beta.76",
"@ohif/ui": "3.12.0-beta.76",
"dcmjs": "0.43.1",
@@ -53,12 +53,12 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.5.19",
"@cornerstonejs/ai": "4.5.19",
"@cornerstonejs/core": "4.5.19",
"@cornerstonejs/labelmap-interpolation": "4.5.19",
"@cornerstonejs/polymorphic-segmentation": "4.5.19",
"@cornerstonejs/tools": "4.5.19",
"@cornerstonejs/adapters": "4.5.20",
"@cornerstonejs/ai": "4.5.20",
"@cornerstonejs/core": "4.5.20",
"@cornerstonejs/labelmap-interpolation": "4.5.20",
"@cornerstonejs/polymorphic-segmentation": "4.5.20",
"@cornerstonejs/tools": "4.5.20",
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
"@kitware/vtk.js": "32.12.0",
"html2canvas": "1.4.1",
+16 -4
View File
@@ -236,16 +236,28 @@ function commandsModule({
return;
}
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
if (!viewport) {
return;
}
if (displaySet.isOverlayDisplaySet) {
// update the previously stored segmentationPresentation with the new viewportId
// presentation so that when we put the referencedDisplaySet back in the viewport
// it will have the correct segmentation representation hydrated
const segmentationType =
// Todo: check if PMAP modality should be handled such as SEG
displaySet.Modality !== 'SEG'
? SegmentationRepresentations.Contour
: viewport.type === CoreEnums.ViewportType.VOLUME_3D
? SegmentationRepresentations.Surface
: SegmentationRepresentations.Labelmap;
commandsManager.runCommand('updateStoredSegmentationPresentation', {
displaySet,
type:
displaySet.Modality === 'SEG'
? SegmentationRepresentations.Labelmap
: SegmentationRepresentations.Contour,
type: segmentationType,
});
}
@@ -682,7 +682,7 @@ export function useViewportRendering(
const actorEntries = viewport.getActors();
const actorEntry = actorEntries?.find(entry =>
entry.referencedId.includes(activeDisplaySetInstanceUID)
entry.referencedId?.includes(activeDisplaySetInstanceUID)
);
if (!actorEntry) {
@@ -714,7 +714,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
// is being used to navigate to the initial view position for measurement
// navigation and other navigation forcing specific views.
let initialImageIndexToUse =
presentations?.positionPresentation?.initialImageIndex ?? <number>initialImageIndex;
presentations?.positionPresentation?.initialImageIndex ?? (initialImageIndex as number);
const { rotation, flipHorizontal, displayArea } = viewportInfo.getViewportOptions();
@@ -933,6 +933,10 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
const displaySet = displaySetService.getDisplaySetByUID(displaySetUIDs[0]);
const displaySetModality = displaySet?.Modality;
// seems like a hack but we need the actor to be ready first before
// we set the properties
const timeoutViewportCallback = (callback: () => void) => setTimeout(callback, 0);
// filter overlay display sets (e.g. segmentation) since they will get handled below via the segmentation service
const filteredVolumeInputArray = volumeInputArray
.map((volumeInput, index) => {
@@ -984,12 +988,21 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
const backgroundDisplaySet = displaySetService.getDisplaySetsBy(
displaySet =>
!displaySet.isOverlayDisplaySet &&
displaySet.images.some(image => image.imageId === sampleImageId)
displaySet.images?.some(image => image.imageId === sampleImageId)
);
if (backgroundDisplaySet.length !== 1) {
throw new Error('Background display set not found');
}
if (viewport.type === csEnums.ViewportType.VOLUME_3D) {
timeoutViewportCallback(() => {
viewportGridService.setDisplaySetsForViewport({
viewportId: viewport.id,
displaySetInstanceUIDs: [backgroundDisplaySet[0].displaySetInstanceUID],
});
});
}
}
});
}
@@ -1006,15 +1019,13 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
viewport.render();
volumesProperties.forEach(({ properties, volumeId }) => {
setTimeout(() => {
// seems like a hack but we need the actor to be ready first before
// we set the properties
timeoutViewportCallback(() => {
viewport.setProperties(properties, volumeId);
viewport.render();
}, 0);
});
});
this.setPresentations(viewport.id, presentations, viewportInfo);
this.setPresentations(viewport.id, presentations);
if (!presentations.positionPresentation) {
const imageIndex = this._getInitialImageIndexForViewport(viewportInfo);
@@ -70,43 +70,6 @@ describe('getUpdatedViewportsForSegmentation', () => {
expect(result).toEqual(mockUpdatedViewports);
});
it('should filter out volume3d viewports', () => {
const mockUpdatedViewports = [
{
viewportOptions: {
viewportType: 'stack',
},
},
{
viewportOptions: {
viewportType: 'volume3d',
},
},
{
viewportOptions: {
viewportType: 'volume',
},
},
];
mockHangingProtocolService.getViewportsRequireUpdate.mockReturnValue(mockUpdatedViewports);
const result = getUpdatedViewportsForSegmentation(defaultParameters);
expect(result).toEqual([
{
viewportOptions: {
viewportType: 'stack',
},
},
{
viewportOptions: {
viewportType: 'volume',
},
},
]);
});
it('should handle viewports without viewportOptions', () => {
const mockUpdatedViewports = [
{
@@ -255,70 +218,22 @@ describe('getUpdatedViewportsForSegmentation', () => {
it('should handle getViewportsRequireUpdate returning null', () => {
mockHangingProtocolService.getViewportsRequireUpdate.mockReturnValue(null);
expect(() => getUpdatedViewportsForSegmentation(defaultParameters)).toThrow();
expect(getUpdatedViewportsForSegmentation(defaultParameters)).toEqual(null);
});
it('should handle mixed viewport types including volume3d', () => {
const mockUpdatedViewports = [
{
viewportOptions: {
viewportType: 'stack',
},
},
{
viewportOptions: {
viewportType: 'volume3d',
},
},
{
viewportOptions: {
viewportType: 'volume3d',
},
},
{
viewportOptions: {
viewportType: 'orthogonal',
},
},
{ viewportOptions: { viewportType: 'stack' } },
{ viewportOptions: { viewportType: 'volume3d' } },
{ viewportOptions: { viewportType: 'volume3d' } },
{ viewportOptions: { viewportType: 'orthogonal' } },
];
mockHangingProtocolService.getViewportsRequireUpdate.mockReturnValue(mockUpdatedViewports);
const result = getUpdatedViewportsForSegmentation(defaultParameters);
expect(result).toEqual([
{
viewportOptions: {
viewportType: 'stack',
},
},
{
viewportOptions: {
viewportType: 'orthogonal',
},
},
]);
});
it('should handle all volume3d viewports', () => {
const mockUpdatedViewports = [
{
viewportOptions: {
viewportType: 'volume3d',
},
},
{
viewportOptions: {
viewportType: 'volume3d',
},
},
];
mockHangingProtocolService.getViewportsRequireUpdate.mockReturnValue(mockUpdatedViewports);
const result = getUpdatedViewportsForSegmentation(defaultParameters);
expect(result).toEqual([]);
expect(result).toEqual(mockUpdatedViewports);
});
it('should handle viewports with undefined viewportType', () => {
@@ -328,6 +243,11 @@ describe('getUpdatedViewportsForSegmentation', () => {
viewportType: undefined,
},
},
{
viewportOptions: {
viewportType: 'volume3d',
},
},
{
viewportOptions: {
someOtherProperty: 'value',
@@ -16,7 +16,7 @@ function getUpdatedViewportsForSegmentation({
isHangingProtocolLayout
);
return updatedViewports.filter(v => v.viewportOptions?.viewportType !== 'volume3d');
return updatedViewports;
}
const getTargetViewport = ({ viewportId, viewportGridService }) => {
@@ -84,7 +84,7 @@ export default function interleaveCenterLoader({
// the imageIds and save them in AllRequests for later use
const AllRequests = [];
volumes.forEach(volume => {
const requests = volume.getImageLoadRequests();
const requests = volume.getImageLoadRequests?.() ?? [];
if (!requests.length || !requests[0] || !requests[0].imageId) {
return;
@@ -83,7 +83,7 @@ export default function interleaveTopToBottom({
// the imageIds and save them in AllRequests for later use
const AllRequests = [];
volumes.forEach(volume => {
const requests = volume.getImageLoadRequests();
const requests = volume.getImageLoadRequests?.() ?? [];
if (!requests?.[0]?.imageId) {
return;
@@ -348,20 +348,4 @@ describe('Transitions Module', () => {
});
});
});
describe('Performance Validation', () => {
it('should handle many rapid calls without issues', () => {
const startTime = performance.now();
for (let i = 0; i < 1000; i++) {
const t = i / 1000;
linear(t);
easeInOut(t, 0.1);
ease(t, 0.2, 1.5);
}
const endTime = performance.now();
expect(endTime - startTime).toBeLessThan(10);
});
});
});
+2 -2
View File
@@ -32,8 +32,8 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@cornerstonejs/core": "4.5.19",
"@cornerstonejs/tools": "4.5.19",
"@cornerstonejs/core": "4.5.20",
"@cornerstonejs/tools": "4.5.20",
"@ohif/core": "3.12.0-beta.76",
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.76",
"@ohif/extension-default": "3.12.0-beta.76",
+2 -2
View File
@@ -42,8 +42,8 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/core": "4.5.19",
"@cornerstonejs/tools": "4.5.19",
"@cornerstonejs/core": "4.5.20",
"@cornerstonejs/tools": "4.5.20",
"@ohif/core": "3.12.0-beta.76",
"@ohif/extension-cornerstone": "3.12.0-beta.76",
"@ohif/extension-default": "3.12.0-beta.76",