diff --git a/CHANGELOG.md b/CHANGELOG.md index 79fbbf37..2a4a4236 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ new `rcN` heading when that rc is published to TestPyPI. ## Unreleased +- Show4DSTEM `to_showdiffraction` hands the current or mean diffraction + pattern to a ShowDiffraction widget, transferring the 1/Å calibration and + passing constructor keyword arguments through. - Add `ChooseLattice`, an interactive 2D selector for choosing an ordered origin, a1, and a2 and exposing their `(row, col)` coordinates and derived lattice vectors for downstream analysis. diff --git a/docs/api/show4dstem.md b/docs/api/show4dstem.md index c008d58c..12f7e1f3 100644 --- a/docs/api/show4dstem.md +++ b/docs/api/show4dstem.md @@ -607,6 +607,26 @@ For performance signoff, inspect `window.__loadprof` after load and `window.__sh4dLiveViStats` while dragging the virtual detector. The maintainer performance notes record the current seven-panel WebGPU compare-grid signoff. +## Handing a pattern to ShowDiffraction + +`to_showdiffraction` moves one diffraction pattern into a ShowDiffraction widget +for indexing, ring fitting, and spot detection. Use `source="current"` for the +pattern at the displayed scan position, or `source="mean"` for the mean over the +whole scan: + +```python +widget = Show4DSTEM(data) +widget.position = (12, 34) + +sd = widget.to_showdiffraction() +mean = widget.to_showdiffraction(source="mean", dp_scale_mode="linear") +``` + +When the viewer carries a 1/Å calibration, `k_pixel_size` transfers so the new +widget reports calibrated distances immediately. The `title` carries over too. +Pass either explicitly to override, along with any other ShowDiffraction +constructor option. + ## Reference ```{eval-rst} diff --git a/src/quantem/widget/show4dstem.py b/src/quantem/widget/show4dstem.py index c8d5430b..775c7408 100644 --- a/src/quantem/widget/show4dstem.py +++ b/src/quantem/widget/show4dstem.py @@ -5708,6 +5708,53 @@ def _update_frame(self, change=None): # avoiding 4 sync trait round-trips per scan-position click). self.frame_bytes = payload + def to_showdiffraction(self, source: str = "current", **kwargs) -> "ShowDiffraction": + """ + Hand a diffraction pattern to a ShowDiffraction widget. + + The 1/Å calibration carries over when this widget is calibrated, so the + new widget opens ready to measure instead of needing manual setup. + + Parameters + ---------- + source : str, default "current" + ``"current"`` sends the pattern at the displayed scan position, + ``"mean"`` sends the mean pattern over the whole scan. + **kwargs + Passed to the ShowDiffraction constructor. ``k_pixel_size`` and + ``title`` are filled from this widget unless given here. + + Returns + ------- + ShowDiffraction + Widget holding the selected pattern. + + Raises + ------ + ValueError + If ``source`` is neither ``"current"`` nor ``"mean"``. + + Examples + -------- + >>> widget.to_showdiffraction() + >>> widget.to_showdiffraction(source="mean", dp_scale_mode="linear") + """ + from quantem.widget.showdiffraction import ShowDiffraction + + if source == "current": + frame = self._diffraction_frame_as_numpy( + self._diffraction_frame_for_index(int(self.frame_idx)) + ) + elif source == "mean": + frame = np.asarray(self._compute.mean_dp(), dtype=np.float32) + else: + raise ValueError(f"source must be current or mean, got {source!r}") + + if self.k_pixel_unit in ("1/Å", "1/A") and "k_pixel_size" not in kwargs: + kwargs["k_pixel_size"] = float(self.k_pixel_size) + kwargs.setdefault("title", self.title) + return ShowDiffraction(frame, **kwargs) + def _diffraction_frame_for_index(self, frame_idx: int): """Return one diffraction pattern at the current scan position.""" data_source = getattr(self, "_data", None) diff --git a/tests/test_show4dstem_handoff.py b/tests/test_show4dstem_handoff.py new file mode 100644 index 00000000..5567a6c0 --- /dev/null +++ b/tests/test_show4dstem_handoff.py @@ -0,0 +1,44 @@ +"""Show4DSTEM to ShowDiffraction handoff.""" + +import numpy as np +import pytest + +from quantem.widget import Show4DSTEM +from quantem.widget.showdiffraction import ShowDiffraction + + +def _scan(seed=0): + rng = np.random.default_rng(seed) + data = rng.poisson(2.0, (3, 4, 32, 32)).astype(np.float32) + data[:, :, 12:16, 20:24] += 40.0 + return data + + +def test_handoff_current_and_mean(): + data = _scan() + w = Show4DSTEM(data, verbose=False) + w.pos_row, w.pos_col = 1, 2 + + current = w.to_showdiffraction(verbose=False) + assert isinstance(current, ShowDiffraction) + assert np.allclose(current._displayed_frame(), data[1, 2]) + + mean = w.to_showdiffraction(source="mean", verbose=False) + assert np.allclose(mean._displayed_frame(), data.reshape(-1, 32, 32).mean(axis=0), atol=1e-4) + + with pytest.raises(ValueError): + w.to_showdiffraction(source="sum") + + +def test_handoff_transfers_calibration_and_kwargs(): + w = Show4DSTEM(_scan(), verbose=False) + w.k_pixel_unit = "1/Å" + w.k_pixel_size = 0.012 + + sd = w.to_showdiffraction(dp_scale_mode="linear", verbose=False) + assert sd.k_pixel_size == pytest.approx(0.012) + assert sd.k_calibrated + assert sd.dp_scale_mode == "linear" + + uncal = Show4DSTEM(_scan(), verbose=False).to_showdiffraction(verbose=False) + assert not uncal.k_calibrated