spacr.qt.screens.make_masks

Workflow inputs and outputs

Make Masks

Curate image/mask pairs for segmentation training, or use Organize for Measure to merge images and their masks into arrays Measure reads. Saving a mask does not train a classifier.

Open: Home → Make Masks.

Inputs and outputs below include conditional alternatives. The guidance and handoff notes say which route applies.

Inputs

  • Microscope images — Source image folder; original files, supported vendor files or imported TIFFs.

  • Label masks — masks/ when retained, or explicitly saved image/mask pairs. Intermediate masks may be removed by cleanup.

Outputs

  • Curated training fields — Separate image and integer-mask files with matching field identities; preserve original images and labels.

  • Label masks — masks/ when retained, or explicitly saved image/mask pairs. Intermediate masks may be removed by cleanup.

After this module

  • Cellpose Workbench: Use independently checked image/mask pairs.

  • Measure: Use Organize for Measure to merge images and their masks into the arrays Measure reads; standalone masks are not merged arrays.

  • Plaque Assay: Use plaque masks with matching source images; cell masks are not automatically plaque labels.

API reference.

Module tutorial.

Interactive editing and curation of segmentation masks.

Make Masks is filed under Tools on the home screen and is the manual half of segmentation: it corrects masks a model got wrong, one field at a time, and its masthead opens the Cellpose workflows that produced them. MakeMasksScreen loads images and labelled masks from <folder>/masks and saves edited labels as uint16 TIFF files.

THE TEN TOOLS, in TOOL_MODES order, because this vocabulary is what a reader needs before opening the screen:

Brush and Erase paint and unpaint the active label a pixel at a time. Erase object removes a whole label in one click. Wand grows a region from the pixel clicked and adds it to the mask by default. Hold Ctrl while clicking to remove that region; the tolerance is relative to the image’s intensity range by default, see spacr.qt.mask_engine.relative_tolerance(). Draw traces a free-form outline that closes and fills as ONE object – the tool a brush is not, because a brush stamps disks along the path, so tracing a rim with it labels the rim and leaves the middle background. Divide / Merge uses a left-button drag to divide and a right-button drag to merge crossed objects under the first touched identifier without changing their pixels. The merged identity is retained when saving and reopening. Dividing drags a line across a merged object and makes it two, keeping the original identifier on the larger component and giving the smaller a new one, with every other object untouched; spacr.qt.mask_engine.DIVIDE_CUT_WIDTH is the cut width. Zoom rectangle-drags the view and changes no labels at all.

Recrop is the ninth and is the only one that changes WHICH field is on screen rather than what is painted on it. A staged crop holding several cells is not one training example, and curating it as one teaches a network that two objects are one picture – so a box round an object writes that region of both the image and the mask as a field of its own (spacr.qt.mask_engine.write_recrop()), queued straight after the current field, and the multi-object original is retired into recropped_originals/ rather than curated (spacr.qt.mask_engine.retire_recropped_original()). A box shorter than spacr.qt.mask_engine.RECROP_MIN_SIDE on a side, or repeating a cut already made past spacr.qt.mask_engine.RECROP_MAX_OVERLAP, is refused; objects the box cuts through are dropped, because an object whose boundary is where the mouse was released is not that object; and the labels that survive are renumbered from one.

Ruler is the tenth. Drag between image pixels to read a length without editing labels. Zoom and pan preserve it; right-click with Ruler selected clears it. The same ruler is available on the paired live preview canvases.

Each field has a spacr.curation.CurationLog, initialized from any existing sidecar. spacr.qt.mask_engine.save_mask() writes the labels and ledger together, allowing spacr.curation.is_curated() to distinguish manually corrected masks from pipeline output. A single gesture produces one ledger entry and one undo step.

MakeMasksScreen.run_cellpose() applies the pipeline’s resolved Cellpose-SAM model to the open field and displays the mask, cell-probability map, and flow field. The intermediate outputs support evaluation of CELLPROB_THRESHOLD when detections are missing or incomplete.

Editor modes are assembled from tool_row_entries() and TOOL_MODES. MakeMasksScreen.add_toolbar_action() inserts non-mode actions into the same toolbar. The settings panel carries the operations that are not gestures – object filling and relabeling, swapping object and background, size filtering and Otsu detection, with undo and redo over all of them – alongside the brush, wand and display controls, and can be hidden to return its width to the canvas.

Image inversion and swapping object and background are separate operations. The first changes the image seen by the detector; the second changes labels.

