cloudmr-ux 4.6.2 → 4.6.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.
@@ -832,13 +832,11 @@ export default function CloudMrNiivueViewer(props) {
832
832
  (_a = nv.cloudMrCancelPolyline) === null || _a === void 0 ? void 0 : _a.call(nv);
833
833
  }
834
834
  }
835
- function syncActiveDraftRefs() {
835
+ function syncActiveShapeDraftRef() {
836
836
  nv._cloudMrActiveShapeDraft = shapeDraftRef.current;
837
- nv._cloudMrActivePenDraft = penDraftRef.current;
838
837
  }
839
- function clearActiveDraftRefs() {
838
+ function clearActiveShapeDraftRef() {
840
839
  nv._cloudMrActiveShapeDraft = null;
841
- nv._cloudMrActivePenDraft = null;
842
840
  }
843
841
  function cancelPenDraftHandler() {
844
842
  var _a;
@@ -853,7 +851,6 @@ export default function CloudMrNiivueViewer(props) {
853
851
  setPenDraft(null);
854
852
  penDraftRef.current = null;
855
853
  nv._cloudMrPenDraftActive = false;
856
- clearActiveDraftRefs();
857
854
  if (drawShapeToolRef.current === "pen") {
858
855
  nvSetDrawingEnabled(true);
859
856
  }
@@ -871,7 +868,6 @@ export default function CloudMrNiivueViewer(props) {
871
868
  setPenDraft(null);
872
869
  penDraftRef.current = null;
873
870
  nv._cloudMrPenDraftActive = false;
874
- clearActiveDraftRefs();
875
871
  setDrawingChanged(true);
876
872
  if (drawShapeToolRef.current === "pen") {
877
873
  nvSetDrawingEnabled(true);
@@ -888,7 +884,6 @@ export default function CloudMrNiivueViewer(props) {
888
884
  function onPenDraftChange(draft) {
889
885
  setPenDraft(draft);
890
886
  penDraftRef.current = draft;
891
- syncActiveDraftRefs();
892
887
  if (draft.kind === "polyline") {
893
888
  syncPolylineDraftToNv(nv, draft);
894
889
  }
@@ -900,7 +895,6 @@ export default function CloudMrNiivueViewer(props) {
900
895
  setPenDraft(draft);
901
896
  penDraftRef.current = draft;
902
897
  nv._cloudMrPenDraftActive = true;
903
- syncActiveDraftRefs();
904
898
  nvSetDrawingEnabled(false);
905
899
  };
906
900
  nv.onPolylineChange = function (count) {
@@ -915,13 +909,11 @@ export default function CloudMrNiivueViewer(props) {
915
909
  if (draft) {
916
910
  setPenDraft(draft);
917
911
  penDraftRef.current = draft;
918
- syncActiveDraftRefs();
919
912
  }
920
913
  }
921
914
  else if (((_b = penDraftRef.current) === null || _b === void 0 ? void 0 : _b.kind) === "polyline") {
922
915
  setPenDraft(null);
923
916
  penDraftRef.current = null;
924
- syncActiveDraftRefs();
925
917
  }
926
918
  };
927
919
  function cancelShapeDraft() {
@@ -934,7 +926,7 @@ export default function CloudMrNiivueViewer(props) {
934
926
  setShapeDraft(null);
935
927
  shapeDraftRef.current = null;
936
928
  nv._cloudMrShapeDraftActive = false;
937
- clearActiveDraftRefs();
929
+ clearActiveShapeDraftRef();
938
930
  if (drawShapeToolRef.current) {
939
931
  nvSetDrawingEnabled(true);
940
932
  }
@@ -947,7 +939,7 @@ export default function CloudMrNiivueViewer(props) {
947
939
  setShapeDraft(null);
948
940
  shapeDraftRef.current = null;
949
941
  nv._cloudMrShapeDraftActive = false;
950
- clearActiveDraftRefs();
942
+ clearActiveShapeDraftRef();
951
943
  if (drawShapeToolRef.current) {
952
944
  nvSetDrawingEnabled(true);
953
945
  }
@@ -956,13 +948,13 @@ export default function CloudMrNiivueViewer(props) {
956
948
  function onShapeDraftChange(draft) {
957
949
  setShapeDraft(draft);
958
950
  shapeDraftRef.current = draft;
959
- syncActiveDraftRefs();
951
+ syncActiveShapeDraftRef();
960
952
  }
961
953
  nv.onShapeDraftReady = function (draft) {
962
954
  setShapeDraft(draft);
963
955
  shapeDraftRef.current = draft;
964
956
  nv._cloudMrShapeDraftActive = true;
965
- syncActiveDraftRefs();
957
+ syncActiveShapeDraftRef();
966
958
  nvSetDrawingEnabled(false);
967
959
  };
968
960
  nv.onApplyActiveDraft = function () {
@@ -23,9 +23,6 @@ import {
23
23
  } from "./polylinePenUtils.js";
24
24
  import {
25
25
  captureFreehandDraft,
26
- capturePenDraftFromClick,
27
- isPenDrawToolActive,
28
- redrawFreehandDraft,
29
26
  shouldDeferFreehandCommit,
30
27
  } from "./penDraftUtils.js";
31
28
 
@@ -1476,89 +1473,83 @@ function cloudMrHasApplyableDraft(nv) {
1476
1473
  return !!(nv._cloudMrShapeDraftActive || nv._cloudMrPenDraftActive);
1477
1474
  }
1478
1475
 
1479
- function getActiveDraft(nv) {
1480
- if (nv._cloudMrPenDraftActive && nv._cloudMrActivePenDraft) {
1481
- return nv._cloudMrActivePenDraft;
1482
- }
1483
- if (nv._cloudMrShapeDraftActive && nv._cloudMrActiveShapeDraft) {
1484
- return nv._cloudMrActiveShapeDraft;
1485
- }
1486
- return null;
1487
- }
1488
-
1489
- function isClickOnActiveDraft(nv) {
1490
- const draft = getActiveDraft(nv);
1491
- if (!draft) return false;
1476
+ function isClickOnActiveShapeDraft(nv) {
1477
+ const draft = nv._cloudMrActiveShapeDraft;
1478
+ if (!draft || !nv._cloudMrShapeDraftActive) return false;
1492
1479
  const vox = voxFromMouse(nv);
1493
1480
  if (!vox) return false;
1494
1481
  return isVoxelPartOfDraft(nv, draft, vox);
1495
1482
  }
1496
1483
 
1497
- /** Commit the active draft when the user clicks outside the ROI being edited. */
1498
- function cloudMrTryApplyDraftOnClickAway(nv) {
1499
- if (!cloudMrHasApplyableDraft(nv) || !isClickWithoutDrag(nv.uiData)) {
1500
- return false;
1501
- }
1502
- if (isClickOnActiveDraft(nv)) {
1503
- return false;
1504
- }
1505
- if (typeof nv.onApplyActiveDraft === "function") {
1506
- nv._cloudMrSuppressDrawingChangedMouseUp = true;
1507
- nv.onApplyActiveDraft();
1484
+ function canReopenShapeDraftOnClick(nv) {
1485
+ const penType = nv.opts.penType;
1486
+ return (
1487
+ nv.opts.deferShapeCommit &&
1488
+ nv.opts.drawingEnabled &&
1489
+ !nv._cloudMrShapeDraftActive &&
1490
+ !nv._cloudMrPenDraftActive &&
1491
+ (penType === NI_PEN_TYPE.RECTANGLE || penType === NI_PEN_TYPE.ELLIPSE)
1492
+ );
1493
+ }
1494
+
1495
+ function cloudMrOpenShapeDraftFromClick(nv) {
1496
+ const reopenDraft = captureShapeDraftFromClick(nv);
1497
+ if (!reopenDraft) return false;
1498
+ redrawDraftShape(nv, reopenDraft);
1499
+ nv._cloudMrSuppressDrawingChangedMouseUp = true;
1500
+ if (typeof nv.onShapeDraftReady === "function") {
1501
+ nv.onShapeDraftReady(reopenDraft);
1508
1502
  }
1509
1503
  return true;
1510
1504
  }
1511
1505
 
1512
1506
  /**
1513
- * When no draft is active and the user clicks an existing ROI, reconstruct a
1514
- * shape or pen draft so the bounding-box overlay reappears for re-editing.
1507
+ * Re-enter rectangle/ellipse edit mode when clicking an existing shape ROI.
1515
1508
  */
1516
- function cloudMrTryReopenDraftOnClick(nv) {
1517
- if (
1518
- nv._cloudMrShapeDraftActive ||
1519
- nv._cloudMrPenDraftActive ||
1520
- !isClickWithoutDrag(nv.uiData)
1521
- ) {
1509
+ function cloudMrTryReopenShapeDraftOnClick(nv) {
1510
+ if (!isClickWithoutDrag(nv.uiData) || !canReopenShapeDraftOnClick(nv)) {
1522
1511
  return false;
1523
1512
  }
1513
+ return cloudMrOpenShapeDraftFromClick(nv);
1514
+ }
1524
1515
 
1525
- const penType = nv.opts.penType;
1526
- const isShapeTool =
1527
- nv.opts.deferShapeCommit &&
1528
- nv.opts.drawingEnabled &&
1529
- (penType === NI_PEN_TYPE.RECTANGLE || penType === NI_PEN_TYPE.ELLIPSE);
1530
- const isPenTool = isPenDrawToolActive(nv);
1531
-
1532
- if (!isShapeTool && !isPenTool) {
1516
+ /**
1517
+ * While editing one shape, mousedown on another shape applies the current
1518
+ * draft immediately and opens the clicked shape for editing.
1519
+ */
1520
+ function cloudMrTrySwitchShapeDraftOnMouseDown(nv) {
1521
+ if (!nv._cloudMrShapeDraftActive || nv._cloudMrPenDraftActive) {
1522
+ return false;
1523
+ }
1524
+ if (!nv.opts.deferShapeCommit || !nv.opts.drawingEnabled) {
1533
1525
  return false;
1534
1526
  }
1535
1527
 
1536
- if (isShapeTool) {
1537
- const reopenDraft = captureShapeDraftFromClick(nv);
1538
- if (!reopenDraft) return false;
1539
- redrawDraftShape(nv, reopenDraft);
1540
- nv._cloudMrSuppressDrawingChangedMouseUp = true;
1541
- if (typeof nv.onShapeDraftReady === "function") {
1542
- nv.onShapeDraftReady(reopenDraft);
1543
- }
1544
- return true;
1528
+ const vox = voxFromMouse(nv);
1529
+ if (!vox || isClickOnActiveShapeDraft(nv)) {
1530
+ return false;
1545
1531
  }
1546
1532
 
1547
- const reopenDraft = capturePenDraftFromClick(nv);
1548
- if (!reopenDraft) return false;
1549
- redrawFreehandDraft(nv, reopenDraft);
1550
- nv._cloudMrSuppressDrawingChangedMouseUp = true;
1551
- if (typeof nv.onPenDraftReady === "function") {
1552
- nv.onPenDraftReady(reopenDraft);
1533
+ const dims = nv.back?.dims;
1534
+ if (!dims || !nv.drawBitmap) return false;
1535
+ const dx = dims[1];
1536
+ const dy = dims[2];
1537
+ const idx = vox[0] + vox[1] * dx + vox[2] * dx * dy;
1538
+ if (!nv.drawBitmap[idx]) {
1539
+ return false;
1553
1540
  }
1554
- if (nv.opts.polylinePenMode) {
1555
- resetPolylineState(nv);
1541
+
1542
+ if (typeof nv.onApplyActiveDraft === "function") {
1543
+ nv.onApplyActiveDraft();
1556
1544
  }
1557
- return true;
1545
+ return cloudMrOpenShapeDraftFromClick(nv);
1558
1546
  }
1559
1547
 
1560
1548
  const _mouseDownListener = Niivue.prototype.mouseDownListener;
1561
1549
  Niivue.prototype.mouseDownListener = function cloudMrMouseDownListener(e) {
1550
+ if (e.button === 0 && cloudMrTrySwitchShapeDraftOnMouseDown(this)) {
1551
+ return;
1552
+ }
1562
1553
  if (shouldDeferFreehandCommit(this) && this.drawBitmap) {
1563
1554
  this._cloudMrFreehandSessionStartBitmap = this.drawBitmap.slice();
1564
1555
  this._cloudMrFreehandAxCorSag = -1;
@@ -1622,30 +1613,19 @@ Niivue.prototype.mouseUpListener = function cloudMrMouseUpListener() {
1622
1613
  _mouseUpListener.call(this);
1623
1614
 
1624
1615
  if (polylineClick) {
1625
- if (cloudMrTryReopenDraftOnClick(this)) return;
1626
1616
  addPolylineVertex(this);
1627
1617
  return;
1628
1618
  }
1629
1619
 
1630
- if (cloudMrHasApplyableDraft(this) && isClickWithoutDrag(this.uiData)) {
1631
- if (cloudMrTryApplyDraftOnClickAway(this)) {
1632
- cloudMrTryReopenDraftOnClick(this);
1633
- return;
1634
- }
1635
- if (isClickOnActiveDraft(this)) {
1636
- return;
1637
- }
1638
- }
1639
-
1640
1620
  if (!pendingDraft?.baseBitmap) {
1641
- cloudMrTryReopenDraftOnClick(this);
1621
+ cloudMrTryReopenShapeDraftOnClick(this);
1642
1622
  return;
1643
1623
  }
1644
1624
  if (isDraftTooSmall(pendingDraft.ptA, pendingDraft.ptB)) {
1645
1625
  this.drawBitmap.set(pendingDraft.baseBitmap);
1646
1626
  this.refreshDrawing(true, false);
1647
1627
  this.drawScene();
1648
- cloudMrTryReopenDraftOnClick(this);
1628
+ cloudMrTryReopenShapeDraftOnClick(this);
1649
1629
  return;
1650
1630
  }
1651
1631
  this._cloudMrSuppressDrawingChangedMouseUp = true;
@@ -13,27 +13,6 @@
13
13
  export function isEraserActive(nv: any): any;
14
14
  export function isFreehandPenActive(nv: any): any;
15
15
  export function shouldDeferFreehandCommit(nv: any): any;
16
- export function isPenDrawToolActive(nv: any): any;
17
- /**
18
- * Re-enter pen edit mode when clicking an existing freehand/polyline ROI.
19
- * Reopens as a freehand draft (move via bounding box) since vertex data is
20
- * not stored in the bitmap alone.
21
- */
22
- export function capturePenDraftFromClick(nv: any): {
23
- kind: string;
24
- baseBitmap: Uint8Array;
25
- axCorSag: any;
26
- penValue: number;
27
- strokeVoxels: [number, number, number][];
28
- bounds: {
29
- x1: any;
30
- y1: any;
31
- z1: any;
32
- x2: any;
33
- y2: any;
34
- z2: any;
35
- };
36
- } | null;
37
16
  export function redrawPolylineDraft(nv: any, draft: any): void;
38
17
  export function translatePolylineVertices(vertices: any, delta: any): any;
39
18
  export function updatePolylineVertex(vertices: any, index: any, newVox: any): any;
@@ -18,8 +18,7 @@ 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, floodFillClusterFromVox, eraseClusterFromBitmap, } from "./shapeDraftUtils";
22
- import { axCorSagFromMouse, voxFromMouse } from "./polylinePenUtils";
21
+ import { translatePt } from "./shapeDraftUtils";
23
22
  import { NI_PEN_TYPE } from "./niivuePenType";
24
23
  /** @typedef {'polyline' | 'freehand'} PenDraftKind */
25
24
  /**
@@ -48,34 +47,6 @@ export function isFreehandPenActive(nv) {
48
47
  export function shouldDeferFreehandCommit(nv) {
49
48
  return !!nv.opts.deferFreehandCommit && isFreehandPenActive(nv);
50
49
  }
51
- export function isPenDrawToolActive(nv) {
52
- return (nv.opts.drawingEnabled &&
53
- nv.opts.penType === NI_PEN_TYPE.PEN &&
54
- nv.opts.penValue > 0 &&
55
- (nv.opts.deferFreehandCommit || nv.opts.polylinePenMode));
56
- }
57
- /**
58
- * Re-enter pen edit mode when clicking an existing freehand/polyline ROI.
59
- * Reopens as a freehand draft (move via bounding box) since vertex data is
60
- * not stored in the bitmap alone.
61
- */
62
- export function capturePenDraftFromClick(nv) {
63
- var seedVox = voxFromMouse(nv);
64
- var cluster = floodFillClusterFromVox(nv, seedVox);
65
- if (!cluster)
66
- return null;
67
- var label = cluster.label, visited = cluster.visited, voxels = cluster.voxels, bounds = cluster.bounds;
68
- var x1 = bounds.x1, y1 = bounds.y1, z1 = bounds.z1, x2 = bounds.x2, y2 = bounds.y2, z2 = bounds.z2;
69
- var axCorSag = nv.drawPenAxCorSag >= 0 ? nv.drawPenAxCorSag : axCorSagFromMouse(nv);
70
- return {
71
- kind: "freehand",
72
- baseBitmap: eraseClusterFromBitmap(nv.drawBitmap, visited),
73
- axCorSag: axCorSag,
74
- penValue: label,
75
- strokeVoxels: voxels,
76
- bounds: { x1: x1, y1: y1, z1: z1, x2: x2, y2: y2, z2: z2 }
77
- };
78
- }
79
50
  export function redrawPolylineDraft(nv, draft) {
80
51
  var _a;
81
52
  if (!((_a = draft === null || draft === void 0 ? void 0 : draft.vertices) === null || _a === void 0 ? void 0 : _a.length) || !draft.baseBitmap)
@@ -62,6 +62,8 @@ export function floodFillClusterFromVox(nv: any, seedVox: any): {
62
62
  /** True when a voxel belongs to the live draft overlay (drawn on top of baseBitmap). */
63
63
  export function isVoxelPartOfDraft(nv: any, draft: any, seedVox: any): boolean;
64
64
  export function eraseClusterFromBitmap(bitmap: any, visited: any): Uint8Array;
65
+ /** Guess rectangle vs ellipse from the filled voxel pattern, not the active tool. */
66
+ export function inferShapePenTypeFromCluster(cluster: any, axCorSag: any): 1 | 2;
65
67
  /**
66
68
  * When the user clicks on an existing filled ROI while the rectangle/ellipse tool
67
69
  * is active (but no draft is currently open), flood-fill the clicked cluster to
@@ -74,7 +76,7 @@ export function captureShapeDraftFromClick(nv: any): {
74
76
  ptB: any[];
75
77
  penValue: number;
76
78
  axCorSag: number;
77
- penType: any;
79
+ penType: 1 | 2;
78
80
  baseBitmap: Uint8Array;
79
81
  } | null;
80
82
  export type ShapeDraftKind = 'rectangle' | 'ellipse';
@@ -295,6 +295,65 @@ export function eraseClusterFromBitmap(bitmap, visited) {
295
295
  });
296
296
  return next;
297
297
  }
298
+ function sliceKey(axCorSag, x, y, z) {
299
+ if (axCorSag === 0)
300
+ return "".concat(x, ",").concat(y);
301
+ if (axCorSag === 1)
302
+ return "".concat(x, ",").concat(z);
303
+ return "".concat(y, ",").concat(z);
304
+ }
305
+ /** Guess rectangle vs ellipse from the filled voxel pattern, not the active tool. */
306
+ export function inferShapePenTypeFromCluster(cluster, axCorSag) {
307
+ var bounds = cluster.bounds, voxels = cluster.voxels;
308
+ var x1 = bounds.x1, y1 = bounds.y1, z1 = bounds.z1, x2 = bounds.x2, y2 = bounds.y2, z2 = bounds.z2;
309
+ var filled = new Set();
310
+ for (var _i = 0, voxels_1 = voxels; _i < voxels_1.length; _i++) {
311
+ var _a = voxels_1[_i], x = _a[0], y = _a[1], z = _a[2];
312
+ filled.add(sliceKey(axCorSag, x, y, z));
313
+ }
314
+ var uMin;
315
+ var uMax;
316
+ var vMin;
317
+ var vMax;
318
+ if (axCorSag === 0) {
319
+ uMin = x1;
320
+ uMax = x2;
321
+ vMin = y1;
322
+ vMax = y2;
323
+ }
324
+ else if (axCorSag === 1) {
325
+ uMin = x1;
326
+ uMax = x2;
327
+ vMin = z1;
328
+ vMax = z2;
329
+ }
330
+ else {
331
+ uMin = y1;
332
+ uMax = y2;
333
+ vMin = z1;
334
+ vMax = z2;
335
+ }
336
+ if (uMax <= uMin || vMax <= vMin) {
337
+ return NI_PEN_TYPE.RECTANGLE;
338
+ }
339
+ var cu = (uMin + uMax) / 2;
340
+ var cv = (vMin + vMax) / 2;
341
+ var ru = Math.max(0.5, (uMax - uMin) / 2);
342
+ var rv = Math.max(0.5, (vMax - vMin) / 2);
343
+ var rectScore = 0;
344
+ var ellipseScore = 0;
345
+ for (var u = uMin; u <= uMax; u++) {
346
+ for (var v = vMin; v <= vMax; v++) {
347
+ var has = filled.has("".concat(u, ",").concat(v));
348
+ var inEllipse = Math.pow(((u - cu) / ru), 2) + Math.pow(((v - cv) / rv), 2) <= 1.05;
349
+ if (has)
350
+ rectScore++;
351
+ if (has === inEllipse)
352
+ ellipseScore++;
353
+ }
354
+ }
355
+ return ellipseScore > rectScore ? NI_PEN_TYPE.ELLIPSE : NI_PEN_TYPE.RECTANGLE;
356
+ }
298
357
  /**
299
358
  * When the user clicks on an existing filled ROI while the rectangle/ellipse tool
300
359
  * is active (but no draft is currently open), flood-fill the clicked cluster to
@@ -311,12 +370,13 @@ export function captureShapeDraftFromClick(nv) {
311
370
  var x1 = bounds.x1, y1 = bounds.y1, z1 = bounds.z1, x2 = bounds.x2, y2 = bounds.y2, z2 = bounds.z2;
312
371
  var baseBitmap = eraseClusterFromBitmap(nv.drawBitmap, visited);
313
372
  var axCorSag = inferAxCorSagFromBounds(x1, y1, z1, x2, y2, z2, nv.drawPenAxCorSag >= 0 ? nv.drawPenAxCorSag : 0);
373
+ var penType = inferShapePenTypeFromCluster(cluster, axCorSag);
314
374
  return {
315
375
  ptA: [x1, y1, z1],
316
376
  ptB: [x2, y2, z2],
317
377
  penValue: label,
318
378
  axCorSag: axCorSag,
319
- penType: nv.opts.penType,
379
+ penType: penType,
320
380
  baseBitmap: baseBitmap
321
381
  };
322
382
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudmr-ux",
3
- "version": "4.6.2",
3
+ "version": "4.6.3",
4
4
  "author": "erosmontin@gmail.com",
5
5
  "license": "MIT",
6
6
  "repository": "erosmontin/cloudmr-ux",