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.
Turn masks and channels into one row per object, in a database.
WHAT IT IS FOR. Segmentation says WHERE the objects are; this module says what they are LIKE. It reads the arrays Mask wrote and produces the table every downstream question is asked of – which genes changed a phenotype, which cells to train a classifier on, which wells to believe.
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 simply 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 identityspacr.schemacomposes. 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 whatspacr.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; Regression, if the question is which perturbation moved which measurement. Both read the database this writes and neither re-measures anything.
THREE THINGS THAT ARE NOT OBVIOUS AND ARE LOAD-BEARING:
A FIELD THAT FAILS TO MEASURE IS RECORDED, SUMMARISED AND STAMPED INTO THE DATABASE. Silence would let a regression analyse 344 of 384 wells and report a result with no sign that forty are missing, which is the failure this module is most careful about – the same reason its 3-D path refuses a volume it cannot measure correctly instead of measuring it wrongly.
THE 2-D PATH IS BIT-IDENTICAL AND DELIBERATELY SO. Mask can emit (Z, Y, X)
label volumes now (see spacr.zstack), and everything about voxel
spacing, volume columns and the units stamp exists so that a 3-D field is
measured in real units or refused. A 2-D field takes exactly the code it took
before, with spacing=None; a screen measured last year and re-measured
today produces the same numbers.
THE RADIUS IS IN 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¶
Raised when Measure cannot start its multiprocessing manager. |
Classes¶
One row of the FEATURES table: one field, and the files that make it. |
|
Rows are fields, columns are channels and mask types. |
|
What one regex did to one set of dropped files. |
Functions¶
|
Sort dropped files into rows and channel/mask columns with one regex. |
|
Crop every object out of an in-memory merged image+mask array. |
|
Where a run of |
|
The measure_crop settings this table decides, over the ones it does not. |
|
Copy image/mask pairs from source folders into a Cellpose training set. |
|
Build an image dataset by cropping individual objects out of the merged |
|
Map each cell to its enclosed nucleus/pathogen labels via mask lookup. |
|
Return per-count-type totals and per-file averages from the measurements DB. |
|
Plot a grid of images with optional titles. |
|
Resolve what a user typed in a mask column to a spaCR object role. |
|
Extract per-object morphology/intensity measurements and (optionally) cropped PNGs from mask stacks. |
|
Measure a table of hand-picked images and masks. The FEATURES entry point. |
|
Deprecated alias for |
|
Save and display figures carried by completed Measure jobs. |
|
Return |
|
Return the number of worker processes |
|
Return the worker count for a set of image fields. |
|
Add an image to a grid and save it as PNG. |
|
Return the five spatial column names for |
|
Write the table out as the folders the Mask module leaves behind. |
Module Contents¶
- exception spacr.measure.ManagerStartError[source]¶
Bases:
spacr.errors.ConfigurationErrorRaised 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;
wellandfieldare.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_fieldname 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
FieldRowper field, in table order.n_channels – how many channel columns the table has.
roles – which mask columns it has, in
spacr.crops.MASK_PLANE_ORDERorder – 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 columnimeans. THE TABLE REMEMBERS THIS BECAUSE THE TABLE OUTLIVES THE DROP. The ranking that turnsC1/C2into 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 a memory the second drop would rank its own files from scratch and putC2in column 0 beside the first drop’sC1. Empty means no column means any particular token yet – every cell was filled by browsing rather than by the regex.
- mask_dims()[source]¶
role -> plane indexon 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.
- 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, because a file that silently vanishes is the one failure a drag-and-drop table cannot afford.
- 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_GROUPSnames 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_GROUPSsends it to a mask column, throughmask_role_of().one of
FIELD_TABLE_CHANNEL_GROUPSsends it to a channel column.
THE CHANNEL TOKEN IS RANKED, NOT READ AS A NUMBER, and that is the one decision here worth knowing about.
C1/C2/C3andw1/w2and0/1/2all have to end up as channels 0, 1, 2, and there is no reading ofC1that is right for all three – a literal read makes the first set start at channel 1 and leaves channel 0 empty for ever. So the DISTINCT channel tokens are 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 SET IS THE TABLE’S, NOT THE DROP’S.
tableremembers which token owns which column inFieldTable.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’sC2in column 0 beside a first field’sC1, 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
patterndoes 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.npyand 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, orNoneto return each object’s own bounding box. This ismeasure_crop’spng_size, resized THE WAY THE RUN RESIZES – the samePIL.Image.resizecall at its default resampling – because this feeds the Measure preview, whose purpose is to show what a run will write. Without it the preview showed bounding boxes while the run wrote squares, and the crop-size setting looked like it did nothing.
- Returns:
list of
{'label', 'area', 'bbox', 'crop'}dicts, largest objects first.
Note
to_rgb=Trueis 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
tablewould write, given the destination it was handed.ONE ANSWER, so that the window and the run cannot disagree about it.
srcis one ofFIELD_TABLE_DECIDED_KEYS, so the FEATURES window shows it filled in and disabled; before this existed the window derived it only when it had been given a destination, and a window opened without one showed the settings spec’spathplaceholder while the run wrote beside the first channel file. A disabled box captioned “the table decides this” that names the wrong folder is worse than no box at all – it is the window telling the user where their results are not.- Parameters:
table – the
FieldTablethe run would measure.dst – the destination the caller was given, or
Noneto derive one from the table.
- Returns:
the project root, or
Nonewhen 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 fromsettingsand defaulted byspacr.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
Nonerather than left out, which is howmeasure_cropis told not to measure it.- Parameters:
table – the
FieldTablethe user filled in.settings – the user’s answers from the settings panel.
dst – the project root the run will write.
srcis itsmergedfolder, which is wheremeasure_cropreads 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_objectslabeled 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/andmasks/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 summarised loudly at the end, so a training set that is quietly short of pairs announces itself.
- 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 integerobject_label, the merged.npyit 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 usingobject_labeland 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/andmeasurements/measurements.db), or themergedfolder itself.object_type – which object table + mask slice to crop. With the default
mask_dimsthe accepted values are'cell','nucleus','pathogen'and'organelle'; any other value ('cytoplasm'included) raisesValueErrorunlessmask_dimsnames 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
.npyarray (and the first three saved as a PNG preview whensave_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’smeasurement_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
columnIDas'c<N>').rows/fields/platesbehave the same forrowID('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
.npyfor >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 (handy 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 hasobject_label,path_name,plateID/rowID/columnID/fieldID,png_path(if saved) andarray(ifreturn_arrays).
Note
The crop keeps the merged array’s dtype. A
uint16field givesuint16crops, in the manifest and in the.npywritten for more than three channels;normalizestretches 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 thennp.clip(crop, 0, 255).astype(np.uint8). Withnormalize=Falsethat 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 columnscell_idand eithernucleusorpathogen.
- spacr.measure.get_object_counts(src)[source]¶
Return per-count-type totals and per-file averages from the measurements DB.
Reads the
object_countstable from<src>/measurements/measurements.dband aggregates bycount_type.- Parameters:
src – Path to the run folder containing
measurements/measurements.db.- Returns:
DataFrame with columns
count_type,total_object_count, andavg_object_count_per_file_name.
- spacr.measure.img_list_to_grid(grid, titles=None)[source]¶
Plot a grid of images with optional titles.
- 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 2isorganelleb, 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, orNonewhen 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 byspacr.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 SQLitemeasurements.db. Whensave_pngis enabled it also crops per-object PNG thumbnails, which are the training input forspacr.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…/mergedfolders. 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 undercloud_cachefirst.cloud_resultscopies 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 inintensity_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;Nonedisables 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 undermeasurements/.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:
Noneon a normal run, which writesmeasurements/measurements.db,measure_crop_settings.csv, and (ifsave_png) PNGs into per-object subfolders undersrc. Whendry_runis set, the list ofspacr.validate.Problemreturned byspacr.validate.run_preflight(), and nothing is written.- Raises:
ValueError – if
srcis not a string or a list of strings.spacr.errors.ConfigurationError – only in strict mode (
settings['strict_errors'], or theSPACR_STRICT_ERRORSenvironment variable). Thenormalize,normalize_by, mask-dimension/min-size andchannelstype checks otherwise print a WARNING and returnNonewithout 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 other way into this module.
measure_crop()starts from amerged/folder a pipeline already built; this starts from a table a user filled in by dropping files onto it, writes that folder, and then callsmeasure_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
FieldTablethe 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
featuresfolder beside the first channel file of the first row, which is where a user who dropped a folder in expects to find the results. Seefield_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_pathis 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.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 aDeprecationWarning.settings – Resolved Measure settings, passed unchanged;
srcidentifies 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.
Noneentries are skipped; each figure is written below<src>/../results/and then closed.settings – Resolved Measure settings.
srcidentifies the output root.
- spacr.measure.resolve_measurement_spacing(settings, ndim, n_z=1)[source]¶
Return
(spacing, stamp)for a measurement ofndimspatial dimensions.spacingis handed straight toskimage.measure.regionprops_table()and (assampling) toscipy.ndimage.distance_transform_edt().stampis the dict ofMEASUREMENT_STAMP_COLUMNSwritten 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_lengthmixes the two. This mirrorsspacr.zstack.resolve_anisotropy(), which raises rather than defaulting to 1.0 because “isotropic” is a claim about the microscope, not a neutral value. Setvoxel_size_z_umandvoxel_size_xy_um(preferred – it also gives physical units), or setanisotropyalone (correct geometry, xy-pixel units).- Parameters:
settings – measure settings dict; reads
voxel_size_z_um,voxel_size_xy_umandanisotropy.ndim – 2 or 3.
n_z – number of z planes behind the measurement; 1 for a 2-D field.
- Returns:
(spacing, stamp).spacingisNonefor 2-D, a(dz, dy, dx)tuple for 3-D.- Raises:
spacr.zstack.UnknownAnisotropyError – 3-D without a voxel size or anisotropy.
spacr.errors.ConfigurationError –
ndimis neither 2 nor 3, or a supplied voxel size is not a positive finite number.
- spacr.measure.resolve_n_jobs(n_jobs, cpu_count=None)[source]¶
Return the number of worker processes
measure_cropwill actually use.Noneselects spaCR’s default. Explicit values are validated and capped at the available CPU count.- Parameters:
n_jobs – what the user asked for.
Nonemeans “pick for me”.cpu_count – core count to resolve against; defaults to
multiprocessing.cpu_count().
- Returns:
an int in
[1, cpu_count].- Raises:
spacr.errors.ConfigurationError –
n_jobsis zero, negative, or not an integer. A pool of zero workers measures nothing, and quietly turning it into some other number is how a run ends up not doing what it was told.
- spacr.measure.resolve_pool_size(n_jobs, n_files, start_method=None)[source]¶
Return the worker count for a set of image fields.
spawnandforkserverstart a fresh interpreter for every worker, so their worker count is capped at the number of fields.forkkeeps the requested count for compatibility.- Parameters:
n_jobs – the resolved worker count from
resolve_n_jobs().n_files – how many fields there are to measure.
start_method – start method name to decide against; defaults to the interpreter’s current default.
- 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 auint16crop 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_corecreates it. If it does not, the stamp fails with a printed warning,cv2.imwritereturns 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 whenplotis true, and appended to in place, so the return value is the object that was passed in.Nonepasses through untouched whileplotis false.plot (bool) – Truthiness, not identity, decides. False (the default) leaves
gridcompletely untouched – the PNG is still written – which is why an ordinary run ends with an empty grid. True appends the crop forimg_list_to_grid(): a crop of exactly dtypeuint16is appended as a high-byte narroweduint8copy, every other dtype is appended unchanged, and auint8crop 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
plotwas true.- Raises:
spacr.crops.CropError –
png_channelshas more than three channels.AttributeError –
gridisNone(or has noappend) andplotis true. The PNG has already been written by then.cv2.error –
img_pathhas 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_channelsin file order — red plane first — withspacr.crops.build_png_channels()andspacr.crops.resolve_png_channel_mapping(); under the legacysettings['png_dims']list that mapping is entry 0 blue, 1 green, 2 red, sopng_dims[0]lands in the file’s BLUE slot.That is the REVERSE of what
png_dimsreads like, and it is LEFT THAT WAY ON PURPOSE: 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.imwriteinterprets a 3-channel array as BGR, sospacr.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.jsonsidecar 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 byspacr.crops.read_crop_png(), andspacr.crops.migrate_crop_folderrewrites 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, inspacr.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 ashomogeneity_distance_<d>andpercentile_<p>. Two plates measured at different radii therefore produce different columns and will not concat.- Parameters:
radius – neighbourhood radius; truncated with
int()for theneighbors_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 whole of what the FEATURES button adds to Measure: it turns a table of hand-picked files into
stack/,masks/andmerged/exactly asspacr.core.preprocess_generate_masks()would have left them, down to the plane-layout manifest, so the run that follows is an ORDINARY measure run and not a second code path that has to be kept in step with this one.- Parameters:
table – a
FieldTablewhoseFieldTable.problems()is empty.dst – the project root to write. It is created if it does not exist.
- 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_cropreads. 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 — sorad_dist_..._bin_0measured 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:2662
- _cell_cycle_by_well._fractions(phases, prefix='')¶
Count and phase fractions of one group of nuclei.
spacr/measure.py:5432
- _cellprofiler_tables.field_masks(stem)¶
{object type: label image}of one field, read once.spacr/measure.py:10104
- _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:10115
- _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:8850
- _confluency_phase_features.log_sd(window)¶
Log of the local standard deviation in
window, in noise units.spacr/measure.py:3825
- _extended_regionprops_table._gini(array)¶
NaN-safe Gini coefficient of an intensity array.
spacr/measure.py:2047
- _extended_regionprops_table._masked_intensity(region)¶
Pixels inside a region across old and new scikit-image names.
spacr/measure.py:2079
- _morphological_measurements._all_masks()¶
Object type -> label image, for the masks this run actually has.
spacr/measure.py:1357
- _morphological_measurements._props(mask)¶
regionprops_table + (3-D only) the explicitly-named volume columns.
spacr/measure.py:1329
- _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_spatialgives.spacr/measure.py:1416
- _morphological_measurements._with_distances(frame, name)¶
Merge the object-distance block onto a props frame.
Props on the LEFT for the reason
_with_spatialgives: ‘label’ has to keep column position 0. A non-empty frame meansname’s mask holds labels, so_all_masksalready carries it.spacr/measure.py:1366
- _morphological_measurements._with_spatial(frame, mask)¶
Merge the spatial block onto a props frame. Props on the LEFT.
spacr/measure.py:1399
- _phases_by_xgboost._model()¶
A fresh classifier with the fixed phase-calling parameters.
spacr/measure.py:5072
- _pin_cupy_cudart_headers._pinned(libname, *args, **kwargs)¶
The pinned runtime header directory for
cudart, else the finder’s answer.spacr/measure.py:817
- _save_viability_figures._keep(fig, name)¶
Save one figure under the viability results folder.
spacr/measure.py:8191
Share of the plate’s objects above
threshold.spacr/measure.py:7247
- _torch_intensity_table.host(tensor)¶
Copy a tensor to a NumPy array.
spacr/measure.py:2365
- _two_population_fit._mixture(v)¶
The fitted two-population density at
v.spacr/measure.py:7172
- _two_population_fit._near(v)¶
How many values lie within
reachofv.spacr/measure.py:7180
- _wound_axis.inside(offset)¶
Mark axis samples inside the image at the given normal offset.
spacr/measure.py:5905
- _wound_closure_tables.planes(items=items)¶
Yield this field’s wound-analysis planes in time order.
spacr/measure.py:6966
- _wound_fronts.running(front)¶
Running median of the good fronts, carried over rows with none.
spacr/measure.py:6121
- assign_paths_by_regex.first(names)¶
The first of
namesthe pattern actually defines.spacr/measure.py:12059
- generate_object_dataset._in(colname, values, prefix)¶
An
IN (...)clause and its parameters, built safely.Placeholders rather than interpolation: the values come from a settings file, and a formatted list is an injection waiting for a filename with a quote in it.
spacr/measure.py:11460
- 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:9575
- measure_crop.make_error_callback(job_file)¶
Bind the filename into the pool’s error callback.
apply_asynchands 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
.npyfilename of the field, as it appears infiles– a bare basename, not a path joined ontosettings['src']. It is used unchanged as the ledger key and as thereported_filesentry, so anything else silently loses the match againstfilesin thefinallysweep and the field is filed a second time as “field produced no result”.- Returns:
A one-argument callable suitable as the
error_callbackofPool.apply_async; it takes the exception and returnsNone. Call it asmake_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:9599
- measure_crop.make_error_callback._on_error(exc)¶
Record one worker’s failure against the file that caused it.
spacr/measure.py:9624
- measure_crop.record_verdict(item, error=None, stage='measure')¶
Count each field once, after its actual final outcome.
spacr/measure.py:9560
- measure_crop.write_callback(item, ticket, error)¶
Record a field only after its packet’s final SQL verdict.
spacr/measure.py:9571
- measure_from_field_table.say(message)¶
Report a stage, if anyone asked to hear about them.
Guarded 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 parked past its widget’s destruction raises
RuntimeErrorfrom the emit. A run must not fail because nobody is listening to it any more.spacr/measure.py:12426
- 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:458