From 74a64e24f96d26cbee45f15bb835269553b08be7 Mon Sep 17 00:00:00 2001 From: Forrest Date: Wed, 23 Oct 2024 15:56:26 -0400 Subject: [PATCH 1/3] fix(ImageMapper): label outlines with I/J slicing --- Sources/Rendering/OpenGL/ImageMapper/index.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Sources/Rendering/OpenGL/ImageMapper/index.js b/Sources/Rendering/OpenGL/ImageMapper/index.js index 3169b54071c..d384c9da821 100644 --- a/Sources/Rendering/OpenGL/ImageMapper/index.js +++ b/Sources/Rendering/OpenGL/ImageMapper/index.js @@ -272,6 +272,7 @@ function vtkOpenGLImageMapper(publicAPI, model) { 'uniform mat4 PCWCMatrix;', 'uniform mat4 vWCtoIDX;', 'uniform ivec3 imageDimensions;', + 'uniform int sliceAxis;', ] ).result; @@ -301,6 +302,11 @@ function vtkOpenGLImageMapper(publicAPI, model) { ' // half voxel fix for labelmapOutline', ' return (index + vec3(0.5)) / vec3(imageDimensions);', '}', + 'vec2 getSliceCoords(vec3 coord, int axis) {', + ' if (axis == 0) return coord.yz;', + ' if (axis == 1) return coord.xz;', + ' if (axis == 2) return coord.xy;', + '}', '#endif', ] ).result; @@ -359,7 +365,7 @@ function vtkOpenGLImageMapper(publicAPI, model) { ` #ifdef vtkImageLabelOutlineOn vec3 centerPosIS = fragCoordToIndexSpace(gl_FragCoord); - float centerValue = texture2D(texture1, centerPosIS.xy).r; + float centerValue = texture2D(texture1, getSliceCoords(centerPosIS, sliceAxis)).r; bool pixelOnBorder = false; vec3 tColor = texture2D(colorTexture1, vec2(centerValue * cscale0 + cshift0, 0.5)).rgb; float scalarOpacity = texture2D(pwfTexture1, vec2(centerValue * pwfscale0 + pwfshift0, 0.5)).r; @@ -383,7 +389,7 @@ function vtkOpenGLImageMapper(publicAPI, model) { gl_FragCoord.y + float(j), gl_FragCoord.z, gl_FragCoord.w); vec3 neighborPosIS = fragCoordToIndexSpace(neighborPixelCoord); - float value = texture2D(texture1, neighborPosIS.xy).r; + float value = texture2D(texture1, getSliceCoords(neighborPosIS, sliceAxis)).r; if (value != centerValue) { pixelOnBorder = true; break; @@ -798,13 +804,15 @@ function vtkOpenGLImageMapper(publicAPI, model) { const worldToIndex = image.getWorldToIndex(); const imageDimensions = image.getDimensions(); + const sliceAxis = model.renderable.getClosestIJKAxis().ijkMode; program.setUniform3i( 'imageDimensions', imageDimensions[0], imageDimensions[1], - 1 + imageDimensions[2] ); + program.setUniformi('sliceAxis', sliceAxis); program.setUniformMatrix('vWCtoIDX', worldToIndex); const labelOutlineKeyMats = model.openGLCamera.getKeyMatrices(ren); From 608e4807871127d4c83c83e4c09f0967fd2482e7 Mon Sep 17 00:00:00 2001 From: Forrest Date: Wed, 23 Oct 2024 16:16:00 -0400 Subject: [PATCH 2/3] fix(ImageMapper): handle SlicingMode.NONE --- Sources/Rendering/OpenGL/ImageMapper/index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/Rendering/OpenGL/ImageMapper/index.js b/Sources/Rendering/OpenGL/ImageMapper/index.js index d384c9da821..20e7b4c3935 100644 --- a/Sources/Rendering/OpenGL/ImageMapper/index.js +++ b/Sources/Rendering/OpenGL/ImageMapper/index.js @@ -804,7 +804,12 @@ function vtkOpenGLImageMapper(publicAPI, model) { const worldToIndex = image.getWorldToIndex(); const imageDimensions = image.getDimensions(); - const sliceAxis = model.renderable.getClosestIJKAxis().ijkMode; + let sliceAxis = model.renderable.getClosestIJKAxis().ijkMode; + + // SlicingMode.NONE equates to SlicingMode.K + if (sliceAxis === SlicingMode.NONE) { + sliceAxis = SlicingMode.K; + } program.setUniform3i( 'imageDimensions', From e2c21ad11984b3b21eccecb2cd73d96ef23d78f9 Mon Sep 17 00:00:00 2001 From: Forrest Date: Mon, 4 Nov 2024 12:59:53 -0500 Subject: [PATCH 3/3] test(ImageMapper): slicing I+J label outlines --- .../ImageMapper/test/testImageLabelOutline.js | 106 ++++++++++++++---- .../test/testImageLabelOutline.png | Bin 5588 -> 0 bytes .../test/testImageLabelOutline_I.png | Bin 0 -> 5228 bytes .../test/testImageLabelOutline_J.png | Bin 0 -> 5947 bytes .../test/testImageLabelOutline_K.png | Bin 0 -> 5960 bytes 5 files changed, 82 insertions(+), 24 deletions(-) delete mode 100644 Sources/Rendering/Core/ImageMapper/test/testImageLabelOutline.png create mode 100644 Sources/Rendering/Core/ImageMapper/test/testImageLabelOutline_I.png create mode 100644 Sources/Rendering/Core/ImageMapper/test/testImageLabelOutline_J.png create mode 100644 Sources/Rendering/Core/ImageMapper/test/testImageLabelOutline_K.png diff --git a/Sources/Rendering/Core/ImageMapper/test/testImageLabelOutline.js b/Sources/Rendering/Core/ImageMapper/test/testImageLabelOutline.js index c83effa3ce3..04737a528c2 100644 --- a/Sources/Rendering/Core/ImageMapper/test/testImageLabelOutline.js +++ b/Sources/Rendering/Core/ImageMapper/test/testImageLabelOutline.js @@ -4,6 +4,7 @@ import 'vtk.js/Sources/Rendering/Misc/RenderingAPIs'; import vtkImageMapper from 'vtk.js/Sources/Rendering/Core/ImageMapper'; import vtkImageSlice from 'vtk.js/Sources/Rendering/Core/ImageSlice'; +import { SlicingMode } from 'vtk.js/Sources/Rendering/Core/ImageMapper/Constants'; import vtkRenderer from 'vtk.js/Sources/Rendering/Core/Renderer'; import vtkRenderWindow from 'vtk.js/Sources/Rendering/Core/RenderWindow'; import vtkImageData from 'vtk.js/Sources/Common/DataModel/ImageData'; @@ -11,11 +12,13 @@ import vtkColorTransferFunction from 'vtk.js/Sources/Rendering/Core/ColorTransfe import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray'; import vtkPiecewiseFunction from 'vtk.js/Sources/Common/DataModel/PiecewiseFunction'; -import baseline from './testImageLabelOutline.png'; +import baselineI from './testImageLabelOutline_I.png'; +import baselineJ from './testImageLabelOutline_J.png'; +import baselineK from './testImageLabelOutline_K.png'; -test('Test ImageMapper', (t) => { +test.onlyIfWebGL('Test ImageMapper label outline', (t) => { const gc = testUtils.createGarbageCollector(t); - t.ok('rendering', 'vtkImageMapper testImage'); + t.ok('rendering', 'vtkImageMapper label outline'); // Create some control UI const container = document.querySelector('body'); @@ -129,9 +132,9 @@ test('Test ImageMapper', (t) => { // Create a one slice vtkImageData that has four quadrants of different values const imageData = gc.registerResource(vtkImageData.newInstance()); - const dims = [10, 10, 1]; + const dims = [10, 10, 10]; imageData.setSpacing(1, 1, 1); - imageData.setOrigin(0.1, 0.1, 0.1); + imageData.setOrigin(0, 0, 0); imageData.setDirection(1, 0, 0, 0, 1, 0, 0, 0, 1); imageData.setExtent(0, dims[0] - 1, 0, dims[1] - 1, 0, dims[2] - 1); @@ -140,14 +143,16 @@ test('Test ImageMapper', (t) => { const values = new Uint8Array(dims[0] * dims[1] * dims[2]); let i = 0; - for (let y = 0; y < dims[1]; y++) { - for (let x = 0; x < dims[0]; x++, i++) { - if ((x < 3 && y < 3) || (x > 7 && y > 7)) { - values[i] = BACKGROUND; - } else if (x > 4 && x < 6 && y > 4 && y < 7) { - values[i] = LOW_VALUE; - } else { - values[i] = HIGH_VALUE; + for (let z = 0; z < dims[2]; z++) { + for (let y = 0; y < dims[1]; y++) { + for (let x = 0; x < dims[0]; x++, i++) { + if ((x < 3 && y < 3) || (x > 7 && y > 7)) { + values[i] = BACKGROUND; + } else if (x > 4 && x < 6 && y > 4 && y < 7) { + values[i] = LOW_VALUE; + } else { + values[i] = HIGH_VALUE; + } } } } @@ -185,15 +190,68 @@ test('Test ImageMapper', (t) => { renderWindow.addView(glwindow); glwindow.setSize(400, 400); - glwindow.captureNextImage().then((image) => { - testUtils.compareImages( - image, - [baseline], - 'Rendering/Core/ImageMapperLabelOutline', - t, - 1, - gc.releaseResources - ); - }); - renderWindow.render(); + function testWithSlicing(slicingMode, baseline) { + const PARAMS = { + I: { + mode: SlicingMode.I, + cameraPos: [1, 0, 0], + cameraDir: [1, 0, 0], + cameraUp: [0, 1, 0], + slice: 2, + }, + J: { + mode: SlicingMode.J, + cameraPos: [0, 1, 0], + cameraDir: [0, 1, 0], + cameraUp: [0, 0, 1], + slice: 6, + }, + K: { + mode: SlicingMode.K, + cameraPos: [0, 0, 1], + cameraDir: [0, 0, -1], + cameraUp: [0, 1, 0], + slice: 5, + }, + }; + + const params = PARAMS[slicingMode]; + + return function testSlicingLabelOutline() { + t.comment(`testImageLabelOutline: ${slicingMode} slicing`); + const { promise, resolve } = Promise.withResolvers(); + + mapper.setSlicingMode(params.mode); + mapper.setSlice(params.slice); + labelMap.mapper.setSlicingMode(params.mode); + labelMap.mapper.setSlice(params.slice); + + renderer.getActiveCamera().setPosition(...params.cameraPos); + renderer.getActiveCamera().setDirectionOfProjection(...params.cameraDir); + renderer.getActiveCamera().setViewUp(...params.cameraUp); + renderer.resetCamera(); + renderer.resetCameraClippingRange(); + + glwindow.captureNextImage().then((image) => { + testUtils.compareImages( + image, + [baseline], + `Rendering/Core/ImageMapperLabelOutline_${slicingMode}`, + t, + 1, + resolve + ); + }); + + renderWindow.render(); + return promise; + }; + } + + [ + testWithSlicing('I', baselineI), + testWithSlicing('J', baselineJ), + testWithSlicing('K', baselineK), + gc.releaseResources, + ].reduce((cur, next) => cur.then(next), Promise.resolve()); }); diff --git a/Sources/Rendering/Core/ImageMapper/test/testImageLabelOutline.png b/Sources/Rendering/Core/ImageMapper/test/testImageLabelOutline.png deleted file mode 100644 index abe786f44e711bf982f9f1326d8cf973e714b364..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5588 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 diff --git a/Sources/Rendering/Core/ImageMapper/test/testImageLabelOutline_I.png b/Sources/Rendering/Core/ImageMapper/test/testImageLabelOutline_I.png new file mode 100644 index 0000000000000000000000000000000000000000..f61e441516a6cff95dfbdca8440e2e475d6b46f5 GIT binary patch literal 5228 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 literal 0 HcmV?d00001