UNPKG

@cornerstonejs/core

Version:
421 lines (420 loc) • 20.4 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 { Representation } from '@kitware/vtk.js/Rendering/Core/Property/Constants.js'; import vtkOpenGLImageResliceMapper from '@kitware/vtk.js/Rendering/OpenGL/ImageResliceMapper.js'; import { getImageDataHash, getTransferFunctionsHash, } from '@kitware/vtk.js/Rendering/OpenGL/RenderWindow/resourceSharingHelper.js'; import vtkOpenGLTexture from '@kitware/vtk.js/Rendering/OpenGL/Texture.js'; import { getCanUseNorm16Texture } from '../../init.js'; const { vtkErrorMacro } = macro; function findLabelOutlineProperties(actor, currentValidInputs) { const labelmapProperties = []; for (let i = 0; i < currentValidInputs.length; i++) { const property = actor.getProperty(currentValidInputs[i].inputIndex); if (property?.getUseLabelOutline()) { labelmapProperties.push({ property, arrayIndex: i, }); } } return labelmapProperties; } function vtkStreamingOpenGLImageResliceMapper(publicAPI, model) { model.classHierarchy.push('vtkStreamingOpenGLImageResliceMapper'); 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 }); } } const numberOfValidInputs = model.currentValidInputs.length; if (numberOfValidInputs <= 0) { vtkErrorMacro('No input!'); return; } model.labelOutlineProperties = findLabelOutlineProperties(actor, model.currentValidInputs); const firstImageData = model.currentValidInputs[0].imageData; const firstScalars = firstImageData.getPointData().getScalars(); const { numberOfComponents } = firstImageData.get('numberOfComponents') || { numberOfComponents: undefined, }; model.multiTexturePerVolumeEnabled = numberOfValidInputs > 1; model.numberOfComponents = model.multiTexturePerVolumeEnabled ? numberOfValidInputs : typeof numberOfComponents === 'number' ? numberOfComponents : (firstScalars?.getNumberOfComponents?.() ?? 1); publicAPI.updateResliceGeometry(); publicAPI.renderPieceStart(ren, actor); publicAPI.renderPieceDraw(ren, actor); publicAPI.renderPieceFinish(ren, actor); publicAPI.invokeEvent({ type: 'EndEvent' }); }; publicAPI.getNeedToRebuildBufferObjects = (ren, actor) => { const useLabelOutline = model.labelOutlineProperties.length > 0; if (model.VBOBuildTime.getMTime() < publicAPI.getMTime() || model.VBOBuildTime.getMTime() < actor.getMTime() || model.VBOBuildTime.getMTime() < model.renderable.getMTime() || model.VBOBuildTime.getMTime() < actor.getProperty(model.currentValidInputs[0].inputIndex)?.getMTime() || model.currentValidInputs.some(({ imageData }) => model.VBOBuildTime.getMTime() < imageData.getMTime()) || model.VBOBuildTime.getMTime() < model.resliceGeom.getMTime() || model.scalarTextures.length !== model.currentValidInputs.length || !model.colorTexture?.getHandle() || !model.pwfTexture?.getHandle() || (useLabelOutline && (!model.labelOutlineThicknessTexture?.getHandle() || !model.labelOutlineOpacityTexture?.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; } } } return false; }; publicAPI.buildBufferObjects = (ren, actor) => { const actorProperties = actor.getProperties(); model.currentValidInputs.forEach(({ imageData }, component) => { if (!model.scalarTextures) { model.scalarTextures = []; } if (!model.scalarTextures[component]) { model.scalarTextures[component] = vtkOpenGLTexture.newInstance(); } const currentTexture = model.scalarTextures[component]; const actorProperty = actorProperties[component]; const updatedExtents = actorProperty.getUpdatedExtents(); const hasUpdatedExtents = !!updatedExtents.length; const canStream = currentTexture && typeof currentTexture.hasUpdatedFrames === 'function'; if (canStream) { const dims = imageData.getDimensions(); const { dataType } = imageData.get('dataType'); const numComps = model.numberOfComponents; currentTexture.setOpenGLRenderWindow(model._openGLRenderWindow); currentTexture.enableUseHalfFloat?.(false); const previousTextureParameters = currentTexture.getTextureParameters?.(); let shouldReset = true; if (previousTextureParameters?.dataType === dataType) { if (previousTextureParameters?.width === dims[0] && previousTextureParameters?.height === dims[1] && previousTextureParameters?.depth === dims[2]) { 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: numComps, dataType, }); currentTexture.create3DFromRaw({ width: dims[0], height: dims[1], depth: dims[2], numComps, dataType, data: null, }); currentTexture.update3DFromRaw(); } else { currentTexture.deactivate?.(); currentTexture.update3DFromRaw(); } if (hasUpdatedExtents) { actorProperty.setUpdatedExtents([]); } return; } const scalars = imageData.getPointData().getScalars(); if (!scalars) { vtkErrorMacro('No scalars available for non-streaming reslice mapper'); return; } const tex = model._openGLRenderWindow.getGraphicsResourceForObject(scalars); const scalarsHash = getImageDataHash(imageData, scalars); const reBuildTex = !tex?.oglObject?.getHandle() || tex?.hash !== scalarsHash; if (reBuildTex && !hasUpdatedExtents) { const newScalarTexture = vtkOpenGLTexture.newInstance(); newScalarTexture.setOpenGLRenderWindow(model._openGLRenderWindow); const dims = imageData.getDimensions(); newScalarTexture.setOglNorm16Ext(model.context.getExtension('EXT_texture_norm16')); newScalarTexture.resetFormatAndType(); newScalarTexture.create3DFilterableFromDataArray({ width: dims[0], height: dims[1], depth: dims[2], dataArray: scalars, }); model._openGLRenderWindow.setGraphicsResourceForObject(scalars, newScalarTexture, scalarsHash); model.scalarTextures[component] = newScalarTexture; } else { model.scalarTextures[component] = tex.oglObject; } if (hasUpdatedExtents) { actorProperty.setUpdatedExtents([]); const dims = imageData.getDimensions(); model.scalarTextures[component].create3DFilterableFromDataArray({ width: dims[0], height: dims[1], depth: dims[2], dataArray: scalars, updatedExtents, }); } replaceGraphicsResource(model._openGLRenderWindow, model._scalarTexturesCore[component], scalars); model._scalarTexturesCore[component] = scalars; }); const firstValidInput = model.currentValidInputs[0]; const firstActorProperty = actorProperties[firstValidInput.inputIndex]; const iComps = firstActorProperty.getIndependentComponents(); const numIComps = iComps ? model.numberOfComponents : 1; const textureHeight = iComps ? 2 * numIComps : 1; const colorTransferFunctions = []; for (let component = 0; component < numIComps; ++component) { colorTransferFunctions.push(firstActorProperty.getRGBTransferFunction(component)); } const colorFuncHash = getTransferFunctionsHash(colorTransferFunctions, iComps, numIComps); const firstColorTransferFunc = firstActorProperty.getRGBTransferFunction(); const cTex = model._openGLRenderWindow.getGraphicsResourceForObject(firstColorTransferFunc); const reBuildC = !cTex?.oglObject?.getHandle() || cTex?.hash !== colorFuncHash; if (reBuildC) { let cWidth = model.renderable.getColorTextureWidth(); if (cWidth <= 0) { cWidth = model.context.getParameter(model.context.MAX_TEXTURE_SIZE); } const cSize = cWidth * textureHeight * 3; const cTable = new Uint8ClampedArray(cSize); const newColorTexture = vtkOpenGLTexture.newInstance(); newColorTexture.setOpenGLRenderWindow(model._openGLRenderWindow); if (firstColorTransferFunc) { const tmpTable = new Float32Array(cWidth * 3); for (let c = 0; c < numIComps; c++) { const cfun = firstActorProperty.getRGBTransferFunction(c); const cRange = cfun.getRange(); cfun.getTable(cRange[0], cRange[1], cWidth, tmpTable, 1); if (iComps) { 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]; } } else { for (let i = 0; i < cWidth * 3; i++) { cTable[c * cWidth * 3 + i] = 255.0 * tmpTable[i]; } } } newColorTexture.resetFormatAndType(); newColorTexture.create2DFromRaw({ width: cWidth, height: textureHeight, numComps: 3, dataType: VtkDataTypes.UNSIGNED_CHAR, data: cTable, }); } else { for (let column = 0; column < cWidth * 3; ++column) { const opacity = (255.0 * column) / ((cWidth - 1) * 3); for (let row = 0; row < textureHeight; ++row) { cTable[row * cWidth * 3 + column + 0] = opacity; cTable[row * cWidth * 3 + column + 1] = opacity; cTable[row * cWidth * 3 + column + 2] = opacity; } } newColorTexture.resetFormatAndType(); newColorTexture.create2DFromRaw({ width: cWidth, height: 1, numComps: 3, dataType: VtkDataTypes.UNSIGNED_CHAR, data: cTable, }); } if (firstColorTransferFunc) { model._openGLRenderWindow.setGraphicsResourceForObject(firstColorTransferFunc, newColorTexture, colorFuncHash); } model.colorTexture = newColorTexture; } else { model.colorTexture = cTex.oglObject; } replaceGraphicsResource(model._openGLRenderWindow, model._colorTextureCore, firstColorTransferFunc); model._colorTextureCore = firstColorTransferFunc; const opacityFunctions = []; for (let component = 0; component < numIComps; ++component) { opacityFunctions.push(firstActorProperty.getPiecewiseFunction(component)); } const opacityFuncHash = getTransferFunctionsHash(opacityFunctions, iComps, numIComps); const firstPwFunc = firstActorProperty.getPiecewiseFunction(); const pwfTex = model._openGLRenderWindow.getGraphicsResourceForObject(firstPwFunc); const reBuildPwf = !pwfTex?.oglObject?.getHandle() || pwfTex?.hash !== opacityFuncHash; if (reBuildPwf) { let pwfWidth = model.renderable.getOpacityTextureWidth(); if (pwfWidth <= 0) { pwfWidth = model.context.getParameter(model.context.MAX_TEXTURE_SIZE); } const pwfSize = pwfWidth * textureHeight; const pwfTable = new Uint8ClampedArray(pwfSize); const newOpacityTexture = vtkOpenGLTexture.newInstance(); newOpacityTexture.setOpenGLRenderWindow(model._openGLRenderWindow); if (firstPwFunc) { const pwfFloatTable = new Float32Array(pwfSize); const tmpTable = new Float32Array(pwfWidth); for (let c = 0; c < numIComps; ++c) { const pwfun = firstActorProperty.getPiecewiseFunction(c); if (pwfun === null) { pwfFloatTable.fill(1.0); } else { const pwfRange = pwfun.getRange(); pwfun.getTable(pwfRange[0], pwfRange[1], pwfWidth, tmpTable, 1); if (iComps) { for (let i = 0; i < pwfWidth; i++) { pwfFloatTable[c * pwfWidth * 2 + i] = tmpTable[i]; pwfFloatTable[c * pwfWidth * 2 + i + pwfWidth] = tmpTable[i]; } } else { for (let i = 0; i < pwfWidth; i++) { pwfFloatTable[i] = tmpTable[i]; } } } } newOpacityTexture.resetFormatAndType(); newOpacityTexture.create2DFromRaw({ width: pwfWidth, height: textureHeight, numComps: 1, dataType: VtkDataTypes.FLOAT, data: pwfFloatTable, }); } else { pwfTable.fill(255.0); newOpacityTexture.resetFormatAndType(); newOpacityTexture.create2DFromRaw({ width: pwfWidth, height: textureHeight, numComps: 1, dataType: VtkDataTypes.UNSIGNED_CHAR, data: pwfTable, }); } if (firstPwFunc) { model._openGLRenderWindow.setGraphicsResourceForObject(firstPwFunc, newOpacityTexture, opacityFuncHash); } model.pwfTexture = newOpacityTexture; } else { model.pwfTexture = pwfTex.oglObject; } replaceGraphicsResource(model._openGLRenderWindow, model._pwfTextureCore, firstPwFunc); model._pwfTextureCore = firstPwFunc; if (model.labelOutlineProperties.length > 0) { publicAPI.updateLabelOutlineThicknessTexture(model.labelOutlineProperties); publicAPI.updateLabelOutlineOpacityTexture(model.labelOutlineProperties); } const vboString = `${model.resliceGeom.getMTime()}A${model.renderable.getSlabThickness()}`; if (!model.tris.getCABO().getElementCount() || model.VBOBuildString !== vboString) { const points = vtkDataArray.newInstance({ numberOfComponents: 3, values: model.resliceGeom.getPoints().getData(), }); points.setName('points'); const cells = vtkDataArray.newInstance({ numberOfComponents: 1, values: model.resliceGeom.getPolys().getData(), }); const options = { points, cellOffset: 0, }; if (model.renderable.getSlabThickness() > 0.0) { const normals = model.resliceGeom.getPointData().getNormals(); if (!normals) { vtkErrorMacro('Slab mode requested without normals'); } else { options.normals = normals; } } model.tris .getCABO() .createVBO(cells, 'polys', Representation.SURFACE, options); } model.VBOBuildString = vboString; model.VBOBuildTime.modified(); }; } const DEFAULT_VALUES = {}; export function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues); vtkOpenGLImageResliceMapper.extend(publicAPI, model, initialValues); if (initialValues.scalarTexture) { model.scalarTextures = [initialValues.scalarTexture]; } else { model.scalarTextures = []; } model.scalarTextureStrings = []; model._scalarTexturesCore = []; vtkStreamingOpenGLImageResliceMapper(publicAPI, model); } export const newInstance = macro.newInstance(extend, 'vtkStreamingOpenGLImageResliceMapper'); export default { newInstance, extend };