de-ebsd 0.2.0__py3-none-any.whl

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 (69) hide show
  1. de_ebsd/__init__.py +76 -0
  2. de_ebsd/core/__init__.py +23 -0
  3. de_ebsd/core/constants.py +81 -0
  4. de_ebsd/core/device.py +12 -0
  5. de_ebsd/core/geometry.py +67 -0
  6. de_ebsd/core/lambert.py +148 -0
  7. de_ebsd/core/rotation.py +139 -0
  8. de_ebsd/crystal/__init__.py +22 -0
  9. de_ebsd/crystal/data/__init__.py +0 -0
  10. de_ebsd/crystal/data/element_properties.py +29 -0
  11. de_ebsd/crystal/data/scattering_tables.py +414 -0
  12. de_ebsd/crystal/lattice.py +517 -0
  13. de_ebsd/crystal/scattering.py +339 -0
  14. de_ebsd/crystal/scattering_torch.py +233 -0
  15. de_ebsd/crystal/structure_factors.py +291 -0
  16. de_ebsd/diffraction/__init__.py +45 -0
  17. de_ebsd/diffraction/backends.py +318 -0
  18. de_ebsd/diffraction/bethe.py +28 -0
  19. de_ebsd/diffraction/kinematic.py +389 -0
  20. de_ebsd/diffraction/kvectors.py +120 -0
  21. de_ebsd/diffraction/prism.py +204 -0
  22. de_ebsd/diffraction/reflections.py +234 -0
  23. de_ebsd/index/__init__.py +61 -0
  24. de_ebsd/index/_kernels.py +222 -0
  25. de_ebsd/index/backproject.py +171 -0
  26. de_ebsd/index/crosscorr.py +654 -0
  27. de_ebsd/index/dictionary.py +362 -0
  28. de_ebsd/index/io.py +324 -0
  29. de_ebsd/index/live/__init__.py +5 -0
  30. de_ebsd/index/live/background.py +103 -0
  31. de_ebsd/index/live/calibration.py +70 -0
  32. de_ebsd/index/live/common.py +44 -0
  33. de_ebsd/index/live/deformation.py +289 -0
  34. de_ebsd/index/live/indexer.py +386 -0
  35. de_ebsd/index/preprocess.py +195 -0
  36. de_ebsd/index/refine/__init__.py +7 -0
  37. de_ebsd/index/refine/highres.py +479 -0
  38. de_ebsd/index/refine/levenberg.py +33 -0
  39. de_ebsd/index/refine/orientation.py +367 -0
  40. de_ebsd/index/refine/pattern_center.py +356 -0
  41. de_ebsd/index/spherical/__init__.py +41 -0
  42. de_ebsd/index/spherical/correlation.py +106 -0
  43. de_ebsd/index/spherical/harmonics.py +311 -0
  44. de_ebsd/index/spherical/indexer.py +345 -0
  45. de_ebsd/index/spherical/wigner.py +190 -0
  46. de_ebsd/sim/__init__.py +20 -0
  47. de_ebsd/sim/detector.py +145 -0
  48. de_ebsd/sim/masterpattern.py +231 -0
  49. de_ebsd/sim/microscope.py +93 -0
  50. de_ebsd/sim/montecarlo/__init__.py +19 -0
  51. de_ebsd/sim/montecarlo/depth.py +167 -0
  52. de_ebsd/sim/montecarlo/transport.py +532 -0
  53. de_ebsd/sim/simulator.py +291 -0
  54. de_ebsd/viz/__init__.py +53 -0
  55. de_ebsd/viz/camera.py +56 -0
  56. de_ebsd/viz/depth_widget.js +76 -0
  57. de_ebsd/viz/explorer.py +647 -0
  58. de_ebsd/viz/figures.py +67 -0
  59. de_ebsd/viz/maps.py +277 -0
  60. de_ebsd/viz/patterns.py +164 -0
  61. de_ebsd/viz/setup_widget.js +171 -0
  62. de_ebsd/viz/views.py +125 -0
  63. de_ebsd/viz/widgets.py +191 -0
  64. de_ebsd-0.2.0.dist-info/METADATA +251 -0
  65. de_ebsd-0.2.0.dist-info/RECORD +69 -0
  66. de_ebsd-0.2.0.dist-info/WHEEL +5 -0
  67. de_ebsd-0.2.0.dist-info/licenses/LICENSE +674 -0
  68. de_ebsd-0.2.0.dist-info/licenses/NOTICE +44 -0
  69. de_ebsd-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,19 @@
