arrayview 0.41.0__tar.gz → 0.41.1__tar.gz

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.
Files changed (112) hide show
  1. {arrayview-0.41.0 → arrayview-0.41.1}/PKG-INFO +1 -1
  2. {arrayview-0.41.0 → arrayview-0.41.1}/pyproject.toml +2 -1
  3. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_analysis.py +1 -0
  4. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_io.py +39 -30
  5. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_launcher.py +310 -71
  6. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_lifecycle.py +16 -4
  7. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_platform.py +11 -2
  8. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_routes_loading.py +100 -9
  9. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_routes_query.py +5 -1
  10. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_routes_state.py +55 -41
  11. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_routes_vectorfield.py +41 -0
  12. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_session.py +16 -2
  13. arrayview-0.41.1/src/arrayview/_source_safety.py +775 -0
  14. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_viewer.html +112 -39
  15. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_vscode_extension.py +155 -7
  16. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_vscode_signal.py +9 -3
  17. arrayview-0.41.1/src/arrayview/_watch.py +57 -0
  18. arrayview-0.41.1/src/arrayview/arrayview-opener.vsix +0 -0
  19. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_api.py +47 -10
  20. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_browser.py +6 -5
  21. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_launch_blackbox.py +275 -3
  22. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_lifecycle_contract.py +158 -1
  23. arrayview-0.41.1/tests/test_nifti_meta.py +174 -0
  24. arrayview-0.41.1/tests/test_source_safety.py +716 -0
  25. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_vscode_ack_protocol.py +33 -0
  26. arrayview-0.41.0/src/arrayview/arrayview-opener.vsix +0 -0
  27. arrayview-0.41.0/tests/test_nifti_meta.py +0 -59
  28. {arrayview-0.41.0 → arrayview-0.41.1}/.gitignore +0 -0
  29. {arrayview-0.41.0 → arrayview-0.41.1}/LICENSE +0 -0
  30. {arrayview-0.41.0 → arrayview-0.41.1}/README.md +0 -0
  31. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/ARCHITECTURE.md +0 -0
  32. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/__init__.py +0 -0
  33. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/__main__.py +0 -0
  34. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_app.py +0 -0
  35. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_config.py +0 -0
  36. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_diagnostics.py +0 -0
  37. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_dicom.py +0 -0
  38. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_diff.py +0 -0
  39. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_icon.png +0 -0
  40. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_imaging.py +0 -0
  41. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_instance_registry.py +0 -0
  42. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_launch_plan.py +0 -0
  43. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_launch_trace.py +0 -0
  44. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_overlays.py +0 -0
  45. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_render.py +0 -0
  46. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_routes_analysis.py +0 -0
  47. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_routes_drop.py +0 -0
  48. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_routes_export.py +0 -0
  49. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_routes_persistence.py +0 -0
  50. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_routes_preferences.py +0 -0
  51. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_routes_preload.py +0 -0
  52. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_routes_rendering.py +0 -0
  53. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_routes_segmentation.py +0 -0
  54. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_routes_websocket.py +0 -0
  55. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_segmentation.py +0 -0
  56. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_server.py +0 -0
  57. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_session_spec.py +0 -0
  58. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_shell.html +0 -0
  59. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_synthetic_mri.py +0 -0
  60. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_torch.py +0 -0
  61. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_tutorial.py +0 -0
  62. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_vectorfield.py +0 -0
  63. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_vscode.py +0 -0
  64. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/_vscode_browser.py +0 -0
  65. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/gsap.min.js +0 -0
  66. {arrayview-0.41.0 → arrayview-0.41.1}/src/arrayview/todo.md +0 -0
  67. {arrayview-0.41.0 → arrayview-0.41.1}/tests/capture_patient_loading.py +0 -0
  68. {arrayview-0.41.0 → arrayview-0.41.1}/tests/capture_v_animation.py +0 -0
  69. {arrayview-0.41.0 → arrayview-0.41.1}/tests/conftest.py +0 -0
  70. {arrayview-0.41.0 → arrayview-0.41.1}/tests/lifecycle_matrix.py +0 -0
  71. {arrayview-0.41.0 → arrayview-0.41.1}/tests/make_vectorfield_test_arrays.py +0 -0
  72. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_1d_arrays.py +0 -0
  73. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_backend_shared.py +0 -0
  74. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_cli.py +0 -0
  75. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_colorbar_hover_highlight.py +0 -0
  76. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_config.py +0 -0
  77. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_dicom.py +0 -0
  78. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_display_opener_results.py +0 -0
  79. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_doctor_commands.py +0 -0
  80. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_drop_import.py +0 -0
  81. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_instance_registry.py +0 -0
  82. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_invocation_contract_matrix.py +0 -0
  83. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_io.py +0 -0
  84. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_large_arrays.py +0 -0
  85. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_launch_diagnostics.py +0 -0
  86. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_launch_execution_ownership.py +0 -0
  87. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_launch_plan.py +0 -0
  88. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_launch_trace.py +0 -0
  89. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_loading_server.py +0 -0
  90. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_mode_consistency.py +0 -0
  91. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_mode_matrix.py +0 -0
  92. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_mode_roundtrip.py +0 -0
  93. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_nifti_series.py +0 -0
  94. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_preferences_ui.py +0 -0
  95. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_public_invocation_regressions.py +0 -0
  96. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_render_failure_reporting.py +0 -0
  97. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_render_progress.py +0 -0
  98. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_rgb_pixel_art.py +0 -0
  99. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_safe_server_stop.py +0 -0
  100. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_server_identity_status.py +0 -0
  101. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_session_spec.py +0 -0
  102. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_spawn_ownership.py +0 -0
  103. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_torch.py +0 -0
  104. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_tutorial.py +0 -0
  105. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_tutorial_browser.py +0 -0
  106. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_value_formatting.py +0 -0
  107. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_view_handle_lifecycle.py +0 -0
  108. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_view_launch_execution.py +0 -0
  109. {arrayview-0.41.0 → arrayview-0.41.1}/tests/test_window_level_preview.py +0 -0
  110. {arrayview-0.41.0 → arrayview-0.41.1}/tests/ui_audit.py +0 -0
  111. {arrayview-0.41.0 → arrayview-0.41.1}/tests/v_anim_frames/.gitkeep +0 -0
  112. {arrayview-0.41.0 → arrayview-0.41.1}/tests/visual_smoke.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arrayview
