cloudmr-ux 4.7.1 → 4.7.3

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.
@@ -63,7 +63,7 @@ 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, fillPolylineDraft, polylineDraftFromNv, syncPolylineDraftToNv, } from './penDraftUtils';
66
+ import { applyPenDraft, cancelPenDraft, fillPolylineDraft, fillFreehandDraft, polylineDraftFromNv, syncPolylineDraftToNv, } from './penDraftUtils';
67
67
  import { CloudMrNiivuePanel } from './CloudMrNiivuePanel';
68
68
  import { Niivue } from './NiivuePatcher';
69
69
  import NVSwitch from './Switch';
@@ -104,7 +104,7 @@ export var nv = new Niivue({
104
104
  crosshairColor: [1, 1, 0],
105
105
  fontColor: [0.00, 0.94, 0.37, 1],
106
106
  isNearestInterpolation: true,
107
- isFilledPen: true,
107
+ isFilledPen: false,
108
108
  penValue: 1,
109
109
  penType: NI_PEN_TYPE.PEN
110
110
  });
@@ -595,7 +595,7 @@ export default function CloudMrNiivueViewer(props) {
595
595
  else if (drawShapeToolRef.current === "pen") {
596
596
  nv.opts.deferFreehandCommit = penDrawModeRef.current === "freehand";
597
597
  nv.opts.polylinePenMode = penDrawModeRef.current === "polyline";
598
- nv.opts.isFilledPen = penDrawModeRef.current === "freehand";
598
+ nv.opts.isFilledPen = false;
599
599
  }
600
600
  }
