spacr.cell_montage

Select microscopy objects associated with a regression coefficient.

The input consists of a guide- or gene-level coefficient, per-object measurements and per-well guide fractions. Because pooled screens do not identify which individual cell contains a guide, the result represents cells whose measured phenotype is consistent with the fitted effect; it does not assign a genotype to individual cells. MontagePlan includes INFERENCE_NOTICE in every generated caption to preserve this distinction.

Selection proceeds in three stages:

  1. wells_for_coefficient() identifies wells with a non-zero fraction of the guide or gene. Gene-level selection sums guide fractions by default; select_montage_per_guide() evaluates guides separately.

  2. score_window() defines target = baseline + effect and retains objects within half_widths robust scales of the target, ordered by |score - target|. The baseline and scale are calculated across the supplied object population rather than separately for each gene.

  3. objects_to_show() selects round(n_objects_in_well * guide_fraction_in_well) objects from each well. Zero-contribution wells remain in the plan for reporting.

Images are obtained through spacr.crops.PngCropSource for exported PNG crops or spacr.crops.MergedCropSource for crops extracted from merged/<fov>.npy. resolve_montage_crop_source() reports when neither source is available. Per-well guide fractions are read from regression_data.csv; the aggregate files grna_well.csv and well_grna.csv do not contain the required well-by-guide fractions.

Exceptions

CoefficientNotFound

The gene or guide named by the coefficient is not in the count data.

MissingScores

The object frame carries no usable per-object classification score.

MontageError

The requested montage cannot be selected from what was supplied.

Classes

Coefficient

One point on the regression plot: a gene or a guide, and its effect.

CropSourceChoice

Which crop source will draw a montage, or why none can.

MontagePlan

Store selected objects and values required to document selection.

RouteRequirements

Describe the inputs and crop shapes available through one pixel route.

ScoreWindow

The band of classification scores a coefficient implies, and its rule.

WellSelection

What one well contributed to the montage, and why that number.

Functions

coefficient_level(→ str)

Return 'grna' or 'gene' for name, read off the count data.

effects_grid_from_results(→ Optional[pandas.DataFrame])

Load the gene-by-measurement effects grid stored beside a run.

fractions_from_counts(→ pandas.DataFrame)

Build per-well guide fractions directly from count CSV files.

guides_for_coefficient(→ List[str])

Return the guides a coefficient covers, in sorted order.

load_montage_objects(→ pandas.DataFrame)

Return the per-object rows a montage selects from, out of one database.

montage_route_requirements(→ RouteRequirements)

Validate the selected route before extracting any crops.

objects_to_show(→ int)

Return round(n_objects * fraction) -- the montage's count rule.

read_well_guide_fractions(→ pandas.DataFrame)

Read the per-well guide fractions a montage needs.

resolve_montage_crop_source(→ CropSourceChoice)

Pick the crop source for a montage, and never raise for "none".

round_half_up(→ int)

Round value to the nearest integer, halves away from zero.

score_window(→ ScoreWindow)

Return the band of scores the coefficient effect implies.

select_montage(→ MontagePlan)

Return the objects to show behind one coefficient.

select_montage_per_guide(→ List[MontagePlan])

Return one MontagePlan per guide of a gene-level coefficient.

wells_for_coefficient(→ pandas.DataFrame)

Return the wells whose count data reports name present.

write_effects_grid(→ str)

Write a sweep's effects grid beside the run. Returns the path.

Module Contents

exception spacr.cell_montage.CoefficientNotFound[source]

Bases: MontageError

The gene or guide named by the coefficient is not in the count data.

Initialize self. See help(type(self)) for accurate signature.

exception spacr.cell_montage.MissingScores[source]

Bases: MontageError

The object frame carries no usable per-object classification score.

Initialize self. See help(type(self)) for accurate signature.

exception spacr.cell_montage.MontageError[source]

Bases: ValueError

The requested montage cannot be selected from what was supplied.

Initialize self. See help(type(self)) for accurate signature.

class spacr.cell_montage.Coefficient[source]

One point on the regression plot: a gene or a guide, and its effect.

Parameters:
  • name – the gene or guide name as the count data spells it.

  • effect – the fitted coefficient.

  • level'gene' or 'grna'.

  • guides – the guides the coefficient covers – one for a guide-level coefficient, all of the gene’s for a gene-level one.