3
- Version: 0.41.0
3
+ Version: 0.41.1
4
4
  Summary: Fast multi-dimensional array viewer
5
5
  Project-URL: Home, https://github.com/oscarvanderheide/arrayview
6
6
  Project-URL: Source, https://github.com/oscarvanderheide/arrayview
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "arrayview"
7
- version = "0.41.0"
7
+ version = "0.41.1"
8
8
  description = "Fast multi-dimensional array viewer"
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  requires-python = ">=3.12"
@@ -63,6 +63,7 @@ test = [
63
63
  "httpx>=0.27",
64
64
  "pytest-playwright>=0.5",
65
65
  "pillow>=12.1.1",
66
+ "nbclassic>=1.3,<2",
66
67
  ]
67
68
  docs = [
68
69
  "mkdocs>=1.6",
@@ -51,6 +51,7 @@ def _build_metadata(session) -> dict:
51
51
  "voxel_sizes": list(sm["voxel_sizes"]),
52
52
  "axis_labels": list(sm["axis_labels"]),
53
53
  "is_oblique": bool(sm["is_oblique"]),
54
+ "is_canonical": bool(sm.get("is_canonical", True)),
54
55
  }
55
56
  meta["ras_resample_active"] = bool(
56
57
  getattr(session, "ras_resample_active", False)
@@ -139,8 +139,13 @@ def _select_npz_array(npz, filepath):
139
139
  return npz[keys[0]]
140
140
 
141
141
 
142
- def _nifti_header_with_meta(filepath):
143
- """Return a NIfTI image and canonical spatial metadata without reading voxels."""
142
+ def _nifti_header_with_meta(filepath, *, native=False):
143
+ """Return a NIfTI image and spatial metadata without reading voxels.
144
+
145
+ Directory collections use canonical metadata because their lazy series
146
+ objects expose canonical axes. A single volume uses native metadata so an
147
+ uncompressed NIfTI can keep its proxy and storage-friendly axis order.
148
+ """
144
149
  nib = _nib()
145
150
  img = nib.load(filepath)
146
151
  original_affine = np.asarray(img.affine, dtype=np.float64)
@@ -151,30 +156,40 @@ def _nifti_header_with_meta(filepath):
151
156
  transform, img.shape[:3]
152
157
  )
