UNPKG

@kitware/vtk.js

Version:

Visualization Toolkit for the Web

309 lines (279 loc) 11 kB
import { m as macro } from '../../macros2.js'; import vtkDataArray from '../../Common/Core/DataArray.js'; import vtkEdgeLocator from '../../Common/DataModel/EdgeLocator.js'; import vtkPolyData from '../../Common/DataModel/PolyData.js'; import { l as normalize } from '../../Common/Core/Math/index.js'; import vtkCaseTable from './ImageMarchingCubes/caseTable.js'; const { vtkErrorMacro, vtkDebugMacro } = macro; // ---------------------------------------------------------------------------- // vtkImageMarchingCubes methods // ---------------------------------------------------------------------------- function vtkImageMarchingCubes(publicAPI, model) { // Set our className model.classHierarchy.push('vtkImageMarchingCubes'); const ids = []; const voxelScalars = []; const voxelGradients = []; const voxelPts = []; const edgeLocator = vtkEdgeLocator.newInstance(); // Retrieve scalars and voxel coordinates. i-j-k is origin of voxel. publicAPI.getVoxelScalars = (i, j, k, slice, dims, origin, spacing, s) => { // First get the indices for the voxel ids[0] = k * slice + j * dims[0] + i; // i, j, k ids[1] = ids[0] + 1; // i+1, j, k ids[2] = ids[0] + dims[0]; // i, j+1, k ids[3] = ids[2] + 1; // i+1, j+1, k ids[4] = ids[0] + slice; // i, j, k+1 ids[5] = ids[4] + 1; // i+1, j, k+1 ids[6] = ids[4] + dims[0]; // i, j+1, k+1 ids[7] = ids[6] + 1; // i+1, j+1, k+1 // Now retrieve the scalars for (let ii = 0; ii < 8; ++ii) { voxelScalars[ii] = s[ids[ii]]; } }; // Retrieve voxel coordinates. i-j-k is origin of voxel. publicAPI.getVoxelPoints = (i, j, k, origin, spacing) => { // (i,i+1),(j,j+1),(k,k+1) - i varies fastest; then j; then k voxelPts[0] = origin[0] + i * spacing[0]; // 0 voxelPts[1] = origin[1] + j * spacing[1]; voxelPts[2] = origin[2] + k * spacing[2]; voxelPts[3] = voxelPts[0] + spacing[0]; // 1 voxelPts[4] = voxelPts[1]; voxelPts[5] = voxelPts[2]; voxelPts[6] = voxelPts[0]; // 2 voxelPts[7] = voxelPts[1] + spacing[1]; voxelPts[8] = voxelPts[2]; voxelPts[9] = voxelPts[3]; // 3 voxelPts[10] = voxelPts[7]; voxelPts[11] = voxelPts[2]; voxelPts[12] = voxelPts[0]; // 4 voxelPts[13] = voxelPts[1]; voxelPts[14] = voxelPts[2] + spacing[2]; voxelPts[15] = voxelPts[3]; // 5 voxelPts[16] = voxelPts[1]; voxelPts[17] = voxelPts[14]; voxelPts[18] = voxelPts[0]; // 6 voxelPts[19] = voxelPts[7]; voxelPts[20] = voxelPts[14]; voxelPts[21] = voxelPts[3]; // 7 voxelPts[22] = voxelPts[7]; voxelPts[23] = voxelPts[14]; }; // Compute point gradient at i-j-k location publicAPI.getPointGradient = (i, j, k, dims, slice, spacing, s, g) => { let sp; let sm; // x-direction if (i === 0) { sp = s[i + 1 + j * dims[0] + k * slice]; sm = s[i + j * dims[0] + k * slice]; g[0] = (sm - sp) / spacing[0]; } else if (i === dims[0] - 1) { sp = s[i + j * dims[0] + k * slice]; sm = s[i - 1 + j * dims[0] + k * slice]; g[0] = (sm - sp) / spacing[0]; } else { sp = s[i + 1 + j * dims[0] + k * slice]; sm = s[i - 1 + j * dims[0] + k * slice]; g[0] = 0.5 * (sm - sp) / spacing[0]; } // y-direction if (j === 0) { sp = s[i + (j + 1) * dims[0] + k * slice]; sm = s[i + j * dims[0] + k * slice]; g[1] = (sm - sp) / spacing[1]; } else if (j === dims[1] - 1) { sp = s[i + j * dims[0] + k * slice]; sm = s[i + (j - 1) * dims[0] + k * slice]; g[1] = (sm - sp) / spacing[1]; } else { sp = s[i + (j + 1) * dims[0] + k * slice]; sm = s[i + (j - 1) * dims[0] + k * slice]; g[1] = 0.5 * (sm - sp) / spacing[1]; } // z-direction if (k === 0) { sp = s[i + j * dims[0] + (k + 1) * slice]; sm = s[i + j * dims[0] + k * slice]; g[2] = (sm - sp) / spacing[2]; } else if (k === dims[2] - 1) { sp = s[i + j * dims[0] + k * slice]; sm = s[i + j * dims[0] + (k - 1) * slice]; g[2] = (sm - sp) / spacing[2]; } else { sp = s[i + j * dims[0] + (k + 1) * slice]; sm = s[i + j * dims[0] + (k - 1) * slice]; g[2] = 0.5 * (sm - sp) / spacing[2]; } }; // Compute voxel gradient values. I-j-k is origin point of voxel. publicAPI.getVoxelGradients = (i, j, k, dims, slice, spacing, scalars) => { const g = []; publicAPI.getPointGradient(i, j, k, dims, slice, spacing, scalars, g); voxelGradients[0] = g[0]; voxelGradients[1] = g[1]; voxelGradients[2] = g[2]; publicAPI.getPointGradient(i + 1, j, k, dims, slice, spacing, scalars, g); voxelGradients[3] = g[0]; voxelGradients[4] = g[1]; voxelGradients[5] = g[2]; publicAPI.getPointGradient(i, j + 1, k, dims, slice, spacing, scalars, g); voxelGradients[6] = g[0]; voxelGradients[7] = g[1]; voxelGradients[8] = g[2]; publicAPI.getPointGradient(i + 1, j + 1, k, dims, slice, spacing, scalars, g); voxelGradients[9] = g[0]; voxelGradients[10] = g[1]; voxelGradients[11] = g[2]; publicAPI.getPointGradient(i, j, k + 1, dims, slice, spacing, scalars, g); voxelGradients[12] = g[0]; voxelGradients[13] = g[1]; voxelGradients[14] = g[2]; publicAPI.getPointGradient(i + 1, j, k + 1, dims, slice, spacing, scalars, g); voxelGradients[15] = g[0]; voxelGradients[16] = g[1]; voxelGradients[17] = g[2]; publicAPI.getPointGradient(i, j + 1, k + 1, dims, slice, spacing, scalars, g); voxelGradients[18] = g[0]; voxelGradients[19] = g[1]; voxelGradients[20] = g[2]; publicAPI.getPointGradient(i + 1, j + 1, k + 1, dims, slice, spacing, scalars, g); voxelGradients[21] = g[0]; voxelGradients[22] = g[1]; voxelGradients[23] = g[2]; }; publicAPI.produceTriangles = (cVal, i, j, k, extent, slice, dims, origin, spacing, scalars, points, tris, normals) => { const CASE_MASK = [1, 2, 4, 8, 16, 32, 64, 128]; const VERT_MAP = [0, 1, 3, 2, 4, 5, 7, 6]; const xyz = []; const n = []; let pId; publicAPI.getVoxelScalars(i, j, k, slice, dims, origin, spacing, scalars); let index = 0; for (let idx = 0; idx < 8; idx++) { if (voxelScalars[VERT_MAP[idx]] >= cVal) { index |= CASE_MASK[idx]; // eslint-disable-line no-bitwise } } const voxelTris = vtkCaseTable.getCase(index); if (voxelTris[0] < 0) { return; // don't get the voxel coordinates, nothing to do } publicAPI.getVoxelPoints(i + extent[0], j + extent[2], k + extent[4], origin, spacing); if (model.computeNormals) { publicAPI.getVoxelGradients(i, j, k, dims, slice, spacing, scalars); } for (let idx = 0; voxelTris[idx] >= 0; idx += 3) { tris.push(3); for (let eid = 0; eid < 3; eid++) { const edgeVerts = vtkCaseTable.getEdge(voxelTris[idx + eid]); pId = undefined; if (model.mergePoints) { pId = edgeLocator.isInsertedEdge(ids[edgeVerts[0]], ids[edgeVerts[1]])?.value; } if (pId === undefined) { const t = (cVal - voxelScalars[edgeVerts[0]]) / (voxelScalars[edgeVerts[1]] - voxelScalars[edgeVerts[0]]); const x0 = voxelPts.slice(edgeVerts[0] * 3, (edgeVerts[0] + 1) * 3); const x1 = voxelPts.slice(edgeVerts[1] * 3, (edgeVerts[1] + 1) * 3); xyz[0] = x0[0] + t * (x1[0] - x0[0]); xyz[1] = x0[1] + t * (x1[1] - x0[1]); xyz[2] = x0[2] + t * (x1[2] - x0[2]); pId = points.length / 3; points.push(xyz[0], xyz[1], xyz[2]); if (model.computeNormals) { const n0 = voxelGradients.slice(edgeVerts[0] * 3, (edgeVerts[0] + 1) * 3); const n1 = voxelGradients.slice(edgeVerts[1] * 3, (edgeVerts[1] + 1) * 3); n[0] = n0[0] + t * (n1[0] - n0[0]); n[1] = n0[1] + t * (n1[1] - n0[1]); n[2] = n0[2] + t * (n1[2] - n0[2]); normalize(n); normals.push(n[0], n[1], n[2]); } if (model.mergePoints) { edgeLocator.insertEdge(ids[edgeVerts[0]], ids[edgeVerts[1]], pId); } } tris.push(pId); } } }; publicAPI.requestData = (inData, outData) => { // implement requestData const input = inData[0]; if (!input) { vtkErrorMacro('Invalid or missing input'); return; } console.time('mcubes'); // Retrieve output and volume data const origin = input.getOrigin(); const spacing = input.getSpacing(); const dims = input.getDimensions(); const s = input.getPointData().getScalars().getData(); // Points - dynamic array const pBuffer = []; // Cells - dynamic array const tBuffer = []; // Normals const nBuffer = []; // Loop over all voxels, determine case and process const extent = input.getExtent(); const slice = dims[0] * dims[1]; for (let k = 0; k < dims[2] - 1; ++k) { for (let j = 0; j < dims[1] - 1; ++j) { for (let i = 0; i < dims[0] - 1; ++i) { publicAPI.produceTriangles(model.contourValue, i, j, k, extent, slice, dims, origin, spacing, s, pBuffer, tBuffer, nBuffer); } } } edgeLocator.initialize(); // Update output const polydata = outData[0]?.initialize() || vtkPolyData.newInstance(); polydata.getPoints().setData(new Float32Array(pBuffer), 3); polydata.getPolys().setData(new Uint32Array(tBuffer)); if (model.computeNormals) { const nData = new Float32Array(nBuffer); const normals = vtkDataArray.newInstance({ numberOfComponents: 3, values: nData, name: 'Normals' }); polydata.getPointData().setNormals(normals); } outData[0] = polydata; vtkDebugMacro('Produced output'); console.timeEnd('mcubes'); }; } // ---------------------------------------------------------------------------- // Object factory // ---------------------------------------------------------------------------- const DEFAULT_VALUES = { contourValue: 0, computeNormals: false, mergePoints: false }; // ---------------------------------------------------------------------------- function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues); // Make this a VTK object macro.obj(publicAPI, model); // Also make it an algorithm with one input and one output macro.algo(publicAPI, model, 1, 1); macro.setGet(publicAPI, model, ['contourValue', 'computeNormals', 'mergePoints']); // Object specific methods macro.algo(publicAPI, model, 1, 1); vtkImageMarchingCubes(publicAPI, model); } // ---------------------------------------------------------------------------- const newInstance = macro.newInstance(extend, 'vtkImageMarchingCubes'); // ---------------------------------------------------------------------------- var vtkImageMarchingCubes$1 = { newInstance, extend }; export { vtkImageMarchingCubes$1 as default, extend, newInstance };