spacr.measure

Workflow inputs and outputs

Measure

Measure reads images and label planes together. Enable crop saving if you need PNG files; keep the database and source project together for streamed crops.

Open: Home → Measure.

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

Inputs

  • Images and label masks — merged/*.npy in the project; channels and integer label planes share each field array.

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

Outputs

  • Measured objects — measurements/measurements.db; object tables depend on the enabled cell, nucleus, pathogen and organelle masks. Relevant tables, depending on the route: cell, nucleus, pathogen, cytoplasm. Relevant columns, depending on the route: plateID, rowID, columnID, fieldID.

  • Object crops — data/**/*_png when save_png is enabled; png_list indexes saved crops. Supported workflows can instead stream crops from merged arrays and masks. Relevant tables, depending on the route: png_list. Relevant columns, depending on the route: png_path, prcfo.

Before this module

  • Mask: Use the same project and the correct image/mask channel indices.

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

  • External Masks: Re-measure only when needed; External Masks can already perform measurement.

  • Timelapse: Use the time-series project with stable frame/object identities.

  • Import: Import matching images and external integer masks to build merged project arrays, then open Measure on that project. Skip this step when compatible measurements have already been imported or computed. Do not append duplicate measurements to an existing imported table.

After this module

  • Annotate: Save or stream crops with stable object identities.

  • Classify: Choose the image or tabular family to match your input.

  • Image UMAP: Choose feature columns and inspect representative crops.

  • Embeddings: Retain encoder and channel-policy provenance.

  • Gate Editor: Use the actual measured feature definitions and units.

  • Graph Builder: Choose explicit variables, groups and filters.

  • QC: Review available checks and missing evidence.

  • Recruitment: Require the intended compartment intensities and identities.

  • Invasion Assay: Require two-colour stain measurements and appropriate baseline controls.

  • Replication Assay: Require explicit parasite-to-vacuole identities.

  • Motility Assay: Combine measured objects with matching tracks.

  • Feature Explorer: Define the class comparison and inspect filtering.

  • Database Browser: Inspect actual tables before exporting.

  • AnnData Export: Export compatible feature and metadata columns.

  • Dose–Response: Join the measured response to explicit doses and controls.

  • Endodyogeny size proxy: Supply the measured project roots and required object/png_list tables. Verify host-cell aggregation and area units before interpreting size bins; the Mask counts database alone is insufficient.

  • Host–Pathogen Analysis: Keep uninfected cells in Measure. Supply whole-vacuole masks, host reference intensities and optional explicit parasite-to-vacuole links; host identity alone does not define a vacuole.

API reference.

Module tutorial.

Turn masks and channels into one row per object, in a database.

What it is for: segmentation gives the location of each object; this module measures its shape, intensity, texture and spatial context. It reads the arrays Mask wrote and produces the per-object table that downstream analyses use – which genes changed a phenotype, which cells to train a classifier on, which wells are reliable.

What it needs: a merged/ folder written by spacr.core.preprocess_generate_masks(): the intensity channels and the label masks for one field, saved together as .npy. Which masks to measure is named per object – cell_mask_dim, nucleus_mask_dim, pathogen_mask_dim and the organelle slots – and an object with no mask dimension is not measured, rather than measured as empty.

What it produces:

  • measurements/measurements.db, one SQLite table per object type, one row per object, keyed by the plate/row/column/field/object identity spacr.schema composes. The columns are shape, intensity, texture and SPATIAL features – how many neighbours an object has within a radius, how far the nearest one is, what fraction of its border touches another.

  • Optionally, one PNG per object (save_png), cropped by the mask. Those crops are what spacr.deep_spacr.deep_spacr() trains on and what Annotate shows, which is why the cropping lives here rather than beside the classifier: they must be cut by the same mask the measurements came from.

What to do next: Annotate or Classify, if the crops were written, or Regression, to test which perturbation changed which measurement. Both read the database this module writes and neither re-measures anything.


Three behaviours that downstream results depend on:

A field that fails to measure is recorded, summarised at the end of the run and stamped into the database. Without that record a regression could analyse 344 of 384 wells and report a result with no sign that forty are missing; for the same reason the 3-D path refuses a volume it cannot measure correctly instead of measuring it wrongly.

The 2-D path is bit-identical to the code before 3-D support. Mask can emit (Z, Y, X) label volumes (see spacr.zstack), and the voxel spacing, volume columns and units stamp exist so that a 3-D field is measured in physical units or refused. A 2-D field runs the same code as before, with spacing=None, so a screen re-measured today produces the same numbers as when it was first measured.

The radius is part of the column name. neighbors_within_30 is a different column from neighbors_within_50, following the same precedent as homogeneity_distance_<d>, so two plates measured at different radii will not silently concatenate into one frame that means two things.

Illumination correction and a user-drawn ROI reach this module through the registries in spacr.measure_hooks rather than by editing it. Both are empty by default and both entry points return their input unchanged when they are, so an ordinary run is byte-identical to one from before they existed.

Exceptions

ManagerStartError

Raised when Measure cannot start its multiprocessing manager.

Classes

FieldRow

One row of the FEATURES table: one field, and the files that make it.

FieldTable

Rows are fields, columns are channels and mask types.

TableAssignment

What one regex did to one set of dropped files.

Functions

assign_paths_by_regex(paths, pattern, *[, table, plate])

Sort dropped files into rows and channel/mask columns with one regex.

crop_objects_from_array(data, mask_dim[, channels, ...])

