spacr.qt.widgets.preview_controls¶
Shared source-selector controls for every live-preview panel.
Four modules ship a live preview — Mask, Measure, Timelapse and Motility —
and each of them opens exactly one source at a time. Before this module they
each had a single Choose … button and no way to move to the next field of
view or to look at a different channel without re-opening the file dialog.
This module supplies the two dropdowns those panels now share, plus the flat “text only” look they wear:
FlatComboBox/FlatButton/FlatSpinBox— chrome-free controls that read like the Live toggle (AiToggleLabel): the theme’s foreground colour, 600 weight, body font size, transparent background, no border, pointing-hand cursor. The accent colour on hover is the only affordance, exactly like the toggles they sit beside.populate_channel_combo()— fills a channel dropdown from a channel count, with an “All channels” entry first.sibling_sources()— lists the other fields of view that live beside the currently-loaded one.enumerate_image_sets()/sample_image_sets()— the sampled source list, described below.install_preview_scale()— each preview’s own scale slider, fromspacr.qt.widgets.preview_scaleand re-exported here so every shared preview control is importable from one place.
The palette is resolved through spacr.qt.theme.active_palette() at build
time and again on every showEvent, so a theme switch made in Preferences
lands the next time the panel is shown rather than baking the dark palette’s
white text onto a light page. That is the failure
spacr.qt.widgets.ai_toggle_label documents.
Why the source list is a sample¶
sibling_sources() lists every comparable file in the folder, and the
panels fed that straight into their field-of-view dropdown. Measured on a
384-well plate at 16 fields and 4 channels (24 576 files) and on one four times
larger (98 304 files):
measurement |
24k before |
24k after |
98k before |
98k after |
|---|---|---|---|---|
folder dropped → panel usable |
279 ms |
139 ms |
1280 ms |
579 ms |
opening the sets dropdown |
175 ms |
2 ms |
689 ms |
2 ms |
every change of field |
270 ms |
1 ms |
1233 ms |
3 ms |
entries in the dropdown |
24 576 |
20 |
98 304 |
20 |
resident memory it held |
16.2 MB |
3.1 MB |
56.0 MB |
39.4 MB |
image files opened |
1 |
1 |
1 |
1 |
The third row is the one users feel: the panels rebuild their selectors on every load, so re-listing and re-populating the whole plate was paid again on each step through it. It was also the wrong list — four of those entries are the same field of view, once per channel.
enumerate_image_sets() replaces it. It reads file names only, via
os.scandir and the project’s own acquisition regex
(spacr.utils._get_regex), and groups them into ImageSet records
keyed by (plate, well, field) with the channel files hanging off each. No
image is opened, decoded or stacked to build that list.
sample_image_sets() then draws a bounded random sample — 20 sets by
default, adjustable from the FlatSpinBox that sits immediately left of
the sets dropdown.
The sample is random but reproducible: the seed is a stable digest of
"<folder name>|<total sets>|<cap>|<nonce>" (sample_seed()), so the
same plate at the same cap yields the same sets in this session, the next
session, and on another machine or mount point — a preview can be described and
returned to. It is deliberately not seeded from the clock or from hash(),
which is salted per process. Re-rendering never re-draws; only an explicit act
does — changing the cap (which is in the seed) or calling
reshuffle() (which bumps the nonce).
The sample is drawn across the whole enumeration and then re-sorted into plate order, so the dropdown still reads A01 → P24 while its membership spans the plate rather than being the first N names alphabetically (which on a plate-ordered folder means “all of row A”).
Attributes¶
Classes¶
Text-only push button styled like the Live toggle. |
|
Text-only dropdown styled like the Live toggle. |
|
Text-only integer box styled like the Live toggle. |
|
One field of view: every channel file that shares a (plate, well, field). |
|
Caches one folder's enumeration and hands out samples of it. |
Functions¶
|
Point a sets dropdown at the sampler's current sample. |
|
Return the entries a channel dropdown shows for |
|
Return |
|
Point a |
|
Group a folder's file names into image sets. Opens nothing. |
|
Refill |
|
Refill an FOV dropdown with |
|
Draw at most |
|
The reproducible seed a sampled preview is drawn with. |
|
Return the channel index a channel dropdown selects, or |
|
Wrap an already-listed set of sources as one |
|
List every comparable source sitting beside |
Module Contents¶
- class spacr.qt.widgets.preview_controls.FlatButton(text: str = '', parent=None, tooltip: str = '')[source]¶
Bases:
_FlatStyleMixin,PySide6.QtWidgets.QPushButtonText-only push button styled like the Live toggle.
- Parameters:
text – the caption.
parent – parent widget.
tooltip – hover text. An empty string leaves the button with no tooltip rather than an empty one, which would otherwise show as a blank box on hover.
Build a flat button.
- Parameters:
text – the label.
parent – parent widget, or
None.tooltip – hover text.
- class spacr.qt.widgets.preview_controls.FlatComboBox(parent=None, tooltip: str = '')[source]¶
Bases:
_FlatStyleMixin,PySide6.QtWidgets.QComboBoxText-only dropdown styled like the Live toggle.
- Parameters:
parent – owning widget.
tooltip – hover help; these controls carry no visible label, so the tooltip is the only place their meaning is written down.
Build a flat combo box whose entries are data, not prose.
The language pass is kept off the items deliberately: they are file names and channel indices, and letting them be rewritten breaks every lookup that reads
currentText()back – the trap that silently reverted the live preview’s outline colour to its default.- Parameters:
parent – parent widget, or
None.tooltip – hover text.
- class spacr.qt.widgets.preview_controls.FlatSpinBox(parent=None, tooltip: str = '', value: int = 20)[source]¶
Bases:
_FlatStyleMixin,PySide6.QtWidgets.QSpinBoxText-only integer box styled like the Live toggle.
Used for the “how many image sets may the preview load” cap that sits immediately left of the sets dropdown. The count of sets actually found is carried in the box’s suffix, so the control states
20 of 24576 setsin one place and a sampled preview can never be mistaken for the whole plate.- Parameters:
parent – parent widget.
tooltip – hover text.
value – the cap to open on. The minimum is 1, and the maximum is left wide open until a folder has been enumerated –
configure_max_sets_boxthen clamps it to the number of sets that exist, so the box cannot ask for more than there are.
Build a flat spin box.
The maximum starts wide open and is clamped once a folder has been enumerated – until then there is no honest ceiling to impose.
- Parameters:
parent – parent widget, or
None.tooltip – hover text.
value – starting value.
- class spacr.qt.widgets.preview_controls.ImageSet[source]¶
One field of view: every channel file that shares a (plate, well, field).
Built from file names alone. Nothing here has been opened or decoded —
channelsmaps a channel ID to a file name, and it is up to the panel to decide which single file it wants to read.- Parameters:
key –
(plateID, wellID, fieldID)as the acquisition regex reports them, or("", "", <file name>)for a name it does not understand.directory – folder the files live in.
channels –
{channel ID: file name}, one representative file per channel.planes –
{channel ID: [file name, ...]}, every plane in acquisition order.
- path(channel: str | None = None) pathlib.Path[source]¶
Full path to one channel’s file — the lowest channel by default.
- class spacr.qt.widgets.preview_controls.ImageSetSampler(max_sets: int = DEFAULT_MAX_SETS)[source]¶
Caches one folder’s enumeration and hands out samples of it.
The panels rebuild their selectors on every image they load. Enumerating the folder each time is what made stepping through a large plate cost 292 ms a step, so the enumeration is done once per folder and every later call reuses it. Only
enumerate()touches the filesystem.Re-sampling is likewise deliberate:
sample()is a pure function of (folder, total, cap, nonce), so re-rendering after any settings change returns the identical sets. The sample changes only when the user changes the cap or callsreshuffle().- Parameters:
max_sets – how many image sets a preview may load at once. It is the
capin the sampling described above, so changing it changes the sample – which is why it is a constructor argument rather than something read per render.
Create the sampler that hands out a bounded slice of a plate.
- Parameters:
max_sets – how many image sets to offer at most. The dropdown never lists a whole plate, so the sample is bounded and – being seeded from the folder – reproducible.
- adopt(directory, sets: Sequence[ImageSet], channels: Sequence[str], metadata_type: str = DEFAULT_METADATA_TYPE, custom_regex: str | None = None) None[source]¶
Install an enumeration produced elsewhere — e.g. on a worker thread.
The dialect the caller grouped with belongs in the cache key, or the very next
enumerate()— the panels run one on every load — misses and re-scans the whole plate on the GUI thread.- Parameters:
directory – the folder the enumeration is of; it becomes the cached folder.
sets – the image sets enumerated from it.
channels – the channel IDs found across the folder.
metadata_type – naming dialect the sets were grouped with.
custom_regex – pattern body when
metadata_type='custom'.
- describe(shown: int) str[source]¶
One sentence saying the preview is a sample, and of what.
showncan exceed the cap by one whensample()had to keep a loaded field that the draw missed; that extra entry is called out rather than quietly inflating the reported sample size.- Parameters:
shown – how many sets the dropdown lists.
- enumerate(directory, suffixes: Sequence[str], metadata_type: str = DEFAULT_METADATA_TYPE, custom_regex: str | None = None, force: bool = False) List[ImageSet][source]¶
Enumerate
directoryunless it is already the cached one.The cache key includes the naming dialect: keying on the folder alone meant that confirming a different regex re-used the grouping built with the old one, so the fix appeared to do nothing until the user opened a different folder.
- Parameters:
directory – folder to enumerate.
suffixes – lower-case suffixes that count as a source, as
enumerate_image_sets()takes them.metadata_type – naming dialect, see
spacr.utils._get_regex().custom_regex – pattern body when
metadata_type='custom'.force – re-scan even when the cache key matches.
- enumerate_paths(directory, lister, force: bool = False) List[ImageSet][source]¶
Cache a caller-supplied listing of whole sources as one set each.
For panels whose field of view is a folder of frames or a stacked array rather than a group of per-channel files.
listeris only called when the folder is not the cached one, which is what keeps stepping through fields free.- Parameters:
directory – the folder the listing belongs to; its string form is the cache key.
lister – a no-argument callable returning the source paths, each wrapped by
sets_from_paths().force – call
listereven when the folder is cached.
- invalidate() None[source]¶
Forget the cache, so the next
enumerate()really scans.
- pin(item: ImageSet | None) None[source]¶
Keep
itemin the list even when the draw missed it.A user who drops one specific file on the panel must find it in the panel’s own dropdown. The pin is sticky: it survives navigating away to a sampled field, so the file they opened stays reachable and the entry list does not shift under them while they browse. Redrawing the sample — the only thing that is allowed to change the list — clears it.
- Parameters:
item – the set to pin;
Noneor a set not in the current enumeration leaves the pin unchanged.
- sample(keep: ImageSet | None = None) List[ImageSet][source]¶
The sets to show: the draw, plus any pinned set.
keeppins as a side effect, so callers can pass whatever is loaded without tracking the pin themselves.
- set_for_path(path) ImageSet | None[source]¶
The enumerated set a given file belongs to, if any.
Indexed by file name on first use rather than scanned. This runs twice on every image load, and a linear scan of a 24 576-set plate put ~10 ms back onto each change of field — most of what the sampling had just taken off.
- Parameters:
path – a file path (
stror path-like) orNone; only its file name is looked up among the sets’ channel files.
- set_max(max_sets: int) bool[source]¶
Change the cap. Returns True when it actually changed.
- Parameters:
max_sets – the new cap, converted with
int; zero or less means no cap. A change clears the pin.
- spacr.qt.widgets.preview_controls.apply_sample_to_combo(combo: PySide6.QtWidgets.QComboBox, box: PySide6.QtWidgets.QSpinBox | None, sampler: ImageSetSampler, current_path, tooltip: str = '') str[source]¶
Point a sets dropdown at the sampler’s current sample.
Configures the cap box, draws the sample (keeping whatever is loaded), and refills the dropdown. Touches no file: the sampler must already have been enumerated.
- Parameters:
combo – the sets dropdown to refill.
box – the cap spin box, or
Noneto leave the sampler’s cap as it is.sampler – an already-enumerated
ImageSetSampler.current_path – the file loaded now (or
None); its set is kept in the sample and selected.tooltip – text placed before the sample sentence in the dropdown’s tooltip.
- Returns:
the sentence stating what fraction of the folder is on show.
- spacr.qt.widgets.preview_controls.channel_labels(n_channels: int, include_all: bool = True) List[str][source]¶
Return the entries a channel dropdown shows for
n_channels.- Parameters:
n_channels – number of channels;
Ch 0up toCh n-1are listed (a negative count lists none).include_all – put
ALL_CHANNELSfirst.
- spacr.qt.widgets.preview_controls.channel_view(image, channel: int | None)[source]¶
Return
imagereduced tochannel, or unchanged.Out-of-range indices and 2-D images fall through untouched — a stale selection must never raise while the user is loading a new field.
- Parameters:
image – an array of shape (H, W, C), or anything else (returned as is);
Noneis returned unchanged.channel – index into the last axis, or
Nonefor all channels.
- spacr.qt.widgets.preview_controls.configure_max_sets_box(box: PySide6.QtWidgets.QSpinBox, total: int) int[source]¶
Point a
FlatSpinBoxat a freshly enumerated folder.The suffix carries the total, so the control reads
20 of 24576 sets. The maximum is clamped to the total so it can never read50 of 12— a cap above what exists is not a real cap.- Parameters:
box – the cap spin box; its suffix, maximum and enabled state are set with signals blocked, and it is enabled only for more than one set.
total – number of image sets in the folder; negative counts as 0.
- Returns:
the cap the box now holds, which the clamp may have lowered. Callers must feed it back to the sampler, or a folder small enough to clamp would leave the box saying 12 while the dropdown showed 50.
- spacr.qt.widgets.preview_controls.enumerate_image_sets(directory, suffixes: Sequence[str], metadata_type: str = DEFAULT_METADATA_TYPE, custom_regex: str | None = None) Tuple[List[ImageSet], List[str]][source]¶
Group a folder’s file names into image sets. Opens nothing.
Reads the directory with
os.scandir()— on Linux that answers “is this a file?” straight out of the dirent, so nostatis issued per entry — and matches each name against_get_regex(). Names the regex understands are grouped by(plateID, wellID, fieldID); names it does not become one set each, so an ad-hoc folder ofa.tif/b.tifstill lists exactly as it always did. Names that start with a dot are skipped, as a run skips them: the._<name>sidecars macOS writes on exFAT and network volumes end in.tiftoo and hold no image.- Parameters:
directory – folder to enumerate.
suffixes – lower-case suffixes that count as a source.
metadata_type – naming dialect, see
spacr.utils._get_regex().custom_regex – pattern body when
metadata_type='custom'.
- Returns:
(sets sorted by key, channel IDs found across the folder).
- spacr.qt.widgets.preview_controls.populate_channel_combo(combo: PySide6.QtWidgets.QComboBox, n_channels: int, include_all: bool = True, keep: str | None = None) None[source]¶
Refill
combowithn_channelsentries, preserving the selection.- Parameters:
combo – the dropdown to refill.
n_channels – how many channels the loaded source holds.
include_all – prepend the
ALL_CHANNELSentry.keep – entry to re-select; defaults to what is selected now.
- spacr.qt.widgets.preview_controls.populate_fov_combo(combo: PySide6.QtWidgets.QComboBox, sources: Sequence[pathlib.Path], current=None, labels: Sequence[str] | None = None) None[source]¶
Refill an FOV dropdown with
sources, selectingcurrent.Each entry stores its full path as item data, so the caller never has to reconstruct a path from the (deliberately short) visible label.
- Parameters:
combo – field-of-view dropdown to clear and refill while its signals are temporarily blocked.
sources – ordered source paths; each becomes one item whose data is the full string path.
labels – visible text per entry; defaults to each path’s file name. Set-based enumeration passes
A01 f003style labels so the entry names the field of view rather than one of its channel files.
- spacr.qt.widgets.preview_controls.sample_image_sets(sets: Sequence, max_sets: int, seed: int) List[source]¶
Draw at most
max_setsentries fromsets, spread across it.The draw is random — so the sample represents the whole plate rather than the first N names, which on a plate-ordered folder is all of row A — but the winners are then restored to their original order, so the dropdown still reads front to back.
max_setsof zero or less means “no cap”.- Parameters:
sets – the entries to draw from, in display order.
max_sets – the most entries to return.
seed – seed for
random.Random, so the same seed draws the same positions.
- spacr.qt.widgets.preview_controls.sample_seed(directory, total: int, max_sets: int, nonce: int = 0) int[source]¶
The reproducible seed a sampled preview is drawn with.
Digest of
"<folder name>|<total sets>|<cap>|<nonce>". Stable across processes and machines — unlikehash(), which is salted per interpreter — so a user can name the plate and the cap and get the same sets back.Deliberately the folder’s name, not its full path: the same plate read from a local copy and from the NAS it was acquired on must preview the same fields, or “the sample I looked at” is not a thing anyone can hand over. Two unrelated folders sharing a name draw the same positions, which selects different sets because their contents differ.
- Parameters:
directory – the folder being sampled; only its name is used.
total – number of image sets in it.
max_sets – the sample cap.
nonce – a counter that changes the seed for a re-draw.
- spacr.qt.widgets.preview_controls.selected_channel(combo: PySide6.QtWidgets.QComboBox) int | None[source]¶
Return the channel index a channel dropdown selects, or
None.NonemeansALL_CHANNELS(or an empty dropdown) — show the source exactly as it is stored.- Parameters:
combo – a channel dropdown filled from
channel_labels(); aCh <n>entry givesn, anything elseNone.
- spacr.qt.widgets.preview_controls.sets_from_paths(paths: Sequence[pathlib.Path]) List[ImageSet][source]¶
Wrap an already-listed set of sources as one
ImageSeteach.The Timelapse and Motility previews’ sources are whole folders of frames or stacked arrays — one source already is one field of view, so there is nothing to group. They still want the cap and the reproducible draw, so they feed their own listing through here and share the sampler.
- Parameters:
paths – the sources (folders or files,
stror path-like); each becomes a set keyed("", "", <name>)in its parent folder.
- spacr.qt.widgets.preview_controls.sibling_sources(path, suffixes: Sequence[str], directories: bool = False) List[pathlib.Path][source]¶
List every comparable source sitting beside
path.Names that start with a dot are left out, as a Mask run leaves them out: on exFAT, FAT and many network shares macOS writes a
._<name>sidecar beside every file, with the same ending and no image in it.- Parameters:
path – the currently-loaded file (or folder).
suffixes – lower-case suffixes that count as a source.
directories – when True, list sibling folders instead of files — the Timelapse preview’s fields of view are folders of frames.
- Returns:
sorted paths, always including
pathitself when it exists.
- spacr.qt.widgets.preview_controls.MAX_SETS_TOOLTIP = Multiline-String[source]¶
Show Value
"""Maximum number of image sets loaded by the preview. For large experiments, filenames are grouped into image sets comprising one field of view and all of its channels. The specified number of sets is sampled across the plate; files outside the sample are not opened. Sampling is deterministic: the same folder and maximum produce the same sets. Changing the maximum selects a new sample, whereas re-rendering with the same settings preserves the selection."""