Projections#

Overview#

The DCA includes 2D projections of 3D images along z (maximum, mean or sum intensity projections, or a single selected plane) for easier rendering and to provide additional input options for model training.

A projection is a derived image, not a new acquisition. It carries the same experimental metadata as its source and MUST satisfy every image-array requirement in this specification (array standard, dca block, required floor). This page adds only what is specific to projections.

The requirements below apply to a projection whose 3D source is also in the DCA. An image that was projected before it was deposited, and whose stack is not shared, is simply a 2D image: it MAY carry dca.projection to record the parameters when they are known (see Projections made before deposit), and nothing else on this page applies to it.

dca.projection records what was done, not how it was configured: the method and the source planes actually used for each output channel, as absolute indices.

This projection model is adapted from the project command in biahub and shaped during collaboration with the biahub team. The four methods, projecting each output channel separately (including keeping only some channels), half-open plane ranges with a step, and the record of how an in-focus plane was found all come from it (see Relation to biahub).

Store Layout#

  • A projection of a 3D image in the DCA MUST be written to its own store, a sibling of its source store. It MUST NOT be written into the source store (for example as an extra resolution level or a labels/ entry).

  • The projection store name SHOULD be the source store name with _proj inserted before its extension (.ome.zarr or .zarr), e.g. plate_01.ome.zarr → plate_01_proj.ome.zarr or plate_01.zarr → plate_01_proj.zarr. A projection whose every channel uses max MAY use _mip instead, as stores written before this version do.

  • The projection store MUST mirror the hierarchy of its source. For an HCS plate this means the same ome.plate rows, columns, wells and field-of-view paths, so that A/3/0 in the projection is the projection of A/3/0 in the source.

Dimensions and Scale#

  • The projected array MUST keep all five dimensions (t, c, z, y, x). The projected axis is kept with size 1 at every level; it is not dropped.

  • t, y and x MUST have the same size as level 0 of the source, and the t, y and x entries of the level-0 scale transform MUST equal the source’s.

  • Each output channel is projected from one source channel. A projection MAY keep only some of the source’s channels, in any order (see Per-channel form).

  • The z entry of the scale transform carries no meaning for a projection; consumers MUST NOT interpret it as a physical voxel size. Copying the source’s z scale and writing 1.0 are both acceptable. The projected planes are recorded in dca.projection (below), not in the coordinate transforms.

  • The projection MUST be computed from level 0 of the source, never from a downsampled level. Lower resolution levels of the projection are then produced by ordinary downsampling (mean filter, as for any raw image array).

  • When every channel uses max or slice, the projection MUST keep the source data type, since each output value is a source value. When any channel uses mean or sum, the projection MAY use a floating-point type (e.g. float32) for the whole array.

Because z has size 1, chunk and shard sizes follow the low-z edge cases of the array standard (e.g. chunks of 16, 1, 1, 128, 128).

Methods#

method

Output value per pixel

processing_variant step

max

Maximum over the selected planes

"maximum intensity projection"

mean

Mean over the selected planes

"mean intensity projection"

sum

Sum over the selected planes

"sum intensity projection"

slice

The value at one selected plane

"z slice"

Metadata#

Processing variant#

dca.image_data.processing_variant is an ordered list of processing steps (see Experimental Metadata Specification). A projection MUST list its source’s steps followed by the step of every method it uses (table above), so the full chain is readable on the projection itself; e.g. a maximum intensity projection of a deconvolved, registered stack lists ["deconvolved", "registered", "maximum intensity projection"], and a projection using max for some channels and slice for others lists both steps. An image that carries dca.projection MUST list exactly the projection steps of the methods it uses. An image MAY list a projection step without carrying dca.projection only when it was projected before deposit and its 3D source is not in the DCA (see Projections made before deposit). A projection of an unprocessed stack lists only its projection steps, since "raw" means no processing at all. A projection whose 3D source is in the DCA MUST copy the source’s image_data.processing_description unchanged, and has none when the source has none, so the description always matches the source; the projection’s own parameters belong in dca.projection. An image projected before deposit has no source to copy from, and describes its own processing (see Projections made before deposit).