Invert image, in the Display category, is THE inversion. It draws the field as its own negative and hands the detectors that same negative: both detect buttons and the live magnifier segment MakeMasksScreen._detector_image(), so a threshold written for bright objects takes dark ones, and a banner above the image says so for as long as it is on. The corner readout’s pixel intensity follows the inverted image. Object mean intensities and the object filter still read the loaded pixels. Saving writes label masks without changing the source image.

IT USED TO BE TWO SWITCHES AND THEY COULD DISAGREE. A display-only “Invert image” drew a negative and left detection untouched; “Invert for detection” inverted what the detectors read and drew nothing. A curator could hold either without the other, and the first one’s status line had to contradict the second one’s banner. They are one switch because the reason for the switch is one intention: inverting a field of dark objects so that Otsu and the magnifier can detect them. _cp_invert is now the same widget object as _invert_display under its old name, so the two cannot come apart.

Swap object and background, in Object operations, is the old “Invert mask” under the name that describes it – it flips the LABEL image, which on an ordinary field leaves one object covering the frame. It is not a picture invert and is deliberately not called one.

THE ARITHMETIC. invert_normalized() normalises the field to 0..1 on its own range and takes 1 - v, and the normalisation is not incidental: the Otsu correction is a MULTIPLIER on an absolute level, so normalising first is what makes one correction value mean the same thing on the next image. invert_intensity() (dtype complement) and invert_for_detection() (reflection about the field’s range) are what this screen used before and are kept for other callers. invert_mask() flips labels.

Additional segmentation tools are opened from the masthead in FOLD_ORDER through FoldStrip. Mask the whole folder applies Cellpose to every image in the selected folder, while Save mask on the Curate page calls spacr.curation.MaskCuration.save_mask(). Folded modules retain their existing widgets inside FoldedModulePanel.

Time-series tracking is integrated with Mask Generation as the Timelapse settings category; see spacr.qt.screens.mask. Motility Assay instead belongs to Measure because it consumes existing masks and writes measurement tables rather than generating masks.

Classes

FoldedModulePanel

One folded module, as the whole screen it was, plus what the host adds.

MakeMasksScreen

Qt widget for the Make Masks app — the successor to Tk ModifyMaskApp.

NapariBridgeScreen

Exchange an image and label mask with an interactive napari viewer.

ObjectFilterList

Make Masks' object filters: one row per regionprop the user added.

Functions

canonical_magnifier_mode(→ str)

The name a magnifier mode goes under today.

cellpose_detect(→ tuple)

Segment one field with model; return labels and both intermediates.

cellpose_intermediates(→ tuple)

Pull (cellprob, flow_rgb) out of one image's flows entry.

cellprob_heatmap(→ numpy.ndarray)

Cellpose's probability map as an RGB heatmap, (H, W, 3) uint8.

flow_rgb(→ Optional[numpy.ndarray])

Cellpose's flow field as (H, W, 3) uint8, or None if it is not one.

fold_description(→ tuple)

(name, description, stage) for a folded module.

is_headless(→ bool)

Return True when no interactive display is attached to this process.

load_cellpose_model(model_name)

Load a Cellpose model through spaCR's own resolver.

stretch_to_uint8(→ numpy.ndarray)

Percentile-stretch any float array to 0-255 uint8.

tool_row_entries(→ List[tuple])

Every canvas tool the toolbar row should hold.

Module Contents

class spacr.qt.screens.make_masks.FoldedModulePanel(key: str, screen: PySide6.QtWidgets.QWidget, title: str, parent: PySide6.QtWidgets.QWidget | None = None, actions=())[source]

Bases: PySide6.QtWidgets.QWidget

One folded module, as the whole screen it was, plus what the host adds.

A fold that reimplemented the module it replaced would keep whatever the person doing the folding happened to think of and quietly drop the rest. So the button opens the module’s OWN widget: every control, every worker, every drop target it had as a tile is what arrives, and the only thing that changed is where it is opened from.

IT IS A PAGE ON THIS SCREEN, not a window over it. A window is the last resort for a fold, and it is what this becomes only when the host has no body to make pages out of — see spacr.qt.screens.map_barcodes.show_as_page(). As a page it is closed by the tab’s own close button, so the standard Close button below belongs to the window shape alone and is added with it.

Parameters:
  • key – the folded module’s registry key.

  • screen – the module’s own widget, already built.

  • title – the page’s caption and the window title — the module’s name.

  • actions – extra buttons for the button row, each (label, tooltip, callback). This is where a capability the folded module lacks and its host has arrives.

  • parent – parent widget; ownership only.

Wrap one folded module’s screen with a title and its actions.

