@cornerstonejs/core
Version:
Cornerstone3D Core
420 lines (419 loc) • 20.6 kB
JavaScript
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 };