spacr.mask_io¶
Cellpose mask I/O — write + read masks as EITHER TIFF or NumPy.
Introduces two helpers to replace scattered np.save/np.load
calls across spacr.object and spacr.io:
save_mask()— writes a single 2-D uint16 mask. Emits.tifby default (smaller on disk + openable in Fiji / napari) but.npyis still supported viafmt="npy"for users who prefer numpy round-trips.load_mask()— reads a mask regardless of on-disk format. Given a path with no suffix, or a stem, it probes.tif,.tiffand.npyin that order and returns the first hit.
Both helpers preserve uint16 dtype (spaCR’s mask convention) and
return arrays with the same shape (H, W). TIFF is written with
LZW compression via tifffile.
Migration path for existing scripts:
# BEFORE np.save(“foo_mask.npy”, mask.astype(np.uint16)) m = np.load(“foo_mask.npy”)
# AFTER save_mask(“foo_mask”, mask) # writes foo_mask.tif m = load_mask(“foo_mask”) # finds foo_mask.tif or foo_mask.npy
The env var SPACR_MASK_FORMAT overrides the default across
the whole process: set to npy to keep the old behaviour.
Masks as ROIs: QuPath GeoJSON, ImageJ RoiSet and COCO¶
The second half of this module moves label masks to and from the formats
other tools curate and train on: masks_to_geojson() and
geojson_to_masks() for QuPath, masks_to_roiset() and
roiset_to_masks() for Fiji’s ROI Manager, masks_to_coco() and
coco_to_masks() for COCO-style training sets, and
export_rois() / import_rois() choosing among them by file name.
Every object is one feature, ROI or annotation, carrying its object type
(cell, nucleus …), its object id and its class; the class defaults to
the object type. Each object is traced on its own, so objects that touch stay
separate. Outlines follow PIXEL EDGES, not pixel centres: a pixel (row,
col) is the square from x = col to col + 1 and y = row to
row + 1, the convention QuPath and ImageJ both use. A pixel is inside a
polygon when its centre is, so a traced outline rasterised back gives the
object exactly: no tolerance, holes and several-part objects included. A hole
is an interior ring (GeoJSON), a path of a composite ROI (ImageJ) or, since a
COCO polygon cannot have one, an RLE segmentation (COCO).
roifile is needed only for ImageJ files and is imported when one is read
or written. COCO RLE is encoded and decoded here, so pycocotools is never
required; the files it writes are the ones pycocotools reads.
Functions¶
|
The file names of the images a COCO dataset annotates. |
|
Rasterise the annotations of one image of a COCO dataset. |
|
Write label masks as QuPath GeoJSON, an ImageJ RoiSet or COCO JSON. |
|
Rasterise a (QuPath) GeoJSON document into label masks. |
|
Read QuPath GeoJSON, an ImageJ RoiSet or COCO JSON into label masks. |
|
Read a Cellpose mask regardless of on-disk format. |
|
Label masks as a COCO instance-segmentation dataset. |
|
Label masks as a QuPath GeoJSON |
|
Write label masks as an ImageJ/Fiji |
|
The polygons that outline one object of a label mask, exactly. |
|
Decode a COCO RLE, compressed (text) or uncompressed (list of runs). |
|
COCO run-length encoding of a binary mask, compressed as COCO writes it. |
|
Which ROI format a file is: |
|
The file suffix a ROI format is written with. |
|
Rasterise an ImageJ |
|
Write a Cellpose mask to disk. |
Module Contents¶
- spacr.mask_io.coco_image_names(data: Any) List[str][source]¶
The file names of the images a COCO dataset annotates.
- Parameters:
data – the parsed dataset, or a path to its
.jsonfile.- Returns:
images[].file_name, in file order.
- spacr.mask_io.coco_to_masks(data: Any, *, file_name: str | None = None, shape: Tuple[int, int] | None = None, object_type: str | None = None, with_classes: bool = False)[source]¶
Rasterise the annotations of one image of a COCO dataset.
Polygons are filled by pixel centre and the parts of one annotation united, as
pycocotoolsunites them; RLE (compressed or not) is decoded exactly. The object type isobject_type, the annotation’sobject_type, or its category name; the id isobject_idor the next free one; the class is the category name.- Parameters:
data – the parsed dataset, or a path to its
.jsonfile.file_name – which image, for a dataset of several.
shape –
(rows, columns); default the image entry’s height and width.object_type – put every annotation in this one object type.
with_classes – also return each object’s class.
- Returns:
{object type: uint16 labels}, or(masks, {object type: {id: class}})withwith_classes.- Raises:
ValueError – the image is missing or an RLE does not fit it.
- spacr.mask_io.export_rois(masks: Any, path: PathLike, fmt: str | None = None, *, classes: Any = None, object_type: str | None = None, file_name: str | None = None, rle: bool = False) pathlib.Path[source]¶
Write label masks as QuPath GeoJSON, an ImageJ RoiSet or COCO JSON.
- Parameters:
masks – a 2-D label mask, or
{object type: mask}.path – the file to write; its suffix picks the format unless
fmtdoes (.geojson,.zip,.jsonfor COCO).fmt –
"geojson","imagej"or"coco".classes –
{id: class}or{object type: {id: class}}.object_type – the type of a single mask (default
"object").file_name – the image’s name, recorded in GeoJSON ids and COCO
images; default the output file’s stem.rle – COCO only: every segmentation as RLE.
- Returns:
the path written.
- Raises:
ImportError – ImageJ output without
roifileinstalled.ValueError – an unknown format or an invalid mask.
- spacr.mask_io.geojson_to_masks(data: Any, shape: Tuple[int, int], *, object_type: str | None = None, with_classes: bool = False)[source]¶
Rasterise a (QuPath) GeoJSON document into label masks.
A pixel belongs to an object when its centre lies inside the polygon and outside its holes; an outline written by
masks_to_geojson()therefore comes back pixel for pixel. The object type is, in order,object_type, the feature’sobject_typeproperty, the type in a"<type> <id>"name, or its classification – so a QuPath project that was never in spaCR gives one mask per class. The id is theobject_idproperty or measurement, or the id in the name; objects without one are numbered after the rest.- Parameters:
data – the parsed document, or a path to a
.geojsonfile.shape – the image’s
(rows, columns).object_type – put every feature in this one object type.
with_classes – also return each object’s class.
- Returns:
{object type: uint16 labels}, or(masks, {object type: {id: class}})withwith_classes.- Raises:
ValueError – the document is not GeoJSON.
- spacr.mask_io.import_rois(path: PathLike, shape: Tuple[int, int] | None = None, fmt: str | None = None, *, file_name: str | None = None, object_type: str | None = None, with_classes: bool = False)[source]¶
Read QuPath GeoJSON, an ImageJ RoiSet or COCO JSON into label masks.
- Parameters:
path – the file.
shape – the image’s
(rows, columns); required for GeoJSON and ImageJ, optional for COCO, which records it.fmt –
"geojson","imagej"or"coco"; default from the file (roi_format()).file_name – COCO only: which image of a dataset of several.
object_type – put every object in this one object type.
with_classes – also return each object’s class.
- Returns:
{object type: uint16 labels}, or(masks, {object type: {id: class}})withwith_classes.- Raises:
ValueError – no
shapewhere one is needed, or an unreadable file.ImportError – an ImageJ file without
roifileinstalled.
- spacr.mask_io.load_mask(path: PathLike) numpy.ndarray[source]¶
Read a Cellpose mask regardless of on-disk format.
- Parameters:
path – mask file or extensionless stem to resolve and load.
Accepts a full path (
foo.tif/foo.npy) OR a stem (foo) — in the stem case, tif → tiff → npy is tried and the first extant file is loaded.- Returns:
uint16 2-D array. Shape unchanged.
- Raises:
FileNotFoundError – when no matching file exists.
ValueError – stored labels cannot be represented exactly as uint16.
- spacr.mask_io.masks_to_coco(masks: Any, *, file_name: str = 'image', classes: Any = None, object_type: str | None = None, rle: bool = False, dataset: Dict[str, Any] | None = None) Dict[str, Any][source]¶
Label masks as a COCO instance-segmentation dataset.
Each object is one annotation with
segmentation,area(pixels),bbox([x, y, width, height]in pixel corners),iscrowd0 and spaCR’sobject_typeandobject_id. Categories are the(object type, class)pairs:nameis the class andsupercategorythe object type. Segmentations are polygons – one flat[x1, y1, x2, y2, ...]list per part – except for an object with a hole, which a COCO polygon cannot express and which is written as RLE;rle=Truewrites every object as compressed RLE, which is exact whoever reads it.- Parameters:
masks – a 2-D label mask, or
{object type: mask}.file_name – the image’s file name, as
images[].file_name.classes –
{id: class}or{object type: {id: class}}; an object without one is classified as its object type.object_type – the type of a single mask (default
"object").rle – write every segmentation as RLE.
dataset – an existing COCO dict to add this image to, as a queue export does;
Nonestarts a new one.
- Returns:
the dataset (
datasetitself when given).- Raises:
ValueError – an invalid mask.
- spacr.mask_io.masks_to_geojson(masks: Any, *, classes: Any = None, object_type: str | None = None, object_kind: str = 'annotation', image_name: str = '') Dict[str, Any][source]¶
Label masks as a QuPath GeoJSON
FeatureCollection.One feature per object: a
Polygon, or aMultiPolygonfor an object in several parts, with holes as interior rings and coordinates in pixels (pixel corners,xright andydown). The properties are QuPath’s own –objectType,classification(nameandcolor),nameandisLocked– plusobject_typeandobject_id, andmeasurements.object_idso the id survives a round trip through QuPath, which keeps measurements and names. The featureidis a UUID derived from the image name, the object type and the id, so exporting the same mask twice writes the same file.- Parameters:
masks – a 2-D label mask, or
{object type: mask}.classes – the class of each object:
{id: class}for a single mask or{object type: {id: class}}; an object without one is classified as its object type.object_type – the type of a single mask (default
"object").object_kind –
"annotation"or"detection", the QuPath object each feature becomes.image_name – the image these masks belong to, used to make the feature ids unique across images.
- Returns:
the
FeatureCollectionas a JSON-ready dict.- Raises:
ValueError – an unknown
object_kindor an invalid mask.
- spacr.mask_io.masks_to_roiset(masks: Any, path: PathLike, *, classes: Any = None, object_type: str | None = None) pathlib.Path[source]¶
Write label masks as an ImageJ/Fiji
RoiSet.zip.An object in one piece without holes is a traced polygon ROI, the kind Fiji’s wand makes; any other object is a composite ROI whose paths are all of its rings. Each ROI is named
"<type>-<id>"(unique, and what the ROI Manager lists) and carriesobject_type,object_idandclassas ROI properties.- Parameters:
masks – a 2-D label mask, or
{object type: mask}.path – the
.zipto write; replaced if it exists.classes –
{id: class}or{object type: {id: class}}; an object without one is classified as its object type.object_type – the type of a single mask (default
"object").
- Returns:
the path written.
- Raises:
ImportError –
roifileis not installed.ValueError – an invalid mask.
- spacr.mask_io.object_polygons(mask: numpy.ndarray, label: int) List[List[numpy.ndarray]][source]¶
The polygons that outline one object of a label mask, exactly.
- Parameters:
mask – 2-D label mask.
label – the object id to outline.
- Returns:
one
[exterior, hole, hole, ...]list per 4-connected part of the object; each ring is a closed(n, 2)integer array ofx, ypixel-corner coordinates. Empty when the id is absent.
- spacr.mask_io.rle_decode(rle: Mapping[str, Any]) numpy.ndarray[source]¶
Decode a COCO RLE, compressed (text) or uncompressed (list of runs).
- Parameters:
rle –
{"size": [rows, columns], "counts": ...}.- Returns:
the 2-D boolean mask.
- Raises:
ValueError – the runs do not add up to the size.
- spacr.mask_io.rle_encode(binary: numpy.ndarray) Dict[str, Any][source]¶
COCO run-length encoding of a binary mask, compressed as COCO writes it.
- Parameters:
binary – 2-D boolean mask.
- Returns:
{"size": [rows, columns], "counts": str}, whatpycocotools.mask.encodereturns withcountsas text.
- spacr.mask_io.roi_format(path: PathLike, fmt: str | None = None) str[source]¶
Which ROI format a file is:
"geojson","imagej"or"coco".- Parameters:
path – the file;
.geojsonis GeoJSON,.zipand.roiImageJ, and an existing.jsonis read to tell COCO from GeoJSON (a new one is COCO).fmt – the answer, when the caller already knows it;
"qupath"and"roiset"are accepted too.
- Returns:
the format.
- Raises:
ValueError – an unknown
fmtor a file of no known format.
- spacr.mask_io.roi_suffix(fmt: str) str[source]¶
The file suffix a ROI format is written with.
- Parameters:
fmt –
"geojson","imagej"or"coco".- Returns:
".geojson",".zip"or".json".
- spacr.mask_io.roiset_to_masks(path: PathLike, shape: Tuple[int, int], *, object_type: str | None = None, with_classes: bool = False)[source]¶
Rasterise an ImageJ
RoiSet.zip(or one.roi) into label masks.Polygon, freehand, traced, rectangle and oval ROIs are read, and a composite ROI by the even-odd rule over its paths, as ImageJ fills it; lines and points have no area and are skipped. A pixel belongs to a ROI when its centre is inside, ImageJ’s own rule, so a set written by
masks_to_roiset()comes back pixel for pixel. Type, id and class come from the ROI properties, else from a"<type>-<id>"name, else the ROI is an"object"numbered after the rest.- Parameters:
path – the
.zipor.roifile.shape – the image’s
(rows, columns).object_type – put every ROI in this one object type.
with_classes – also return each object’s class.
- Returns:
{object type: uint16 labels}, or(masks, {object type: {id: class}})withwith_classes.- Raises:
ImportError –
roifileis not installed.
- spacr.mask_io.save_mask(path: PathLike, mask: numpy.ndarray, fmt: str = None) pathlib.Path[source]¶
Write a Cellpose mask to disk.
- Parameters:
path – destination — extension is optional.
foo,foo.tif,foo.npyall work.mask – 2-D integer mask; will be cast to uint16.
fmt – force a format (
"tif","tiff", or"npy"). Defaults toDEFAULT_FORMAT(env-overridable).
- Returns:
the resolved on-disk path.
- Raises:
ValueError – labels are nonfinite, negative, fractional or will not fit in uint16, or
fmtnames a format this module cannot write.
An id above 65535 is refused rather than cast. The cast wraps, and the first value it wraps to is 0 — background — so object 65536 would come back from disk fused with everything that was never segmented at all, and nothing downstream could tell that from a mask with one fewer object.