Parameters:
  • key – the module’s registry key.

  • screen – the screen to wrap.

  • title – the caption over it.

  • parent – parent widget.

  • actions – extra buttons for the panel’s own row.

add_close_button() → None[source]

Give this panel the Close button a window needs.

A page is closed by its tab. A window has no tab, so the row that carries the host’s extra actions carries a Close beside them — added when the panel becomes a window rather than always, so a page never shows a button that would hide it inside its own tab.

class spacr.qt.screens.make_masks.MakeMasksScreen(parent: PySide6.QtWidgets.QWidget | None = None)[source]

Bases: PySide6.QtWidgets.QWidget

Qt widget for the Make Masks app — the successor to Tk ModifyMaskApp.

Owns the canvas, the tools panel, and the file-navigation state; see the module docstring for the full feature list.

Parameters:

parent – parent widget.

Build the editor, its canvas and its tool panel.

Parameters:

parent – parent widget.

add_toolbar_action(button: PySide6.QtWidgets.QPushButton) → PySide6.QtWidgets.QPushButton[source]

Insert a non-mode action into the editor toolbar.

The button is placed after the other actions in the part of the row that scrolls. The Magnifier and the settings toggle are pinned outside it, so nothing added here can come between them or push them out of sight.

Parameters:

button – Action button to insert.

Returns:

The same button.

apply_object_filter(*, on_load: bool = False) → int[source]

Apply the filter list to the field on screen; return how many it hides.

Runs itself when a field loads – a draft segmentation usually arrives with the same class of junk in every field – whenever the list is edited, and when the user presses Filter.

One engine: mask_engine.apply_filters(), the same one Mask generation’s object_filters setting runs, with one regionprops_table pass for every property the list names. The result is one undo step and one ledger entry that records the whole list, so the mask’s provenance says which filters shaped it.

Every hidden object is written into the Filter category’s ledger, one red row each, cleared at the start of every run.

Parameters:

on_load – True when this is the automatic run on opening a field. It starts a fresh baseline and keeps a run that hid nothing quiet.

clear_mask() → None[source]

Zero the current mask without asking, recording it in history.

The confirmation lives in _on_clear_mask(); this is the scriptable entry point (and what the undo stack sees).

closeEvent(event)[source]

Drain the background image loader before Qt destroys this screen.

_MaskLoadWorker is parented to this widget, so without this the screen’s destructor deletes a QThread that is still decoding a large TIFF, and Qt answers that with qFatal("QThread: Destroyed while thread is still running") — a core dump, not an exception. The window is exactly as wide as one image decode, which is why it shows up in a loaded test shard and almost never by hand.

Any folded module still open goes with it: each one is a window of its own, and several of them own worker threads and viewers that must be told to stop rather than be collected out from under Qt. A model download is cancelled; a backend install is left to finish, because pip stopped half way can leave the environment broken.

Parameters:

event – the close event; it is passed on to the base class once the workers and folded modules have been stopped.

close_folded() → None[source]

Close every folded module, and everything it started.

Closing the panel is not enough. A module polls the machine’s RAM and GPU on a worker thread, and Qt answers a running QThread being destroyed by aborting the process — so the module’s own close handler, which drains that worker, has to run. A page nested inside another module’s tabs never gets one from Qt, which is why they are closed by hand here.

EVERY MODULE THAT WAS BUILT, not every module that was opened. A module is built the moment something asks this screen to point it at the open folder — seed_folded() does, and so does any test or caller reaching for folded_screen() — and pointing Model Compare at a folder starts a load thread before its panel has ever been on screen. Walking the panels alone left those threads running with nothing holding them, and the process died of it several actions later, in whatever happened to be running when the memory behind them was touched.

contribute_images_and_masks(*, show: bool = True, upload=None, threaded: bool = True)[source]

Open the dialog that sends images and masks to a community dataset.

One click from curation: the dialog opens on the image on screen and its mask, with every curated image of the folder one choice away, and an images folder plus a masks folder as the third way in.

Parameters:
  • show – show the dialog; False only builds it (tests).

  • upload – fn(folder, target) -> url in place of the real upload (tests).

  • threaded – send on a worker thread.

Returns:

the dialog.

download_zoo_model(entry) → bool[source]

Ask, then download a zoo Cellpose model and select it when it lands.

The download goes through spacr.model_zoo.install() on a worker thread (spacr.qt.model_install.CheckpointDownload), into the folder the Model zoo picker uses, so either route finds the file the other fetched. The bar under the Model row shows the bytes as they arrive, and the screen stays usable. A model that publishes no checksum is downloaded only after the user has been told spaCR cannot then check it.

