UNPKG

@cornerstonejs/core

Version:
420 lines (419 loc) • 20.6 kB
import macro from '@kitware/vtk.js/macros.js'; import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray.js'; import { VtkDataTypes } from '@kitware/vtk.js/Common/Core/DataArray/Constants.js'; import vtkOpenGLVolumeMapper from '@kitware/vtk.js/Rendering/OpenGL/VolumeMapper.js'; import vtkOpenGLTexture from '@kitware/vtk.js/Rendering/OpenGL/Texture.js'; import { Filter } from '@kitware/vtk.js/Rendering/OpenGL/Texture/Constants.js'; import { getTransferFunctionsHash } from '@kitware/vtk.js/Rendering/OpenGL/RenderWindow/resourceSharingHelper.js'; import { Representation } from '@kitware/vtk.js/Rendering/Core/Property/Constants.js'; import { BlendMode } from '@kitware/vtk.js/Rendering/Core/VolumeMapper/Constants.js'; import { getCanUseNorm16Texture } from '../../init.js'; import canUseFloatOpacityTexture from './canUseFloatOpacityTexture.js'; function vtkStreamingOpenGLVolumeMapper(publicAPI, model) { model.classHierarchy.push('vtkStreamingOpenGLVolumeMapper'); const graphicsResourceReferenceCount = new Map(); function decreaseGraphicsResourceCount(openGLRenderWindow, coreObject) { if (!coreObject) { return; } const oldCount = graphicsResourceReferenceCount.get(coreObject) ?? 0; const newCount = oldCount - 1; if (newCount <= 0) { openGLRenderWindow.unregisterGraphicsResourceUser(coreObject, publicAPI); graphicsResourceReferenceCount.delete(coreObject); } else { graphicsResourceReferenceCount.set(coreObject, newCount); } } function increaseGraphicsResourceCount(openGLRenderWindow, coreObject) { if (!coreObject) { return; } const oldCount = graphicsResourceReferenceCount.get(coreObject) ?? 0; const newCount = oldCount + 1; graphicsResourceReferenceCount.set(coreObject, newCount); if (oldCount <= 0) { openGLRenderWindow.registerGraphicsResourceUser(coreObject, publicAPI); } } function replaceGraphicsResource(openGLRenderWindow, oldResourceCoreObject, newResourceCoreObject) { if (oldResourceCoreObject === newResourceCoreObject) { return; } decreaseGraphicsResourceCount(openGLRenderWindow, oldResourceCoreObject); increaseGraphicsResourceCount(openGLRenderWindow, newResourceCoreObject); } publicAPI.renderPiece = (ren, actor) => { publicAPI.invokeEvent({ type: 'StartEvent' }); model.renderable.update(); const numberOfInputs = model.renderable.getNumberOfInputPorts(); model.currentValidInputs = []; for (let inputIndex = 0; inputIndex < numberOfInputs; ++inputIndex) { const imageData = model.renderable.getInputData(inputIndex); if (imageData && !imageData.isDeleted()) { model.currentValidInputs.push({ imageData, inputIndex }); } } let newNumberOfLights = 0; if (model.currentValidInputs.length > 0) { const volumeProperties = actor.getProperties(); const firstValidInput = model.currentValidInputs[0]; const firstImageData = firstValidInput.imageData; const firstVolumeProperty = volumeProperties[firstValidInput.inputIndex]; if (firstVolumeProperty.getShade() && model.renderable.getBlendMode() === BlendMode.COMPOSITE_BLEND) { ren.getLights().forEach((light) => { if (light.getSwitch() > 0) { newNumberOfLights++; } }); } const numberOfValidInputs = model.currentValidInputs.length; const multiTexturePerVolumeEnabled = numberOfValidInputs > 1; const { numberOfComponents } = firstImageData.get('numberOfComponents'); model.numberOfComponents = multiTexturePerVolumeEnabled ? numberOfValidInputs : numberOfComponents; if (model.numberOfComponents > 1) { model.useIndependentComponents = firstVolumeProperty.getIndependentComponents(); } else { model.useIndependentComponents = false; } } if (newNumberOfLights !== model.numberOfLights) { model.numberOfLights = newNumberOfLights; publicAPI.modified(); } publicAPI.invokeEvent({ type: 'EndEvent' }); if (model.currentValidInputs.length === 0) { return; } publicAPI.renderPieceStart(ren, actor); publicAPI.renderPieceDraw(ren, actor); publicAPI.renderPieceFinish(ren, actor); }; publicAPI.buildBufferObjects = (ren, actor) => { if (!model.jitterTexture.getHandle()) { const jitterArray = new Float32Array(32 * 32); for (let i = 0; i < 32 * 32; ++i) { jitterArray[i] = Math.random(); } model.jitterTexture.setMinificationFilter(Filter.NEAREST); model.jitterTexture.setMagnificationFilter(Filter.NEAREST); model.jitterTexture.create2DFromRaw({ width: 32, height: 32, numComps: 1, dataType: VtkDataTypes.FLOAT, data: jitterArray, }); } const volumeProperties = actor.getProperties(); const firstValidInput = model.currentValidInputs[0]; const firstVolumeProperty = volumeProperties[firstValidInput.inputIndex]; const numberOfComponents = model.numberOfComponents; const useIndependentComps = model.useIndependentComponents; const numIComps = useIndependentComps ? numberOfComponents : 1; const opacityFunctions = []; for (let component = 0; component < numIComps; ++component) { opacityFunctions.push(firstVolumeProperty.getScalarOpacity(component)); } const opacityFuncHash = getTransferFunctionsHash(opacityFunctions, useIndependentComps, numIComps); const firstScalarOpacityFunc = firstVolumeProperty.getScalarOpacity(); const opTex = model._openGLRenderWindow.getGraphicsResourceForObject(firstScalarOpacityFunc); const reBuildOp = !opTex?.oglObject?.getHandle() || opTex.hash !== opacityFuncHash; if (reBuildOp) { const newOpacityTexture = vtkOpenGLTexture.newInstance(); newOpacityTexture.setOpenGLRenderWindow(model._openGLRenderWindow); let oWidth = model.renderable.getOpacityTextureWidth(); if (oWidth <= 0) { oWidth = model.context.getParameter(model.context.MAX_TEXTURE_SIZE); } const oSize = oWidth * 2 * numIComps; const ofTable = new Float32Array(oSize); const tmpTable = new Float32Array(oWidth); for (let c = 0; c < numIComps; ++c) { const ofun = firstVolumeProperty.getScalarOpacity(c); const opacityFactor = publicAPI.getCurrentSampleDistance(ren) / firstVolumeProperty.getScalarOpacityUnitDistance(c); const oRange = ofun.getRange(); ofun.getTable(oRange[0], oRange[1], oWidth, tmpTable, 1); for (let i = 0; i < oWidth; ++i) { ofTable[c * oWidth * 2 + i] = 1.0 - (1.0 - tmpTable[i]) ** opacityFactor; ofTable[c * oWidth * 2 + i + oWidth] = ofTable[c * oWidth * 2 + i]; } } newOpacityTexture.resetFormatAndType(); newOpacityTexture.setMinificationFilter(Filter.LINEAR); newOpacityTexture.setMagnificationFilter(Filter.LINEAR); if (canUseFloatOpacityTexture(model._openGLRenderWindow, model.context)) { newOpacityTexture.create2DFromRaw({ width: oWidth, height: 2 * numIComps, numComps: 1, dataType: VtkDataTypes.FLOAT, data: ofTable, }); } else { const oTable = new Uint8ClampedArray(oSize); for (let i = 0; i < oSize; ++i) { oTable[i] = 255.0 * ofTable[i]; } newOpacityTexture.create2DFromRaw({ width: oWidth, height: 2 * numIComps, numComps: 1, dataType: VtkDataTypes.UNSIGNED_CHAR, data: oTable, }); } if (firstScalarOpacityFunc) { model._openGLRenderWindow.setGraphicsResourceForObject(firstScalarOpacityFunc, newOpacityTexture, opacityFuncHash); } model.opacityTexture = newOpacityTexture; } else { model.opacityTexture = opTex.oglObject; } replaceGraphicsResource(model._openGLRenderWindow, model._opacityTextureCore, firstScalarOpacityFunc); model._opacityTextureCore = firstScalarOpacityFunc; const colorTransferFunctions = []; for (let component = 0; component < numIComps; ++component) { colorTransferFunctions.push(firstVolumeProperty.getRGBTransferFunction(component)); } const colorFuncHash = getTransferFunctionsHash(colorTransferFunctions, useIndependentComps, numIComps); const firstColorTransferFunc = firstVolumeProperty.getRGBTransferFunction(); const cTex = model._openGLRenderWindow.getGraphicsResourceForObject(firstColorTransferFunc); const reBuildC = !cTex?.oglObject?.getHandle() || cTex?.hash !== colorFuncHash; if (reBuildC) { const newColorTexture = vtkOpenGLTexture.newInstance(); newColorTexture.setOpenGLRenderWindow(model._openGLRenderWindow); let cWidth = model.renderable.getColorTextureWidth(); if (cWidth <= 0) { cWidth = model.context.getParameter(model.context.MAX_TEXTURE_SIZE); } const cSize = cWidth * 2 * numIComps * 3; const cTable = new Uint8ClampedArray(cSize); const tmpTable = new Float32Array(cWidth * 3); for (let c = 0; c < numIComps; ++c) { const cfun = firstVolumeProperty.getRGBTransferFunction(c); const cRange = cfun.getRange(); cfun.getTable(cRange[0], cRange[1], cWidth, tmpTable, 1); for (let i = 0; i < cWidth * 3; ++i) { cTable[c * cWidth * 6 + i] = 255.0 * tmpTable[i]; cTable[c * cWidth * 6 + i + cWidth * 3] = 255.0 * tmpTable[i]; } } newColorTexture.resetFormatAndType(); newColorTexture.setMinificationFilter(Filter.LINEAR); newColorTexture.setMagnificationFilter(Filter.LINEAR); newColorTexture.create2DFromRaw({ width: cWidth, height: 2 * numIComps, numComps: 3, dataType: VtkDataTypes.UNSIGNED_CHAR, data: cTable, }); model._openGLRenderWindow.setGraphicsResourceForObject(firstColorTransferFunc, newColorTexture, colorFuncHash); model.colorTexture = newColorTexture; } else { model.colorTexture = cTex.oglObject; } replaceGraphicsResource(model._openGLRenderWindow, model._colorTextureCore, firstColorTransferFunc); model._colorTextureCore = firstColorTransferFunc; model.currentValidInputs.forEach(({ imageData, inputIndex: _inputIndex }, component) => { if (!model.scalarTextures) { model.scalarTextures = []; } if (!model.scalarTextures[component]) { console.warn(`ScalarTexture for component ${component} not initialized, skipping.`); return; } const currentTexture = model.scalarTextures[component]; const toString = `${imageData.getMTime()}-${currentTexture.getMTime()}`; if (!model.scalarTextureStrings) { model.scalarTextureStrings = []; } if (model.scalarTextureStrings[component] !== toString) { const dims = imageData.getDimensions(); currentTexture.setOpenGLRenderWindow(model._openGLRenderWindow); currentTexture.enableUseHalfFloat(false); const previousTextureParameters = currentTexture.getTextureParameters(); const dataType = imageData.get('dataType').dataType; const textureNumberOfComponents = imageData.getPointData()?.getScalars()?.getNumberOfComponents?.() ?? imageData.get('numberOfComponents').numberOfComponents ?? 1; let shouldReset = true; if (previousTextureParameters?.dataType === dataType) { if (previousTextureParameters?.width === dims[0]) { if (previousTextureParameters?.height === dims[1]) { if (previousTextureParameters?.depth === dims[2]) { if (previousTextureParameters?.numberOfComponents === textureNumberOfComponents) { shouldReset = false; } } } } } if (shouldReset) { const norm16Ext = model.context.getExtension('EXT_texture_norm16'); currentTexture.setOglNorm16Ext(getCanUseNorm16Texture() ? norm16Ext : null); currentTexture.resetFormatAndType(); currentTexture.setTextureParameters({ width: dims[0], height: dims[1], depth: dims[2], numberOfComponents: textureNumberOfComponents, dataType, }); currentTexture.create3DFromRaw({ width: dims[0], height: dims[1], depth: dims[2], numComps: textureNumberOfComponents, dataType, data: null, }); currentTexture.update3DFromRaw(); } else { currentTexture.deactivate(); currentTexture.update3DFromRaw(); } model.scalarTextureStrings[component] = toString; } if (!model._scalarTexturesCore) { model._scalarTexturesCore = []; } }); const labelOutlineThicknessArray = firstVolumeProperty.getLabelOutlineThickness(); const lTex = model._openGLRenderWindow.getGraphicsResourceForObject(labelOutlineThicknessArray); const labelOutlineThicknessHash = labelOutlineThicknessArray.join('-'); const reBuildL = !lTex?.oglObject?.getHandle() || lTex?.hash !== labelOutlineThicknessHash; if (reBuildL) { const newLabelOutlineThicknessTexture = vtkOpenGLTexture.newInstance(); newLabelOutlineThicknessTexture.setOpenGLRenderWindow(model._openGLRenderWindow); let lWidth = model.renderable.getLabelOutlineTextureWidth(); if (lWidth <= 0) { lWidth = model.context.getParameter(model.context.MAX_TEXTURE_SIZE); } const lHeight = 1; const lSize = lWidth * lHeight; const lTable = new Uint8Array(lSize); for (let i = 0; i < lWidth; ++i) { const thickness = typeof labelOutlineThicknessArray[i] !== 'undefined' ? labelOutlineThicknessArray[i] : labelOutlineThicknessArray[0]; lTable[i] = thickness; } newLabelOutlineThicknessTexture.resetFormatAndType(); newLabelOutlineThicknessTexture.setMinificationFilter(Filter.NEAREST); newLabelOutlineThicknessTexture.setMagnificationFilter(Filter.NEAREST); newLabelOutlineThicknessTexture.create2DFromRaw({ width: lWidth, height: lHeight, numComps: 1, dataType: VtkDataTypes.UNSIGNED_CHAR, data: lTable, }); if (labelOutlineThicknessArray) { model._openGLRenderWindow.setGraphicsResourceForObject(labelOutlineThicknessArray, newLabelOutlineThicknessTexture, labelOutlineThicknessHash); } model.labelOutlineThicknessTexture = newLabelOutlineThicknessTexture; } else { model.labelOutlineThicknessTexture = lTex.oglObject; } replaceGraphicsResource(model._openGLRenderWindow, model._labelOutlineThicknessTextureCore, labelOutlineThicknessArray); model._labelOutlineThicknessTextureCore = labelOutlineThicknessArray; if (!model.tris.getCABO().getElementCount()) { const ptsArray = new Float32Array(12); for (let i = 0; i < 4; i++) { ptsArray[i * 3] = (i % 2) * 2 - 1.0; ptsArray[i * 3 + 1] = i > 1 ? 1.0 : -1.0; ptsArray[i * 3 + 2] = -1.0; } const cellArray = new Uint16Array(8); cellArray[0] = 3; cellArray[1] = 0; cellArray[2] = 1; cellArray[3] = 3; cellArray[4] = 3; cellArray[5] = 0; cellArray[6] = 3; cellArray[7] = 2; const points = vtkDataArray.newInstance({ numberOfComponents: 3, values: ptsArray, }); points.setName('points'); const cells = vtkDataArray.newInstance({ numberOfComponents: 1, values: cellArray, }); model.tris.getCABO().createVBO(cells, 'polys', Representation.SURFACE, { points, cellOffset: 0, }); } model.VBOBuildTime.modified(); }; publicAPI.getNeedToRebuildBufferObjects = (ren, actor) => { if (model.VBOBuildTime.getMTime() < publicAPI.getMTime() || model.VBOBuildTime.getMTime() < actor.getMTime() || model.VBOBuildTime.getMTime() < model.renderable.getMTime() || model.VBOBuildTime.getMTime() < actor.getProperty().getMTime() || model.VBOBuildTime.getMTime() < model.colorTexture?.getMTime() || model.VBOBuildTime.getMTime() < model.labelOutlineThicknessTexture?.getMTime() || !model.colorTexture?.getHandle() || !model.labelOutlineThicknessTexture?.getHandle()) { return true; } if (model.scalarTextures && model.scalarTextures.length > 0) { for (let i = 0; i < model.scalarTextures.length; i++) { const texture = model.scalarTextures[i]; if (texture && (model.VBOBuildTime.getMTime() < texture.getMTime() || !texture.getHandle())) { return true; } } } if (model.currentValidInputs && model.currentValidInputs.length > 0) { for (let i = 0; i < model.currentValidInputs.length; i++) { const input = model.currentValidInputs[i]; if (input && input.imageData && model.VBOBuildTime.getMTime() < input.imageData.getMTime()) { return true; } } } return false; }; } const DEFAULT_VALUES = {}; export function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues); vtkOpenGLVolumeMapper.extend(publicAPI, model, initialValues); if (initialValues.scalarTexture) { model.scalarTextures = [initialValues.scalarTexture]; } else { model.scalarTextures = []; } model.scalarTextureStrings = []; model._scalarTexturesCore = []; model.previousState = {}; vtkStreamingOpenGLVolumeMapper(publicAPI, model); } export const newInstance = macro.newInstance(extend, 'vtkStreamingOpenGLVolumeMapper'); export default { newInstance, extend };