describe() str[source]

Return 'GRA14 (gene, effect +0.700)' for a caption or a log.

class spacr.cell_montage.CropSourceChoice[source]

Which crop source will draw a montage, or why none can.

The tab needs both answers in the same shape: the design says a tab that cannot be filled must say why rather than be absent, so “there is no source” is a value here and not an exception.

Parameters:
  • source – the spacr.crops.CropSource, or None.

  • kind'png', 'merged', or '' when unavailable.

  • reason – why that source was picked, or why none could be.

  • available – whether a montage can be drawn at all.

  • requirements – what that route needs and what it can offer – RouteRequirements, checked up front so a missing channel list is reported as a missing channel list rather than surfacing later as a crop that will not cut. None when nothing asked.

describe() str[source]

Return the one-line sentence for the tab’s status line.

requirement_notes() Tuple[str, ...][source]

The caption lines this route’s requirements oblige, if any.

class spacr.cell_montage.MontagePlan[source]

Store selected objects and values required to document selection.

Parameters:
  • coefficient – the point that was clicked.

  • window – the score window that was applied.

  • wells – one WellSelection per well the count data reported the coefficient present in, including wells that contributed no selected objects.

  • objects – the selected object rows, in well order then by distance to the target, with montage_distance, montage_well and montage_rank added.

  • score_column – the per-object score the selection used.

  • guide_aggregation'sum' or 'separate'.

  • guides – the guides the coefficient covers.

  • source_kind'png' / 'merged' / '' – which crop source will draw these, when one was resolved.

  • source_reason – why that source was picked.

  • cap – the cap that was applied.

  • n_before_cap – how many objects the rule selected before the cap.

  • notes – everything the caption has to disclose that is not a per-well count.

arithmetic() str[source]

The whole sum, in words a reader can check.

Everything a reader would need to reproduce the selection by hand: where the baseline came from, what the target is and why, how wide the window is and in what units, and round(n x fraction) for each well with the total they add to.

caption() str[source]

Return the caption the montage must carry.

The caption includes the wells, score window, selection settings, count calculation, crop source, zero-contribution wells and the notice that guide membership is inferred rather than observed.

rows() List[Dict[str, Any]][source]

Return the selected objects as plain dicts for a crop source.

spacr.crops.PngCropSource and MergedCropSource both take a row mapping, so this is what the tab hands to source.get_many(...).

settings_line() str[source]

Return the selection settings, including their default values.

These settings apply to the complete screen rather than individual genes. Non-default values are reported explicitly in the montage.

Returns:

one or two lines – what was in force, and (only when something was) what was changed from the default.

summary() str[source]

Return the one-line status-bar sentence.

property capped: bool[source]

True when the cap trimmed the montage.

property is_empty: bool[source]

True when no object survived the selection.

The tab shows the caption anyway: “no objects” with the wells and the window that produced none is an answer, and an empty tab is not.

property n_objects: int[source]

How many objects the montage holds.

property zero_wells: Tuple[WellSelection, ...][source]

The wells that reported the guide but contributed no object.

class spacr.cell_montage.RouteRequirements[source]

Describe the inputs and crop shapes available through one pixel route.

Requirements are validated before extraction. 'merged-mask' requires image channels, a mask array and either a bounding box or object id; object identifiers permit object-shaped crops. 'merged-bbox' requires tabular coordinates and image channels and supports bounding-box crops only. 'png' uses the shape created when measure_crop exported the image.

Parameters:
  • route'png', 'merged-mask', 'merged-bbox', or 'none'.

  • shapes – Crop shapes from CROP_SHAPES that the route can produce. This is empty for 'png' because exported PNGs retain the shape used when they were created.

  • missing – Required inputs that are absent, expressed as user-facing descriptions.

  • assumed – Missing values replaced by defaults and reported in the status caption rather than treated as errors.

  • detail – Explanation of how the route was identified.

describe() str[source]

The one-line answer for the tab’s status line.

offers(shape: str) bool[source]

True when shape is a crop this route can really produce.

Parameters:

shape – crop shape to check against this route’s capabilities.

why_not(shape: str) str[source]