Parameters:

entry – the zoo’s record of the model.

Returns:

True when a download was started.

export_all_rois(target: str, fmt: str) → List[str][source]

Write every field’s mask as ROIs.

GeoJSON and ImageJ are one file per field in the folder target (<stem>.geojson, <stem>_RoiSet.zip), the way QuPath and Fiji open them beside the image; COCO is one dataset file, target, holding every field. The field on screen is exported as it is on screen; the others as saved. Fields without objects are left out.

Parameters:
  • target – the folder (GeoJSON, ImageJ) or file (COCO).

  • fmt – "geojson", "imagej" or "coco".

Returns:

the files written.

export_field_rois(path: str, fmt: str | None = None) → str[source]

Write the mask on screen as QuPath GeoJSON, a RoiSet or COCO JSON.

Parameters:
  • path – the file to write.

  • fmt – "geojson", "imagej" or "coco"; default from the suffix of path.

Returns:

the path written, or "" when no field is open.

finish_recrop() → bool[source]

Archive a recropped source field and advance to its first child.

The source image, mask, and ledger move to recropped_originals/ and remain recoverable through the recrop manifest.

Returns:

True if a field was archived.

folded_screen(key: str) → PySide6.QtWidgets.QWidget | None[source]

The folded module’s own screen, built on first use and kept.

Parameters:

key – one of FOLD_ORDER. Keys that share a screen — see FOLD_HOSTS — resolve to the one widget that hosts them both.

Returns:

the screen, or None for a key this screen does not fold.

import_all_rois(source: str, fmt: str, object_type: str | None = None) → List[str][source]

Import ROIs for every field that has them and save them as masks.

GeoJSON and ImageJ files are found in the folder source by the field’s stem (<stem>.geojson, <stem>_RoiSet.zip …); a COCO source is one file matched on file_name. Each matched field’s mask is written as _on_save() writes it; the field on screen is replaced on the canvas instead, as one undoable edit, and saved with the rest when the user saves it.

Parameters:
  • source – the folder (GeoJSON, ImageJ) or file (COCO).

  • fmt – "geojson", "imagej" or "coco".

  • object_type – the object type to take from files of several; default the folder’s own type, else the first in the file.

Returns:

the names of the fields that were given masks.

import_field_rois(path: str, fmt: str | None = None, object_type: str | None = None) → int[source]

Replace the mask on screen with the objects of a ROI file.

One edit on the undo stack and in the curation ledger; nothing is written until the mask is saved. A file of several object types puts object_type on the canvas, else the folder’s own type, else the user’s choice.

Parameters:
  • path – the GeoJSON, RoiSet or COCO file.

  • fmt – its format; default from the file.

  • object_type – the object type to take.

Returns:

the number of objects on screen afterwards, or -1 when nothing was imported.

mask_settings() → dict[source]

The configured chain and PSF as the Mask module’s settings.

spacr.qt.detect_chain.chain_settings() writes the image steps as enhance_* keys and the PSF controls write the psf_* keys, which is exactly what spacr.psf_pipeline.prepare_chain() reads back, so the chain a plate run applies is the chain configured here – whether or not Apply is on, since Apply is this screen’s switch.

mask_whole_folder() → bool[source]

Segment every image in the open folder with the current model.

The one folded button that does something rather than opening something: it points the applying half of the Cellpose workbench at the folder already open here and starts it. “The current model” is whatever that tab holds — the checkpoint the Train tab produced if there is one, and the stock model otherwise.

The Apply half writes one mask per image into <src>/masks, which is the nested layout’s masks folder and nobody else’s: in a sibling session it would be images/masks, beside the set’s real masks rather than in them, and a folder of _seg.npy bundles has no images for it to read. Both are refused, in the status line.

Returns:

whether a run was started. A folder that is not open, a session whose masks are not in <folder>/masks, and a confirmation that is declined, all answer False.

open_folded(key: str) → FoldedModulePanel | None[source]

Open a folded module on this screen, pointed at the open field.

The module arrives as a PAGE beside the editor, which is where a fold belongs; it becomes a window only if this screen has no body to make pages out of.

Parameters:

key – one of FOLD_ORDER.

Returns:

the module’s panel, or None for a key this screen does not fold. Pressing the same button again raises the page that is already there rather than building a second one.

open_paths(paths) → bool[source]

Open a drop the way its contents say, asking where there is a choice.

