From 90c1145b0ee74034cee8bebe542d37c400a56a53 Mon Sep 17 00:00:00 2001 From: Alireza Date: Fri, 17 Jul 2026 11:28:50 -0400 Subject: [PATCH 1/2] fix(core): re-apply invert after colormap when scrolling a stack _setPropertiesFromCache runs on every stack scroll and (since #2679) calls setColormap, which rebuilds the RGB transfer function in its default, non-inverted state. The setInvertColor call that follows was then skipped by its no-op guard (setInvertColorGPU only flips the TF when the requested value differs from this.invert) because this.invert still held the carried-over value, so the freshly rebuilt TF stayed non-inverted and the inversion was lost. This surfaced as "invert is lost when scrolling after a reset": resetProperties applies a colormap, so this.colormap becomes defined and is then re-applied on every subsequent scroll, clobbering the inversion. Clear this.invert right after setColormap so the tracked flag matches the rebuilt (non-inverted) TF and setInvertColor actually re-applies the inversion. Colormap preservation from #2679 is retained. Fixes OHIF/Viewers#6153 --- packages/core/src/RenderingEngine/StackViewport.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/core/src/RenderingEngine/StackViewport.ts b/packages/core/src/RenderingEngine/StackViewport.ts index 2d6021985a..6ddb080863 100644 --- a/packages/core/src/RenderingEngine/StackViewport.ts +++ b/packages/core/src/RenderingEngine/StackViewport.ts @@ -995,6 +995,15 @@ class StackViewport extends Viewport { if (typeof colormap !== 'undefined') { this.setColormap(colormap); + // setColormap rebuilds the RGB transfer function in its default, + // non-inverted state, so `this.invert` no longer reflects the actor's + // actual LUT. Clear the tracked flag so the setInvertColor below actually + // re-applies the inversion instead of being skipped by its no-op guard + // (setInvertColorGPU only flips the TF when the requested value differs + // from this.invert). Without this, invert is lost when scrolling after a + // reset: reset applies a colormap, which is then re-applied on every + // scroll and clobbers the inversion. + this.invert = false; } this.setInterpolationType(interpolationType); From 9aa94cd4f2b6e56d1304ed8e2a042b87a9127be4 Mon Sep 17 00:00:00 2001 From: Alireza Date: Fri, 17 Jul 2026 11:51:17 -0400 Subject: [PATCH 2/2] test(core): preserve stack inversion after scrolling --- .../test/stackViewport_gpu_render_test.js | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/packages/core/test/stackViewport_gpu_render_test.js b/packages/core/test/stackViewport_gpu_render_test.js index d0dbda759d..84beec9e26 100644 --- a/packages/core/test/stackViewport_gpu_render_test.js +++ b/packages/core/test/stackViewport_gpu_render_test.js @@ -26,7 +26,8 @@ const { cache, RenderingEngine, utilities, imageLoader, metaData, Enums } = cornerstone3D; const { Events, ViewportType, InterpolationType } = Enums; -const { calibratedPixelSpacingMetadataProvider } = utilities; +const { calibratedPixelSpacingMetadataProvider, transferFunctionUtils } = + utilities; const { fakeImageLoader, fakeMetaDataProvider, compareImages } = testUtils; @@ -916,6 +917,55 @@ describe('renderingCore -- Stack', () => { .catch(done.fail); }); + it('Should preserve inversion when scrolling after resetProperties', function (done) { + testUtils.createViewports(renderingEngine, { + viewportId, + orientation: Enums.OrientationAxis.AXIAL, + }); + + const imageInfo = { + loader: 'fakeImageLoader', + name: 'imageURI', + rows: 64, + columns: 64, + barStart: 20, + barWidth: 5, + xSpacing: 1, + ySpacing: 1, + sliceIndex: 0, + }; + const imageId1 = testUtils.encodeImageIdInfo(imageInfo); + const imageId2 = testUtils.encodeImageIdInfo({ + ...imageInfo, + sliceIndex: 1, + }); + + const vp = renderingEngine.getViewport(viewportId); + const getTransferFunctionColors = () => { + const actor = vp.getDefaultActor().actor; + const transferFunction = actor.getProperty().getRGBTransferFunction(0); + + return transferFunctionUtils + .getTransferFunctionNodes(transferFunction) + .map(([, red, green, blue]) => [red, green, blue]); + }; + + vp.setStack([imageId1, imageId2], 0) + .then(() => { + vp.resetProperties(); + vp.setProperties({ invert: true }); + + const invertedColors = getTransferFunctionColors(); + + return vp.setImageIdIndex(1).then(() => { + expect(vp.getProperties().invert).toBe(true); + expect(getTransferFunctionColors()).toEqual(invertedColors); + }); + }) + .then(done) + .catch(done.fail); + }); + it('Should be able to resetProperties API', function (done) { const element = testUtils.createViewports(renderingEngine, { viewportId,