Projection block#

Each image group of a projection whose 3D source is in the DCA MUST carry a dca.projection object in its zarr.json. It records the parameters a reader needs to interpret the array.

dca.projection describes the array, like dca.channels and dca.normalization_statistics. It is not part of the experimental-metadata record: it is defined by the DCA JSON Schema ($defs/Projection), lives in zarr.json only, and is not mirrored to the experimental-metadata Parquet. What the image is (REMBI Image Data “Type”) is dca.image_data.processing_variant above. The full derivation (pipeline, versions) belongs to catalog lineage, as described for processing_variant in Experimental Metadata Specification.

The block takes one of two forms: the uniform form, when every channel is projected the same way over the same planes, or the per-channel form. Exactly one of method (uniform) and channels (per-channel) MUST be present. Both forms share these fields:

Field

Type

Requirement

Description

source_store

string

MUST when the 3D source is in the DCA; omitted otherwise

The source store, as either its name (e.g. "plate_01.ome.zarr"), resolved as a sibling of this store, or its full URL or absolute path (e.g. "s3://bucket/datasets/plate_01.ome.zarr"). The name form SHOULD be preferred, since it stays valid when the stores are moved together. The image group inside the source has the same path as this one (see Store Layout).

z_dim_source

integer

MUST when any z_start / z_end is given; MAY otherwise

Size of z in level 0 of the source.

focus

object

SHOULD when planes were chosen around a detected in-focus plane

How the in-focus plane was found. See Focus.

Plane ranges#

Both forms record the planes used with the same fields, as absolute source z indices:

  • z_start: the first plane actually used (0-based).

  • z_end: the exclusive upper bound, with 0 <= z_start < z_end <= z_dim_source.

  • z_step: the spacing between the planes used (default 1). MUST NOT be given without z_start and z_end.

The planes used are exactly z_start, z_start + z_step, …, every one below z_end (Python’s range(z_start, z_end, z_step)). z_start MUST be the first plane actually used, not a bound that was clipped to the stack: with a step, clipping can shift which planes are used. For example, a window from 20 planes below an in-focus plane of 19 with z_step = 2 starts at plane -1, so the planes inside the stack are 1, 3, 5, …, and z_start is 1, not 0.

With a step, every z_end from the last plane used + 1 up to the last plane used + z_step selects the same planes. z_end SHOULD be the last plane used + 1, so that each set of planes is written one way; e.g. planes 1, 3, …, 19 are z_start = 1, z_end = 20, z_step = 2.

z_start and z_end MUST be given together. For slice they MUST be given, with z_end - z_start = 1. When they are absent the planes used are not known; readers MUST NOT assume a full-depth projection.

Uniform form#

Every channel is projected with the same method over the same planes, and the projection keeps every source channel, in the source’s order.

Field

Type

Requirement

Description

method

string

MUST

One of the Methods.

z_start, z_end, z_step

integer

MAY; MUST for slice

The planes used (see Plane ranges).

z_fraction

number

MAY

Fraction of the source depth requested, in (0, 1], taken as a central slab. 1.0 is a full-depth projection. When it disagrees with z_start / z_end (through rounding), z_start and z_end are authoritative.

A projection over a central slab rather than the full depth is expressed through z_start and z_end, e.g. the middle 60% of a 50-plane stack is z_fraction = 0.6, z_start = 10, z_end = 40.

Per-channel form#

channels maps each projected output channel, by its dca.channels[].name, to how it was projected. Every key MUST be one of the image’s channel names. A channel that is not a key was not projected along z (for example a reconstruction or a virtual stain computed another way), and its processing_variant steps describe it. A source channel that is not projected is simply absent.

Field

Type

Requirement

Description

method

string

MUST

One of the Methods.

source_channel

string

MAY

Name of the source channel this output channel was projected from. Defaults to the output channel’s own name.