spacr.drop_classification.classify_drop() says what was dropped, and:

  • image files (with or without folders) open as one queue, in drop order, each field edited where it lies with its mask in its own <folder>/masks – _open_queue();

  • one folder of images opens as it is;

  • one folder whose subfolders look like channels (DAPI/, GFP/…, or the same fields in each) opens “Organize for Measure” with a channel column per subfolder; any other folder whose images sit in subfolders is offered for consolidation;

  • several folders of images open “Organize for Measure” with a channel column per folder;

  • when that popup is cancelled (or nobody can see it), the drop opens as it always did: the folder as it is, or a queue;

  • images dropped with their masks open with those masks (_open_with_masks());

  • a folder spaCR wrote is named in the console and its images, if any, open – a .npy is never opened as an image.

Everything the drop held that is used for nothing is listed in the console.

Parameters:

paths – the dropped files and folders, in the order dropped.

Returns:

whether something was opened or a job started.

open_queue(queue) → bool[source]

Open the fields a curation session offers, in the order it offers.

The session decides WHICH fields and in WHAT ORDER – reviewed ones already dropped, --limit already applied – and this screen shows them. All three layouts are edited where they lie:

  • nested opens the queue folder, masks in <folder>/masks;

  • sibling opens <folder>/images and reads and saves the masks in <folder>/masks beside it, never images/masks;

  • seg opens the queue folder with the _seg.npy bundles as its fields, each saved back into itself.

What the session had to say – a prob or easy order that fell back for want of scores, fields the scores do not name, a resume record beside the folder rather than in it – is put on screen with it, not only in the terminal that started it. It stays on the status line after the first field loads, including a field large enough to load in the background, which lands after this returns.

Parameters:

queue – a spacr.curation_queue.CurationQueue.

Returns:

whether the editor is now on that session.

recrop(x0: int, y0: int, x1: int, y1: int) → str | None[source]

Extract one selected region into a separate training field.

Accepted crops are written immediately, inserted after the source field in the queue, and marked on the canvas. Rejected selections are reported in the status label without modifying the queue.

Parameters:
  • x0 – x of the first selection corner, in image pixels.

  • y0 – y of the first selection corner, in image pixels.

  • x1 – x of the opposite corner, in image pixels. The corners may come in either order and are clipped to the image.

  • y1 – y of the opposite corner, in image pixels.

Returns:

Filename of the recropped field, or None if the selection was rejected or could not be written.

run_cellpose() → int[source]

Segment the open field with the chosen model; objects found.

The two panes are filled BEFORE the mask is touched, and they are filled even when the run found nothing at all. A run that returns an empty mask is exactly the run whose probability map you need to see: it says whether the network found nothing, or found plenty and the threshold threw it away.

This programmatic method is synchronous and returns the object count. The toolbar uses a background worker instead, taking a snapshot and discarding results after field changes or mask edits. If toolbar detection is already running, this method returns zero without starting a second run.

With Invert on the model is given the INVERTED field (_detector_image()), and the status line and the ledger entry both say so.

save_curated_mask() → str[source]

Write the labels Curate corrected back to the mask file.

The Curate page retains corrected labels in its curation session. This method calls spacr.curation.MaskCuration.save_mask(), which writes the labels and, when edits exist, writes the corresponding curation ledger beside the output file.

Returns:

the path written, or "" when there is nothing to write.

seed_folded(key: str) → dict[source]

Point a folded module at the field this screen has open.

The whole reason these are buttons on this masthead rather than rows of their own is that the folder is already chosen here; a folded module that opened on an empty path would have folded the file dialog in with it.

Parameters:

key – one of FOLD_ORDER. Note that this is the button’s key, not its host’s: the two Cellpose halves share a screen and seed different halves of it.

Returns:

what was seeded, as {name: value}. Empty when no folder is open, which is not a failure — the module opens on its own file picker exactly as its tile did.

settings_shown() → bool[source]

Whether the settings group is on screen.

class spacr.qt.screens.make_masks.NapariBridgeScreen(parent: PySide6.QtWidgets.QWidget | None = None)[source]

Bases: PySide6.QtWidgets.QWidget

Exchange an image and label mask with an interactive napari viewer.

napari is imported only when the viewer is opened, and spaCR’s existing Qt event loop remains active. Corrected labels are validated and recorded in the same curation ledger used by the Curate screen. The corresponding headless operations are available from spacr.napari_bridge.

Parameters:

parent – parent widget.

Build the bridge’s two path rows and its launch button.

Parameters:

parent – parent widget.

closeEvent(event)[source]

Stop background work and unlink before going away.

Parameters:

event – the Qt close event.

close_viewer() → None[source]

