spacr.seg_qc

Score segmentation masks the moment they exist, before Measure runs.

Why this exists

measure_crop is the expensive step in spaCR: it opens every mask, cuts every object out of every channel, computes hundreds of features per object and writes them to SQLite. On a full plate that is hours. A plate whose segmentation collapsed — a channel out of focus, a diameter two-fold wrong, a confluent monolayer that fused into slabs — costs exactly the same hours and then produces a measurement table that has to be thrown away. The failure is obvious from the masks alone; nobody looks, because looking means opening a thousand .npy files by hand. Mask statistics can highlight suspicious fields, but cannot establish their cause or validate biological interpretation.

So this module looks. It reads the label masks that spacr.object has just written, scores every field against a handful of robust statistics, and prints a scorecard naming fields that exceed configured thresholds. It changes nothing: seg_qc='report' (the default) surfaces the flags, it does not silently drop fields. Deciding which fields to keep is the user’s call and it needs the evidence, not a filter.

Design constraint: this runs right after segmentation on every field of the plate, so it must be cheap. Everything below is np.bincount on a label image — microseconds per field. The one expensive check (the distance-transform cross-check for fusion) runs only on fields dense enough for fusion to be the explanation, so a healthy plate never pays for it. Like spacr.diameter, this module imports no torch and no cellpose — that is a tested property (see tests/test_seg_qc.py), not an aspiration.

What is measured, and why that number

Every threshold below is a keyword argument with a default in QC_DEFAULTS, and every one of them is exposed as a seg_qc_* setting (see SETTING_KEYS) so a user with 3 px organelles or a deliberately confluent assay can move it.

object count and its deviation from the plate median

count_ratio is n_objects / plate_median; outside [seg_qc_count_ratio, 1/seg_qc_count_ratio] (0.25 to 4-fold) the field is flagged. Such a difference may reflect treatment, infection, seeding, acquisition or segmentation. Inspect the experimental layout and images; the count ratio alone cannot distinguish those explanations.

under-segmentation (fused objects)

Detected the way spacr.diameter detects it, and for the same reason: both halves of the signature are required. The field must be dense enough for fusion to be the explanation (foreground at or above seg_qc_foreground_fraction, default 0.35 — the same number diameter.estimate_diameters(fused_fraction=...) uses, because it is the same question), and the mask must under-count what the pixels support: either one label covers more than seg_qc_max_object_fraction of the field (0.25, again diameter.py’s number), or the Euclidean distance transform of the foreground resolves at least seg_qc_split_ratio (2.0) inscribed-circle maxima per mask object. Requiring both matters in each direction: a dense but correctly separated field reaches 35% foreground and is fine, while an elongated or hollow object shatters the distance transform into several maxima and is also fine. diameter.py demands a 5-fold seed excess because it compares against raw Otsu components, where a confluent monolayer is one blob; here the comparison is against a Cellpose mask that already separates most objects, and the smallest fusion worth catching — every object being a welded pair — is exactly 2. Large, irregular or confluent biological objects can also trigger the heuristic; confirm boundaries against the raw images.

over-segmentation (shattered objects)

Two signatures. Absolutely: at least seg_qc_tiny_fraction (0.30) of the field’s objects are under seg_qc_min_diameter (5 px) across — spaCR’s diameter estimator already discards components below 4 px as debris, but these defaults must be adapted for genuinely small objects. Relative to the plate: the field holds seg_qc_size_ratio-fold more objects than the plate median at 1/seg_qc_size_ratio of its median diameter. That knob defaults to 1.4 = sqrt(2) on purpose — two objects welded into one have sqrt(2) times the equivalent diameter of one, and one object split in two has 1/sqrt(2) of it, so 1.4 is the fused-pair / split-in-half signature itself rather than an arbitrary tolerance.

% border-touching objects

Objects that touch the field edge are truncated, so their crops are cut off and their areas understate the truth. For objects of diameter d on a W-wide field the geometric expectation is about 2*d/W — 8% for 60 px cells on a 1400 px field. seg_qc_border_fraction defaults to 0.30: a configurable review threshold, not a universal geometric limit.

size outliers