z_start, z_end

integer, or list of integers

MAY; MUST for slice

The planes used (see Plane ranges). When the planes differ between timepoints (e.g. a window that follows z drift), each is a list with one entry per level-0 timepoint; z_start[t] and z_end[t] are then the planes used at timepoint t. z_start and z_end MUST both be integers or both be lists of that length.

z_step

integer

MAY

See Plane ranges.

Focus#

When the planes of a channel were chosen around an in-focus plane detected on the data, focus SHOULD record how. It is provenance: the planes themselves are always the ones in z_start / z_end, and nothing else depends on this block.

Field

Type

Requirement

Description

channel

string

MUST

Source channel the focus was measured on. It need not be an output channel.

per

string

MUST

"fov": one in-focus plane for the whole field of view (e.g. the median over timepoints). "timepoint": one plane per timepoint.

z

integer or null

MUST when per is "fov"

The in-focus plane, or null when none was found.

z_per_t

list of integers or null

MUST when per is "timepoint"; MAY otherwise

The in-focus plane of each timepoint, one entry per level-0 timepoint; null for a timepoint where none was found.

parameters

object

MAY

Settings of the focus-finding method (e.g. a detection numerical aperture, illumination wavelength or crop), recorded as given by the tool.

When no in-focus plane was found, a tool typically falls back to another plane (e.g. the middle of the stack). That fallback SHOULD be recorded as null rather than as if it had been found, so a reader can tell a detected focus from a fallback; the planes the projection used, fallback included, are still the ones in z_start / z_end. A plane that is given MUST lie within the source stack (below z_dim_source when that is given).

Relation to biahub#

biahub’s project command uses the same four methods (max, mean, sum, slice) and projects each output channel separately, so its output fits the per-channel form. Its settings describe how to choose planes (absolute start / stop, or below / above planes around a fixed or detected in-focus plane); dca.projection records the planes that choice resolved to. For a project run:

project settings / output

dca.projection

channels.<name>.method

channels.<name>.method

channels.<name>.z (start / stop, or center with below / above; step)

channels.<name>.z_start / z_end / z_step: the planes the selection kept after dropping those outside the stack, with z_start the first plane kept (see Plane ranges); lists when the window follows a per-timepoint focus

focus.channel, focus.per

focus.channel, focus.per

focus.z, focus.z_per_t (recorded per position)

focus.z, focus.z_per_t, with null where no focus was found

focus-finding settings recorded beside them (na_det, lambda_ill, center_crop_xy)

focus.parameters

(not recorded)

source_store, z_dim_source

Projections made before deposit#

Some datasets are deposited as 2D images that were projected from stacks that are not shared. These are 2D datasets: the store-layout, pairing and source rules above do not apply, and dca.projection is not required.

  • processing_variant MAY list the projection step as part of the image’s history, with the detail in processing_description.

  • When the parameters are known, dca.projection MAY record them, without source_store: e.g. z_dim_source for the depth of the original stack and, per projected channel, the method and planes used. Channels that were not projected are left out of channels.

Whether a projection’s 3D source is in the DCA is decided when a dataset is validated as a whole: a projection store named after a 3D sibling (plate_01_proj.ome.zarr or plate_01_mip.ome.zarr next to plate_01.ome.zarr, or plate_01_proj.zarr next to plate_01.zarr; the two extensions need not match) is a projection of that store, and MUST then carry dca.projection with source_store.

Normalization Statistics#

A projection has a different intensity distribution from its source (a maximum shifts every percentile upward, a sum scales with the number of planes). dca.normalization_statistics of a projection MUST be computed from the projected array. It MUST NOT be copied from the source.

Examples#

A maximum intensity projection in the uniform form, plate_01_mip.ome.zarr/A/3/0/zarr.json (abridged):