Close the active napari viewer, if present.

describe_mask(path: str = '') → str[source]

Describe a mask file and its recorded curation state.

image_path() → str[source]

Return the optional source-image path entered in the form.

mask_path() → str[source]

Return the mask path entered in the form.

open_in_napari() → Any[source]

Open the selected image and mask in napari.

Returns:

The napari viewer, or None if validation or startup fails. Missing optional dependencies are reported in the status pane.

say(text: str, *, append: bool = False) → str[source]

Display a status message and return the complete displayed text.

Parameters:
  • text – the message; converted to str.

  • append – add the message below the current text, after a blank line, instead of replacing it.

set_paths(mask: str = '', image: str = '') → None[source]

Set the mask and optional source-image paths.

take_mask_back()[source]

Import corrected labels and record the mask correction.

Returns:

spacr.napari_bridge.CorrectionResult, or None if no active handoff exists or validation fails.

viewer() → Any[source]

Return the active napari viewer, or None.

class spacr.qt.screens.make_masks.ObjectFilterList(parent=None)[source]

Bases: PySide6.QtWidgets.QWidget

Make Masks’ object filters: one row per regionprop the user added.

The Filter category used to hold four fixed boxes – minimum and maximum area, minimum and maximum mean intensity. It now starts EMPTY, and “Add a filter” offers every scalar property skimage.measure.regionprops() computes (see mask_engine.filter_properties()); each added row carries the property, a minimum, a maximum and a Remove button. A blank bound is off. The old four are two of the rows a user can add: area and intensity_mean.

The intensity statistics are offered only while an intensity image is open (set_intensity_available()), so a property that cannot be measured is never offered, rather than offered and refused later.

filters() is the serialised list – the same one Mask generation reads from object_filters – and changed fires whenever it may have changed, which is what applies the list live.

Build the Add control and the empty row list.

add_filter(name, minimum=None, maximum=None, *, notify: bool = True) → dict[source]

Add one row; return it as {widget, property, min, max, remove}.

Parameters:
  • name – the regionprop the row filters on.

  • minimum – the lower bound, or None for none.

  • maximum – the upper bound, or None for none.

  • notify – emit changed once the row is added.

Raises:

ValueError – when name is not a scalar regionprop, or is an intensity property while no intensity image is open.

filters() → List[dict][source]

The rows as the serialised filter list the engine and a run read.

Raises:

ValueError – when a row’s minimum is above its maximum.

offered() → List[str][source]

The properties the Add control offers now.

remove_filter(index: int) → None[source]

Remove row index; the objects only it hid come back.

Parameters:

index – the row, in the order rows were added.

rows() → List[dict][source]

The rows, in the order they were added.

set_bounds(index: int, minimum=None, maximum=None) → None[source]

Set row index’s bounds as if typed, and apply them.

Parameters:
  • index – the row, in the order rows were added.

  • minimum – the lower bound, or None to clear it.

  • maximum – the upper bound, or None to clear it.

set_filter(name, minimum=None, maximum=None) → None[source]

Set the bounds of the first row for name, adding it if absent.

Parameters:
  • name – the regionprop, current or legacy spelling.

  • minimum – the lower bound, or None to clear it.

  • maximum – the upper bound, or None to clear it.

set_filters(filters) → None[source]

Replace every row with filters, a list or a legacy bounds dict.

A dict of the old four bounds (min_area and the rest) is migrated by mask_engine.legacy_filters(), so a saved state from before the filter list opens as the rows it meant.

Parameters:

filters – a filter list in any form mask_engine.normalise_filters() accepts, or the legacy dict.

set_intensity_available(available: bool) → None[source]

Offer the intensity properties only when an image is open.

Parameters:

available – whether an intensity image is open.

spacr.qt.screens.make_masks.canonical_magnifier_mode(mode) → str[source]

The name a magnifier mode goes under today.

Parameters:

mode – a mode name, possibly one this screen has renamed.

Returns:

the current name; an unknown mode is handed back unchanged, so _segment_region() can still say it does not know it.

spacr.qt.screens.make_masks.cellpose_detect(image: numpy.ndarray, model, *, diameter: int = 0, normalize: bool = True, flow_threshold: float = FLOW_THRESHOLD, cellprob_threshold: float = CELLPROB_THRESHOLD, min_size: int = 0) → tuple[source]

Segment one field with model; return labels and both intermediates.

The image goes in as a batch of one, which is what spacr.spacr_cellpose.parse_cellpose4_output() — the repository’s own reader of this return value — is written for. Handed a bare 2-D array instead, eval returns a flat three-member flows list and that function reads len(masks) as the number of images and finds the image height, so the parse fails on an image that segmented perfectly well.