The fraction of objects whose equivalent diameter falls outside median ± seg_qc_outlier_mad * 1.4826 * MAD. Median and MAD, not mean and standard deviation: five pieces of debris inflate a standard deviation until nothing is an outlier any more, which is the exact case this check exists to catch (tests/test_seg_qc.py contrasts the two). k = 5 is deliberately loose — real size distributions are lognormal and heavier tailed than Gaussian, so 3 sigma flags a few percent of a perfectly good field. The flag fires when more than seg_qc_outlier_fraction (0.15) of objects are out there. Heavy tails, mixed populations and segmentation defects can all trigger it; the flag does not identify which is present. Border objects are excluded from every size statistic, exactly as diameter._region_diameters excludes them, because a truncated object’s area is a lie.

empty / near-empty fields

Zero objects is a failure. Fewer than seg_qc_min_objects (10) is a warning and suppresses the per-field robust statistics, because below ten objects a MAD is one object’s opinion — the same threshold at which diameter.py collapses its confidence two levels. If the plate median is also below that floor (a low-MOI pathogen channel, say), empty fields are demoted to a warning: that is a property of the assay, not of the field.

Public API

FieldQC

One field’s verdict: counts, flags, the numbers behind them, severity.

score_field(mask, object_type, **thresholds)

Score one label image, with no plate context.

score_masks(source, object_type, ...)

Score a folder of .npy masks, a 3-D stack or a list of 2-D masks, then add the plate-relative flags.

summarize_qc(field_qcs)

Plate-level rollup: verdict, counts per severity, the failing field names.

format_scorecard(field_qcs)

The printable card.

write_scorecard(field_qcs, dst, object_type)

<dst>/qc/segmentation_qc_<object_type>.csv, one row per field.

run_segmentation_qc(...)

What spacr.object calls: score, write, print, honour seg_qc.

thresholds_from_settings(settings)

Pull the seg_qc_* knobs out of a settings dict.

Reading the verdict back — what a GUI shows before Measure

Everything above runs once, at mask time, and writes a CSV. The second half of this module reads that CSV back and turns it into something a user can act on, without scoring a single mask again:

FLAG_GUIDANCE / explain_flag(flag)

Every flag in FLAGS in plain language: what it means for the measurements, possible explanations, and what to inspect. Where uneven illumination is one possible cause, the entry says so and points at spacr.illumination, which estimates the lamp profile from the plate’s own fields and divides it out.

parse_field_name(name) / FieldAddress

plate1_E07_3 → plate plate1, well E07, row E, column 7. Naming the plate and the wells is the difference between “3 plates failed” and something a user can go and look at.

diagnose(field_qcs) → [Finding]

Per-plate, per-flag findings that name the plate and the wells, plus the positional ones no single field can see: a plate whose object count or object size steps between one half of the rows (or columns) and the other. These patterns warrant checking illumination, experimental layout, treatment and biology; they do not establish a cause.

read_digest(src) → QCDigest

The cheap path: locate qc/segmentation_qc_*.csv under a project, parse it, roll it up, diagnose it, and compare each card’s mtime against its mask stack so a card written before the last re-mask is reported as out of date rather than believed. Scores nothing.

score_digest(src) → QCDigest

The expensive path, for the user who asks for it explicitly: score the mask stacks under a project, write the cards, return the same digest.

format_digest(digest)

The printable version.

Exceptions

SegmentationQCFailed

The plate failed segmentation QC and seg_qc='stop' was set.

Classes

FieldAddress

Where on the plate a scored field sits.

FieldQC

One field's segmentation verdict.

Finding

One thing worth telling the user, with the evidence attached.

FlagGuidance

One flag, in the words of someone deciding whether to run Measure.

QCDigest

Everything a screen needs to show a segmentation verdict.

Scorecard

One qc/segmentation_qc_<object_type>.csv, read back.

Functions

diagnose(→ List[Finding])

Turn per-field verdicts into findings that name plates, wells and causes.

explain_flag(→ FlagGuidance)

Return the plain-language entry for flag.

find_mask_stacks(→ Dict[str, str])

{object_type: folder} for the mask stacks under a plate.

find_scorecards(→ Tuple[str, ...])

Every qc/segmentation_qc_<object>.csv under src.

format_digest(→ str)

Render a digest as text — what the console prints, what a test reads.

format_findings(→ str)

Render findings as text, one block each.

format_scorecard(→ str)

Render the scorecard a human reads before deciding to run Measure.

mask_stack_mtime(→ float)

Modification time of the newest mask in folder, or 0.0.

parse_field_name(→ FieldAddress)

Split a field name into plate / well / row / column.

qc_mode(→ str)

Read seg_qc out of a settings dict and normalise it.

qc_roots(→ Tuple[str, ...])

Project folders to look for scorecards under, best first.

read_digest(→ QCDigest)

