cloudmr-ux 4.6.7 → 4.6.9
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.d.ts +2 -0
- package/dist/CmrComponents/niivue-viewer/CloudMrNiivuePanel.js +5 -4
- package/dist/CmrComponents/niivue-viewer/CloudMrNiivueViewer.js +80 -23
- package/dist/CmrComponents/niivue-viewer/NiivuePatcher.js +57 -11
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/DrawColorPlatte.d.ts +1 -3
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/DrawColorPlatte.js +15 -32
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/MroDrawToolkit.js +1 -1
- package/dist/CmrComponents/niivue-viewer/penDraftUtils.d.ts +12 -0
- package/dist/CmrComponents/niivue-viewer/penDraftUtils.js +24 -1
- package/dist/CmrComponents/niivue-viewer/shapeDraftUtils.d.ts +12 -0
- package/dist/CmrComponents/niivue-viewer/shapeDraftUtils.js +3 -3
- package/package.json +1 -1
|
@@ -51,6 +51,7 @@ export interface CloudMrNiivuePanelProps {
|
|
|
51
51
|
shapeDraft: import("./shapeDraftUtils").ShapeDraft | null;
|
|
52
52
|
onShapeDraftChange: (draft: import("./shapeDraftUtils").ShapeDraft) => void;
|
|
53
53
|
onApplyShapeDraft: () => void;
|
|
54
|
+
onApplyShapeDraftKeepTool?: () => void;
|
|
54
55
|
onCancelShapeDraft: () => void;
|
|
55
56
|
penDraft: {
|
|
56
57
|
kind: "polyline" | "freehand";
|
|
@@ -71,6 +72,7 @@ export interface CloudMrNiivuePanelProps {
|
|
|
71
72
|
} | null;
|
|
72
73
|
onPenDraftChange: (draft: NonNullable<CloudMrNiivuePanelProps["penDraft"]>) => void;
|
|
73
74
|
onApplyPenDraft: () => void;
|
|
75
|
+
onApplyPenDraftKeepTool?: () => void;
|
|
74
76
|
onCancelPenDraft: () => void;
|
|
75
77
|
}
|
|
76
78
|
export declare function CloudMrNiivuePanel(props: CloudMrNiivuePanelProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -107,15 +107,16 @@ export function CloudMrNiivuePanel(props) {
|
|
|
107
107
|
props.resampleImage();
|
|
108
108
|
}, [histogram]);
|
|
109
109
|
function applyDrawShapeTool(tool) {
|
|
110
|
-
var _a, _b;
|
|
110
|
+
var _a, _b, _c, _d;
|
|
111
111
|
if (props.shapeDraft) {
|
|
112
|
-
|
|
112
|
+
// Apply (commit) the current draft rather than discarding it
|
|
113
|
+
(_a = props.onApplyShapeDraftKeepTool) === null || _a === void 0 ? void 0 : _a.call(props);
|
|
113
114
|
}
|
|
114
115
|
if (props.penDraft) {
|
|
115
|
-
props.
|
|
116
|
+
(_b = props.onApplyPenDraftKeepTool) === null || _b === void 0 ? void 0 : _b.call(props);
|
|
116
117
|
}
|
|
117
118
|
if (tool !== "pen") {
|
|
118
|
-
(
|
|
119
|
+
(_d = (_c = props.drawToolkitProps).onPenDrawModeChange) === null || _d === void 0 ? void 0 : _d.call(_c, "freehand");
|
|
119
120
|
}
|
|
120
121
|
props.setDrawShapeTool(tool);
|
|
121
122
|
var nv = props.nv;
|
|
@@ -849,22 +849,28 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
849
849
|
nvSetDrawingEnabled(true);
|
|
850
850
|
}
|
|
851
851
|
}
|
|
852
|
-
function applyPenDraftHandler() {
|
|
853
|
-
var
|
|
852
|
+
function applyPenDraftHandler(_a) {
|
|
853
|
+
var _b;
|
|
854
|
+
var _c = _a === void 0 ? {} : _a, _d = _c.keepTool, keepTool = _d === void 0 ? false : _d;
|
|
854
855
|
var draft = penDraftRef.current;
|
|
855
856
|
if (!draft)
|
|
856
857
|
return;
|
|
857
858
|
applyPenDraft(nv, draft);
|
|
859
|
+
markPenVoxelKind(draft, draft.kind === "polyline" ? 3 : 2);
|
|
858
860
|
if (draft.kind === "polyline") {
|
|
859
|
-
(
|
|
861
|
+
(_b = nv.cloudMrResetPolyline) === null || _b === void 0 ? void 0 : _b.call(nv);
|
|
860
862
|
setPolylineVertexCount(0);
|
|
861
863
|
}
|
|
862
864
|
setPenDraft(null);
|
|
863
865
|
penDraftRef.current = null;
|
|
864
866
|
nv._cloudMrPenDraftActive = false;
|
|
865
867
|
setDrawingChanged(true);
|
|
866
|
-
if (
|
|
867
|
-
|
|
868
|
+
if (!keepTool) {
|
|
869
|
+
// Deactivate tool entirely — palette closes, user re-enters edit by clicking the ROI
|
|
870
|
+
setDrawShapeTool(null);
|
|
871
|
+
nv.opts.deferFreehandCommit = false;
|
|
872
|
+
nv.opts.polylinePenMode = false;
|
|
873
|
+
nvSetDrawingEnabled(false);
|
|
868
874
|
}
|
|
869
875
|
resampleImage();
|
|
870
876
|
}
|
|
@@ -891,6 +897,21 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
891
897
|
nv._cloudMrPenDraftActive = true;
|
|
892
898
|
nvSetDrawingEnabled(false);
|
|
893
899
|
};
|
|
900
|
+
// Called by NiivuePatcher when the user clicks an applied pen ROI to re-edit it.
|
|
901
|
+
// penKind: 2=freehand, 3=polyline
|
|
902
|
+
nv.onPenDraftReopenReady = function (draft, penKind) {
|
|
903
|
+
setPenDraft(draft);
|
|
904
|
+
penDraftRef.current = draft;
|
|
905
|
+
nv._cloudMrPenDraftActive = true;
|
|
906
|
+
// Auto-select pen tool so the palette opens
|
|
907
|
+
setDrawShapeTool("pen");
|
|
908
|
+
var mode = penKind === 3 ? "polyline" : "freehand";
|
|
909
|
+
setPenDrawMode(mode);
|
|
910
|
+
penDrawModeRef.current = mode;
|
|
911
|
+
nv.opts.deferFreehandCommit = false;
|
|
912
|
+
nv.opts.polylinePenMode = false;
|
|
913
|
+
nvSetDrawingEnabled(false);
|
|
914
|
+
};
|
|
894
915
|
nv.onPolylineChange = function (count) {
|
|
895
916
|
var _a, _b;
|
|
896
917
|
setPolylineVertexCount(count);
|
|
@@ -924,19 +945,65 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
924
945
|
nvSetDrawingEnabled(true);
|
|
925
946
|
}
|
|
926
947
|
}
|
|
927
|
-
function
|
|
928
|
-
if (!
|
|
948
|
+
function ensureToolKindBitmap() {
|
|
949
|
+
if (!nv.drawBitmap)
|
|
950
|
+
return;
|
|
951
|
+
if (!nv._cloudMrToolKindBitmap || nv._cloudMrToolKindBitmap.length !== nv.drawBitmap.length) {
|
|
952
|
+
nv._cloudMrToolKindBitmap = new Uint8Array(nv.drawBitmap.length);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
function markShapeVoxelKind(draft) {
|
|
956
|
+
if (!nv.drawBitmap || !(draft === null || draft === void 0 ? void 0 : draft.baseBitmap))
|
|
957
|
+
return;
|
|
958
|
+
ensureToolKindBitmap();
|
|
959
|
+
var tkb = nv._cloudMrToolKindBitmap;
|
|
960
|
+
for (var i = 0; i < nv.drawBitmap.length; i++) {
|
|
961
|
+
if (nv.drawBitmap[i] === draft.penValue && draft.baseBitmap[i] !== draft.penValue) {
|
|
962
|
+
tkb[i] = 1; // shape
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
function markPenVoxelKind(draft, kind) {
|
|
967
|
+
var _a;
|
|
968
|
+
if (!nv.drawBitmap)
|
|
969
|
+
return;
|
|
970
|
+
ensureToolKindBitmap();
|
|
971
|
+
var tkb = nv._cloudMrToolKindBitmap;
|
|
972
|
+
var dims = (_a = nv.back) === null || _a === void 0 ? void 0 : _a.dims;
|
|
973
|
+
if (draft.kind === "freehand" && draft.strokeVoxels && dims) {
|
|
974
|
+
var dx = dims[1];
|
|
975
|
+
var dy = dims[2];
|
|
976
|
+
for (var _i = 0, _b = draft.strokeVoxels; _i < _b.length; _i++) {
|
|
977
|
+
var _c = _b[_i], x = _c[0], y = _c[1], z = _c[2];
|
|
978
|
+
tkb[x + y * dx + z * dx * dy] = kind;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
else if (draft.baseBitmap) {
|
|
982
|
+
for (var i = 0; i < nv.drawBitmap.length; i++) {
|
|
983
|
+
if (nv.drawBitmap[i] === draft.penValue && draft.baseBitmap[i] !== draft.penValue) {
|
|
984
|
+
tkb[i] = kind;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
function applyShapeDraft(_a) {
|
|
990
|
+
var _b = _a === void 0 ? {} : _a, _c = _b.keepTool, keepTool = _c === void 0 ? false : _c;
|
|
991
|
+
var draft = shapeDraftRef.current;
|
|
992
|
+
if (!draft)
|
|
929
993
|
return;
|
|
930
994
|
nv.drawAddUndoBitmap(nv.drawFillOverwrites);
|
|
995
|
+
markShapeVoxelKind(draft);
|
|
931
996
|
setDrawingChanged(true);
|
|
932
997
|
setShapeDraft(null);
|
|
933
998
|
shapeDraftRef.current = null;
|
|
934
999
|
nv._cloudMrShapeDraftActive = false;
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1000
|
+
if (!keepTool) {
|
|
1001
|
+
// Deactivate tool entirely — palette closes, user re-enters edit by clicking the ROI
|
|
1002
|
+
setDrawShapeTool(null);
|
|
1003
|
+
nv.opts.deferShapeCommit = false;
|
|
1004
|
+
nv.opts.penType = NI_PEN_TYPE.PEN;
|
|
1005
|
+
nvSetDrawingEnabled(false);
|
|
1006
|
+
}
|
|
940
1007
|
resampleImage();
|
|
941
1008
|
}
|
|
942
1009
|
function onShapeDraftChange(draft) {
|
|
@@ -999,16 +1066,6 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
999
1066
|
return undefined;
|
|
1000
1067
|
}
|
|
1001
1068
|
var onKeyDown = function (event) {
|
|
1002
|
-
if (event.key === "Escape") {
|
|
1003
|
-
event.preventDefault();
|
|
1004
|
-
if (shapeDraft) {
|
|
1005
|
-
cancelShapeDraft();
|
|
1006
|
-
}
|
|
1007
|
-
else if (penDraft) {
|
|
1008
|
-
cancelPenDraftHandler();
|
|
1009
|
-
}
|
|
1010
|
-
return;
|
|
1011
|
-
}
|
|
1012
1069
|
if (event.key === "Enter") {
|
|
1013
1070
|
event.preventDefault();
|
|
1014
1071
|
if (shapeDraft) {
|
|
@@ -1543,7 +1600,7 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
1543
1600
|
props.rois[selectedROI].filename : undefined) }), props.niis[selectedVolume] != undefined && _jsx(CloudMrNiivuePanel, { nv: nv, transformFactors: transformFactors, decimalPrecision: decimalPrecision, locationData: locationData, locationTableVisible: locationTableVisible, pipelineID: props.pipelineID, resampleImage: resampleImage, rois: rois, drawToolkitProps: drawToolkitProps, drawShapeTool: drawShapeTool, setDrawShapeTool: setDrawShapeTool, mins: boundMins, maxs: boundMaxs, mms: mms, min: min, max: max, setMin: setMin, setMax: setMax, unzipAndRenderROI: unpackROI, zipAndSendROI: zipAndSendDrawingLayer, setLabelAlias: setLabelAlias, onAfterRoiUpload: function () {
|
|
1544
1601
|
var _a;
|
|
1545
1602
|
void ((_a = props.refreshPipelineRois) === null || _a === void 0 ? void 0 : _a.call(props));
|
|
1546
|
-
}, gamma: gamma, gammaKey: gammaKey, setGamma: setGamma, shapeDraft: shapeDraft, onShapeDraftChange: onShapeDraftChange, onApplyShapeDraft: applyShapeDraft, onCancelShapeDraft: cancelShapeDraft, penDraft: penDraft, onPenDraftChange: onPenDraftChange, onApplyPenDraft: applyPenDraftHandler, onCancelPenDraft: cancelPenDraftHandler }, "".concat(selectedVolume))] })));
|
|
1603
|
+
}, gamma: gamma, gammaKey: gammaKey, setGamma: setGamma, shapeDraft: shapeDraft, onShapeDraftChange: onShapeDraftChange, onApplyShapeDraft: function () { return applyShapeDraft(); }, onApplyShapeDraftKeepTool: function () { return applyShapeDraft({ keepTool: true }); }, onCancelShapeDraft: cancelShapeDraft, penDraft: penDraft, onPenDraftChange: onPenDraftChange, onApplyPenDraft: function () { return applyPenDraftHandler(); }, onApplyPenDraftKeepTool: function () { return applyPenDraftHandler({ keepTool: true }); }, onCancelPenDraft: cancelPenDraftHandler }, "".concat(selectedVolume))] })));
|
|
1547
1604
|
}
|
|
1548
1605
|
function niiToVolume(nii) {
|
|
1549
1606
|
return {
|
|
@@ -19,9 +19,12 @@ import {
|
|
|
19
19
|
isPolylinePenActive,
|
|
20
20
|
previewPolylineSegment,
|
|
21
21
|
resetPolylineState,
|
|
22
|
+
voxFromMouse,
|
|
22
23
|
} from "./polylinePenUtils.js";
|
|
23
24
|
import {
|
|
24
25
|
captureFreehandDraft,
|
|
26
|
+
capturePenDraftFromClick,
|
|
27
|
+
redrawFreehandDraft,
|
|
25
28
|
shouldDeferFreehandCommit,
|
|
26
29
|
} from "./penDraftUtils.js";
|
|
27
30
|
|
|
@@ -1486,18 +1489,30 @@ function cloudMrTryApplyDraftOnRightClick(nv, event) {
|
|
|
1486
1489
|
return true;
|
|
1487
1490
|
}
|
|
1488
1491
|
|
|
1492
|
+
/** Returns the tool-kind code stored for the voxel under the mouse (0=unknown,1=shape,2=pen freehand,3=pen polyline). */
|
|
1493
|
+
function clickedVoxelToolKind(nv) {
|
|
1494
|
+
const bitmap = nv._cloudMrToolKindBitmap;
|
|
1495
|
+
if (!bitmap || !nv.back?.dims) return 0;
|
|
1496
|
+
const seedVox = voxFromMouse(nv);
|
|
1497
|
+
if (!seedVox) return 0;
|
|
1498
|
+
const dims = nv.back.dims;
|
|
1499
|
+
const dx = dims[1];
|
|
1500
|
+
const dy = dims[2];
|
|
1501
|
+
const idx = seedVox[0] + seedVox[1] * dx + seedVox[2] * dx * dy;
|
|
1502
|
+
return bitmap[idx] || 0;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1489
1505
|
/**
|
|
1490
|
-
* Re-enter rectangle/ellipse edit mode when clicking an existing applied ROI.
|
|
1491
|
-
*
|
|
1492
|
-
* so clicking an ROI after apply always re-enters edit mode.
|
|
1506
|
+
* Re-enter rectangle/ellipse edit mode when clicking an existing applied shape ROI.
|
|
1507
|
+
* Skips voxels that were drawn with the pen tool (those are handled by pen reopen).
|
|
1493
1508
|
*/
|
|
1494
1509
|
function cloudMrTryReopenShapeDraftOnClick(nv) {
|
|
1495
|
-
if (nv._cloudMrShapeDraftActive || nv._cloudMrPenDraftActive)
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1510
|
+
if (nv._cloudMrShapeDraftActive || nv._cloudMrPenDraftActive) return false;
|
|
1511
|
+
if (!isClickWithoutDrag(nv.uiData)) return false;
|
|
1512
|
+
|
|
1513
|
+
// If we know this voxel was drawn with the pen, skip shape reopen
|
|
1514
|
+
const kind = clickedVoxelToolKind(nv);
|
|
1515
|
+
if (kind === 2 || kind === 3) return false;
|
|
1501
1516
|
|
|
1502
1517
|
const reopenDraft = captureShapeDraftFromClick(nv);
|
|
1503
1518
|
if (!reopenDraft) return false;
|
|
@@ -1510,6 +1525,33 @@ function cloudMrTryReopenShapeDraftOnClick(nv) {
|
|
|
1510
1525
|
return true;
|
|
1511
1526
|
}
|
|
1512
1527
|
|
|
1528
|
+
/**
|
|
1529
|
+
* Re-enter pen (freehand) edit mode when clicking an existing applied pen ROI.
|
|
1530
|
+
* Skips voxels that were drawn with a shape tool.
|
|
1531
|
+
*/
|
|
1532
|
+
function cloudMrTryReopenPenDraftOnClick(nv) {
|
|
1533
|
+
if (nv._cloudMrShapeDraftActive || nv._cloudMrPenDraftActive) return false;
|
|
1534
|
+
if (!isClickWithoutDrag(nv.uiData)) return false;
|
|
1535
|
+
|
|
1536
|
+
// Only handle pen voxels — skip if this was drawn with a shape tool
|
|
1537
|
+
const kind = clickedVoxelToolKind(nv);
|
|
1538
|
+
if (kind === 1) return false;
|
|
1539
|
+
|
|
1540
|
+
const draft = capturePenDraftFromClick(nv);
|
|
1541
|
+
if (!draft) return false;
|
|
1542
|
+
|
|
1543
|
+
// Look up pen sub-mode (freehand=2 or polyline=3) from the kind bitmap
|
|
1544
|
+
let penKind = kind; // 2 or 3; if 0 (unknown) default to freehand (2)
|
|
1545
|
+
if (penKind !== 3) penKind = 2;
|
|
1546
|
+
|
|
1547
|
+
redrawFreehandDraft(nv, draft);
|
|
1548
|
+
nv._cloudMrSuppressDrawingChangedMouseUp = true;
|
|
1549
|
+
if (typeof nv.onPenDraftReopenReady === "function") {
|
|
1550
|
+
nv.onPenDraftReopenReady(draft, penKind);
|
|
1551
|
+
}
|
|
1552
|
+
return true;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1513
1555
|
const _mouseDownListener = Niivue.prototype.mouseDownListener;
|
|
1514
1556
|
Niivue.prototype.mouseDownListener = function cloudMrMouseDownListener(e) {
|
|
1515
1557
|
if (e.button === RIGHT_MOUSE_BUTTON && cloudMrTryApplyDraftOnRightClick(this, e)) {
|
|
@@ -1583,14 +1625,18 @@ Niivue.prototype.mouseUpListener = function cloudMrMouseUpListener() {
|
|
|
1583
1625
|
}
|
|
1584
1626
|
|
|
1585
1627
|
if (!pendingDraft?.baseBitmap) {
|
|
1586
|
-
cloudMrTryReopenShapeDraftOnClick(this)
|
|
1628
|
+
if (!cloudMrTryReopenShapeDraftOnClick(this)) {
|
|
1629
|
+
cloudMrTryReopenPenDraftOnClick(this);
|
|
1630
|
+
}
|
|
1587
1631
|
return;
|
|
1588
1632
|
}
|
|
1589
1633
|
if (isDraftTooSmall(pendingDraft.ptA, pendingDraft.ptB)) {
|
|
1590
1634
|
this.drawBitmap.set(pendingDraft.baseBitmap);
|
|
1591
1635
|
this.refreshDrawing(true, false);
|
|
1592
1636
|
this.drawScene();
|
|
1593
|
-
cloudMrTryReopenShapeDraftOnClick(this)
|
|
1637
|
+
if (!cloudMrTryReopenShapeDraftOnClick(this)) {
|
|
1638
|
+
cloudMrTryReopenPenDraftOnClick(this);
|
|
1639
|
+
}
|
|
1594
1640
|
return;
|
|
1595
1641
|
}
|
|
1596
1642
|
this._cloudMrSuppressDrawingChangedMouseUp = true;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export default function DrawColorPlatte({ expanded, updateDrawPen, setDrawingEnabled, showPenModes, penDrawMode, onPenDrawModeChange, polylineVertexCount, penDraftActive, onApplyPenDraft,
|
|
1
|
+
export default function DrawColorPlatte({ expanded, updateDrawPen, setDrawingEnabled, showPenModes, penDrawMode, onPenDrawModeChange, polylineVertexCount, penDraftActive, onApplyPenDraft, onFillPenDraft, brushSize, updateBrushSize, shapeDraftActive, onApplyShapeDraft, }: {
|
|
2
2
|
expanded: any;
|
|
3
3
|
updateDrawPen: any;
|
|
4
4
|
setDrawingEnabled: any;
|
|
@@ -8,11 +8,9 @@ export default function DrawColorPlatte({ expanded, updateDrawPen, setDrawingEna
|
|
|
8
8
|
polylineVertexCount?: number | undefined;
|
|
9
9
|
penDraftActive?: boolean | undefined;
|
|
10
10
|
onApplyPenDraft: any;
|
|
11
|
-
onCancelPenDraft: any;
|
|
12
11
|
onFillPenDraft: any;
|
|
13
12
|
brushSize?: number | undefined;
|
|
14
13
|
updateBrushSize: any;
|
|
15
14
|
shapeDraftActive?: boolean | undefined;
|
|
16
15
|
onApplyShapeDraft: any;
|
|
17
|
-
onCancelShapeDraft: any;
|
|
18
16
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -11,12 +11,11 @@ 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 Apply
|
|
14
|
+
* Pen palette adds freehand vs polyline mode; pen/shape drafts show Apply while adjusting.
|
|
15
15
|
*/
|
|
16
16
|
import { Stack, IconButton, Button, Tooltip, Typography } from "@mui/material";
|
|
17
17
|
import FiberManualRecordIcon from "@mui/icons-material/FiberManualRecord";
|
|
18
18
|
import CheckIcon from "@mui/icons-material/Check";
|
|
19
|
-
import CloseIcon from "@mui/icons-material/Close";
|
|
20
19
|
import { BrushSizeSlider } from "./BrushSizeSlider";
|
|
21
20
|
var FILLED_COLORS = [
|
|
22
21
|
{ sx: { color: "red" } },
|
|
@@ -37,7 +36,7 @@ var modeBtnSx = function (active) { return ({
|
|
|
37
36
|
px: 0.75
|
|
38
37
|
}); };
|
|
39
38
|
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, onApplyPenDraft = _a.onApplyPenDraft,
|
|
39
|
+
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, onApplyPenDraft = _a.onApplyPenDraft, onFillPenDraft = _a.onFillPenDraft, _f = _a.brushSize, brushSize = _f === void 0 ? 1 : _f, updateBrushSize = _a.updateBrushSize, _g = _a.shapeDraftActive, shapeDraftActive = _g === void 0 ? false : _g, onApplyShapeDraft = _a.onApplyShapeDraft;
|
|
41
40
|
return (_jsxs(Stack, __assign({ style: {
|
|
42
41
|
position: "absolute",
|
|
43
42
|
top: "100%",
|
|
@@ -54,38 +53,14 @@ export default function DrawColorPlatte(_a) {
|
|
|
54
53
|
}, direction: "column", spacing: 0.5, sx: { py: expanded ? 0.5 : 0 } }, { children: [showPenModes && expanded && (_jsxs(Stack, __assign({ direction: "row", alignItems: "center", spacing: 0.5, sx: { px: 0.75, pt: 0.25 } }, { children: [_jsx(Button, __assign({ size: "small", onClick: function () { return onPenDrawModeChange === null || onPenDrawModeChange === void 0 ? void 0 : onPenDrawModeChange("freehand"); }, sx: modeBtnSx(penDrawMode === "freehand") }, { children: "Freehand" })), _jsx(Button, __assign({ size: "small", onClick: function () { return onPenDrawModeChange === null || onPenDrawModeChange === void 0 ? void 0 : onPenDrawModeChange("polyline"); }, sx: modeBtnSx(penDrawMode === "polyline") }, { children: "Polyline" }))] }))), showPenModes && expanded && updateBrushSize && (_jsx(BrushSizeSlider, { label: "Line thickness", brushSize: brushSize, updateBrushSize: updateBrushSize })), _jsx(Stack, __assign({ direction: "row" }, { children: FILLED_COLORS.map(function (color, index) { return (_jsx(IconButton, __assign({ onClick: function () {
|
|
55
54
|
updateDrawPen({ target: { value: index + 1 } });
|
|
56
55
|
setDrawingEnabled(true);
|
|
57
|
-
} }, { children: _jsx(FiberManualRecordIcon, { sx: color.sx }) }), index)); }) })), showPenModes && penDrawMode === "polyline" && expanded && polylineVertexCount === 0 && (_jsx(Typography, __assign({ sx: { px: 1, pb: 0.5, fontSize: "0.68rem", color: "#aaa", userSelect: "none" } }, { children: "Click each vertex to draw connected line segments" }))), showPenModes && penDraftActive && expanded && (_jsxs(Stack, __assign({ direction: "row", alignItems: "center", justifyContent: "
|
|
58
|
-
color: "#
|
|
59
|
-
fontSize: ACTION_FONT_SIZE,
|
|
60
|
-
textTransform: "none",
|
|
61
|
-
minWidth: 0,
|
|
62
|
-
py: 0.25,
|
|
63
|
-
px: 0.75,
|
|
64
|
-
"& .MuiButton-startIcon": { mr: 0.5, ml: 0 }
|
|
65
|
-
} }, { children: "Cancel" })) })), _jsxs(Stack, __assign({ direction: "row", alignItems: "center", spacing: 1 }, { children: [penDrawMode === "polyline" && polylineVertexCount >= 3 && (_jsx(Tooltip, __assign({ title: "Fill interior (keeps outline editable until Apply)" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "fill polyline", onClick: function () { return onFillPenDraft === null || onFillPenDraft === void 0 ? void 0 : onFillPenDraft(); }, sx: {
|
|
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",
|
|
56
|
+
} }, { children: _jsx(FiberManualRecordIcon, { sx: color.sx }) }), index)); }) })), showPenModes && penDrawMode === "polyline" && expanded && polylineVertexCount === 0 && (_jsx(Typography, __assign({ sx: { px: 1, pb: 0.5, fontSize: "0.68rem", color: "#aaa", userSelect: "none" } }, { children: "Click each vertex to draw connected line segments" }))), showPenModes && penDraftActive && expanded && (_jsxs(Stack, __assign({ direction: "row", alignItems: "center", justifyContent: "flex-end", spacing: 1, sx: { px: 1, py: 0.5, borderTop: "1px solid #555", width: "100%" } }, { children: [penDrawMode === "polyline" && polylineVertexCount >= 3 && (_jsx(Tooltip, __assign({ title: "Fill interior (keeps outline editable until Apply)" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "fill polyline", onClick: function () { return onFillPenDraft === null || onFillPenDraft === void 0 ? void 0 : onFillPenDraft(); }, sx: {
|
|
57
|
+
color: "#c9a0e8",
|
|
82
58
|
fontSize: ACTION_FONT_SIZE,
|
|
83
59
|
textTransform: "none",
|
|
84
60
|
minWidth: 0,
|
|
85
61
|
py: 0.25,
|
|
86
|
-
px: 0.75
|
|
87
|
-
|
|
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: {
|
|
62
|
+
px: 0.75
|
|
63
|
+
} }, { 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: {
|
|
89
64
|
color: "#c9a0e8",
|
|
90
65
|
fontSize: ACTION_FONT_SIZE,
|
|
91
66
|
textTransform: "none",
|
|
@@ -93,5 +68,13 @@ export default function DrawColorPlatte(_a) {
|
|
|
93
68
|
py: 0.25,
|
|
94
69
|
px: 0.75,
|
|
95
70
|
"& .MuiButton-startIcon": { mr: 0.5, ml: 0 }
|
|
96
|
-
} }, { children: "Apply" })) }))] })))
|
|
71
|
+
} }, { children: "Apply" })) }))] }))), shapeDraftActive && expanded && (_jsx(Stack, __assign({ direction: "row", alignItems: "center", justifyContent: "flex-end", sx: { px: 1, py: 0.5, borderTop: "1px solid #555", width: "100%" } }, { children: _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: {
|
|
72
|
+
color: "#c9a0e8",
|
|
73
|
+
fontSize: ACTION_FONT_SIZE,
|
|
74
|
+
textTransform: "none",
|
|
75
|
+
minWidth: 0,
|
|
76
|
+
py: 0.25,
|
|
77
|
+
px: 0.75,
|
|
78
|
+
"& .MuiButton-startIcon": { mr: 0.5, ml: 0 }
|
|
79
|
+
} }, { children: "Apply" })) })) })))] })));
|
|
97
80
|
}
|
|
@@ -189,7 +189,7 @@ export function MroDrawToolkit(props) {
|
|
|
189
189
|
alignItems: "center",
|
|
190
190
|
gap: 4,
|
|
191
191
|
overflow: "visible"
|
|
192
|
-
} }, { children: [_jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "d" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Pen" }, { children: _jsx(IconButton, __assign({ "aria-label": "pen", size: "small", onClick: clickPen, sx: __assign(__assign({}, toolBtnSx), shapeSelectedSx("pen")) }, { children: _jsx(DrawIcon, { sx: { color: "inherit" } }) })) })), _jsx(DrawColorPlatte, { expanded: expandedOption === "d", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, showPenModes: true, penDrawMode: props.penDrawMode, onPenDrawModeChange: props.onPenDrawModeChange, polylineVertexCount: props.polylineVertexCount, onCancelPolyline: props.onCancelPolyline, penDraftActive: props.penDraftActive, onApplyPenDraft: props.onApplyPenDraft,
|
|
192
|
+
} }, { children: [_jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "d" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Pen" }, { children: _jsx(IconButton, __assign({ "aria-label": "pen", size: "small", onClick: clickPen, sx: __assign(__assign({}, toolBtnSx), shapeSelectedSx("pen")) }, { children: _jsx(DrawIcon, { sx: { color: "inherit" } }) })) })), _jsx(DrawColorPlatte, { expanded: expandedOption === "d", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, showPenModes: true, penDrawMode: props.penDrawMode, onPenDrawModeChange: props.onPenDrawModeChange, polylineVertexCount: props.polylineVertexCount, onCancelPolyline: props.onCancelPolyline, penDraftActive: props.penDraftActive, onApplyPenDraft: props.onApplyPenDraft, onFillPenDraft: props.onFillPenDraft, brushSize: props.brushSize, updateBrushSize: props.updateBrushSize })] })), _jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "r" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Rectangle" }, { children: _jsx(IconButton, __assign({ "aria-label": "rectangle", size: "small", onClick: clickRectangle, sx: __assign(__assign({}, toolBtnSx), shapeSelectedSx("rectangle")) }, { children: _jsx(CropSquareOutlinedIcon, { sx: { color: "inherit" } }) })) })), _jsx(DrawColorPlatte, { expanded: expandedOption === "r", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, shapeDraftActive: props.shapeDraftActive && drawShapeTool === "rectangle", onApplyShapeDraft: props.onApplyShapeDraft })] })), _jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "l" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Ellipse" }, { children: _jsx(IconButton, __assign({ "aria-label": "ellipse", size: "small", onClick: clickEllipse, sx: __assign(__assign({}, toolBtnSx), shapeSelectedSx("ellipse")) }, { children: _jsx(CircleOutlinedIcon, { sx: { color: "inherit" } }) })) })), _jsx(DrawColorPlatte, { expanded: expandedOption === "l", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, shapeDraftActive: props.shapeDraftActive && drawShapeTool === "ellipse", onApplyShapeDraft: props.onApplyShapeDraft })] })), _jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "e" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Eraser" }, { children: _jsx(IconButton, __assign({ "aria-label": "erase", size: "small", onClick: clickEraser, sx: __assign(__assign({}, toolBtnSx), (eraserActive
|
|
193
193
|
? { backgroundColor: "rgba(88, 15, 139, 0.12)", color: "#580f8b" }
|
|
194
194
|
: {})) }, { children: filled || !eraserActive ? (_jsx(EraserIcon, {})) : (_jsx(AutoFixNormalOutlinedIcon, { sx: { color: ICON_COLOR } })) })) })), _jsx(EraserPlatte, { expandEraseOptions: expandedOption === "e", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, brushSize: props.brushSize, updateBrushSize: props.updateBrushSize })] })), _jsx(Tooltip, __assign({ title: "Undo" }, { children: _jsx(IconButton, __assign({ "aria-label": "revert", size: "small", onClick: function () { return props.drawUndo(); }, sx: toolBtnSx }, { children: _jsx(ReplyIcon, { sx: { color: ICON_COLOR } }) })) })), _jsx(Tooltip, __assign({ title: "Save screenshot" }, { children: _jsx("span", { children: _jsx(IconButton, __assign({ "aria-label": "capture", size: "small", disabled: !vol, onClick: function () { return vol && props.nv.saveScene("".concat(vol.name, "_drawing.png")); }, sx: toolBtnSx }, { children: _jsx(CameraAltIcon, { sx: { color: ICON_COLOR } }) })) }) })), _jsx(Tooltip, __assign({ title: "Clear drawing" }, { children: _jsx(IconButton, __assign({ "aria-label": "delete", size: "small", onClick: function () {
|
|
195
195
|
props.nv.clearDrawing();
|
|
@@ -49,6 +49,18 @@ export function polylineDraftFromNv(nv: any, { filled }?: {
|
|
|
49
49
|
/** Fill polyline interior without closing the outline or committing the draft. */
|
|
50
50
|
export function fillPolylineDraft(nv: any, draft: any): any;
|
|
51
51
|
export function applyPenDraft(nv: any, draft: any): void;
|
|
52
|
+
/**
|
|
53
|
+
* Flood-fill from the clicked voxel to reconstruct a freehand PenDraft for re-editing.
|
|
54
|
+
* Returns null if the click didn't land on a labeled voxel.
|
|
55
|
+
*/
|
|
56
|
+
export function capturePenDraftFromClick(nv: any): {
|
|
57
|
+
kind: string;
|
|
58
|
+
baseBitmap: Uint8Array;
|
|
59
|
+
axCorSag: number;
|
|
60
|
+
penValue: number;
|
|
61
|
+
strokeVoxels: [number, number, number][];
|
|
62
|
+
bounds: object;
|
|
63
|
+
} | null;
|
|
52
64
|
export function cancelPenDraft(nv: any, draft: any): void;
|
|
53
65
|
export type PenDraftKind = 'polyline' | 'freehand';
|
|
54
66
|
export type PenDraft = {
|
|
@@ -18,8 +18,9 @@ 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, inferAxCorSagFromBounds, } from "./shapeDraftUtils";
|
|
22
22
|
import { NI_PEN_TYPE } from "./niivuePenType";
|
|
23
|
+
import { voxFromMouse } from "./polylinePenUtils";
|
|
23
24
|
/** @typedef {'polyline' | 'freehand'} PenDraftKind */
|
|
24
25
|
/**
|
|
25
26
|
* @typedef {Object} PenDraft
|
|
@@ -265,6 +266,28 @@ export function applyPenDraft(nv, draft) {
|
|
|
265
266
|
nv.onDrawingChanged("draw");
|
|
266
267
|
}
|
|
267
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Flood-fill from the clicked voxel to reconstruct a freehand PenDraft for re-editing.
|
|
271
|
+
* Returns null if the click didn't land on a labeled voxel.
|
|
272
|
+
*/
|
|
273
|
+
export function capturePenDraftFromClick(nv) {
|
|
274
|
+
var seedVox = voxFromMouse(nv);
|
|
275
|
+
var cluster = floodFillClusterFromVox(nv, seedVox);
|
|
276
|
+
if (!cluster)
|
|
277
|
+
return null;
|
|
278
|
+
var label = cluster.label, visited = cluster.visited, voxels = cluster.voxels, bounds = cluster.bounds;
|
|
279
|
+
var x1 = bounds.x1, y1 = bounds.y1, z1 = bounds.z1, x2 = bounds.x2, y2 = bounds.y2, z2 = bounds.z2;
|
|
280
|
+
var baseBitmap = eraseClusterFromBitmap(nv.drawBitmap, visited);
|
|
281
|
+
var axCorSag = inferAxCorSagFromBounds(x1, y1, z1, x2, y2, z2, nv.drawPenAxCorSag >= 0 ? nv.drawPenAxCorSag : 0);
|
|
282
|
+
return {
|
|
283
|
+
kind: "freehand",
|
|
284
|
+
baseBitmap: baseBitmap,
|
|
285
|
+
axCorSag: axCorSag,
|
|
286
|
+
penValue: label,
|
|
287
|
+
strokeVoxels: voxels,
|
|
288
|
+
bounds: bounds
|
|
289
|
+
};
|
|
290
|
+
}
|
|
268
291
|
export function cancelPenDraft(nv, draft) {
|
|
269
292
|
if (draft === null || draft === void 0 ? void 0 : draft.baseBitmap) {
|
|
270
293
|
nv.drawBitmap.set(draft.baseBitmap);
|
|
@@ -49,6 +49,18 @@ export function captureDeferredShapeDraft(nv: any): {
|
|
|
49
49
|
baseBitmap: Uint8Array | null;
|
|
50
50
|
};
|
|
51
51
|
export function shouldDeferShapeCommit(nv: any): any;
|
|
52
|
+
export function inferAxCorSagFromBounds(x1: any, y1: any, z1: any, x2: any, y2: any, z2: any, fallback?: number): number;
|
|
53
|
+
/**
|
|
54
|
+
* Flood-fill a connected voxel cluster from a seed.
|
|
55
|
+
* @returns {{ label: number, visited: Set<number>, voxels: [number,number,number][], bounds: object } | null}
|
|
56
|
+
*/
|
|
57
|
+
export function floodFillClusterFromVox(nv: any, seedVox: any): {
|
|
58
|
+
label: number;
|
|
59
|
+
visited: Set<number>;
|
|
60
|
+
voxels: [number, number, number][];
|
|
61
|
+
bounds: object;
|
|
62
|
+
} | null;
|
|
63
|
+
export function eraseClusterFromBitmap(bitmap: any, visited: any): Uint8Array;
|
|
52
64
|
/**
|
|
53
65
|
* When the user clicks on an existing filled ROI while the rectangle/ellipse tool
|
|
54
66
|
* is active (but no draft is currently open), flood-fill the clicked cluster to
|
|
@@ -197,7 +197,7 @@ function decodeVoxelIndex(idx, dx, dy) {
|
|
|
197
197
|
var x = rem % dx;
|
|
198
198
|
return [x, y, z];
|
|
199
199
|
}
|
|
200
|
-
function inferAxCorSagFromBounds(x1, y1, z1, x2, y2, z2, fallback) {
|
|
200
|
+
export function inferAxCorSagFromBounds(x1, y1, z1, x2, y2, z2, fallback) {
|
|
201
201
|
if (fallback === void 0) { fallback = 0; }
|
|
202
202
|
var spanX = x2 - x1;
|
|
203
203
|
var spanY = y2 - y1;
|
|
@@ -221,7 +221,7 @@ function sliceKey(axCorSag, x, y, z) {
|
|
|
221
221
|
* Flood-fill a connected voxel cluster from a seed.
|
|
222
222
|
* @returns {{ label: number, visited: Set<number>, voxels: [number,number,number][], bounds: object } | null}
|
|
223
223
|
*/
|
|
224
|
-
function floodFillClusterFromVox(nv, seedVox) {
|
|
224
|
+
export function floodFillClusterFromVox(nv, seedVox) {
|
|
225
225
|
var _a;
|
|
226
226
|
var dims = (_a = nv.back) === null || _a === void 0 ? void 0 : _a.dims;
|
|
227
227
|
if (!dims || !nv.drawBitmap || !seedVox)
|
|
@@ -281,7 +281,7 @@ function floodFillClusterFromVox(nv, seedVox) {
|
|
|
281
281
|
bounds: { x1: x1, y1: y1, z1: z1, x2: x2, y2: y2, z2: z2 }
|
|
282
282
|
};
|
|
283
283
|
}
|
|
284
|
-
function eraseClusterFromBitmap(bitmap, visited) {
|
|
284
|
+
export function eraseClusterFromBitmap(bitmap, visited) {
|
|
285
285
|
var next = new Uint8Array(bitmap);
|
|
286
286
|
visited.forEach(function (idx) {
|
|
287
287
|
next[idx] = 0;
|