diameter is passed as None when it is 0, which is Cellpose’s “work it out from the image”; it is the one pre-SAM sizing argument Cellpose 4 still honours, since eval rescales by 30/diameter.

Parameters:
  • image – one 2-D field, as the canvas holds it.

  • model – a loaded CellposeModel (see load_cellpose_model()), or anything with the same eval.

Returns:

(labels, cellprob, flow_rgb) — an int32 label image, and the two maps as cellpose_intermediates() reads them.

spacr.qt.screens.make_masks.cellpose_intermediates(flows) → tuple[source]

Pull (cellprob, flow_rgb) out of one image’s flows entry.

Measured against cellpose 4.2.1.1: CellposeModel.eval returns (masks, flows, styles), and for one 2-D image flows is a list of three arrays that are three different things — flows[0] an (H, W, 3) uint8 RGB rendering of the field, flows[1] the (2, H, W) float32 vectors, and flows[2] the (H, W) float32 cell-probability map. Indexing it as though the members were interchangeable is how a flow pane ends up showing the probability map.

Parameters:

flows – one image’s flows list, as spacr.spacr_cellpose.parse_cellpose4_output() hands it over per image, or the raw list from a single-image eval.

Returns:

(cellprob, rgb), either of which may be None when this Cellpose did not produce it.

spacr.qt.screens.make_masks.cellprob_heatmap(cellprob: numpy.ndarray) → numpy.ndarray[source]

Cellpose’s probability map as an RGB heatmap, (H, W, 3) uint8.

A greyscale probability map beside a greyscale image is two pictures that look alike and mean different things. A colour ramp says at a glance which pixels the network was confident about, which is the whole reason to look at this map before moving a threshold.

Matplotlib’s magma is used where it is importable, and a plain black-to-white ramp stands in where it is not, so the pane is never the thing that fails.

Parameters:

cellprob – Cellpose’s cell-probability map, a 2-D float array; it is percentile-stretched with stretch_to_uint8() before colouring.

spacr.qt.screens.make_masks.flow_rgb(flow: numpy.ndarray) → numpy.ndarray | None[source]

Cellpose’s flow field as (H, W, 3) uint8, or None if it is not one.

eval hands back the flow field twice over and the two entries are not the same thing: flows[0] is already an RGB picture of the field (hue = direction), while flows[1] is the raw (2, H, W) vector field. This takes the picture where it is given one and builds an equivalent from the vectors otherwise, so the pane fills whichever entry a caller passes.

Parameters:

flow – either Cellpose’s RGB flow picture, an array of shape (H, W, 3 or more), or the raw vector field of shape (2, H, W); None or any other shape gives None.

spacr.qt.screens.make_masks.fold_description(key: str) → tuple[source]

(name, description, stage) for a folded module.

The app registry answers while it still holds the module’s row; once the row has been dropped — which is what folding a module ends in — the answer comes from FOLD_FALLBACK, so the button goes on carrying the name, the sentence and the maturity colour its tile had.

Parameters:

key – the app registry key of the folded module; an unknown key gives empty strings.

spacr.qt.screens.make_masks.is_headless() → bool[source]

Return True when no interactive display is attached to this process.

A modal QMessageBox runs its own event loop and only returns once somebody clicks a button. Under the offscreen / minimal Qt platform plugins — CI, a headless server, an SSH session with no X — nobody can, so the call never returns and the whole app hangs. Any message triggered by data rather than by a user gesture therefore has to degrade to the status line instead.

Sibling screens (align / batch / convert / report / plate_view / …) solve this by never opening a modal at all — see their _set_status docstrings, which cite this screen as the case that actually hung. That is not sufficient here because “Clear mask” genuinely needs a yes/no answer, so this screen keeps the modal when — and only when — there is somebody able to answer it.

spacr.qt.screens.make_masks.load_cellpose_model(model_name: str)[source]

Load a Cellpose model through spaCR’s own resolver.

spacr.utils._resolve_cellpose_pretrained() is what the pipeline itself calls: it maps every pre-SAM name onto cpsam, keeps a fine-tuned checkpoint path as itself, and raises rather than quietly substituting stock weights for a checkpoint that is not there. Going around it with a second, simpler call would give this screen a different answer from the run it is meant to be correcting.