Read the segmentation verdict for a project. Scores nothing.

read_scorecard(→ Tuple[List[FieldQC], str])

Rebuild FieldQC rows from a scorecard CSV.

run_segmentation_qc(→ Optional[Dict[str, Any]])

Score a plate's masks, write the card, print the summary.

score_digest(, thresholds, Any]] = None, write, ...)

Score the mask stacks under src and return the same digest.

score_field(→ FieldQC)

Score one label mask on its own, with no knowledge of the plate.

score_masks(→ List[FieldQC])

Score every field of a plate and add the plate-relative flags.

summarize_qc(→ Dict[str, Any])

Roll the per-field verdicts up into one plate verdict.

thresholds_from_settings(→ Dict[str, float])

Pull the seg_qc_* knobs out of a spaCR settings dict.

write_scorecard(→ Optional[str])

Write one CSV row per field to <dst>/qc/segmentation_qc_<object_type>.csv.

Module Contents

exception spacr.seg_qc.SegmentationQCFailed(message: str, summary: Dict[str, Any] | None = None)[source]

Bases: RuntimeError

The plate failed segmentation QC and seg_qc='stop' was set.

Carries the summary so a caller can report WHICH fields failed and why, rather than only that something did.

Parameters:
  • message – the exception’s own message.

  • summary – the QC summary, so a caller can report WHICH fields failed and why rather than only that something did. None when there is nothing to carry.

Initialize the message and copy its structured QC summary.

class spacr.seg_qc.FieldAddress[source]

Where on the plate a scored field sits.

Parameters:
  • field – the field name as the scorecard holds it.

  • plate – first underscore-separated token, or the whole stem when there is no underscore (a hand-assembled folder is its own plate).

  • well – second token when it parses as a well, else ''.

  • row – the well’s letter part, uppercased, else ''.

  • column – the well’s numeric part as an int, else None.

__str__() → str[source]

Return plate/well when known, otherwise the plate name.

property known: bool[source]

True when a well could actually be read out of the name.

class spacr.seg_qc.FieldQC[source]

One field’s segmentation verdict.

Parameters:
  • field – the mask’s name — the .npy stem, so it can be found on disk and opened.

  • object_type – 'cell', 'nucleus', 'pathogen', 'organelle' — whatever was segmented.

  • n_objects – labels present in the mask.

  • flags – the named defects, e.g. ['under_segmented', 'high_border_fraction']. Empty means clean.

  • metrics – the numbers behind the flags, all floats so the card can be written to CSV without special cases. float('nan') marks a metric that could not be computed (too few objects, or no plate context).

  • severity – 'ok', 'warn' or 'fail' — the worst of the flags raised.

  • note – the same verdict in prose, with the numbers in it.

__str__() → str[source]

Return one line naming the field, count, severity, and flags.

property failed: bool[source]

True when this field’s measurements would be wrong, not just noisy.

class spacr.seg_qc.Finding[source]

One thing worth telling the user, with the evidence attached.

Parameters:
  • severity – 'fail', 'warn' or 'ok' — how a banner should colour it. Never a gate: nothing in this module blocks anything.

  • kind – 'flag', 'count_gradient', 'size_gradient' or 'clean'. What the finding is about, for a caller that wants to show one kind and not another.

  • headline – one line, with the plate and the number in it.

  • detail – what it means for the measurements and why it happens.

  • fix – what to do about it.

  • flag – the seg_qc flag behind it, for kind='flag'.

  • plate – the plate it is about, when it is about one.

  • object_type – what was segmented.

  • wells – the wells implicated, in name order.

  • fields – the field names implicated, in name order.

  • n_fields – how many fields are implicated.

  • illumination – True when uneven illumination is a named cause, so a caller can offer spacr.illumination beside the finding.

__str__() → str[source]

Return the severity-tagged finding headline.

text() → str[source]

Headline, detail and fix as one paragraph.

class spacr.seg_qc.FlagGuidance[source]

One flag, in the words of someone deciding whether to run Measure.

Parameters:
  • flag – the flag name, a member of FLAGS.

  • severity – 'fail' or 'warn', from _FLAG_SEVERITY.

  • headline – the flag in five words, for a table cell or a chip.

  • means – what it does to the measurements — not what the mask looks like, but which numbers in measurements.db come out wrong.

  • causes – the usual causes, most common first.

  • fix – what to do next, naming the setting or the module that does it.

  • illumination – True when uneven illumination is one of the causes, i.e. when spacr.illumination is part of the answer.

