@cornerstonejs/core
Version:
Cornerstone3D Core
1,011 lines (1,010 loc) • 42.9 kB
JavaScript
import cache from '../cache/cache.js';
import { InterpolationType } from '../enums/index.js';
import { mat3, vec3 } from 'gl-matrix';
import RLEVoxelMap from './RLEVoxelMap.js';
import isEqual from './isEqual.js';
import { iterateOverPointsInShapeVoxelManager } from './pointInShapeCallback.js';
const DEFAULT_RLE_SIZE = 5 * 1024;
const worldToIndexDeltaScratch = [0, 0, 0];
const worldToIndexResultScratch = [0, 0, 0];
const worldToIndexTransformCache = new WeakMap();
export default class VoxelManager {
get id() {
return this._id;
}
constructor(dimensions, options) {
this.modifiedSlices = new Set();
this.boundsIJK = [
[Infinity, -Infinity],
[Infinity, -Infinity],
[Infinity, -Infinity],
];
this.scalarData = null;
this._scalarDataIsCachedExpansion = false;
this._sliceDataCache = null;
this.getAtIJK = (i, j, k) => {
return this._get(i + j * this.width + k * this.frameSize);
};
this.setAtIJK = (i, j, k, v) => {
const index = i + j * this.width + k * this.frameSize;
const changed = this._set(index, v);
if (changed !== false) {
this.modifiedSlices.add(k);
VoxelManager.addBounds(this.boundsIJK, [i, j, k]);
this.invalidateCachedScalarExpansion();
}
return changed;
};
this.getAtIJKPoint = ([i, j, k]) => this.getAtIJK(i, j, k);
this.setAtIJKPoint = ([i, j, k], v) => {
this.setAtIJK(i, j, k, v);
};
this.getAtIndex = (index) => this._get(index);
this.setAtIndex = (index, v) => {
const changed = this._set(index, v);
if (changed !== false) {
const pointIJK = this.toIJK(index);
this.modifiedSlices.add(pointIJK[2]);
VoxelManager.addBounds(this.boundsIJK, pointIJK);
this.invalidateCachedScalarExpansion();
}
return changed;
};
this.getMiddleSliceData = () => {
const middleSliceIndex = Math.floor(this.dimensions[2] / 2);
return this.getSliceData({
sliceIndex: middleSliceIndex,
slicePlane: 2,
});
};
this.forEach = (callback, options = {}) => {
const isInObjectBoundsIJK = options.boundsIJK || this.getBoundsIJK();
const isInObject = options.isInObject || this.isInObject || (() => true);
const returnPoints = options.returnPoints || false;
const useLPSTransform = options.imageData;
const iMin = Math.min(isInObjectBoundsIJK[0][0], isInObjectBoundsIJK[0][1]);
const iMax = Math.max(isInObjectBoundsIJK[0][0], isInObjectBoundsIJK[0][1]);
const jMin = Math.min(isInObjectBoundsIJK[1][0], isInObjectBoundsIJK[1][1]);
const jMax = Math.max(isInObjectBoundsIJK[1][0], isInObjectBoundsIJK[1][1]);
const kMin = Math.min(isInObjectBoundsIJK[2][0], isInObjectBoundsIJK[2][1]);
const kMax = Math.max(isInObjectBoundsIJK[2][0], isInObjectBoundsIJK[2][1]);
const pointsInShape = [];
if (useLPSTransform) {
const pointsInShape = iterateOverPointsInShapeVoxelManager({
voxelManager: this,
imageData: options.imageData,
bounds: [
[iMin, iMax],
[jMin, jMax],
[kMin, kMax],
],
pointInShapeFn: isInObject,
callback,
returnPoints,
});
return pointsInShape;
}
if (this.map) {
if (this.map instanceof RLEVoxelMap) {
return this.rleForEach(callback, options);
}
for (const index of this.map.keys()) {
const pointIJK = this.toIJK(index);
if (!isInObject(null, pointIJK)) {
continue;
}
const value = this._get(index);
if (returnPoints) {
pointsInShape.push({
value,
index,
pointIJK,
pointLPS: null,
});
}
callback({ value, index, pointIJK, pointLPS: null });
}
return pointsInShape;
}
else {
for (let k = kMin; k <= kMax; k++) {
const kIndex = k * this.frameSize;
for (let j = jMin; j <= jMax; j++) {
const jIndex = kIndex + j * this.width;
for (let i = iMin, index = jIndex + i; i <= iMax; i++, index++) {
const value = this.getAtIndex(index);
const pointIJK = [i, j, k];
if (!isInObject(null, pointIJK)) {
continue;
}
if (returnPoints) {
pointsInShape.push({
value,
index,
pointIJK,
pointLPS: null,
});
}
callback({ value, index, pointIJK: [i, j, k], pointLPS: null });
}
}
}
return pointsInShape;
}
};
this.getSliceData = ({ sliceIndex, slicePlane, }) => {
const [width, height, depth] = this.dimensions;
const frameSize = width * height;
const startIndex = sliceIndex * frameSize;
let sliceSize;
const SliceDataConstructor = this.getConstructor();
function isValidConstructor(ctor) {
return typeof ctor === 'function';
}
if (!isValidConstructor(SliceDataConstructor)) {
return new Uint8Array(0);
}
let sliceData;
switch (slicePlane) {
case 0:
sliceSize = height * depth;
sliceData = new SliceDataConstructor(sliceSize);
for (let i = 0; i < height; i++) {
for (let j = 0; j < depth; j++) {
const index = sliceIndex + i * width + j * frameSize;
this.setSliceDataValue(sliceData, i + j * height, this._get(index));
}
}
break;
case 1:
sliceSize = width * depth;
sliceData = new SliceDataConstructor(sliceSize);
for (let i = 0; i < width; i++) {
for (let j = 0; j < depth; j++) {
const index = i + sliceIndex * width + j * frameSize;
this.setSliceDataValue(sliceData, i + j * width, this._get(index));
}
}
break;
case 2:
sliceSize = width * height;
sliceData = new SliceDataConstructor(sliceSize);
for (let i = 0; i < sliceSize; i++) {
this.setSliceDataValue(sliceData, i, this._get(startIndex + i));
}
break;
default:
throw new Error('Oblique plane - todo - implement as ortho normal vector');
}
return sliceData;
};
this.dimensions = dimensions;
this.width = dimensions[0];
this.frameSize = this.width * dimensions[1];
this._get = options._get;
this._set = options._set;
this._id = options._id || '';
this._getConstructor = options._getConstructor;
this.numberOfComponents = options.numberOfComponents || 1;
this.scalarData = options.scalarData;
this._getScalarData = options._getScalarData;
this._updateScalarData = options._updateScalarData;
}
getMinMax() {
let min, max;
const callback = ({ value: v }) => {
const isArray = Array.isArray(v);
if (min === undefined) {
min = isArray ? [...v] : v;
max = isArray ? [...v] : v;
}
if (isArray) {
for (let i = 0; i < v.length; i++) {
min[i] = Math.min(min[i], v[i]);
max[i] = Math.max(max[i], v[i]);
}
}
else {
min = Math.min(min, v);
max = Math.max(max, v);
}
};
this.forEach(callback, { boundsIJK: this.getDefaultBounds() });
return { min, max };
}
invalidateCachedScalarExpansion() {
if (this._scalarDataIsCachedExpansion) {
this.scalarData = null;
this._scalarDataIsCachedExpansion = false;
}
}
toIJK(index) {
return [
index % this.width,
Math.floor((index % this.frameSize) / this.width),
Math.floor(index / this.frameSize),
];
}
toIndex(ijk) {
return ijk[0] + ijk[1] * this.width + ijk[2] * this.frameSize;
}
getDefaultBounds() {
return this.dimensions.map((dimension) => [0, dimension - 1]);
}
getBoundsIJK() {
if (this.boundsIJK[0][0] < this.dimensions[0]) {
return this.boundsIJK;
}
return this.getDefaultBounds();
}
rleForEach(callback, options) {
const boundsIJK = options?.boundsIJK || this.getBoundsIJK();
const { isWithinObject } = options || {};
const map = this.map;
if (!map) {
console.warn('No map found, you need to use a map voxel manager to use rleForEach');
return;
}
map.defaultValue = undefined;
for (let k = boundsIJK[2][0]; k <= boundsIJK[2][1]; k++) {
for (let j = boundsIJK[1][0]; j <= boundsIJK[1][1]; j++) {
const row = map.getRun(j, k);
if (!row) {
continue;
}
for (const rle of row) {
const { start, end, value } = rle;
const baseIndex = this.toIndex([0, j, k]);
for (let i = start; i < end; i++) {
const callbackArguments = {
value,
index: baseIndex + i,
pointIJK: [i, j, k],
};
if (isWithinObject?.(callbackArguments) === false) {
continue;
}
callback(callbackArguments);
}
}
}
}
}
getScalarData(storeScalarData = false) {
if (this.scalarData) {
this._updateScalarData?.(this.scalarData);
return this.scalarData;
}
if (this._getScalarData) {
const scalarData = this._getScalarData();
if (storeScalarData) {
this.scalarData = scalarData;
this._scalarDataIsCachedExpansion = true;
}
return scalarData;
}
throw new Error('No scalar data available');
}
setScalarData(newScalarData) {
this.scalarData = newScalarData;
this._scalarDataIsCachedExpansion = false;
}
getScalarDataLength() {
if (this.scalarData) {
return this.scalarData.length;
}
if (this._getScalarDataLength) {
return this._getScalarDataLength();
}
throw new Error('No scalar data available');
}
get sizeInBytes() {
return this.getScalarDataLength() * this.bytePerVoxel;
}
get bytePerVoxel() {
if (this.scalarData) {
return this.scalarData.BYTES_PER_ELEMENT;
}
const value = this._get(0);
return value.BYTES_PER_ELEMENT;
}
clearBounds() {
this.boundsIJK.map((bound) => {
bound[0] = Infinity;
bound[1] = -Infinity;
});
}
clear() {
this.map?.clear();
this.clearBounds();
this.modifiedSlices.clear();
this.points?.clear();
}
getConstructor() {
if (this.scalarData) {
return this.scalarData.constructor;
}
if (this._getConstructor) {
return this._getConstructor();
}
console.warn('No scalar data available or can be used to get the constructor');
return Float32Array;
}
getArrayOfModifiedSlices() {
return Array.from(this.modifiedSlices);
}
resetModifiedSlices() {
this.modifiedSlices.clear();
}
setBounds(bounds) {
this.boundsIJK = bounds;
}
static addBounds(bounds, point) {
if (!bounds) {
bounds = [
[Infinity, -Infinity],
[Infinity, -Infinity],
[Infinity, -Infinity],
];
}
bounds[0][0] = Math.min(point[0], bounds[0][0]);
bounds[0][1] = Math.max(point[0], bounds[0][1]);
bounds[1][0] = Math.min(point[1], bounds[1][0]);
bounds[1][1] = Math.max(point[1], bounds[1][1]);
bounds[2][0] = Math.min(point[2], bounds[2][0]);
bounds[2][1] = Math.max(point[2], bounds[2][1]);
}
static getWorldToIndexTransform(volumeGeometry) {
const cachedTransform = worldToIndexTransformCache.get(volumeGeometry);
if (cachedTransform) {
return cachedTransform;
}
const direction = volumeGeometry.direction;
const spacing = volumeGeometry.spacing;
const transform = mat3.fromValues(direction[0] / spacing[0], direction[3] / spacing[1], direction[6] / spacing[2], direction[1] / spacing[0], direction[4] / spacing[1], direction[7] / spacing[2], direction[2] / spacing[0], direction[5] / spacing[1], direction[8] / spacing[2]);
worldToIndexTransformCache.set(volumeGeometry, transform);
return transform;
}
static worldToIndexContinuous(volumeGeometry, worldPos) {
const origin = volumeGeometry.origin;
worldToIndexDeltaScratch[0] = worldPos[0] - origin[0];
worldToIndexDeltaScratch[1] = worldPos[1] - origin[1];
worldToIndexDeltaScratch[2] = worldPos[2] - origin[2];
const continuousIndex = vec3.transformMat3(worldToIndexResultScratch, worldToIndexDeltaScratch, VoxelManager.getWorldToIndexTransform(volumeGeometry));
return [continuousIndex[0], continuousIndex[1], continuousIndex[2]];
}
static sampleAtWorldCoordinates(volume, worldX, worldY, worldZ, interpolationType = InterpolationType.LINEAR) {
if (!volume.voxelManager) {
return NaN;
}
const origin = volume.origin;
worldToIndexDeltaScratch[0] = worldX - origin[0];
worldToIndexDeltaScratch[1] = worldY - origin[1];
worldToIndexDeltaScratch[2] = worldZ - origin[2];
const continuousIndex = vec3.transformMat3(worldToIndexResultScratch, worldToIndexDeltaScratch, VoxelManager.getWorldToIndexTransform(volume));
return VoxelManager.sampleAtContinuousCoordinates(volume.voxelManager, volume.dimensions, continuousIndex[0], continuousIndex[1], continuousIndex[2], interpolationType);
}
static sampleAtWorld(volume, worldPos, interpolationType = InterpolationType.LINEAR) {
return VoxelManager.sampleAtWorldCoordinates(volume, worldPos[0], worldPos[1], worldPos[2], interpolationType);
}
static sampleAtContinuousIndex(voxelManager, dimensions, continuousIndex, interpolationType = InterpolationType.LINEAR) {
return VoxelManager.sampleAtContinuousCoordinates(voxelManager, dimensions, continuousIndex[0], continuousIndex[1], continuousIndex[2], interpolationType);
}
static sampleAtContinuousCoordinates(voxelManager, dimensions, iC, jC, kC, interpolationType) {
return interpolationType === InterpolationType.NEAREST
? VoxelManager.sampleNearestAtContinuousCoordinates(voxelManager, dimensions, iC, jC, kC)
: VoxelManager.sampleLinearAtContinuousCoordinates(voxelManager, dimensions, iC, jC, kC);
}
static sampleNearestAtContinuousCoordinates(voxelManager, dimensions, iC, jC, kC) {
const i = Math.floor(iC + 0.5 - 1e-6);
const j = Math.floor(jC + 0.5 - 1e-6);
const k = Math.floor(kC + 0.5 - 1e-6);
const dx = dimensions[0];
const dy = dimensions[1];
const dz = dimensions[2];
if (i < 0 || i >= dx || j < 0 || j >= dy || k < 0 || k >= dz) {
return NaN;
}
return Number(voxelManager.getAtIJK(i, j, k));
}
static sampleLinearAtContinuousCoordinates(voxelManager, dimensions, i, j, k) {
const dx = dimensions[0];
const dy = dimensions[1];
const dz = dimensions[2];
if (i < 0 || i > dx - 1 || j < 0 || j > dy - 1 || k < 0 || k > dz - 1) {
return NaN;
}
const i0 = Math.floor(i);
const j0 = Math.floor(j);
const k0 = Math.floor(k);
const i1 = Math.min(i0 + 1, dx - 1);
const j1 = Math.min(j0 + 1, dy - 1);
const k1 = Math.min(k0 + 1, dz - 1);
const di = i - i0;
const dj = j - j0;
const dk = k - k0;
const oneMinusDi = 1 - di;
const oneMinusDj = 1 - dj;
const oneMinusDk = 1 - dk;
const c000 = Number(voxelManager.getAtIJK(i0, j0, k0));
const c100 = Number(voxelManager.getAtIJK(i1, j0, k0));
const c010 = Number(voxelManager.getAtIJK(i0, j1, k0));
const c110 = Number(voxelManager.getAtIJK(i1, j1, k0));
const c001 = Number(voxelManager.getAtIJK(i0, j0, k1));
const c101 = Number(voxelManager.getAtIJK(i1, j0, k1));
const c011 = Number(voxelManager.getAtIJK(i0, j1, k1));
const c111 = Number(voxelManager.getAtIJK(i1, j1, k1));
const c00 = c000 * oneMinusDi + c100 * di;
const c10 = c010 * oneMinusDi + c110 * di;
const c01 = c001 * oneMinusDi + c101 * di;
const c11 = c011 * oneMinusDi + c111 * di;
const c0 = c00 * oneMinusDj + c10 * dj;
const c1 = c01 * oneMinusDj + c11 * dj;
return c0 * oneMinusDk + c1 * dk;
}
addPoint(point) {
const index = Array.isArray(point)
? point[0] + this.width * point[1] + this.frameSize * point[2]
: point;
if (!this.points) {
this.points = new Set();
}
this.points.add(index);
}
getPoints() {
return this.points
? [...this.points].map((index) => this.toIJK(index))
: [];
}
setSliceDataValue(sliceData, index, value) {
if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
sliceData[index * value.length + i] = this.toNumber(value[i]);
}
}
else {
sliceData[index] = this.toNumber(value);
}
}
toNumber(value) {
if (typeof value === 'number') {
return value;
}
if (Array.isArray(value)) {
return value[0] || 0;
}
return 0;
}
static _createRGBScalarVolumeVoxelManager({ dimensions, scalarData, numberOfComponents = 3, id, }) {
const voxels = new VoxelManager(dimensions, {
_get: (index) => {
index *= numberOfComponents;
return [
scalarData[index++],
scalarData[index++],
scalarData[index++],
];
},
_id: id || '_createRGBScalarVolumeVoxelManager',
_set: (index, v) => {
index *= 3;
const isChanged = !isEqual(scalarData[index], v);
scalarData[index++] = v[0];
scalarData[index++] = v[1];
scalarData[index++] = v[2];
return isChanged;
},
numberOfComponents,
scalarData,
});
voxels.clear = () => {
scalarData.fill(0);
};
return voxels;
}
static createImageVolumeVoxelManager({ dimensions, imageIds, numberOfComponents = 1, id, }) {
const pixelsPerSlice = dimensions[0] * dimensions[1];
const depth = dimensions[2];
const sliceVoxelManagers = new Array(depth);
let lastSliceIndex = -1;
let lastSliceVoxelManager = null;
const warnedMissingImageIds = new Set();
const warnedMissingImages = new Set();
const resolveSliceVoxelManager = (sliceIndex) => {
if (sliceIndex < 0 || sliceIndex >= depth) {
return null;
}
const cachedVoxelManager = sliceVoxelManagers[sliceIndex];
if (cachedVoxelManager !== undefined) {
return cachedVoxelManager;
}
const imageId = imageIds[sliceIndex];
if (!imageId) {
if (!warnedMissingImageIds.has(sliceIndex)) {
warnedMissingImageIds.add(sliceIndex);
console.warn(`ImageId not found for sliceIndex: ${sliceIndex}`);
}
sliceVoxelManagers[sliceIndex] = null;
return null;
}
const image = cache.getImage(imageId);
if (!image?.voxelManager) {
if (!warnedMissingImages.has(imageId)) {
warnedMissingImages.add(imageId);
console.warn(`Image not found for imageId: ${imageId}`);
}
return null;
}
const imageVoxelManager = image.voxelManager;
sliceVoxelManagers[sliceIndex] = imageVoxelManager;
return imageVoxelManager;
};
function getVoxelValue(index) {
const sliceIndex = Math.floor(index / pixelsPerSlice);
if (sliceIndex < 0 || sliceIndex >= depth) {
return null;
}
const imageVoxelManager = sliceIndex === lastSliceIndex
? lastSliceVoxelManager
: resolveSliceVoxelManager(sliceIndex);
if (!imageVoxelManager) {
return null;
}
if (sliceIndex !== lastSliceIndex) {
lastSliceIndex = sliceIndex;
lastSliceVoxelManager = imageVoxelManager;
}
const pixelIndex = index - sliceIndex * pixelsPerSlice;
return imageVoxelManager.getAtIndex(pixelIndex);
}
function setVoxelValue(index, v) {
const sliceIndex = Math.floor(index / pixelsPerSlice);
if (sliceIndex < 0 || sliceIndex >= depth) {
return false;
}
const imageVoxelManager = sliceIndex === lastSliceIndex
? lastSliceVoxelManager
: resolveSliceVoxelManager(sliceIndex);
if (!imageVoxelManager) {
return false;
}
if (sliceIndex !== lastSliceIndex) {
lastSliceIndex = sliceIndex;
lastSliceVoxelManager = imageVoxelManager;
}
const pixelIndex = index - sliceIndex * pixelsPerSlice;
const currentValue = imageVoxelManager.getAtIndex(pixelIndex);
const isChanged = !isEqual(v, currentValue);
if (!isChanged) {
return isChanged;
}
imageVoxelManager.setAtIndex(pixelIndex, v);
return true;
}
const _getConstructor = () => {
const imageVoxelManager = resolveSliceVoxelManager(0);
if (!imageVoxelManager) {
return null;
}
return imageVoxelManager.getConstructor();
};
const voxelManager = new VoxelManager(dimensions, {
_get: getVoxelValue,
_set: setVoxelValue,
numberOfComponents,
_getConstructor,
_id: id || 'createImageVolumeVoxelManager',
});
voxelManager.invalidateCache = () => {
sliceVoxelManagers.fill(undefined);
lastSliceIndex = -1;
lastSliceVoxelManager = null;
};
voxelManager.getMiddleSliceData = () => {
const middleSliceIndex = Math.floor(dimensions[2] / 2);
return voxelManager.getSliceData({
sliceIndex: middleSliceIndex,
slicePlane: 2,
});
};
voxelManager.clear = () => {
for (const imageId of imageIds) {
const image = cache.getImage(imageId);
image.voxelManager.clear();
}
};
voxelManager.getRange = () => {
let minValue = Infinity;
let maxValue = -Infinity;
for (const imageId of imageIds) {
const image = cache.getImage(imageId);
if (!image) {
continue;
}
if (image.minPixelValue < minValue) {
minValue = image.minPixelValue;
}
if (image.maxPixelValue > maxValue) {
maxValue = image.maxPixelValue;
}
}
if (minValue === Infinity && maxValue === -Infinity) {
return [0, 0];
}
return [minValue, maxValue];
};
voxelManager._getScalarDataLength = () => {
const imageVoxelManager = resolveSliceVoxelManager(0);
if (!imageVoxelManager) {
return 0;
}
return imageVoxelManager.getScalarDataLength() * dimensions[2];
};
voxelManager.getCompleteScalarDataArray = () => {
const ScalarDataConstructor = voxelManager._getConstructor();
if (!ScalarDataConstructor) {
return new Uint8Array(0);
}
const dataLength = voxelManager.getScalarDataLength();
const scalarData = new ScalarDataConstructor(dataLength);
const sliceSize = dimensions[0] * dimensions[1] * numberOfComponents;
for (let sliceIndex = 0; sliceIndex < dimensions[2]; sliceIndex++) {
const imageVoxelManager = resolveSliceVoxelManager(sliceIndex);
if (imageVoxelManager) {
const sliceStart = sliceIndex * sliceSize;
const pixelData = imageVoxelManager.getScalarData();
if (numberOfComponents === 1) {
scalarData.set(pixelData, sliceStart);
}
else {
for (let i = 0; i < pixelData.length; i += numberOfComponents) {
for (let j = 0; j < numberOfComponents; j++) {
scalarData[sliceStart + i + j] = pixelData[i + j];
}
}
}
}
}
return scalarData;
};
voxelManager.setCompleteScalarDataArray = (scalarData) => {
const sliceSize = dimensions[0] * dimensions[1] * numberOfComponents;
const SliceDataConstructor = voxelManager._getConstructor();
let minValue = Infinity;
let maxValue = -Infinity;
for (let sliceIndex = 0; sliceIndex < dimensions[2]; sliceIndex++) {
const imageVoxelManager = resolveSliceVoxelManager(sliceIndex);
if (imageVoxelManager && SliceDataConstructor) {
const sliceStart = sliceIndex * sliceSize;
const sliceEnd = sliceStart + sliceSize;
const sliceData = new SliceDataConstructor(sliceSize);
sliceData.set(scalarData.subarray(sliceStart, sliceEnd));
if (imageVoxelManager.scalarData) {
imageVoxelManager.scalarData.set(sliceData);
imageVoxelManager.modifiedSlices.add(sliceIndex);
}
else {
for (let i = 0; i < sliceSize; i++) {
imageVoxelManager.setAtIndex(i, sliceData[i]);
}
}
for (let i = 0; i < sliceData.length; i++) {
const value = sliceData[i];
minValue = Math.min(minValue, value);
maxValue = Math.max(maxValue, value);
}
const imageId = imageIds[sliceIndex];
const image = cache.getImage(imageId);
if (image) {
image.minPixelValue = minValue;
image.maxPixelValue = maxValue;
}
}
}
for (let k = 0; k < dimensions[2]; k++) {
voxelManager.modifiedSlices.add(k);
}
voxelManager.boundsIJK = [
[0, dimensions[0] - 1],
[0, dimensions[1] - 1],
[0, dimensions[2] - 1],
];
};
return voxelManager;
}
static createScalarVolumeVoxelManager({ dimensions, scalarData, numberOfComponents, id, }) {
if (dimensions.length !== 3) {
throw new Error('Dimensions must be provided as [number, number, number] for [width, height, depth]');
}
if (!numberOfComponents) {
numberOfComponents =
scalarData.length / dimensions[0] / dimensions[1] / dimensions[2];
if (numberOfComponents > 4 ||
numberOfComponents < 1 ||
numberOfComponents === 2) {
throw new Error(`Number of components ${numberOfComponents} must be 1, 3 or 4`);
}
}
if (numberOfComponents > 1) {
return VoxelManager._createRGBScalarVolumeVoxelManager({
dimensions,
scalarData,
numberOfComponents,
id,
});
}
return VoxelManager._createNumberVolumeVoxelManager({
dimensions,
scalarData,
id,
});
}
static createScalarDynamicVolumeVoxelManager({ imageIdGroups, dimensions, dimensionGroupNumber = 1, timePoint = 0, numberOfComponents = 1, id, }) {
let activeDimensionGroup = 0;
if (dimensionGroupNumber !== undefined) {
activeDimensionGroup = dimensionGroupNumber - 1;
}
else if (timePoint !== undefined) {
console.warn('Warning: timePoint parameter is deprecated. Please use dimensionGroupNumber instead. timePoint is zero-based while dimensionGroupNumber starts at 1.');
activeDimensionGroup = timePoint;
}
if (!numberOfComponents) {
const firstImage = cache.getImage(imageIdGroups[0][0]);
if (!firstImage) {
throw new Error('Unable to determine number of components: No image found');
}
numberOfComponents =
firstImage.getPixelData().length / (dimensions[0] * dimensions[1]);
if (numberOfComponents > 4 ||
numberOfComponents < 1 ||
numberOfComponents === 2) {
throw new Error(`Number of components ${numberOfComponents} must be 1, 3 or 4`);
}
}
const voxelGroups = imageIdGroups.map((imageIds) => {
return VoxelManager.createImageVolumeVoxelManager({
dimensions,
imageIds,
numberOfComponents,
id,
});
});
const voxelManager = new VoxelManager(dimensions, {
_get: (index) => voxelGroups[activeDimensionGroup]._get(index),
_set: (index, v) => voxelGroups[activeDimensionGroup]._set(index, v),
numberOfComponents,
_id: id || 'createScalarDynamicVolumeVoxelManager',
});
voxelManager.getScalarDataLength = () => {
return voxelGroups[activeDimensionGroup].getScalarDataLength();
};
voxelManager.getConstructor = () => {
return voxelGroups[activeDimensionGroup].getConstructor();
};
voxelManager.getRange = () => {
return voxelGroups[activeDimensionGroup].getRange();
};
voxelManager.getMiddleSliceData = () => {
return voxelGroups[activeDimensionGroup].getMiddleSliceData();
};
voxelManager.setTimePoint = (newTimePoint) => {
console.warn('Warning: setTimePoint is deprecated. Please use setDimensionGroupNumber instead. Note that timePoint is zero-based while dimensionGroupNumber starts at 1.');
voxelManager.setDimensionGroupNumber(newTimePoint + 1);
};
voxelManager.setDimensionGroupNumber = (newDimensionGroupNumber) => {
activeDimensionGroup = newDimensionGroupNumber - 1;
voxelManager._get = (index) => voxelGroups[activeDimensionGroup]._get(index);
voxelManager._set = (index, v) => voxelGroups[activeDimensionGroup]._set(index, v);
};
voxelManager.getAtIndexAndTimePoint = (index, tp) => {
console.warn('Warning: getAtIndexAndTimePoint is deprecated. Please use getAtIndexAndDimensionGroup instead. Note that timePoint is zero-based while dimensionGroupNumber starts at 1.');
return voxelManager.getAtIndexAndDimensionGroup(index, tp + 1);
};
voxelManager.getAtIndexAndDimensionGroup = (index, dimensionGroupNumber) => {
return voxelGroups[dimensionGroupNumber - 1]._get(index);
};
voxelManager.getTimePointScalarData = (tp) => {
console.warn('Warning: getTimePointScalarData is deprecated. Please use getDimensionGroupScalarData instead. Note that timePoint is zero-based while dimensionGroupNumber starts at 1.');
return voxelManager.getDimensionGroupScalarData(tp + 1);
};
voxelManager.getDimensionGroupScalarData = (dimensionGroupNumber) => {
return voxelGroups[dimensionGroupNumber - 1].getCompleteScalarDataArray();
};
voxelManager.getCurrentTimePointScalarData = () => {
console.warn('Warning: getCurrentTimePointScalarData is deprecated. Please use getCurrentDimensionGroupScalarData instead.');
return voxelManager.getCurrentDimensionGroupScalarData();
};
voxelManager.getCurrentDimensionGroupScalarData = () => {
return voxelGroups[activeDimensionGroup].getCompleteScalarDataArray();
};
voxelManager.getCompleteScalarDataArray = () => {
return voxelGroups[activeDimensionGroup].getCompleteScalarDataArray();
};
voxelManager.getCurrentTimePoint = () => {
console.warn('Warning: getCurrentTimePoint is deprecated. Please use getCurrentDimensionGroupNumber instead. Note that timePoint is zero-based while dimensionGroupNumber starts at 1.');
return activeDimensionGroup;
};
voxelManager.getCurrentDimensionGroupNumber = () => {
return activeDimensionGroup + 1;
};
return voxelManager;
}
static createImageVoxelManager({ width, height, scalarData, numberOfComponents = 1, id, }) {
const dimensions = [width, height, 1];
if (!numberOfComponents) {
numberOfComponents = scalarData.length / width / height;
if (numberOfComponents > 4 ||
numberOfComponents < 1 ||
numberOfComponents === 2) {
throw new Error(`Number of components ${numberOfComponents} must be 1, 3 or 4`);
}
}
if (numberOfComponents > 1) {
return VoxelManager._createRGBScalarVolumeVoxelManager({
dimensions,
scalarData,
numberOfComponents,
id,
});
}
return VoxelManager._createNumberVolumeVoxelManager({
dimensions,
scalarData,
id,
});
}
static _createNumberVolumeVoxelManager({ dimensions, scalarData, id, }) {
const voxels = new VoxelManager(dimensions, {
_get: (index) => scalarData[index],
_set: (index, v) => {
const isChanged = scalarData[index] !== v;
scalarData[index] = v;
return isChanged;
},
_getConstructor: () => scalarData.constructor,
_id: id || '_createNumberVolumeVoxelManager',
});
voxels.scalarData = scalarData;
voxels.clear = () => {
voxels.scalarData.fill(0);
};
voxels.getMiddleSliceData = () => {
const middleSliceIndex = Math.floor(dimensions[2] / 2);
return voxels.getSliceData({
sliceIndex: middleSliceIndex,
slicePlane: 2,
});
};
return voxels;
}
static createMapVoxelManager({ dimension, id, }) {
const map = new Map();
const voxelManager = new VoxelManager(dimension, {
_get: map.get.bind(map),
_set: (index, v) => map.set(index, v) && true,
_id: id || 'createMapVoxelManager',
});
voxelManager.map = map;
return voxelManager;
}
static createHistoryVoxelManager(sourceVoxelManager, id) {
const map = new Map();
const { dimensions } = sourceVoxelManager;
const voxelManager = new VoxelManager(dimensions, {
_get: (index) => map.get(index),
_set: function (index, v) {
if (!map.has(index)) {
const oldV = this.sourceVoxelManager.getAtIndex(index);
if (oldV === v) {
return false;
}
map.set(index, oldV);
}
else if (v === map.get(index)) {
map.delete(index);
}
this.sourceVoxelManager.setAtIndex(index, v);
},
_id: id || 'createHistoryVoxelManager',
});
voxelManager.map = map;
voxelManager.scalarData = sourceVoxelManager.scalarData;
voxelManager.sourceVoxelManager = sourceVoxelManager;
return voxelManager;
}
static createRLEHistoryVoxelManager(sourceVoxelManager, id) {
const { dimensions } = sourceVoxelManager;
const map = new RLEVoxelMap(dimensions[0], dimensions[1], dimensions[2]);
const voxelManager = new VoxelManager(dimensions, {
_get: (index) => map.get(index),
_set: function (index, v) {
const originalV = map.get(index);
if (originalV === undefined) {
const oldV = this.sourceVoxelManager.getAtIndex(index);
if (oldV === v || (oldV === undefined && v === 0) || v === null) {
return false;
}
map.set(index, oldV ?? 0);
}
else if (v === originalV || v === null) {
map.delete(index);
v = originalV;
}
this.sourceVoxelManager.setAtIndex(index, v);
},
_getScalarData: RLEVoxelMap.getScalarData,
_updateScalarData: (scalarData) => {
map.updateScalarData(scalarData);
return scalarData;
},
_id: id || 'createRLEHistoryVoxelManager',
});
voxelManager.map = map;
voxelManager.sourceVoxelManager = sourceVoxelManager;
return voxelManager;
}
static createLazyVoxelManager({ dimensions, planeFactory, id, }) {
const map = new Map();
const [width, height] = dimensions;
const planeSize = width * height;
const voxelManager = new VoxelManager(dimensions, {
_get: (index) => map.get(Math.floor(index / planeSize))[index % planeSize],
_set: (index, v) => {
const k = Math.floor(index / planeSize);
let layer = map.get(k);
if (!layer) {
layer = planeFactory(width, height);
map.set(k, layer);
}
layer[index % planeSize] = v;
return true;
},
_id: id || 'createLazyVoxelManager',
});
voxelManager.map = map;
return voxelManager;
}
static createRLEVolumeVoxelManager({ dimensions, id, }) {
const [width, height, depth] = dimensions;
const map = new RLEVoxelMap(width, height, depth);
const voxelManager = new VoxelManager(dimensions, {
_get: (index) => map.get(index),
_set: (index, v) => {
map.set(index, v);
return true;
},
_getScalarData: RLEVoxelMap.getScalarData,
_updateScalarData: (scalarData) => {
map.updateScalarData(scalarData);
return scalarData;
},
_id: id || 'createRLEVolumeVoxelManager',
});
voxelManager.map = map;
voxelManager.getPixelData = map.getPixelData.bind(map);
return voxelManager;
}
static createRLEImageVoxelManager({ dimensions, id, }) {
const [width, height] = dimensions;
return VoxelManager.createRLEVolumeVoxelManager({
dimensions: [width, height, 1],
id,
});
}
static addInstanceToImage(image) {
const { width, height } = image;
const scalarData = image.voxelManager.getScalarData();
if (scalarData.length >= width * height) {
image.voxelManager = VoxelManager.createScalarVolumeVoxelManager({
dimensions: [width, height, 1],
scalarData,
});
return;
}
image.voxelManager = VoxelManager.createRLEVolumeVoxelManager({
dimensions: [width, height, 1],
});
image.getPixelData = image.voxelManager.getPixelData;
image.sizeInBytes = DEFAULT_RLE_SIZE;
}
}