@kitware/vtk.js
Version:
Visualization Toolkit for the Web
391 lines (311 loc) • 12.7 kB
JavaScript
import _slicedToArray from '@babel/runtime/helpers/slicedToArray';
import { vec3 } from 'gl-matrix';
import WebworkerPromise from 'webworker-promise';
import macro from '../../macros.js';
import vtkImageData from '../../Common/DataModel/ImageData.js';
import vtkDataArray from '../../Common/Core/DataArray.js';
import vtkPolygon from '../../Common/DataModel/Polygon.js';
import { W as WorkerFactory } from '../../_virtual/rollup-plugin-worker-loader__module_Sources/Filters/General/PaintFilter/PaintFilter.worker.js';
var vtkErrorMacro = macro.vtkErrorMacro; // ----------------------------------------------------------------------------
// vtkPaintFilter methods
// ----------------------------------------------------------------------------
function vtkPaintFilter(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkPaintFilter');
var worker = null;
var workerPromise = null;
var history = {}; // --------------------------------------------------------------------------
function resetHistory() {
history.index = -1;
history.snapshots = [];
history.labels = [];
}
function pushToHistory(snapshot, label) {
// Clear any "redo" info
var spliceIndex = history.index + 1;
var spliceLength = history.snapshots.length - history.index;
history.snapshots.splice(spliceIndex, spliceLength);
history.labels.splice(spliceIndex, spliceLength); // Push new snapshot
history.snapshots.push(snapshot);
history.labels.push(label);
history.index++;
} // --------------------------------------------------------------------------
publicAPI.startStroke = function () {
if (model.labelMap) {
if (!workerPromise) {
worker = new WorkerFactory();
workerPromise = new WebworkerPromise(worker);
}
workerPromise.exec('start', {
bufferType: 'Uint8Array',
dimensions: model.labelMap.getDimensions(),
slicingMode: model.slicingMode
});
}
}; // --------------------------------------------------------------------------
publicAPI.endStroke = function () {
var endStrokePromise;
if (workerPromise) {
endStrokePromise = workerPromise.exec('end');
endStrokePromise.then(function (strokeBuffer) {
publicAPI.applyBinaryMask(strokeBuffer);
worker.terminate();
worker = null;
workerPromise = null;
});
}
return endStrokePromise;
};
publicAPI.applyBinaryMask = function (maskBuffer) {
var scalars = model.labelMap.getPointData().getScalars();
var data = scalars.getData();
var maskLabelMap = new Uint8Array(maskBuffer);
var diffCount = 0;
for (var i = 0; i < maskLabelMap.length; i++) {
// maskLabelMap is a binary mask
diffCount += maskLabelMap[i];
} // Format: [ [index, oldLabel], ...]
// I could use an ArrayBuffer, which would place limits
// on the values of index/old, but will be more efficient.
var snapshot = new Array(diffCount);
var label = model.label;
var diffIdx = 0;
if (model.voxelFunc) {
var bgScalars = model.backgroundImage.getPointData().getScalars();
var voxel = [];
for (var _i = 0; _i < maskLabelMap.length; _i++) {
if (maskLabelMap[_i]) {
bgScalars.getTuple(_i, voxel); // might not fill up snapshot
if (model.voxelFunc(voxel, _i, label)) {
snapshot[diffIdx++] = [_i, data[_i]];
data[_i] = label;
}
}
}
} else {
for (var _i2 = 0; _i2 < maskLabelMap.length; _i2++) {
if (maskLabelMap[_i2]) {
if (data[_i2] !== label) {
snapshot[diffIdx++] = [_i2, data[_i2]];
data[_i2] = label;
}
}
}
}
pushToHistory(snapshot, label);
scalars.setData(data);
scalars.modified();
model.labelMap.modified();
publicAPI.modified();
}; // --------------------------------------------------------------------------
publicAPI.addPoint = function (point) {
if (workerPromise) {
var worldPt = [point[0], point[1], point[2]];
var indexPt = [0, 0, 0];
vec3.transformMat4(indexPt, worldPt, model.maskWorldToIndex);
indexPt[0] = Math.round(indexPt[0]);
indexPt[1] = Math.round(indexPt[1]);
indexPt[2] = Math.round(indexPt[2]);
var spacing = model.labelMap.getSpacing();
var radius = spacing.map(function (s) {
return model.radius / s;
});
workerPromise.exec('paint', {
point: indexPt,
radius: radius
});
}
}; // --------------------------------------------------------------------------
publicAPI.paintRectangle = function (point1, point2) {
if (workerPromise) {
var index1 = [0, 0, 0];
var index2 = [0, 0, 0];
vec3.transformMat4(index1, point1, model.maskWorldToIndex);
vec3.transformMat4(index2, point2, model.maskWorldToIndex);
index1[0] = Math.round(index1[0]);
index1[1] = Math.round(index1[1]);
index1[2] = Math.round(index1[2]);
index2[0] = Math.round(index2[0]);
index2[1] = Math.round(index2[1]);
index2[2] = Math.round(index2[2]);
workerPromise.exec('paintRectangle', {
point1: index1,
point2: index2
});
}
}; // --------------------------------------------------------------------------
publicAPI.paintEllipse = function (center, scale3) {
if (workerPromise) {
var realCenter = [0, 0, 0];
var origin = [0, 0, 0];
var realScale3 = [0, 0, 0];
vec3.transformMat4(realCenter, center, model.maskWorldToIndex);
vec3.transformMat4(origin, origin, model.maskWorldToIndex);
vec3.transformMat4(realScale3, scale3, model.maskWorldToIndex);
vec3.subtract(realScale3, realScale3, origin);
realScale3 = realScale3.map(function (s) {
return s === 0 ? 0.25 : Math.abs(s);
});
workerPromise.exec('paintEllipse', {
center: realCenter,
scale3: realScale3
});
}
}; // --------------------------------------------------------------------------
publicAPI.canUndo = function () {
return history.index > -1;
}; // --------------------------------------------------------------------------
publicAPI.paintPolygon = function (pointList) {
if (workerPromise && pointList.length > 0) {
var polygon = vtkPolygon.newInstance();
var poly = [];
for (var i = 0; i < pointList.length / 3; i++) {
poly.push([pointList[3 * i + 0], pointList[3 * i + 1], pointList[3 * i + 2]]);
}
polygon.setPoints(poly);
if (!polygon.triangulate()) {
console.log('triangulation failed!');
}
var points = polygon.getPointArray();
var triangleList = new Float32Array(points.length);
var numPoints = Math.floor(triangleList.length / 3);
for (var _i3 = 0; _i3 < numPoints; _i3++) {
var point = points.slice(3 * _i3, 3 * _i3 + 3);
var voxel = triangleList.subarray(3 * _i3, 3 * _i3 + 3);
vec3.transformMat4(voxel, point, model.maskWorldToIndex);
}
workerPromise.exec('paintTriangles', {
triangleList: triangleList
});
}
}; // --------------------------------------------------------------------------
publicAPI.applyLabelMap = function (labelMap) {
var currentMapData = model.labelMap.getPointData().getScalars().getData();
var newMapData = labelMap.getPointData().getScalars().getData(); // Compute snapshot
var snapshot = [];
for (var i = 0; i < newMapData.length; ++i) {
if (currentMapData[i] !== newMapData[i]) {
snapshot.push([i, currentMapData[i]]);
}
}
pushToHistory(snapshot, model.label);
model.labelMap = labelMap;
publicAPI.modified();
}; // --------------------------------------------------------------------------
publicAPI.undo = function () {
if (history.index > -1) {
var scalars = model.labelMap.getPointData().getScalars();
var data = scalars.getData();
var snapshot = history.snapshots[history.index];
for (var i = 0; i < snapshot.length; i++) {
if (!snapshot[i]) {
break;
}
var _snapshot$i = _slicedToArray(snapshot[i], 2),
index = _snapshot$i[0],
oldLabel = _snapshot$i[1];
data[index] = oldLabel;
}
history.index--;
scalars.setData(data);
scalars.modified();
model.labelMap.modified();
publicAPI.modified();
}
}; // --------------------------------------------------------------------------
publicAPI.canRedo = function () {
return history.index < history.labels.length - 1;
}; // --------------------------------------------------------------------------
publicAPI.redo = function () {
if (history.index < history.labels.length - 1) {
var scalars = model.labelMap.getPointData().getScalars();
var data = scalars.getData();
var redoLabel = history.labels[history.index + 1];
var snapshot = history.snapshots[history.index + 1];
for (var i = 0; i < snapshot.length; i++) {
if (!snapshot[i]) {
break;
}
var _snapshot$i2 = _slicedToArray(snapshot[i], 1),
index = _snapshot$i2[0];
data[index] = redoLabel;
}
history.index++;
scalars.setData(data);
scalars.modified();
model.labelMap.modified();
publicAPI.modified();
}
}; // --------------------------------------------------------------------------
var superSetLabelMap = publicAPI.setLabelMap;
publicAPI.setLabelMap = function (lm) {
if (superSetLabelMap(lm)) {
model.maskWorldToIndex = model.labelMap.getWorldToIndex();
resetHistory();
return true;
}
return false;
}; // --------------------------------------------------------------------------
publicAPI.requestData = function (inData, outData) {
if (!model.backgroundImage) {
vtkErrorMacro('No background image');
return;
}
if (!model.backgroundImage.getPointData().getScalars()) {
vtkErrorMacro('Background image has no scalars');
return;
}
if (!model.labelMap) {
// clone background image properties
var labelMap = vtkImageData.newInstance(model.backgroundImage.get('spacing', 'origin', 'direction'));
labelMap.setDimensions(model.backgroundImage.getDimensions());
labelMap.computeTransforms(); // right now only support 256 labels
var values = new Uint8Array(model.backgroundImage.getNumberOfPoints());
var dataArray = vtkDataArray.newInstance({
numberOfComponents: 1,
// labelmap with single component
values: values
});
labelMap.getPointData().setScalars(dataArray);
publicAPI.setLabelMap(labelMap);
}
if (!model.maskWorldToIndex) {
model.maskWorldToIndex = model.labelMap.getWorldToIndex();
}
var scalars = model.labelMap.getPointData().getScalars();
if (!scalars) {
vtkErrorMacro('Mask image has no scalars');
return;
}
model.labelMap.modified();
outData[0] = model.labelMap;
}; // --------------------------------------------------------------------------
// Initialization
// --------------------------------------------------------------------------
resetHistory();
} // ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------
var DEFAULT_VALUES = {
backgroundImage: null,
labelMap: null,
maskWorldToIndex: null,
voxelFunc: null,
radius: 1,
label: 0,
slicingMode: null
}; // ----------------------------------------------------------------------------
function extend(publicAPI, model) {
var initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
Object.assign(model, DEFAULT_VALUES, initialValues); // Make this a VTK object
macro.obj(publicAPI, model); // Also make it an algorithm with no input and one output
macro.algo(publicAPI, model, 0, 1);
macro.setGet(publicAPI, model, ['backgroundImage', 'labelMap', 'maskWorldToIndex', 'voxelFunc', 'label', 'radius', 'slicingMode']); // Object specific methods
vtkPaintFilter(publicAPI, model);
} // ----------------------------------------------------------------------------
var newInstance = macro.newInstance(extend, 'vtkPaintFilter'); // ----------------------------------------------------------------------------
var vtkPaintFilter$1 = {
newInstance: newInstance,
extend: extend
};
export { vtkPaintFilter$1 as default, extend, newInstance };