Skip to content
Merged
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
15 changes: 13 additions & 2 deletions spyde/actions/lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,9 +314,18 @@ def show_tree_node(plot, tree, new_signal) -> None:
log.debug("re-emitting signal tree after transform failed: %s", e)


def paint_signal_plots(tree, data, *, levels: tuple[float, float] | None = None) -> None:
def paint_signal_plots(tree, data, *, levels: tuple[float, float] | None = None) -> int:
"""Paint *data* onto every signal plot of *tree*. With *levels* the plot's
contrast is locked to that range; otherwise it re-auto-levels."""
contrast is locked to that range; otherwise it re-auto-levels.

Returns the number of plots whose ``set_data`` SUCCEEDED. A failed paint
is still swallowed (a progressive compute's per-chunk callback must never
fail the compute) and logged at DEBUG, but the count makes the swallow
observable — callers that ignore the return are unaffected, and a caller
that must know whether pixels actually landed (the live signal preview,
and the tests that used to infer it from counters that incremented even
when the paint raised) can assert on it."""
painted = 0
for sp in list(getattr(tree, "signal_plots", []) or []):
try:
if levels is not None:
Expand All @@ -325,8 +334,10 @@ def paint_signal_plots(tree, data, *, levels: tuple[float, float] | None = None)
else:
sp.needs_auto_level = True
sp.set_data(data)
painted += 1
except Exception as e:
log.debug("painting signal plot failed: %s", e)
return painted


def progress_emitter(prefix: str, *, min_interval: float = 0.5) -> Callable[[int, int], None]:
Expand Down
7 changes: 6 additions & 1 deletion spyde/actions/live_signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ def __init__(self, session, tree, *, render: Callable[[tuple], Any],
self.blocks_seen = 0
#: (a) auto-sample paints driven by a landing block
self.frames_painted = 0
#: of those, paints whose set_data actually SUCCEEDED on >=1 signal
#: plot (paint_signal_plots' return) — frames_painted counts attempts,
#: so a swallowed set_data failure is visible as landed < painted.
self.frames_landed = 0
#: (b) navigator-driven reads answered from the already-computed region
self.frames_served = 0
#: navigator-driven reads that landed on a position the batch has not
Expand Down Expand Up @@ -210,7 +214,8 @@ def _paint(self, frame) -> None:

def _apply():
if not self._closed:
paint_signal_plots(tree, frame)
if paint_signal_plots(tree, frame) > 0:
self.frames_landed += 1

dispatch = getattr(self.session, "_dispatch_to_main", None)
if dispatch is None:
Expand Down
5,300 changes: 2,643 additions & 2,657 deletions spyde/tests/.test_durations

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion spyde/tests/migrated/test_action_deselect.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from __future__ import annotations

import time
from spyde.tests.migrated.conftest import _settle


def _signal_plot(session):
Expand All @@ -22,7 +23,7 @@ def _run_action(session):
src = _signal_plot(session)
assert src is not None
session._dispatch_toolbar_action(src, ACTION, {})
time.sleep(0.4)
_settle(session)
return src


Expand Down
5 changes: 3 additions & 2 deletions spyde/tests/migrated/test_action_dispatch_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from __future__ import annotations

import time
from spyde.tests.migrated.conftest import _settle


def _signal_plot(session):
Expand All @@ -26,7 +27,7 @@ def test_generic_dispatch_stores_action_instance(self, stem_4d_dataset):
plot = _signal_plot(session)

session._dispatch_toolbar_action(plot, "FFT", {})
time.sleep(0.3)
_settle(session)

art = session._action_artifacts.get((plot.window_id, "FFT"))
assert art is not None, "FFT artifacts not tracked"
Expand All @@ -37,7 +38,7 @@ def test_update_vi_reaches_generic_action(self, stem_4d_dataset):
session = stem_4d_dataset["window"]
plot = _signal_plot(session)
session._dispatch_toolbar_action(plot, "FFT", {})
time.sleep(0.3)
_settle(session)

art = session._action_artifacts.get((plot.window_id, "FFT"))
assert art is not None
Expand Down
5 changes: 3 additions & 2 deletions spyde/tests/migrated/test_axes_edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import numpy as np
import hyperspy.api as hs
from spyde.tests.migrated.conftest import _settle


def _signal_plot(session):
Expand All @@ -27,7 +28,7 @@ def test_set_axis_writes_back_and_re_emits(self):
s = hs.signals.Signal2D(np.zeros((4, 5, 24, 24), np.float32))
s.set_signal_type("electron_diffraction")
session._add_signal(s)
time.sleep(0.3)
_settle(session)
plot = _signal_plot(session)
tree = plot.signal_tree

Expand Down Expand Up @@ -72,7 +73,7 @@ def test_scale_edit_preserves_origin_pixel(self):
s.axes_manager._axes[3].scale = 0.1
s.axes_manager._axes[3].offset = -1.0 # origin pixel = 1.0/0.1 = 10
session._add_signal(s)
time.sleep(0.3)
_settle(session)
plot = _signal_plot(session)
axx = plot.signal_tree.root.axes_manager._axes[3]
origin_px = -float(axx.offset) / float(axx.scale)
Expand Down
17 changes: 9 additions & 8 deletions spyde/tests/migrated/test_center_zero_beam.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import numpy as np
import hyperspy.api as hs
from spyde.tests.migrated.conftest import _settle


def _signal_plot(session):
Expand Down Expand Up @@ -55,7 +56,7 @@ def test_auto_centers_beam(self):
session = Session(n_workers=1, threads_per_worker=1)
try:
session._add_signal(_off_center_4d(beam=(18, 14)))
time.sleep(0.4)
_settle(session)
src = _signal_plot(session)
before = src.plot_state.current_signal

Expand Down Expand Up @@ -85,7 +86,7 @@ def test_auto_region_shows_draggable_widget_and_run_uses_it(self):
session = Session(n_workers=1, threads_per_worker=1)
try:
session._add_signal(_off_center_4d(sig=(64, 64), beam=(18, 14)))
time.sleep(0.4)
_settle(session)
src = _signal_plot(session)
tree = src.signal_tree

Expand Down Expand Up @@ -122,7 +123,7 @@ def test_region_widget_torn_down_on_close(self):
session = Session(n_workers=1, threads_per_worker=1)
try:
session._add_signal(_off_center_4d(sig=(32, 32)))
time.sleep(0.4)
_settle(session)
src = _signal_plot(session)
tree = src.signal_tree

Expand Down Expand Up @@ -160,7 +161,7 @@ def test_activating_automatic_tab_shows_full_frame_box_immediately(self):
session = Session(n_workers=1, threads_per_worker=1)
try:
session._add_signal(_off_center_4d(sig=(48, 32)))
time.sleep(0.4)
_settle(session)
src = _signal_plot(session)
tree = src.signal_tree

Expand All @@ -182,7 +183,7 @@ def test_region_drag_handler_runs_once_per_event(self):
session = Session(n_workers=1, threads_per_worker=1)
try:
session._add_signal(_off_center_4d(sig=(32, 32)))
time.sleep(0.4)
_settle(session)
src = _signal_plot(session)
tree = src.signal_tree

Expand Down Expand Up @@ -222,7 +223,7 @@ def test_node_switch_tears_down_region_and_markers(self):
session = Session(n_workers=1, threads_per_worker=1)
try:
session._add_signal(_off_center_4d())
time.sleep(0.4)
_settle(session)
src = _signal_plot(session)
tree = src.signal_tree

Expand All @@ -249,7 +250,7 @@ def test_tree_close_tears_down_region_widgets(self):
session = Session(n_workers=1, threads_per_worker=1)
try:
session._add_signal(_off_center_4d())
time.sleep(0.4)
_settle(session)
src = _signal_plot(session)
tree = src.signal_tree

Expand All @@ -272,7 +273,7 @@ def test_manual_center_from_crosshair(self):
session = Session(n_workers=1, threads_per_worker=1)
try:
session._add_signal(_off_center_4d(beam=(18, 14)))
time.sleep(0.4)
_settle(session)
src = _signal_plot(session)
tree = src.signal_tree
before = src.plot_state.current_signal
Expand Down
12 changes: 9 additions & 3 deletions spyde/tests/migrated/test_console_preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,9 @@ def test_stop_clears_nav_refresh(self, window):
session.console.submit_preview("", pid + 999, True)
n0 = _count_results(msgs, pid)
session.console.notify_nav_changed()
time.sleep(0.6)
# Negative-evidence window (a rerun that must NOT happen has no event
# to poll for); 0.3 s is ~10x the preview thread's dispatch latency.
time.sleep(0.3)
assert _count_results(msgs, pid) == n0, "nav move re-ran a STOPPED preview"
# And the stop itself emitted nothing.
assert not any(m.get("type") == "console_preview_result"
Expand All @@ -522,7 +524,9 @@ def test_exec_clears_nav_refresh(self, window):
assert _exec(session, msgs, "1 + 1") is not None
n0 = _count_results(msgs, pid)
session.console.notify_nav_changed()
time.sleep(0.6)
# Negative-evidence window (a rerun that must NOT happen has no event
# to poll for); 0.3 s is ~10x the preview thread's dispatch latency.
time.sleep(0.3)
assert _count_results(msgs, pid) == n0, "nav move resurrected a pre-exec preview"

def test_explicit_preview_not_nav_tracked(self, window):
Expand All @@ -535,7 +539,9 @@ def test_explicit_preview_not_nav_tracked(self, window):
pid = res["preview_id"]
n0 = _count_results(msgs, pid)
session.console.notify_nav_changed()
time.sleep(0.6)
# Negative-evidence window (a rerun that must NOT happen has no event
# to poll for); 0.3 s is ~10x the preview thread's dispatch latency.
time.sleep(0.3)
assert _count_results(msgs, pid) == n0

def test_run_update_fires_hook_only_on_change(self, window, monkeypatch):
Expand Down
9 changes: 5 additions & 4 deletions spyde/tests/migrated/test_crop_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from spyde.actions.base import (
CropAction, _crop_signal, crop_open, crop_close, crop_set_region,
)
from spyde.tests.migrated.conftest import _settle


class TestCropSlicing:
Expand Down Expand Up @@ -80,7 +81,7 @@ def test_run_adds_a_cropped_node(self, stem_4d_dataset):
# x 2..14 (=12 cols), y 4..10 (=6 rows) → signal_shape (12, 6).
act = CropAction.for_plot(plot, x0=2, x1=14, y0=4, y1=10)
new = act.run()
time.sleep(0.2)
_settle(session)

assert new is not None
assert tuple(new.axes_manager.signal_shape) == (12, 6)
Expand All @@ -96,7 +97,7 @@ def test_run_keeps_a_lazy_movie_lazy(self, movie_dataset):
if not p.is_navigator and p.plot_state is not None)
act = CropAction.for_plot(plot, x0=4, x1=20, y0=6, y1=18)
new = act.run()
time.sleep(0.2)
_settle(session)
assert new is not None
assert new._lazy is True
assert isinstance(new.data, da.Array), "cropped movie must stay lazy"
Expand Down Expand Up @@ -148,7 +149,7 @@ def test_widget_drives_a_nonsquare_crop(self, stem_4d_dataset):
# Run picks up the WIDGET's geometry — typed fields are stale/absent.
act = CropAction.for_plot(plot)
new = act.run()
time.sleep(0.2)
_settle(session)

assert new is not None
assert tuple(new.axes_manager.signal_shape) == (12, 6)
Expand All @@ -170,7 +171,7 @@ def test_widget_partially_out_of_bounds_is_clamped(self, stem_4d_dataset):

act = CropAction.for_plot(plot)
new = act.run()
time.sleep(0.2)
_settle(session)

assert new is not None
# Clamped to the full valid frame (0..w, 0..h) rather than raising or
Expand Down
Loading
Loading