1
+ """Monte Carlo electron transport and the exit-depth statistics it produces."""
2
+
3
+ from de_ebsd.sim.montecarlo.depth import DepthDistribution
4
+ from de_ebsd.sim.montecarlo.transport import (
5
+ MaterialProperties,
6
+ TrajectoryResult,
7
+ simulate_depth_distribution,
8
+ simulate_trajectory,
9
+ soft_depth_histogram,
10
+ )
11
+
12
+ __all__ = [
13
+ "DepthDistribution",
14
+ "MaterialProperties",
15
+ "TrajectoryResult",
16
+ "simulate_depth_distribution",
17
+ "simulate_trajectory",
18
+ "soft_depth_histogram",
19
+ ]
@@ -0,0 +1,167 @@
1
+ """Backscatter yield versus energy and exit depth.
2
+
3
+ Loads EMsoft ``EMMCOpenCL`` statistics or builds them natively, and derives the
4
+ two quantities the master pattern needs: the 99% integration depth per energy
5
+ bin (EMsoft ``izzmax``) and the depth weighting ``lambda_E(z)``, corrected for
6
+ the normal absorption already present in the Bloch eigenvalues.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+
13
+ import h5py
14
+ import numpy as np
15
+
16
+ __all__ = ["DepthDistribution"]
17
+
18
+
19
+ @dataclass
20
+ class DepthDistribution:
21
+ """Backscatter yield versus energy and exit depth.
22
+
23
+ Attributes
24
+ ----------
25
+ energies_kev
26
+ Energy bin centers in keV, ascending, shape (nE,).
27
+ depth_step
28
+ Depth bin size in nm.
29
+ accum_z
30
+ Electron counts per (energy, depth, y, x), shape (nE, nz, ny, nx);
31
+ the lateral window is EMsoft's central +/- numsx/10 region.
32
+ n_total
33
+ Total number of incident electrons (totnum_el * multiplier).
34
+ """
35
+
36
+ energies_kev: np.ndarray
37
+ depth_step: float
38
+ accum_z: np.ndarray
39
+ n_total: float
40
+
41
+ @classmethod
42
+ def from_emsoft(cls, path: str) -> DepthDistribution:
43
+ """Load from an EMsoft Monte Carlo (or master pattern) HDF5 file."""
44
+ with h5py.File(path, "r") as f:
45
+ mc = f["EMData/MCOpenCL"]
46
+ nml = f["NMLparameters/MCCLNameList"]
47
+ # EMsoft writes Fortran order (nE, nz, nx, ny); h5py presents the
48
+ # reversed C order (ny, nx, nz, nE).
49
+ accum_z = np.asarray(mc["accum_z"]).transpose(3, 2, 1, 0).astype(np.float64)
50
+ e_min = float(nml["Ehistmin"][0])
51
+ e_bin = float(nml["Ebinsize"][0])
52
+ n_e = accum_z.shape[0]
53
+ energies = e_min + e_bin * np.arange(n_e)
54
+ depth_step = float(nml["depthstep"][0])
55
+ multiplier = float(np.asarray(mc.get("multiplier", [1]))[0])
56
+ n_total = float(np.asarray(mc["totnum_el"])[0]) * multiplier
57
+ return cls(
58
+ energies_kev=energies,
59
+ depth_step=depth_step,
60
+ accum_z=accum_z,
61
+ n_total=n_total,
62
+ )
63
+
64
+ @classmethod
65
+ def single_energy(
66
+ cls,
67
+ energy_kev: float,
68
+ thickness: float = 100.0,
69
+ depth_step: float = 1.0,
70
+ decay_length: float = 30.0,
71
+ ) -> DepthDistribution:
72
+ """A simple exponential exit-depth model for one energy bin.
73
+
74
+ A coarse stand-in that reproduces band *geometry* but not the full
75
+ dynamical depth contrast. Superseded as the standalone default by the
76
+ native Monte Carlo (:meth:`from_simulation`), which the simulator now
77
+ builds automatically when no depth profile is supplied; this remains a
78
+ fast zero-config fallback for direct backend use.
79
+ """
80
+ n_z = int(round(thickness / depth_step))
81
+ z = depth_step * np.arange(n_z)
82
+ profile = np.exp(-z / decay_length)
83
+ return cls(
84
+ energies_kev=np.array([energy_kev]),
85
+ depth_step=depth_step,
86
+ accum_z=profile[None, :, None, None],
87
+ n_total=float(profile.sum()),
88
+ )
89
+
90
+ @classmethod
91
+ def from_simulation(cls, material, beam_energy_kev: float, **kwargs) -> DepthDistribution:
92
+ """Build depth statistics from the native Monte Carlo (EMsoft-free).
93
+
94
+ Delegates to :func:`de_ebsd.sim.montecarlo.transport.simulate_depth_distribution`
95
+ (see it for the keyword arguments); ``material`` is a
96
+ :class:`~de_ebsd.crystal.lattice.Crystal` or a ``MaterialProperties``.
97
+ """
98
+ from de_ebsd.sim.montecarlo.transport import simulate_depth_distribution
99
+
100
+ return simulate_depth_distribution(material, beam_energy_kev, **kwargs)
101
+
102
+ @property
103
+ def n_energies(self) -> int:
104
+ return len(self.energies_kev)
105
+
106
+ @property
107
+ def n_depths(self) -> int:
108
+ return self.accum_z.shape[1]
109
+
110
+ def energy_index(self, energy_kev: float) -> int:
111
+ """Index of the energy bin closest to ``energy_kev``."""
112
+ return int(np.argmin(np.abs(self.energies_kev - energy_kev)))
113
+
114
+ def integration_depths(self, fraction: float = 0.99) -> np.ndarray:
115
+ """Number of depth bins to integrate per energy (EMsoft ``izzmax``).
116
+
117
+ For each lateral pixel the depth reaching ``fraction`` of that
118
+ pixel's yield is found; the maximum over pixels (cumulative over
119
+ energies, as in EMsoft) is returned per energy bin.
120
+ """
121
+ n_e, n_z = self.n_energies, self.n_depths
122
+ izz = np.zeros(n_e, dtype=np.int64)
123
+ running_max = 0
124
+ for i_e in range(n_e):
125
+ counts = self.accum_z[i_e] # (nz, ny, nx)
126
+ total = counts.sum(axis=0)
127
+ cum = np.cumsum(counts, axis=0)
128
+ need = fraction * total
129
+ # first depth index where the cumulative count reaches the target
130
+ reached = cum >= need[None, :, :]
131
+ first = np.where(reached.any(axis=0), reached.argmax(axis=0), n_z - 1)
132
+ running_max = max(running_max, int(first[total > 0].max(initial=0)) + 1)
133
+ izz[i_e] = running_max
134
+ return izz
135
+
136
+ def depth_weights(self, energy_kev: float, normal_absorption_length: float) -> np.ndarray:
137
+ """The weighting lambda_E(z) for one energy bin (EMsoft ``lambdaE``).
138
+
139
+ The lateral-summed exit fraction per depth bin, multiplied by
140
+ ``exp(+2 pi z / x_gp)`` to cancel the normal absorption that the
141
+ Bloch eigenvalues already contain.
142
+ """
143
+ # EMsoft's lateral window is -nsx/10:nsx/10 with nsx = (numsx-1)/2; for
144
+ # the stored accum_z that window spans its full lateral extent, so the
145
+ # full lateral sum is the correct match.
146
+ i_e = self.energy_index(energy_kev)
147
+ fraction = self.accum_z[i_e].sum(axis=(1, 2)) / self.n_total
148
+ z = self.depth_step * np.arange(self.n_depths)
149
+ return fraction * np.exp(2.0 * np.pi * z / normal_absorption_length)
150
+
151
+ def __repr__(self) -> str:
152
+ e = self.energies_kev
153
+ return (
154
+ f"DepthDistribution({self.n_energies} energy bins "
155
+ f"[{e.min():g}..{e.max():g} keV], {self.n_depths} depth bins x "
156
+ f"{self.depth_step:g} nm, {self.n_total:.3g} electrons)"
157
+ )
158
+
159
+ def show(self, energy_kev: float | None = None):
160
+ """Inspect exit-depth bins and the backend's 99% integration window.
161
+
162
+ Returns an energy/slice-selectable anywidget, also exportable to HTML.
163
+ Selection is inspection only: it does not truncate or mutate the model.
164
+ """
165
+ from de_ebsd.viz.widgets import DepthWidget
166
+
167
+ return DepthWidget.from_distribution(self, energy_kev=energy_kev)