Skip to content
Open
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
12 changes: 12 additions & 0 deletions js/showdiffraction/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,9 @@ function ShowDiffraction() {
const [, setRingRemoveRequest] = useModelState<number>("_ring_remove_request");
const [dpColormap, setDpColormap] = useModelState<string>("dp_colormap");
const [dpScaleMode, setDpScaleMode] = useModelState<string>("dp_scale_mode");
const [displayDenoise, setDisplayDenoise] = useModelState<string>("denoise");
const [detectDenoise, setDetectDenoise] = useModelState<string>("detect_denoise");
const [showDetectionView, setShowDetectionView] = useModelState<boolean>("show_detection_view");
const [dpInvert, setDpInvert] = useModelState<boolean>("dp_invert");
const [dpVminPct, setDpVminPct] = useModelState<number>("dp_vmin_pct");
const [dpVmaxPct, setDpVmaxPct] = useModelState<number>("dp_vmax_pct");
Expand Down Expand Up @@ -1732,6 +1735,14 @@ function ShowDiffraction() {
<MenuItem value="log" sx={{ fontSize: 10 }}>Log</MenuItem>
<MenuItem value="sqrt" sx={{ fontSize: 10 }}>Sqrt</MenuItem>
</Select>
<Typography sx={{ ...typography.label, fontSize: 10 }}>Denoise</Typography>
<Select size="small" value={displayDenoise} onChange={(e) => setDisplayDenoise(String(e.target.value))} sx={{ ...themedSelect, minWidth: 80 }} MenuProps={topToolbarMenuProps} title="Display-only denoise">
{["none", "gaussian", "anscombe", "nlm", "tv"].map(n => <MenuItem key={n} value={n} sx={{ fontSize: 10 }}>{n}</MenuItem>)}
</Select>
<Typography sx={{ ...typography.label, fontSize: 10 }}>Detect</Typography>
<Select size="small" value={detectDenoise} onChange={(e) => setDetectDenoise(String(e.target.value))} sx={{ ...themedSelect, minWidth: 70 }} MenuProps={topToolbarMenuProps} title="Detection preprocessing">
{["auto", "none", "gaussian", "anscombe"].map(n => <MenuItem key={n} value={n} sx={{ fontSize: 10 }}>{n}</MenuItem>)}
</Select>
{centerMode === "manual" && (
<Typography sx={{ ...typography.value, color: themeColors.accent }}>click to set</Typography>
)}
Expand Down Expand Up @@ -1891,6 +1902,7 @@ function ShowDiffraction() {
["Azim", "Azimuthal profile", showAzimuthal, () => setShowAzimuthal(!showAzimuthal)],
["HKL", "hkl labels", showHkl, () => setShowHkl(!showHkl)],
["Mask View", "Mask overlay", showMask, () => setShowMask(!showMask)],
["Detect View", "Detection frame view", showDetectionView, () => setShowDetectionView(!showDetectionView)],
["Invert", "Invert colormap", dpInvert, () => setDpInvert(!dpInvert)],
["Stats", "Statistics", showStats, () => setShowStats(!showStats)],
["Quality", "Analysis quality", showQc, toggleQuality],
Expand Down
15 changes: 13 additions & 2 deletions src/quantem/widget/showdiffraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -2166,6 +2166,7 @@ def _load_initial_state(self, state) -> None:
self.load_state_dict(self._resolve_state(state))

def _ingest_data(self, data):
self._filter_cache = {}
array = to_numpy(data)
is_integer = np.issubdtype(array.dtype, np.integer)
array = array.astype(np.float32)
Expand Down Expand Up @@ -2287,7 +2288,17 @@ def _detection_frame(self) -> np.ndarray:
mode = self._resolve_detect_denoise(frame)
if mode == "none":
return frame
return apply_display_filter(frame, mode=mode, sigma=2.0)
return self._filtered_frame(mode)

def _filtered_frame(self, mode: str) -> np.ndarray:
key = (int(self.frame_idx), mode)
cached = self._filter_cache.get(key)
if cached is None:
cached = apply_display_filter(self._displayed_frame(), mode=mode, sigma=2.0)
if len(self._filter_cache) >= 8:
self._filter_cache.pop(next(iter(self._filter_cache)))
self._filter_cache[key] = cached
return cached

def _resolve_detect_denoise(self, frame: np.ndarray) -> str:
mode = self.detect_denoise
Expand Down Expand Up @@ -2317,7 +2328,7 @@ def _update_frame(self, change=None):
if self.show_detection_view:
frame = self._detection_frame()
elif self.denoise != "none":
frame = apply_display_filter(self._displayed_frame(), mode=self.denoise, sigma=2.0)
frame = self._filtered_frame(self.denoise)
else:
frame = self._displayed_frame()
self.dp_stats = [
Expand Down
15 changes: 15 additions & 0 deletions tests/test_showdiffraction_denoise.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,21 @@ def test_recover_predicted_rings_rescues_missed_reflection():
assert [x["hkl"] for x in sorted(w.rings, key=lambda y: y["radius_px"])] == list(hkls)


def test_filtered_frames_are_cached_per_mode():
dp, _ = _spot_dp()
counts = np.random.default_rng(5).poisson(dp * 0.05).astype(np.float32)

w = ShowDiffraction(counts, detect_denoise="anscombe", verbose=False)
first = w._detection_frame()
assert w._detection_frame() is first

w.detect_denoise = "gaussian"
assert w._detection_frame() is not first

w._ingest_data(counts[None])
assert w._detection_frame() is not first


def test_detect_denoise_state_and_validation():
dp, _ = _spot_dp()
w = ShowDiffraction(dp.astype(np.float32), detect_denoise="gaussian", verbose=False)
Expand Down
28 changes: 28 additions & 0 deletions tests/test_showdiffraction_frontend_source.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from __future__ import annotations

import pathlib


def test_showdiffraction_denoise_controls_bind_synced_traits() -> None:
repo_root = pathlib.Path(__file__).resolve().parents[1]
source = (repo_root / "js" / "showdiffraction" / "index.tsx").read_text(
encoding="utf-8"
)

assert 'useModelState<string>("denoise")' in source
assert 'useModelState<string>("detect_denoise")' in source
assert 'useModelState<boolean>("show_detection_view")' in source

assert ">Denoise</Typography>" in source
assert ">Detect</Typography>" in source
assert '"Detect View"' in source


def test_slow_denova_modes_stay_out_of_the_dropdowns() -> None:
repo_root = pathlib.Path(__file__).resolve().parents[1]
source = (repo_root / "js" / "showdiffraction" / "index.tsx").read_text(
encoding="utf-8"
)

assert '"denova_tv"' not in source
assert '"denova_tv12"' not in source