cloudmr-ux 4.8.2 → 4.8.4
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 -2
- package/dist/CmrComponents/niivue-viewer/CloudMrNiivuePanel.js +12 -11
- package/dist/CmrComponents/niivue-viewer/CloudMrNiivueViewer.js +103 -32
- package/dist/CmrComponents/niivue-viewer/NiivuePatcher.js +3 -7
- package/dist/CmrComponents/niivue-viewer/PenDraftOverlay.js +10 -3
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/DrawColorPlatte.d.ts +3 -4
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/DrawColorPlatte.js +70 -41
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/EraserPlatte.d.ts +2 -3
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/EraserPlatte.js +6 -14
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/MroDrawToolkit.js +82 -45
- package/dist/CmrComponents/niivue-viewer/penDraftUtils.d.ts +20 -4
- package/dist/CmrComponents/niivue-viewer/penDraftUtils.js +41 -6
- package/package.json +1 -1
|
@@ -25,8 +25,8 @@ export interface CloudMrNiivuePanelProps {
|
|
|
25
25
|
locationData: any[];
|
|
26
26
|
decimalPrecision: number;
|
|
27
27
|
drawToolkitProps: CloudMrDrawToolkitProps;
|
|
28
|
-
drawShapeTool: "pen" | "rectangle" | "ellipse" | null;
|
|
29
|
-
setDrawShapeTool: (t: "pen" | "rectangle" | "ellipse" | null) => void;
|
|
28
|
+
drawShapeTool: "pen" | "polyline" | "rectangle" | "ellipse" | null;
|
|
29
|
+
setDrawShapeTool: (t: "pen" | "polyline" | "rectangle" | "ellipse" | null) => void;
|
|
30
30
|
resampleImage: () => void;
|
|
31
31
|
layerList: React.ComponentProps<any>[];
|
|
32
32
|
mins: number[];
|
|
@@ -109,24 +109,24 @@ export function CloudMrNiivuePanel(props) {
|
|
|
109
109
|
props.resampleImage();
|
|
110
110
|
}, [histogram]);
|
|
111
111
|
function applyDrawShapeTool(tool) {
|
|
112
|
-
var _a, _b, _c
|
|
112
|
+
var _a, _b, _c;
|
|
113
113
|
if (props.shapeDraft) {
|
|
114
|
-
// Apply (commit) the current draft rather than discarding it
|
|
115
114
|
(_a = props.onApplyShapeDraftKeepTool) === null || _a === void 0 ? void 0 : _a.call(props);
|
|
116
115
|
}
|
|
117
116
|
if (props.penDraft) {
|
|
118
117
|
(_b = props.onApplyPenDraftKeepTool) === null || _b === void 0 ? void 0 : _b.call(props);
|
|
119
118
|
}
|
|
120
|
-
if (tool !== "pen") {
|
|
121
|
-
(_d = (_c = props.drawToolkitProps).onPenDrawModeChange) === null || _d === void 0 ? void 0 : _d.call(_c, "freehand");
|
|
122
|
-
}
|
|
123
119
|
props.setDrawShapeTool(tool);
|
|
124
120
|
var nv = props.nv;
|
|
125
|
-
var penMode = (_e = props.drawToolkitProps.penDrawMode) !== null && _e !== void 0 ? _e : "freehand";
|
|
126
121
|
nv.opts.deferShapeCommit = tool === "rectangle" || tool === "ellipse";
|
|
127
122
|
if (tool === "pen") {
|
|
128
|
-
nv.opts.polylinePenMode =
|
|
129
|
-
nv.opts.isFilledPen =
|
|
123
|
+
nv.opts.polylinePenMode = false;
|
|
124
|
+
nv.opts.isFilledPen = true;
|
|
125
|
+
nv.opts.deferFreehandCommit = false;
|
|
126
|
+
}
|
|
127
|
+
else if (tool === "polyline") {
|
|
128
|
+
nv.opts.polylinePenMode = true;
|
|
129
|
+
nv.opts.isFilledPen = false;
|
|
130
130
|
nv.opts.deferFreehandCommit = false;
|
|
131
131
|
}
|
|
132
132
|
else {
|
|
@@ -143,13 +143,14 @@ export function CloudMrNiivuePanel(props) {
|
|
|
143
143
|
nv.opts.penBounds = 0;
|
|
144
144
|
nv.opts.penSize = 1;
|
|
145
145
|
}
|
|
146
|
-
else if (tool === "pen") {
|
|
147
|
-
var penBrush = (
|
|
146
|
+
else if (tool === "pen" || tool === "polyline") {
|
|
147
|
+
var penBrush = (_c = props.drawToolkitProps.brushSize) !== null && _c !== void 0 ? _c : 1;
|
|
148
148
|
nv.opts.penBounds = (penBrush - 1) / 2;
|
|
149
149
|
nv.opts.penSize = penBrush;
|
|
150
150
|
}
|
|
151
151
|
nv.drawScene();
|
|
152
|
-
if ((tool === "rectangle" || tool === "ellipse"
|
|
152
|
+
if ((tool === "rectangle" || tool === "ellipse" || tool === "pen" || tool === "polyline") &&
|
|
153
|
+
!props.drawToolkitProps.drawingEnabled) {
|
|
153
154
|
props.drawToolkitProps.setDrawingEnabled(true);
|
|
154
155
|
}
|
|
155
156
|
}
|
|
@@ -63,8 +63,8 @@ import { SettingsPanel } from './SettingsPanel';
|
|
|
63
63
|
import { NumberPicker } from './NumberPicker';
|
|
64
64
|
import { ColorPicker } from './ColorPicker';
|
|
65
65
|
import { LayersPanel } from './LayersPanel';
|
|
66
|
-
import { applyPenDraft, cancelPenDraft, captureFreehandDraft, fillPolylineDraft, unfillPolylineDraft, polylineDraftFromNv, syncPolylineDraftToNv, registerAppliedPolyline,
|
|
67
|
-
import { registerAppliedShape, updateShapeRegistryErosionState, removeShapeRegistryEntry, clearShapeRegistry, } from './shapeDraftUtils';
|
|
66
|
+
import { applyPenDraft, cancelPenDraft, captureFreehandDraft, fillPolylineDraft, unfillPolylineDraft, polylineDraftFromNv, redrawPolylineDraft, redrawFreehandDraft, syncPolylineDraftToNv, registerAppliedPolyline, collectPolylineAppliedVoxelIndices, } from './penDraftUtils';
|
|
67
|
+
import { registerAppliedShape, updateShapeRegistryErosionState, removeShapeRegistryEntry, clearShapeRegistry, redrawDraftShape, } from './shapeDraftUtils';
|
|
68
68
|
import { CloudMrNiivuePanel } from './CloudMrNiivuePanel';
|
|
69
69
|
import { Niivue } from './NiivuePatcher';
|
|
70
70
|
import NVSwitch from './Switch';
|
|
@@ -577,22 +577,23 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
577
577
|
function nvUpdateBrushSize(size) {
|
|
578
578
|
setBrushSize(size);
|
|
579
579
|
brushSizeRef.current = size;
|
|
580
|
-
|
|
580
|
+
var tool = drawShapeToolRef.current;
|
|
581
|
+
if ((tool === "pen" || tool === "polyline") && drawPen !== 8) {
|
|
581
582
|
applyNvBrushSize(size);
|
|
582
583
|
}
|
|
583
584
|
}
|
|
584
585
|
function nvUpdateEraserSize(size) {
|
|
585
586
|
setEraserSize(size);
|
|
586
587
|
eraserSizeRef.current = size;
|
|
587
|
-
if (drawPen ===
|
|
588
|
+
if (drawPen === 8) {
|
|
588
589
|
applyNvBrushSize(size);
|
|
589
590
|
}
|
|
590
591
|
}
|
|
591
592
|
function nvUpdateDrawPen(a) {
|
|
592
|
-
var _a;
|
|
593
|
+
var _a, _b, _c;
|
|
593
594
|
var raw = Number(a.target.value);
|
|
594
595
|
setDrawPen(raw);
|
|
595
|
-
var isEraser = raw ===
|
|
596
|
+
var isEraser = raw === 8;
|
|
596
597
|
if (raw === 8) {
|
|
597
598
|
nv.setPenValue(0, true);
|
|
598
599
|
}
|
|
@@ -609,10 +610,53 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
609
610
|
}
|
|
610
611
|
else if (drawShapeToolRef.current === "pen") {
|
|
611
612
|
nv.opts.deferFreehandCommit = false;
|
|
612
|
-
nv.opts.polylinePenMode =
|
|
613
|
-
nv.opts.isFilledPen =
|
|
613
|
+
nv.opts.polylinePenMode = false;
|
|
614
|
+
nv.opts.isFilledPen = true;
|
|
615
|
+
applyNvBrushSize(brushSizeRef.current);
|
|
616
|
+
}
|
|
617
|
+
else if (drawShapeToolRef.current === "polyline") {
|
|
618
|
+
nv.opts.deferFreehandCommit = false;
|
|
619
|
+
nv.opts.polylinePenMode = true;
|
|
620
|
+
nv.opts.isFilledPen = false;
|
|
614
621
|
applyNvBrushSize(brushSizeRef.current);
|
|
615
622
|
}
|
|
623
|
+
// Immediately repaint any active draft in the new color so the user sees the change.
|
|
624
|
+
if (!isEraser) {
|
|
625
|
+
var colorValue = raw & 7;
|
|
626
|
+
if (shapeDraftRef.current) {
|
|
627
|
+
var next = __assign(__assign({}, shapeDraftRef.current), { penValue: colorValue });
|
|
628
|
+
shapeDraftRef.current = next;
|
|
629
|
+
setShapeDraft(next);
|
|
630
|
+
redrawDraftShape(nv, next);
|
|
631
|
+
}
|
|
632
|
+
else if (penDraftRef.current) {
|
|
633
|
+
var next = __assign(__assign({}, penDraftRef.current), { penValue: colorValue });
|
|
634
|
+
penDraftRef.current = next;
|
|
635
|
+
setPenDraft(next);
|
|
636
|
+
if (next.kind === "polyline") {
|
|
637
|
+
var count = (_c = (_b = next.vertices) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0;
|
|
638
|
+
if (count >= 2) {
|
|
639
|
+
// Redraw segments in the new color, then update the base so the
|
|
640
|
+
// rubber-band preview also uses the new color.
|
|
641
|
+
redrawPolylineDraft(nv, __assign(__assign({}, next), { filled: false }));
|
|
642
|
+
if (nv.drawBitmap)
|
|
643
|
+
nv._cloudMrPolylineBaseBitmap = nv.drawBitmap.slice();
|
|
644
|
+
// Re-apply fill on top when polygon is closeable (base stays segments-only).
|
|
645
|
+
if (count >= 3) {
|
|
646
|
+
nv.drawPenAxCorSag = next.axCorSag;
|
|
647
|
+
nv.drawPenFillPts = next.vertices.map(function (v) { return __spreadArray([], v, true); });
|
|
648
|
+
nv._cloudMrSkipNextUndoBitmap = true;
|
|
649
|
+
nv.drawPenFilled();
|
|
650
|
+
nv.refreshDrawing(false, false);
|
|
651
|
+
nv.drawScene();
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
else if (next.kind === "freehand") {
|
|
656
|
+
redrawFreehandDraft(nv, next);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
616
660
|
}
|
|
617
661
|
function deactivateDrawTools() {
|
|
618
662
|
var _a, _b;
|
|
@@ -866,40 +910,58 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
866
910
|
nv.opts.deferFreehandCommit = false;
|
|
867
911
|
}
|
|
868
912
|
function cancelPenDraftHandler() {
|
|
869
|
-
var _a;
|
|
913
|
+
var _a, _b;
|
|
870
914
|
var draft = penDraftRef.current;
|
|
871
915
|
if (!draft)
|
|
872
916
|
return;
|
|
873
917
|
var registryId = draft._registryId;
|
|
874
918
|
cancelPenDraft(nv, draft);
|
|
875
|
-
if (registryId
|
|
876
|
-
|
|
919
|
+
if (registryId) {
|
|
920
|
+
// Restore the exact committed voxels from the registry (works for both the
|
|
921
|
+
// legacy polyline-draft case and the new freehand-converted case).
|
|
922
|
+
var entry_1 = (_a = nv._cloudMrPolylineRegistry) === null || _a === void 0 ? void 0 : _a.find(function (e) { return e.id === registryId; });
|
|
923
|
+
if (entry_1) {
|
|
924
|
+
var bitmap_1 = nv.drawBitmap;
|
|
925
|
+
entry_1.voxelIndices.forEach(function (idx) {
|
|
926
|
+
if (idx < bitmap_1.length)
|
|
927
|
+
bitmap_1[idx] = entry_1.penValue;
|
|
928
|
+
});
|
|
929
|
+
nv.refreshDrawing(true, false);
|
|
930
|
+
nv.drawScene();
|
|
931
|
+
}
|
|
877
932
|
}
|
|
878
933
|
if (draft.kind === "polyline") {
|
|
879
|
-
(
|
|
934
|
+
(_b = nv.cloudMrResetPolyline) === null || _b === void 0 ? void 0 : _b.call(nv);
|
|
880
935
|
setPolylineVertexCount(0);
|
|
881
936
|
}
|
|
882
937
|
setPenDraft(null);
|
|
883
938
|
penDraftRef.current = null;
|
|
884
939
|
nv._cloudMrPenDraftActive = false;
|
|
885
|
-
|
|
940
|
+
var tool = drawShapeToolRef.current;
|
|
941
|
+
if (tool === "pen" || tool === "polyline") {
|
|
886
942
|
nvSetDrawingEnabled(true);
|
|
887
943
|
}
|
|
888
944
|
}
|
|
889
945
|
function applyPenDraftHandler(_a) {
|
|
890
|
-
var _b, _c, _d;
|
|
891
|
-
var
|
|
946
|
+
var _b, _c, _d, _e, _f;
|
|
947
|
+
var _g = _a === void 0 ? {} : _a, _h = _g.keepTool, keepTool = _h === void 0 ? false : _h;
|
|
892
948
|
var draft = penDraftRef.current;
|
|
893
949
|
if (!draft)
|
|
894
950
|
return;
|
|
895
951
|
if (draft.kind === "polyline" && ((_b = nv._cloudMrPolylineVertices) === null || _b === void 0 ? void 0 : _b.length) >= 2) {
|
|
896
|
-
var fresh = polylineDraftFromNv(nv, { filled:
|
|
952
|
+
var fresh = polylineDraftFromNv(nv, { filled: ((_c = draft.vertices) === null || _c === void 0 ? void 0 : _c.length) >= 3 });
|
|
897
953
|
if (fresh) {
|
|
898
954
|
draft = fresh;
|
|
899
955
|
penDraftRef.current = draft;
|
|
900
956
|
}
|
|
901
957
|
}
|
|
902
|
-
|
|
958
|
+
// Auto-fill polyline on apply — fillPolylineDraft updates _cloudMrPolylineBaseBitmap
|
|
959
|
+
// to the filled state so applyPenDraft commits the correct filled bitmap.
|
|
960
|
+
if (draft.kind === "polyline" && ((_d = draft.vertices) === null || _d === void 0 ? void 0 : _d.length) >= 3) {
|
|
961
|
+
draft = fillPolylineDraft(nv, draft);
|
|
962
|
+
penDraftRef.current = draft;
|
|
963
|
+
}
|
|
964
|
+
draft = (_e = applyPenDraft(nv, draft)) !== null && _e !== void 0 ? _e : draft;
|
|
903
965
|
if (draft.kind === "polyline" && nv._cloudMrPolylineSessionStartBitmap) {
|
|
904
966
|
draft = __assign(__assign({}, draft), { baseBitmap: new Uint8Array(nv._cloudMrPolylineSessionStartBitmap) });
|
|
905
967
|
}
|
|
@@ -907,17 +969,22 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
907
969
|
markPenVoxelKind(draft, draft.kind === "polyline" ? 3 : 2);
|
|
908
970
|
if (draft.kind === "polyline") {
|
|
909
971
|
registerAppliedPolyline(nv, draft, draft._registryId);
|
|
910
|
-
(
|
|
972
|
+
(_f = nv.cloudMrResetPolyline) === null || _f === void 0 ? void 0 : _f.call(nv);
|
|
911
973
|
setPolylineVertexCount(0);
|
|
912
974
|
}
|
|
975
|
+
else if (draft._registryId && nv._cloudMrPolylineRegistry) {
|
|
976
|
+
// Was a committed polyline reopened as a freehand draft (move-only).
|
|
977
|
+
// Remove the stale registry entry so it is not matched on future clicks.
|
|
978
|
+
nv._cloudMrPolylineRegistry = nv._cloudMrPolylineRegistry.filter(function (e) { return e.id !== draft._registryId; });
|
|
979
|
+
}
|
|
913
980
|
setPenDraft(null);
|
|
914
981
|
penDraftRef.current = null;
|
|
915
982
|
nv._cloudMrPenDraftActive = false;
|
|
916
983
|
setDrawingChanged(true);
|
|
917
984
|
if (keepTool) {
|
|
918
|
-
var
|
|
919
|
-
nv.opts.polylinePenMode =
|
|
920
|
-
nv.opts.isFilledPen =
|
|
985
|
+
var tool = drawShapeToolRef.current;
|
|
986
|
+
nv.opts.polylinePenMode = tool === "polyline";
|
|
987
|
+
nv.opts.isFilledPen = tool === "pen";
|
|
921
988
|
nv.opts.deferFreehandCommit = false;
|
|
922
989
|
nvSetDrawingEnabled(true);
|
|
923
990
|
}
|
|
@@ -974,11 +1041,10 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
974
1041
|
setPenDraft(draft);
|
|
975
1042
|
penDraftRef.current = draft;
|
|
976
1043
|
nv._cloudMrPenDraftActive = true;
|
|
977
|
-
// Auto-select pen tool so the palette opens
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
penDrawModeRef.current = mode;
|
|
1044
|
+
// Auto-select the correct pen tool so the right palette opens
|
|
1045
|
+
var tool = draft.kind === "polyline" ? "polyline" : "pen";
|
|
1046
|
+
setDrawShapeTool(tool);
|
|
1047
|
+
penDrawModeRef.current = draft.kind === "polyline" ? "polyline" : "freehand";
|
|
982
1048
|
if (draft.kind === "polyline") {
|
|
983
1049
|
setPolylineVertexCount((_b = (_a = draft.vertices) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0);
|
|
984
1050
|
}
|
|
@@ -989,13 +1055,18 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
989
1055
|
nv.onPolylineChange = function (count) {
|
|
990
1056
|
var _a, _b;
|
|
991
1057
|
setPolylineVertexCount(count);
|
|
992
|
-
if (
|
|
993
|
-
var
|
|
994
|
-
var preserveFill = (prev === null || prev === void 0 ? void 0 : prev.kind) === "polyline" &&
|
|
995
|
-
prev.filled &&
|
|
996
|
-
((_a = prev.vertices) === null || _a === void 0 ? void 0 : _a.length) === count;
|
|
997
|
-
var draft = polylineDraftFromNv(nv, { filled: preserveFill });
|
|
1058
|
+
if (drawShapeToolRef.current === "polyline" && count >= 2) {
|
|
1059
|
+
var draft = polylineDraftFromNv(nv, { filled: count >= 3 });
|
|
998
1060
|
if (draft) {
|
|
1061
|
+
if (count >= 3) {
|
|
1062
|
+
// Show fill live on canvas after each vertex commit.
|
|
1063
|
+
// Save/restore _cloudMrPolylineBaseBitmap (segments-only) so the
|
|
1064
|
+
// rubber-band preview still works correctly on mouse move.
|
|
1065
|
+
var savedBase = (_a = nv._cloudMrPolylineBaseBitmap) === null || _a === void 0 ? void 0 : _a.slice();
|
|
1066
|
+
redrawPolylineDraft(nv, draft);
|
|
1067
|
+
if (savedBase)
|
|
1068
|
+
nv._cloudMrPolylineBaseBitmap = savedBase;
|
|
1069
|
+
}
|
|
999
1070
|
setPenDraft(draft);
|
|
1000
1071
|
penDraftRef.current = draft;
|
|
1001
1072
|
}
|
|
@@ -30,7 +30,6 @@ import {
|
|
|
30
30
|
redrawFreehandDraft,
|
|
31
31
|
redrawPolylineDraft,
|
|
32
32
|
shouldDeferFreehandCommit,
|
|
33
|
-
syncPolylineDraftToNv,
|
|
34
33
|
} from "./penDraftUtils.js";
|
|
35
34
|
|
|
36
35
|
/*
|
|
@@ -1587,12 +1586,9 @@ function cloudMrTryReopenPenDraftOnClick(nv) {
|
|
|
1587
1586
|
if (kind !== 3) penKind = 2;
|
|
1588
1587
|
}
|
|
1589
1588
|
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
} else {
|
|
1594
|
-
redrawFreehandDraft(nv, draft);
|
|
1595
|
-
}
|
|
1589
|
+
// capturePolylineDraftFromClick always returns a freehand draft now (exact stored
|
|
1590
|
+
// voxels, no vertex state, no re-flood-fill), so the redraw path is uniform.
|
|
1591
|
+
redrawFreehandDraft(nv, draft);
|
|
1596
1592
|
nv._cloudMrSuppressDrawingChangedMouseUp = true;
|
|
1597
1593
|
if (typeof nv.onPenDraftReopenReady === "function") {
|
|
1598
1594
|
nv.onPenDraftReopenReady(draft, penKind);
|
|
@@ -24,6 +24,8 @@ import { boundsToFreehandCorners, redrawFreehandDraft, redrawPolylineDraft, sync
|
|
|
24
24
|
import { canvasDeltaToVoxDelta, clientToCanvasPos, voxToOverlayPos, voxUnderClient, } from "./shapeDraftUtils";
|
|
25
25
|
import { useNiivueViewerTheme } from "./NiivueViewerThemeContext";
|
|
26
26
|
var HANDLE_SIZE = 10;
|
|
27
|
+
/** Extra space (px) added around the polyline bounding box so it's easy to see and click. */
|
|
28
|
+
var POLYLINE_BOX_PADDING = 14;
|
|
27
29
|
function cloneFreehandDraft(draft) {
|
|
28
30
|
return __assign(__assign({}, draft), { strokeVoxels: draft.strokeVoxels.map(function (v) { return __spreadArray([], v, true); }), bounds: draft.bounds ? __assign({}, draft.bounds) : draft.bounds });
|
|
29
31
|
}
|
|
@@ -100,7 +102,13 @@ export function PenDraftOverlay(_a) {
|
|
|
100
102
|
var height = Math.max.apply(Math, ys) - top;
|
|
101
103
|
if (width < 2 && height < 2)
|
|
102
104
|
return null;
|
|
103
|
-
|
|
105
|
+
var pad = isPolyline ? POLYLINE_BOX_PADDING : 0;
|
|
106
|
+
return {
|
|
107
|
+
left: left - pad,
|
|
108
|
+
top: top - pad,
|
|
109
|
+
width: Math.max(width, 2) + pad * 2,
|
|
110
|
+
height: Math.max(height, 2) + pad * 2
|
|
111
|
+
};
|
|
104
112
|
}, [cornerCss, draft === null || draft === void 0 ? void 0 : draft.axCorSag, freehandCorners, isPolyline, nv, overlayKey]);
|
|
105
113
|
var applyDraft = useCallback(function (nextDraft) {
|
|
106
114
|
if (nextDraft.kind === "polyline") {
|
|
@@ -211,7 +219,6 @@ export function PenDraftOverlay(_a) {
|
|
|
211
219
|
background: theme.accentMutedBgLight,
|
|
212
220
|
boxSizing: "border-box",
|
|
213
221
|
pointerEvents: "none"
|
|
214
|
-
} }), centerCss && (_jsx("div", { role: "presentation", onPointerDown: function (e) { return startDrag(e, "move"); }, style: __assign(__assign({}, handleStyle), { left: centerCss.x, top: centerCss.y, cursor: "move", borderRadius: 2, width: 12, height: 12, marginLeft: -6, marginTop: -6 }), title: "Move shape" }))
|
|
215
|
-
cornerCss.map(function (pos, i) { return (_jsx("div", { role: "presentation", onPointerDown: function (e) { return startDrag(e, "corner", i); }, style: __assign(__assign({}, handleStyle), { left: pos.x, top: pos.y, cursor: "grab" }), title: "Move vertex" }, "handle-".concat(i))); })] })));
|
|
222
|
+
} }), centerCss && (_jsx("div", { role: "presentation", onPointerDown: function (e) { return startDrag(e, "move"); }, style: __assign(__assign({}, handleStyle), { left: centerCss.x, top: centerCss.y, cursor: "move", borderRadius: 2, width: 12, height: 12, marginLeft: -6, marginTop: -6 }), title: "Move shape" }))] })));
|
|
216
223
|
}
|
|
217
224
|
export default PenDraftOverlay;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
export default function DrawColorPlatte({ expanded, updateDrawPen, setDrawingEnabled,
|
|
1
|
+
export default function DrawColorPlatte({ expanded, updateDrawPen, setDrawingEnabled, penToolKind, currentPenValue, polylineVertexCount, penDraftActive, penDraftKind, penDraftFilled, onApplyPenDraft, onDeletePenDraft, onFillPenDraft, brushSize, updateBrushSize, shapeDraftActive, onApplyShapeDraft, onDeleteShapeDraft, }: {
|
|
2
2
|
expanded: any;
|
|
3
3
|
updateDrawPen: any;
|
|
4
4
|
setDrawingEnabled: any;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
onPenDrawModeChange: any;
|
|
5
|
+
penToolKind?: null | undefined;
|
|
6
|
+
currentPenValue?: number | undefined;
|
|
8
7
|
polylineVertexCount?: number | undefined;
|
|
9
8
|
penDraftActive?: boolean | undefined;
|
|
10
9
|
penDraftKind: any;
|
|
@@ -11,7 +11,8 @@ var __assign = (this && this.__assign) || function () {
|
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
* Color palette for pen/shape ROI tools. Freehand and polyline each use their own
|
|
15
|
+
* toolbar button and pass `penToolKind` to show the matching options.
|
|
15
16
|
*/
|
|
16
17
|
import { Stack, IconButton, Button, Tooltip, Typography } from "@mui/material";
|
|
17
18
|
import FiberManualRecordIcon from "@mui/icons-material/FiberManualRecord";
|
|
@@ -19,25 +20,38 @@ import CheckIcon from "@mui/icons-material/Check";
|
|
|
19
20
|
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
|
|
20
21
|
import { BrushSizeSlider } from "./BrushSizeSlider";
|
|
21
22
|
var FILLED_COLORS = [
|
|
22
|
-
{ sx: { color: "red" } },
|
|
23
|
-
{ sx: { color: "green" } },
|
|
24
|
-
{ sx: { color: "blue" } },
|
|
25
|
-
{ sx: { color: "yellow" } },
|
|
26
|
-
{ sx: { color: "cyan" } },
|
|
27
|
-
{ sx: { color: "#e81ce8" } },
|
|
23
|
+
{ sx: { color: "red" }, glow: "rgba(255, 72, 72, 0.55)" },
|
|
24
|
+
{ sx: { color: "green" }, glow: "rgba(72, 200, 72, 0.55)" },
|
|
25
|
+
{ sx: { color: "blue" }, glow: "rgba(72, 140, 255, 0.55)" },
|
|
26
|
+
{ sx: { color: "yellow" }, glow: "rgba(255, 220, 72, 0.5)" },
|
|
27
|
+
{ sx: { color: "cyan" }, glow: "rgba(72, 220, 255, 0.55)" },
|
|
28
|
+
{ sx: { color: "#e81ce8" }, glow: "rgba(232, 28, 232, 0.5)" },
|
|
28
29
|
];
|
|
30
|
+
/** Soft halo that hugs the circular icon shape (drop-shadow, not box-shadow). */
|
|
31
|
+
function swatchGlowFilter(glow, _a) {
|
|
32
|
+
var _b = _a === void 0 ? {} : _a, _c = _b.active, active = _c === void 0 ? false : _c, _d = _b.hover, hover = _d === void 0 ? false : _d;
|
|
33
|
+
if (active) {
|
|
34
|
+
return "drop-shadow(0 0 1px ".concat(glow, ") drop-shadow(0 0 4px ").concat(glow, ") drop-shadow(0 0 8px rgba(255, 255, 255, 0.12))");
|
|
35
|
+
}
|
|
36
|
+
if (hover) {
|
|
37
|
+
return "drop-shadow(0 0 2px rgba(255, 255, 255, 0.28))";
|
|
38
|
+
}
|
|
39
|
+
return "none";
|
|
40
|
+
}
|
|
29
41
|
var ACTION_FONT_SIZE = "0.75rem";
|
|
30
42
|
var ACTION_ICON_SIZE = "0.875rem";
|
|
31
|
-
var modeBtnSx = function (active) { return ({
|
|
32
|
-
color: active ? "#c9a0e8" : "#bbb",
|
|
33
|
-
fontSize: ACTION_FONT_SIZE,
|
|
34
|
-
textTransform: "none",
|
|
35
|
-
minWidth: 0,
|
|
36
|
-
py: 0.25,
|
|
37
|
-
px: 0.75
|
|
38
|
-
}); };
|
|
39
43
|
export default function DrawColorPlatte(_a) {
|
|
40
|
-
var expanded = _a.expanded, updateDrawPen = _a.updateDrawPen, setDrawingEnabled = _a.setDrawingEnabled,
|
|
44
|
+
var expanded = _a.expanded, updateDrawPen = _a.updateDrawPen, setDrawingEnabled = _a.setDrawingEnabled,
|
|
45
|
+
/** @type {"freehand" | "polyline" | null} */
|
|
46
|
+
_b = _a.penToolKind,
|
|
47
|
+
/** @type {"freehand" | "polyline" | null} */
|
|
48
|
+
penToolKind = _b === void 0 ? null : _b,
|
|
49
|
+
/** Currently active pen value (1–6); used to highlight the selected swatch. */
|
|
50
|
+
_c = _a.currentPenValue,
|
|
51
|
+
/** Currently active pen value (1–6); used to highlight the selected swatch. */
|
|
52
|
+
currentPenValue = _c === void 0 ? 1 : _c, _d = _a.polylineVertexCount, polylineVertexCount = _d === void 0 ? 0 : _d, _e = _a.penDraftActive, penDraftActive = _e === void 0 ? false : _e, penDraftKind = _a.penDraftKind, _f = _a.penDraftFilled, penDraftFilled = _f === void 0 ? false : _f, onApplyPenDraft = _a.onApplyPenDraft, onDeletePenDraft = _a.onDeletePenDraft, onFillPenDraft = _a.onFillPenDraft, _g = _a.brushSize, brushSize = _g === void 0 ? 1 : _g, updateBrushSize = _a.updateBrushSize, _h = _a.shapeDraftActive, shapeDraftActive = _h === void 0 ? false : _h, onApplyShapeDraft = _a.onApplyShapeDraft, onDeleteShapeDraft = _a.onDeleteShapeDraft;
|
|
53
|
+
var isFreehandTool = penToolKind === "freehand";
|
|
54
|
+
var isPolylineTool = penToolKind === "polyline";
|
|
41
55
|
return (_jsxs(Stack, __assign({ style: {
|
|
42
56
|
position: "absolute",
|
|
43
57
|
top: "100%",
|
|
@@ -51,13 +65,37 @@ export default function DrawColorPlatte(_a) {
|
|
|
51
65
|
borderTopLeftRadius: "6pt",
|
|
52
66
|
borderTopRightRadius: "6pt",
|
|
53
67
|
background: "#333"
|
|
54
|
-
}, direction: "column", spacing: 0.5, sx: { py: expanded ? 0.5 : 0 } }, { children: [
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
68
|
+
}, direction: "column", spacing: 0.5, sx: { py: expanded ? 0.5 : 0 } }, { children: [(isFreehandTool || isPolylineTool) && expanded && updateBrushSize && (_jsx(BrushSizeSlider, { label: "Line thickness", brushSize: brushSize, updateBrushSize: updateBrushSize })), _jsx(Stack, __assign({ direction: "row", sx: { px: 0.25 } }, { children: FILLED_COLORS.map(function (color, index) {
|
|
69
|
+
var penValue = index + 1;
|
|
70
|
+
var isActive = penValue === (currentPenValue & 7);
|
|
71
|
+
return (_jsx(IconButton, __assign({ "aria-label": "Color ".concat(penValue), "aria-pressed": isActive, onClick: function () {
|
|
72
|
+
updateDrawPen({ target: { value: penValue } });
|
|
73
|
+
setDrawingEnabled(true);
|
|
74
|
+
}, sx: {
|
|
75
|
+
p: "6px",
|
|
76
|
+
borderRadius: "50%",
|
|
77
|
+
boxShadow: "none",
|
|
78
|
+
transition: "background-color 0.15s ease",
|
|
79
|
+
"&:hover": {
|
|
80
|
+
backgroundColor: "rgba(255, 255, 255, 0.06)"
|
|
81
|
+
},
|
|
82
|
+
"& .color-swatch": {
|
|
83
|
+
fontSize: "1.2rem",
|
|
84
|
+
display: "block",
|
|
85
|
+
transition: "filter 0.18s ease",
|
|
86
|
+
filter: swatchGlowFilter(color.glow, { active: isActive })
|
|
87
|
+
},
|
|
88
|
+
"&:hover .color-swatch": {
|
|
89
|
+
filter: swatchGlowFilter(color.glow, {
|
|
90
|
+
active: isActive,
|
|
91
|
+
hover: !isActive
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
} }, { children: _jsx(FiberManualRecordIcon, { className: "color-swatch", sx: color.sx }) }), index));
|
|
95
|
+
}) })), isPolylineTool && expanded && (_jsx(Typography, __assign({ sx: { px: 1, pb: 0.5, fontSize: "0.68rem", color: "#aaa", userSelect: "none" } }, { children: "Click to add vertices \u00B7 Right-click to fill" }))), penDraftActive &&
|
|
96
|
+
expanded &&
|
|
97
|
+
((isFreehandTool && penDraftKind === "freehand") ||
|
|
98
|
+
(isPolylineTool && penDraftKind === "polyline")) && (_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: "Delete this ROI drawing" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "delete pen draft", onClick: function () { return onDeletePenDraft === null || onDeletePenDraft === void 0 ? void 0 : onDeletePenDraft(); }, startIcon: _jsx(DeleteOutlineIcon, { sx: { fontSize: ACTION_ICON_SIZE } }), sx: {
|
|
61
99
|
color: "#f44336",
|
|
62
100
|
fontSize: ACTION_FONT_SIZE,
|
|
63
101
|
textTransform: "none",
|
|
@@ -65,24 +103,15 @@ export default function DrawColorPlatte(_a) {
|
|
|
65
103
|
py: 0.25,
|
|
66
104
|
px: 0.75,
|
|
67
105
|
"& .MuiButton-startIcon": { mr: 0.5, ml: 0 }
|
|
68
|
-
} }, { children: "Delete" })) })),
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
} }, { children: penDraftFilled ? "Undo Fill" : "Fill" })) }))), penDrawMode === "polyline" && (_jsx(Tooltip, __assign({ title: "Apply polyline (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: {
|
|
78
|
-
color: "#c9a0e8",
|
|
79
|
-
fontSize: ACTION_FONT_SIZE,
|
|
80
|
-
textTransform: "none",
|
|
81
|
-
minWidth: 0,
|
|
82
|
-
py: 0.25,
|
|
83
|
-
px: 0.75,
|
|
84
|
-
"& .MuiButton-startIcon": { mr: 0.5, ml: 0 }
|
|
85
|
-
} }, { children: "Apply" })) })))] }))] }))), shapeDraftActive && expanded && (_jsx(Stack, __assign({ direction: "row", alignItems: "center", justifyContent: "flex-start", sx: { px: 1, py: 0.5, borderTop: "1px solid #555", width: "100%" } }, { children: _jsx(Tooltip, __assign({ title: "Delete this ROI drawing" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "delete shape draft", onClick: function () { return onDeleteShapeDraft === null || onDeleteShapeDraft === void 0 ? void 0 : onDeleteShapeDraft(); }, startIcon: _jsx(DeleteOutlineIcon, { sx: { fontSize: ACTION_ICON_SIZE } }), sx: {
|
|
106
|
+
} }, { children: "Delete" })) })), isPolylineTool && (_jsx(Tooltip, __assign({ title: "Apply polyline (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: {
|
|
107
|
+
color: "#c9a0e8",
|
|
108
|
+
fontSize: ACTION_FONT_SIZE,
|
|
109
|
+
textTransform: "none",
|
|
110
|
+
minWidth: 0,
|
|
111
|
+
py: 0.25,
|
|
112
|
+
px: 0.75,
|
|
113
|
+
"& .MuiButton-startIcon": { mr: 0.5, ml: 0 }
|
|
114
|
+
} }, { children: "Apply" })) })))] }))), shapeDraftActive && expanded && (_jsx(Stack, __assign({ direction: "row", alignItems: "center", justifyContent: "flex-start", sx: { px: 1, py: 0.5, borderTop: "1px solid #555", width: "100%" } }, { children: _jsx(Tooltip, __assign({ title: "Delete this ROI drawing" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "delete shape draft", onClick: function () { return onDeleteShapeDraft === null || onDeleteShapeDraft === void 0 ? void 0 : onDeleteShapeDraft(); }, startIcon: _jsx(DeleteOutlineIcon, { sx: { fontSize: ACTION_ICON_SIZE } }), sx: {
|
|
86
115
|
color: "#f44336",
|
|
87
116
|
fontSize: ACTION_FONT_SIZE,
|
|
88
117
|
textTransform: "none",
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
/** Eraser palette — size control only (filled/region eraser is always used). */
|
|
2
|
+
export default function EraserPlatte({ expandEraseOptions, eraserSize, updateEraserSize, }: {
|
|
2
3
|
expandEraseOptions: any;
|
|
3
|
-
updateDrawPen: any;
|
|
4
|
-
setDrawingEnabled: any;
|
|
5
4
|
eraserSize?: number | undefined;
|
|
6
5
|
updateEraserSize: any;
|
|
7
6
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -9,18 +9,13 @@ var __assign = (this && this.__assign) || function () {
|
|
|
9
9
|
};
|
|
10
10
|
return __assign.apply(this, arguments);
|
|
11
11
|
};
|
|
12
|
-
import { jsx as _jsx
|
|
13
|
-
import { Stack
|
|
14
|
-
import FiberManualRecordIcon from "@mui/icons-material/FiberManualRecord";
|
|
15
|
-
import FiberManualRecordOutlinedIcon from "@mui/icons-material/FiberManualRecordOutlined";
|
|
12
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
+
import { Stack } from "@mui/material";
|
|
16
14
|
import { BrushSizeSlider } from "./BrushSizeSlider";
|
|
15
|
+
/** Eraser palette — size control only (filled/region eraser is always used). */
|
|
17
16
|
export default function EraserPlatte(_a) {
|
|
18
|
-
var expandEraseOptions = _a.expandEraseOptions,
|
|
19
|
-
|
|
20
|
-
_jsx(FiberManualRecordIcon, { style: { color: "white" } }, "e0"),
|
|
21
|
-
_jsx(FiberManualRecordOutlinedIcon, { style: { color: "white" } }, "e1"),
|
|
22
|
-
];
|
|
23
|
-
return (_jsxs(Stack, __assign({ style: {
|
|
17
|
+
var expandEraseOptions = _a.expandEraseOptions, _b = _a.eraserSize, eraserSize = _b === void 0 ? 1 : _b, updateEraserSize = _a.updateEraserSize;
|
|
18
|
+
return (_jsx(Stack, __assign({ style: {
|
|
24
19
|
position: "absolute",
|
|
25
20
|
top: "100%",
|
|
26
21
|
left: 0,
|
|
@@ -33,8 +28,5 @@ export default function EraserPlatte(_a) {
|
|
|
33
28
|
borderTopRightRadius: "6pt",
|
|
34
29
|
background: "#333",
|
|
35
30
|
width: 150
|
|
36
|
-
}, direction: "column" }, { children:
|
|
37
|
-
updateDrawPen({ target: { value: index === 0 ? 8 : 0 } });
|
|
38
|
-
setDrawingEnabled(true);
|
|
39
|
-
} }, { children: value }), index)); }) }))] })));
|
|
31
|
+
}, direction: "column" }, { children: updateEraserSize && (_jsx(BrushSizeSlider, { label: "Eraser size", brushSize: eraserSize, updateBrushSize: updateEraserSize })) })));
|
|
40
32
|
}
|
|
@@ -20,9 +20,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
20
20
|
import React, { useState, useEffect } from "react";
|
|
21
21
|
import { Box, Card, CardContent, IconButton, Slider, Stack, Tooltip, Typography, } from "@mui/material";
|
|
22
22
|
import DrawIcon from "@mui/icons-material/Draw";
|
|
23
|
+
import PolylineIcon from "@mui/icons-material/Polyline";
|
|
23
24
|
import CropSquareOutlinedIcon from "@mui/icons-material/CropSquareOutlined";
|
|
24
25
|
import CircleOutlinedIcon from "@mui/icons-material/CircleOutlined";
|
|
25
|
-
import AutoFixNormalOutlinedIcon from "@mui/icons-material/AutoFixNormalOutlined";
|
|
26
26
|
import ReplyIcon from "@mui/icons-material/Reply";
|
|
27
27
|
import CameraAltIcon from "@mui/icons-material/CameraAlt";
|
|
28
28
|
import SvgIcon from "@mui/material/SvgIcon";
|
|
@@ -42,6 +42,14 @@ function clickTargetIsNiivueCanvas(target) {
|
|
|
42
42
|
return false;
|
|
43
43
|
return !!target.closest("#niiCanvas");
|
|
44
44
|
}
|
|
45
|
+
/** Click-away scoped to one tool button + its dropdown palette. */
|
|
46
|
+
function ToolClickAway(_a) {
|
|
47
|
+
var active = _a.active, onClickAway = _a.onClickAway, sx = _a.sx, children = _a.children;
|
|
48
|
+
var box = _jsx(Box, __assign({ sx: sx }, { children: children }));
|
|
49
|
+
if (!active)
|
|
50
|
+
return box;
|
|
51
|
+
return _jsx(ClickAwayListener, __assign({ onClickAway: onClickAway }, { children: box }));
|
|
52
|
+
}
|
|
45
53
|
/** Same icon tone as typical MUI on-paper controls (matches slice panel body). */
|
|
46
54
|
var ICON_COLOR = "#212121";
|
|
47
55
|
function EraserIcon(props) {
|
|
@@ -71,11 +79,12 @@ export function MroDrawToolkit(props) {
|
|
|
71
79
|
var _b = useState("n"), expandedOption = _b[0], setExpandedOption = _b[1];
|
|
72
80
|
var _c = useState(false), expandOpacityOptions = _c[0], setExpandOpacityOptions = _c[1];
|
|
73
81
|
var _d = useState(undefined), setMaskColor = _d[1];
|
|
74
|
-
var filled = props.drawPen > 7;
|
|
75
82
|
useEffect(function () {
|
|
76
|
-
|
|
83
|
+
var eraserSelected = props.drawPen === 8;
|
|
84
|
+
// Close palette when tool is deactivated programmatically (e.g. after Apply).
|
|
85
|
+
// Keep eraser palette open when switching from shape/pen edit → eraser in one click.
|
|
77
86
|
if (drawShapeTool === null && !props.shapeDraftActive && !props.penDraftActive) {
|
|
78
|
-
setExpandedOption("n");
|
|
87
|
+
setExpandedOption(eraserSelected ? "e" : "n");
|
|
79
88
|
return;
|
|
80
89
|
}
|
|
81
90
|
if (!props.shapeDraftActive && !props.penDraftActive)
|
|
@@ -86,14 +95,20 @@ export function MroDrawToolkit(props) {
|
|
|
86
95
|
setExpandedOption("l");
|
|
87
96
|
else if (drawShapeTool === "pen")
|
|
88
97
|
setExpandedOption("d");
|
|
89
|
-
|
|
98
|
+
else if (drawShapeTool === "polyline")
|
|
99
|
+
setExpandedOption("p");
|
|
100
|
+
}, [props.shapeDraftActive, props.penDraftActive, drawShapeTool, props.drawPen]);
|
|
90
101
|
var shapeSelectedSx = function (shape) {
|
|
102
|
+
if (shape === "pen")
|
|
103
|
+
return (drawShapeTool === "pen") ? theme.selectedToolSx : {};
|
|
104
|
+
if (shape === "polyline")
|
|
105
|
+
return (drawShapeTool === "polyline") ? theme.selectedToolSx : {};
|
|
91
106
|
return drawShapeTool === shape ? theme.selectedToolSx : {};
|
|
92
107
|
};
|
|
93
108
|
var eraserActive = expandedOption === "e";
|
|
94
109
|
function leaveEraserIfActive() {
|
|
95
110
|
var _a;
|
|
96
|
-
if (!eraserActive && props.drawPen !==
|
|
111
|
+
if (!eraserActive && props.drawPen !== 8) {
|
|
97
112
|
return;
|
|
98
113
|
}
|
|
99
114
|
(_a = props.onDeactivateDrawTools) === null || _a === void 0 ? void 0 : _a.call(props);
|
|
@@ -112,6 +127,19 @@ export function MroDrawToolkit(props) {
|
|
|
112
127
|
props.setDrawingEnabled(true);
|
|
113
128
|
}
|
|
114
129
|
}
|
|
130
|
+
function clickPolyline() {
|
|
131
|
+
leaveEraserIfActive();
|
|
132
|
+
onDrawShapeToolChange === null || onDrawShapeToolChange === void 0 ? void 0 : onDrawShapeToolChange("polyline");
|
|
133
|
+
if (expandedOption === "p") {
|
|
134
|
+
setExpandedOption("n");
|
|
135
|
+
props.setDrawingEnabled(false);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
setExpandedOption("p");
|
|
139
|
+
setExpandOpacityOptions(false);
|
|
140
|
+
props.setDrawingEnabled(true);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
115
143
|
function clickRectangle() {
|
|
116
144
|
leaveEraserIfActive();
|
|
117
145
|
onDrawShapeToolChange === null || onDrawShapeToolChange === void 0 ? void 0 : onDrawShapeToolChange("rectangle");
|
|
@@ -165,43 +193,52 @@ export function MroDrawToolkit(props) {
|
|
|
165
193
|
"&:hover": { backgroundColor: "rgba(0,0,0,0.04)" },
|
|
166
194
|
"&.Mui-disabled": { color: "rgba(0,0,0,0.26)" }
|
|
167
195
|
};
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
196
|
+
function shouldIgnoreToolClickAway(event) {
|
|
197
|
+
if (clickTargetIsNiivueCanvas(event.target))
|
|
198
|
+
return true;
|
|
199
|
+
if (props.shapeDraftActive || props.penDraftActive)
|
|
200
|
+
return true;
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
function handleDrawToolClickAway(event) {
|
|
204
|
+
var _a;
|
|
205
|
+
if (shouldIgnoreToolClickAway(event))
|
|
206
|
+
return;
|
|
207
|
+
if (expandedOption === "n" || expandedOption === "m")
|
|
208
|
+
return;
|
|
209
|
+
setExpandedOption("n");
|
|
210
|
+
setExpandOpacityOptions(false);
|
|
211
|
+
(_a = props.onDeactivateDrawTools) === null || _a === void 0 ? void 0 : _a.call(props);
|
|
212
|
+
}
|
|
213
|
+
var toolGroupSx = { position: "relative", display: "inline-flex", alignItems: "center" };
|
|
214
|
+
return (_jsxs("div", __assign({ style: __assign({ width: "100%", position: "relative", zIndex: 1080 }, props.style) }, { children: [_jsx("div", __assign({ className: "title", style: { width: "100%" } }, { children: "ROI Tools" })), _jsx(Card, __assign({ variant: "outlined", sx: {
|
|
215
|
+
mb: 2,
|
|
216
|
+
borderTopLeftRadius: 0,
|
|
217
|
+
borderTopRightRadius: 0,
|
|
218
|
+
overflow: "visible"
|
|
219
|
+
} }, { children: _jsx(CardContent, __assign({ sx: {
|
|
220
|
+
overflow: "visible",
|
|
221
|
+
"&:last-child": { paddingBottom: 2 }
|
|
222
|
+
} }, { children: _jsx("div", __assign({ style: { display: "flex", flexDirection: "column", overflow: "visible" } }, { children: _jsxs("div", __assign({ style: {
|
|
223
|
+
display: "flex",
|
|
224
|
+
flexDirection: "row",
|
|
225
|
+
flexWrap: "wrap",
|
|
226
|
+
alignItems: "center",
|
|
227
|
+
gap: 4,
|
|
228
|
+
overflow: "visible"
|
|
229
|
+
} }, { children: [_jsxs(ToolClickAway, __assign({ active: expandedOption === "d", onClickAway: handleDrawToolClickAway, sx: __assign(__assign({}, toolGroupSx), { zIndex: expandedOption === "d" ? 1600 : "auto" }) }, { children: [_jsx(Tooltip, __assign({ title: "Freehand" }, { children: _jsx(IconButton, __assign({ "aria-label": "freehand", 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, penToolKind: "freehand", currentPenValue: props.drawPen, penDraftActive: props.penDraftActive, penDraftKind: props.penDraftKind, onDeletePenDraft: props.onDeletePenDraft, brushSize: props.brushSize, updateBrushSize: props.updateBrushSize })] })), _jsxs(ToolClickAway, __assign({ active: expandedOption === "p", onClickAway: handleDrawToolClickAway, sx: __assign(__assign({}, toolGroupSx), { zIndex: expandedOption === "p" ? 1600 : "auto" }) }, { children: [_jsx(Tooltip, __assign({ title: "Polyline" }, { children: _jsx(IconButton, __assign({ "aria-label": "polyline", size: "small", onClick: clickPolyline, sx: __assign(__assign({}, toolBtnSx), shapeSelectedSx("polyline")) }, { children: _jsx(PolylineIcon, { sx: { color: "inherit" } }) })) })), _jsx(DrawColorPlatte, { expanded: expandedOption === "p", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, penToolKind: "polyline", currentPenValue: props.drawPen, polylineVertexCount: props.polylineVertexCount, penDraftActive: props.penDraftActive, penDraftKind: props.penDraftKind, penDraftFilled: props.penDraftFilled, onApplyPenDraft: props.onApplyPenDraft, onDeletePenDraft: props.onDeletePenDraft, onFillPenDraft: props.onFillPenDraft, brushSize: props.brushSize, updateBrushSize: props.updateBrushSize })] })), _jsxs(ToolClickAway, __assign({ active: expandedOption === "r", onClickAway: handleDrawToolClickAway, sx: __assign(__assign({}, toolGroupSx), { zIndex: expandedOption === "r" ? 1600 : "auto" }) }, { 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, currentPenValue: props.drawPen, shapeDraftActive: props.shapeDraftActive && drawShapeTool === "rectangle", onApplyShapeDraft: props.onApplyShapeDraft, onDeleteShapeDraft: props.onDeleteShapeDraft })] })), _jsxs(ToolClickAway, __assign({ active: expandedOption === "l", onClickAway: handleDrawToolClickAway, sx: __assign(__assign({}, toolGroupSx), { zIndex: expandedOption === "l" ? 1600 : "auto" }) }, { 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, currentPenValue: props.drawPen, shapeDraftActive: props.shapeDraftActive && drawShapeTool === "ellipse", onApplyShapeDraft: props.onApplyShapeDraft, onDeleteShapeDraft: props.onDeleteShapeDraft })] })), _jsxs(ToolClickAway, __assign({ active: expandedOption === "e", onClickAway: handleDrawToolClickAway, sx: __assign(__assign({}, toolGroupSx), { zIndex: expandedOption === "e" ? 1600 : "auto" }) }, { children: [_jsx(Tooltip, __assign({ title: "Eraser" }, { children: _jsx(IconButton, __assign({ "aria-label": "erase", size: "small", onClick: clickEraser, sx: __assign(__assign({}, toolBtnSx), (eraserActive ? theme.selectedToolSx : {})) }, { children: _jsx(EraserIcon, {}) })) })), _jsx(EraserPlatte, { expandEraseOptions: expandedOption === "e", eraserSize: props.eraserSize, updateEraserSize: props.updateEraserSize })] })), _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 () {
|
|
230
|
+
var _a;
|
|
231
|
+
(_a = props.onClearDrawing) === null || _a === void 0 ? void 0 : _a.call(props);
|
|
232
|
+
setExpandedOption("n");
|
|
233
|
+
setExpandOpacityOptions(false);
|
|
234
|
+
}, sx: toolBtnSx }, { children: _jsx(DeleteIcon, { sx: { color: ICON_COLOR } }) })) })), _jsx(Tooltip, __assign({ title: "ROI visibility" }, { children: _jsx(IconButton, __assign({ "aria-label": "visible", size: "small", onClick: function () { return props.toggleROIVisible(); }, sx: toolBtnSx }, { children: props.roiVisible ? (_jsx(VisibilityIcon, { sx: { color: ICON_COLOR } })) : (_jsx(VisibilityOffIcon, { sx: { color: ICON_COLOR, opacity: 0.45 } })) })) })), _jsxs(Box, __assign({ sx: {
|
|
235
|
+
position: "relative",
|
|
236
|
+
zIndex: expandOpacityOptions ? 1600 : "auto",
|
|
237
|
+
display: "inline-flex",
|
|
238
|
+
alignItems: "center",
|
|
239
|
+
color: ICON_COLOR
|
|
240
|
+
} }, { children: [_jsx(Tooltip, __assign({ title: "ROI opacity" }, { children: _jsx(IconButton, __assign({ "aria-label": "opaque", size: "small", onClick: function () { return setExpandOpacityOptions(!expandOpacityOptions); }, sx: toolBtnSx }, { children: _jsx(OpacityIcon, { sx: { color: ICON_COLOR } }) })) })), _jsx(Typography, __assign({ component: "span", sx: { fontSize: "0.7rem", mr: 0.25, userSelect: "none", color: ICON_COLOR } }, { children: props.drawingOpacity.toFixed(2) })), _jsx(OpacityPlatte, { drawingOpacity: props.drawingOpacity, setDrawingOpacity: props.setDrawingOpacity, expanded: expandOpacityOptions })] })), _jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "m" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Mask by intensity range" }, { children: _jsx(IconButton, __assign({ "aria-label": "fill", size: "small", onClick: clickMask, sx: toolBtnSx }, { children: _jsx(FormatColorFillIcon, { sx: { color: ICON_COLOR } }) })) })), _jsx(MaskPlatte, { resampleImage: function () {
|
|
241
|
+
props.resampleImage();
|
|
242
|
+
props.setDrawingChanged(true);
|
|
243
|
+
}, expanded: expandedOption === "m", nv: props.nv, setMaskColor: setMaskColor, unfocus: function () { return setExpandedOption("n"); } })] }))] })) })) })) }))] })));
|
|
207
244
|
}
|
|
@@ -48,16 +48,32 @@ export function fillPolylineDraft(nv: any, draft: any): any;
|
|
|
48
48
|
export function unfillPolylineDraft(nv: any, draft: any): any;
|
|
49
49
|
export function applyPenDraft(nv: any, draft: any): any;
|
|
50
50
|
/**
|
|
51
|
-
* Reconstruct a
|
|
52
|
-
*
|
|
51
|
+
* Reconstruct a PenDraft from a registered polyline entry, returned as a
|
|
52
|
+
* **freehand** draft so it follows the same simple move-only edit flow.
|
|
53
|
+
*
|
|
54
|
+
* Using the exact stored voxel set instead of re-running drawPenFilled avoids
|
|
55
|
+
* two bugs:
|
|
56
|
+
* 1. Re-running the flood-fill on complex/self-intersecting shapes can produce
|
|
57
|
+
* a different pixel set (winding-rule differences, sub-voxel rounding).
|
|
58
|
+
* 2. syncPolylineDraftToNv was planting old vertices back into
|
|
59
|
+
* _cloudMrPolylineVertices; a React re-render could then re-enable
|
|
60
|
+
* polylinePenMode and the next canvas click would append to the stale
|
|
61
|
+
* vertices, visually "recreating" the old shape.
|
|
53
62
|
*/
|
|
54
63
|
export function capturePolylineDraftFromClick(nv: any): {
|
|
55
64
|
kind: string;
|
|
56
65
|
baseBitmap: Uint8Array;
|
|
57
66
|
axCorSag: any;
|
|
58
67
|
penValue: any;
|
|
59
|
-
|
|
60
|
-
|
|
68
|
+
strokeVoxels: any[];
|
|
69
|
+
bounds: {
|
|
70
|
+
x1: number;
|
|
71
|
+
y1: number;
|
|
72
|
+
z1: number;
|
|
73
|
+
x2: number;
|
|
74
|
+
y2: number;
|
|
75
|
+
z2: number;
|
|
76
|
+
};
|
|
61
77
|
_registryId: any;
|
|
62
78
|
} | null;
|
|
63
79
|
/**
|
|
@@ -420,22 +420,57 @@ export function applyPenDraft(nv, draft) {
|
|
|
420
420
|
return draft;
|
|
421
421
|
}
|
|
422
422
|
/**
|
|
423
|
-
* Reconstruct a
|
|
424
|
-
*
|
|
423
|
+
* Reconstruct a PenDraft from a registered polyline entry, returned as a
|
|
424
|
+
* **freehand** draft so it follows the same simple move-only edit flow.
|
|
425
|
+
*
|
|
426
|
+
* Using the exact stored voxel set instead of re-running drawPenFilled avoids
|
|
427
|
+
* two bugs:
|
|
428
|
+
* 1. Re-running the flood-fill on complex/self-intersecting shapes can produce
|
|
429
|
+
* a different pixel set (winding-rule differences, sub-voxel rounding).
|
|
430
|
+
* 2. syncPolylineDraftToNv was planting old vertices back into
|
|
431
|
+
* _cloudMrPolylineVertices; a React re-render could then re-enable
|
|
432
|
+
* polylinePenMode and the next canvas click would append to the stale
|
|
433
|
+
* vertices, visually "recreating" the old shape.
|
|
425
434
|
*/
|
|
426
435
|
export function capturePolylineDraftFromClick(nv) {
|
|
436
|
+
var _a;
|
|
427
437
|
var seedVox = voxFromMouse(nv);
|
|
428
438
|
var entry = findPolylineRegistryEntry(nv, seedVox);
|
|
429
|
-
if (!entry)
|
|
439
|
+
if (!entry || !((_a = nv.back) === null || _a === void 0 ? void 0 : _a.dims))
|
|
430
440
|
return null;
|
|
441
|
+
var dx = nv.back.dims[1];
|
|
442
|
+
var dy = nv.back.dims[2];
|
|
443
|
+
// Decode stored linear indices back to [x, y, z] triples.
|
|
444
|
+
var strokeVoxels = [];
|
|
445
|
+
var x1 = Infinity, y1 = Infinity, z1 = Infinity;
|
|
446
|
+
var x2 = -Infinity, y2 = -Infinity, z2 = -Infinity;
|
|
447
|
+
entry.voxelIndices.forEach(function (idx) {
|
|
448
|
+
var z = Math.floor(idx / (dx * dy));
|
|
449
|
+
var rem = idx - z * dx * dy;
|
|
450
|
+
var y = Math.floor(rem / dx);
|
|
451
|
+
var x = rem % dx;
|
|
452
|
+
strokeVoxels.push([x, y, z]);
|
|
453
|
+
if (x < x1)
|
|
454
|
+
x1 = x;
|
|
455
|
+
if (x > x2)
|
|
456
|
+
x2 = x;
|
|
457
|
+
if (y < y1)
|
|
458
|
+
y1 = y;
|
|
459
|
+
if (y > y2)
|
|
460
|
+
y2 = y;
|
|
461
|
+
if (z < z1)
|
|
462
|
+
z1 = z;
|
|
463
|
+
if (z > z2)
|
|
464
|
+
z2 = z;
|
|
465
|
+
});
|
|
431
466
|
var baseBitmap = eraseClusterFromBitmap(nv.drawBitmap, entry.voxelIndices);
|
|
432
467
|
return {
|
|
433
|
-
kind: "
|
|
468
|
+
kind: "freehand",
|
|
434
469
|
baseBitmap: baseBitmap,
|
|
435
470
|
axCorSag: entry.axCorSag,
|
|
436
471
|
penValue: entry.penValue,
|
|
437
|
-
|
|
438
|
-
|
|
472
|
+
strokeVoxels: strokeVoxels,
|
|
473
|
+
bounds: { x1: x1, y1: y1, z1: z1, x2: x2, y2: y2, z2: z2 },
|
|
439
474
|
_registryId: entry.id
|
|
440
475
|
};
|
|
441
476
|
}
|