spacr.qt.widgets.timelapse_preview¶
Timelapse live preview — segment once, re-link live.
The Timelapse module is mask generation over a time series followed by
frame-to-frame linking. Those two halves cost wildly different amounts:
segmenting twelve frames with Cellpose is tens of seconds, re-linking the
same twelve label images with a new timelapse_displacement is
milliseconds. A preview that re-segments on every slider move is unusable,
so this panel splits them:
Per-frame masks are cached under a segmentation signature — the source path, the frame indices, and every setting that can change a label image (model, channel, diameter, flow threshold, cell probability, normalisation). Change a tracking setting and the signature is unchanged, the cache hits, and only
link_tracks()runs. Change a segmentation setting and the signature moves, so the masks are rebuilt.The sequence is read lazily.
FrameSequencenever materialises the whole time series: a directory reads one file at a time, a multi-page TIFF reads one page at a time, and an.npystack is memory-mapped and sliced. Only the frames the preview actually shows are touched, and a small LRU keeps the scrubber responsive without pinning the movie in RAM.
What the panel shows is chosen around the two failure modes people actually tune a tracker against:
Fragmentation — one object becoming several track ids. Surfaced as track count, mean/median track length, the number of tracks shorter than a live-settable N, and the count of tracks that start after the first frame or end before the last one.
Identity swaps — a track jumping to a different object. Surfaced as the number of within-track steps longer than the displacement limit the user is tuning, plus the largest single step. Masks are relabelled by track id and drawn in a per-track colour, so a swap is visible as an object changing colour mid-movie.
Both indicators are computed without ground truth and are labelled in the UI as indicators, not measurements.
Optional backends (trackastra, ultrack) are detected before they
are called: absent, the panel writes one inline line naming the package
and the install command. An ImportError traceback never reaches the
user.
Exceptions¶
A queued movie field was abandoned before it retained its arrays. |
|
A linking backend cannot run here, with an actionable reason. |
Classes¶
A time series read one frame at a time. |
|
Interactive tracking preview — Timelapse module. |
|
One preview pass. |
|
What the user is actually tuning against. |
Functions¶
|
Whether linking backend |
|
Open, segment and link one additional field without touching Qt UI. |
|
Build the |
|
Return a 2-D plane from a frame stored either (H, W, C) or (C, H, W). |
|
Link a (T, H, W) label stack into tracks with the chosen backend. |
|
Never let one bad sibling strand the remaining movie-field queue. |
|
Whether the JobRunner thread executing this field was cancelled. |
|
Open a sequence and list its neighbours. No Qt, so it runs on a worker. |
|
Recolour a label stack by track id using spaCR's own relabeller. |
|
Render one preview frame: image, mask outlines, and track history. |
|
Do the work of one preview: masks (maybe cached), then linking. |
|
Segment one frame with Cellpose and return an |
|
Segment every preview frame of |
|
Deterministic colour for a track id, stable across frames and runs. |
|
Summarise a track table into the numbers that drive a tuning decision. |
Module Contents¶
- exception spacr.qt.widgets.timelapse_preview.MovieFieldCancelled[source]¶
Bases:
RuntimeErrorA queued movie field was abandoned before it retained its arrays.
Initialize self. See help(type(self)) for accurate signature.
Bases:
RuntimeErrorA linking backend cannot run here, with an actionable reason.
Raised instead of letting an
ImportErrorescape so the panel can put the message inline. The string always names the package and the command that fixes it.Initialize self. See help(type(self)) for accurate signature.
- class spacr.qt.widgets.timelapse_preview.FrameSequence(kind: str, source, n_available: int, indices: Sequence[int], label: str = '', cache_size: int = 6)[source]¶
A time series read one frame at a time.
Three layouts are understood, and none of them is ever read whole:
a directory of per-frame image files — one file opened per access;
a multi-page TIFF — one page decoded per access (
tifffile.imread(path, key=i));an
.npystack whose first axis is time — memory-mapped, so a slice touches only that plane’s pages.
- Parameters:
kind –
"files","tiff"or"npy".source – the list of paths (
files) or the single path.n_available – how many frames exist on disk.
indices – the subset of frame indices this sequence exposes, so a 400-frame movie can be previewed as its first 12 frames.
label – what to call the sequence in the UI. Empty falls back to
str(source), which for a file list is the list – fine for one path, unreadable for four hundred, so anything user-facing should pass a name.cache_size – how many decoded frames to keep in the LRU.
- Variables:
read_count – number of decodes actually performed — the instrument the tests assert against to prove nothing is read eagerly.
Hold one sequence of frames, read lazily and cached.
- Parameters:
kind – what the frames are – images, masks, or an overlay.
source – where to read them from.
n_available – how many frames exist.
indices – which of them this sequence shows.
label – the caption for this sequence.
cache_size – how many decoded frames to keep.
- frame(i: int) numpy.ndarray[source]¶
Return preview-frame
i(0-based overindices).- Parameters:
i – preview-frame position; outside
0tolen(indices) - 1raisesIndexError. Frames read are kept in a small cache.
- classmethod open(path, max_frames: int = 12) FrameSequence[source]¶
Open
pathas a sequence, reading at most metadata to do so.- Parameters:
path – a directory of frames, a multi-page TIFF, or an
.npystack whose first axis is time.max_frames – cap on the number of frames the preview exposes.
- Raises:
FileNotFoundError – if the path does not exist.
ValueError – if the path holds no usable time series.
- class spacr.qt.widgets.timelapse_preview.TimelapsePreviewPanel(parent=None, *, threaded: bool = True)[source]¶
Bases:
spacr.qt.widgets.preview_contract.LivePreviewContract,PySide6.QtWidgets.QWidgetInteractive tracking preview — Timelapse module.
Same contract as
LivePreviewPanel, and now literally the same code for the shared half: a standaloneQWidget, aQThreadworker that emits results over signals,LivePreviewContractfor the run/cancel/status protocol,set_propagate_callback()to push tuned values back into the main settings panel, and abuild_*_cardfactory.- Parameters:
parent – parent widget.
threaded – whether the jobs run off the GUI thread. Opening a sequence reads a TIFF header or memory-maps a stack and then lists every sibling field of view, which is not GUI-thread work on a plate. False runs each job inline, emitting the same signals in the same order, so a test can drive this panel synchronously without the behaviour diverging.
Build the preview: its canvases, its scrub bar and its controls.
- Parameters:
parent – parent widget.
threaded – whether work runs on a worker.
- apply_settings(settings: dict) None[source]¶
Seed the preview from the main Timelapse settings dict.
- Parameters:
settings – the Timelapse settings; a copy is kept, and the linking mode, displacement, memory, object, transient filter and that object’s channel, diameter and model are read from it.
- attach_movie_panel(movie) None[source]¶
Wire a
TimelapseMoviePanelto this preview.Kept as a seam rather than a constructor argument so the movie is optional: the panel is built by two different callers and a screen that only wants the stats view should not pay for the frames.
- Parameters:
movie – the movie panel; its
max_fields_changedsignal is connected and a previously attached panel is disconnected.
- closeEvent(event)[source]¶
Let a running pass finish before the widget is torn down.
A
QThreadcollected while it is still running aborts the whole process, and this panel’s worker outlives the emit that produced its result by a few instructions.- Parameters:
event – the close event; passed on to the base class after the workers finish (up to 5 s each).
- dragEnterEvent(event)[source]¶
Accept a drag carrying a timelapse folder or one of its frames.
- Parameters:
event – the Qt drag event.
- dragMoveEvent(event)[source]¶
Keep accepting while a timelapse folder or one of its frames stays over the panel.
- Parameters:
event – the Qt drag event.
- dropEvent(event)[source]¶
Take the dropped input and preview it.
- Parameters:
event – the Qt drop event.
- load_masks(path) bool[source]¶
Synchronously use ready-made label images instead of segmenting.
For programmatic callers and tests. The GUI uses
load_masks_async(), because this one opens the folder on the thread that calls it.- Parameters:
path – the label images: a directory of frames, a multi-page TIFF, or an
.npystack whose first axis is time.- Returns:
False, with the reason in the status line, when it cannot be opened.
- load_masks_async(path) bool[source]¶
Open
pathas a mask sequence on a worker, then install it here.THE GUI ENTRY POINT, and the reason it exists is that
load_masks()opens the sequence inline.FrameSequence.openon a folder of label images is a stat, a listing and oneis_file()per entry – hundreds of round trips for a plate – and the folder comes from the user, which on one such workstation means it can be a sleeping/nas_mntshare where a single stat had not returned after twenty seconds (measured; seespacr.qt.path_probe). Run from_pick_masks()that froze the whole window the moment the file dialog closed.The same worker function as the image sequence, so the mask sequence’s first frame is warmed off the GUI thread too.
- Parameters:
path – the label images: a directory of frames, a multi-page TIFF, or an
.npystack whose first axis is time.Noneor empty submits nothing.- Returns:
Truewhen a job was submitted.
- load_sequence(path) bool[source]¶
Synchronously open
pathas the preview sequence.For programmatic callers and tests, mirroring
LivePreviewPanel.load_image. The GUI usesload_sequence_async().- Parameters:
path – the image sequence: a directory of frames, a multi-page TIFF, or an
.npystack whose first axis is time.- Returns:
False, with the reason in the status line, when it cannot be opened.
- load_sequence_async(path, *, list_siblings: bool = True) bool[source]¶
Open
pathon a worker, then install it on the GUI thread.Every GUI entry point – the drop handler, the Choose-sequence dialog and the FOV dropdown – comes through here.
- Parameters:
path – the image sequence: a directory of frames, a multi-page TIFF, or an
.npystack whose first axis is time.Noneor empty submits nothing.list_siblings – list the sibling fields next to
pathfor the field dropdown;Falsereuses the cached listing.
- Returns:
Truewhen a job was submitted.
- refresh_model_choices() None[source]¶
Re-read the Cellpose model list and add anything new.
spacr.settings.cellpose_model_choicesonly reads the API when Cellpose is already imported, because importing it costs ~2.5 s and this panel is built while a page is being laid out. That means the first build usually gets the shipped fallback — so ask again every time the panel is shown. After the first segmentation Cellpose is loaded and a checkpoint the user registered appears here.Additive on purpose: the current selection is never disturbed, and an entry is never removed, so a value the user picked cannot vanish under them because a probe came back thinner.
- set_propagate_callback(cb) None[source]¶
Register a
callback(dict)that writes tuned values into the main settings panel (wired by the AppScreen).- Parameters:
cb – the callable, or
Noneto remove it.
- settings_for_propagation() dict[source]¶
Map the preview’s widgets onto real Timelapse setting keys.
- class spacr.qt.widgets.timelapse_preview.TimelapseRequest[source]¶
One preview pass.
cached_masksis what makes re-linking cheap.
- class spacr.qt.widgets.timelapse_preview.TrackStats[source]¶
What the user is actually tuning against.
fragmentation_*andsuspicious_jumpsare indicators computed without ground truth, not measurements — the panel labels them as such.
- spacr.qt.widgets.timelapse_preview.backend_available(mode: str) Tuple[bool, str][source]¶
Whether linking backend
modecan run, and why not when it cannot.Checks with
importlib.util.find_spec(), so nothing heavy is imported just to answer the question and a missing optional dependency never raises.- Parameters:
mode – linking backend name, one of
TRACK_MODES(case-insensitive);iouneeds no optional package.- Returns:
(True, "")when usable, else(False, message)where the message names the package and the command that installs it.
- spacr.qt.widgets.timelapse_preview.build_movie_field(path, *, max_frames: int, seg: Dict[str, Any], track: Dict[str, Any], cached_masks: numpy.ndarray | None = None, cancelled: Callable[[], bool] | None = None) Dict[str, Any][source]¶
Open, segment and link one additional field without touching Qt UI.
Raw frames are retained for the movie, so a cache miss reads each frame exactly once and hands that same array to segmentation.
cancelledis checked between frames and before linking; the production callback reads the worker QThread’s interruption flag, which lets lowering the Fields cap stop an expensive sibling before it retains the rest of the sequence.- Parameters:
path – the field to open: a directory of frames, a multi-page TIFF, or an
.npystack whose first axis is time.max_frames – cap on the number of frames read.
seg – segmentation parameters, as
segment_frame()takes them; itschannelis also returned.track – keyword arguments for
link_tracks().cached_masks – an existing (T, H, W) label stack to reuse instead of segmenting; its frame count must match the sequence.
cancelled – a no-argument callable returning
Trueto stop.
- spacr.qt.widgets.timelapse_preview.build_timelapse_preview_card(host, *, panel_later: bool = False)[source]¶
Build the
Track previewcard + panel pair.Mirrors
spacr.qt.screens.hyperparam.build_hyperparam_cardandapp_screen._build_live_preview_card: returns the pair without adding it to any layout, so the host screen puts it in whatever splitter it likes and starts it hidden behind the toggle.- Parameters:
host – the
AppScreenasking for the card.panel_later – return
Nonefor the panel and leave the card empty, for_fill_timelapse_preview_card()to fill the first time it is shown – how Mask carries this preview for its folded Timelapse switch without building ~130 widgets nobody has asked to see.
- Returns:
(panel, card).
- spacr.qt.widgets.timelapse_preview.frame_channel(frame: numpy.ndarray, channel: int) numpy.ndarray[source]¶
Return a 2-D plane from a frame stored either (H, W, C) or (C, H, W).
A merged frame written by spaCR is channel-last, but a raw acquisition TIFF page is often channel-first. Guessing wrong turns a 3-channel image into a 3-pixel-tall one, so pick the axis that actually looks like a channel axis (small, and not the only small axis).
- Parameters:
frame – the frame array; a 2-D frame is returned as is and one with other than three dimensions is squeezed.
channel – the channel index to take (wrapped modulo the channel count on a channel-first frame).
- spacr.qt.widgets.timelapse_preview.link_tracks(masks: numpy.ndarray, mode: str = 'iou', displacement: float = 50.0, memory: int = 3, iou_threshold: float = 0.1, images: numpy.ndarray | None = None, trackastra_model: str = 'general_2d', trackastra_linking: str = 'greedy')[source]¶
Link a (T, H, W) label stack into tracks with the chosen backend.
- Parameters:
masks – label stack with at least two frames; any other shape raises
ValueError.mode – linking backend, one of
TRACK_MODES.displacement – maximum displacement between frames, in pixels, passed to the
trackpy,btrackandultrackbackends.memory – frames an object may vanish for (
trackpyonly).iou_threshold – minimum overlap to link (
iouonly).images – intensity stack for
trackastra.trackastra_model –
trackastramodel name.trackastra_linking –
trackastralinking mode.
- Returns:
a DataFrame with
frame,original_label,track_id,xandy— the same layout the pipeline’s trackers emit.- Raises:
TrackerUnavailable – when the backend’s package is missing, with a message naming the package and the install command.
- spacr.qt.widgets.timelapse_preview.movie_field_payload(**kwargs) Dict[str, Any][source]¶
Never let one bad sibling strand the remaining movie-field queue.
- spacr.qt.widgets.timelapse_preview.movie_worker_interrupted() bool[source]¶
Whether the JobRunner thread executing this field was cancelled.
- spacr.qt.widgets.timelapse_preview.open_sequence_payload(path, max_frames: int = 12, list_siblings: bool = True) Dict[str, Any][source]¶
Open a sequence and list its neighbours. No Qt, so it runs on a worker.
Warms the sequence’s own frame cache with frame 0, because
_frame_channel_countdecodes exactly that frame on the GUI thread to fill the channel dropdown – doing it here turns that read into a cache hit rather than a second trip to disk.- Parameters:
path – sequence file or frame directory accepted by
FrameSequence.open(); the same value is returned as text in the payload even when opening fails.list_siblings –
Falsereuses the sampler’s cached listing; the FOV dropdown hands out a path it has already enumerated.
- Returns:
{path, sequence, siblings, error}. Whenlist_siblingsis truesiblingsis ALWAYS a list, neverNone, even if the listing failed – see the fallback below for why that matters.
- spacr.qt.widgets.timelapse_preview.relabel_by_track(masks: numpy.ndarray, tracks) numpy.ndarray[source]¶
Recolour a label stack by track id using spaCR’s own relabeller.
This is what makes an identity swap visible: an object that keeps its track id keeps its colour across frames, and one that is handed to another track changes colour mid-movie.
- Parameters:
masks – the (T, H, W) label stack the tracks were built from.
tracks – the track table from
link_tracks();Noneor an empty table gives an all-zero stack.
- spacr.qt.widgets.timelapse_preview.render_frame(image: numpy.ndarray, labels: numpy.ndarray | None = None, tracks=None, frame: int = 0, tail: int = 12, normalise: bool = True, lo_pct: float = 2.0, hi_pct: float = 98.0, channel: int = 0) numpy.ndarray[source]¶
Render one preview frame: image, mask outlines, and track history.
Outlines are coloured per track id so a fragmented track shows up as an object that changes colour partway through, and the trailing polyline shows where each object came from over the last
tailframes.- Parameters:
image – the frame to draw, 2-D or with a channel axis; the plane is picked with
frame_channel().labels – optional track-relabelled mask for this frame, outlined per track colour.
tracks – optional track table with
x,y,frameandtrack_id; trails are drawn up toframe.frame – index of this frame in the track table.
tail – how many earlier frames each trail reaches back.
normalise – stretch the plane between the two percentiles.
lo_pct – lower percentile of the stretch.
hi_pct – upper percentile of the stretch.
channel – channel of
imageto draw.
- Returns:
an RGB
uint8array.
- spacr.qt.widgets.timelapse_preview.run_preview_pass(req: TimelapseRequest) Dict[str, Any][source]¶
Do the work of one preview: masks (maybe cached), then linking.
- Parameters:
req – the request; its cached masks are used first, then its mask sequence, then its image sequence is segmented with
req.seg, and the masks are linked withreq.track. With none of these it raisesValueError.
- spacr.qt.widgets.timelapse_preview.segment_frame(image: numpy.ndarray, params: Dict[str, Any]) numpy.ndarray[source]¶
Segment one frame with Cellpose and return an
int32label image.Cellpose is imported inside the call so importing this module — as the test suite does — costs nothing and needs no CUDA stack. The panel calls this through the module global, which is also what lets a test swap in a counting stub to prove that tuning a tracking setting never reaches segmentation.
- Parameters:
image – the frame, 2-D or with a channel axis.
params – segmentation settings;
model,channel,normalise,lo_pct,hi_pct,diameter,flow_thresholdandcellprobare read, each with a default. Acellpose3:...model is segmented in the Cellpose 3 backend byspacr.object._cellpose3_masks(), as the run segments it, and acellpose_dino:<path>model in the Cellpose-DINO backend byspacr.object._cellpose_dino_masks().
- spacr.qt.widgets.timelapse_preview.segment_sequence(seq: FrameSequence, params: Dict[str, Any]) numpy.ndarray[source]¶
Segment every preview frame of
seqinto a (T, H, W) label stack.Reads and segments one frame at a time so the whole movie is never resident, then stacks only the label images (which are far smaller than the raw multi-channel frames).
- Parameters:
seq – the frame sequence to segment.
params – segmentation settings, as
segment_frame()takes them.
- spacr.qt.widgets.timelapse_preview.track_colour(track_id: int) Tuple[int, int, int][source]¶
Deterministic colour for a track id, stable across frames and runs.
- Parameters:
track_id – the track id, converted with
int; it indexesTRACK_COLOURScyclically.
- spacr.qt.widgets.timelapse_preview.track_stats(tracks, n_frames: int, min_length: int = 3, displacement_limit: float = 50.0) TrackStats[source]¶
Summarise a track table into the numbers that drive a tuning decision.
- Parameters:
tracks – DataFrame with
frame,track_idand (for the swap indicator)x/y.n_frames – how many frames the preview covered.
min_length – tracks shorter than this are counted as short — the live-settable threshold that says what “too short” means here.
displacement_limit – the linking radius being tuned; a within-track step longer than this is counted as a swap risk.