153
158
 
154
- rot = affine_canonical[:3, :3]
159
+ data_affine = original_affine if native else affine_canonical
160
+ rot = data_affine[:3, :3]
155
161
  voxel_sizes = tuple(float(np.linalg.norm(rot[:, i])) for i in range(3))
162
+ canonical_rot = affine_canonical[:3, :3]
163
+ canonical_voxel_sizes = tuple(
164
+ float(np.linalg.norm(canonical_rot[:, i])) for i in range(3)
165
+ )
156
166
  norm_rot = np.zeros((3, 3))
157
167
  for i in range(3):
158
- if voxel_sizes[i] > 0:
159
- norm_rot[:, i] = rot[:, i] / voxel_sizes[i]
160
- pos_labels = ("R", "A", "S")
161
- neg_labels = ("L", "P", "I")
162
- axis_labels = tuple(
163
- pos_labels[i] if norm_rot[i, i] >= 0 else neg_labels[i] for i in range(3)
164
- )
168
+ if canonical_voxel_sizes[i] > 0:
169
+ norm_rot[:, i] = canonical_rot[:, i] / canonical_voxel_sizes[i]
170
+ axis_labels = tuple(str(v) for v in nib.aff2axcodes(data_affine))
165
171
  off_diag_max = max(
166
172
  (abs(norm_rot[i, j]) for i in range(3) for j in range(3) if i != j),
167
173
  default=0.0,
168
174
  )
169
175
  canonical_shape = tuple(int(img.shape[int(axis)]) for axis in transform[:, 0])
170
- return img, {
176
+ meta = {
171
177
  "affine": original_affine,
172
178
  "affine_canonical": affine_canonical,
179
+ "data_affine": data_affine,
173
180
  "voxel_sizes": voxel_sizes,
174
181
  "axis_labels": axis_labels,
175
182
  "is_oblique": bool(off_diag_max > 1e-3),
176
- "canonical_shape": canonical_shape,
183
+ "is_canonical": bool(
184
+ np.array_equal(transform, np.asarray([[0, 1], [1, 1], [2, 1]]))
185
+ ),
186
+ "to_canonical": transform,
177
187
  }
188
+ if native:
189
+ meta["spatial_shape"] = tuple(int(v) for v in img.shape[:3])
190
+ else:
191
+ meta["canonical_shape"] = canonical_shape
192
+ return img, meta
178
193
 
179
194
 
180
195
  def _nifti_display_array(proxy):
@@ -373,25 +388,19 @@ def _get_progressive_nifti_pool(*, overlay_prefetch=False):
373
388
 
374
389
 
375
390
  def _load_nifti_with_meta(filepath):
376
- """Load a NIfTI file, canonical-reorient, return (array, meta).
377
-
378
- meta is a dict with keys:
379
- affine : original 4x4 affine (RAS+ mm)
380
- affine_canonical : 4x4 affine after as_closest_canonical
381
- voxel_sizes : tuple (sx, sy, sz) in mm, post-reorient
382
- axis_labels : tuple of 3 strs from {"R","L","A","P","S","I"}
383
- — positive direction of each canonical axis
384
- is_oblique : bool — True if rotation part has off-diagonal magnitude > 1e-3
385
- after normalizing voxel sizes
386
- """
387
- img, meta = _nifti_header_with_meta(filepath)
388
- canon = _nib().as_closest_canonical(img)
389
-
390
- # NOTE: reorient requires materializing axis permutes/flips. .nii.gz is
391
- # already eager (gzip not seekable), so this is free; .nii loses mmap as a
392
- # necessary cost to apply the reorient.
393
- arr = np.asarray(canon.dataobj)
391
+ """Load a NIfTI file in native voxel order and return (array, meta).
394
392
 
393
+ Uncompressed .nii stays proxy-backed. This preserves cheap native-plane
394
+ reads even when the affine describes permuted or flipped anatomical axes.
395
+ Compressed .nii.gz still has to be decoded eagerly because gzip is not
396
+ random-access, but it is no longer silently reordered.
397
+ """
398
+ img, meta = _nifti_header_with_meta(filepath, native=True)
399
+ arr = (
400
+ img.dataobj
401
+ if filepath.endswith(".nii")
402
+ else _nifti_display_array(img.dataobj)
403
+ )
395
404
  return arr, meta
396
405
 
397
406