Why shape is unavailable, or '' when it is available.

Parameters:

shape – requested crop shape whose availability is explained.

The returned sentence is attached to a disabled control. An unavailable object-shaped crop is not substituted with a bounding box.

property satisfied: bool[source]

True when this route has everything it needs to cut a crop.

class spacr.cell_montage.ScoreWindow[source]

The band of classification scores a coefficient implies, and its rule.

Parameters:
  • target – the per-object score the coefficient implies.

  • low – the window’s lower bound, inclusive.

  • high – the window’s upper bound, inclusive.

  • baseline – the score of an object carrying no guide of interest.

  • baseline_source'screen_median' when derived from every object supplied, 'given' when the caller passed one.

  • scale – the robust scale (MAD-sigma) the half-width is measured in.

  • half_widths – how many scales wide each side is.

  • n_scored – how many objects the baseline and scale were computed from – the whole supplied frame, not the selected wells.

  • observed_low – smallest score seen across all objects.

  • observed_high – largest score seen across all objects.

  • degenerate – True when every score is identical, so the window has no width to speak of and admits everything.

contains(scores) numpy.ndarray[source]

Return the boolean mask of scores that fall inside the window.

Parameters:

scores – any array-like of per-object scores. Non-finite entries are outside the window whatever the bounds are – a NaN score is a missing measurement, not a near miss.

describe() str[source]

Return the one-line statement of the rule and the width.

property target_is_observable: bool[source]

True when the implied score lies inside the observed score range.

False is a real finding, not an error: it says no object anywhere in the screen scores anything like what the coefficient implies, so the montage is showing the least-far objects rather than close ones.

class spacr.cell_montage.WellSelection[source]

What one well contributed to the montage, and why that number.

Parameters:
  • well – the well key as a display string.

  • fraction – the guide’s fraction in this well, from the count data.

  • n_objects – how many objects the object frame holds for this well – the population round(n x fraction) is computed from.

  • n_reportedcell_count as the count data recorded it, or None. Kept beside n_objects rather than instead of it, because when they disagree the montage has to say so.

  • n_expectedround(n_objects x fraction) – the count rule.

  • n_in_window – how many of this well’s objects fall inside the score window at all.

  • n_selected – how many were actually taken, after the window and after any cap.

  • note – why n_selected is not n_expected, or ''.

  • share – normalized fraction actually used for n_expected; None means the raw fraction was used.

describe() str[source]

Return the one-line account of this well’s contribution.

The fraction shown is the one the count was reached with, so the equation on screen is one the reader can check.

property contributed: bool[source]

True when this well put at least one object into the montage.

spacr.cell_montage.coefficient_level(counts: pandas.DataFrame, name: str, *, guide_column: str = 'grna', gene_column: str = 'gene') str[source]

Return 'grna' or 'gene' for name, read off the count data.

A guide wins a tie. A library where one string is both a guide name and a gene name is pathological, and resolving it as the gene would silently widen the montage to that gene’s other guides.

Parameters:
  • counts – the per-well count data.

  • name – the coefficient’s name.

  • guide_column – the guide column, default 'grna'.

  • gene_column – the gene column, default 'gene'.

Raises:

CoefficientNotFound – the name is in neither column.

spacr.cell_montage.effects_grid_from_results(path) pandas.DataFrame | None[source]

Load the gene-by-measurement effects grid stored beside a run.

Parameters:

path (path-like) – Results CSV or directory containing EFFECTS_GRID_FILE.

Returns:

pandas.DataFrame or None – Effects indexed by guide, or None when the grid is absent, empty, or unreadable.

Notes

The persisted grid lets multivariate montage selection work across application sessions. Callers may fall back to single-score attribution when no valid grid is available.

spacr.cell_montage.fractions_from_counts(paths: Sequence[str]) pandas.DataFrame[source]

Build per-well guide fractions directly from count CSV files.

A guide’s fraction in a well is its share of that well’s reads:

fraction = count / (sum of count over the well)

This is the same calculation applied by spacr.ml.process_reads() before fitting. regression_data.csv persists the resulting join but is not required as the source of the fractions.

Parameters:

paths – the count CSVs from the regression input table – the count column of the rows the database provider already returns, so no new plumbing reaches them.

