cloudmr-ux 4.6.1 → 4.6.2
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/CmrComponents/niivue-viewer/CloudMrNiivueViewer.js +18 -0
- package/dist/CmrComponents/niivue-viewer/NiivuePatcher.js +83 -18
- package/dist/CmrComponents/niivue-viewer/penDraftUtils.d.ts +21 -0
- package/dist/CmrComponents/niivue-viewer/penDraftUtils.js +30 -1
- package/dist/CmrComponents/niivue-viewer/shapeDraftUtils.d.ts +16 -3
- package/dist/CmrComponents/niivue-viewer/shapeDraftUtils.js +64 -22
- package/package.json +1 -1
|
@@ -832,6 +832,14 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
832
832
|
(_a = nv.cloudMrCancelPolyline) === null || _a === void 0 ? void 0 : _a.call(nv);
|
|
833
833
|
}
|
|
834
834
|
}
|
|
835
|
+
function syncActiveDraftRefs() {
|
|
836
|
+
nv._cloudMrActiveShapeDraft = shapeDraftRef.current;
|
|
837
|
+
nv._cloudMrActivePenDraft = penDraftRef.current;
|
|
838
|
+
}
|
|
839
|
+
function clearActiveDraftRefs() {
|
|
840
|
+
nv._cloudMrActiveShapeDraft = null;
|
|
841
|
+
nv._cloudMrActivePenDraft = null;
|
|
842
|
+
}
|
|
835
843
|
function cancelPenDraftHandler() {
|
|
836
844
|
var _a;
|
|
837
845
|
var draft = penDraftRef.current;
|
|
@@ -845,6 +853,7 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
845
853
|
setPenDraft(null);
|
|
846
854
|
penDraftRef.current = null;
|
|
847
855
|
nv._cloudMrPenDraftActive = false;
|
|
856
|
+
clearActiveDraftRefs();
|
|
848
857
|
if (drawShapeToolRef.current === "pen") {
|
|
849
858
|
nvSetDrawingEnabled(true);
|
|
850
859
|
}
|
|
@@ -862,6 +871,7 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
862
871
|
setPenDraft(null);
|
|
863
872
|
penDraftRef.current = null;
|
|
864
873
|
nv._cloudMrPenDraftActive = false;
|
|
874
|
+
clearActiveDraftRefs();
|
|
865
875
|
setDrawingChanged(true);
|
|
866
876
|
if (drawShapeToolRef.current === "pen") {
|
|
867
877
|
nvSetDrawingEnabled(true);
|
|
@@ -878,6 +888,7 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
878
888
|
function onPenDraftChange(draft) {
|
|
879
889
|
setPenDraft(draft);
|
|
880
890
|
penDraftRef.current = draft;
|
|
891
|
+
syncActiveDraftRefs();
|
|
881
892
|
if (draft.kind === "polyline") {
|
|
882
893
|
syncPolylineDraftToNv(nv, draft);
|
|
883
894
|
}
|
|
@@ -889,6 +900,7 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
889
900
|
setPenDraft(draft);
|
|
890
901
|
penDraftRef.current = draft;
|
|
891
902
|
nv._cloudMrPenDraftActive = true;
|
|
903
|
+
syncActiveDraftRefs();
|
|
892
904
|
nvSetDrawingEnabled(false);
|
|
893
905
|
};
|
|
894
906
|
nv.onPolylineChange = function (count) {
|
|
@@ -903,11 +915,13 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
903
915
|
if (draft) {
|
|
904
916
|
setPenDraft(draft);
|
|
905
917
|
penDraftRef.current = draft;
|
|
918
|
+
syncActiveDraftRefs();
|
|
906
919
|
}
|
|
907
920
|
}
|
|
908
921
|
else if (((_b = penDraftRef.current) === null || _b === void 0 ? void 0 : _b.kind) === "polyline") {
|
|
909
922
|
setPenDraft(null);
|
|
910
923
|
penDraftRef.current = null;
|
|
924
|
+
syncActiveDraftRefs();
|
|
911
925
|
}
|
|
912
926
|
};
|
|
913
927
|
function cancelShapeDraft() {
|
|
@@ -920,6 +934,7 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
920
934
|
setShapeDraft(null);
|
|
921
935
|
shapeDraftRef.current = null;
|
|
922
936
|
nv._cloudMrShapeDraftActive = false;
|
|
937
|
+
clearActiveDraftRefs();
|
|
923
938
|
if (drawShapeToolRef.current) {
|
|
924
939
|
nvSetDrawingEnabled(true);
|
|
925
940
|
}
|
|
@@ -932,6 +947,7 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
932
947
|
setShapeDraft(null);
|
|
933
948
|
shapeDraftRef.current = null;
|
|
934
949
|
nv._cloudMrShapeDraftActive = false;
|
|
950
|
+
clearActiveDraftRefs();
|
|
935
951
|
if (drawShapeToolRef.current) {
|
|
936
952
|
nvSetDrawingEnabled(true);
|
|
937
953
|
}
|
|
@@ -940,11 +956,13 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
940
956
|
function onShapeDraftChange(draft) {
|
|
941
957
|
setShapeDraft(draft);
|
|
942
958
|
shapeDraftRef.current = draft;
|
|
959
|
+
syncActiveDraftRefs();
|
|
943
960
|
}
|
|
944
961
|
nv.onShapeDraftReady = function (draft) {
|
|
945
962
|
setShapeDraft(draft);
|
|
946
963
|
shapeDraftRef.current = draft;
|
|
947
964
|
nv._cloudMrShapeDraftActive = true;
|
|
965
|
+
syncActiveDraftRefs();
|
|
948
966
|
nvSetDrawingEnabled(false);
|
|
949
967
|
};
|
|
950
968
|
nv.onApplyActiveDraft = function () {
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
captureDeferredShapeDraft,
|
|
7
7
|
captureShapeDraftFromClick,
|
|
8
8
|
isDraftTooSmall,
|
|
9
|
+
isVoxelPartOfDraft,
|
|
9
10
|
redrawDraftShape,
|
|
10
11
|
shouldDeferShapeCommit,
|
|
11
12
|
} from "./shapeDraftUtils.js";
|
|
@@ -22,6 +23,9 @@ import {
|
|
|
22
23
|
} from "./polylinePenUtils.js";
|
|
23
24
|
import {
|
|
24
25
|
captureFreehandDraft,
|
|
26
|
+
capturePenDraftFromClick,
|
|
27
|
+
isPenDrawToolActive,
|
|
28
|
+
redrawFreehandDraft,
|
|
25
29
|
shouldDeferFreehandCommit,
|
|
26
30
|
} from "./penDraftUtils.js";
|
|
27
31
|
|
|
@@ -1472,31 +1476,83 @@ function cloudMrHasApplyableDraft(nv) {
|
|
|
1472
1476
|
return !!(nv._cloudMrShapeDraftActive || nv._cloudMrPenDraftActive);
|
|
1473
1477
|
}
|
|
1474
1478
|
|
|
1479
|
+
function getActiveDraft(nv) {
|
|
1480
|
+
if (nv._cloudMrPenDraftActive && nv._cloudMrActivePenDraft) {
|
|
1481
|
+
return nv._cloudMrActivePenDraft;
|
|
1482
|
+
}
|
|
1483
|
+
if (nv._cloudMrShapeDraftActive && nv._cloudMrActiveShapeDraft) {
|
|
1484
|
+
return nv._cloudMrActiveShapeDraft;
|
|
1485
|
+
}
|
|
1486
|
+
return null;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
function isClickOnActiveDraft(nv) {
|
|
1490
|
+
const draft = getActiveDraft(nv);
|
|
1491
|
+
if (!draft) return false;
|
|
1492
|
+
const vox = voxFromMouse(nv);
|
|
1493
|
+
if (!vox) return false;
|
|
1494
|
+
return isVoxelPartOfDraft(nv, draft, vox);
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
/** Commit the active draft when the user clicks outside the ROI being edited. */
|
|
1498
|
+
function cloudMrTryApplyDraftOnClickAway(nv) {
|
|
1499
|
+
if (!cloudMrHasApplyableDraft(nv) || !isClickWithoutDrag(nv.uiData)) {
|
|
1500
|
+
return false;
|
|
1501
|
+
}
|
|
1502
|
+
if (isClickOnActiveDraft(nv)) {
|
|
1503
|
+
return false;
|
|
1504
|
+
}
|
|
1505
|
+
if (typeof nv.onApplyActiveDraft === "function") {
|
|
1506
|
+
nv._cloudMrSuppressDrawingChangedMouseUp = true;
|
|
1507
|
+
nv.onApplyActiveDraft();
|
|
1508
|
+
}
|
|
1509
|
+
return true;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1475
1512
|
/**
|
|
1476
|
-
* When no draft is active and the user clicks an existing ROI
|
|
1477
|
-
*
|
|
1478
|
-
* clicked cluster so the bounding-box overlay reappears for re-editing.
|
|
1513
|
+
* When no draft is active and the user clicks an existing ROI, reconstruct a
|
|
1514
|
+
* shape or pen draft so the bounding-box overlay reappears for re-editing.
|
|
1479
1515
|
*/
|
|
1480
|
-
function
|
|
1481
|
-
const penType = nv.opts.penType;
|
|
1516
|
+
function cloudMrTryReopenDraftOnClick(nv) {
|
|
1482
1517
|
if (
|
|
1483
|
-
!nv.opts.deferShapeCommit ||
|
|
1484
|
-
!nv.opts.drawingEnabled ||
|
|
1485
1518
|
nv._cloudMrShapeDraftActive ||
|
|
1486
1519
|
nv._cloudMrPenDraftActive ||
|
|
1487
|
-
!isClickWithoutDrag(nv.uiData)
|
|
1488
|
-
(penType !== NI_PEN_TYPE.RECTANGLE && penType !== NI_PEN_TYPE.ELLIPSE)
|
|
1520
|
+
!isClickWithoutDrag(nv.uiData)
|
|
1489
1521
|
) {
|
|
1490
1522
|
return false;
|
|
1491
1523
|
}
|
|
1492
1524
|
|
|
1493
|
-
const
|
|
1494
|
-
|
|
1525
|
+
const penType = nv.opts.penType;
|
|
1526
|
+
const isShapeTool =
|
|
1527
|
+
nv.opts.deferShapeCommit &&
|
|
1528
|
+
nv.opts.drawingEnabled &&
|
|
1529
|
+
(penType === NI_PEN_TYPE.RECTANGLE || penType === NI_PEN_TYPE.ELLIPSE);
|
|
1530
|
+
const isPenTool = isPenDrawToolActive(nv);
|
|
1531
|
+
|
|
1532
|
+
if (!isShapeTool && !isPenTool) {
|
|
1533
|
+
return false;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
if (isShapeTool) {
|
|
1537
|
+
const reopenDraft = captureShapeDraftFromClick(nv);
|
|
1538
|
+
if (!reopenDraft) return false;
|
|
1539
|
+
redrawDraftShape(nv, reopenDraft);
|
|
1540
|
+
nv._cloudMrSuppressDrawingChangedMouseUp = true;
|
|
1541
|
+
if (typeof nv.onShapeDraftReady === "function") {
|
|
1542
|
+
nv.onShapeDraftReady(reopenDraft);
|
|
1543
|
+
}
|
|
1544
|
+
return true;
|
|
1545
|
+
}
|
|
1495
1546
|
|
|
1496
|
-
|
|
1547
|
+
const reopenDraft = capturePenDraftFromClick(nv);
|
|
1548
|
+
if (!reopenDraft) return false;
|
|
1549
|
+
redrawFreehandDraft(nv, reopenDraft);
|
|
1497
1550
|
nv._cloudMrSuppressDrawingChangedMouseUp = true;
|
|
1498
|
-
if (typeof nv.
|
|
1499
|
-
nv.
|
|
1551
|
+
if (typeof nv.onPenDraftReady === "function") {
|
|
1552
|
+
nv.onPenDraftReady(reopenDraft);
|
|
1553
|
+
}
|
|
1554
|
+
if (nv.opts.polylinePenMode) {
|
|
1555
|
+
resetPolylineState(nv);
|
|
1500
1556
|
}
|
|
1501
1557
|
return true;
|
|
1502
1558
|
}
|
|
@@ -1566,21 +1622,30 @@ Niivue.prototype.mouseUpListener = function cloudMrMouseUpListener() {
|
|
|
1566
1622
|
_mouseUpListener.call(this);
|
|
1567
1623
|
|
|
1568
1624
|
if (polylineClick) {
|
|
1625
|
+
if (cloudMrTryReopenDraftOnClick(this)) return;
|
|
1569
1626
|
addPolylineVertex(this);
|
|
1570
1627
|
return;
|
|
1571
1628
|
}
|
|
1572
1629
|
|
|
1630
|
+
if (cloudMrHasApplyableDraft(this) && isClickWithoutDrag(this.uiData)) {
|
|
1631
|
+
if (cloudMrTryApplyDraftOnClickAway(this)) {
|
|
1632
|
+
cloudMrTryReopenDraftOnClick(this);
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1635
|
+
if (isClickOnActiveDraft(this)) {
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1573
1640
|
if (!pendingDraft?.baseBitmap) {
|
|
1574
|
-
|
|
1575
|
-
cloudMrTryReopenShapeDraftOnClick(this);
|
|
1641
|
+
cloudMrTryReopenDraftOnClick(this);
|
|
1576
1642
|
return;
|
|
1577
1643
|
}
|
|
1578
1644
|
if (isDraftTooSmall(pendingDraft.ptA, pendingDraft.ptB)) {
|
|
1579
1645
|
this.drawBitmap.set(pendingDraft.baseBitmap);
|
|
1580
1646
|
this.refreshDrawing(true, false);
|
|
1581
1647
|
this.drawScene();
|
|
1582
|
-
|
|
1583
|
-
cloudMrTryReopenShapeDraftOnClick(this);
|
|
1648
|
+
cloudMrTryReopenDraftOnClick(this);
|
|
1584
1649
|
return;
|
|
1585
1650
|
}
|
|
1586
1651
|
this._cloudMrSuppressDrawingChangedMouseUp = true;
|
|
@@ -13,6 +13,27 @@
|
|
|
13
13
|
export function isEraserActive(nv: any): any;
|
|
14
14
|
export function isFreehandPenActive(nv: any): any;
|
|
15
15
|
export function shouldDeferFreehandCommit(nv: any): any;
|
|
16
|
+
export function isPenDrawToolActive(nv: any): any;
|
|
17
|
+
/**
|
|
18
|
+
* Re-enter pen edit mode when clicking an existing freehand/polyline ROI.
|
|
19
|
+
* Reopens as a freehand draft (move via bounding box) since vertex data is
|
|
20
|
+
* not stored in the bitmap alone.
|
|
21
|
+
*/
|
|
22
|
+
export function capturePenDraftFromClick(nv: any): {
|
|
23
|
+
kind: string;
|
|
24
|
+
baseBitmap: Uint8Array;
|
|
25
|
+
axCorSag: any;
|
|
26
|
+
penValue: number;
|
|
27
|
+
strokeVoxels: [number, number, number][];
|
|
28
|
+
bounds: {
|
|
29
|
+
x1: any;
|
|
30
|
+
y1: any;
|
|
31
|
+
z1: any;
|
|
32
|
+
x2: any;
|
|
33
|
+
y2: any;
|
|
34
|
+
z2: any;
|
|
35
|
+
};
|
|
36
|
+
} | null;
|
|
16
37
|
export function redrawPolylineDraft(nv: any, draft: any): void;
|
|
17
38
|
export function translatePolylineVertices(vertices: any, delta: any): any;
|
|
18
39
|
export function updatePolylineVertex(vertices: any, index: any, newVox: any): any;
|
|
@@ -18,7 +18,8 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
|
18
18
|
}
|
|
19
19
|
return to.concat(ar || Array.prototype.slice.call(from));
|
|
20
20
|
};
|
|
21
|
-
import { translatePt } from "./shapeDraftUtils";
|
|
21
|
+
import { translatePt, floodFillClusterFromVox, eraseClusterFromBitmap, } from "./shapeDraftUtils";
|
|
22
|
+
import { axCorSagFromMouse, voxFromMouse } from "./polylinePenUtils";
|
|
22
23
|
import { NI_PEN_TYPE } from "./niivuePenType";
|
|
23
24
|
/** @typedef {'polyline' | 'freehand'} PenDraftKind */
|
|
24
25
|
/**
|
|
@@ -47,6 +48,34 @@ export function isFreehandPenActive(nv) {
|
|
|
47
48
|
export function shouldDeferFreehandCommit(nv) {
|
|
48
49
|
return !!nv.opts.deferFreehandCommit && isFreehandPenActive(nv);
|
|
49
50
|
}
|
|
51
|
+
export function isPenDrawToolActive(nv) {
|
|
52
|
+
return (nv.opts.drawingEnabled &&
|
|
53
|
+
nv.opts.penType === NI_PEN_TYPE.PEN &&
|
|
54
|
+
nv.opts.penValue > 0 &&
|
|
55
|
+
(nv.opts.deferFreehandCommit || nv.opts.polylinePenMode));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Re-enter pen edit mode when clicking an existing freehand/polyline ROI.
|
|
59
|
+
* Reopens as a freehand draft (move via bounding box) since vertex data is
|
|
60
|
+
* not stored in the bitmap alone.
|
|
61
|
+
*/
|
|
62
|
+
export function capturePenDraftFromClick(nv) {
|
|
63
|
+
var seedVox = voxFromMouse(nv);
|
|
64
|
+
var cluster = floodFillClusterFromVox(nv, seedVox);
|
|
65
|
+
if (!cluster)
|
|
66
|
+
return null;
|
|
67
|
+
var label = cluster.label, visited = cluster.visited, voxels = cluster.voxels, bounds = cluster.bounds;
|
|
68
|
+
var x1 = bounds.x1, y1 = bounds.y1, z1 = bounds.z1, x2 = bounds.x2, y2 = bounds.y2, z2 = bounds.z2;
|
|
69
|
+
var axCorSag = nv.drawPenAxCorSag >= 0 ? nv.drawPenAxCorSag : axCorSagFromMouse(nv);
|
|
70
|
+
return {
|
|
71
|
+
kind: "freehand",
|
|
72
|
+
baseBitmap: eraseClusterFromBitmap(nv.drawBitmap, visited),
|
|
73
|
+
axCorSag: axCorSag,
|
|
74
|
+
penValue: label,
|
|
75
|
+
strokeVoxels: voxels,
|
|
76
|
+
bounds: { x1: x1, y1: y1, z1: z1, x2: x2, y2: y2, z2: z2 }
|
|
77
|
+
};
|
|
78
|
+
}
|
|
50
79
|
export function redrawPolylineDraft(nv, draft) {
|
|
51
80
|
var _a;
|
|
52
81
|
if (!((_a = draft === null || draft === void 0 ? void 0 : draft.vertices) === null || _a === void 0 ? void 0 : _a.length) || !draft.baseBitmap)
|
|
@@ -49,6 +49,19 @@ export function captureDeferredShapeDraft(nv: any): {
|
|
|
49
49
|
baseBitmap: Uint8Array | null;
|
|
50
50
|
};
|
|
51
51
|
export function shouldDeferShapeCommit(nv: any): any;
|
|
52
|
+
/**
|
|
53
|
+
* Flood-fill a connected voxel cluster from a seed.
|
|
54
|
+
* @returns {{ label: number, visited: Set<number>, voxels: [number,number,number][], bounds: object } | null}
|
|
55
|
+
*/
|
|
56
|
+
export function floodFillClusterFromVox(nv: any, seedVox: any): {
|
|
57
|
+
label: number;
|
|
58
|
+
visited: Set<number>;
|
|
59
|
+
voxels: [number, number, number][];
|
|
60
|
+
bounds: object;
|
|
61
|
+
} | null;
|
|
62
|
+
/** True when a voxel belongs to the live draft overlay (drawn on top of baseBitmap). */
|
|
63
|
+
export function isVoxelPartOfDraft(nv: any, draft: any, seedVox: any): boolean;
|
|
64
|
+
export function eraseClusterFromBitmap(bitmap: any, visited: any): Uint8Array;
|
|
52
65
|
/**
|
|
53
66
|
* When the user clicks on an existing filled ROI while the rectangle/ellipse tool
|
|
54
67
|
* is active (but no draft is currently open), flood-fill the clicked cluster to
|
|
@@ -57,9 +70,9 @@ export function shouldDeferShapeCommit(nv: any): any;
|
|
|
57
70
|
* Returns null if the click didn't land on a labeled voxel.
|
|
58
71
|
*/
|
|
59
72
|
export function captureShapeDraftFromClick(nv: any): {
|
|
60
|
-
ptA:
|
|
61
|
-
ptB:
|
|
62
|
-
penValue:
|
|
73
|
+
ptA: any[];
|
|
74
|
+
ptB: any[];
|
|
75
|
+
penValue: number;
|
|
63
76
|
axCorSag: number;
|
|
64
77
|
penType: any;
|
|
65
78
|
baseBitmap: Uint8Array;
|
|
@@ -203,27 +203,21 @@ function inferAxCorSagFromBounds(x1, y1, z1, x2, y2, z2, fallback) {
|
|
|
203
203
|
var spanY = y2 - y1;
|
|
204
204
|
var spanZ = z2 - z1;
|
|
205
205
|
if (spanZ <= spanX && spanZ <= spanY)
|
|
206
|
-
return 0;
|
|
206
|
+
return 0;
|
|
207
207
|
if (spanY <= spanX && spanY <= spanZ)
|
|
208
|
-
return 1;
|
|
208
|
+
return 1;
|
|
209
209
|
if (spanX <= spanY && spanX <= spanZ)
|
|
210
|
-
return 2;
|
|
210
|
+
return 2;
|
|
211
211
|
return fallback;
|
|
212
212
|
}
|
|
213
213
|
/**
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
* reconstruct a ShapeDraft so the bounding-box overlay reappears for re-editing.
|
|
217
|
-
*
|
|
218
|
-
* Returns null if the click didn't land on a labeled voxel.
|
|
214
|
+
* Flood-fill a connected voxel cluster from a seed.
|
|
215
|
+
* @returns {{ label: number, visited: Set<number>, voxels: [number,number,number][], bounds: object } | null}
|
|
219
216
|
*/
|
|
220
|
-
export function
|
|
217
|
+
export function floodFillClusterFromVox(nv, seedVox) {
|
|
221
218
|
var _a;
|
|
222
219
|
var dims = (_a = nv.back) === null || _a === void 0 ? void 0 : _a.dims;
|
|
223
|
-
if (!dims || !nv.drawBitmap)
|
|
224
|
-
return null;
|
|
225
|
-
var seedVox = voxFromMouse(nv);
|
|
226
|
-
if (!seedVox)
|
|
220
|
+
if (!dims || !nv.drawBitmap || !seedVox)
|
|
227
221
|
return null;
|
|
228
222
|
var dx = dims[1];
|
|
229
223
|
var dy = dims[2];
|
|
@@ -232,15 +226,20 @@ export function captureShapeDraftFromClick(nv) {
|
|
|
232
226
|
var label = nv.drawBitmap[seedIdx];
|
|
233
227
|
if (!label)
|
|
234
228
|
return null;
|
|
235
|
-
// BFS flood-fill to find connected cluster of the same label
|
|
236
229
|
var visited = new Set();
|
|
237
230
|
var queue = [seedIdx];
|
|
238
231
|
visited.add(seedIdx);
|
|
239
|
-
var
|
|
240
|
-
var
|
|
232
|
+
var voxels = [];
|
|
233
|
+
var x1 = Infinity;
|
|
234
|
+
var y1 = Infinity;
|
|
235
|
+
var z1 = Infinity;
|
|
236
|
+
var x2 = -Infinity;
|
|
237
|
+
var y2 = -Infinity;
|
|
238
|
+
var z2 = -Infinity;
|
|
241
239
|
while (queue.length > 0) {
|
|
242
240
|
var idx = queue.shift();
|
|
243
241
|
var _b = decodeVoxelIndex(idx, dx, dy), x = _b[0], y = _b[1], z = _b[2];
|
|
242
|
+
voxels.push([x, y, z]);
|
|
244
243
|
x1 = Math.min(x1, x);
|
|
245
244
|
y1 = Math.min(y1, y);
|
|
246
245
|
z1 = Math.min(z1, z);
|
|
@@ -248,9 +247,12 @@ export function captureShapeDraftFromClick(nv) {
|
|
|
248
247
|
y2 = Math.max(y2, y);
|
|
249
248
|
z2 = Math.max(z2, z);
|
|
250
249
|
var neighbors = [
|
|
251
|
-
[x + 1, y, z],
|
|
252
|
-
[x
|
|
253
|
-
[x, y
|
|
250
|
+
[x + 1, y, z],
|
|
251
|
+
[x - 1, y, z],
|
|
252
|
+
[x, y + 1, z],
|
|
253
|
+
[x, y - 1, z],
|
|
254
|
+
[x, y, z + 1],
|
|
255
|
+
[x, y, z - 1],
|
|
254
256
|
];
|
|
255
257
|
for (var _i = 0, neighbors_1 = neighbors; _i < neighbors_1.length; _i++) {
|
|
256
258
|
var _c = neighbors_1[_i], nx = _c[0], ny = _c[1], nz = _c[2];
|
|
@@ -265,9 +267,49 @@ export function captureShapeDraftFromClick(nv) {
|
|
|
265
267
|
}
|
|
266
268
|
if (!Number.isFinite(x1))
|
|
267
269
|
return null;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
270
|
+
return {
|
|
271
|
+
label: label,
|
|
272
|
+
visited: visited,
|
|
273
|
+
voxels: voxels,
|
|
274
|
+
bounds: { x1: x1, y1: y1, z1: z1, x2: x2, y2: y2, z2: z2 }
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
/** True when a voxel belongs to the live draft overlay (drawn on top of baseBitmap). */
|
|
278
|
+
export function isVoxelPartOfDraft(nv, draft, seedVox) {
|
|
279
|
+
var _a;
|
|
280
|
+
if (!(draft === null || draft === void 0 ? void 0 : draft.baseBitmap) || !(nv === null || nv === void 0 ? void 0 : nv.drawBitmap) || !seedVox)
|
|
281
|
+
return false;
|
|
282
|
+
var dims = (_a = nv.back) === null || _a === void 0 ? void 0 : _a.dims;
|
|
283
|
+
if (!dims)
|
|
284
|
+
return false;
|
|
285
|
+
var dx = dims[1];
|
|
286
|
+
var dy = dims[2];
|
|
287
|
+
var idx = voxelIndex(seedVox[0], seedVox[1], seedVox[2], dx, dy);
|
|
288
|
+
return (nv.drawBitmap[idx] === draft.penValue &&
|
|
289
|
+
draft.baseBitmap[idx] !== draft.penValue);
|
|
290
|
+
}
|
|
291
|
+
export function eraseClusterFromBitmap(bitmap, visited) {
|
|
292
|
+
var next = new Uint8Array(bitmap);
|
|
293
|
+
visited.forEach(function (idx) {
|
|
294
|
+
next[idx] = 0;
|
|
295
|
+
});
|
|
296
|
+
return next;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* When the user clicks on an existing filled ROI while the rectangle/ellipse tool
|
|
300
|
+
* is active (but no draft is currently open), flood-fill the clicked cluster to
|
|
301
|
+
* reconstruct a ShapeDraft so the bounding-box overlay reappears for re-editing.
|
|
302
|
+
*
|
|
303
|
+
* Returns null if the click didn't land on a labeled voxel.
|
|
304
|
+
*/
|
|
305
|
+
export function captureShapeDraftFromClick(nv) {
|
|
306
|
+
var seedVox = voxFromMouse(nv);
|
|
307
|
+
var cluster = floodFillClusterFromVox(nv, seedVox);
|
|
308
|
+
if (!cluster)
|
|
309
|
+
return null;
|
|
310
|
+
var label = cluster.label, visited = cluster.visited, bounds = cluster.bounds;
|
|
311
|
+
var x1 = bounds.x1, y1 = bounds.y1, z1 = bounds.z1, x2 = bounds.x2, y2 = bounds.y2, z2 = bounds.z2;
|
|
312
|
+
var baseBitmap = eraseClusterFromBitmap(nv.drawBitmap, visited);
|
|
271
313
|
var axCorSag = inferAxCorSagFromBounds(x1, y1, z1, x2, y2, z2, nv.drawPenAxCorSag >= 0 ? nv.drawPenAxCorSag : 0);
|
|
272
314
|
return {
|
|
273
315
|
ptA: [x1, y1, z1],
|