Centralised Utilities (aiod_utils)¶
Almost every part of AIoD needs to do the same handful of low-level things: open an image, split it up, encode the resulting masks, and preprocess the data on the way in. To ensure everything behaves the same no matter where it happens (i.e. the Napari plugin or the Nextflow pipeline), we have a centralised utilities package aiod_utils.
It holds this shared behaviour in one place, and is installed by the front-ends and by every model environment in Segment-Flow.
It can also be used standalone, if any of the features are useful in other projects. This page will outline the main capabilities of aiod_utils so you can decide if it's useful to you!
Four Modules
aiod_utils.io— loading images and describing their dimensionsaiod_utils.stacks— deciding how to split an image for parallelisationaiod_utils.rle— compactly storing the resulting masksaiod_utils.preprocess— modular, serialisable preprocessing steps
Do I need to know this?
As a general user, no...this all happens under the hood. This page is useful if you are adding to or developing AIoD, or find you want to use one of its capabilities in your own project!
"Universal" Input/Output¶
Bioimaging has an unfortunate number of file formats, and users should not have to care which one their data happens to be in. aiod_utils.io wraps BioIO to provide a single entry point for loading images.
This ensures that images load consistently in both the Napari plugin and the Nextflow pipeline.
Consistent Reader Selection¶
BioIO can often read a given file with more than one plugin, and its default choice is not always the best one (for example, a plain .tiff being handled by the OME-TIFF reader, or Bio-Formats being picked over a dedicated reader). We set our own preference order, e.g. ensuring that a .tiff file is read by bioio-tifffile not bioio-ome-tiff.
Unsupported Formats
While bioio-bioformats covers many file formats, it is a much heavier dependency, and so is an optional install:
For deployed AIoD solutions, this may not be within your control as a user. In that case, we recommend converting your data up front with e.g. bioformats2raw.
Describing Input Data¶
Metadata is frequently missing, wrong, or interpreted differently by different readers, and Segment-Flow needs to know image shapes before it starts splitting anything. aiod_utils.io.image_paths_to_csv writes out a simple CSV of paths and dimensions, which the pipeline treats as the source of truth. See creating the input CSV for the format, and step 3 of the command-line tutorial for how to produce one.
Dynamic Stacks¶
As covered in our concepts page, AIoD parallelises by splitting each image into substacks — tiles (2D) or sub-volumes (3D). aiod_utils.stacks defines how this is done. Other pipelines/projects can use it too, and it lets a front-end show you what would happen before you run anything (e.g. how many parallel jobs would be submitted).
Why 'substack'?
"Tile", "patch", "chunk", and "block" all already mean something specific in adjacent tools, and a "substack" is expected to be a larger sub-unit of the original volume than those.
Sizing to the Hardware¶
Given a memory budget (from the Nextflow profile), the data type, and the image shape, we calculate the largest substack that will fit (with some headroom, as the peak memory of a model is rather more than the size of its input).
The dimensions are scaled proportionally rather than to a cube. Scaling each dimension by the same factor keeps the substack shaped like the image, which uses the memory budget far better for the anisotropic data that is common in volume EM, where a naive cube would waste most of the budget on a dimension that is already small.
Automatic Splitting
Deriving the budget automatically from the resources actually available is still in development. Until then, splitting uses sensible defaults which you can override directly.
Requested vs. Sensible¶
The num_substacks parameter lets you request a specific number of splits per dimension, or auto (recommended). Requests are treated as a strong preference rather than an instruction: if the resulting substacks would be too small to be useful, or too large to fit on a GPU, we fall back to the automatic calculation.
Substack Overlap¶
Overlap is specified as a fraction per dimension rather than an absolute number of pixels. Note that the number of substacks is guaranteed, but the exact overlap may be adjusted slightly, as the two cannot always be satisfied simultaneously.
Tracking Where Substacks Came From¶
Substack indices are encoded into the output filenames (..._x0-512_y0-512_z0-32), so the pipeline's parallel jobs can run independently and still be stitched back together at the end.
This also works with downsampling: indices are converted with rounding that ensures regions never overlap incorrectly.
Customised Run Length Encoding Format¶
Run length encoding (RLE) is a lossless compression method that suits segmentation masks well, as they are mostly long runs of the same value.
Our implementation has a few advantages over a default RLE implementation:
- Encoding of both binary and instance masks
- Metadata stored with the encoding, so a mask file is self-describing (it records its own type, and can carry run information)
- Instance masks are encoded within their own bounding box, rather than against the full image, improving compression and encode/decode time
The last point is what makes this usable at scale. Models like Segment Anything can produce hundreds of instances per slice, most of which occupy a tiny fraction of the frame; encoding each one against the whole frame means the cost grows with the image size rather than the object size. Storing each instance as an offset plus a local encoding makes dense results much cheaper to encode.
Why not just save a TIFF?
A run length encoded mask is far smaller (typically ~100x-2000x) than the image it came from, which matters when results are being written by many parallel jobs, moved around a shared cache, and reloaded into a viewer.
Binary and Instance Masks¶
The two mask types are handled by the same interface, and are freely convertible in both directions:
- Binary — each element is a slice of a foreground/background mask, and is the natural output of semantic segmentation
- Instance — each element is a labelled object, allowing instances to overlap, which is necessary for models like SAM where the same pixel can belong to more than one mask
The mask type is inferred if not given, but we recommend being explicit!
Preprocessing¶
Preprocessing lives here rather than in the pipeline for the same reason as everything else on this page: the Napari plugin needs to offer the options, the pipeline needs to run them, and a saved config needs to mean the same thing to both.
Each step is a subclass of a common Preprocess class that declares its own name, parameters (with defaults, human-readable labels, and tooltips), and whether it changes the image shape. That one definition is used in several places:
- The Napari plugin builds its widgets automatically from the declared parameters, so a new step needs no UI code
- Steps serialise to and from the
preprocessblock of a Nextflow params file, and are validated before a run starts rather than failing inside a job - Steps that change the image shape can report their output shape without running, so substack sizes and job counts can be calculated before the run
- Parameters are rendered into a canonical string, used in filenames and hashes so that differently-preprocessed versions of the same data never collide in the AIoD cache
Currently available steps are downsampling (with a choice of aggregation), CLAHE, and rank filtering (mean/median over a configurable neighbourhood).
The filter's neighbourhood is given as a family — round or square — rather than a concrete shape, and the matching 2D or 3D structuring element (disk/ball, square/cube) is chosen from the image itself when the step runs. This means one saved preprocessing set works unchanged on both 2D and 3D data. The concrete shape names are still accepted and mapped to their family, so older configs keep working.
Preprocessing Sets¶
Preprocessing is specified as one or more sets of steps, with the pipeline running over each set in turn. This is useful for e.g. applying different CLAHE parameters to get better performance in different regions of an image, then combining the results.
Each set is run through Segment-Flow separately, so one set of input parameters can run a model over several differently-preprocessed versions of the data.
Extending Preprocessing Functionality¶
Adding a new step is intentionally low-effort: it just needs a new subclass. See expanding preprocessing for details.