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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ZoomTool.js","sourceRoot":"","sources":["../../../../src/Utils/segmentation/tools/ZoomTool.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAItC,MAAM,OAAO,QAAS,SAAQ,QAAQ;IAKpC,YACE,GAAgB,EAChB,SAAsB,EACtB,iBAAiC,EACjC,SAAuB;QAEvB,KAAK,CAAC,GAAG,CAAC,CAAC;QACX,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,iBAAiB,GAAG,iBAAiB,CAAC;QAC3C,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED,yBAAyB;IAEzB,oBAAoB;QAClB,IAAI,YAAY,GAAG,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"ZoomTool.js","sourceRoot":"","sources":["../../../../src/Utils/segmentation/tools/ZoomTool.ts"],"names":[],"mappings":"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"}
|
package/dist/bundle.esm.js
CHANGED
|
@@ -70871,6 +70871,17 @@ class MaskVolume {
|
|
|
70871
70871
|
* @throws {RangeError} If any dimension or the channel count is < 1.
|
|
70872
70872
|
*/
|
|
70873
70873
|
constructor(width, height, depth, channels = 1, customColorMap) {
|
|
70874
|
+
/**
|
|
70875
|
+
* Monotonic data-mutation counter.
|
|
70876
|
+
*
|
|
70877
|
+
* Incremented by every method that mutates voxel data (not color).
|
|
70878
|
+
* Consumers (e.g. the contour cache in RenderingUtils) key cached
|
|
70879
|
+
* derivations on this value so an edit automatically invalidates them —
|
|
70880
|
+
* it is impossible to forget an invalidation point because every write
|
|
70881
|
+
* bumps the version. Color-map changes do **not** bump it (colors are
|
|
70882
|
+
* applied at fill time, never cached).
|
|
70883
|
+
*/
|
|
70884
|
+
this.version = 0;
|
|
70874
70885
|
if (width < 1 || height < 1 || depth < 1) {
|
|
70875
70886
|
throw new RangeError(`Dimensions must be ≥ 1, got (${width}, ${height}, ${depth})`);
|
|
70876
70887
|
}
|
|
@@ -70960,6 +70971,16 @@ class MaskVolume {
|
|
|
70960
70971
|
*/
|
|
70961
70972
|
setVoxel(x, y, z, value, channel = 0) {
|
|
70962
70973
|
this.data[this.getIndex(x, y, z, channel)] = value;
|
|
70974
|
+
this.version++;
|
|
70975
|
+
}
|
|
70976
|
+
/**
|
|
70977
|
+
* Return the current data-mutation version.
|
|
70978
|
+
*
|
|
70979
|
+
* Increments on every voxel write. Use as a cache key so derived data
|
|
70980
|
+
* (e.g. extracted contours) is invalidated whenever the volume changes.
|
|
70981
|
+
*/
|
|
70982
|
+
getVersion() {
|
|
70983
|
+
return this.version;
|
|
70963
70984
|
}
|
|
70964
70985
|
// ── Color map management ──────────────────────────────────────────
|
|
70965
70986
|
/**
|
|
@@ -71116,6 +71137,7 @@ class MaskVolume {
|
|
|
71116
71137
|
throw new Error(`ImageData size mismatch: expected ${expectedW}×${expectedH}, ` +
|
|
71117
71138
|
`got ${imageData.width}×${imageData.height}`);
|
|
71118
71139
|
}
|
|
71140
|
+
this.version++;
|
|
71119
71141
|
const pixels = imageData.data;
|
|
71120
71142
|
const { width, height } = this.dims;
|
|
71121
71143
|
const ch = this.numChannels;
|
|
@@ -71217,6 +71239,7 @@ class MaskVolume {
|
|
|
71217
71239
|
throw new Error(`ImageData size mismatch: expected ${expectedW}×${expectedH}, ` +
|
|
71218
71240
|
`got ${imageData.width}×${imageData.height}`);
|
|
71219
71241
|
}
|
|
71242
|
+
this.version++;
|
|
71220
71243
|
// Build RGB→channel lookup map for O(1) reverse lookup
|
|
71221
71244
|
const rgbToChannel = this.buildRgbToChannelMap();
|
|
71222
71245
|
const pixels = imageData.data;
|
|
@@ -71481,6 +71504,7 @@ class MaskVolume {
|
|
|
71481
71504
|
throw new Error(`Data length mismatch: expected ${this.data.length}, got ${newData.length}`);
|
|
71482
71505
|
}
|
|
71483
71506
|
this.data = newData;
|
|
71507
|
+
this.version++;
|
|
71484
71508
|
}
|
|
71485
71509
|
/**
|
|
71486
71510
|
* Create an independent deep copy of this volume.
|
|
@@ -71520,6 +71544,7 @@ class MaskVolume {
|
|
|
71520
71544
|
*/
|
|
71521
71545
|
clear() {
|
|
71522
71546
|
this.data.fill(0);
|
|
71547
|
+
this.version++;
|
|
71523
71548
|
}
|
|
71524
71549
|
/**
|
|
71525
71550
|
* Check if the volume contains any non-zero voxel data.
|
|
@@ -71572,6 +71597,7 @@ class MaskVolume {
|
|
|
71572
71597
|
*/
|
|
71573
71598
|
clearSlice(sliceIndex, axis = 'z', channel) {
|
|
71574
71599
|
this.validateSliceIndex(sliceIndex, axis);
|
|
71600
|
+
this.version++;
|
|
71575
71601
|
const [sliceW, sliceH] = this.getSliceDimensions(axis);
|
|
71576
71602
|
const volData = this.data;
|
|
71577
71603
|
const nch = this.numChannels;
|
|
@@ -71683,6 +71709,7 @@ class MaskVolume {
|
|
|
71683
71709
|
*/
|
|
71684
71710
|
setSliceUint8(sliceIndex, data, axis = 'z') {
|
|
71685
71711
|
this.validateSliceIndex(sliceIndex, axis);
|
|
71712
|
+
this.version++;
|
|
71686
71713
|
const [sliceW, sliceH] = this.getSliceDimensions(axis);
|
|
71687
71714
|
const nch = this.numChannels;
|
|
71688
71715
|
const rowStride = this.dims.width * nch;
|
|
@@ -72126,10 +72153,18 @@ const CELL_POLYGONS = [
|
|
|
72126
72153
|
/* 15 (full) */ [[[0, 0], [1, 0], [1, 1], [0, 1]]],
|
|
72127
72154
|
];
|
|
72128
72155
|
/**
|
|
72129
|
-
* Emit
|
|
72156
|
+
* Emit polygons covering the voxels where `labels === targetLabel`.
|
|
72130
72157
|
* Pure geometry function — no Canvas dependency. Consumers can build a Path2D
|
|
72131
72158
|
* (see {@link extractLabelContours}) or walk the vertex arrays directly
|
|
72132
72159
|
* (tests, export, stroke rendering).
|
|
72160
|
+
*
|
|
72161
|
+
* Boundary cells (marching-squares cases 1–14) emit their cut polygon as
|
|
72162
|
+
* usual. Solid interior (case 15) is **coalesced into horizontal run-length
|
|
72163
|
+
* rectangles** instead of one unit square per cell, collapsing the subpath
|
|
72164
|
+
* count from O(area) to ≈ O(perimeter + rows). The filled region is
|
|
72165
|
+
* pixel-identical (contiguous full-cell squares tile exactly into a rectangle
|
|
72166
|
+
* with the same TL→TR→BR→BL winding), which is what keeps slice-scrubbing
|
|
72167
|
+
* fast on large masks without changing the rendered silhouette.
|
|
72133
72168
|
*/
|
|
72134
72169
|
function extractLabelPolygons(labels, width, height, targetLabel, stride = 1, channelOffset = 0, bbox) {
|
|
72135
72170
|
var _a, _b, _c, _d;
|
|
@@ -72151,7 +72186,25 @@ function extractLabelPolygons(labels, width, height, targetLabel, stride = 1, ch
|
|
|
72151
72186
|
// map to voxel-square CENTERS in render space. Voxel (i, j) occupies
|
|
72152
72187
|
// render square [i, i+1] × [j, j+1]; its center is (i+0.5, j+0.5).
|
|
72153
72188
|
const SHIFT = 0.5;
|
|
72189
|
+
// Emit a single rectangle covering a contiguous run of full (code 15)
|
|
72190
|
+
// cells [runStart .. runEnd] on row j. A full cell i covers render square
|
|
72191
|
+
// [i+0.5, i+1.5] × [j+0.5, j+1.5]; consecutive full cells tile perfectly,
|
|
72192
|
+
// so the run is pixel-identical to the per-cell squares it replaces.
|
|
72193
|
+
// Vertices use the same TL→TR→BR→BL (CCW screen-space) winding as the
|
|
72194
|
+
// case-15 unit square, so the nonzero fill matches at shared edges.
|
|
72195
|
+
const pushFullRun = (runStart, runEnd, j) => {
|
|
72196
|
+
const x0 = runStart + SHIFT;
|
|
72197
|
+
const x1 = runEnd + 1 + SHIFT;
|
|
72198
|
+
const y0 = j + SHIFT;
|
|
72199
|
+
const y1 = j + 1 + SHIFT;
|
|
72200
|
+
out.push([[x0, y0], [x1, y0], [x1, y1], [x0, y1]]);
|
|
72201
|
+
};
|
|
72154
72202
|
for (let j = j0; j < j1; j++) {
|
|
72203
|
+
// Index where the current contiguous run of full (code 15) cells began,
|
|
72204
|
+
// or -1 when no run is open. Coalescing the solid interior into row runs
|
|
72205
|
+
// drops the per-cell square count from O(area) to O(rows + perimeter),
|
|
72206
|
+
// which is what keeps fast slice-scrubbing smooth on large masks.
|
|
72207
|
+
let runStart = -1;
|
|
72155
72208
|
for (let i = i0; i < i1; i++) {
|
|
72156
72209
|
const tl = sample(i, j);
|
|
72157
72210
|
const tr = sample(i + 1, j);
|
|
@@ -72161,6 +72214,17 @@ function extractLabelPolygons(labels, width, height, targetLabel, stride = 1, ch
|
|
|
72161
72214
|
(tr ? 4 : 0) |
|
|
72162
72215
|
(br ? 2 : 0) |
|
|
72163
72216
|
(bl ? 1 : 0);
|
|
72217
|
+
if (code === 15) {
|
|
72218
|
+
// Extend (or open) the current full-cell run; defer emission.
|
|
72219
|
+
if (runStart === -1)
|
|
72220
|
+
runStart = i;
|
|
72221
|
+
continue;
|
|
72222
|
+
}
|
|
72223
|
+
// Non-full cell ends any open run — flush it as one rectangle.
|
|
72224
|
+
if (runStart !== -1) {
|
|
72225
|
+
pushFullRun(runStart, i - 1, j);
|
|
72226
|
+
runStart = -1;
|
|
72227
|
+
}
|
|
72164
72228
|
if (code === 0)
|
|
72165
72229
|
continue;
|
|
72166
72230
|
const polygons = CELL_POLYGONS[code];
|
|
@@ -72173,6 +72237,10 @@ function extractLabelPolygons(labels, width, height, targetLabel, stride = 1, ch
|
|
|
72173
72237
|
out.push(abs);
|
|
72174
72238
|
}
|
|
72175
72239
|
}
|
|
72240
|
+
// Flush a run that reached the end of the row (last visited cell is i1-1).
|
|
72241
|
+
if (runStart !== -1) {
|
|
72242
|
+
pushFullRun(runStart, i1 - 1, j);
|
|
72243
|
+
}
|
|
72176
72244
|
}
|
|
72177
72245
|
return out;
|
|
72178
72246
|
}
|
|
@@ -73637,6 +73705,23 @@ class RenderingUtils {
|
|
|
73637
73705
|
this._reusableSliceBuffer = null;
|
|
73638
73706
|
this._reusableBufferWidth = 0;
|
|
73639
73707
|
this._reusableBufferHeight = 0;
|
|
73708
|
+
/**
|
|
73709
|
+
* Per-layer contour cache.
|
|
73710
|
+
*
|
|
73711
|
+
* Caches the *expensive* part of slice rendering — `getSliceUint8` +
|
|
73712
|
+
* `findLabelsInSlice` + `extractLabelContours` (Path2D build) — keyed by
|
|
73713
|
+
* `${axis}:${sliceIndex}:${volume.version}`. The contours live in voxel
|
|
73714
|
+
* coordinates; zoom only changes the `ctx.scale` transform, so on zoom /
|
|
73715
|
+
* recomposite we reuse the cached Path2D and just re-fill, skipping
|
|
73716
|
+
* marching-squares entirely. A volume edit bumps its version → cache miss
|
|
73717
|
+
* → recompute (correct, automatic). Colors and channel visibility are
|
|
73718
|
+
* applied at fill time and intentionally *not* cached, so toggling them
|
|
73719
|
+
* needs no recompute.
|
|
73720
|
+
*
|
|
73721
|
+
* One entry per layer (the current slice). Switching slice/axis or
|
|
73722
|
+
* editing overwrites it.
|
|
73723
|
+
*/
|
|
73724
|
+
this._contourCache = new Map();
|
|
73640
73725
|
this.state = state;
|
|
73641
73726
|
}
|
|
73642
73727
|
// ── Volume Accessor Helpers ──────────────────────────────────────
|
|
@@ -73735,14 +73820,27 @@ class RenderingUtils {
|
|
|
73735
73820
|
const volume = this.getVolumeForLayer(layer);
|
|
73736
73821
|
if (!volume)
|
|
73737
73822
|
return;
|
|
73738
|
-
const channelVis = this.state.gui_states.layerChannel.channelVisibility[layer];
|
|
73739
|
-
const slice = volume.getSliceUint8(sliceIndex, axis);
|
|
73740
|
-
const W = slice.width;
|
|
73741
|
-
const H = slice.height;
|
|
73742
73823
|
const stride = volume.getChannels();
|
|
73743
|
-
const
|
|
73744
|
-
|
|
73824
|
+
const cacheKey = `${axis}:${sliceIndex}:${volume.getVersion()}`;
|
|
73825
|
+
// Cache miss → run the expensive extraction once. Hits (zoom,
|
|
73826
|
+
// recomposite, contrast toggle) skip straight to the fill below.
|
|
73827
|
+
let entry = this._contourCache.get(layer);
|
|
73828
|
+
if (!entry || entry.key !== cacheKey) {
|
|
73829
|
+
const slice = volume.getSliceUint8(sliceIndex, axis);
|
|
73830
|
+
const W = slice.width;
|
|
73831
|
+
const H = slice.height;
|
|
73832
|
+
const labels = findLabelsInSlice(slice.data, W, H, stride, 0);
|
|
73833
|
+
const paths = new Map();
|
|
73834
|
+
for (const lbl of labels) {
|
|
73835
|
+
paths.set(lbl, extractLabelContours(slice.data, W, H, lbl, stride, 0));
|
|
73836
|
+
}
|
|
73837
|
+
entry = { key: cacheKey, W, H, labels, paths };
|
|
73838
|
+
this._contourCache.set(layer, entry);
|
|
73839
|
+
}
|
|
73840
|
+
if (entry.labels.length === 0)
|
|
73745
73841
|
return;
|
|
73842
|
+
const { W, H, labels, paths } = entry;
|
|
73843
|
+
const channelVis = this.state.gui_states.layerChannel.channelVisibility[layer];
|
|
73746
73844
|
targetCtx.save();
|
|
73747
73845
|
// Vector fill — imageSmoothingEnabled is irrelevant here, but keep
|
|
73748
73846
|
// it off to match the rest of the pipeline.
|
|
@@ -73759,8 +73857,10 @@ class RenderingUtils {
|
|
|
73759
73857
|
for (const lbl of labels) {
|
|
73760
73858
|
if (channelVis && channelVis[lbl] === false)
|
|
73761
73859
|
continue;
|
|
73860
|
+
const path = paths.get(lbl);
|
|
73861
|
+
if (!path)
|
|
73862
|
+
continue;
|
|
73762
73863
|
const color = volume.getChannelColor(lbl);
|
|
73763
|
-
const path = extractLabelContours(slice.data, W, H, lbl, stride, 0);
|
|
73764
73864
|
targetCtx.fillStyle =
|
|
73765
73865
|
`rgba(${color.r}, ${color.g}, ${color.b}, ${color.a / 255})`;
|
|
73766
73866
|
targetCtx.fill(path, 'nonzero');
|
|
@@ -73778,6 +73878,7 @@ class RenderingUtils {
|
|
|
73778
73878
|
this._reusableSliceBuffer = null;
|
|
73779
73879
|
this._reusableBufferWidth = 0;
|
|
73780
73880
|
this._reusableBufferHeight = 0;
|
|
73881
|
+
this._contourCache.clear();
|
|
73781
73882
|
}
|
|
73782
73883
|
/**
|
|
73783
73884
|
* Apply the same flip transform used by flipDisplayImageByAxis() to any
|
|
@@ -74536,7 +74637,30 @@ class ZoomTool extends BaseTool {
|
|
|
74536
74637
|
}
|
|
74537
74638
|
// ===== Zoom Wheel =====
|
|
74538
74639
|
configMouseZoomWheel() {
|
|
74539
|
-
|
|
74640
|
+
// Coalesce multiple wheel events fired within one frame into a single
|
|
74641
|
+
// resizePaintArea() call via requestAnimationFrame. Each resize triggers
|
|
74642
|
+
// a full display redraw (+ mask recomposite), so without coalescing a
|
|
74643
|
+
// fast scroll fires dozens of these per frame. The accumulation is done
|
|
74644
|
+
// on the logical zoom (sizeFactor) — not the DOM offsetWidth — so deltas
|
|
74645
|
+
// still compound correctly even though the DOM size hasn't updated yet.
|
|
74646
|
+
let rafId = null;
|
|
74647
|
+
let pending = null;
|
|
74648
|
+
const flush = () => {
|
|
74649
|
+
rafId = null;
|
|
74650
|
+
if (!pending)
|
|
74651
|
+
return;
|
|
74652
|
+
const p = pending;
|
|
74653
|
+
pending = null;
|
|
74654
|
+
if (p.recenter) {
|
|
74655
|
+
this.callbacks.resetPaintAreaUIPosition();
|
|
74656
|
+
}
|
|
74657
|
+
else {
|
|
74658
|
+
this.callbacks.resetPaintAreaUIPosition(p.l, p.t);
|
|
74659
|
+
}
|
|
74660
|
+
this.callbacks.resizePaintArea(p.moveDistance);
|
|
74661
|
+
this.callbacks.setIsDrawFalse(1000);
|
|
74662
|
+
this.ctx.nrrd_states.view.sizeFactor = p.moveDistance;
|
|
74663
|
+
};
|
|
74540
74664
|
return (e) => {
|
|
74541
74665
|
var _a, _b;
|
|
74542
74666
|
if ((_a = this.ctx.eventRouter) === null || _a === void 0 ? void 0 : _a.isShiftHeld()) {
|
|
@@ -74551,36 +74675,38 @@ class ZoomTool extends BaseTool {
|
|
|
74551
74675
|
const delta = e.detail ? e.detail > 0 : e.wheelDelta < 0;
|
|
74552
74676
|
this.ctx.protectedData.isDrawing = true;
|
|
74553
74677
|
const rect = this.container.getBoundingClientRect();
|
|
74554
|
-
const
|
|
74555
|
-
|
|
74556
|
-
|
|
74557
|
-
|
|
74558
|
-
|
|
74559
|
-
const ratioT = (e.clientY -
|
|
74560
|
-
rect.top -
|
|
74561
|
-
this.mainAreaContainer.offsetTop -
|
|
74562
|
-
this.ctx.protectedData.canvases.drawingCanvas.offsetTop) /
|
|
74563
|
-
this.ctx.protectedData.canvases.drawingCanvas.offsetHeight;
|
|
74678
|
+
const drawingCanvas = this.ctx.protectedData.canvases.drawingCanvas;
|
|
74679
|
+
const ratioL = (e.clientX - rect.left - this.mainAreaContainer.offsetLeft - drawingCanvas.offsetLeft) /
|
|
74680
|
+
drawingCanvas.offsetWidth;
|
|
74681
|
+
const ratioT = (e.clientY - rect.top - this.mainAreaContainer.offsetTop - drawingCanvas.offsetTop) /
|
|
74682
|
+
drawingCanvas.offsetHeight;
|
|
74564
74683
|
const ratioDelta = !delta ? 1 + 0.1 : 1 - 0.1;
|
|
74565
|
-
|
|
74566
|
-
|
|
74567
|
-
const
|
|
74568
|
-
|
|
74569
|
-
moveDistance = w / this.ctx.nrrd_states.image.originWidth;
|
|
74684
|
+
// Compound from the latest pending target (this frame) or the current
|
|
74685
|
+
// committed sizeFactor.
|
|
74686
|
+
const base = pending ? pending.moveDistance : this.ctx.nrrd_states.view.sizeFactor;
|
|
74687
|
+
let moveDistance = base * ratioDelta;
|
|
74570
74688
|
if (moveDistance > 8) {
|
|
74689
|
+
// Max zoom: original behaviour clamps without re-laying out.
|
|
74571
74690
|
moveDistance = 8;
|
|
74691
|
+
this.ctx.nrrd_states.view.sizeFactor = moveDistance;
|
|
74692
|
+
this.callbacks.setIsDrawFalse(1000);
|
|
74693
|
+
return;
|
|
74572
74694
|
}
|
|
74573
|
-
|
|
74695
|
+
if (moveDistance < 1) {
|
|
74574
74696
|
moveDistance = 1;
|
|
74575
|
-
|
|
74576
|
-
this.callbacks.resizePaintArea(moveDistance);
|
|
74697
|
+
pending = { moveDistance, l: 0, t: 0, recenter: true };
|
|
74577
74698
|
}
|
|
74578
74699
|
else {
|
|
74579
|
-
this.
|
|
74580
|
-
this.
|
|
74700
|
+
// Target displayed size for this zoom level → keep cursor anchored.
|
|
74701
|
+
const w = this.ctx.nrrd_states.image.originWidth * moveDistance;
|
|
74702
|
+
const h = this.ctx.nrrd_states.image.originHeight * moveDistance;
|
|
74703
|
+
const l = Math.round(e.clientX - this.mainAreaContainer.offsetLeft - w * ratioL - rect.left);
|
|
74704
|
+
const t = Math.round(e.clientY - this.mainAreaContainer.offsetTop - h * ratioT - rect.top);
|
|
74705
|
+
pending = { moveDistance, l, t, recenter: false };
|
|
74706
|
+
}
|
|
74707
|
+
if (rafId === null) {
|
|
74708
|
+
rafId = requestAnimationFrame(flush);
|
|
74581
74709
|
}
|
|
74582
|
-
this.callbacks.setIsDrawFalse(1000);
|
|
74583
|
-
this.ctx.nrrd_states.view.sizeFactor = moveDistance;
|
|
74584
74710
|
};
|
|
74585
74711
|
}
|
|
74586
74712
|
}
|
|
@@ -79324,7 +79450,7 @@ function evaluateElement(element, weights) {
|
|
|
79324
79450
|
}
|
|
79325
79451
|
|
|
79326
79452
|
// import * as kiwrious from "copper3d_plugin_heart_k";
|
|
79327
|
-
const REVISION = "v3.4.
|
|
79453
|
+
const REVISION = "v3.4.5-beta";
|
|
79328
79454
|
console.log(`%cCopper3D Visualisation %cBeta:${REVISION}`, "padding: 3px;color:white; background:#023047", "padding: 3px;color:white; background:#f50a25");
|
|
79329
79455
|
|
|
79330
79456
|
export { CHANNEL_COLORS, CHANNEL_HEX_COLORS, CameraViewPoint, Copper3dTrackballControls, GaussianSmoother, MeshNodeTool, NrrdTools, REVISION, addBoxHelper, addLabelToScene, configKiwriousHeart, convert3DPostoScreenPos, convertScreenPosto3DPos, copperMScene, copperMSceneRenderer, copperRenderer, copperRendererOnDemond, copperScene, copperSceneOnDemond, createTexture2D_NRRD, fullScreenListenner, kiwrious, loading, removeGuiFolderChilden, rgbaToCss, rgbaToHex, setHDRFilePath, throttle };
|