Crop every object out of an in-memory merged image+mask array.

field_table_destination(table[, dst])

Where a run of table would write, given the destination it was handed.

field_table_settings(table[, settings, dst])

The measure_crop settings this table decides, over the ones it does not.

generate_cellpose_train_set(folders, dst[, min_objects])

Copy image/mask pairs from source folders into a Cellpose training set.

generate_object_dataset(src[, object_type, channels, ...])

Build an image dataset by cropping individual objects out of the merged

get_components(cell_mask, nucleus_mask, pathogen_mask)

Map each cell to its enclosed nucleus/pathogen labels via mask lookup.

get_object_counts(src)

Return per-count-type totals and per-file averages from the measurements DB.

img_list_to_grid(grid[, titles])

Plot a grid of images with optional titles.

mask_role_of(token)

Resolve what a user typed in a mask column to a spaCR object role.

measure_crop(settings)

Extract per-object morphology/intensity measurements and (optionally) cropped PNGs from mask stacks.

measure_from_field_table(table[, settings, dst, progress])

Measure a table of hand-picked images and masks. The FEATURES entry point.

measure_volume_rows(rows, *[, object_type, progress])

Measure current or saved 3D image sets into their image-folder databases.

process_meassure_crop_results(partial_results, settings)

Deprecated alias for process_measure_crop_results().

process_measure_crop_results(partial_results, settings)

Save and display figures carried by completed Measure jobs.

resolve_measurement_spacing(settings, ndim[, n_z])

Return (spacing, stamp) for a measurement of ndim spatial dimensions.

resolve_n_jobs(n_jobs[, cpu_count])

Return the number of worker processes measure_crop will actually use.

resolve_pool_size(n_jobs, n_files[, start_method])

Return the worker count for a set of image fields.

save_and_add_image_to_grid(png_channels, img_path, grid)

Add an image to a grid and save it as PNG.

spatial_column_names(radius)

Return the five spatial column names for radius, in emitted order.

write_field_table_project(table, dst)

Write the table out as the folders the Mask module leaves behind.

Module Contents

exception spacr.measure.ManagerStartError[source]

Bases: spacr.errors.ConfigurationError

Raised when Measure cannot start its multiprocessing manager.

The exception message reports the active start method, underlying error, and practical remedies. No fields are measured after this error.

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

class spacr.measure.FieldRow[source]

One row of the FEATURES table: one field, and the files that make it.

A row becomes exactly one merged/<stem>.npy, so it is also one field in the measurements database.

Variables:
  • label – what the row is called in the table’s first column, taken from the filenames. It is not the database identity; well and field are.

  • channels – channel index -> source path. The indices are positions on the merged array’s channel axis, counted from zero.

  • masks – role -> source path, for the roles this row supplies. The same mask file may appear in several rows, which is how one drawn mask is measured against several acquisitions.

  • well – the well id this field is filed under. Hand-drawn fields did not come from a plate, so they share one well by default and the table shows it rather than inventing a different one per row.

  • field – the field number within that well, unique per row.

stem(plate)[source]

The plate_well_field name this row is written and measured as.

Parameters:

plate – the plate name the whole table carries.

Returns:

the stem, which spacr.schema.parse_field_stem() reads back into the plate, row, column and field the database is keyed by.

class spacr.measure.FieldTable[source]

Rows are fields, columns are channels and mask types.

This is the thing the FEATURES window edits and the only input measure_from_field_table() needs. It is deliberately Qt-free: the window drives it, and the tests drive it without a window.

Variables:
  • rows – one FieldRow per field, in table order.

  • n_channels – how many channel columns the table has.

  • roles – which mask columns it has, in spacr.crops.MASK_PLANE_ORDER order – which is the order the planes are stacked in, so the two cannot drift.

  • plate – the plate name every row’s stem starts with. It names where the files came from rather than claiming a plate that was never run.

  • channel_tokens – which channel token owns which channel column, by position: channel_tokens[i] is the token that column i means. The table stores this because it is filled over several drops. The ranking that turns C1/C2 into columns 0 and 1 is a property of a set of tokens, and a user fills this table one field at a time, so without the stored tokens the second drop would rank its own files from scratch and put C2 in column 0 beside the first drop’s C1. Empty means no column means any particular token yet – every cell was filled by browsing rather than by the regex.

is_ready()[source]

Whether the table is complete enough to measure.

mask_dims()[source]

role -> plane index on the merged array this table would write.

The masks follow the channels with no gap, which is the only layout spacr.crops.read_merged_plane_layout() accepts – it recomputes the indices from the channel count and the order and refuses a manifest that disagrees.

ordered_roles()[source]

The mask columns in merged-plane order, duplicates removed.

problems()[source]

Everything that would stop this table being measured, as sentences.

Empty means measure_from_field_table() will run. The window shows these live, so a user never presses a Run button that is going to refuse.

class spacr.measure.TableAssignment[source]

What one regex did to one set of dropped files.

Variables:
  • table – the table the files were assigned into.

  • assigned – (path, row label, column caption) for every file that landed somewhere, in the order the paths were given.

  • unassigned – (path, reason) for every file that did not. The window lists these so that no dropped file is left out without a reason.

spacr.measure.assign_paths_by_regex(paths, pattern, *, table=None, plate=None)[source]

Sort dropped files into rows and channel/mask columns with one regex.