__str__() → str[source]

Return the flag identifier and its short explanatory headline.

text() → str[source]

The whole entry as one paragraph, causes numbered.

class spacr.seg_qc.QCDigest[source]

Everything a screen needs to show a segmentation verdict.

Advisory by construction: there is no field here a caller is meant to branch a Run button on, and blocks_run is a constant False that exists to say so in code rather than only in a docstring.

Parameters:
  • root – the project folder the cards were found under.

  • verdict – 'ok', 'warn', 'fail', 'missing' (nothing has scored these masks) or 'error'.

  • headline – the single most actionable sentence — the worst finding, with its plate and its number in it. What a banner puts in bold.

  • subhead – the counts behind it: how many fields, how many failed.

  • scorecards – one per object type found.

  • findings – what diagnose() made of current, readable cards.

  • stale – True when any card is older than its masks.

  • checked_at – when this digest was built (time.time()).

  • blocks_run – always False; segmentation QC advises the user but never prevents them from continuing a run.

__str__() → str[source]

Return the overall verdict followed by its actionable headline.

property failing_fields: Tuple[str, ...][source]

Fields scored 'fail' in current, readable cards.

property n_fields: int[source]

Fields scored in current, readable cards; stale rows are excluded.

property object_types: Tuple[str, ...][source]

What was segmented, in card order.

property plates: Tuple[str, ...][source]

The plates the cards cover, sorted.

class spacr.seg_qc.Scorecard[source]

One qc/segmentation_qc_<object_type>.csv, read back.

Parameters:
  • path – where it was read from.

  • object_type – what it scored, taken from the file name.

  • field_qcs – the rows, rebuilt as FieldQC.

  • summary – what summarize_qc() makes of them.

  • mtime – the card’s modification time.

  • masks_mtime – the newest mask file in the stack it describes, or 0.0 when that stack could not be found.

  • stale – True when a mask is newer than the card, i.e. the plate was re-masked and nothing has scored the new masks.

  • error – why the card could not be read, when it could not be.

__str__() → str[source]

Return object type and verdict, marking a stale card out of date.

property verdict: str[source]

'ok', 'warn', 'fail', 'empty' or 'error'.

spacr.seg_qc.diagnose(field_qcs: Sequence[FieldQC], *, gradient_ratio: float = GRADIENT_RATIO, min_fields_per_half: int = MIN_FIELDS_PER_HALF, max_named: int = MAX_NAMED) → List[Finding][source]

Turn per-field verdicts into findings that name plates, wells and causes.

Two kinds, because they answer different questions:

  • flag findings — one per (plate, flag): which wells, how many fields, what that flag does to the measurements and what to do about it. This is the scorecard, grouped and translated.

  • gradient findings — a plate whose object count or object size steps between one half of its rows (or columns) and the other. No single field can raise this and no field-level flag implies it: a plate can be entirely free of per-field flags and still be lit unevenly enough to put a position term in every intensity feature.

Parameters:
  • field_qcs – what score_masks() returned, or what read_scorecard() read back off disk.

  • gradient_ratio – fold step between plate halves at which the positional findings fire.

  • min_fields_per_half – fewest fields a half-plate needs before its median is compared.

  • max_named – how many wells or fields a finding names.

Returns:

findings, failures first, then by how many fields each implicates. An empty list means there is nothing to say.

spacr.seg_qc.explain_flag(flag: str) → FlagGuidance[source]

Return the plain-language entry for flag.

Parameters:

flag – a member of FLAGS.

Returns:

its FlagGuidance.

Raises:

KeyError – for a flag with no entry, which is a bug in this module rather than a caller error — every flag it can raise is explained.

spacr.seg_qc.find_mask_stacks(root: str) → Dict[str, str][source]

{object_type: folder} for the mask stacks under a plate.

spacr.object writes <src>/<object_type>_mask_stack, where src is a folder inside the plate (norm_channel_stack), so the stacks sit one level below the plate root the card is written at. Both levels are checked, and neither is walked further.

Parameters:

root – a plate folder.

spacr.seg_qc.find_scorecards(src: Any) → Tuple[str, ...][source]

Every qc/segmentation_qc_<object>.csv under src.

Parameters:

src – whatever qc_roots() accepts.

Returns:

absolute paths, sorted, flag sidecars excluded.

spacr.seg_qc.format_digest(digest: QCDigest) → str[source]

Render a digest as text — what the console prints, what a test reads.

Parameters:

digest – completed segmentation-QC digest to render.

