midas-diffract 0.1.1__tar.gz → 0.2.0__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 (25) hide show
  1. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/PKG-INFO +3 -2
  2. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/README.md +1 -1
  3. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/midas_diffract/__init__.py +1 -1
  4. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/midas_diffract/forward.py +60 -0
  5. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/midas_diffract.egg-info/PKG-INFO +3 -2
  6. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/midas_diffract.egg-info/SOURCES.txt +1 -0
  7. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/midas_diffract.egg-info/requires.txt +1 -0
  8. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/pyproject.toml +2 -1
  9. midas_diffract-0.2.0/tests/test_distortion_layer.py +78 -0
  10. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/LICENSE +0 -0
  11. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/midas_diffract/hkls.py +0 -0
  12. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/midas_diffract/losses.py +0 -0
  13. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/midas_diffract/optimize.py +0 -0
  14. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/midas_diffract/simulate_panel_zarrs.py +0 -0
  15. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/midas_diffract.egg-info/dependency_links.txt +0 -0
  16. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/midas_diffract.egg-info/top_level.txt +0 -0
  17. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/setup.cfg +0 -0
  18. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/tests/test_c_comparison.py +0 -0
  19. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/tests/test_forward.py +0 -0
  20. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/tests/test_hkls.py +0 -0
  21. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/tests/test_losses.py +0 -0
  22. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/tests/test_multi_detector.py +0 -0
  23. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/tests/test_strain_tensor.py +0 -0
  24. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/tests/test_tilts.py +0 -0
  25. {midas_diffract-0.1.1 → midas_diffract-0.2.0}/tests/test_wedge.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: midas-diffract
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: End-to-end differentiable forward model for High-Energy Diffraction Microscopy (FF, NF, pf-HEDM)
5
5
  Author-email: Hemant Sharma <hsharma@anl.gov>
6
6
  License-Expression: BSD-3-Clause
@@ -18,6 +18,7 @@ Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: numpy>=1.22
20
20
  Requires-Dist: torch>=2.0
21
+ Requires-Dist: midas-distortion>=0.2.0
21
22
  Provides-Extra: dev
22
23
  Requires-Dist: pytest>=7.0; extra == "dev"
23
24
  Requires-Dist: pytest-cov; extra == "dev"
@@ -102,7 +103,7 @@ MIDAS distribution. See the companion paper and the MIDAS repository
102
103
 
103
104
  ## Scope
104
105
 
105
- `midas-diffract` v0.1.0 is deliberately focused on the forward model and its
106
+ `midas-diffract` v0.1.x is deliberately focused on the forward model and its
106
107
  gradient chain. The following capabilities build on this substrate and are
107
108
  released separately as they mature:
108
109
 
@@ -75,7 +75,7 @@ MIDAS distribution. See the companion paper and the MIDAS repository
75
75
 
76
76
  ## Scope
77
77
 
78
- `midas-diffract` v0.1.0 is deliberately focused on the forward model and its
78
+ `midas-diffract` v0.1.x is deliberately focused on the forward model and its
79
79
  gradient chain. The following capabilities build on this substrate and are
80
80
  released separately as they mature:
81
81
 
@@ -28,7 +28,7 @@ Quick start
28
28
  loss.backward()