The regex is matched against each file’s BASENAME. What it captures decides where the file goes:

  • one of FIELD_TABLE_FIELD_GROUPS names the row. Files sharing a field token share a row, which is what makes a four-channel field one row rather than four.

  • one of FIELD_TABLE_MASK_GROUPS sends it to a mask column, through mask_role_of().

  • one of FIELD_TABLE_CHANNEL_GROUPS sends it to a channel column.

The channel token is ranked, not read as a number. C1/C2/C3 and w1/w2 and 0/1/2 all have to end up as channels 0, 1, 2, and no literal reading of C1 is right for all three – a literal read makes the first set start at channel 1 and leaves channel 0 empty. The distinct channel tokens are therefore sorted (numerically on their trailing digits) and mapped onto 0, 1, 2 … in that order. The mapping is therefore a property of the set of files, not of any one of them, which is why the window shows the assignment rather than describing the rule.

The ranked set is the table’s, not the drop’s. table stores which token owns which column in FieldTable.channel_tokens, and a later drop is ranked against the union of what it brings and what is already there. A token that ranks before one already placed renumbers the columns and moves the files already in them (_renumber_channels()), so every row agrees about what channel 0 is. Ranking each drop on its own instead would put a second field’s C2 in column 0 beside a first field’s C1, and the only sign of it would be in the database.

Parameters:
  • paths – file paths to assign.

  • pattern – a regex with at least a field group and one of a channel or mask group.

  • table – an existing table to add to. A new one is built when this is None; its channel count and mask columns come from what the files turn out to hold.

  • plate – the plate name for a new table.

Returns:

a TableAssignment. Nothing is read from disk and nothing is written.

Raises:

re.error – if pattern does not compile. The window catches this and shows it under the box rather than letting it reach a run.

spacr.measure.crop_objects_from_array(data, mask_dim, channels=(0, 1, 2), min_area=0, max_area=0, mask_background=True, normalize=True, percentiles=(1, 99), buffer=10, to_rgb=True, limit=None, size=None)[source]

Crop every object out of an in-memory merged image+mask array.

This is the no-database counterpart of generate_object_dataset(), used by the Measure live preview to show what the crops will look like before a run: it reads the object labels straight from a mask slice of a single merged .npy and returns the cropped, normalised images.

Parameters:
  • data – merged array (H, W, C) — image channels then mask slices.

  • mask_dim – slice index of the object-class mask to crop by.

  • channels – image channel indices to assemble (order = RGB order).

  • min_area – smallest object area (px) to keep; 0 = no lower bound.

  • max_area – largest object area (px) to keep; 0 = no upper bound.

  • mask_background – zero pixels outside the object.

  • normalize – per-channel percentile-normalise each crop.

  • percentiles – (low, high) for normalisation.

  • buffer – padding (px) around each object’s bounding box.

  • to_rgb – assemble the chosen channels into an HxWx3 uint8 image (1→grey→RGB, 2→padded, 3→RGB, >3→first three); else keep N channels in the merged array’s own dtype.

  • limit – cap the number of objects returned.

  • size – (width, height) to resize every crop to, or None to return each object’s own bounding box. This is measure_crop’s png_size, resized the same way the run resizes – the same PIL.Image.resize call at its default resampling – because this feeds the Measure preview, which shows what a run will write. With None the preview shows bounding boxes, while a run writes crops of png_size.

Returns:

list of {'label', 'area', 'bbox', 'crop'} dicts, largest objects first.

Note

to_rgb=True is the one place this function leaves the working dtype, because a GUI image is 8-bit. It narrows with _crop_to_uint8() (a rescale off the dtype range), not with a clip at 255 – a clip made every pixel of an unnormalised 16-bit object come back as pure white, so the preview showed a white blob and the run it was previewing did not.

spacr.measure.field_table_destination(table, dst=None)[source]

Where a run of table would write, given the destination it was handed.

The window and the run both call this function, so they report the same folder. src is one of FIELD_TABLE_DECIDED_KEYS, so the FEATURES window shows it filled in and disabled; with dst=None the destination is a features folder beside the first channel file of the first row.

Parameters:
  • table – the FieldTable the run would measure.

  • dst – the destination the caller was given, or None to derive one from the table.

Returns:

the project root, or None when the table is too empty to derive one. Nothing is read from disk.

spacr.measure.field_table_settings(table, settings=None, dst=None)[source]

The measure_crop settings this table decides, over the ones it does not.

Everything the table can answer is answered from the table: the channel list, the mask plane of every object it supplies, the crop modes that are possible, the PNG channels, and src. Every other key is the user’s, taken from settings and defaulted by spacr.settings.get_measure_crop_settings() exactly as the Measure module defaults them – so the FEATURES window and the Measure module disagree about nothing.

A role the table does NOT supply is set to None rather than left out, which is how measure_crop is told not to measure it.

Parameters:
  • table – the FieldTable the user filled in.

  • settings – the user’s answers from the settings panel.

  • dst – the project root the run will write. src is its merged folder, which is where measure_crop reads fields from.

Returns:

a new settings dict. Nothing is read from disk.

spacr.measure.generate_cellpose_train_set(folders, dst, min_objects=5)[source]

Copy image/mask pairs from source folders into a Cellpose training set.

Only pairs whose mask contains at least min_objects labeled objects (background label 0 excluded) are copied. Files are renamed with their source folder name as prefix to avoid collisions.

Parameters:
  • folders – Iterable of source folders, each containing a masks/ subfolder and the raw images alongside it.

  • dst – Destination folder; imgs/ and masks/ subfolders are created if missing.

  • min_objects – Minimum number of unique object labels required in a mask for the pair to be included. Default 5.