spacr.seg_qc.format_findings(findings: Sequence[Finding]) → str[source]

Render findings as text, one block each.

spacr.seg_qc.format_scorecard(field_qcs: Sequence[FieldQC], plate_fail_fraction: float | None = None, max_rows: int = _MAX_CARD_ROWS) → str[source]

Render the scorecard a human reads before deciding to run Measure.

The table lists the fields that are not clean — on a good plate that is no rows at all, and on a bad one it is the list you want. Clean fields are counted, not printed: a 1536-field plate must not scroll a terminal.

Parameters:
  • field_qcs – what score_masks() returned.

  • plate_fail_fraction – passed to summarize_qc().

  • max_rows – how many bad fields to name before summarising the rest.

Returns:

a multi-line string, ready to print.

spacr.seg_qc.mask_stack_mtime(folder: str) → float[source]

Modification time of the newest mask in folder, or 0.0.

The folder’s own mtime is included: spacr.object writes masks by atomic rename, which touches the directory, so a stack whose files were all replaced is caught even if the sample below misses the newest one.

Parameters:

folder – a <object_type>_mask_stack folder.

spacr.seg_qc.parse_field_name(name: str) → FieldAddress[source]

Split a field name into plate / well / row / column.

Never raises and never guesses: a name that does not carry a well comes back with empty well, row and column, and callers do not make the positional claims that need them.

Parameters:

name – a field name, file name or path — extension and directories are stripped.

spacr.seg_qc.qc_mode(settings: Mapping[str, Any]) → str[source]

Read seg_qc out of a settings dict and normalise it.

Accepts the three documented modes plus the shapes a settings CSV round trip produces (None, '', 'False', True). Anything unrecognised falls back to 'report', because the cost of computing a scorecard nobody asked for is a second, and the cost of silently skipping one somebody did ask for is a wasted Measure run.

Parameters:

settings – any mapping.

Returns:

'off', 'report' or 'flag'.

spacr.seg_qc.qc_roots(src: Any) → Tuple[str, ...][source]

Project folders to look for scorecards under, best first.

Handles the shapes settings['src'] actually takes:

  • a plate folder — cards are in <src>/qc;

  • the merged folder Measure is usually pointed at (<plate>/merged) — cards are one level up, the same hop spacr.ports.project_root() makes;

  • a list of plates, which is how several plates are run at once;

  • a project root holding plate subfolders, each with its own qc/.

Parameters:

src – a path, a list of paths, or None.

Returns:

existing folders, de-duplicated, in the order above.

spacr.seg_qc.read_digest(src: Any, **kwargs) → QCDigest[source]

Read the segmentation verdict for a project. Scores nothing.

The cheap path, and the one a screen calls: locate the scorecards the mask run already wrote, parse them, roll them up, diagnose them, and date each one against its mask stack.

Parameters:
  • src – whatever qc_roots() accepts — a plate folder, a merged folder, a list of plates or a project root.

  • kwargs – passed to diagnose().

Returns:

a QCDigest. verdict == 'missing' when no card exists, which is emphatically not the same as 'ok'.

spacr.seg_qc.read_scorecard(path: str) → Tuple[List[FieldQC], str][source]

Rebuild FieldQC rows from a scorecard CSV.

The rows are handed back to summarize_qc() and diagnose() rather than re-derived, so what a screen shows is the verdict the run printed and not a second opinion that could disagree with it.

Parameters:

path – a qc/segmentation_qc_<object_type>.csv.

Returns:

(field_qcs, error). A damaged card comes back as ([], reason): half a card is a different verdict, and this module does not invent one.

spacr.seg_qc.run_segmentation_qc(source: Any, object_type: str = 'object', dst: str | None = None, mode: str = 'report', thresholds: Mapping[str, Any] | None = None, verbose: bool = True, print_fn=print) → Dict[str, Any] | None[source]

Score a plate’s masks, write the card, print the summary.

This is the entry point spacr.object calls once per object type, immediately after the masks are on disk.

Parameters:
  • source – whatever score_masks() accepts — normally the <object_type>_mask_stack folder.

  • object_type – what was segmented.

  • dst – plate folder the qc/ subfolder is written under. None scores and prints without writing anything.

  • mode – 'off' does nothing at all and returns None; 'report' computes, writes and prints, changing nothing; 'flag' additionally writes segmentation_qc_<object_type>_flags.json mapping field name to flags, for a downstream step to consume. No mode deletes or skips a field — surfacing the problem is the job.

  • thresholds – overrides for QC_DEFAULTS, e.g. from thresholds_from_settings().

  • verbose – True prints the full card, False prints the one-line verdict. The verdict is always printed: a plate that failed QC must not be able to fail quietly.

  • print_fn – injection point for tests and for the GUI’s log widget.

