spacr.qt.annotate_engine

Pure-Python backend for the Qt annotate screen.

The image-processing pipeline (normalize / channel-filter / outline / colored border) and the SQLite-backed page fetch + background save worker are all Tk-free. The Qt screen wraps this with a QWidget UI.

Semantics mirror spacr.gui_elements.AnnotateApp so annotations made in either GUI are read/written the same way from the same measurements/measurements.db.

Module Contents

spacr.qt.annotate_engine.label_to_hex(val: int | None) str | None[source]

Map an annotation value to a hex border color.

None / 0 / non-int -> None (no border). 1 -> blue (#4A9EFF-ish), 2 -> red, 3+ -> golden-ratio hue rotation.

spacr.qt.annotate_engine.normalize_pil(img: PIL.Image.Image, percentiles: Tuple[float, float] = (1.0, 99.0), normalize_channels: Iterable[str] | None = None) PIL.Image.Image[source]

Normalize the given PIL image per-channel using percentile stretch.

If normalize_channels is None or empty, the image is returned unchanged (aside from clipping to 8-bit range).

spacr.qt.annotate_engine.filter_channels_pil(img: PIL.Image.Image, channels: Iterable[str] | None = None) PIL.Image.Image[source]

Zero out channels not present in channels (e.g. [‘r’,’g’]).

spacr.qt.annotate_engine.outline_image(base_img: PIL.Image.Image, full_img: PIL.Image.Image, outline_channels: Iterable[str] | None = None, edge_sigma: float = 1.0, edge_thickness: float = 1.0, edge_transparency: float = 100.0, edge_image: bool = False, outline_threshold_factor: float = 1.0, object_size: Tuple[int, int] = (0, 0)) PIL.Image.Image[source]

Overlay per-channel object outlines on base_img.

Mirrors AnnotateApp.outline_image (Tk) semantics: for every channel in outline_channels, compute an Otsu-thresholded foreground mask on the corresponding channel of full_img, extract the boundary, optionally dilate it, then alpha-blend it over the channel in base_img with edge_transparency/100 opacity. Peak-normalized so thin edges stay visible.

spacr.qt.annotate_engine.add_colored_border(img: PIL.Image.Image, width: int, color: str) PIL.Image.Image[source]

Return img with an inset colored border of width px.

class spacr.qt.annotate_engine.AnnotateSettings[source]

Every knob the Annotate screen exposes, packed into one dataclass.

Sensible defaults let callers instantiate AnnotateSettings() and override just the handful of fields they care about.

src: str = ''[source]
db_path: str = ''[source]
annotation_column: str = 'annotate'[source]
image_size: Tuple[int, int] = (200, 200)[source]
image_type: str | None = None[source]
channels: List[str] | None = None[source]
percentiles: Tuple[float, float] = (1.0, 99.0)[source]
normalize_channels: List[str] | None = None[source]
measurement: Any | None = None[source]
threshold: Any | None = None[source]
threshold_direction: Any | None = None[source]
outline: List[str] | None = None[source]
outline_threshold_factor: float = 1.0[source]
outline_sigma: float = 1.0[source]
edge_thickness: float = 1.0[source]
edge_transparency: float = 100.0[source]
edge_image: bool = False[source]
object_size: Tuple[int, int] = (0, 0)[source]
grid_rows: int = 5[source]
grid_cols: int = 5[source]
property page_size: int[source]

Number of thumbnails per page (grid_rows * grid_cols, min 1).

spacr.qt.annotate_engine.ensure_annotation_column(db_path: str, column: str) None[source]

Add column INTEGER to png_list if missing and index png_path.

spacr.qt.annotate_engine.count_rows(db_path: str, image_type: str | None = None) int[source]

Return the number of png_list rows, optionally filtered by image_type.

Parameters:
  • db_path – path to measurements.db; missing files count as 0.

  • image_type – optional substring to filter png_path on.

spacr.qt.annotate_engine.fetch_page(db_path: str, annotation_column: str, offset: int, page_size: int, image_type: str | None = None) List[Tuple[str, int | None]][source]

Read one page of (png_path, annotation) rows in insertion order.

spacr.qt.annotate_engine.fetch_filtered_paths(db_path: str, annotation_column: str, measurements: List[str], thresholds: List[float], directions: List[str], image_type: str | None = None) List[Tuple[str, int | None]][source]

Return ALL (png_path, annotation) rows matching every one of the measurement/threshold/direction triples.

Rows come from a merge of png_list with the measurement tables (via spacr.io._read_and_join_tables) — same code path as the Tk app — filtered on png_path substring when image_type is given. Callers paginate the returned list themselves.

spacr.qt.annotate_engine.class_counts(db_path: str, annotation_column: str) List[Tuple[int, int]][source]

Return sorted list of (class_value, count) for annotated rows.

spacr.qt.annotate_engine.clear_column(db_path: str, annotation_column: str) None[source]

Null every value in annotation_column of png_list.

Parameters:
  • db_path – path to measurements.db; missing files are ignored.

  • annotation_column – column to reset.

spacr.qt.annotate_engine.find_last_annotated_offset(db_path: str, annotation_column: str, page_size: int, image_type: str | None = None) int | None[source]

Return the page-aligned offset of the last annotated row, or None.

class spacr.qt.annotate_engine.SaveWorker(db_path: str, annotation_column: str)[source]

Runs in a daemon thread; consumes {png_path: annotation} batches from a Queue and commits them to the DB in coalesced transactions.

db_path[source]
annotation_column[source]
start() None[source]

Spawn the daemon writer thread if it isn’t already running.

stop(wait: bool = True) None[source]

Signal the writer to exit; when wait is True block up to 5 s.

submit(batch: dict) None[source]

Enqueue a copy of the batch for saving.

property busy: bool[source]

True while the writer thread is inside a commit.

property pending_batches: int[source]

Number of submitted-but-not-yet-committed batches.

property last_save_ts: float | None[source]

POSIX timestamp of the most recent successful commit, or None.