Returns:

The finalized spacr.errors.RunLedger. Unreadable masks and failed copies are recorded on it, and the ledger prints a summary of them at the end, so a training set with missing pairs is reported.

spacr.measure.generate_object_dataset(src, object_type='cell', channels=(0, 1, 2), min_area=None, max_area=None, columns=None, rows=None, fields=None, plates=None, where=None, criteria=None, output_dir=None, png_size=(128, 128), mask_background=True, normalize=True, percentiles=(1, 99), buffer=10, mask_dims=None, save_png=True, return_arrays=False, limit=None, db_path=None, verbose=True)[source]

Build an image dataset by cropping individual objects out of the merged image+mask arrays, selected by measurement and/or metadata criteria.

spaCR’s merged/ arrays store the image channels first and then one integer label-mask slice per object class (cell, nucleus, pathogen, organelle). The measurements database records, for every object, its integer object_label, the merged .npy it came from (path_name), its well/field metadata, and its features (e.g. cell_area). This function queries that database for the objects you want, then for each hit slices the object out of its array using object_label and the class mask, assembles the channels you ask for into an image, and saves a PNG.

Example — an RGB dataset from image channels 0, 2, 4 for cells larger than 10000 px² in columns 1 and 2:

generate_object_dataset(
    "/data/plate1", object_type="cell",
    channels=(0, 2, 4), min_area=10000, columns=[1, 2])
Parameters:
  • src – experiment root (the folder that holds merged/ and measurements/measurements.db), or the merged folder itself.

  • object_type – which object table + mask slice to crop. With the default mask_dims the accepted values are 'cell', 'nucleus', 'pathogen' and 'organelle'; any other value ('cytoplasm' included) raises ValueError unless mask_dims names its slice explicitly.

  • channels – image channel indices to include, in output order. Three indices → an RGB image; one → greyscale; two → padded to RGB; more than three → kept as an .npy array (and the first three saved as a PNG preview when save_png).

  • min_area – keep only objects with {object_type}_area > this. In a database measured from 2-D fields that column is a px^2 area; in one measured from 3-D volumes it is a volume, in voxels or um^3 according to the row’s measurement_units. This function crops 2-D arrays only and refuses a volumetric one, so in practice the threshold is always px^2 here – but read the stamp before carrying a number between databases.

  • max_area – keep only objects with {object_type}_area < this.

  • columns – list of plate column numbers to include (matched against columnID as 'c<N>'). rows / fields / plates behave the same for rowID ('r<N>') / fieldID ('f<N>') / plateID (raw token).

  • where – raw SQL boolean fragment ANDed onto the query, for anything the shortcuts don’t cover (e.g. "cell_eccentricity < 0.8").

  • criteria – dict of {column: (op, value)} ANDed onto the query, e.g. {"cell_area": (">", 10000), "columnID": ("in", ["c1", "c2"])}.

  • output_dir – where PNGs (and any .npy for >3 channels) are written; defaults to <root>/object_dataset/<object_type>.

  • png_size – (width, height) the crop is resized to.

  • mask_background – zero out pixels outside the object (isolate it).

  • normalize – per-channel percentile-normalise before writing.

  • percentiles – (low, high) percentiles for normalisation.

  • buffer – pixels of padding around the object’s bounding box.

  • mask_dims – dict mapping object type → its mask slice index. Defaults to spaCR’s layout {cell:4, nucleus:5, pathogen:6, organelle:7} (four image channels). Override if your arrays have a different channel count.

  • save_png – write PNG files (set False to only collect arrays).

  • return_arrays – also return the cropped arrays in the manifest.

  • limit – cap the number of objects processed, for example for previews.

  • db_path – explicit path to measurements.db (else derived from src).

  • verbose – print a short progress summary.

Returns:

a manifest list[dict]; each entry has object_label, path_name, plateID/rowID/columnID/fieldID, png_path (if saved) and array (if return_arrays).

Note

The crop keeps the merged array’s dtype. A uint16 field gives uint16 crops, in the manifest and in the .npy written for more than three channels; normalize stretches into that dtype’s full range, not into 0-255. The single narrowing to 8 bit happens in _save_object_crop(), where PIL needs it, and it rescales (_crop_to_uint8()).

It used to cast to float32, normalise into 0-255 and then np.clip(crop, 0, 255).astype(np.uint8). With normalize=False that clip hit every 16-bit pixel brighter than 255 – i.e. the whole object – so the PNG written to disk was a solid white silhouette. The datasets built from it were trained on saturated images and nothing said so.

spacr.measure.get_components(cell_mask, nucleus_mask, pathogen_mask)[source]

Map each cell to its enclosed nucleus/pathogen labels via mask lookup.

Parameters:
  • cell_mask – Label mask of cells.

  • nucleus_mask – Label mask of nuclei.

  • pathogen_mask – Label mask of pathogens.

Returns:

Tuple (nucleus_df, pathogen_df) where each DataFrame has one row per (cell, child) pair with columns cell_id and either nucleus or pathogen.

spacr.measure.get_object_counts(src)[source]

Return per-count-type totals and per-file averages from the measurements DB.

Reads the object_counts table from <src>/measurements/measurements.db and aggregates by count_type.

Parameters:

src – Path to the run folder containing measurements/measurements.db.

Returns:

DataFrame with columns count_type, total_object_count, and avg_object_count_per_file_name.

spacr.measure.img_list_to_grid(grid, titles=None)[source]

Plot a grid of images with optional titles.

