cloudmr-ux 4.6.1 → 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.
- package/dist/CmrComponents/niivue-viewer/CloudMrNiivueViewer.js +10 -0
- package/dist/CmrComponents/niivue-viewer/NiivuePatcher.js +64 -19
- package/dist/CmrComponents/niivue-viewer/shapeDraftUtils.d.ts +19 -4
- package/dist/CmrComponents/niivue-viewer/shapeDraftUtils.js +125 -23
- package/package.json +1 -1
|
@@ -832,6 +832,12 @@ 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 syncActiveShapeDraftRef() {
|
|
836
|
+
nv._cloudMrActiveShapeDraft = shapeDraftRef.current;
|
|
837
|
+
}
|
|
838
|
+
function clearActiveShapeDraftRef() {
|
|
839
|
+
nv._cloudMrActiveShapeDraft = null;
|
|
840
|
+
}
|
|
835
841
|
function cancelPenDraftHandler() {
|
|
836
842
|
var _a;
|
|
837
843
|
var draft = penDraftRef.current;
|
|
@@ -920,6 +926,7 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
920
926
|
setShapeDraft(null);
|
|
921
927
|
shapeDraftRef.current = null;
|
|
922
928
|
nv._cloudMrShapeDraftActive = false;
|
|
929
|
+
clearActiveShapeDraftRef();
|
|
923
930
|
if (drawShapeToolRef.current) {
|
|
924
931
|
nvSetDrawingEnabled(true);
|
|
925
932
|
}
|
|
@@ -932,6 +939,7 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
932
939
|
setShapeDraft(null);
|
|
933
940
|
shapeDraftRef.current = null;
|
|
934
941
|
nv._cloudMrShapeDraftActive = false;
|
|
942
|
+
clearActiveShapeDraftRef();
|
|
935
943
|
if (drawShapeToolRef.current) {
|
|
936
944
|
nvSetDrawingEnabled(true);
|
|
937
945
|
}
|
|
@@ -940,11 +948,13 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
940
948
|
function onShapeDraftChange(draft) {
|
|
941
949
|
setShapeDraft(draft);
|
|
942
950
|
shapeDraftRef.current = draft;
|
|
951
|
+
syncActiveShapeDraftRef();
|
|
943
952
|
}
|
|
944
953
|
nv.onShapeDraftReady = function (draft) {
|
|
945
954
|
setShapeDraft(draft);
|
|
946
955
|
shapeDraftRef.current = draft;
|
|
947
956
|
nv._cloudMrShapeDraftActive = true;
|
|
957
|
+
syncActiveShapeDraftRef();
|
|
948
958
|
nvSetDrawingEnabled(false);
|
|
949
959
|
};
|
|
950
960
|
nv.onApplyActiveDraft = function () {
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
captureDeferredShapeDraft,
|
|
7
7
|
captureShapeDraftFromClick,
|
|
8
8
|
isDraftTooSmall,
|
|
9
|
+
isVoxelPartOfDraft,
|
|
9
10
|
redrawDraftShape,
|
|
10
11
|
shouldDeferShapeCommit,
|
|
11
12
|
} from "./shapeDraftUtils.js";
|
|
@@ -1472,27 +1473,28 @@ function cloudMrHasApplyableDraft(nv) {
|
|
|
1472
1473
|
return !!(nv._cloudMrShapeDraftActive || nv._cloudMrPenDraftActive);
|
|
1473
1474
|
}
|
|
1474
1475
|
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1476
|
+
function isClickOnActiveShapeDraft(nv) {
|
|
1477
|
+
const draft = nv._cloudMrActiveShapeDraft;
|
|
1478
|
+
if (!draft || !nv._cloudMrShapeDraftActive) return false;
|
|
1479
|
+
const vox = voxFromMouse(nv);
|
|
1480
|
+
if (!vox) return false;
|
|
1481
|
+
return isVoxelPartOfDraft(nv, draft, vox);
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
function canReopenShapeDraftOnClick(nv) {
|
|
1481
1485
|
const penType = nv.opts.penType;
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
nv._cloudMrShapeDraftActive
|
|
1486
|
-
nv._cloudMrPenDraftActive
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
return false;
|
|
1491
|
-
}
|
|
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
|
+
}
|
|
1492
1494
|
|
|
1495
|
+
function cloudMrOpenShapeDraftFromClick(nv) {
|
|
1493
1496
|
const reopenDraft = captureShapeDraftFromClick(nv);
|
|
1494
1497
|
if (!reopenDraft) return false;
|
|
1495
|
-
|
|
1496
1498
|
redrawDraftShape(nv, reopenDraft);
|
|
1497
1499
|
nv._cloudMrSuppressDrawingChangedMouseUp = true;
|
|
1498
1500
|
if (typeof nv.onShapeDraftReady === "function") {
|
|
@@ -1501,8 +1503,53 @@ function cloudMrTryReopenShapeDraftOnClick(nv) {
|
|
|
1501
1503
|
return true;
|
|
1502
1504
|
}
|
|
1503
1505
|
|
|
1506
|
+
/**
|
|
1507
|
+
* Re-enter rectangle/ellipse edit mode when clicking an existing shape ROI.
|
|
1508
|
+
*/
|
|
1509
|
+
function cloudMrTryReopenShapeDraftOnClick(nv) {
|
|
1510
|
+
if (!isClickWithoutDrag(nv.uiData) || !canReopenShapeDraftOnClick(nv)) {
|
|
1511
|
+
return false;
|
|
1512
|
+
}
|
|
1513
|
+
return cloudMrOpenShapeDraftFromClick(nv);
|
|
1514
|
+
}
|
|
1515
|
+
|
|
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) {
|
|
1525
|
+
return false;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
const vox = voxFromMouse(nv);
|
|
1529
|
+
if (!vox || isClickOnActiveShapeDraft(nv)) {
|
|
1530
|
+
return false;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
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;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
if (typeof nv.onApplyActiveDraft === "function") {
|
|
1543
|
+
nv.onApplyActiveDraft();
|
|
1544
|
+
}
|
|
1545
|
+
return cloudMrOpenShapeDraftFromClick(nv);
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1504
1548
|
const _mouseDownListener = Niivue.prototype.mouseDownListener;
|
|
1505
1549
|
Niivue.prototype.mouseDownListener = function cloudMrMouseDownListener(e) {
|
|
1550
|
+
if (e.button === 0 && cloudMrTrySwitchShapeDraftOnMouseDown(this)) {
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1506
1553
|
if (shouldDeferFreehandCommit(this) && this.drawBitmap) {
|
|
1507
1554
|
this._cloudMrFreehandSessionStartBitmap = this.drawBitmap.slice();
|
|
1508
1555
|
this._cloudMrFreehandAxCorSag = -1;
|
|
@@ -1571,7 +1618,6 @@ Niivue.prototype.mouseUpListener = function cloudMrMouseUpListener() {
|
|
|
1571
1618
|
}
|
|
1572
1619
|
|
|
1573
1620
|
if (!pendingDraft?.baseBitmap) {
|
|
1574
|
-
// No new draft drawn — check if user clicked an existing ROI to re-edit it
|
|
1575
1621
|
cloudMrTryReopenShapeDraftOnClick(this);
|
|
1576
1622
|
return;
|
|
1577
1623
|
}
|
|
@@ -1579,7 +1625,6 @@ Niivue.prototype.mouseUpListener = function cloudMrMouseUpListener() {
|
|
|
1579
1625
|
this.drawBitmap.set(pendingDraft.baseBitmap);
|
|
1580
1626
|
this.refreshDrawing(true, false);
|
|
1581
1627
|
this.drawScene();
|
|
1582
|
-
// Tiny drag treated as a click — also try to reopen an existing ROI
|
|
1583
1628
|
cloudMrTryReopenShapeDraftOnClick(this);
|
|
1584
1629
|
return;
|
|
1585
1630
|
}
|
|
@@ -49,6 +49,21 @@ export function captureDeferredShapeDraft(nv: any): {
|
|
|
49
49
|
baseBitmap: Uint8Array | null;
|
|
50
50
|
};
|
|
51
51
|
export function shouldDeferShapeCommit(nv: any): any;
|
|
52
|
+
/**
|
|
53
|
+
* Flood-fill a connected voxel cluster from a seed.
|
|
54
|
+
* @returns {{ label: number, visited: Set<number>, voxels: [number,number,number][], bounds: object } | null}
|
|
55
|
+
*/
|
|
56
|
+
export function floodFillClusterFromVox(nv: any, seedVox: any): {
|
|
57
|
+
label: number;
|
|
58
|
+
visited: Set<number>;
|
|
59
|
+
voxels: [number, number, number][];
|
|
60
|
+
bounds: object;
|
|
61
|
+
} | null;
|
|
62
|
+
/** True when a voxel belongs to the live draft overlay (drawn on top of baseBitmap). */
|
|
63
|
+
export function isVoxelPartOfDraft(nv: any, draft: any, seedVox: any): boolean;
|
|
64
|
+
export function eraseClusterFromBitmap(bitmap: any, visited: any): Uint8Array;
|
|
65
|
+
/** Guess rectangle vs ellipse from the filled voxel pattern, not the active tool. */
|
|
66
|
+
export function inferShapePenTypeFromCluster(cluster: any, axCorSag: any): 1 | 2;
|
|
52
67
|
/**
|
|
53
68
|
* When the user clicks on an existing filled ROI while the rectangle/ellipse tool
|
|
54
69
|
* is active (but no draft is currently open), flood-fill the clicked cluster to
|
|
@@ -57,11 +72,11 @@ export function shouldDeferShapeCommit(nv: any): any;
|
|
|
57
72
|
* Returns null if the click didn't land on a labeled voxel.
|
|
58
73
|
*/
|
|
59
74
|
export function captureShapeDraftFromClick(nv: any): {
|
|
60
|
-
ptA:
|
|
61
|
-
ptB:
|
|
62
|
-
penValue:
|
|
75
|
+
ptA: any[];
|
|
76
|
+
ptB: any[];
|
|
77
|
+
penValue: number;
|
|
63
78
|
axCorSag: number;
|
|
64
|
-
penType:
|
|
79
|
+
penType: 1 | 2;
|
|
65
80
|
baseBitmap: Uint8Array;
|
|
66
81
|
} | null;
|
|
67
82
|
export type ShapeDraftKind = 'rectangle' | 'ellipse';
|
|
@@ -203,27 +203,21 @@ function inferAxCorSagFromBounds(x1, y1, z1, x2, y2, z2, fallback) {
|
|
|
203
203
|
var spanY = y2 - y1;
|
|
204
204
|
var spanZ = z2 - z1;
|
|
205
205
|
if (spanZ <= spanX && spanZ <= spanY)
|
|
206
|
-
return 0;
|
|
206
|
+
return 0;
|
|
207
207
|
if (spanY <= spanX && spanY <= spanZ)
|
|
208
|
-
return 1;
|
|
208
|
+
return 1;
|
|
209
209
|
if (spanX <= spanY && spanX <= spanZ)
|
|
210
|
-
return 2;
|
|
210
|
+
return 2;
|
|
211
211
|
return fallback;
|
|
212
212
|
}
|
|
213
213
|
/**
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
* reconstruct a ShapeDraft so the bounding-box overlay reappears for re-editing.
|
|
217
|
-
*
|
|
218
|
-
* Returns null if the click didn't land on a labeled voxel.
|
|
214
|
+
* Flood-fill a connected voxel cluster from a seed.
|
|
215
|
+
* @returns {{ label: number, visited: Set<number>, voxels: [number,number,number][], bounds: object } | null}
|
|
219
216
|
*/
|
|
220
|
-
export function
|
|
217
|
+
export function floodFillClusterFromVox(nv, seedVox) {
|
|
221
218
|
var _a;
|
|
222
219
|
var dims = (_a = nv.back) === null || _a === void 0 ? void 0 : _a.dims;
|
|
223
|
-
if (!dims || !nv.drawBitmap)
|
|
224
|
-
return null;
|
|
225
|
-
var seedVox = voxFromMouse(nv);
|
|
226
|
-
if (!seedVox)
|
|
220
|
+
if (!dims || !nv.drawBitmap || !seedVox)
|
|
227
221
|
return null;
|
|
228
222
|
var dx = dims[1];
|
|
229
223
|
var dy = dims[2];
|
|
@@ -232,15 +226,20 @@ export function captureShapeDraftFromClick(nv) {
|
|
|
232
226
|
var label = nv.drawBitmap[seedIdx];
|
|
233
227
|
if (!label)
|
|
234
228
|
return null;
|
|
235
|
-
// BFS flood-fill to find connected cluster of the same label
|
|
236
229
|
var visited = new Set();
|
|
237
230
|
var queue = [seedIdx];
|
|
238
231
|
visited.add(seedIdx);
|
|
239
|
-
var
|
|
240
|
-
var
|
|
232
|
+
var voxels = [];
|
|
233
|
+
var x1 = Infinity;
|
|
234
|
+
var y1 = Infinity;
|
|
235
|
+
var z1 = Infinity;
|
|
236
|
+
var x2 = -Infinity;
|
|
237
|
+
var y2 = -Infinity;
|
|
238
|
+
var z2 = -Infinity;
|
|
241
239
|
while (queue.length > 0) {
|
|
242
240
|
var idx = queue.shift();
|
|
243
241
|
var _b = decodeVoxelIndex(idx, dx, dy), x = _b[0], y = _b[1], z = _b[2];
|
|
242
|
+
voxels.push([x, y, z]);
|
|
244
243
|
x1 = Math.min(x1, x);
|
|
245
244
|
y1 = Math.min(y1, y);
|
|
246
245
|
z1 = Math.min(z1, z);
|
|
@@ -248,9 +247,12 @@ export function captureShapeDraftFromClick(nv) {
|
|
|
248
247
|
y2 = Math.max(y2, y);
|
|
249
248
|
z2 = Math.max(z2, z);
|
|
250
249
|
var neighbors = [
|
|
251
|
-
[x + 1, y, z],
|
|
252
|
-
[x
|
|
253
|
-
[x, y
|
|
250
|
+
[x + 1, y, z],
|
|
251
|
+
[x - 1, y, z],
|
|
252
|
+
[x, y + 1, z],
|
|
253
|
+
[x, y - 1, z],
|
|
254
|
+
[x, y, z + 1],
|
|
255
|
+
[x, y, z - 1],
|
|
254
256
|
];
|
|
255
257
|
for (var _i = 0, neighbors_1 = neighbors; _i < neighbors_1.length; _i++) {
|
|
256
258
|
var _c = neighbors_1[_i], nx = _c[0], ny = _c[1], nz = _c[2];
|
|
@@ -265,16 +267,116 @@ export function captureShapeDraftFromClick(nv) {
|
|
|
265
267
|
}
|
|
266
268
|
if (!Number.isFinite(x1))
|
|
267
269
|
return null;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
270
|
+
return {
|
|
271
|
+
label: label,
|
|
272
|
+
visited: visited,
|
|
273
|
+
voxels: voxels,
|
|
274
|
+
bounds: { x1: x1, y1: y1, z1: z1, x2: x2, y2: y2, z2: z2 }
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
/** True when a voxel belongs to the live draft overlay (drawn on top of baseBitmap). */
|
|
278
|
+
export function isVoxelPartOfDraft(nv, draft, seedVox) {
|
|
279
|
+
var _a;
|
|
280
|
+
if (!(draft === null || draft === void 0 ? void 0 : draft.baseBitmap) || !(nv === null || nv === void 0 ? void 0 : nv.drawBitmap) || !seedVox)
|
|
281
|
+
return false;
|
|
282
|
+
var dims = (_a = nv.back) === null || _a === void 0 ? void 0 : _a.dims;
|
|
283
|
+
if (!dims)
|
|
284
|
+
return false;
|
|
285
|
+
var dx = dims[1];
|
|
286
|
+
var dy = dims[2];
|
|
287
|
+
var idx = voxelIndex(seedVox[0], seedVox[1], seedVox[2], dx, dy);
|
|
288
|
+
return (nv.drawBitmap[idx] === draft.penValue &&
|
|
289
|
+
draft.baseBitmap[idx] !== draft.penValue);
|
|
290
|
+
}
|
|
291
|
+
export function eraseClusterFromBitmap(bitmap, visited) {
|
|
292
|
+
var next = new Uint8Array(bitmap);
|
|
293
|
+
visited.forEach(function (idx) {
|
|
294
|
+
next[idx] = 0;
|
|
295
|
+
});
|
|
296
|
+
return next;
|
|
297
|
+
}
|
|
298
|
+
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
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* When the user clicks on an existing filled ROI while the rectangle/ellipse tool
|
|
359
|
+
* is active (but no draft is currently open), flood-fill the clicked cluster to
|
|
360
|
+
* reconstruct a ShapeDraft so the bounding-box overlay reappears for re-editing.
|
|
361
|
+
*
|
|
362
|
+
* Returns null if the click didn't land on a labeled voxel.
|
|
363
|
+
*/
|
|
364
|
+
export function captureShapeDraftFromClick(nv) {
|
|
365
|
+
var seedVox = voxFromMouse(nv);
|
|
366
|
+
var cluster = floodFillClusterFromVox(nv, seedVox);
|
|
367
|
+
if (!cluster)
|
|
368
|
+
return null;
|
|
369
|
+
var label = cluster.label, visited = cluster.visited, bounds = cluster.bounds;
|
|
370
|
+
var x1 = bounds.x1, y1 = bounds.y1, z1 = bounds.z1, x2 = bounds.x2, y2 = bounds.y2, z2 = bounds.z2;
|
|
371
|
+
var baseBitmap = eraseClusterFromBitmap(nv.drawBitmap, visited);
|
|
271
372
|
var axCorSag = inferAxCorSagFromBounds(x1, y1, z1, x2, y2, z2, nv.drawPenAxCorSag >= 0 ? nv.drawPenAxCorSag : 0);
|
|
373
|
+
var penType = inferShapePenTypeFromCluster(cluster, axCorSag);
|
|
272
374
|
return {
|
|
273
375
|
ptA: [x1, y1, z1],
|
|
274
376
|
ptB: [x2, y2, z2],
|
|
275
377
|
penValue: label,
|
|
276
378
|
axCorSag: axCorSag,
|
|
277
|
-
penType:
|
|
379
|
+
penType: penType,
|
|
278
380
|
baseBitmap: baseBitmap
|
|
279
381
|
};
|
|
280
382
|
}
|