copper3d 3.4.3 → 3.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Utils/segmentation/RenderingUtils.d.ts +17 -0
- package/dist/Utils/segmentation/RenderingUtils.js +40 -7
- package/dist/Utils/segmentation/RenderingUtils.js.map +1 -1
- package/dist/Utils/segmentation/core/MarchingSquares.d.ts +9 -1
- package/dist/Utils/segmentation/core/MarchingSquares.js +42 -1
- package/dist/Utils/segmentation/core/MarchingSquares.js.map +1 -1
- package/dist/Utils/segmentation/core/MaskVolume.d.ts +18 -0
- package/dist/Utils/segmentation/core/MaskVolume.js +27 -0
- package/dist/Utils/segmentation/core/MaskVolume.js.map +1 -1
- package/dist/Utils/segmentation/tools/ZoomTool.js +48 -23
- package/dist/Utils/segmentation/tools/ZoomTool.js.map +1 -1
- package/dist/bundle.esm.js +158 -32
- package/dist/bundle.umd.js +158 -32
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/types/Utils/segmentation/RenderingUtils.d.ts +17 -0
- package/dist/types/Utils/segmentation/core/MarchingSquares.d.ts +9 -1
- package/dist/types/Utils/segmentation/core/MaskVolume.d.ts +18 -0
- package/dist/types/index.d.ts +1 -1
- package/package.json +1 -1
package/dist/bundle.umd.js
CHANGED
|
@@ -70879,6 +70879,17 @@ void main() {
|
|
|
70879
70879
|
* @throws {RangeError} If any dimension or the channel count is < 1.
|
|
70880
70880
|
*/
|
|
70881
70881
|
constructor(width, height, depth, channels = 1, customColorMap) {
|
|
70882
|
+
/**
|
|
70883
|
+
* Monotonic data-mutation counter.
|
|
70884
|
+
*
|
|
70885
|
+
* Incremented by every method that mutates voxel data (not color).
|
|
70886
|
+
* Consumers (e.g. the contour cache in RenderingUtils) key cached
|
|
70887
|
+
* derivations on this value so an edit automatically invalidates them —
|
|
70888
|
+
* it is impossible to forget an invalidation point because every write
|
|
70889
|
+
* bumps the version. Color-map changes do **not** bump it (colors are
|
|
70890
|
+
* applied at fill time, never cached).
|
|
70891
|
+
*/
|
|
70892
|
+
this.version = 0;
|
|
70882
70893
|
if (width < 1 || height < 1 || depth < 1) {
|
|
70883
70894
|
throw new RangeError(`Dimensions must be ≥ 1, got (${width}, ${height}, ${depth})`);
|
|
70884
70895
|
}
|
|
@@ -70968,6 +70979,16 @@ void main() {
|
|
|
70968
70979
|
*/
|
|
70969
70980
|
setVoxel(x, y, z, value, channel = 0) {
|
|
70970
70981
|
this.data[this.getIndex(x, y, z, channel)] = value;
|
|
70982
|
+
this.version++;
|
|
70983
|
+
}
|
|
70984
|
+
/**
|
|
70985
|
+
* Return the current data-mutation version.
|
|
70986
|
+
*
|
|
70987
|
+
* Increments on every voxel write. Use as a cache key so derived data
|
|
70988
|
+
* (e.g. extracted contours) is invalidated whenever the volume changes.
|
|
70989
|
+
*/
|
|
70990
|
+
getVersion() {
|
|
70991
|
+
return this.version;
|
|
70971
70992
|
}
|
|
70972
70993
|
// ── Color map management ──────────────────────────────────────────
|
|
70973
70994
|
/**
|
|
@@ -71124,6 +71145,7 @@ void main() {
|
|
|
71124
71145
|
throw new Error(`ImageData size mismatch: expected ${expectedW}×${expectedH}, ` +
|
|
71125
71146
|
`got ${imageData.width}×${imageData.height}`);
|
|
71126
71147
|
}
|
|
71148
|
+
this.version++;
|
|
71127
71149
|
const pixels = imageData.data;
|
|
71128
71150
|
const { width, height } = this.dims;
|
|
71129
71151
|
const ch = this.numChannels;
|
|
@@ -71225,6 +71247,7 @@ void main() {
|
|
|
71225
71247
|
throw new Error(`ImageData size mismatch: expected ${expectedW}×${expectedH}, ` +
|
|
71226
71248
|
`got ${imageData.width}×${imageData.height}`);
|
|
71227
71249
|
}
|
|
71250
|
+
this.version++;
|
|
71228
71251
|
// Build RGB→channel lookup map for O(1) reverse lookup
|
|
71229
71252
|
const rgbToChannel = this.buildRgbToChannelMap();
|
|
71230
71253
|
const pixels = imageData.data;
|
|
@@ -71489,6 +71512,7 @@ void main() {
|
|
|
71489
71512
|
throw new Error(`Data length mismatch: expected ${this.data.length}, got ${newData.length}`);
|
|
71490
71513
|
}
|
|
71491
71514
|
this.data = newData;
|
|
71515
|
+
this.version++;
|
|
71492
71516
|
}
|
|
71493
71517
|
/**
|
|
71494
71518
|
* Create an independent deep copy of this volume.
|
|
@@ -71528,6 +71552,7 @@ void main() {
|
|
|
71528
71552
|
*/
|
|
71529
71553
|
clear() {
|
|
71530
71554
|
this.data.fill(0);
|
|
71555
|
+
this.version++;
|
|
71531
71556
|
}
|
|
71532
71557
|
/**
|
|
71533
71558
|
* Check if the volume contains any non-zero voxel data.
|
|
@@ -71580,6 +71605,7 @@ void main() {
|
|
|
71580
71605
|
*/
|
|
71581
71606
|
clearSlice(sliceIndex, axis = 'z', channel) {
|
|
71582
71607
|
this.validateSliceIndex(sliceIndex, axis);
|
|
71608
|
+
this.version++;
|
|
71583
71609
|
const [sliceW, sliceH] = this.getSliceDimensions(axis);
|
|
71584
71610
|
const volData = this.data;
|
|
71585
71611
|
const nch = this.numChannels;
|
|
@@ -71691,6 +71717,7 @@ void main() {
|
|
|
71691
71717
|
*/
|
|
71692
71718
|
setSliceUint8(sliceIndex, data, axis = 'z') {
|
|
71693
71719
|
this.validateSliceIndex(sliceIndex, axis);
|
|
71720
|
+
this.version++;
|
|
71694
71721
|
const [sliceW, sliceH] = this.getSliceDimensions(axis);
|
|
71695
71722
|
const nch = this.numChannels;
|
|
71696
71723
|
const rowStride = this.dims.width * nch;
|
|
@@ -72134,10 +72161,18 @@ void main() {
|
|
|
72134
72161
|
/* 15 (full) */ [[[0, 0], [1, 0], [1, 1], [0, 1]]],
|
|
72135
72162
|
];
|
|
72136
72163
|
/**
|
|
72137
|
-
* Emit
|
|
72164
|
+
* Emit polygons covering the voxels where `labels === targetLabel`.
|
|
72138
72165
|
* Pure geometry function — no Canvas dependency. Consumers can build a Path2D
|
|
72139
72166
|
* (see {@link extractLabelContours}) or walk the vertex arrays directly
|
|
72140
72167
|
* (tests, export, stroke rendering).
|
|
72168
|
+
*
|
|
72169
|
+
* Boundary cells (marching-squares cases 1–14) emit their cut polygon as
|
|
72170
|
+
* usual. Solid interior (case 15) is **coalesced into horizontal run-length
|
|
72171
|
+
* rectangles** instead of one unit square per cell, collapsing the subpath
|
|
72172
|
+
* count from O(area) to ≈ O(perimeter + rows). The filled region is
|
|
72173
|
+
* pixel-identical (contiguous full-cell squares tile exactly into a rectangle
|
|
72174
|
+
* with the same TL→TR→BR→BL winding), which is what keeps slice-scrubbing
|
|
72175
|
+
* fast on large masks without changing the rendered silhouette.
|
|
72141
72176
|
*/
|
|
72142
72177
|
function extractLabelPolygons(labels, width, height, targetLabel, stride = 1, channelOffset = 0, bbox) {
|
|
72143
72178
|
var _a, _b, _c, _d;
|
|
@@ -72159,7 +72194,25 @@ void main() {
|
|
|
72159
72194
|
// map to voxel-square CENTERS in render space. Voxel (i, j) occupies
|
|
72160
72195
|
// render square [i, i+1] × [j, j+1]; its center is (i+0.5, j+0.5).
|
|
72161
72196
|
const SHIFT = 0.5;
|
|
72197
|
+
// Emit a single rectangle covering a contiguous run of full (code 15)
|
|
72198
|
+
// cells [runStart .. runEnd] on row j. A full cell i covers render square
|
|
72199
|
+
// [i+0.5, i+1.5] × [j+0.5, j+1.5]; consecutive full cells tile perfectly,
|
|
72200
|
+
// so the run is pixel-identical to the per-cell squares it replaces.
|
|
72201
|
+
// Vertices use the same TL→TR→BR→BL (CCW screen-space) winding as the
|
|
72202
|
+
// case-15 unit square, so the nonzero fill matches at shared edges.
|
|
72203
|
+
const pushFullRun = (runStart, runEnd, j) => {
|
|
72204
|
+
const x0 = runStart + SHIFT;
|
|
72205
|
+
const x1 = runEnd + 1 + SHIFT;
|
|
72206
|
+
const y0 = j + SHIFT;
|
|
72207
|
+
const y1 = j + 1 + SHIFT;
|
|
72208
|
+
out.push([[x0, y0], [x1, y0], [x1, y1], [x0, y1]]);
|
|
72209
|
+
};
|
|
72162
72210
|
for (let j = j0; j < j1; j++) {
|
|
72211
|
+
// Index where the current contiguous run of full (code 15) cells began,
|
|
72212
|
+
// or -1 when no run is open. Coalescing the solid interior into row runs
|
|
72213
|
+
// drops the per-cell square count from O(area) to O(rows + perimeter),
|
|
72214
|
+
// which is what keeps fast slice-scrubbing smooth on large masks.
|
|
72215
|
+
let runStart = -1;
|
|
72163
72216
|
for (let i = i0; i < i1; i++) {
|
|
72164
72217
|
const tl = sample(i, j);
|
|
72165
72218
|
const tr = sample(i + 1, j);
|
|
@@ -72169,6 +72222,17 @@ void main() {
|
|
|
72169
72222
|
(tr ? 4 : 0) |
|
|
72170
72223
|
(br ? 2 : 0) |
|
|
72171
72224
|
(bl ? 1 : 0);
|
|
72225
|
+
if (code === 15) {
|
|
72226
|
+
// Extend (or open) the current full-cell run; defer emission.
|
|
72227
|
+
if (runStart === -1)
|
|
72228
|
+
runStart = i;
|
|
72229
|
+
continue;
|
|
72230
|
+
}
|
|
72231
|
+
// Non-full cell ends any open run — flush it as one rectangle.
|
|
72232
|
+
if (runStart !== -1) {
|
|
72233
|
+
pushFullRun(runStart, i - 1, j);
|
|
72234
|
+
runStart = -1;
|
|
72235
|
+
}
|
|
72172
72236
|
if (code === 0)
|
|
72173
72237
|
continue;
|
|
72174
72238
|
const polygons = CELL_POLYGONS[code];
|
|
@@ -72181,6 +72245,10 @@ void main() {
|
|
|
72181
72245
|
out.push(abs);
|
|
72182
72246
|
}
|
|
72183
72247
|
}
|
|
72248
|
+
// Flush a run that reached the end of the row (last visited cell is i1-1).
|
|
72249
|
+
if (runStart !== -1) {
|
|
72250
|
+
pushFullRun(runStart, i1 - 1, j);
|
|
72251
|
+
}
|
|
72184
72252
|
}
|
|
72185
72253
|
return out;
|
|
72186
72254
|
}
|
|
@@ -73645,6 +73713,23 @@ void main() {
|
|
|
73645
73713
|
this._reusableSliceBuffer = null;
|
|
73646
73714
|
this._reusableBufferWidth = 0;
|
|
73647
73715
|
this._reusableBufferHeight = 0;
|
|
73716
|
+
/**
|
|
73717
|
+
* Per-layer contour cache.
|
|
73718
|
+
*
|
|
73719
|
+
* Caches the *expensive* part of slice rendering — `getSliceUint8` +
|
|
73720
|
+
* `findLabelsInSlice` + `extractLabelContours` (Path2D build) — keyed by
|
|
73721
|
+
* `${axis}:${sliceIndex}:${volume.version}`. The contours live in voxel
|
|
73722
|
+
* coordinates; zoom only changes the `ctx.scale` transform, so on zoom /
|
|
73723
|
+
* recomposite we reuse the cached Path2D and just re-fill, skipping
|
|
73724
|
+
* marching-squares entirely. A volume edit bumps its version → cache miss
|
|
73725
|
+
* → recompute (correct, automatic). Colors and channel visibility are
|
|
73726
|
+
* applied at fill time and intentionally *not* cached, so toggling them
|
|
73727
|
+
* needs no recompute.
|
|
73728
|
+
*
|
|
73729
|
+
* One entry per layer (the current slice). Switching slice/axis or
|
|
73730
|
+
* editing overwrites it.
|
|
73731
|
+
*/
|
|
73732
|
+
this._contourCache = new Map();
|
|
73648
73733
|
this.state = state;
|
|
73649
73734
|
}
|
|
73650
73735
|
// ── Volume Accessor Helpers ──────────────────────────────────────
|
|
@@ -73743,14 +73828,27 @@ void main() {
|
|
|
73743
73828
|
const volume = this.getVolumeForLayer(layer);
|
|
73744
73829
|
if (!volume)
|
|
73745
73830
|
return;
|
|
73746
|
-
const channelVis = this.state.gui_states.layerChannel.channelVisibility[layer];
|
|
73747
|
-
const slice = volume.getSliceUint8(sliceIndex, axis);
|
|
73748
|
-
const W = slice.width;
|
|
73749
|
-
const H = slice.height;
|
|
73750
73831
|
const stride = volume.getChannels();
|
|
73751
|
-
const
|
|
73752
|
-
|
|
73832
|
+
const cacheKey = `${axis}:${sliceIndex}:${volume.getVersion()}`;
|
|
73833
|
+
// Cache miss → run the expensive extraction once. Hits (zoom,
|
|
73834
|
+
// recomposite, contrast toggle) skip straight to the fill below.
|
|
73835
|
+
let entry = this._contourCache.get(layer);
|
|
73836
|
+
if (!entry || entry.key !== cacheKey) {
|
|
73837
|
+
const slice = volume.getSliceUint8(sliceIndex, axis);
|
|
73838
|
+
const W = slice.width;
|
|
73839
|
+
const H = slice.height;
|
|
73840
|
+
const labels = findLabelsInSlice(slice.data, W, H, stride, 0);
|
|
73841
|
+
const paths = new Map();
|
|
73842
|
+
for (const lbl of labels) {
|
|
73843
|
+
paths.set(lbl, extractLabelContours(slice.data, W, H, lbl, stride, 0));
|
|
73844
|
+
}
|
|
73845
|
+
entry = { key: cacheKey, W, H, labels, paths };
|
|
73846
|
+
this._contourCache.set(layer, entry);
|
|
73847
|
+
}
|
|
73848
|
+
if (entry.labels.length === 0)
|
|
73753
73849
|
return;
|
|
73850
|
+
const { W, H, labels, paths } = entry;
|
|
73851
|
+
const channelVis = this.state.gui_states.layerChannel.channelVisibility[layer];
|
|
73754
73852
|
targetCtx.save();
|
|
73755
73853
|
// Vector fill — imageSmoothingEnabled is irrelevant here, but keep
|
|
73756
73854
|
// it off to match the rest of the pipeline.
|
|
@@ -73767,8 +73865,10 @@ void main() {
|
|
|
73767
73865
|
for (const lbl of labels) {
|
|
73768
73866
|
if (channelVis && channelVis[lbl] === false)
|
|
73769
73867
|
continue;
|
|
73868
|
+
const path = paths.get(lbl);
|
|
73869
|
+
if (!path)
|
|
73870
|
+
continue;
|
|
73770
73871
|
const color = volume.getChannelColor(lbl);
|
|
73771
|
-
const path = extractLabelContours(slice.data, W, H, lbl, stride, 0);
|
|
73772
73872
|
targetCtx.fillStyle =
|
|
73773
73873
|
`rgba(${color.r}, ${color.g}, ${color.b}, ${color.a / 255})`;
|
|
73774
73874
|
targetCtx.fill(path, 'nonzero');
|
|
@@ -73786,6 +73886,7 @@ void main() {
|
|
|
73786
73886
|
this._reusableSliceBuffer = null;
|
|
73787
73887
|
this._reusableBufferWidth = 0;
|
|
73788
73888
|
this._reusableBufferHeight = 0;
|
|
73889
|
+
this._contourCache.clear();
|
|
73789
73890
|
}
|
|
73790
73891
|
/**
|
|
73791
73892
|
* Apply the same flip transform used by flipDisplayImageByAxis() to any
|
|
@@ -74544,7 +74645,30 @@ void main() {
|
|
|
74544
74645
|
}
|
|
74545
74646
|
// ===== Zoom Wheel =====
|
|
74546
74647
|
configMouseZoomWheel() {
|
|
74547
|
-
|
|
74648
|
+
// Coalesce multiple wheel events fired within one frame into a single
|
|
74649
|
+
// resizePaintArea() call via requestAnimationFrame. Each resize triggers
|
|
74650
|
+
// a full display redraw (+ mask recomposite), so without coalescing a
|
|
74651
|
+
// fast scroll fires dozens of these per frame. The accumulation is done
|
|
74652
|
+
// on the logical zoom (sizeFactor) — not the DOM offsetWidth — so deltas
|
|
74653
|
+
// still compound correctly even though the DOM size hasn't updated yet.
|
|
74654
|
+
let rafId = null;
|
|
74655
|
+
let pending = null;
|
|
74656
|
+
const flush = () => {
|
|
74657
|
+
rafId = null;
|
|
74658
|
+
if (!pending)
|
|
74659
|
+
return;
|
|
74660
|
+
const p = pending;
|
|
74661
|
+
pending = null;
|
|
74662
|
+
if (p.recenter) {
|
|
74663
|
+
this.callbacks.resetPaintAreaUIPosition();
|
|
74664
|
+
}
|
|
74665
|
+
else {
|
|
74666
|
+
this.callbacks.resetPaintAreaUIPosition(p.l, p.t);
|
|
74667
|
+
}
|
|
74668
|
+
this.callbacks.resizePaintArea(p.moveDistance);
|
|
74669
|
+
this.callbacks.setIsDrawFalse(1000);
|
|
74670
|
+
this.ctx.nrrd_states.view.sizeFactor = p.moveDistance;
|
|
74671
|
+
};
|
|
74548
74672
|
return (e) => {
|
|
74549
74673
|
var _a, _b;
|
|
74550
74674
|
if ((_a = this.ctx.eventRouter) === null || _a === void 0 ? void 0 : _a.isShiftHeld()) {
|
|
@@ -74559,36 +74683,38 @@ void main() {
|
|
|
74559
74683
|
const delta = e.detail ? e.detail > 0 : e.wheelDelta < 0;
|
|
74560
74684
|
this.ctx.protectedData.isDrawing = true;
|
|
74561
74685
|
const rect = this.container.getBoundingClientRect();
|
|
74562
|
-
const
|
|
74563
|
-
|
|
74564
|
-
|
|
74565
|
-
|
|
74566
|
-
|
|
74567
|
-
const ratioT = (e.clientY -
|
|
74568
|
-
rect.top -
|
|
74569
|
-
this.mainAreaContainer.offsetTop -
|
|
74570
|
-
this.ctx.protectedData.canvases.drawingCanvas.offsetTop) /
|
|
74571
|
-
this.ctx.protectedData.canvases.drawingCanvas.offsetHeight;
|
|
74686
|
+
const drawingCanvas = this.ctx.protectedData.canvases.drawingCanvas;
|
|
74687
|
+
const ratioL = (e.clientX - rect.left - this.mainAreaContainer.offsetLeft - drawingCanvas.offsetLeft) /
|
|
74688
|
+
drawingCanvas.offsetWidth;
|
|
74689
|
+
const ratioT = (e.clientY - rect.top - this.mainAreaContainer.offsetTop - drawingCanvas.offsetTop) /
|
|
74690
|
+
drawingCanvas.offsetHeight;
|
|
74572
74691
|
const ratioDelta = !delta ? 1 + 0.1 : 1 - 0.1;
|
|
74573
|
-
|
|
74574
|
-
|
|
74575
|
-
const
|
|
74576
|
-
|
|
74577
|
-
moveDistance = w / this.ctx.nrrd_states.image.originWidth;
|
|
74692
|
+
// Compound from the latest pending target (this frame) or the current
|
|
74693
|
+
// committed sizeFactor.
|
|
74694
|
+
const base = pending ? pending.moveDistance : this.ctx.nrrd_states.view.sizeFactor;
|
|
74695
|
+
let moveDistance = base * ratioDelta;
|
|
74578
74696
|
if (moveDistance > 8) {
|
|
74697
|
+
// Max zoom: original behaviour clamps without re-laying out.
|
|
74579
74698
|
moveDistance = 8;
|
|
74699
|
+
this.ctx.nrrd_states.view.sizeFactor = moveDistance;
|
|
74700
|
+
this.callbacks.setIsDrawFalse(1000);
|
|
74701
|
+
return;
|
|
74580
74702
|
}
|
|
74581
|
-
|
|
74703
|
+
if (moveDistance < 1) {
|
|
74582
74704
|
moveDistance = 1;
|
|
74583
|
-
|
|
74584
|
-
this.callbacks.resizePaintArea(moveDistance);
|
|
74705
|
+
pending = { moveDistance, l: 0, t: 0, recenter: true };
|
|
74585
74706
|
}
|
|
74586
74707
|
else {
|
|
74587
|
-
this.
|
|
74588
|
-
this.
|
|
74708
|
+
// Target displayed size for this zoom level → keep cursor anchored.
|
|
74709
|
+
const w = this.ctx.nrrd_states.image.originWidth * moveDistance;
|
|
74710
|
+
const h = this.ctx.nrrd_states.image.originHeight * moveDistance;
|
|
74711
|
+
const l = Math.round(e.clientX - this.mainAreaContainer.offsetLeft - w * ratioL - rect.left);
|
|
74712
|
+
const t = Math.round(e.clientY - this.mainAreaContainer.offsetTop - h * ratioT - rect.top);
|
|
74713
|
+
pending = { moveDistance, l, t, recenter: false };
|
|
74714
|
+
}
|
|
74715
|
+
if (rafId === null) {
|
|
74716
|
+
rafId = requestAnimationFrame(flush);
|
|
74589
74717
|
}
|
|
74590
|
-
this.callbacks.setIsDrawFalse(1000);
|
|
74591
|
-
this.ctx.nrrd_states.view.sizeFactor = moveDistance;
|
|
74592
74718
|
};
|
|
74593
74719
|
}
|
|
74594
74720
|
}
|
|
@@ -79332,7 +79458,7 @@ void main() {
|
|
|
79332
79458
|
}
|
|
79333
79459
|
|
|
79334
79460
|
// import * as kiwrious from "copper3d_plugin_heart_k";
|
|
79335
|
-
const REVISION = "v3.4.
|
|
79461
|
+
const REVISION = "v3.4.5-beta";
|
|
79336
79462
|
console.log(`%cCopper3D Visualisation %cBeta:${REVISION}`, "padding: 3px;color:white; background:#023047", "padding: 3px;color:white; background:#f50a25");
|
|
79337
79463
|
|
|
79338
79464
|
exports.CHANNEL_COLORS = CHANNEL_COLORS;
|
package/dist/index.d.ts
CHANGED
|
@@ -25,6 +25,6 @@ import type { ToolMode, IAnnotationCallbacks } from "./Utils/segmentation/core/t
|
|
|
25
25
|
import { CHANNEL_COLORS, CHANNEL_HEX_COLORS, rgbaToHex, rgbaToCss } from "./Utils/segmentation/core/index";
|
|
26
26
|
import type { LayerId, ChannelValue } from "./Utils/segmentation/core/index";
|
|
27
27
|
import "./css/style.css";
|
|
28
|
-
export declare const REVISION = "v3.4.
|
|
28
|
+
export declare const REVISION = "v3.4.5-beta";
|
|
29
29
|
export { copperRenderer, copperRendererOnDemond, copperMSceneRenderer, setHDRFilePath, addLabelToScene, convert3DPostoScreenPos, convertScreenPosto3DPos, addBoxHelper, fullScreenListenner, configKiwriousHeart, copperScene, copperSceneOnDemond, copperMScene, CameraViewPoint, kiwrious, NrrdTools, loading, Copper3dTrackballControls, createTexture2D_NRRD, MeshNodeTool, throttle, removeGuiFolderChilden, CHANNEL_COLORS, CHANNEL_HEX_COLORS, rgbaToHex, rgbaToCss, GaussianSmoother, };
|
|
30
30
|
export type { positionType, screenPosType, optsType, nrrdMeshesType, nrrdSliceType, SensorDecodedValue_kiwrious, SensorReadResult_kiwrious, HeartRateResult_kiwrious, loadingBarType, IPaintImage, exportPaintImageType, IOptVTKLoader, ICommXYZ, IGUIStates, IGuiParameterSettings, INrrdStates, NrrdState, GuiState, IGuiMeta, ToolMode, IAnnotationCallbacks, LayerId, ChannelValue, };
|
package/dist/index.js
CHANGED
|
@@ -21,7 +21,7 @@ import { MeshNodeTool } from "./Utils/MeshNodeTool";
|
|
|
21
21
|
import { removeGuiFolderChilden } from "./Utils/segmentation/coreTools/gui";
|
|
22
22
|
import { CHANNEL_COLORS, CHANNEL_HEX_COLORS, rgbaToHex, rgbaToCss } from "./Utils/segmentation/core/index";
|
|
23
23
|
import "./css/style.css";
|
|
24
|
-
export const REVISION = "v3.4.
|
|
24
|
+
export const REVISION = "v3.4.5-beta";
|
|
25
25
|
console.log(`%cCopper3D Visualisation %cBeta:${REVISION}`, "padding: 3px;color:white; background:#023047", "padding: 3px;color:white; background:#f50a25");
|
|
26
26
|
export { copperRenderer, copperRendererOnDemond, copperMSceneRenderer, setHDRFilePath, addLabelToScene, convert3DPostoScreenPos, convertScreenPosto3DPos, addBoxHelper, fullScreenListenner, configKiwriousHeart, copperScene, copperSceneOnDemond, copperMScene, CameraViewPoint, kiwrious, NrrdTools, loading, Copper3dTrackballControls, createTexture2D_NRRD, MeshNodeTool, throttle, removeGuiFolderChilden, CHANNEL_COLORS, CHANNEL_HEX_COLORS, rgbaToHex, rgbaToCss, GaussianSmoother, };
|
|
27
27
|
//# sourceMappingURL=index.js.map
|
|
@@ -18,6 +18,23 @@ export declare class RenderingUtils {
|
|
|
18
18
|
private _reusableSliceBuffer;
|
|
19
19
|
private _reusableBufferWidth;
|
|
20
20
|
private _reusableBufferHeight;
|
|
21
|
+
/**
|
|
22
|
+
* Per-layer contour cache.
|
|
23
|
+
*
|
|
24
|
+
* Caches the *expensive* part of slice rendering — `getSliceUint8` +
|
|
25
|
+
* `findLabelsInSlice` + `extractLabelContours` (Path2D build) — keyed by
|
|
26
|
+
* `${axis}:${sliceIndex}:${volume.version}`. The contours live in voxel
|
|
27
|
+
* coordinates; zoom only changes the `ctx.scale` transform, so on zoom /
|
|
28
|
+
* recomposite we reuse the cached Path2D and just re-fill, skipping
|
|
29
|
+
* marching-squares entirely. A volume edit bumps its version → cache miss
|
|
30
|
+
* → recompute (correct, automatic). Colors and channel visibility are
|
|
31
|
+
* applied at fill time and intentionally *not* cached, so toggling them
|
|
32
|
+
* needs no recompute.
|
|
33
|
+
*
|
|
34
|
+
* One entry per layer (the current slice). Switching slice/axis or
|
|
35
|
+
* editing overwrites it.
|
|
36
|
+
*/
|
|
37
|
+
private _contourCache;
|
|
21
38
|
constructor(state: CanvasState);
|
|
22
39
|
/**
|
|
23
40
|
* Get MaskVolume for a specific layer
|
|
@@ -35,10 +35,18 @@ export interface ContourBBox {
|
|
|
35
35
|
/** A single polygon's vertices in voxel-space coordinates. */
|
|
36
36
|
export type ContourPolygon = ReadonlyArray<readonly [number, number]>;
|
|
37
37
|
/**
|
|
38
|
-
* Emit
|
|
38
|
+
* Emit polygons covering the voxels where `labels === targetLabel`.
|
|
39
39
|
* Pure geometry function — no Canvas dependency. Consumers can build a Path2D
|
|
40
40
|
* (see {@link extractLabelContours}) or walk the vertex arrays directly
|
|
41
41
|
* (tests, export, stroke rendering).
|
|
42
|
+
*
|
|
43
|
+
* Boundary cells (marching-squares cases 1–14) emit their cut polygon as
|
|
44
|
+
* usual. Solid interior (case 15) is **coalesced into horizontal run-length
|
|
45
|
+
* rectangles** instead of one unit square per cell, collapsing the subpath
|
|
46
|
+
* count from O(area) to ≈ O(perimeter + rows). The filled region is
|
|
47
|
+
* pixel-identical (contiguous full-cell squares tile exactly into a rectangle
|
|
48
|
+
* with the same TL→TR→BR→BL winding), which is what keeps slice-scrubbing
|
|
49
|
+
* fast on large masks without changing the rendered silhouette.
|
|
42
50
|
*/
|
|
43
51
|
export declare function extractLabelPolygons(labels: Uint8Array, width: number, height: number, targetLabel: number, stride?: number, channelOffset?: number, bbox?: ContourBBox): ContourPolygon[];
|
|
44
52
|
/**
|
|
@@ -45,6 +45,17 @@ export declare class MaskVolume {
|
|
|
45
45
|
private readonly bytesPerSlice;
|
|
46
46
|
/** Per-channel color map used for colored rendering modes. */
|
|
47
47
|
private colorMap;
|
|
48
|
+
/**
|
|
49
|
+
* Monotonic data-mutation counter.
|
|
50
|
+
*
|
|
51
|
+
* Incremented by every method that mutates voxel data (not color).
|
|
52
|
+
* Consumers (e.g. the contour cache in RenderingUtils) key cached
|
|
53
|
+
* derivations on this value so an edit automatically invalidates them —
|
|
54
|
+
* it is impossible to forget an invalidation point because every write
|
|
55
|
+
* bumps the version. Color-map changes do **not** bump it (colors are
|
|
56
|
+
* applied at fill time, never cached).
|
|
57
|
+
*/
|
|
58
|
+
private version;
|
|
48
59
|
/**
|
|
49
60
|
* Create a new MaskVolume.
|
|
50
61
|
*
|
|
@@ -105,6 +116,13 @@ export declare class MaskVolume {
|
|
|
105
116
|
* @throws {RangeError} If any coordinate is out of bounds.
|
|
106
117
|
*/
|
|
107
118
|
setVoxel(x: number, y: number, z: number, value: number, channel?: number): void;
|
|
119
|
+
/**
|
|
120
|
+
* Return the current data-mutation version.
|
|
121
|
+
*
|
|
122
|
+
* Increments on every voxel write. Use as a cache key so derived data
|
|
123
|
+
* (e.g. extracted contours) is invalidated whenever the volume changes.
|
|
124
|
+
*/
|
|
125
|
+
getVersion(): number;
|
|
108
126
|
/**
|
|
109
127
|
* Update the color for a specific label/channel in the color map.
|
|
110
128
|
*
|
package/dist/types/index.d.ts
CHANGED
|
@@ -25,6 +25,6 @@ import type { ToolMode, IAnnotationCallbacks } from "./Utils/segmentation/core/t
|
|
|
25
25
|
import { CHANNEL_COLORS, CHANNEL_HEX_COLORS, rgbaToHex, rgbaToCss } from "./Utils/segmentation/core/index";
|
|
26
26
|
import type { LayerId, ChannelValue } from "./Utils/segmentation/core/index";
|
|
27
27
|
import "./css/style.css";
|
|
28
|
-
export declare const REVISION = "v3.4.
|
|
28
|
+
export declare const REVISION = "v3.4.5-beta";
|
|
29
29
|
export { copperRenderer, copperRendererOnDemond, copperMSceneRenderer, setHDRFilePath, addLabelToScene, convert3DPostoScreenPos, convertScreenPosto3DPos, addBoxHelper, fullScreenListenner, configKiwriousHeart, copperScene, copperSceneOnDemond, copperMScene, CameraViewPoint, kiwrious, NrrdTools, loading, Copper3dTrackballControls, createTexture2D_NRRD, MeshNodeTool, throttle, removeGuiFolderChilden, CHANNEL_COLORS, CHANNEL_HEX_COLORS, rgbaToHex, rgbaToCss, GaussianSmoother, };
|
|
30
30
|
export type { positionType, screenPosType, optsType, nrrdMeshesType, nrrdSliceType, SensorDecodedValue_kiwrious, SensorReadResult_kiwrious, HeartRateResult_kiwrious, loadingBarType, IPaintImage, exportPaintImageType, IOptVTKLoader, ICommXYZ, IGUIStates, IGuiParameterSettings, INrrdStates, NrrdState, GuiState, IGuiMeta, ToolMode, IAnnotationCallbacks, LayerId, ChannelValue, };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "copper3d",
|
|
3
3
|
"description": "A 3d visualisation package base on threejs provides multiple scenes and Nrrd image load funtion.",
|
|
4
|
-
"version": "3.4.
|
|
4
|
+
"version": "3.4.5",
|
|
5
5
|
"main": "dist/bundle.umd.js",
|
|
6
6
|
"moudle": "dist/bundle.esm.js",
|
|
7
7
|
"types": "dist/types/index.d.ts",
|