29
29
  """
30
30
 
31
- __version__ = "0.1.1"
31
+ __version__ = "0.2.0"
32
32
 
33
33
  from .forward import (
34
34
  HEDMForwardModel,
@@ -89,6 +89,16 @@ class HEDMGeometry:
89
89
  # Default False preserves the existing
90
90
  # behaviour: NF applies tilts, FF skips.
91
91
  # Set True for raw multi-panel simulation.
92
+ # Radial detector distortion (canonical midas_distortion v2 model). Like
93
+ # tilts, this maps an IDEAL prediction to the RAW detector position and is
94
+ # OFF by default -- the FF/pf experimental pipeline pre-corrects distortion
95
+ # at peak-finding time (transforms), so the forward must NOT re-apply it for
96
+ # the indexer/fit-grain. Raw-pixel-patch consumers (pf_odf, grain_odf) that
97
+ # never go through transforms set ``apply_distortion=True`` and supply the
98
+ # calibrated v2 coefficients to predict in the raw frame.
99
+ apply_distortion: bool = False
100
+ p_distortion: "list[float] | None" = None # 15 v2 coeffs (midas_distortion P_COEF_NAMES order); None/zeros => no-op
101
+ rho_d: "float | None" = None # distortion radius normalization (um); None => resolve from detector corner
92
102
  multi_mode: str = "layered" # "layered" (default): NF semantics --
93
103
  # spot must land on the detector at
94
104
  # EVERY distance (AllDistsFound).
@@ -342,6 +352,25 @@ class HEDMForwardModel(nn.Module):
342
352
 
343
353
  # Multi-detector / multi-panel configuration
344
354
  self.apply_tilts = bool(geometry.apply_tilts)
355
+
356
+ # Radial detector distortion (canonical midas_distortion v2 model),
357
+ # applied ideal->raw in project_to_detector when apply_distortion=True.
358
+ # OFF by default => identity => indexer/fit-grain output unchanged.
359
+ self.apply_distortion = bool(getattr(geometry, "apply_distortion", False))
360
+ p_dist = getattr(geometry, "p_distortion", None)
361
+ if p_dist is not None:
362
+ self.p_distortion = nn.Parameter(
363
+ torch.as_tensor(p_dist, dtype=torch.float64, device=device),
364
+ requires_grad=False,
365
+ )
366
+ self._has_distortion = bool(
367
+ torch.any(torch.abs(self.p_distortion.detach()) > 0.0).item()
368
+ )
369
+ else:
370
+ self.p_distortion = None
371
+ self._has_distortion = False
372
+ self.rho_d = getattr(geometry, "rho_d", None)
373
+
345
374
  if geometry.multi_mode not in ("layered", "panel"):
346
375
  raise ValueError(
347
376
  f"Unknown multi_mode {geometry.multi_mode!r}; "
@@ -1163,6 +1192,37 @@ class HEDMForwardModel(nn.Module):
1163
1192
  ydet_d = torch.stack(out_y, dim=0)
1164
1193
  zdet_d = torch.stack(out_z, dim=0)
1165
1194
 
1195
+ # Ideal->raw radial distortion (canonical midas_distortion v2 model),
1196
+ # applied on the BC-relative detector-plane coords (um) before pixel
1197
+ # conversion -- mirrors midas_calibrate_v2.forward.geometry. Gated OFF
1198
+ # by default so the indexer/fit-grain (ideal frame) are byte-unchanged;
1199
+ # raw-patch consumers (pf_odf, grain_odf) opt in. R is BC-relative, so
1200
+ # the distortion is frame-flip invariant; the eta convention (and any
1201
+ # phase offset between the calibration frame and this frame) is the one
1202
+ # thing to validate empirically (see implementation_plan_distortion_layer).
1203
+ if self.apply_distortion and self._has_distortion:
1204
+ from midas_distortion import apply_distortion as _apply_dist, \
1205
+ v2_term_layout as _v2_terms, resolve_rho_d_um as _resolve_rho_d
1206
+ eps = torch.tensor(1e-9, dtype=ydet_d.dtype, device=ydet_d.device)
1207
+ R = torch.sqrt(ydet_d * ydet_d + zdet_d * zdet_d).clamp(min=eps)
1208
+ # eta convention matches calibrate_v2 forward: atan2(-y, z), degrees.
1209
+ eta_deg_d = self.RAD2DEG * torch.atan2(-ydet_d, zdet_d)
1210
+ # resolve_rho_d_um passes a supplied rho_d through, or computes the
1211
+ # max BC-relative corner distance (um) when None.
1212
+ rho_d_val, _rho_how = _resolve_rho_d(
1213
+ self.rho_d,
1214
+ NrPixelsY=self.n_pixels_y, NrPixelsZ=self.n_pixels_z,
1215
+ BC_y=float(self._y_BC.reshape(-1)[0]),
1216
+ BC_z=float(self._z_BC.reshape(-1)[0]),
1217
+ pxY=self.px,
1218
+ )
1219
+ rho_d_t = torch.as_tensor(float(rho_d_val), dtype=R.dtype, device=R.device)
1220
+ p_v2 = self.p_distortion.to(R.dtype)
1221
+ R_corr = _apply_dist(R, eta_deg_d, p_v2, rho_d_t, terms=_v2_terms())
1222
+ scale = R_corr / R
1223
+ ydet_d = ydet_d * scale
1224
+ zdet_d = zdet_d * scale
1225
+
1166
1226
  # FF/PF: y-axis on detector flipped (yBC - ydet/px), validated against C
1167
1227
  # NF: not flipped (yBC + ydet/px), validated against C
1168
1228
  y_sign = -1.0 if self.flip_y else 1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: midas-diffract
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: End-to-end differentiable forward model for High-Energy Diffraction Microscopy (FF, NF, pf-HEDM)
5
5
  Author-email: Hemant Sharma <hsharma@anl.gov>
6
6
  License-Expression: BSD-3-Clause
@@ -18,6 +18,7 @@ Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: numpy>=1.22
20
20
  Requires-Dist: torch>=2.0
21
+ Requires-Dist: midas-distortion>=0.2.0
21
22
  Provides-Extra: dev
22
23
  Requires-Dist: pytest>=7.0; extra == "dev"
23
24
  Requires-Dist: pytest-cov; extra == "dev"
@@ -102,7 +103,7 @@ MIDAS distribution. See the companion paper and the MIDAS repository
102
103
 
103
104
  ## Scope
104
105
 
105
- `midas-diffract` v0.1.0 is deliberately focused on the forward model and its
106
+ `midas-diffract` v0.1.x is deliberately focused on the forward model and its
106
107
  gradient chain. The following capabilities build on this substrate and are
107
108
  released separately as they mature:
108
109
 
@@ -13,6 +13,7 @@ midas_diffract.egg-info/dependency_links.txt
13
13
  midas_diffract.egg-info/requires.txt
14
14
  midas_diffract.egg-info/top_level.txt
15
15
  tests/test_c_comparison.py
16
+ tests/test_distortion_layer.py
16
17
  tests/test_forward.py
17
18
  tests/test_hkls.py
18
19
  tests/test_losses.py
@@ -1,5 +1,6 @@
1
1
  numpy>=1.22
2
2
  torch>=2.0
3
+ midas-distortion>=0.2.0
3
4
 
4
5
  [dev]
5
6
  pytest>=7.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "midas-diffract"
7
- version = "0.1.1"
7
+ version = "0.2.0"
8
8
  description = "End-to-end differentiable forward model for High-Energy Diffraction Microscopy (FF, NF, pf-HEDM)"
9
9
  readme = "README.md"
10
10
  license = "BSD-3-Clause"
@@ -26,6 +26,7 @@ classifiers = [
26
26
  dependencies = [
27
27
  "numpy>=1.22",
28
28
  "torch>=2.0",
29
+ "midas-distortion>=0.2.0",
29
30
  ]
30
31
 
31
32
  [project.optional-dependencies]
@@ -0,0 +1,78 @@
1
+ """Tests for the gated ideal->raw radial-distortion layer (midas_distortion v2).
2
+
3
+ Guarantees:
4
+ 1. default (apply_distortion=False) is BYTE-IDENTICAL to the pre-layer forward
5
+ -- proves the indexer/fit-grain (ideal-frame) path is untouched.
6
+ 2. apply_distortion=True with zero coeffs is also identity.
7
+ 3. nonzero coeffs shift predicted spots (ideal->raw) by a sane, radius-growing
8
+ amount.
9
+ 4. the layer is differentiable w.r.t. the distortion coefficients.
10
+ """
11
+ import numpy as np
12
+ import pytest
13
+ import torch
14
+
15
+ from midas_diffract.forward import HEDMForwardModel, HEDMGeometry
16
+
17
+ _GD = dict(
18
+ Lsd=752000.0, y_BC=695.0, z_BC=874.0, px=172.0,
19
+ omega_start=180.0, omega_step=-0.25, n_frames=1440,
20
+ n_pixels_y=1679, n_pixels_z=1679, min_eta=6.0, wavelength=0.172979,
21
+ )
22
+ # A representative calibrated v2 coefficient set (Bucsek Pilatus 2M CeO2).
23
+ _COEFFS = [0.00707, -0.01, 0.00624, 0.01, -34.76, 0.00234, 81.47,
24
+ -0.00369, -12.29, -0.00727, -5.29, -0.00863, -1.51, -0.00446, -7.79]
25
+
26
+
27
+ def _model(**extra):
28
+ rng = np.random.default_rng(0)
29
+ hk = torch.tensor(rng.standard_normal((40, 3)), dtype=torch.float64)
30
+ th = torch.tensor(np.abs(rng.standard_normal(40)) * 0.04 + 0.04, dtype=torch.float64)
31
+ hi = torch.tensor(rng.integers(-3, 4, (40, 3)), dtype=torch.float64)
32
+ g = HEDMGeometry(**_GD, **extra)
33
+ return HEDMForwardModel(hk, th, g, hkls_int=hi).double()
34
+
35
+
36
+ _EUL = torch.tensor([[0.3, 0.5, 0.2]], dtype=torch.float64)
37
+ _POS = torch.zeros(1, 3, dtype=torch.float64)
38
+
39
+
40
+ def test_default_off_is_unchanged():
41
+ """No distortion fields == apply_distortion=False == zero coeffs (identity)."""
42
+ o0 = _model().forward(_EUL, _POS)
43
+ o_off = _model(apply_distortion=False, p_distortion=[0.0] * 15).forward(_EUL, _POS)
44
+ o_zero = _model(apply_distortion=True, p_distortion=[0.0] * 15, rho_d=2.0e5).forward(_EUL, _POS)
45
+ assert torch.equal(o0.y_pixel, o_off.y_pixel)
46
+ assert torch.equal(o0.z_pixel, o_off.z_pixel)
47
+ # zero coeffs with the layer ACTIVE must also be a no-op (D == 1).
48
+ assert torch.allclose(o0.y_pixel, o_zero.y_pixel, atol=1e-9)
49
+ assert torch.allclose(o0.z_pixel, o_zero.z_pixel, atol=1e-9)
50
+
51
+
52
+ def test_distortion_shifts_spots():
53
+ o0 = _model().forward(_EUL, _POS)
54
+ o2 = _model(apply_distortion=True, p_distortion=_COEFFS, rho_d=2.0e5).forward(_EUL, _POS)
55
+ vm = (o0.valid > 0.5) & (o2.valid > 0.5)
56
+ assert int(vm.sum()) > 5
57
+ dy = (o2.y_pixel - o0.y_pixel)[vm].abs()
58
+ assert float(dy.median()) > 0.05 # measurable
59
+ assert float(dy.max()) < 50.0 # but physical (sub-module)
60
+
61
+
62
+ def test_distortion_is_differentiable():
63
+ m = _model(apply_distortion=True, p_distortion=_COEFFS, rho_d=2.0e5)
64
+ m.p_distortion.requires_grad_(True)
65
+ out = m.forward(_EUL, _POS)
66
+ loss = (out.y_pixel * out.valid).sum() + (out.z_pixel * out.valid).sum()
67
+ loss.backward()
68
+ assert m.p_distortion.grad is not None
69
+ assert float(m.p_distortion.grad.norm()) > 0.0
70
+
71
+
72
+ @pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA not available")
73
+ def test_distortion_cpu_cuda_parity():
74
+ o_cpu = _model(apply_distortion=True, p_distortion=_COEFFS, rho_d=2.0e5).forward(_EUL, _POS)
75
+ m = _model(apply_distortion=True, p_distortion=_COEFFS, rho_d=2.0e5).to("cuda")
76
+ o_gpu = m.forward(_EUL.cuda(), _POS.cuda())
77
+ assert torch.allclose(o_cpu.y_pixel, o_gpu.y_pixel.cpu(), atol=1e-7)
78
+ assert torch.allclose(o_cpu.z_pixel, o_gpu.z_pixel.cpu(), atol=1e-7)
File without changes
File without changes