UNPKG

@cornerstonejs/core

Version:
378 lines (377 loc) • 12.9 kB
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;