ON A CPU THE WEIGHTS ARE LOADED IN FLOAT32. Cellpose-SAM loads them in bfloat16 by default, and a processor without native bfloat16 arithmetic runs every tile through a slow emulation of it: measured on an AMD Ryzen 9 5950X, one 256 px tile took 162-197 s in bfloat16 and 31-46 s in float32, alternating, under the same load. A whole field is a hundred such tiles, so this is the difference between most of an hour and most of a day. On a GPU nothing changes: spacr.accelerator.cellpose_kwargs() decides there, as it does for the pipeline.

A cellpose3:<name or path> model – a stock Cellpose 3 model or a bioimage.io Cellpose 3 checkpoint – is not a Cellpose 4 model at all: Cellpose 4 would load such a checkpoint and segment nonsense with it. It is loaded by _backend_model(), in the Cellpose 3 backend’s own environment, as Mask generation loads it. A cellpose_dino:<path> model, a Cellpose-DINO checkpoint, is loaded the same way in the Cellpose-DINO backend, and a stardist:, instanseg: or omnipose: model in its own backend.

Parameters:

model_name – a Cellpose model name, the path of a fine-tuned checkpoint, resolved by spacr.utils._resolve_cellpose_pretrained(), or cellpose3:<name or path>.

spacr.qt.screens.make_masks.stretch_to_uint8(array: numpy.ndarray, lower_pct: float = 1.0, upper_pct: float = 99.0) → numpy.ndarray[source]

Percentile-stretch any float array to 0-255 uint8.

The probability map runs roughly -12..+8 and the flow components ±5; the intensity image is 16-bit counts. Shown raw beside each other they are three different scales and none of them is readable. Stretching each one by its own percentiles is what puts them on the same footing as the contrast-stretched intensity image the canvas already draws, which is the only way the panes can be compared with it by eye.

Parameters:
  • array – any numeric array; it is read as float32, and an empty array gives an empty uint8 array.

  • lower_pct – percentile mapped to 0.

  • upper_pct – percentile mapped to 255.

spacr.qt.screens.make_masks.tool_row_entries() → List[tuple][source]

Every canvas tool the toolbar row should hold.

TOOL_MODES first, in its own order, then any other MODE_* constant in this module the table does not mention — labelled from its own value and drawn with whatever spacr.qt.iconset.icon() has for that name, which is a fallback glyph when it has nothing. Alphabetical among themselves, so the row is the same on every run.

MODE_WAND_ERASE is the legacy canvas action, now selected with Ctrl on the single Wand tool rather than a separate toolbar button. MODE_NONE is excluded because it is not a tool: it is the canvas with no tool held, which is what the row shows when nothing is checked.

Nested helpers

MakeMasksScreen._apply_magnifier_drag.common(field, default)

Return a shared frame value, or the explicit mixed/unknown value.

spacr/qt/screens/make_masks.py:16481

MakeMasksScreen._apply_uncertainty_ranking.stem(name: str) → str
Parameters:

name – a field’s file name.

Returns:

its stem, as the curation queue names it.

spacr/qt/screens/make_masks.py:10151

MakeMasksScreen._build_methods_card.row(field: str, caption: str, widget, tip: str) → None

Add one parameter row and remember it under its field name.

spacr/qt/screens/make_masks.py:13671

MakeMasksScreen._build_propagate_card.row(field: str, caption: str, widget, tip: str) → None

Add one parameter row and remember it under its field name.

spacr/qt/screens/make_masks.py:14039

MakeMasksScreen._end_blind.ask()

Confirm revealing field paths and recording the unblind event.

spacr/qt/screens/make_masks.py:10323

MakeMasksScreen._offer_prompt_install.watch(dialog)

Put the install’s progress and its ending in the console.

spacr/qt/screens/make_masks.py:16154

_Sam2SeedDialog.run.work()

Propagate the seeds off the GUI thread and report the labels.

spacr/qt/screens/make_masks.py:17888

_counting_tiles.before_a_tile(_module, inputs)

Count this call’s tiles, or stop the run here.

spacr/qt/screens/make_masks.py:3720

_uncertainty_snapshot._score(image)

Score primary TTA and the optional second model in one frame.

Parameters:

image – one field.

Returns:

its uncertainty over four or eight aligned passes.

spacr/qt/screens/make_masks.py:4851

_uncertainty_snapshot.secondary(image)

Segment a transform with the captured second model.

Parameters:

image – one transformed field.

Returns:

labels, cell probability and vectors.

spacr/qt/screens/make_masks.py:4841

_uncertainty_snapshot.segment(image)
Parameters:

image – one transformed field.

Returns:

its labels, cell probability and flow vectors under the captured detection settings.

spacr/qt/screens/make_masks.py:4825