Returns:

one row per (well, guide) with prc, grna and fraction, and gene when the counts carry it.

Raises:

MontageError – no readable counts, or a file short of a column.

Well totals contain one row per well and are merged with validate='many_to_one' to prevent duplicated guide rows.

spacr.cell_montage.guides_for_coefficient(counts: pandas.DataFrame, name: str, *, level: str | None = None, guide_column: str = 'grna', gene_column: str = 'gene') List[str][source]

Return the guides a coefficient covers, in sorted order.

Parameters:
  • counts – the per-well count data.

  • name – the coefficient’s name.

  • level'gene' / 'grna'; None reads it off the data with coefficient_level().

  • guide_column – the guide column.

  • gene_column – the gene column.

Returns:

one guide for a guide-level coefficient, the gene’s guides for a gene-level one.

spacr.cell_montage.load_montage_objects(db_path: str, *, object_type: str = 'cell', score_column: str = 'pred', table: str = 'png_list', src: str | None = None, scores: Any = None, verbose: bool = False) pandas.DataFrame[source]

Return the per-object rows a montage selects from, out of one database.

Reads png_list – which is where spacr.predictions.merge_cv_predictions() writes the per-object classification score – and hands it to spacr.io.crop_rows_from_png_list(), which is the join that recovers path_name and the integer object_label a merged crop is cut by. So the frame that comes back serves both crop sources: png_path for PngCropSource, path_name plus object_label for MergedCropSource.

Parameters:
  • db_path – the measurements.db.

  • object_type – which crop mode’s rows to read.

  • score_column – the per-object classification score column.

  • table – the table holding the crops; 'png_list'.

  • src – the screen’s current folder. Defaults to the plate folder the database sits in. Crop paths are recorded absolute at crop time, so a screen that has moved computer – or a NAS mounted somewhere else – carries 60,000 paths that no longer exist while every file is present; see spacr.portable_paths. Only paths that resolve to an existing file are rewritten.

  • scores – where the per-object classification scores are, when the database has none: a frame, a path, or several paths – the score CSVs the run was fitted on. Used ONLY when png_list carries no score column, and never written back.

  • verbose – let the io join report the rows it could not place.

Returns:

the object frame, with prc composed when the well keys are there.

Raises:
  • MontageError – the database is missing or has no such table.

  • MissingScores – the table has no score column, or no finite score in it – i.e. classification has not been merged into this database.

spacr.cell_montage.montage_route_requirements(source, objects=None, *, object_type: str = 'cell', channels: Sequence[int] | None = None, channels_declared: bool | None = None) RouteRequirements[source]

Validate the selected route before extracting any crops.

Parameters:
  • source – the spacr.crops.CropSource (or the CropSourceChoice holding one) that will draw the montage. None is the “no source” answer and comes back as route 'none'.

  • objects – the per-object frame the crops will be cut from. Its COLUMNS are what decide between the two merged routes – an object id means the mask can be followed, only a bounding box means it cannot. None skips the row checks and reports the route the source alone implies.

  • object_type – which mask plane the crop is cut by.

  • channels – the channels the user asked for, if any.

  • channels_declared – whether a channel list exists at all – typed by the user or recorded by the run. None infers it from channels. False is the case the request names specifically: A USER MISSING A CHANNEL LIST IS TOLD THAT, not told there is no source.

Returns:

the RouteRequirements.

spacr.cell_montage.objects_to_show(n_objects: int, fraction: float) int[source]

Return round(n_objects * fraction) – the montage’s count rule.

This is the expected count implied by the guide fraction. A pooled design supports an expected count but not the identity of guide-bearing objects.

Parameters:
  • n_objects – how many objects the well actually has to draw from. Negative counts are refused; zero is legal and yields zero.

  • fraction – the guide’s fraction in that well, in [0, 1]. A fraction outside that range is refused rather than clipped – it means the count data and the well key did not line up, and a clipped montage would hide that.

Returns:

how many objects that well contributes.

spacr.cell_montage.read_well_guide_fractions(path: str) pandas.DataFrame[source]

Read the per-well guide fractions a montage needs.

Parameters:

path – a regression results folder, or the CSV itself. A folder is resolved to regression_data.csv.

Returns:

the frame, validated to carry a well key, grna, gene and fraction.

Raises:

MontageError – the path names grna_well.csv or well_grna.csv – refused by name, with what each actually holds, because they are the obvious guess and neither can answer the question; or the file is missing, or short of a needed column.

spacr.cell_montage.resolve_montage_crop_source(src, *, object_type: str = 'cell', prefer: str | None = None, objects: pandas.DataFrame | None = None, channels: Sequence[int] | None = None) CropSourceChoice[source]

Pick the crop source for a montage, and never raise for “none”.

A thin wrapper on spacr.crops.resolve_crop_source(), which is the module that already knows how to choose between the exported PNGs and merged/*.npy and already says which it chose. The wrapper exists for exactly one reason: a missing source has to arrive as a sentence the tab can display, not as an exception it has to catch to stay on screen.

Parameters:
  • src – a settings mapping (with src, optionally crop_source) or the experiment root / its merged folder.

  • object_type – which object the crops are cut by.

  • prefer – force 'png' or 'merged'.

  • objects – the per-object frame, so the ROUTE’s own requirements can be checked up front – an object id means the mask can be followed, a bounding box alone means it cannot. Left out, the requirements are those the source alone implies.

  • channels – the channels the caller asked for, so “no channel list anywhere” can be reported as exactly that.

Returns:

a CropSourceChoice; available is False, with the reason, when neither source exists.

spacr.cell_montage.round_half_up(value: float) int[source]

Round value to the nearest integer, halves away from zero.

Deliberately not round() or numpy.round(), both of which round halves to even: a well whose n * fraction is 2.5 would then contribute 2 while an otherwise identical well at 3.5 contributed 4. That is a montage whose per-well counts depend on the parity of a number the caption calls “round(n x fraction)”, which no reader could reconstruct.

Parameters:

value – the number to round. Non-finite input raises rather than producing a count.

Returns:

the rounded integer.

spacr.cell_montage.score_window(objects: pandas.DataFrame, effect: float, *, score_column: str = DEFAULT_SCORE_COLUMN, half_widths: float = WINDOW_HALF_WIDTHS, baseline: float | None = None, baseline_label: str | None = None) ScoreWindow[source]

Return the band of scores the coefficient effect implies.

THE RULE, stated once and applied identically to every coefficient:

  • the target is baseline + effect. Under the well-level model a well’s score is baseline + fraction * effect, so a well made entirely of cells carrying the guide would score baseline + effect – which is the score one such cell implies.

  • the baseline is the median per-object score over every object supplied. One number for the screen, so it does not move when the gene does.

  • closest” is smallest |score - target|, and the window is target +/- half_widths * scale where scale is 1.4826 * MAD of the same screen-wide score distribution.

Parameters:
  • objects – every object available to the montage – all wells, not just the coefficient’s. Passing only the selected wells is what makes a window per-gene, so the whole frame is the argument.

  • effect – the fitted coefficient.

  • score_column – the per-object classification score.

  • half_widths – the window’s half-width in robust scales. Overriding WINDOW_HALF_WIDTHS is allowed and is written into the caption, because a widened window changes which cells a reader is looking at.

  • baseline – use this baseline instead of the screen median – the fitted intercept, say. Recorded as 'given'.

  • baseline_label – what to record as the baseline’s SOURCE when one is given, e.g. 'the model's fitted intercept'. The caption reads this back, so a montage centred on the intercept says so rather than saying given, which names no source at all. Ignored when baseline is None – the screen median has one source and it is already named.

Raises:
  • MissingScores – no usable score column, or no finite score in it.

  • MontageError – a non-positive or non-finite half_widths.

spacr.cell_montage.select_montage(objects: pandas.DataFrame, counts: pandas.DataFrame, name: str, effect: float, *, level: str | None = None, score_column: str = DEFAULT_SCORE_COLUMN, half_widths: float = WINDOW_HALF_WIDTHS, baseline: float | None = None, baseline_label: str | None = None, cap: int = MAX_OBJECTS, guide_aggregation: str = 'sum', guide_column: str = 'grna', gene_column: str = 'gene', fraction_column: str = 'fraction', crop_source: CropSourceChoice | None = None, guides: Sequence[str] | None = None, show_all: bool = False, picking: str = 'rank', effects: Mapping[str, float] | None = None, effects_grid: pandas.DataFrame | None = None, exclude_grnas: Sequence[str] | None = None, normalise_fraction: bool = True, threshold: float = 0.55) MontagePlan[source]

Return the objects to show behind one coefficient.

The whole selection, in the order the design states it: the wells the count data reports the guide present in, the objects in those wells whose classification score is closest to what the coefficient implies, and round(n x fraction) of them per well.

Parameters:
  • objectsevery object available – all wells, not just the coefficient’s. The window’s baseline and scale are computed from this whole frame precisely so that they do not move when the gene does, so pre-filtering it to the coefficient’s wells is the one thing that makes the window tunable.

  • counts – the per-well count data (regression_data.csv).

  • name – the coefficient’s gene or guide name.

  • effect – its fitted coefficient.

  • level'gene' / 'grna'; None reads it off the counts.

  • score_column – the per-object classification score.

  • half_widths – the score window’s half-width in robust scales.

  • baseline – an explicit baseline instead of the screen median.

  • baseline_label – what to record as that baseline’s source, so the caption names it – 'the fitted intercept' rather than given.

  • cap – the largest montage to return; the objects closest to the implied score survive, and the caption says what was trimmed.

  • guide_aggregation'sum' or 'separate'. 'separate' is served by select_montage_per_guide(); passing it here is an error rather than a silent sum.

  • guide_column – the guide column in counts.

  • gene_column – the gene column in counts.

  • fraction_column – the per-well guide fraction column in counts.

  • crop_source – an already-resolved CropSourceChoice, so the plan can say which source will draw it.

  • guides – override the guides the coefficient covers – used by select_montage_per_guide() to plan one guide at a time.

Returns:

the MontagePlan.

Raises:
spacr.cell_montage.select_montage_per_guide(objects: pandas.DataFrame, counts: pandas.DataFrame, name: str, effect: float, **kwargs) List[MontagePlan][source]

Return one MontagePlan per guide of a gene-level coefficient.

Keeping guides separate shows whether each guide selects comparable cells, complementing the gene-level montage built from summed guide fractions.

Parameters:
  • objects – every object available, as for select_montage().

  • counts – the per-well count data.

  • name – the gene (or guide) name.

  • effect – fitted gene-level coefficient shared by every guide plan; guides differ only in the wells and cells they select.

  • kwargs – forwarded to select_montage().

Returns:

one plan per guide, in guide order. A guide whose wells report it nowhere is skipped, and the guides that produced a plan are on each plan’s guides.

spacr.cell_montage.wells_for_coefficient(counts: pandas.DataFrame, name: str, *, level: str | None = None, guide_aggregation: str = 'sum', guide_column: str = 'grna', gene_column: str = 'gene', fraction_column: str = 'fraction') pandas.DataFrame[source]

Return the wells whose count data reports name present.

Step 1 of the selection. A gene’s guides are summed by default – the gene-level coefficient describes the fraction of the well carrying ANY guide against the gene – and the sum is refused if it exceeds 1, because that means the same well was counted twice.

Parameters:
  • counts – per-well count data: a well key (prc, or all of plateID/rowID/columnID), grna, gene and fraction. regression_data.csv is that frame; grna_well.csv and well_grna.csv are NOT – see read_well_guide_fractions().

  • name – the coefficient’s name.

  • level'gene' / 'grna'; None reads it off the data.

  • guide_aggregation'sum' (one fraction per well, the default) or 'separate' (a grna column is kept and one row per well/guide comes back, for select_montage_per_guide()).

  • guide_column – the guide column.

  • gene_column – the gene column.

  • fraction_column – the per-well guide fraction column.

Returns:

one row per well (or per well/guide) with the well key, fraction, and cell_count when the count data carries it.

Raises:

CoefficientNotFound – no well reports the coefficient present.

spacr.cell_montage.write_effects_grid(effects, folder) str[source]

Write a sweep’s effects grid beside the run. Returns the path.

Parameters:
  • effects – gene-by-measurement effects table to persist.

  • folder – run directory in which to write the grid.

Written by the sweep so the montage can read it later – and in another session, which is the half a panel-to-panel handover cannot do.