Parameters:
  • grid (list) – List of images to be plotted.

  • titles (list) – List of titles for the images.

Returns:

fig (Figure) – The matplotlib figure object containing the image grid.

spacr.measure.mask_role_of(token)[source]

Resolve what a user typed in a mask column to a spaCR object role.

Accepts the role’s own name, the plural and the common laboratory synonyms (nuclei, parasite, mito), and the numbered organelle spelling the settings forms use on screen – Organelle 2 is organelleb, because the slots are lettered internally and numbered for the reader.

Parameters:

token – what the regex captured or the user chose, in any case.

Returns:

a role from spacr.crops.MASK_PLANE_ORDER, or None when the token names no object spaCR can measure.

spacr.measure.measure_crop(settings)[source]

Extract per-object morphology/intensity measurements and (optionally) cropped PNGs from mask stacks.

Consumes the merged/ folder produced by spacr.core.preprocess_generate_masks() (channel arrays + mask stacks saved as .npy), computes shape, intensity, texture and spatial features per cell / nucleus / pathogen / cytoplasm object, and writes them to a SQLite measurements.db. When save_png is enabled it also crops per-object PNG thumbnails, which are the training input for spacr.deep_spacr.deep_spacr().

Parameters:

settings –

Settings dict, canonicalized via spacr.settings.get_measure_crop_settings(). Key entries the function reads:

  • src (str or list) — one or more plate or …/merged folders, each measured in turn into its own measurements folder. Text naming several plates (a pasted list, curly quotes, one path per line) is split into its plate folders. A cloud address Make Masks has analysed is measured from the local folder Make Masks staged it in; a cloud folder of merged stacks is mirrored under cloud_cache first. cloud_results copies the measurements folder back to cloud storage.

  • psf_measurement_source — original (default) uses the normal rescaled/preprocessed intensities; processed adds an explicitly calibrated PSF before quantitative features. The immutable kernel reaches every worker. Source images and exported crops stay unchanged. Field provenance is saved in intensity_rescale; incompatible existing PSF measurements are refused before any rows are appended.

  • cell_mask_dim / nucleus_mask_dim / pathogen_mask_dim — channel index of each mask stack; None disables that object type.

  • cell_min_size / nucleus_min_size / pathogen_min_size / cytoplasm_min_size — pixel-area cutoffs.

  • channels — list of intensity channels to measure.

  • crop_mode — list drawn from ['cell','nucleus','pathogen', 'cytoplasm']; each entry produces one PNG per object.

  • save_png — write per-object PNG thumbnails.

  • normalize — [lower_pct, upper_pct] for PNG normalization.

  • normalize_by — 'png' (per-crop) or 'fov' (per-field).

  • timelapse, timelapse_objects, n_jobs, test_mode.

  • database_write_queue_gib — optional SQLite queued-data RAM budget from zero to 64 GiB. Omit to use Preferences (default one GiB). Zero uses disk-only buffering; overflow lives under measurements/.write_queue. Each field commits atomically and unfinished write packets remain available after failure.

  • dry_run — validate the settings, report the plan and stop; the input folders are inspected but nothing is written.

Returns:

None on a normal run, which writes measurements/measurements.db, measure_crop_settings.csv, and (if save_png) PNGs into per-object subfolders under src. When dry_run is set, the list of spacr.validate.Problem returned by spacr.validate.run_preflight(), and nothing is written.

Raises:
  • ValueError – if src is not a string or a list of strings.

  • spacr.errors.ConfigurationError – only in strict mode (settings['strict_errors'], or the SPACR_STRICT_ERRORS environment variable). The normalize, normalize_by, mask-dimension/min-size and channels type checks otherwise print a WARNING and return None without measuring anything.

Example

from spacr.measure import measure_crop
settings = {
    'src': '/data/plate01/merged',
    'cell_mask_dim': 4, 'nucleus_mask_dim': 5, 'pathogen_mask_dim': 6,
    'channels': [0, 1, 2, 3],
    'crop_mode': ['cell'], 'save_png': True,
    'normalize': [1, 99], 'normalize_by': 'png',
}
measure_crop(settings)

See also

spacr.core.preprocess_generate_masks() — upstream mask generation. spacr.io.generate_dataset() — build a training set from the PNGs. spacr.deep_spacr.deep_spacr() — train a CNN on the crops.

spacr.measure.measure_from_field_table(table, settings=None, dst=None, progress=None)[source]

Measure a table of hand-picked images and masks. The FEATURES entry point.

The second entry point into this module. measure_crop() starts from a merged/ folder a pipeline already built; this starts from a table a user filled in by dropping files onto it, writes that folder, and then calls measure_crop() itself – unchanged, with no flag saying where the fields came from. The database, the crops and the folder tree are therefore the Measure module’s, because they are made by it.

Parameters:
  • table – the FieldTable the FEATURES window edited.

  • settings – the user’s answers from the settings panel. The keys the table decides are overwritten from it – see field_table_settings().

  • dst – the project root to write. Defaults to a features folder beside the first channel file of the first row, which is where a user who dropped a folder in expects to find the results. See field_table_destination(), which is the one place that default is worked out.

  • progress – called with a sentence as each stage starts, or None. It runs on whatever thread this does – the FEATURES window runs this on a worker and its callback only emits a signal. Writing the arrays and measuring them are separate stages because on a large table the second takes minutes and the first does not.

Returns:

{'destination', 'db_path', 'settings', 'stems', 'merged'}. db_path is the measurements database whether or not it exists, so a caller can report the path it was asked for.