{
  "zarr_format": 3,
  "node_type": "group",
  "attributes": {
    "ome": {
      "version": "0.5",
      "multiscales": [ "..." ]
    },
    "dca": {
      "version": "0.3",
      "url": "...",
      "channels": [ "..." ],
      "normalization_statistics": { "..." },
      "projection": {
        "source_store": "plate_01.ome.zarr",
        "method": "max",
        "z_fraction": 1.0,
        "z_start": 0,
        "z_end": 50,
        "z_dim_source": 50
      },
      "image_data": {
        "processing_variant": ["deconvolved", "maximum intensity projection"],
        "processing_description": "deconvolved with a measured PSF"
      },
      "study": { "..." },
      "biosample": { "..." },
      "specimen": { "..." },
      "study_component": { "..." },
      "provenance": { "..." }
    }
  }
}

A per-channel projection of a three-timepoint stack that keeps three of the source’s channels. nuclei is the mean over a window of 20 planes below to 40 planes above the in-focus plane found on a phase channel at each timepoint (planes 19, 18 and 20), clipped at the bottom of the stack. plate_01_proj.ome.zarr/A/3/0/zarr.json (dca only, abridged):

{
  "version": "0.3",
  "channels": [
    {"name": "membrane", "index": 0, "...": "..."},
    {"name": "nuclei", "index": 1, "...": "..."},
    {"name": "mCherry", "index": 2, "...": "..."}
  ],
  "projection": {
    "source_store": "plate_01.ome.zarr",
    "z_dim_source": 86,
    "channels": {
      "membrane": {"method": "max", "z_start": 0, "z_end": 86},
      "nuclei": {"method": "mean", "z_start": [0, 0, 0], "z_end": [59, 58, 60]},
      "mCherry": {"method": "slice", "z_start": 42, "z_end": 43}
    },
    "focus": {
      "channel": "Phase3D",
      "per": "timepoint",
      "z_per_t": [19, 18, 20],
      "parameters": {"na_det": 1.35, "lambda_ill": 0.5, "center_crop_xy": [800, 800]}
    }
  },
  "image_data": {
    "processing_variant": [
      "maximum intensity projection", "mean intensity projection", "z slice"
    ]
  }
}

Reading this example:

  • The 3D source is in the DCA, so source_store names it, and the store is plate_01_proj.ome.zarr next to plate_01.ome.zarr. _mip does not apply, because not every channel uses max. Without the shared source, the same block without source_store would describe a projection made before deposit.

  • The image has three timepoints, so nuclei’s per-timepoint z_start / z_end and focus.z_per_t each have three entries.

  • All three output channels are projected, so all three are keys. An output channel that was not a z projection, such as a reconstruction, would be left out of channels.

  • processing_variant lists one step per method used: max, mean and slice.

  • focus records that the nuclei window followed the in-focus planes 19, 18 and 20. It is provenance: the planes used are already in z_start / z_end, and removing focus would not change what the block says. Phase3D is a source channel that the projection does not keep.

An image projected before deposit, recording the known parameters for its two projected channels; its label-free channel, a reconstruction, is not a key (dca only, abridged):

{
  "channels": [
    {"name": "Phase2D", "index": 0, "...": "..."},
    {"name": "GFP", "index": 1, "...": "..."},
    {"name": "mCherry", "index": 2, "...": "..."}
  ],
  "projection": {
    "z_dim_source": 7,
    "channels": {
      "GFP": {"method": "max", "z_start": 0, "z_end": 7},
      "mCherry": {"method": "max", "z_start": 0, "z_end": 7}
    }
  },
  "image_data": {
    "processing_variant": ["maximum intensity projection"],
    "processing_description": "Maximum intensity projection of a 7-plane z-stack (2 um steps)"
  }
}

Migration from v0.2#

v0.2 did not specify projections. Projection stores written under v0.2 carry the uniform-form fields, with the same names, in a top-level mip attribute beside ome and dca. Upgrading such a store to v0.3 (which rewrites dca.version in every image group anyway) MUST move that object to dca.projection unchanged and remove the top-level mip. Readers MAY fall back to a top-level mip when reading stores that declare dca.version "0.2".