Returns:

{'mode', 'object_type', 'field_qcs', 'summary', 'csv_path', 'flags'}, or None when mode is 'off'.

spacr.seg_qc.score_digest(src: Any, object_types: Sequence[str] = (), thresholds: Mapping[str, Any] | None = None, write: bool = True, **kwargs) → QCDigest[source]

Score the mask stacks under src and return the same digest.

The expensive path — it opens every mask — so it is never called to draw a screen. It exists for the user who has just been told the verdict is missing or out of date and asks for it to be brought up to date; writing the cards is what makes the next read_digest() cheap again.

Parameters:
  • src – whatever qc_roots() accepts.

  • object_types – which stacks to score; empty means all that exist.

  • thresholds – overrides for QC_DEFAULTS, e.g. from thresholds_from_settings().

  • write – write each card back to <plate>/qc/.

  • kwargs – passed to diagnose().

spacr.seg_qc.score_field(mask: Any, object_type: str = 'object', field: str = 'field', **thresholds: Any) → FieldQC[source]

Score one label mask on its own, with no knowledge of the plate.

Everything here is plate-independent: counts, foreground, border fraction, the robust size range, the fusion cross-check. The comparisons that need the plate (count and size relative to the plate median) are added by score_masks() afterwards.

Parameters:
  • mask – a 2-D label image (or anything numpy.squeeze() reduces to one). Booleans are labelled first; floats are rounded.

  • object_type – what was segmented, carried through to the card.

  • field – the field’s name, used in the card and the CSV.

  • thresholds – any key of QC_DEFAULTS. Unknown keys raise, so a typo in a threshold name cannot silently do nothing.

Returns:

a FieldQC. A mask that cannot be read comes back flagged unreadable_mask rather than raising — one corrupt field must not cost the plate its scorecard.

spacr.seg_qc.score_masks(source: Any, object_type: str = 'object', **thresholds: Any) → List[FieldQC][source]

Score every field of a plate and add the plate-relative flags.

Parameters:
  • source – a folder of .npy masks (the <object>_mask_stack folder spacr.object writes), a single .npy file, a 3-D mask stack, a {name: mask} mapping, or a sequence of 2-D masks.

  • object_type – what was segmented.

  • thresholds – any key of QC_DEFAULTS.

Returns:

one FieldQC per field, in field-name order. An empty list when the source holds no mask — that is not an error, it is what a run with save=False looks like.

spacr.seg_qc.summarize_qc(field_qcs: Sequence[FieldQC], plate_fail_fraction: float | None = None) → Dict[str, Any][source]

Roll the per-field verdicts up into one plate verdict.

Parameters:
  • field_qcs – what score_masks() returned.

  • plate_fail_fraction – fraction of failing fields at which the plate itself is called a failure; defaults to QC_DEFAULTS['plate_fail_fraction'] (0.10 — roughly one column of a 96-well plate. Below that you can drop the bad fields and keep the plate; above it, what Measure produces is not the experiment).

Returns:

a dict with the counts, the flag tally, the failing and warning field names, the plate medians and a one-line message.

spacr.seg_qc.thresholds_from_settings(settings: Mapping[str, Any]) → Dict[str, float][source]

Pull the seg_qc_* knobs out of a spaCR settings dict.

Parameters:

settings – any mapping; keys that are absent, blank or not numeric are skipped so the corresponding QC_DEFAULTS entry stands.

Returns:

{threshold_name: value}, ready to splat into score_field() or score_masks().

spacr.seg_qc.write_scorecard(field_qcs: Sequence[FieldQC], dst: str, object_type: str = 'object') → str | None[source]

Write one CSV row per field to <dst>/qc/segmentation_qc_<object_type>.csv.

Parameters:
  • field_qcs – what score_masks() returned.

  • dst – the plate folder; the qc subfolder is created if needed.

  • object_type – names the file.

Returns:

the path written, or None when there was nothing to write.

Nested helpers

qc_roots._add(path: str) → None

Append one unique existing directory to the captured candidates.

Parameters:

path – candidate directory path; false-like, missing, non-directory, and already-recorded values are ignored.

Returns:

None. Accepted paths retain discovery order.

spacr/seg_qc.py:2013