Raises:

spacr.errors.ConfigurationError – if the table is incomplete or a file breaks the array contract. Nothing is measured in that case.

Example

from spacr.measure import (
    assign_paths_by_regex, measure_from_field_table)

found = assign_paths_by_regex(
    paths,
    r'(?P<field>fov\d+)_(?:C(?P<channel>\d+)'
    r'|(?P<mask>cell|nucleus))')
measure_from_field_table(found.table, {'save_png': True})

See also

measure_crop() – the run this delegates to, unchanged. write_field_table_project() – the folders it writes first.

spacr.measure.measure_volume_rows(rows, *, object_type='cell', progress=None)[source]

Measure current or saved 3D image sets into their image-folder databases.

Parameters:
  • rows – dictionaries with source_path, optional mask_path and extra channel_paths. The current image supplies a snapshot containing immutable channels, copied labels, axes, spacing and unit, so unsaved edits are measured without changing their saved mask.

  • object_type – canonical object role, such as cell or nucleus.

  • progress – optional worker-safe callback receiving translated text.

Returns:

database paths, image count and object count. All rows are measured before any write; each destination commits atomically.

Stored morphology, intensity, correlation and spatial feature names follow Measure. Pixel coordinates stay explicitly pixel coordinates; physical metadata are converted to micrometres. Repeating an image set replaces only this entry point’s earlier rows. Other fields and tables are retained.

spacr.measure.process_meassure_crop_results(partial_results, settings)[source]

Deprecated alias for process_measure_crop_results().

The misspelled name remains available for existing scripts and will be removed in a future major release.

Parameters:
  • partial_results – Completed Measure job tuples, passed unchanged to process_measure_crop_results() after a DeprecationWarning.

  • settings – Resolved Measure settings, passed unchanged; src identifies the output root.

spacr.measure.process_measure_crop_results(partial_results, settings)[source]

Save and display figures carried by completed Measure jobs.

Parameters:
  • partial_results – Completed job tuples. None entries are skipped; each figure is written below <src>/../results/ and then closed.

  • settings – Resolved Measure settings. src identifies the output root.

spacr.measure.resolve_measurement_spacing(settings, ndim, n_z=1)[source]

Return (spacing, stamp) for a measurement of ndim spatial dimensions.

spacing is handed straight to skimage.measure.regionprops_table() and (as sampling) to scipy.ndimage.distance_transform_edt(). stamp is the dict of MEASUREMENT_STAMP_COLUMNS written onto every row so the units are recorded rather than inferred.

2-D returns (None, px stamp) unconditionally. Even when a voxel size is configured it is not applied, so a 2-D run is numerically identical to every spaCR run before this function existed.

3-D requires a z/xy relationship and will not invent one. With anisotropic voxels an unspaced volume is not merely in unusual units: a voxel count is not proportional to a physical volume, a distance transform measures a different length along z than along x, and major_axis_length mixes the two. This mirrors spacr.zstack.resolve_anisotropy(), which raises rather than defaulting to 1.0 because “isotropic” is a claim about the microscope, not a neutral value. Set voxel_size_z_um and voxel_size_xy_um (preferred – it also gives physical units), or set anisotropy alone (correct geometry, xy-pixel units).

Parameters:
  • settings – measure settings dict; reads voxel_size_z_um, voxel_size_xy_um and anisotropy.

  • ndim – 2 or 3.

  • n_z – number of z planes behind the measurement; 1 for a 2-D field.

Returns:

(spacing, stamp). spacing is None for 2-D, a (dz, dy, dx) tuple for 3-D.

Raises:
spacr.measure.resolve_n_jobs(n_jobs, cpu_count=None)[source]

Return the number of worker processes measure_crop will actually use.

None selects spaCR’s default, cpu_count - N_JOBS_HEADROOM (at least 1). Explicit values are validated and capped at the available CPU count.

Parameters:
  • n_jobs – the requested worker count, or None for the default.

  • cpu_count – core count to resolve against; defaults to multiprocessing.cpu_count().

Returns:

an int in [1, cpu_count].

Raises:

spacr.errors.ConfigurationError – n_jobs is zero, negative, or not an integer. A pool of zero workers measures nothing, and the value is refused rather than replaced with a worker count the user did not request.

spacr.measure.resolve_pool_size(n_jobs, n_files, start_method=None)[source]

Return the worker count for a set of image fields.

The count is capped at the number of fields for every start method. spawn and forkserver start a fresh interpreter for every worker, and worker starts are paced ten seconds apart, so a surplus fork worker also delays the run by ten seconds and then has nothing to do.

Parameters:
  • n_jobs – the resolved worker count from resolve_n_jobs().

  • n_files – how many fields there are to measure.

  • start_method – accepted for existing callers; every start method is capped the same way.

Returns:

an int >= 1.

spacr.measure.save_and_add_image_to_grid(png_channels, img_path, grid, plot=False)[source]

Add an image to a grid and save it as PNG.

