Skip to content

Commit fd218e8

Browse files
authored
fix(rendering): norm16 and half float was not scaling correctly (#1404)
* feat: Add resetInitialization function to core package * feat: Update renderingPipelinesCPU test to include CPU rendering and handle annotations * api
1 parent b6bbf9b commit fd218e8

File tree

15 files changed

+566
-16
lines changed

15 files changed

+566
-16
lines changed

common/reviews/api/core.api.md

+3
Original file line numberDiff line numberDiff line change
@@ -2889,6 +2889,9 @@ enum RequestType {
28892889
Thumbnail = "thumbnail"
28902890
}
28912891

2892+
// @public (undocumented)
2893+
export function resetInitialization(): void;
2894+
28922895
// @public (undocumented)
28932896
export function resetUseCPURendering(): void;
28942897

packages/core/src/RenderingEngine/vtkClasses/vtkStreamingOpenGLVolumeMapper.js

+27-12
Original file line numberDiff line numberDiff line change
@@ -185,22 +185,37 @@ function vtkStreamingOpenGLVolumeMapper(publicAPI, model) {
185185
}
186186

187187
if (shouldReset) {
188-
model.scalarTexture.setOglNorm16Ext(
189-
model.context.getExtension('EXT_texture_norm16')
190-
);
188+
const norm16Ext = model.context.getExtension('EXT_texture_norm16');
189+
model.scalarTexture.setOglNorm16Ext(norm16Ext);
191190

192191
model.scalarTexture.releaseGraphicsResources(model._openGLRenderWindow);
193192
model.scalarTexture.resetFormatAndType();
194193

195-
model.scalarTexture.create3DFilterableFromRaw(
196-
dims[0],
197-
dims[1],
198-
dims[2],
199-
numComp,
200-
scalars.getDataType(),
201-
scalars.getData(),
202-
model.renderable.getPreferSizeOverAccuracy()
203-
);
194+
// If preferSizeOverAccuracy is true or we're using a norm16 texture,
195+
// we need to use the FromDataArray method to create the texture for scaling.
196+
// Otherwise, we can use the FromRaw method.
197+
if (
198+
model.renderable.getPreferSizeOverAccuracy() ||
199+
(norm16Ext && dataType === VtkDataTypes.UNSIGNED_SHORT) ||
200+
dataType === VtkDataTypes.SHORT
201+
) {
202+
model.scalarTexture.create3DFilterableFromDataArray(
203+
dims[0],
204+
dims[1],
205+
dims[2],
206+
scalars,
207+
model.renderable.getPreferSizeOverAccuracy()
208+
);
209+
} else {
210+
model.scalarTexture.create3DFilterableFromRaw(
211+
dims[0],
212+
dims[1],
213+
dims[2],
214+
numComp,
215+
scalars.getDataType(),
216+
scalars.getData()
217+
);
218+
}
204219
} else {
205220
model.scalarTexture.deactivate();
206221
model.scalarTexture.update3DFromRaw(data);

packages/core/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
getWebWorkerManager,
4646
canRenderFloatTextures,
4747
peerImport,
48+
resetInitialization,
4849
} from './init';
4950

5051
// Classes
@@ -89,6 +90,7 @@ export {
8990
init,
9091
isCornerstoneInitialized,
9192
peerImport,
93+
resetInitialization,
9294
// configs
9395
getConfiguration,
9496
setConfiguration,

packages/core/src/init.ts

+5
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,10 @@ function isCornerstoneInitialized(): boolean {
272272
return csRenderInitialized;
273273
}
274274

275+
function resetInitialization(): void {
276+
csRenderInitialized = false;
277+
}
278+
275279
/**
276280
* This function returns a copy of the config object. This is used to prevent the
277281
* config object from being modified by other parts of the program.
@@ -328,4 +332,5 @@ export {
328332
getWebWorkerManager,
329333
canRenderFloatTextures,
330334
peerImport,
335+
resetInitialization,
331336
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
import {
2+
RenderingEngine,
3+
Types,
4+
Enums,
5+
setVolumesForViewports,
6+
volumeLoader,
7+
getRenderingEngines,
8+
cache,
9+
resetInitialization,
10+
} from '@cornerstonejs/core';
11+
import {
12+
initDemo,
13+
createImageIdsAndCacheMetaData,
14+
setTitleAndDescription,
15+
addDropdownToToolbar,
16+
} from '../../../../utils/demo/helpers';
17+
import * as cornerstoneTools from '@cornerstonejs/tools';
18+
19+
// This is for debugging purposes
20+
console.warn(
21+
'Click on index.ts to open source code for this example --------->'
22+
);
23+
24+
const {
25+
ToolGroupManager,
26+
StackScrollMouseWheelTool,
27+
ZoomTool,
28+
EllipticalROITool,
29+
Enums: csToolsEnums,
30+
} = cornerstoneTools;
31+
32+
const { ViewportType } = Enums;
33+
const { MouseBindings } = csToolsEnums;
34+
35+
// Define a unique id for the volume
36+
const volumeName = 'CT_VOLUME_ID'; // Id of the volume less loader prefix
37+
const volumeLoaderScheme = 'cornerstoneStreamingImageVolume'; // Loader id which defines which volume loader to use
38+
const volumeId = `${volumeLoaderScheme}:${volumeName}`; // VolumeId with loader id + volume id
39+
40+
// ======== Set up page ======== //
41+
setTitleAndDescription(
42+
'Annotation Tools On Volumes',
43+
'Here we demonstrate how annotation tools can be drawn/rendered on any plane.'
44+
);
45+
46+
const size = '500px';
47+
const content = document.getElementById('content');
48+
const viewportGrid = document.createElement('div');
49+
50+
viewportGrid.style.display = 'flex';
51+
viewportGrid.style.display = 'flex';
52+
viewportGrid.style.flexDirection = 'row';
53+
54+
const element1 = document.createElement('div');
55+
const element2 = document.createElement('div');
56+
element1.oncontextmenu = () => false;
57+
element2.oncontextmenu = () => false;
58+
59+
element1.style.width = size;
60+
element1.style.height = size;
61+
element2.style.width = size;
62+
element2.style.height = size;
63+
64+
viewportGrid.appendChild(element1);
65+
viewportGrid.appendChild(element2);
66+
67+
content.appendChild(viewportGrid);
68+
69+
const instructions = document.createElement('p');
70+
instructions.innerText =
71+
'Left Click to draw length measurements on any viewport.\n Use the mouse wheel to scroll through the stack.';
72+
73+
content.append(instructions);
74+
75+
const toolGroupId = 'TOOLGROUP';
76+
77+
// Add tools to Cornerstone3D
78+
cornerstoneTools.addTool(cornerstoneTools.EllipticalROITool);
79+
cornerstoneTools.addTool(ZoomTool);
80+
cornerstoneTools.addTool(StackScrollMouseWheelTool);
81+
82+
// Define a tool group, which defines how mouse events map to tool commands for
83+
// Any viewport using the group
84+
const toolGroup = ToolGroupManager.createToolGroup(toolGroupId);
85+
86+
// Add the tools to the tool group and specify which volume they are pointing at
87+
toolGroup.addTool(EllipticalROITool.toolName, { volumeId });
88+
toolGroup.addTool(ZoomTool.toolName, { volumeId });
89+
toolGroup.addTool(StackScrollMouseWheelTool.toolName);
90+
91+
// Set the initial state of the tools, here we set one tool active on left click.
92+
// This means left click will draw that tool.
93+
toolGroup.setToolActive(EllipticalROITool.toolName, {
94+
bindings: [
95+
{
96+
mouseButton: MouseBindings.Primary, // Left Click
97+
},
98+
],
99+
});
100+
101+
toolGroup.setToolActive(ZoomTool.toolName, {
102+
bindings: [
103+
{
104+
mouseButton: MouseBindings.Secondary, // Right Click
105+
},
106+
],
107+
});
108+
109+
// As the Stack Scroll mouse wheel is a tool using the `mouseWheelCallback`
110+
// hook instead of mouse buttons, it does not need to assign any mouse button.
111+
toolGroup.setToolActive(StackScrollMouseWheelTool.toolName);
112+
113+
const configToUse = {
114+
rendering: {
115+
useNorm16Texture: false,
116+
preferSizeOverAccuracy: false,
117+
},
118+
};
119+
120+
async function handleRenderingOptionChange(selectedValue) {
121+
const renderingEngines = getRenderingEngines();
122+
const renderingEngine = renderingEngines[0];
123+
124+
switch (selectedValue) {
125+
case 'Prefer size over accuracy':
126+
configToUse.rendering.preferSizeOverAccuracy = true;
127+
configToUse.rendering.useNorm16Texture = false;
128+
break;
129+
case 'Use norm 16 texture':
130+
configToUse.rendering.useNorm16Texture = true;
131+
configToUse.rendering.preferSizeOverAccuracy = false;
132+
break;
133+
default:
134+
configToUse.rendering.useNorm16Texture = false;
135+
configToUse.rendering.preferSizeOverAccuracy = false;
136+
break;
137+
}
138+
139+
// Destroy and reinitialize the rendering engine
140+
resetInitialization();
141+
renderingEngine.destroy();
142+
cache.purgeCache();
143+
await run();
144+
}
145+
146+
addDropdownToToolbar({
147+
options: {
148+
values: ['Default', 'Prefer size over accuracy', 'Use norm 16 texture'],
149+
defaultValue: 'Default',
150+
},
151+
onSelectedValueChange: handleRenderingOptionChange,
152+
});
153+
154+
/**
155+
* Runs the demo
156+
*/
157+
async function run() {
158+
// Init Cornerstone and related libraries
159+
await initDemo({
160+
core: {
161+
...configToUse,
162+
},
163+
});
164+
165+
// Get Cornerstone imageIds and fetch metadata into RAM
166+
const imageIds = await createImageIdsAndCacheMetaData({
167+
StudyInstanceUID:
168+
'1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463',
169+
SeriesInstanceUID:
170+
'1.3.6.1.4.1.14519.5.2.1.7009.2403.226151125820845824875394858561',
171+
wadoRsRoot: 'https://d3t6nz73ql33tx.cloudfront.net/dicomweb',
172+
});
173+
174+
// Instantiate a rendering engine
175+
const renderingEngineId = 'myRenderingEngine';
176+
const renderingEngine = new RenderingEngine(renderingEngineId);
177+
178+
// Create the viewports
179+
const viewportIds = ['CT_AXIAL_STACK', 'CT_SAGITTAL_STACK'];
180+
181+
const viewportInputArray = [
182+
{
183+
viewportId: viewportIds[0],
184+
type: ViewportType.ORTHOGRAPHIC,
185+
element: element1,
186+
defaultOptions: {
187+
orientation: Enums.OrientationAxis.AXIAL,
188+
background: <Types.Point3>[0.2, 0, 0.2],
189+
},
190+
},
191+
{
192+
viewportId: viewportIds[1],
193+
type: ViewportType.ORTHOGRAPHIC,
194+
element: element2,
195+
defaultOptions: {
196+
orientation: Enums.OrientationAxis.SAGITTAL,
197+
background: <Types.Point3>[0.2, 0, 0.2],
198+
},
199+
},
200+
];
201+
202+
renderingEngine.setViewports(viewportInputArray);
203+
204+
// Set the tool group on the viewports
205+
viewportIds.forEach((viewportId) =>
206+
toolGroup.addViewport(viewportId, renderingEngineId)
207+
);
208+
209+
// Define a volume in memory
210+
const volume = await volumeLoader.createAndCacheVolume(volumeId, {
211+
imageIds,
212+
});
213+
214+
// Set the volume to load
215+
volume.load();
216+
217+
setVolumesForViewports(renderingEngine, [{ volumeId }], viewportIds);
218+
219+
// Render the image
220+
renderingEngine.renderViewports(viewportIds);
221+
}
222+
223+
run();

0 commit comments

Comments
 (0)