@niivue/niivue 0.67.1-dev.0 → 0.67.1-dev.1

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.
@@ -770,6 +770,7 @@ declare class NVZarrHelper {
770
770
  onAllChunksLoaded?: () => void;
771
771
  private constructor();
772
772
  static create(hostImage: NVImage, url: string, options: NVZarrHelperOptions): Promise<NVZarrHelper>;
773
+ loadInitialChunks(): Promise<void>;
773
774
  private updateLevelInfo;
774
775
  private configureHostImage;
775
776
  /** Get unit-converted voxel scales */
@@ -791,6 +792,7 @@ declare class NVZarrHelper {
791
792
  */
792
793
  private updateAffine;
793
794
  beginDrag(): void;
795
+ endDrag(): void;
794
796
  panBy(dx: number, dy: number, dz?: number): Promise<void>;
795
797
  panTo(newCenterX: number, newCenterY: number, newCenterZ?: number): Promise<void>;
796
798
  setPyramidLevel(level: number): Promise<void>;
@@ -812,6 +814,21 @@ declare class NVZarrHelper {
812
814
  height: number;
813
815
  depth: number;
814
816
  };
817
+ /**
818
+ * Convert physical (mm) coordinates back to real zarr level pixel coordinates.
819
+ * Inverts the affine: levelPixel = (mm - OME_translation) / scale
820
+ */
821
+ mmToLevelCoords(mmX: number, mmY: number, mmZ: number): {
822
+ width: number;
823
+ height: number;
824
+ depth: number;
825
+ level: number;
826
+ levelDims: {
827
+ width: number;
828
+ height: number;
829
+ depth: number;
830
+ };
831
+ };
815
832
  private clampCenter;
816
833
  private getVisibleChunks;
817
834
  private updateVolume;
@@ -879,7 +896,7 @@ declare function copyAffine(affine: number[][]): number[][];
879
896
  */
880
897
  declare function transformsEqual(a: AffineTransform, b: AffineTransform, epsilon?: number): boolean;
881
898
 
882
- type TypedVoxelArray = Float32Array | Uint8Array | Int16Array | Float64Array | Uint16Array;
899
+ type TypedVoxelArray = Float32Array | Uint8Array | Int16Array | Float64Array | Uint16Array | Int32Array | Uint32Array;
883
900
  /**
884
901
  * a NVImage encapsulates some image data and provides methods to query and operate on images
885
902
  */
@@ -1,4 +1,4 @@
1
- import { ar as Volume, x as NiftiHeader } from '../nvdocument-CcP6istx.js';
1
+ import { ar as Volume, x as NiftiHeader } from '../nvdocument-B64_4nuJ.js';
2
2
  import { vec3, mat4, vec4 } from 'gl-matrix';
3
3
  import 'nifti-reader-js';
4
4
 
package/dist/index.d.ts CHANGED
@@ -567,6 +567,7 @@ declare class NVZarrHelper {
567
567
  onAllChunksLoaded?: () => void;
568
568
  private constructor();
569
569
  static create(hostImage: NVImage, url: string, options: NVZarrHelperOptions): Promise<NVZarrHelper>;
570
+ loadInitialChunks(): Promise<void>;
570
571
  private updateLevelInfo;
571
572
  private configureHostImage;
572
573
  /** Get unit-converted voxel scales */
@@ -588,6 +589,7 @@ declare class NVZarrHelper {
588
589
  */
589
590
  private updateAffine;
590
591
  beginDrag(): void;
592
+ endDrag(): void;
591
593
  panBy(dx: number, dy: number, dz?: number): Promise<void>;
592
594
  panTo(newCenterX: number, newCenterY: number, newCenterZ?: number): Promise<void>;
593
595
  setPyramidLevel(level: number): Promise<void>;
@@ -609,6 +611,21 @@ declare class NVZarrHelper {
609
611
  height: number;
610
612
  depth: number;
611
613
  };
614
+ /**
615
+ * Convert physical (mm) coordinates back to real zarr level pixel coordinates.
616
+ * Inverts the affine: levelPixel = (mm - OME_translation) / scale
617
+ */
618
+ mmToLevelCoords(mmX: number, mmY: number, mmZ: number): {
619
+ width: number;
620
+ height: number;
621
+ depth: number;
622
+ level: number;
623
+ levelDims: {
624
+ width: number;
625
+ height: number;
626
+ depth: number;
627
+ };
628
+ };
612
629
  private clampCenter;
613
630
  private getVisibleChunks;
614
631
  private updateVolume;
@@ -676,7 +693,7 @@ declare function copyAffine(affine: number[][]): number[][];
676
693
  */
677
694
  declare function transformsEqual(a: AffineTransform, b: AffineTransform, epsilon?: number): boolean;
678
695
 
679
- type TypedVoxelArray = Float32Array | Uint8Array | Int16Array | Float64Array | Uint16Array;
696
+ type TypedVoxelArray = Float32Array | Uint8Array | Int16Array | Float64Array | Uint16Array | Int32Array | Uint32Array;
680
697
  /**
681
698
  * a NVImage encapsulates some image data and provides methods to query and operate on images
682
699
  */
@@ -2314,6 +2331,7 @@ declare class Niivue {
2314
2331
  private canvasObserver;
2315
2332
  syncOpts: SyncOpts;
2316
2333
  readyForSync: boolean;
2334
+ private _skipDragInDraw;
2317
2335
  uiData: UIData;
2318
2336
  back: NVImage | null;
2319
2337
  overlays: NVImage[];
package/dist/index.js CHANGED
@@ -5249,7 +5249,7 @@ var forEach3 = (function() {
5249
5249
  // package.json
5250
5250
  var package_default = {
5251
5251
  name: "@niivue/niivue",
5252
- version: "0.67.0",
5252
+ version: "0.67.1-dev.0",
5253
5253
  description: "minimal webgl2 nifti image viewer",
5254
5254
  types: "./build/niivue/index.d.ts",
5255
5255
  main: "./build/niivue/index.js",
@@ -31227,9 +31227,11 @@ function createTypedVoxelArray(datatypeCode, size) {
31227
31227
  case 512 /* DT_UINT16 */:
31228
31228
  return new Uint16Array(size);
31229
31229
  case 4 /* DT_INT16 */:
31230
- case 8 /* DT_INT32 */:
31231
31230
  return new Int16Array(size);
31231
+ case 8 /* DT_INT32 */:
31232
+ return new Int32Array(size);
31232
31233
  case 768 /* DT_UINT32 */:
31234
+ return new Uint32Array(size);
31233
31235
  case 16 /* DT_FLOAT32 */:
31234
31236
  return new Float32Array(size);
31235
31237
  case 64 /* DT_FLOAT64 */:
@@ -31353,13 +31355,15 @@ var NVZarrHelper = class _NVZarrHelper {
31353
31355
  const level = Math.max(0, Math.min(options.level, helper.pyramidInfo.levels.length - 1));
31354
31356
  helper.pyramidLevel = level;
31355
31357
  helper.updateLevelInfo();
31356
- helper.centerX = helper.levelDims.width / 2;
31357
- helper.centerY = helper.levelDims.height / 2;
31358
- helper.centerZ = helper.levelDims.depth / 2;
31358
+ helper.centerX = helper.volumeDims.width / 2;
31359
+ helper.centerY = helper.volumeDims.height / 2;
31360
+ helper.centerZ = helper.volumeDims.depth / 2;
31359
31361
  helper.configureHostImage();
31360
- await helper.updateVolume();
31361
31362
  return helper;
31362
31363
  }
31364
+ async loadInitialChunks() {
31365
+ await this.updateVolume();
31366
+ }
31363
31367
  updateLevelInfo() {
31364
31368
  const levelInfo = this.pyramidInfo.levels[this.pyramidLevel];
31365
31369
  const shape = levelInfo.shape;
@@ -31431,12 +31435,12 @@ var NVZarrHelper = class _NVZarrHelper {
31431
31435
  img.img = createTypedVoxelArray(this.datatypeCode, width * height * depth);
31432
31436
  this.updateAffine();
31433
31437
  img.originalAffine = copyAffine(hdr.affine);
31434
- img.cal_min = NaN;
31435
- img.cal_max = NaN;
31436
- img.robust_min = NaN;
31437
- img.robust_max = NaN;
31438
- img.global_min = NaN;
31439
- img.global_max = NaN;
31438
+ img.cal_min = 0;
31439
+ img.cal_max = 0;
31440
+ img.robust_min = 0;
31441
+ img.robust_max = 0;
31442
+ img.global_min = 0;
31443
+ img.global_max = 0;
31440
31444
  }
31441
31445
  /** Get unit-converted voxel scales */
31442
31446
  getConvertedScales() {
@@ -31555,6 +31559,9 @@ var NVZarrHelper = class _NVZarrHelper {
31555
31559
  beginDrag() {
31556
31560
  this.centerAtDragStart = { x: this.centerX, y: this.centerY, z: this.centerZ };
31557
31561
  }
31562
+ endDrag() {
31563
+ this.centerAtDragStart = null;
31564
+ }
31558
31565
  async panBy(dx, dy, dz = 0) {
31559
31566
  this.centerX -= dx;
31560
31567
  this.centerY -= dy;
@@ -31564,12 +31571,18 @@ var NVZarrHelper = class _NVZarrHelper {
31564
31571
  await this.updateVolume();
31565
31572
  }
31566
31573
  async panTo(newCenterX, newCenterY, newCenterZ) {
31574
+ const prevX = this.centerX;
31575
+ const prevY = this.centerY;
31576
+ const prevZ = this.centerZ;
31567
31577
  this.centerX = newCenterX;
31568
31578
  this.centerY = newCenterY;
31569
31579
  if (newCenterZ !== void 0) {
31570
31580
  this.centerZ = newCenterZ;
31571
31581
  }
31572
31582
  this.clampCenter();
31583
+ if (Math.abs(this.centerX - prevX) < 1e-3 && Math.abs(this.centerY - prevY) < 1e-3 && Math.abs(this.centerZ - prevZ) < 1e-3) {
31584
+ return;
31585
+ }
31573
31586
  this.updateAffine();
31574
31587
  await this.updateVolume();
31575
31588
  }
@@ -31609,6 +31622,54 @@ var NVZarrHelper = class _NVZarrHelper {
31609
31622
  getVolumeDims() {
31610
31623
  return { ...this.volumeDims };
31611
31624
  }
31625
+ /**
31626
+ * Convert physical (mm) coordinates back to real zarr level pixel coordinates.
31627
+ * Inverts the affine: levelPixel = (mm - OME_translation) / scale
31628
+ */
31629
+ mmToLevelCoords(mmX, mmY, mmZ) {
31630
+ const { scaleW, scaleH, scaleD } = this.getConvertedScales();
31631
+ const axisNames = this.pyramidInfo.axisMapping.spatialAxisNames;
31632
+ const mmByAxis = { x: mmX, y: mmY, z: mmZ };
31633
+ let levelW = 0;
31634
+ let levelH = 0;
31635
+ let levelD = 0;
31636
+ if (axisNames.length >= 2) {
31637
+ const wAxisName = axisNames[axisNames.length - 1];
31638
+ const hAxisName = axisNames[axisNames.length - 2];
31639
+ const dAxisName = axisNames.length >= 3 ? axisNames[axisNames.length - 3] : "z";
31640
+ const mmW = mmByAxis[wAxisName] ?? mmX;
31641
+ const mmH = mmByAxis[hAxisName] ?? mmY;
31642
+ const mmD = mmByAxis[dAxisName] ?? mmZ;
31643
+ if (this.hasTranslations) {
31644
+ const { transW, transH, transD } = this.getConvertedTranslations();
31645
+ levelW = scaleW !== 0 ? (mmW - transW) / scaleW : 0;
31646
+ levelH = scaleH !== 0 ? (mmH - transH) / scaleH : 0;
31647
+ levelD = scaleD !== 0 ? (mmD - transD) / scaleD : 0;
31648
+ } else {
31649
+ levelW = scaleW !== 0 ? mmW / scaleW : 0;
31650
+ levelH = scaleH !== 0 ? mmH / scaleH : 0;
31651
+ levelD = scaleD !== 0 ? mmD / scaleD : 0;
31652
+ }
31653
+ } else {
31654
+ if (this.hasTranslations) {
31655
+ const { transW, transH, transD } = this.getConvertedTranslations();
31656
+ levelW = scaleW !== 0 ? (mmX - transW) / scaleW : 0;
31657
+ levelH = scaleH !== 0 ? (mmY - transH) / scaleH : 0;
31658
+ levelD = scaleD !== 0 ? (mmZ - transD) / scaleD : 0;
31659
+ } else {
31660
+ levelW = scaleW !== 0 ? mmX / scaleW : 0;
31661
+ levelH = scaleH !== 0 ? mmY / scaleH : 0;
31662
+ levelD = scaleD !== 0 ? mmZ / scaleD : 0;
31663
+ }
31664
+ }
31665
+ return {
31666
+ width: Math.round(levelW),
31667
+ height: Math.round(levelH),
31668
+ depth: Math.round(levelD),
31669
+ level: this.pyramidLevel,
31670
+ levelDims: { ...this.levelDims }
31671
+ };
31672
+ }
31612
31673
  clampCenter() {
31613
31674
  const halfW = this.volumeDims.width / 2;
31614
31675
  const halfH = this.volumeDims.height / 2;
@@ -43414,6 +43475,7 @@ var Niivue = class {
43414
43475
  crosshair: false
43415
43476
  });
43416
43477
  __publicField(this, "readyForSync", false);
43478
+ __publicField(this, "_skipDragInDraw", false);
43417
43479
  // UI Data
43418
43480
  __publicField(this, "uiData", {
43419
43481
  mousedown: false,
@@ -44510,6 +44572,9 @@ var Niivue = class {
44510
44572
  }
44511
44573
  if (this.uiData.isDragging) {
44512
44574
  this.uiData.isDragging = false;
44575
+ for (const vol of this.getZarrVolumes()) {
44576
+ vol.zarrHelper?.endDrag();
44577
+ }
44513
44578
  if (currentDragMode === 7 /* angle */) {
44514
44579
  if (this.uiData.angleState === "drawing_first_line") {
44515
44580
  this.uiData.angleFirstLine = [this.uiData.dragStart[0], this.uiData.dragStart[1], this.uiData.dragEnd[0], this.uiData.dragEnd[1]];
@@ -44655,6 +44720,9 @@ var Niivue = class {
44655
44720
  }
44656
44721
  if (this.uiData.isDragging) {
44657
44722
  this.uiData.isDragging = false;
44723
+ for (const vol of this.getZarrVolumes()) {
44724
+ vol.zarrHelper?.endDrag();
44725
+ }
44658
44726
  if (this.getCurrentDragMode() === 1 /* contrast */) {
44659
44727
  this.calculateNewRange();
44660
44728
  this.refreshLayers(this.volumes[0], 0);
@@ -44714,6 +44782,9 @@ var Niivue = class {
44714
44782
  if (this.uiData.isDragging || this.uiData.mousedown) {
44715
44783
  log.debug("Mouse left canvas during drag, resetting drag state.");
44716
44784
  this.uiData.isDragging = false;
44785
+ for (const vol of this.getZarrVolumes()) {
44786
+ vol.zarrHelper?.endDrag();
44787
+ }
44717
44788
  const resetState = createResetButtonState();
44718
44789
  this.uiData.mouseButtonLeftDown = resetState.mouseButtonLeftDown;
44719
44790
  this.uiData.mouseButtonCenterDown = resetState.mouseButtonCenterDown;
@@ -45866,8 +45937,11 @@ var Niivue = class {
45866
45937
  if (volume.zarrHelper) {
45867
45938
  volume.zarrHelper.onChunksUpdated = () => {
45868
45939
  this.updateGLVolume();
45940
+ this._skipDragInDraw = true;
45869
45941
  this.drawScene();
45942
+ this._skipDragInDraw = false;
45870
45943
  };
45944
+ volume.zarrHelper.loadInitialChunks().catch((err2) => log.error("Failed to load initial zarr chunks", err2));
45871
45945
  }
45872
45946
  log.debug("loaded volume", volume.name);
45873
45947
  log.debug(volume);
@@ -49994,7 +50068,9 @@ var Niivue = class {
49994
50068
  }
49995
50069
  if (panPromises.length > 0) {
49996
50070
  Promise.all(panPromises).then(() => {
50071
+ this._skipDragInDraw = true;
49997
50072
  this.drawScene();
50073
+ this._skipDragInDraw = false;
49998
50074
  }).catch((err2) => {
49999
50075
  log.error("Failed to pan Zarr:", err2);
50000
50076
  });
@@ -53271,7 +53347,7 @@ var Niivue = class {
53271
53347
  this.dragForSlicer3D([this.uiData.dragStart[0], this.uiData.dragStart[1], this.uiData.dragEnd[0], this.uiData.dragEnd[1]]);
53272
53348
  return;
53273
53349
  }
53274
- if (this.getCurrentDragMode() === 3 /* pan */) {
53350
+ if (this.getCurrentDragMode() === 3 /* pan */ && !this._skipDragInDraw) {
53275
53351
  this.dragForPanZoom([this.uiData.dragStart[0], this.uiData.dragStart[1], this.uiData.dragEnd[0], this.uiData.dragEnd[1]]);
53276
53352
  return;
53277
53353
  }