spacr.qt.crop_thumbs¶
Decode object crops lazily and cache the most recent ones for hover previews.
A hover preview shows the crop under the cursor. A PNG decode takes a few milliseconds and a mouse sweep produces about a hundred hover events per second, so decoding on every event makes the plot lag behind the cursor.
Decoding therefore never happens on the paint path, and each crop is decoded once:
CropThumbnails.peek()answers from memory or not at all. It is what a hover handler calls, because the answer has to be instant or absent.CropThumbnails.pixmap()decodes on a miss. A caller runs it behind a short debounce (or on a click), never once per mouse-move event.The cache is keyed on
(path, mtime, size, px), followingspacr.crops, so a crop re-written by a re-run is re-read rather than served stale from the last session’s decode.
Why not QPixmap(path)¶
Crop PNGs are not ordinary images. Anything spaCR wrote before the BGR fix has
the first stain in the blue channel, and a 16-bit single-channel crop opened
with convert('RGB') is clipped to solid white by PIL. Both are corrected by
spacr.qt.annotate_engine.load_crop_image(), which is therefore the only
loader used here, so a hover preview shows the same colours as the annotation
grid.
Classes¶
A bounded LRU of decoded crop thumbnails. |
Functions¶
|
|
A snapshot of the decoded-thumbnail caches that still have owners. |
|
|
The crop's path on this machine, re-anchored if the dataset moved. |
Module Contents¶
- class spacr.qt.crop_thumbs.CropThumbnails(db_path: str = '', *, size: int = DEFAULT_SIZE, capacity: int = DEFAULT_CAPACITY)[source]¶
A bounded LRU of decoded crop thumbnails.
- Parameters:
db_path – the
measurements.dbthe crops belong to. Used to re-anchor moved paths and to resolve the folder’s stored channel order; optional, because a folder of crops is a legitimate source too.size – longest edge of the thumbnails, in pixels.
capacity – how many to keep.
Not a
QObject, and holds no widget: a screen can own one, a test can drive one, and neither keeps the other alive.Create the thumbnail cache.
- Parameters:
db_path – measurements database the crops are read from.
size – longest edge of a decoded thumbnail, in pixels.
capacity – how many thumbnails to keep; the least recently used are dropped first.
- __contains__(path: object) bool[source]¶
Report whether a crop path is cached.
- Parameters:
path – the crop path; coerced with
str().- Returns:
Trueif it has an entry, including a cached failure.
- cache_budget_entries()[source]¶
(key, bytes, last use, in use)rows for the global sweep.A QLabel/QGraphicsItem that is displaying a pixmap owns its own implicitly-shared Qt value. Removing this lookup entry cannot blank that control, so no thumbnail entry needs pinning here.
- drop_cache_budget_entry(key) bool[source]¶
Evict one decoded thumbnail selected by the memory policy.
- Parameters:
key – cache key of the entry to evict, an
(abspath, mtime_ns, size, px)tuple; an unknown key is ignored andFalseis returned.
- peek(path: str) PySide6.QtGui.QPixmap | None[source]¶
The thumbnail if it is already decoded, else
None. Never blocks.The call a mouse-move handler makes. Returning
Nonemeans “not yet”, not “there is no crop” — askpixmap()for that, off the hover path.- Parameters:
path – path of the crop image; an empty value returns
None.
- pixmap(path: str) PySide6.QtGui.QPixmap | None[source]¶
The thumbnail, decoding it if this is the first time.
- Parameters:
path – path of the crop image to decode; an empty value returns
None.- Returns:
the
QPixmap, orNonewhen the crop cannot be read. A failure is cached asNonerather than raised: a missing file under the cursor must not throw out of a mouse handler, and it must not be retried sixty times a second either.
- spacr.qt.crop_thumbs.crop_paths_for_keys(db_path: str, keys) Dict[str, str][source]¶
{object key: crop path}, resolved ONCE for a whole plot.spacr.active_learning.crops_for_object_keys()scans the crop table per call — suitable for opening a subset, too slow to run once per hover event. This is the plot-time call: resolve every plotted point up front and let hover be a dict lookup.That function keeps the caller’s order but drops keys it cannot resolve, so when everything resolves the answer is a zip and costs one scan. When some keys miss, the returned list is a subsequence and no longer lines up, so the range is bisected: each half is asked separately until a half is either fully resolved (zip it) or a single key (resolve it or record the miss). That is a handful of extra scans for a handful of missing crops, rather than one scan per key.
Keys with no crop are absent from the result rather than mapped to
"", which would read as a crop path.
- spacr.qt.crop_thumbs.live_thumbnail_caches()[source]¶
A snapshot of the decoded-thumbnail caches that still have owners.
- spacr.qt.crop_thumbs.resolve_crop_path(path: str, db_path: str = '') str[source]¶
The crop’s path on this machine, re-anchored if the dataset moved.
png_liststores absolute paths built at measure time, so a dataset copied to another disk resolves to nothing and every preview is blank.spacr.qt.screens.annotate._reanchor_png_path()already knows how to rebuild one from the/data/segment under the database’s own root; this borrows it rather than growing a third copy of that rule, and falls back to the stored path when the Annotate screen is not importable (a headless test, a trimmed install).- Parameters:
path – crop path as stored in
png_list; returned unchanged when it is empty or already exists as a file.