Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
20 changes: 20 additions & 0 deletions docs/api/show4dstem.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
47 changes: 47 additions & 0 deletions src/quantem/widget/show4dstem.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
44 changes: 44 additions & 0 deletions tests/test_show4dstem_handoff.py
Original file line number Diff line number Diff line change
@@ -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