Parameters:
  • png_channels (ndarray) – The crop in file order – red plane first – as spacr.crops.build_png_channels() assembles it. Written without narrowing, so a uint16 crop becomes a 16-bit PNG; a float crop is silently written as 8-bit by cv2. Four or more channels raise rather than losing one to an alpha plane.

  • img_path (str) – Where the PNG goes. Its parent folder is stamped with the format sidecar and must already exist – the caller in _measure_crop_core creates it. If it does not, the stamp fails with a printed warning, cv2.imwrite returns False, and the call returns normally having written nothing at all. A bare filename with no directory part stamps the current working directory.

  • grid (list) – Anything with append; read only when plot is true, and appended to in place, so the return value is the object that was passed in. None passes through untouched while plot is false.

  • plot (bool) – Truthiness, not identity, decides. False (the default) leaves grid completely untouched – the PNG is still written – which is why an ordinary run ends with an empty grid. True appends the crop for img_list_to_grid(): a crop of exactly dtype uint16 is appended as a high-byte narrowed uint8 copy, every other dtype is appended unchanged, and a uint8 crop is appended by reference, so a caller that reuses its buffer mutates what is already in the grid.

Returns:

grid (list) – The same object that was passed in, with the crop appended only if plot was true.

Raises:
  • spacr.crops.CropError – png_channels has more than three channels.

  • AttributeError – grid is None (or has no append) and plot is true. The PNG has already been written by then.

  • cv2.error – img_path has no extension cv2 recognises. The folder sidecar has already been written by then.

Note

The file’s colour slots hold what the mapping declares. The caller assembles png_channels in file order — red plane first — with spacr.crops.build_png_channels() and spacr.crops.resolve_png_channel_mapping(); under the legacy settings['png_dims'] list that mapping is entry 0 blue, 1 green, 2 red, so png_dims[0] lands in the file’s BLUE slot.

That is the REVERSE of what png_dims reads like, and it is kept deliberately: every crop already on disk was written with this mapping, so flipping it would silently change what each colour means and invalidate the models trained on those crops. The mapping is declared rather than corrected.

cv2.imwrite interprets a 3-channel array as BGR, so spacr.crops.to_cv2_bgr() reverses the array once, here, and cv2’s interpretation lands the red plane in the file’s red slot. It refuses more than three channels rather than letting cv2 write the fourth as an alpha plane for every reader to drop in silence.

The format is versioned: spacr.crops.stamp_crop_folder() drops a .spacr_crop_format.json sidecar into the crop folder before the first PNG lands, recording format 3 (declared_rgb). An unmarked folder means format 1 (legacy), whose bytes match format 3 for the same declared mapping, so both are read as-is; only format 2, whose stored channel order is reversed, is corrected by spacr.crops.read_crop_png(), and spacr.crops.migrate_crop_folder rewrites such a folder in place.

