py4D-browser-transform 0.1.0__tar.gz → 0.1.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 (16) hide show
  1. {py4d_browser_transform-0.1.0/src/py4D_browser_transform.egg-info → py4d_browser_transform-0.1.1}/PKG-INFO +1 -1
  2. {py4d_browser_transform-0.1.0 → py4d_browser_transform-0.1.1/src/py4D_browser_transform.egg-info}/PKG-INFO +1 -1
  3. {py4d_browser_transform-0.1.0 → py4d_browser_transform-0.1.1}/src/py4d_browser_plugin/transform/__init__.py +1 -1
  4. {py4d_browser_transform-0.1.0 → py4d_browser_transform-0.1.1}/src/py4d_browser_plugin/transform/checkpoints.py +17 -4
  5. {py4d_browser_transform-0.1.0 → py4d_browser_transform-0.1.1}/src/py4d_browser_plugin/transform/datacube_ops.py +8 -4
  6. {py4d_browser_transform-0.1.0 → py4d_browser_transform-0.1.1}/tests/test_transform_datacube_updates.py +54 -1
  7. {py4d_browser_transform-0.1.0 → py4d_browser_transform-0.1.1}/LICENSE.txt +0 -0
  8. {py4d_browser_transform-0.1.0 → py4d_browser_transform-0.1.1}/README.md +0 -0
  9. {py4d_browser_transform-0.1.0 → py4d_browser_transform-0.1.1}/pyproject.toml +0 -0
  10. {py4d_browser_transform-0.1.0 → py4d_browser_transform-0.1.1}/setup.cfg +0 -0
  11. {py4d_browser_transform-0.1.0 → py4d_browser_transform-0.1.1}/src/py4D_browser_transform.egg-info/SOURCES.txt +0 -0
  12. {py4d_browser_transform-0.1.0 → py4d_browser_transform-0.1.1}/src/py4D_browser_transform.egg-info/dependency_links.txt +0 -0
  13. {py4d_browser_transform-0.1.0 → py4d_browser_transform-0.1.1}/src/py4D_browser_transform.egg-info/requires.txt +0 -0
  14. {py4d_browser_transform-0.1.0 → py4d_browser_transform-0.1.1}/src/py4D_browser_transform.egg-info/top_level.txt +0 -0
  15. {py4d_browser_transform-0.1.0 → py4d_browser_transform-0.1.1}/src/py4d_browser_plugin/transform/dialogs.py +0 -0
  16. {py4d_browser_transform-0.1.0 → py4d_browser_transform-0.1.1}/src/py4d_browser_plugin/transform/transform.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py4D-browser-transform
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: py4D-browser plugin that adds utility functions which transform the datacube
5
5
  Author-email: Chia-Hao Lee <chia-hao.lee@cornell.edu>
6
6
  Project-URL: Homepage, https://github.com/chiahao3/py4D-browser-transform
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py4D-browser-transform
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: py4D-browser plugin that adds utility functions which transform the datacube
5
5
  Author-email: Chia-Hao Lee <chia-hao.lee@cornell.edu>
6
6
  Project-URL: Homepage, https://github.com/chiahao3/py4D-browser-transform
@@ -1,2 +1,2 @@
1
1
  from .transform import TransformPlugin
2
- __version__ = '0.1.0' # 2026.05.23
2
+ __version__ = '0.1.1' # 2026.07.21
@@ -22,10 +22,23 @@ class DatacubeCheckpoint:
22
22
 
23
23
  def copy_datacube(datacube):
24
24
  if hasattr(datacube, "copy"):
25
- return datacube.copy()
26
- copied = type(datacube)(datacube.data.copy())
27
- if hasattr(datacube, "calibration"):
28
- copied.calibration = datacube.calibration.copy()
25
+ copied = datacube.copy()
26
+ else:
27
+ copied = type(datacube)(datacube.data.copy())
28
+ if hasattr(datacube, "calibration"):
29
+ copied.calibration = datacube.calibration.copy()
30
+
31
+ # py4DSTEM's DataCube.copy() rebuilds a fresh Root, and constructing a
32
+ # DataCube with an explicit calibration object names that root
33
+ # "py4DSTEM_root" instead of preserving the original's name. That
34
+ # silently breaks py4D-browser's calibration reload for any file whose
35
+ # root name it hardcodes (e.g. "datacube_root"), so keep the original
36
+ # root's name on the copy.
37
+ original_root = getattr(datacube, "root", None)
38
+ copied_root = getattr(copied, "root", None)
39
+ if original_root is not None and copied_root is not None:
40
+ copied_root.name = original_root.name
41
+
29
42
  return copied
30
43
 
31
44
 
@@ -71,20 +71,24 @@ def _update_transform_calibration(datacube, starts, spacing_factors):
71
71
  )
72
72
 
73
73
  q_factors = (spacing_factors[2], spacing_factors[3])
