@cornerstonejs/core
Version:
Cornerstone3D Core
378 lines (377 loc) • 12.9 kB
JavaScript
import { deepClone } from './deepClone.js';
const ADJACENT_ALL = [
[0, -1, 0],
[0, 1, 0],
[0, 0, -1],
[0, 0, 1],
];
const ADJACENT_SINGLE_PLANE = [
[0, -1, 0],
[0, 1, 0],
];
const ADJACENT_IN = [
[0, -1, 0],
[0, 1, 0],
[0, 0, -1],
];
const ADJACENT_OUT = [
[0, -1, 0],
[0, 1, 0],
[0, 0, 1],
];
class RLEVoxelMap {
static copyMap(destination, source) {
for (const [index, row] of source.rows) {
destination.rows.set(index, deepClone(row));
}
}
constructor(width, height, depth = 1) {
this.rows = new Map();
this.height = 1;
this.width = 1;
this.depth = 1;
this.jMultiple = 1;
this.kMultiple = 1;
this.numComps = 1;
this.pixelDataConstructor = Uint8Array;
this.updateScalarData = function (scalarData) {
scalarData.fill(0);
const callback = (index, rle, row) => {
const { start, end, value } = rle;
for (let i = start; i < end; i++) {
scalarData[index + i] = value;
}
};
this.forEach(callback);
};
this.get = (index) => {
const i = index % this.jMultiple;
const j = (index - i) / this.jMultiple;
const rle = this.getRLE(i, j);
return rle?.value ?? this.defaultValue;
};
this.getRun = (j, k) => {
const runIndex = j + k * this.height;
return this.rows.get(runIndex);
};
this.set = (index, value) => {
if (value === undefined) {
return;
}
const i = index % this.width;
const j = (index - i) / this.width;
const row = this.rows.get(j);
if (!row) {
this.rows.set(j, [{ start: i, end: i + 1, value }]);
return;
}
const rleIndex = this.findIndex(row, i);
const rle1 = row[rleIndex];
const rle0 = row[rleIndex - 1];
if (!rle1) {
if (!rle0 || rle0.value !== value || rle0.end !== i) {
row[rleIndex] = { start: i, end: i + 1, value };
return;
}
rle0.end++;
return;
}
const { start, end, value: oldValue } = rle1;
if (value === oldValue && i >= start) {
return;
}
const rleInsert = { start: i, end: i + 1, value };
const isAfter = i > start;
const insertIndex = isAfter ? rleIndex + 1 : rleIndex;
const rlePrev = isAfter ? rle1 : rle0;
let rleNext = isAfter ? row[rleIndex + 1] : rle1;
if (rlePrev?.value === value && rlePrev?.end === i) {
rlePrev.end++;
if (rleNext?.value === value && rleNext.start === i + 1) {
rlePrev.end = rleNext.end;
row.splice(rleIndex, 1);
}
else if (rleNext?.start === i) {
rleNext.start++;
if (rleNext.start === rleNext.end) {
row.splice(rleIndex, 1);
rleNext = row[rleIndex];
if (rleNext?.start === i + 1 && rleNext.value === value) {
rlePrev.end = rleNext.end;
row.splice(rleIndex, 1);
}
}
}
return;
}
if (rleNext?.value === value && rleNext.start === i + 1) {
rleNext.start--;
if (rlePrev?.end > i) {
rlePrev.end = i;
if (rlePrev.end === rlePrev.start) {
row.splice(rleIndex, 1);
}
}
return;
}
if (rleNext?.start === i && rleNext.end === i + 1) {
rleNext.value = value;
const nextnext = row[rleIndex + 1];
if (nextnext?.start == i + 1 && nextnext.value === value) {
row.splice(rleIndex + 1, 1);
rleNext.end = nextnext.end;
}
return;
}
if (i === rleNext?.start) {
rleNext.start++;
}
if (isAfter && end > i + 1) {
row.splice(insertIndex, 0, rleInsert, {
start: i + 1,
end: rlePrev.end,
value: rlePrev.value,
});
}
else {
row.splice(insertIndex, 0, rleInsert);
}
if (rlePrev?.end > i) {
rlePrev.end = i;
}
};
this.width = width;
this.height = height;
this.depth = depth;
this.jMultiple = width;
this.kMultiple = this.jMultiple * height;
}
toIJK(index) {
const i = index % this.jMultiple;
const j = ((index - i) / this.jMultiple) % this.height;
const k = Math.floor(index / this.kMultiple);
return [i, j, k];
}
toIndex([i, j, k]) {
return i + k * this.kMultiple + j * this.jMultiple;
}
getRLE(i, j, k = 0) {
const row = this.rows.get(j + k * this.height);
if (!row) {
return;
}
const index = this.findIndex(row, i);
const rle = row[index];
return i >= rle?.start ? rle : undefined;
}
has(index) {
const i = index % this.jMultiple;
const j = (index - i) / this.jMultiple;
const rle = this.getRLE(i, j);
return rle?.value !== undefined;
}
delete(index) {
const i = index % this.width;
const j = (index - i) / this.width;
const row = this.rows.get(j);
if (!row) {
return;
}
const rleIndex = this.findIndex(row, i);
const rle = row[rleIndex];
if (!rle || rle.start > i) {
return;
}
if (rle.end === i + 1) {
rle.end--;
if (rle.start >= rle.end) {
row.splice(rleIndex, 1);
if (!row.length) {
this.rows.delete(j);
}
}
return;
}
if (rle.start === i) {
rle.start++;
return;
}
const newRle = {
value: rle.value,
start: i + 1,
end: rle.end,
};
rle.end = i;
row.splice(rleIndex + 1, 0, newRle);
}
findIndex(row, i) {
for (let index = 0; index < row.length; index++) {
const { end: iEnd } = row[index];
if (i < iEnd) {
return index;
}
}
return row.length;
}
forEach(callback, options) {
const rowModified = options?.rowModified;
for (const [baseIndex, row] of this.rows) {
const rowToUse = rowModified ? [...row] : row;
for (const rle of rowToUse) {
callback(baseIndex * this.width, rle, row);
}
}
}
forEachRow(callback) {
for (const [baseIndex, row] of this.rows) {
callback(baseIndex * this.width, row);
}
}
clear() {
this.rows.clear();
}
keys() {
return [...this.rows.keys()];
}
getPixelData(k = 0, pixelData) {
if (!pixelData) {
pixelData = new this.pixelDataConstructor(this.width * this.height * this.numComps);
}
else {
pixelData.fill(0);
}
const { width, height, numComps } = this;
for (let j = 0; j < height; j++) {
const row = this.getRun(j, k);
if (!row) {
continue;
}
if (numComps === 1) {
for (const rle of row) {
const rowOffset = j * width;
const { start, end, value } = rle;
for (let i = start; i < end; i++) {
pixelData[rowOffset + i] = value;
}
}
}
else {
for (const rle of row) {
const rowOffset = j * width * numComps;
const { start, end, value } = rle;
for (let i = start; i < end; i += numComps) {
for (let comp = 0; comp < numComps; comp++) {
pixelData[rowOffset + i + comp] = value[comp];
}
}
}
}
}
return pixelData;
}
floodFill(i, j, k, value, options) {
const rle = this.getRLE(i, j, k);
if (!rle) {
throw new Error(`Initial point ${i},${j},${k} isn't in the RLE`);
}
const stack = [[rle, j, k]];
const replaceValue = rle.value;
if (replaceValue === value) {
throw new Error(`source (${replaceValue}) and destination (${value}) are identical`);
}
return this.flood(stack, replaceValue, value, options);
}
flood(stack, sourceValue, value, options) {
let sum = 0;
const { planar = true, diagonals = true, singlePlane = false, } = options || {};
const childOptions = { planar, diagonals, singlePlane };
while (stack.length) {
const top = stack.pop();
const [current] = top;
if (current.value !== sourceValue) {
continue;
}
current.value = value;
sum += current.end - current.start;
const adjacents = this.findAdjacents(top, childOptions).filter((adjacent) => adjacent && adjacent[0].value === sourceValue);
stack.push(...adjacents);
}
return sum;
}
fillFrom(getter, boundsIJK) {
for (let k = boundsIJK[2][0]; k <= boundsIJK[2][1]; k++) {
for (let j = boundsIJK[1][0]; j <= boundsIJK[1][1]; j++) {
let rle;
let row;
for (let i = boundsIJK[0][0]; i <= boundsIJK[0][1]; i++) {
const value = getter(i, j, k);
if (value === undefined) {
rle = undefined;
continue;
}
if (!row) {
row = [];
this.rows.set(j + k * this.height, row);
}
if (rle && rle.value !== value) {
rle = undefined;
}
if (!rle) {
rle = { start: i, end: i, value };
row.push(rle);
}
rle.end++;
}
}
}
}
findAdjacents(item, { diagonals = true, planar = true, singlePlane = false }) {
const [rle, j, k, adjacentsDelta] = item;
const { start, end } = rle;
const leftRle = start > 0 && this.getRLE(start - 1, j, k);
const rightRle = end < this.width && this.getRLE(end, j, k);
const range = diagonals
? [start > 0 ? start - 1 : start, end < this.width ? end + 1 : end]
: [start, end];
const adjacents = [];
if (leftRle) {
adjacents.push([leftRle, j, k]);
}
if (rightRle) {
adjacents.push([rightRle, j, k]);
}
for (const delta of adjacentsDelta ||
(singlePlane ? ADJACENT_SINGLE_PLANE : ADJACENT_ALL)) {
const [, delta1, delta2] = delta;
const testJ = delta1 + j;
const testK = delta2 + k;
if (testJ < 0 || testJ >= this.height) {
continue;
}
if (testK < 0 || testK >= this.depth) {
continue;
}
const row = this.getRun(testJ, testK);
if (!row) {
continue;
}
for (const testRle of row) {
const newAdjacentDelta = adjacentsDelta ||
(singlePlane && ADJACENT_SINGLE_PLANE) ||
(planar && delta2 > 0 && ADJACENT_OUT) ||
(planar && delta2 < 0 && ADJACENT_IN) ||
ADJACENT_ALL;
if (!(testRle.end <= range[0] || testRle.start >= range[1])) {
adjacents.push([testRle, testJ, testK, newAdjacentDelta]);
}
}
}
return adjacents;
}
}
RLEVoxelMap.getScalarData = function (ArrayType = Uint8ClampedArray) {
const scalarData = new ArrayType(this.frameSize);
this.map.updateScalarData(scalarData);
return scalarData;
};
export default RLEVoxelMap;