cloudmr-ux 4.6.0 → 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/CloudMrNiivuePanel.js +1 -1
- package/dist/CmrComponents/niivue-viewer/CloudMrNiivueViewer.js +18 -36
- package/dist/CmrComponents/niivue-viewer/NiivuePatcher.js +93 -9
- package/dist/CmrComponents/niivue-viewer/PenDraftOverlay.d.ts +5 -3
- package/dist/CmrComponents/niivue-viewer/PenDraftOverlay.js +7 -1
- package/dist/CmrComponents/niivue-viewer/ShapeDraftOverlay.d.ts +3 -2
- package/dist/CmrComponents/niivue-viewer/ShapeDraftOverlay.js +7 -2
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/DrawColorPlatte.d.ts +1 -3
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/DrawColorPlatte.js +15 -28
- 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 +28 -0
- package/dist/CmrComponents/niivue-viewer/shapeDraftUtils.js +134 -0
- package/package.json +1 -1
|
@@ -186,7 +186,7 @@ export function CloudMrNiivuePanel(props) {
|
|
|
186
186
|
left: 0,
|
|
187
187
|
width: "100%",
|
|
188
188
|
height: "100%"
|
|
189
|
-
} }), props.shapeDraft && (_jsx(ShapeDraftOverlay, { nv: props.nv, draft: props.shapeDraft, onDraftChange: props.onShapeDraftChange, overlayKey: props.mms })), props.penDraft && (_jsx(PenDraftOverlay, { nv: props.nv, draft: props.penDraft, onDraftChange: props.onPenDraftChange, overlayKey: props.mms }))] }))] })), _jsxs(Box, __assign({ sx: {
|
|
189
|
+
} }), props.shapeDraft && (_jsx(ShapeDraftOverlay, { nv: props.nv, draft: props.shapeDraft, onDraftChange: props.onShapeDraftChange, onApplyDraft: props.onApplyShapeDraft, overlayKey: props.mms })), props.penDraft && (_jsx(PenDraftOverlay, { nv: props.nv, draft: props.penDraft, onDraftChange: props.onPenDraftChange, onApplyDraft: props.onApplyPenDraft, overlayKey: props.mms }))] }))] })), _jsxs(Box, __assign({ sx: {
|
|
190
190
|
width: {
|
|
191
191
|
xs: "100%",
|
|
192
192
|
md: "35%"
|
|
@@ -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 () {
|
|
@@ -961,32 +979,6 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
961
979
|
React.useEffect(function () {
|
|
962
980
|
nv._cloudMrShapeDraftActive = shapeDraft != null;
|
|
963
981
|
}, [shapeDraft]);
|
|
964
|
-
React.useEffect(function () {
|
|
965
|
-
if (!shapeDraft && !penDraft) {
|
|
966
|
-
return undefined;
|
|
967
|
-
}
|
|
968
|
-
var canvas = document.getElementById("niiCanvas");
|
|
969
|
-
if (!canvas) {
|
|
970
|
-
return undefined;
|
|
971
|
-
}
|
|
972
|
-
var applyOnRightClick = function (event) {
|
|
973
|
-
var _a;
|
|
974
|
-
event.preventDefault();
|
|
975
|
-
event.stopPropagation();
|
|
976
|
-
(_a = nv.onApplyActiveDraft) === null || _a === void 0 ? void 0 : _a.call(nv);
|
|
977
|
-
};
|
|
978
|
-
var onMouseDown = function (event) {
|
|
979
|
-
if (event.button === 2) {
|
|
980
|
-
applyOnRightClick(event);
|
|
981
|
-
}
|
|
982
|
-
};
|
|
983
|
-
canvas.addEventListener("mousedown", onMouseDown, true);
|
|
984
|
-
canvas.addEventListener("contextmenu", applyOnRightClick, true);
|
|
985
|
-
return function () {
|
|
986
|
-
canvas.removeEventListener("mousedown", onMouseDown, true);
|
|
987
|
-
canvas.removeEventListener("contextmenu", applyOnRightClick, true);
|
|
988
|
-
};
|
|
989
|
-
}, [shapeDraft, penDraft]);
|
|
990
982
|
React.useEffect(function () {
|
|
991
983
|
if (!shapeDraft && !penDraft) {
|
|
992
984
|
return undefined;
|
|
@@ -1000,16 +992,6 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
1000
992
|
else if (penDraft) {
|
|
1001
993
|
cancelPenDraftHandler();
|
|
1002
994
|
}
|
|
1003
|
-
return;
|
|
1004
|
-
}
|
|
1005
|
-
if (event.key === "Enter") {
|
|
1006
|
-
event.preventDefault();
|
|
1007
|
-
if (shapeDraft) {
|
|
1008
|
-
applyShapeDraft();
|
|
1009
|
-
}
|
|
1010
|
-
else if (penDraft) {
|
|
1011
|
-
applyPenDraftHandler();
|
|
1012
|
-
}
|
|
1013
995
|
}
|
|
1014
996
|
};
|
|
1015
997
|
window.addEventListener("keydown", onKeyDown);
|
|
@@ -4,9 +4,13 @@
|
|
|
4
4
|
import { Niivue, NVImage, NVImageFromUrlOptions } from "@niivue/niivue";
|
|
5
5
|
import {
|
|
6
6
|
captureDeferredShapeDraft,
|
|
7
|
+
captureShapeDraftFromClick,
|
|
7
8
|
isDraftTooSmall,
|
|
9
|
+
isVoxelPartOfDraft,
|
|
10
|
+
redrawDraftShape,
|
|
8
11
|
shouldDeferShapeCommit,
|
|
9
12
|
} from "./shapeDraftUtils.js";
|
|
13
|
+
import { NI_PEN_TYPE } from "./niivuePenType.js";
|
|
10
14
|
import {
|
|
11
15
|
addPolylineVertex,
|
|
12
16
|
axCorSagFromMouse,
|
|
@@ -19,6 +23,9 @@ import {
|
|
|
19
23
|
} from "./polylinePenUtils.js";
|
|
20
24
|
import {
|
|
21
25
|
captureFreehandDraft,
|
|
26
|
+
capturePenDraftFromClick,
|
|
27
|
+
isPenDrawToolActive,
|
|
28
|
+
redrawFreehandDraft,
|
|
22
29
|
shouldDeferFreehandCommit,
|
|
23
30
|
} from "./penDraftUtils.js";
|
|
24
31
|
|
|
@@ -1465,29 +1472,93 @@ Niivue.prototype.cloudMrResetPolyline = function cloudMrResetPolyline() {
|
|
|
1465
1472
|
resetPolylineState(this);
|
|
1466
1473
|
};
|
|
1467
1474
|
|
|
1468
|
-
const RIGHT_MOUSE_BUTTON = 2;
|
|
1469
|
-
|
|
1470
1475
|
function cloudMrHasApplyableDraft(nv) {
|
|
1471
1476
|
return !!(nv._cloudMrShapeDraftActive || nv._cloudMrPenDraftActive);
|
|
1472
1477
|
}
|
|
1473
1478
|
|
|
1474
|
-
function
|
|
1475
|
-
if (
|
|
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)) {
|
|
1476
1503
|
return false;
|
|
1477
1504
|
}
|
|
1478
|
-
event.preventDefault();
|
|
1479
|
-
event.stopPropagation();
|
|
1480
1505
|
if (typeof nv.onApplyActiveDraft === "function") {
|
|
1506
|
+
nv._cloudMrSuppressDrawingChangedMouseUp = true;
|
|
1481
1507
|
nv.onApplyActiveDraft();
|
|
1482
1508
|
}
|
|
1483
1509
|
return true;
|
|
1484
1510
|
}
|
|
1485
1511
|
|
|
1512
|
+
/**
|
|
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.
|
|
1515
|
+
*/
|
|
1516
|
+
function cloudMrTryReopenDraftOnClick(nv) {
|
|
1517
|
+
if (
|
|
1518
|
+
nv._cloudMrShapeDraftActive ||
|
|
1519
|
+
nv._cloudMrPenDraftActive ||
|
|
1520
|
+
!isClickWithoutDrag(nv.uiData)
|
|
1521
|
+
) {
|
|
1522
|
+
return false;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
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
|
+
}
|
|
1546
|
+
|
|
1547
|
+
const reopenDraft = capturePenDraftFromClick(nv);
|
|
1548
|
+
if (!reopenDraft) return false;
|
|
1549
|
+
redrawFreehandDraft(nv, reopenDraft);
|
|
1550
|
+
nv._cloudMrSuppressDrawingChangedMouseUp = true;
|
|
1551
|
+
if (typeof nv.onPenDraftReady === "function") {
|
|
1552
|
+
nv.onPenDraftReady(reopenDraft);
|
|
1553
|
+
}
|
|
1554
|
+
if (nv.opts.polylinePenMode) {
|
|
1555
|
+
resetPolylineState(nv);
|
|
1556
|
+
}
|
|
1557
|
+
return true;
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1486
1560
|
const _mouseDownListener = Niivue.prototype.mouseDownListener;
|
|
1487
1561
|
Niivue.prototype.mouseDownListener = function cloudMrMouseDownListener(e) {
|
|
1488
|
-
if (e.button === RIGHT_MOUSE_BUTTON && cloudMrTryApplyDraftOnRightClick(this, e)) {
|
|
1489
|
-
return;
|
|
1490
|
-
}
|
|
1491
1562
|
if (shouldDeferFreehandCommit(this) && this.drawBitmap) {
|
|
1492
1563
|
this._cloudMrFreehandSessionStartBitmap = this.drawBitmap.slice();
|
|
1493
1564
|
this._cloudMrFreehandAxCorSag = -1;
|
|
@@ -1551,17 +1622,30 @@ Niivue.prototype.mouseUpListener = function cloudMrMouseUpListener() {
|
|
|
1551
1622
|
_mouseUpListener.call(this);
|
|
1552
1623
|
|
|
1553
1624
|
if (polylineClick) {
|
|
1625
|
+
if (cloudMrTryReopenDraftOnClick(this)) return;
|
|
1554
1626
|
addPolylineVertex(this);
|
|
1555
1627
|
return;
|
|
1556
1628
|
}
|
|
1557
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
|
+
|
|
1558
1640
|
if (!pendingDraft?.baseBitmap) {
|
|
1641
|
+
cloudMrTryReopenDraftOnClick(this);
|
|
1559
1642
|
return;
|
|
1560
1643
|
}
|
|
1561
1644
|
if (isDraftTooSmall(pendingDraft.ptA, pendingDraft.ptB)) {
|
|
1562
1645
|
this.drawBitmap.set(pendingDraft.baseBitmap);
|
|
1563
1646
|
this.refreshDrawing(true, false);
|
|
1564
1647
|
this.drawScene();
|
|
1648
|
+
cloudMrTryReopenDraftOnClick(this);
|
|
1565
1649
|
return;
|
|
1566
1650
|
}
|
|
1567
1651
|
this._cloudMrSuppressDrawingChangedMouseUp = true;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Adjust handles for polyline (vertex drag) or freehand (move only) drafts.
|
|
3
|
+
* @param {{ nv: any, draft: any, onDraftChange: (d: any) => void, onApplyDraft?: () => void, overlayKey?: unknown }} props
|
|
3
4
|
*/
|
|
4
|
-
export function PenDraftOverlay({ nv, draft, onDraftChange, overlayKey }: {
|
|
5
|
+
export function PenDraftOverlay({ nv, draft, onDraftChange, onApplyDraft, overlayKey }: {
|
|
5
6
|
nv: any;
|
|
6
7
|
draft: any;
|
|
7
|
-
onDraftChange: any;
|
|
8
|
-
|
|
8
|
+
onDraftChange: (d: any) => void;
|
|
9
|
+
onApplyDraft?: (() => void) | undefined;
|
|
10
|
+
overlayKey?: unknown;
|
|
9
11
|
}): import("react/jsx-runtime").JSX.Element | null;
|
|
10
12
|
export default PenDraftOverlay;
|
|
@@ -29,9 +29,10 @@ function cloneFreehandDraft(draft) {
|
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
31
|
* Adjust handles for polyline (vertex drag) or freehand (move only) drafts.
|
|
32
|
+
* @param {{ nv: any, draft: any, onDraftChange: (d: any) => void, onApplyDraft?: () => void, overlayKey?: unknown }} props
|
|
32
33
|
*/
|
|
33
34
|
export function PenDraftOverlay(_a) {
|
|
34
|
-
var nv = _a.nv, draft = _a.draft, onDraftChange = _a.onDraftChange, overlayKey = _a.overlayKey;
|
|
35
|
+
var nv = _a.nv, draft = _a.draft, onDraftChange = _a.onDraftChange, onApplyDraft = _a.onApplyDraft, overlayKey = _a.overlayKey;
|
|
35
36
|
var dragRef = useRef(null);
|
|
36
37
|
var draftRef = useRef(draft);
|
|
37
38
|
draftRef.current = draft;
|
|
@@ -109,6 +110,7 @@ export function PenDraftOverlay(_a) {
|
|
|
109
110
|
onDraftChange(nextDraft);
|
|
110
111
|
}, [nv, onDraftChange]);
|
|
111
112
|
finishDragRef.current = function () {
|
|
113
|
+
var hadDrag = dragRef.current != null;
|
|
112
114
|
dragRef.current = null;
|
|
113
115
|
if (onPointerMoveRef.current) {
|
|
114
116
|
window.removeEventListener("pointermove", onPointerMoveRef.current);
|
|
@@ -116,6 +118,10 @@ export function PenDraftOverlay(_a) {
|
|
|
116
118
|
if (finishDragRef.current) {
|
|
117
119
|
window.removeEventListener("pointerup", finishDragRef.current);
|
|
118
120
|
}
|
|
121
|
+
// Auto-apply as soon as the user releases the handle after a move/resize
|
|
122
|
+
if (hadDrag && onApplyDraft) {
|
|
123
|
+
onApplyDraft();
|
|
124
|
+
}
|
|
119
125
|
};
|
|
120
126
|
onPointerMoveRef.current = function (event) {
|
|
121
127
|
var drag = dragRef.current;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Overlay handles for adjusting a rectangle/ellipse draft before commit.
|
|
3
|
-
* @param {{ nv: any, draft: import('./shapeDraftUtils').ShapeDraft, onDraftChange: (d: any) => void, overlayKey?: unknown }} props
|
|
3
|
+
* @param {{ nv: any, draft: import('./shapeDraftUtils').ShapeDraft, onDraftChange: (d: any) => void, onApplyDraft?: () => void, overlayKey?: unknown }} props
|
|
4
4
|
*/
|
|
5
|
-
export function ShapeDraftOverlay({ nv, draft, onDraftChange, overlayKey }: {
|
|
5
|
+
export function ShapeDraftOverlay({ nv, draft, onDraftChange, onApplyDraft, overlayKey }: {
|
|
6
6
|
nv: any;
|
|
7
7
|
draft: import('./shapeDraftUtils').ShapeDraft;
|
|
8
8
|
onDraftChange: (d: any) => void;
|
|
9
|
+
onApplyDraft?: (() => void) | undefined;
|
|
9
10
|
overlayKey?: unknown;
|
|
10
11
|
}): import("react/jsx-runtime").JSX.Element | null;
|
|
11
12
|
export default ShapeDraftOverlay;
|
|
@@ -25,11 +25,11 @@ var HANDLE_SIZE = 10;
|
|
|
25
25
|
var ACCENT = "#580f8b";
|
|
26
26
|
/**
|
|
27
27
|
* Overlay handles for adjusting a rectangle/ellipse draft before commit.
|
|
28
|
-
* @param {{ nv: any, draft: import('./shapeDraftUtils').ShapeDraft, onDraftChange: (d: any) => void, overlayKey?: unknown }} props
|
|
28
|
+
* @param {{ nv: any, draft: import('./shapeDraftUtils').ShapeDraft, onDraftChange: (d: any) => void, onApplyDraft?: () => void, overlayKey?: unknown }} props
|
|
29
29
|
*/
|
|
30
30
|
export function ShapeDraftOverlay(_a) {
|
|
31
31
|
var _b;
|
|
32
|
-
var nv = _a.nv, draft = _a.draft, onDraftChange = _a.onDraftChange, overlayKey = _a.overlayKey;
|
|
32
|
+
var nv = _a.nv, draft = _a.draft, onDraftChange = _a.onDraftChange, onApplyDraft = _a.onApplyDraft, overlayKey = _a.overlayKey;
|
|
33
33
|
var dragRef = useRef(null);
|
|
34
34
|
var draftRef = useRef(draft);
|
|
35
35
|
draftRef.current = draft;
|
|
@@ -84,6 +84,7 @@ export function ShapeDraftOverlay(_a) {
|
|
|
84
84
|
onDraftChange(nextDraft);
|
|
85
85
|
}, [nv, onDraftChange]);
|
|
86
86
|
finishDragRef.current = function () {
|
|
87
|
+
var hadDrag = dragRef.current != null;
|
|
87
88
|
dragRef.current = null;
|
|
88
89
|
if (onPointerMoveRef.current) {
|
|
89
90
|
window.removeEventListener("pointermove", onPointerMoveRef.current);
|
|
@@ -91,6 +92,10 @@ export function ShapeDraftOverlay(_a) {
|
|
|
91
92
|
if (finishDragRef.current) {
|
|
92
93
|
window.removeEventListener("pointerup", finishDragRef.current);
|
|
93
94
|
}
|
|
95
|
+
// Auto-apply as soon as the user releases the handle after a move/resize
|
|
96
|
+
if (hadDrag && onApplyDraft) {
|
|
97
|
+
onApplyDraft();
|
|
98
|
+
}
|
|
94
99
|
};
|
|
95
100
|
onPointerMoveRef.current = function (event) {
|
|
96
101
|
var drag = dragRef.current;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export default function DrawColorPlatte({ expanded, updateDrawPen, setDrawingEnabled, showPenModes, penDrawMode, onPenDrawModeChange, polylineVertexCount, penDraftActive,
|
|
1
|
+
export default function DrawColorPlatte({ expanded, updateDrawPen, setDrawingEnabled, showPenModes, penDrawMode, onPenDrawModeChange, polylineVertexCount, penDraftActive, onCancelPenDraft, onFillPenDraft, brushSize, updateBrushSize, shapeDraftActive, onCancelShapeDraft, }: {
|
|
2
2
|
expanded: any;
|
|
3
3
|
updateDrawPen: any;
|
|
4
4
|
setDrawingEnabled: any;
|
|
@@ -7,12 +7,10 @@ export default function DrawColorPlatte({ expanded, updateDrawPen, setDrawingEna
|
|
|
7
7
|
onPenDrawModeChange: any;
|
|
8
8
|
polylineVertexCount?: number | undefined;
|
|
9
9
|
penDraftActive?: boolean | undefined;
|
|
10
|
-
onApplyPenDraft: any;
|
|
11
10
|
onCancelPenDraft: any;
|
|
12
11
|
onFillPenDraft: any;
|
|
13
12
|
brushSize?: number | undefined;
|
|
14
13
|
updateBrushSize: any;
|
|
15
14
|
shapeDraftActive?: boolean | undefined;
|
|
16
|
-
onApplyShapeDraft: any;
|
|
17
15
|
onCancelShapeDraft: any;
|
|
18
16
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -11,12 +11,13 @@ var __assign = (this && this.__assign) || function () {
|
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
13
|
/**
|
|
14
|
-
* Pen palette adds freehand vs polyline mode; pen/shape drafts show
|
|
14
|
+
* Pen palette adds freehand vs polyline mode; pen/shape drafts show Cancel while adjusting.
|
|
15
|
+
* Shapes are applied automatically on mouse release.
|
|
15
16
|
*/
|
|
16
17
|
import { Stack, IconButton, Button, Tooltip, Typography } from "@mui/material";
|
|
17
18
|
import FiberManualRecordIcon from "@mui/icons-material/FiberManualRecord";
|
|
18
|
-
import CheckIcon from "@mui/icons-material/Check";
|
|
19
19
|
import CloseIcon from "@mui/icons-material/Close";
|
|
20
|
+
import FormatColorFillIcon from "@mui/icons-material/FormatColorFill";
|
|
20
21
|
import { BrushSizeSlider } from "./BrushSizeSlider";
|
|
21
22
|
var FILLED_COLORS = [
|
|
22
23
|
{ sx: { color: "red" } },
|
|
@@ -37,7 +38,7 @@ var modeBtnSx = function (active) { return ({
|
|
|
37
38
|
px: 0.75
|
|
38
39
|
}); };
|
|
39
40
|
export default function DrawColorPlatte(_a) {
|
|
40
|
-
var expanded = _a.expanded, updateDrawPen = _a.updateDrawPen, setDrawingEnabled = _a.setDrawingEnabled, _b = _a.showPenModes, showPenModes = _b === void 0 ? false : _b, _c = _a.penDrawMode, penDrawMode = _c === void 0 ? "freehand" : _c, onPenDrawModeChange = _a.onPenDrawModeChange, _d = _a.polylineVertexCount, polylineVertexCount = _d === void 0 ? 0 : _d, _e = _a.penDraftActive, penDraftActive = _e === void 0 ? false : _e,
|
|
41
|
+
var expanded = _a.expanded, updateDrawPen = _a.updateDrawPen, setDrawingEnabled = _a.setDrawingEnabled, _b = _a.showPenModes, showPenModes = _b === void 0 ? false : _b, _c = _a.penDrawMode, penDrawMode = _c === void 0 ? "freehand" : _c, onPenDrawModeChange = _a.onPenDrawModeChange, _d = _a.polylineVertexCount, polylineVertexCount = _d === void 0 ? 0 : _d, _e = _a.penDraftActive, penDraftActive = _e === void 0 ? false : _e, onCancelPenDraft = _a.onCancelPenDraft, onFillPenDraft = _a.onFillPenDraft, _f = _a.brushSize, brushSize = _f === void 0 ? 1 : _f, updateBrushSize = _a.updateBrushSize, _g = _a.shapeDraftActive, shapeDraftActive = _g === void 0 ? false : _g, onCancelShapeDraft = _a.onCancelShapeDraft;
|
|
41
42
|
return (_jsxs(Stack, __assign({ style: {
|
|
42
43
|
position: "absolute",
|
|
43
44
|
top: "100%",
|
|
@@ -62,36 +63,22 @@ export default function DrawColorPlatte(_a) {
|
|
|
62
63
|
py: 0.25,
|
|
63
64
|
px: 0.75,
|
|
64
65
|
"& .MuiButton-startIcon": { mr: 0.5, ml: 0 }
|
|
65
|
-
} }, { children: "Cancel" })) })),
|
|
66
|
-
color: "#c9a0e8",
|
|
67
|
-
fontSize: ACTION_FONT_SIZE,
|
|
68
|
-
textTransform: "none",
|
|
69
|
-
minWidth: 0,
|
|
70
|
-
py: 0.25,
|
|
71
|
-
px: 0.75
|
|
72
|
-
} }, { children: "Fill" })) }))), _jsx(Tooltip, __assign({ title: "Apply shape (enter or right-click)" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "apply pen draft", onClick: function () { return onApplyPenDraft === null || onApplyPenDraft === void 0 ? void 0 : onApplyPenDraft(); }, startIcon: _jsx(CheckIcon, { sx: { fontSize: ACTION_ICON_SIZE } }), sx: {
|
|
73
|
-
color: "#c9a0e8",
|
|
74
|
-
fontSize: ACTION_FONT_SIZE,
|
|
75
|
-
textTransform: "none",
|
|
76
|
-
minWidth: 0,
|
|
77
|
-
py: 0.25,
|
|
78
|
-
px: 0.75,
|
|
79
|
-
"& .MuiButton-startIcon": { mr: 0.5, ml: 0 }
|
|
80
|
-
} }, { children: "Apply" })) }))] }))] }))), shapeDraftActive && expanded && (_jsxs(Stack, __assign({ direction: "row", alignItems: "center", justifyContent: "space-between", sx: { px: 1, py: 0.5, borderTop: "1px solid #555", width: "100%" } }, { children: [_jsx(Tooltip, __assign({ title: "Cancel shape (Esc)" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "cancel shape", onClick: function () { return onCancelShapeDraft === null || onCancelShapeDraft === void 0 ? void 0 : onCancelShapeDraft(); }, startIcon: _jsx(CloseIcon, { sx: { fontSize: ACTION_ICON_SIZE } }), sx: {
|
|
81
|
-
color: "#ccc",
|
|
82
|
-
fontSize: ACTION_FONT_SIZE,
|
|
83
|
-
textTransform: "none",
|
|
84
|
-
minWidth: 0,
|
|
85
|
-
py: 0.25,
|
|
86
|
-
px: 0.75,
|
|
87
|
-
"& .MuiButton-startIcon": { mr: 0.5, ml: 0 }
|
|
88
|
-
} }, { children: "Cancel" })) })), _jsx(Tooltip, __assign({ title: "Apply shape (enter or right-click)" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "apply shape", onClick: function () { return onApplyShapeDraft === null || onApplyShapeDraft === void 0 ? void 0 : onApplyShapeDraft(); }, startIcon: _jsx(CheckIcon, { sx: { fontSize: ACTION_ICON_SIZE } }), sx: {
|
|
66
|
+
} }, { children: "Cancel" })) })), penDrawMode === "polyline" && polylineVertexCount >= 3 && (_jsx(Tooltip, __assign({ title: "Fill interior (keeps outline editable until release)" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "fill polyline", onClick: function () { return onFillPenDraft === null || onFillPenDraft === void 0 ? void 0 : onFillPenDraft(); }, startIcon: _jsx(FormatColorFillIcon, { sx: { fontSize: ACTION_ICON_SIZE } }), sx: {
|
|
89
67
|
color: "#c9a0e8",
|
|
90
68
|
fontSize: ACTION_FONT_SIZE,
|
|
91
69
|
textTransform: "none",
|
|
92
70
|
minWidth: 0,
|
|
93
71
|
py: 0.25,
|
|
94
72
|
px: 0.75,
|
|
73
|
+
ml: "auto",
|
|
95
74
|
"& .MuiButton-startIcon": { mr: 0.5, ml: 0 }
|
|
96
|
-
} }, { children: "
|
|
75
|
+
} }, { children: "Fill" })) })))] }))), shapeDraftActive && expanded && (_jsx(Stack, __assign({ direction: "row", alignItems: "center", sx: { px: 1, py: 0.5, borderTop: "1px solid #555", width: "100%" } }, { children: _jsx(Tooltip, __assign({ title: "Cancel shape (Esc)" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "cancel shape", onClick: function () { return onCancelShapeDraft === null || onCancelShapeDraft === void 0 ? void 0 : onCancelShapeDraft(); }, startIcon: _jsx(CloseIcon, { sx: { fontSize: ACTION_ICON_SIZE } }), sx: {
|
|
76
|
+
color: "#ccc",
|
|
77
|
+
fontSize: ACTION_FONT_SIZE,
|
|
78
|
+
textTransform: "none",
|
|
79
|
+
minWidth: 0,
|
|
80
|
+
py: 0.25,
|
|
81
|
+
px: 0.75,
|
|
82
|
+
"& .MuiButton-startIcon": { mr: 0.5, ml: 0 }
|
|
83
|
+
} }, { children: "Cancel" })) })) })))] })));
|
|
97
84
|
}
|
|
@@ -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,34 @@ 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;
|
|
65
|
+
/**
|
|
66
|
+
* When the user clicks on an existing filled ROI while the rectangle/ellipse tool
|
|
67
|
+
* is active (but no draft is currently open), flood-fill the clicked cluster to
|
|
68
|
+
* reconstruct a ShapeDraft so the bounding-box overlay reappears for re-editing.
|
|
69
|
+
*
|
|
70
|
+
* Returns null if the click didn't land on a labeled voxel.
|
|
71
|
+
*/
|
|
72
|
+
export function captureShapeDraftFromClick(nv: any): {
|
|
73
|
+
ptA: any[];
|
|
74
|
+
ptB: any[];
|
|
75
|
+
penValue: number;
|
|
76
|
+
axCorSag: number;
|
|
77
|
+
penType: any;
|
|
78
|
+
baseBitmap: Uint8Array;
|
|
79
|
+
} | null;
|
|
52
80
|
export type ShapeDraftKind = 'rectangle' | 'ellipse';
|
|
53
81
|
export type ShapeDraft = {
|
|
54
82
|
ptA: [number, number, number];
|
|
@@ -19,6 +19,7 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
|
19
19
|
return to.concat(ar || Array.prototype.slice.call(from));
|
|
20
20
|
};
|
|
21
21
|
import { NI_PEN_TYPE } from "./niivuePenType";
|
|
22
|
+
import { voxFromMouse } from "./polylinePenUtils";
|
|
22
23
|
/** @typedef {'rectangle' | 'ellipse'} ShapeDraftKind */
|
|
23
24
|
/**
|
|
24
25
|
* @typedef {Object} ShapeDraft
|
|
@@ -186,3 +187,136 @@ export function shouldDeferShapeCommit(nv) {
|
|
|
186
187
|
(penType === NI_PEN_TYPE.RECTANGLE || penType === NI_PEN_TYPE.ELLIPSE) &&
|
|
187
188
|
nv.drawShapePreviewBitmap);
|
|
188
189
|
}
|
|
190
|
+
function voxelIndex(x, y, z, dx, dy) {
|
|
191
|
+
return x + y * dx + z * dx * dy;
|
|
192
|
+
}
|
|
193
|
+
function decodeVoxelIndex(idx, dx, dy) {
|
|
194
|
+
var z = Math.floor(idx / (dx * dy));
|
|
195
|
+
var rem = idx - z * dx * dy;
|
|
196
|
+
var y = Math.floor(rem / dx);
|
|
197
|
+
var x = rem % dx;
|
|
198
|
+
return [x, y, z];
|
|
199
|
+
}
|
|
200
|
+
function inferAxCorSagFromBounds(x1, y1, z1, x2, y2, z2, fallback) {
|
|
201
|
+
if (fallback === void 0) { fallback = 0; }
|
|
202
|
+
var spanX = x2 - x1;
|
|
203
|
+
var spanY = y2 - y1;
|
|
204
|
+
var spanZ = z2 - z1;
|
|
205
|
+
if (spanZ <= spanX && spanZ <= spanY)
|
|
206
|
+
return 0;
|
|
207
|
+
if (spanY <= spanX && spanY <= spanZ)
|
|
208
|
+
return 1;
|
|
209
|
+
if (spanX <= spanY && spanX <= spanZ)
|
|
210
|
+
return 2;
|
|
211
|
+
return fallback;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Flood-fill a connected voxel cluster from a seed.
|
|
215
|
+
* @returns {{ label: number, visited: Set<number>, voxels: [number,number,number][], bounds: object } | null}
|
|
216
|
+
*/
|
|
217
|
+
export function floodFillClusterFromVox(nv, seedVox) {
|
|
218
|
+
var _a;
|
|
219
|
+
var dims = (_a = nv.back) === null || _a === void 0 ? void 0 : _a.dims;
|
|
220
|
+
if (!dims || !nv.drawBitmap || !seedVox)
|
|
221
|
+
return null;
|
|
222
|
+
var dx = dims[1];
|
|
223
|
+
var dy = dims[2];
|
|
224
|
+
var dz = dims[3];
|
|
225
|
+
var seedIdx = voxelIndex(seedVox[0], seedVox[1], seedVox[2], dx, dy);
|
|
226
|
+
var label = nv.drawBitmap[seedIdx];
|
|
227
|
+
if (!label)
|
|
228
|
+
return null;
|
|
229
|
+
var visited = new Set();
|
|
230
|
+
var queue = [seedIdx];
|
|
231
|
+
visited.add(seedIdx);
|
|
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;
|
|
239
|
+
while (queue.length > 0) {
|
|
240
|
+
var idx = queue.shift();
|
|
241
|
+
var _b = decodeVoxelIndex(idx, dx, dy), x = _b[0], y = _b[1], z = _b[2];
|
|
242
|
+
voxels.push([x, y, z]);
|
|
243
|
+
x1 = Math.min(x1, x);
|
|
244
|
+
y1 = Math.min(y1, y);
|
|
245
|
+
z1 = Math.min(z1, z);
|
|
246
|
+
x2 = Math.max(x2, x);
|
|
247
|
+
y2 = Math.max(y2, y);
|
|
248
|
+
z2 = Math.max(z2, z);
|
|
249
|
+
var neighbors = [
|
|
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],
|
|
256
|
+
];
|
|
257
|
+
for (var _i = 0, neighbors_1 = neighbors; _i < neighbors_1.length; _i++) {
|
|
258
|
+
var _c = neighbors_1[_i], nx = _c[0], ny = _c[1], nz = _c[2];
|
|
259
|
+
if (nx < 0 || ny < 0 || nz < 0 || nx >= dx || ny >= dy || nz >= dz)
|
|
260
|
+
continue;
|
|
261
|
+
var nIdx = voxelIndex(nx, ny, nz, dx, dy);
|
|
262
|
+
if (visited.has(nIdx) || nv.drawBitmap[nIdx] !== label)
|
|
263
|
+
continue;
|
|
264
|
+
visited.add(nIdx);
|
|
265
|
+
queue.push(nIdx);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (!Number.isFinite(x1))
|
|
269
|
+
return null;
|
|
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);
|
|
313
|
+
var axCorSag = inferAxCorSagFromBounds(x1, y1, z1, x2, y2, z2, nv.drawPenAxCorSag >= 0 ? nv.drawPenAxCorSag : 0);
|
|
314
|
+
return {
|
|
315
|
+
ptA: [x1, y1, z1],
|
|
316
|
+
ptB: [x2, y2, z2],
|
|
317
|
+
penValue: label,
|
|
318
|
+
axCorSag: axCorSag,
|
|
319
|
+
penType: nv.opts.penType,
|
|
320
|
+
baseBitmap: baseBitmap
|
|
321
|
+
};
|
|
322
|
+
}
|