74
- old_q_pixel_size = calibration.get_Q_pixel_size()
75
74
  if q_factors[0] == q_factors[1] and q_factors[0] != 1:
75
+ old_q_pixel_size = calibration.get_Q_pixel_size()
76
76
  _set_calibration_value(
77
77
  calibration, "set_Q_pixel_size", old_q_pixel_size * q_factors[0]
78
78
  )
79
79
 
80
+ # The origin (qx0, qy0) is stored in *pixel* coordinates of the Q axes,
81
+ # not physical units, so it must be re-expressed in the transformed
82
+ # array's own pixel grid: shift by the crop start, then rescale by
83
+ # however many old pixels now make up one new pixel (step * bin).
80
84
  origin = calibration.get_origin() if hasattr(calibration, "get_origin") else None
81
- if origin is not None and old_q_pixel_size is not None:
85
+ if origin is not None:
82
86
  qx0, qy0 = origin
83
87
  if qx0 is not None and qy0 is not None:
84
88
  calibration.set_origin(
85
89
  (
86
- qx0 - starts[2] * old_q_pixel_size,
87
- qy0 - starts[3] * old_q_pixel_size,
90
+ (qx0 - starts[2]) / spacing_factors[2],
91
+ (qy0 - starts[3]) / spacing_factors[3],
88
92
  )
89
93
  )
90
94
 
@@ -319,10 +319,31 @@ def test_apply_datacube_operations_adjusts_q_origin_for_slices(monkeypatch):
319
319
  ],
320
320
  )
321
321
 
322
- assert transformed.calibration.get_origin() == (9.0, 18.0)
322
+ assert transformed.calibration.get_origin() == (8.0, 16.0)
323
323
  assert datacube.calibration.get_origin() == (10.0, 20.0)
324
324
 
325
325
 
326
+ def test_apply_datacube_operations_adjusts_q_origin_for_binning(monkeypatch):
327
+ module = _load_datacube_ops_module(monkeypatch)
328
+ data = np.arange(2 * 2 * 8 * 8).reshape(2, 2, 8, 8)
329
+ datacube = _Datacube(data.copy(), _Calibration(q_pixel_size=0.5, origin=(6.0, 8.0)))
330
+
331
+ transformed = module.apply_datacube_operations(
332
+ datacube,
333
+ [
334
+ {"slice": ":", "bin": 1},
335
+ {"slice": ":", "bin": 1},
336
+ {"slice": "2:", "bin": 2},
337
+ {"slice": "0:", "bin": 2},
338
+ ],
339
+ )
340
+
341
+ # New origin = (old_origin - crop_start) / (step * bin), in the
342
+ # transformed array's own pixel grid.
343
+ assert transformed.calibration.get_origin() == (2.0, 4.0)
344
+ assert datacube.calibration.get_origin() == (6.0, 8.0)
345
+
346
+
326
347
  def test_apply_datacube_operations_preserves_pixel_size_for_asymmetric_spacing(monkeypatch):
327
348
  module = _load_datacube_ops_module(monkeypatch)
328
349
  data = np.arange(4 * 4 * 6 * 6).reshape(4, 4, 6, 6)
@@ -342,6 +363,38 @@ def test_apply_datacube_operations_preserves_pixel_size_for_asymmetric_spacing(m
342
363
  assert transformed.calibration.get_Q_pixel_size() == 3
343
364
 
344
365
 
366
+ class _Root:
367
+ def __init__(self, name):
368
+ self.name = name
369
+
370
+
371
+ class _DatacubeWithRoot(_Datacube):
372
+ def __init__(self, data, calibration=None, root_name="datacube_root"):
373
+ super().__init__(data, calibration)
374
+ self.root = _Root(root_name)
375
+
376
+ def copy(self):
377
+ # Mimic py4DSTEM's DataCube.copy(): rebuilding via the constructor
378
+ # gives the new object a differently-named root.
379
+ copied = _DatacubeWithRoot(
380
+ self.data.copy(), self.calibration.copy(), root_name="py4DSTEM_root"
381
+ )
382
+ return copied
383
+
384
+
385
+ def test_copy_datacube_preserves_original_root_name(monkeypatch):
386
+ _install_pyqt_stubs(monkeypatch)
387
+ module = importlib.import_module("py4d_browser_plugin.transform.checkpoints")
388
+
389
+ datacube = _DatacubeWithRoot(np.arange(2 * 2 * 2 * 2).reshape(2, 2, 2, 2))
390
+ assert datacube.root.name == "datacube_root"
391
+
392
+ copied = module.copy_datacube(datacube)
393
+
394
+ assert copied.root.name == "datacube_root"
395
+ assert datacube.root.name == "datacube_root"
396
+
397
+
345
398
  def test_apply_datacube_operations_invalid_input_does_not_mutate(monkeypatch):
346
399
  module = _load_datacube_ops_module(monkeypatch)
347
400
  data = np.arange(2 * 3 * 4 * 5).reshape(2, 3, 4, 5)