Crops are still uint16, so these are 16-bit PNGs and no intensity is discarded at write time. The narrowing to 8 bit happens once, on read, in spacr.crops.narrow_to_uint8(), which always takes the HIGH BYTE (// 256) — replacing PIL’s two incompatible rules (high byte for an RGB PNG, a clip at 255 for a single-channel one, which returned solid white for any crop brighter than that).

spacr.measure.spatial_column_names(radius)[source]

Return the five spatial column names for radius, in emitted order.

The radius is baked into neighbors_within_<r> – the same precedent as homogeneity_distance_<d> and percentile_<p>. Two plates measured at different radii therefore produce different columns and will not concat.

Parameters:

radius – neighbourhood radius; truncated with int() for the neighbors_within_<r> name. The other four names do not depend on it.

Returns:

list of five column names.

spacr.measure.write_field_table_project(table, dst)[source]

Write the table out as the folders the Mask module leaves behind.

This is the only step the FEATURES button adds to Measure: it turns a table of hand-picked files into stack/, masks/ and merged/ exactly as spacr.core.preprocess_generate_masks() would have left them, down to the plane-layout manifest, so the run that follows is an ordinary measure run rather than a separate code path.

Parameters:
Returns:

{'destination', 'merged', 'stack', 'masks', 'stems'}.

Raises:

spacr.errors.ConfigurationError – if the table is incomplete, or if any file breaks the uint16 array contract measure_crop reads. Nothing is written past the field that failed.

Nested helpers

_calculate_radial_distribution._calculate_average_intensity(distance_map, single_channel_image, num_bins, region_mask)

Calculate the average intensity of a single-channel image based on the distance map.

Only pixels inside region_mask (the cell) are binned. The previous version multiplied the distance map by the cell mask instead, which set every pixel outside the cell to distance 0 and dumped the whole field background into bin 0 — so rad_dist_..._bin_0 measured background, not the innermost shell, and inverted the meaning of the feature.

Parameters:
  • distance_map (numpy.ndarray) – Distance from the object boundary.

  • single_channel_image (numpy.ndarray) – The single-channel image.

  • num_bins (int) – The number of bins for the radial distribution.

  • region_mask (numpy.ndarray) – Boolean mask of the parent cell.

Returns:

numpy.ndarray – The radial distribution of average intensities. Bins with no pixels are NaN rather than a meaningless 0.

spacr/measure.py:2663

_cell_cycle_by_well._fractions(phases, prefix='')

Count and phase fractions of one group of nuclei.

spacr/measure.py:5510

_cellprofiler_tables.field_masks(stem)

{object type: label image} of one field, read once.

spacr/measure.py:10426

_cellprofiler_tables.lookup(role, image_numbers, xs, ys)

The spaCR label under each centre in role’s mask.

A centre outside the image cannot identify an edge object; the check comes before rounding so negative subpixel positions stay unmatched.

spacr/measure.py:10437

_commit_measure_packet.dispatch(operation)

Save one approved operation to the packet’s central database.

Parameters:

operation – approved helper name, positional arguments and keywords.

spacr/measure.py:9115

_confluency_phase_features.log_sd(window)

Log of the local standard deviation in window, in noise units.

spacr/measure.py:3840

_extended_regionprops_table._gini(array)

NaN-safe Gini coefficient of an intensity array.

spacr/measure.py:2048

_extended_regionprops_table._masked_intensity(region)

Pixels inside a region across old and new scikit-image names.

spacr/measure.py:2080

_morphological_measurements._all_masks()

Object type -> label image, for the masks this run actually has.

spacr/measure.py:1358

_morphological_measurements._props(mask)

regionprops_table + (3-D only) the explicitly-named volume columns.

spacr/measure.py:1330

_morphological_measurements._with_bystanders(frame, mask, pathogen_links)

Merge the bystander block onto the CELL props frame.

A cell is infected if it holds a pathogen, a bystander if it holds none but sits within the reach of one that does, and distal otherwise. Without the split the last two are the same row, and the uninfected control is a mixture whose variance hides the effect every infection comparison is looking for.

The reach is derived from this field’s own cells, as a multiple of their median diameter, so it means the same thing at 20x and 63x.

Props on the LEFT, for the reason _with_spatial gives.

spacr/measure.py:1417

_morphological_measurements._with_distances(frame, name)

Merge the object-distance block onto a props frame.

Props on the LEFT for the reason _with_spatial gives: ‘label’ has to keep column position 0. A non-empty frame means name’s mask holds labels, so _all_masks already carries it.

spacr/measure.py:1367

_morphological_measurements._with_spatial(frame, mask)

Merge the spatial block onto a props frame. Props on the LEFT.

spacr/measure.py:1400

_phases_by_xgboost._model()

A fresh classifier with the fixed phase-calling parameters.

spacr/measure.py:5119

_pin_cupy_cudart_headers._pinned(libname, *args, **kwargs)

The pinned runtime header directory for cudart, else the finder’s answer.

spacr/measure.py:818

_save_viability_figures._keep(fig, name)

Save one figure under the viability results folder.

spacr/measure.py:8315

_stain_cut._share(threshold)

Share of the plate’s objects above threshold.

spacr/measure.py:7361

_torch_intensity_table.host(tensor)

Copy a tensor to a NumPy array.

spacr/measure.py:2366

_two_population_fit._mixture(v)

The fitted two-population density at v.

spacr/measure.py:7286

_two_population_fit._near(v)

How many values lie within reach of v.

spacr/measure.py:7294

_wound_axis.inside(offset)

Mark axis samples inside the image at the given normal offset.

spacr/measure.py:5985

_wound_closure_tables.planes(items=items)

Yield this field’s wound-analysis planes in time order.

spacr/measure.py:7080

_wound_fronts.running(front)

Running median of the good fronts, carried over rows with none.

spacr/measure.py:6201

_write_volume_measurements.write(frame)

Validate the destination inside its writer transaction before changing it.

spacr/measure.py:12999

assign_paths_by_regex.first(names)

The first of names the pattern actually defines.

spacr/measure.py:12425

generate_object_dataset._in(colname, values, prefix)

An IN (...) clause and its parameters, built safely.

Values are passed as ? placeholders rather than formatted into the SQL, because they come from a settings file and a value containing a quote would otherwise break or alter the query.

spacr/measure.py:11783

measure_crop.job_callback(result)

Save returned figures and report field computation progress.

Parameters:

result – index, average duration, surviving labels, figures, error text and an optional queued-write ticket. An integer zero labels value records the original field failure. SQLite success is recorded separately by the writer after committing all scientific rows. Other backends keep their existing synchronous worker verdict.

spacr/measure.py:9895

measure_crop.make_error_callback(job_file)

Bind the filename into the pool’s error callback.

apply_async hands the error callback only the exception, so the file has to be closed over. Without this hook a worker that died outright vanished entirely: the exception sat on an AsyncResult nobody read, and the run still printed “Successfully completed run”.

Parameters:

job_file – The .npy filename of the field, as it appears in files – a bare basename, not a path joined onto settings['src']. It is used unchanged as the ledger key and as the reported_files entry, so anything else silently loses the match against files in the finally sweep and the field is filed a second time as “field produced no result”.

Returns:

A one-argument callable suitable as the error_callback of Pool.apply_async; it takes the exception and returns None. Call it as make_error_callback(file)(exc) when raising the exception yourself, which is what the retry loop does on the last attempt – the ledger counts fields, not tries, so a field that failed twice and then worked must not be reported here at all.

spacr/measure.py:9919

measure_crop.make_error_callback._on_error(exc)

Record one worker’s failure against the file that caused it.

spacr/measure.py:9944

measure_crop.record_verdict(item, error=None, stage='measure')

Count each field once, after its actual final outcome.

spacr/measure.py:9880

measure_crop.write_callback(item, ticket, error)

Record a field only after its packet’s final SQL verdict.

spacr/measure.py:9891

measure_from_field_table.say(message)

Pass a stage message to progress, when a callback was given.

Exceptions from the callback are ignored because the caller is a window that may be closed while this is still running: the FEATURES window’s callback emits a Qt signal, and a worker that outlives its widget raises RuntimeError from the emit. The run continues in that case.

spacr/measure.py:12790

measure_volume_rows.report(message)

Keep a closed progress display from changing a database result.

spacr/measure.py:13052

resolve_measurement_spacing._positive(name)

One spacing value, refused unless it is a positive number.

A zero or negative spacing makes every physical measurement wrong by a factor nobody can recover afterwards, so it is refused rather than defaulted.

spacr/measure.py:459