601
601
  function deactivateDrawTools() {
@@ -825,7 +825,7 @@ export default function CloudMrNiivueViewer(props) {
825
825
  setPenDrawMode(mode);
826
826
  penDrawModeRef.current = mode;
827
827
  nv.opts.polylinePenMode = mode === "polyline";
828
- nv.opts.isFilledPen = mode === "freehand";
828
+ nv.opts.isFilledPen = false;
829
829
  nv.opts.deferFreehandCommit =
830
830
  drawShapeToolRef.current === "pen" && mode === "freehand";
831
831
  if (mode === "freehand") {
@@ -874,12 +874,20 @@ export default function CloudMrNiivueViewer(props) {
874
874
  }
875
875
  resampleImage();
876
876
  }
877
- function fillPolylineDraftHandler() {
877
+ function fillPenDraftHandler() {
878
878
  var draft = penDraftRef.current;
879
- if (!draft || draft.kind !== "polyline" || draft.vertices.length < 3)
879
+ if (!draft)
880
880
  return;
881
- var next = fillPolylineDraft(nv, draft);
882
- onPenDraftChange(next);
881
+ if (draft.kind === "polyline") {
882
+ if (draft.vertices.length < 3)
883
+ return;
884
+ onPenDraftChange(fillPolylineDraft(nv, draft));
885
+ }
886
+ else if (draft.kind === "freehand") {
887
+ if (!draft.pathVertices || draft.pathVertices.length < 3)
888
+ return;
889
+ onPenDraftChange(fillFreehandDraft(nv, draft));
890
+ }
883
891
  }
884
892
  function onPenDraftChange(draft) {
885
893
  setPenDraft(draft);
@@ -951,6 +959,7 @@ export default function CloudMrNiivueViewer(props) {
951
959
  nv._cloudMrPolylineVertices = null;
952
960
  nv._cloudMrPolylineBaseBitmap = null;
953
961
  nv._cloudMrPolylineSessionStartBitmap = null;
962
+ nv._cloudMrFreehandPath = null;
954
963
  nv._cloudMrToolKindBitmap = null;
955
964
  }
956
965
  function clearDrawingHandler() {
@@ -1619,7 +1628,7 @@ export default function CloudMrNiivueViewer(props) {
1619
1628
  onCancelPolyline: cancelPolylineDraft,
1620
1629
  onApplyPenDraft: applyPenDraftHandler,
1621
1630
  onCancelPenDraft: cancelPenDraftHandler,
1622
- onFillPenDraft: fillPolylineDraftHandler,
1631
+ onFillPenDraft: fillPenDraftHandler,
1623
1632
  penDraftActive: penDraft != null,
1624
1633
  brushSize: brushSize,
1625
1634
  updateBrushSize: nvUpdateBrushSize,
@@ -1560,6 +1560,7 @@ Niivue.prototype.mouseDownListener = function cloudMrMouseDownListener(e) {
1560
1560
  if (shouldDeferFreehandCommit(this) && this.drawBitmap) {
1561
1561
  this._cloudMrFreehandSessionStartBitmap = this.drawBitmap.slice();
1562
1562
  this._cloudMrFreehandAxCorSag = -1;
1563
+ this._cloudMrFreehandPath = [];
1563
1564
  }
1564
1565
  _mouseDownListener.call(this, e);
1565
1566
  if (shouldDeferFreehandCommit(this) && this._cloudMrFreehandSessionStartBitmap) {
@@ -1567,6 +1568,10 @@ Niivue.prototype.mouseDownListener = function cloudMrMouseDownListener(e) {
1567
1568
  if (axCorSag >= 0) {
1568
1569
  this._cloudMrFreehandAxCorSag = axCorSag;
1569
1570
  }
1571
+ const vox = voxFromMouse(this);
1572
+ if (vox && this._cloudMrFreehandPath) {
1573
+ this._cloudMrFreehandPath.push([vox[0], vox[1], vox[2]]);
1574
+ }
1570
1575
  }
1571
1576
  };
1572
1577
 
@@ -1593,6 +1598,19 @@ Niivue.prototype.mouseClick = function cloudMrMouseClick(...args) {
1593
1598
  const _mouseMoveListener = Niivue.prototype.mouseMoveListener;
1594
1599
  Niivue.prototype.mouseMoveListener = function cloudMrMouseMoveListener(event) {
1595
1600
  const result = _mouseMoveListener.call(this, event);
1601
+ if (shouldDeferFreehandCommit(this) && this.uiData?.isDragging) {
1602
+ const vox = voxFromMouse(this);
1603
+ if (vox) {
1604
+ if (!this._cloudMrFreehandPath) {
1605
+ this._cloudMrFreehandPath = [];
1606
+ }
1607
+ const path = this._cloudMrFreehandPath;
1608
+ const last = path[path.length - 1];
1609
+ if (!last || last[0] !== vox[0] || last[1] !== vox[1] || last[2] !== vox[2]) {
1610
+ path.push([vox[0], vox[1], vox[2]]);
1611
+ }
1612
+ }
1613
+ }
1596
1614
  if (isPolylinePenActive(this) && this._cloudMrPolylineVertices?.length > 0) {
1597
1615
  previewPolylineSegment(this);
1598
1616
  }
@@ -53,14 +53,15 @@ export default function DrawColorPlatte(_a) {
53
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 () {
54
54
  updateDrawPen({ target: { value: index + 1 } });
55
55
  setDrawingEnabled(true);
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: {
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) ||
57
+ (penDrawMode === "freehand" && penDraftActive) ? (_jsx(Tooltip, __assign({ title: "Fill interior (keeps outline editable until Apply)" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "fill pen draft", onClick: function () { return onFillPenDraft === null || onFillPenDraft === void 0 ? void 0 : onFillPenDraft(); }, sx: {
57
58
  color: "#c9a0e8",
58
59
  fontSize: ACTION_FONT_SIZE,
59
60
  textTransform: "none",
60
61
  minWidth: 0,
61
62
  py: 0.25,
62
63
  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: {
64
+ } }, { children: "Fill" })) }))) : null, _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: {
64
65
  color: "#c9a0e8",
65
66
  fontSize: ACTION_FONT_SIZE,
66
67
  textTransform: "none",
@@ -7,6 +7,7 @@
7
7
  * @property {number} penValue
8
8
  * @property {[number, number, number][]} [vertices]
9
9
  * @property {[number, number, number][]} [strokeVoxels]
10
+ * @property {[number, number, number][]} [pathVertices]
10
11
  * @property {{ x1: number, y1: number, x2: number, y2: number, z1: number, z2: number }} [bounds]
11
12
  * @property {boolean} [filled]
12
13
  */
@@ -23,6 +24,7 @@ export function captureFreehandDraft(nv: any, sessionStartBitmap: any, axCorSag:
23
24
  axCorSag: any;
24
25
  penValue: any;
25
26
  strokeVoxels: number[][];
27
+ pathVertices: any;
26
28
  bounds: {
27
29
  x1: number;
28
30
  y1: number;
@@ -46,6 +48,8 @@ export function polylineDraftFromNv(nv: any, { filled }?: {
46
48
  penValue: any;
47
49
  filled: boolean;
48
50
  } | null;
51
+ /** Fill freehand interior from the traced stroke path (outline stays editable until Apply). */
52
+ export function fillFreehandDraft(nv: any, draft: any): any;
49
53
  /** Fill polyline interior without closing the outline or committing the draft. */
50
54
  export function fillPolylineDraft(nv: any, draft: any): any;
51
55
  export function applyPenDraft(nv: any, draft: any): void;
@@ -70,6 +74,7 @@ export type PenDraft = {
70
74
  penValue: number;
71
75
  vertices?: [number, number, number][] | undefined;
72
76
  strokeVoxels?: [number, number, number][] | undefined;
77
+ pathVertices?: [number, number, number][] | undefined;
73
78
  bounds?: {
74
79
  x1: number;
75
80
  y1: number;
@@ -30,6 +30,7 @@ import { voxFromMouse } from "./polylinePenUtils";
30
30
  * @property {number} penValue
31
31
  * @property {[number, number, number][]} [vertices]
32
32
  * @property {[number, number, number][]} [strokeVoxels]
33
+ * @property {[number, number, number][]} [pathVertices]
33
34
  * @property {{ x1: number, y1: number, x2: number, y2: number, z1: number, z2: number }} [bounds]
34
35
  * @property {boolean} [filled]
35
36
  */
@@ -39,8 +40,7 @@ export function isEraserActive(nv) {
39
40
  nv.opts.penValue === 0);
40
41
  }
41
42
  export function isFreehandPenActive(nv) {
42
- return (nv.opts.isFilledPen &&
43
- nv.opts.drawingEnabled &&
43
+ return (nv.opts.drawingEnabled &&
44
44
  nv.opts.penType === NI_PEN_TYPE.PEN &&
45
45
  !nv.opts.polylinePenMode &&
46
46
  nv.opts.penValue > 0);
@@ -82,7 +82,7 @@ export function syncPolylineDraftToNv(nv, draft) {
82
82
  nv._cloudMrPolylineAxCorSag = draft.axCorSag;
83
83
  }
84
84
  export function captureFreehandDraft(nv, sessionStartBitmap, axCorSag) {
85
- var _a;
85
+ var _a, _b;
86
86
  if (!nv.drawBitmap || !sessionStartBitmap)
87
87
  return null;
88
88
  var penValue = nv.opts.penValue;
@@ -108,7 +108,7 @@ export function captureFreehandDraft(nv, sessionStartBitmap, axCorSag) {
108
108
  var y2 = -Infinity;
109
109
  var z2 = -Infinity;
110
110
  for (var _i = 0, strokeVoxels_1 = strokeVoxels; _i < strokeVoxels_1.length; _i++) {
111
- var _b = strokeVoxels_1[_i], x = _b[0], y = _b[1], z = _b[2];
111
+ var _c = strokeVoxels_1[_i], x = _c[0], y = _c[1], z = _c[2];
112
112
  x1 = Math.min(x1, x);
113
113
  y1 = Math.min(y1, y);
114
114
  z1 = Math.min(z1, z);
@@ -116,23 +116,86 @@ export function captureFreehandDraft(nv, sessionStartBitmap, axCorSag) {
116
116
  y2 = Math.max(y2, y);
117
117
  z2 = Math.max(z2, z);
118
118
  }
119
+ var pathVertices = ((_b = nv._cloudMrFreehandPath) === null || _b === void 0 ? void 0 : _b.length)
120
+ ? nv._cloudMrFreehandPath.map(function (v) { return __spreadArray([], v, true); })
121
+ : undefined;
122
+ nv._cloudMrFreehandPath = [];
119
123
  return {
120
124
  kind: "freehand",
121
125
  baseBitmap: new Uint8Array(sessionStartBitmap),
122
126
  axCorSag: axCorSag,
123
127
  penValue: penValue,
124
128
  strokeVoxels: strokeVoxels,
129
+ pathVertices: pathVertices,
125
130
  bounds: { x1: x1, y1: y1, z1: z1, x2: x2, y2: y2, z2: z2 }
126
131
  };
127
132
  }
128
- export function redrawFreehandDraft(nv, draft) {
133
+ function collectStrokeVoxelsFromBitmap(nv, draft) {
129
134
  var _a;
130
- if (!((_a = draft === null || draft === void 0 ? void 0 : draft.strokeVoxels) === null || _a === void 0 ? void 0 : _a.length) || !draft.baseBitmap)
135
+ var dims = (_a = nv.back) === null || _a === void 0 ? void 0 : _a.dims;
136
+ if (!dims || !nv.drawBitmap || !(draft === null || draft === void 0 ? void 0 : draft.baseBitmap))
137
+ return draft.strokeVoxels || [];
138
+ var strokeVoxels = [];
139
+ for (var i = 0; i < nv.drawBitmap.length; i++) {
140
+ if (nv.drawBitmap[i] === draft.penValue && draft.baseBitmap[i] !== draft.penValue) {
141
+ var z = Math.floor(i / (dims[1] * dims[2]));
142
+ var rem = i - z * dims[1] * dims[2];
143
+ var y = Math.floor(rem / dims[1]);
144
+ var x = rem % dims[1];
145
+ strokeVoxels.push([x, y, z]);
146
+ }
147
+ }
148
+ return strokeVoxels;
149
+ }
150
+ function boundsFromVoxels(voxels) {
151
+ var x1 = Infinity;
152
+ var y1 = Infinity;
153
+ var z1 = Infinity;
154
+ var x2 = -Infinity;
155
+ var y2 = -Infinity;
156
+ var z2 = -Infinity;
157
+ for (var _i = 0, voxels_1 = voxels; _i < voxels_1.length; _i++) {
158
+ var _a = voxels_1[_i], x = _a[0], y = _a[1], z = _a[2];
159
+ x1 = Math.min(x1, x);
160
+ y1 = Math.min(y1, y);
161
+ z1 = Math.min(z1, z);
162
+ x2 = Math.max(x2, x);
163
+ y2 = Math.max(y2, y);
164
+ z2 = Math.max(z2, z);
165
+ }
166
+ return { x1: x1, y1: y1, z1: z1, x2: x2, y2: y2, z2: z2 };
167
+ }
168
+ export function redrawFreehandDraft(nv, draft) {
169
+ var _a, _b, _c;
170
+ if (!(draft === null || draft === void 0 ? void 0 : draft.baseBitmap))
131
171
  return;
132
172
  nv.drawBitmap.set(draft.baseBitmap);
133
- for (var _i = 0, _b = draft.strokeVoxels; _i < _b.length; _i++) {
134
- var _c = _b[_i], x = _c[0], y = _c[1], z = _c[2];
135
- nv.drawPt(x, y, z, draft.penValue);
173
+ nv.drawPenAxCorSag = draft.axCorSag;
174
+ if (!draft.filled) {
175
+ if (!((_a = draft.strokeVoxels) === null || _a === void 0 ? void 0 : _a.length))
176
+ return;
177
+ for (var _i = 0, _d = draft.strokeVoxels; _i < _d.length; _i++) {
178
+ var _e = _d[_i], x = _e[0], y = _e[1], z = _e[2];
179
+ nv.drawPt(x, y, z, draft.penValue);
180
+ }
181
+ }
182
+ else if (((_b = draft.pathVertices) === null || _b === void 0 ? void 0 : _b.length) >= 3) {
183
+ for (var _f = 0, _g = draft.strokeVoxels || []; _f < _g.length; _f++) {
184
+ var _h = _g[_f], x = _h[0], y = _h[1], z = _h[2];
185
+ nv.drawPt(x, y, z, draft.penValue);
186
+ }
187
+ nv.drawPenFillPts = draft.pathVertices.map(function (v) { return __spreadArray([], v, true); });
188
+ nv._cloudMrSkipNextUndoBitmap = true;
189
+ nv.drawPenFilled();
190
+ }
191
+ else if ((_c = draft.strokeVoxels) === null || _c === void 0 ? void 0 : _c.length) {
192
+ for (var _j = 0, _k = draft.strokeVoxels; _j < _k.length; _j++) {
193
+ var _l = _k[_j], x = _l[0], y = _l[1], z = _l[2];
194
+ nv.drawPt(x, y, z, draft.penValue);
195
+ }
196
+ }
197
+ else {
198
+ return;
136
199
  }
137
200
  nv.refreshDrawing(false, false);
138
201
  nv.drawScene();
@@ -239,6 +302,21 @@ export function polylineDraftFromNv(nv, _a) {
239
302
  filled: filled
240
303
  };
241
304
  }
305
+ /** Fill freehand interior from the traced stroke path (outline stays editable until Apply). */
306
+ export function fillFreehandDraft(nv, draft) {
307
+ var path = draft.pathVertices;
308
+ if (!path || path.length < 3)
309
+ return draft;
310
+ redrawFreehandDraft(nv, __assign(__assign({}, draft), { filled: false }));
311
+ nv.drawPenAxCorSag = draft.axCorSag;
312
+ nv.drawPenFillPts = path.map(function (v) { return __spreadArray([], v, true); });
313
+ nv._cloudMrSkipNextUndoBitmap = true;
314
+ nv.drawPenFilled();
315
+ nv.refreshDrawing(false, false);
316
+ nv.drawScene();
317
+ var strokeVoxels = collectStrokeVoxelsFromBitmap(nv, draft);
318
+ return __assign(__assign({}, draft), { filled: true, strokeVoxels: strokeVoxels, bounds: boundsFromVoxels(strokeVoxels) });
319
+ }
242
320
  /** Fill polyline interior without closing the outline or committing the draft. */
243
321
  export function fillPolylineDraft(nv, draft) {
244
322
  if (!(draft === null || draft === void 0 ? void 0 : draft.vertices) || draft.vertices.length < 3)
@@ -285,6 +285,7 @@ var ALLOWED_EXTENSIONS = [
285
285
  "jpeg",
286
286
  "zip",
287
287
  "seq",
288
+ "mtrk",
288
289
  ];
289
290
  var createPayload = function (uploadToken, file, fileAlias) { return __awaiter(void 0, void 0, void 0, function () {
290
291
  var fileExtension, lowerExt, fileType, isSafe, lambdaFile, UploadHeaders, endpoints;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudmr-ux",
3
- "version": "4.7.1",
3
+ "version": "4.7.3",
4
4
  "author": "erosmontin@gmail.com",
5
5
  "license": "MIT",
6
6
  "repository": "erosmontin/cloudmr-ux",