MibBigDataLabelsIndex

class core.MibBigDataLabelsIndex

Bases: core.@MibLabels.MibLabels

MIBBIGDATALABELSINDEX - read-only label overlay served from a pyramid the image does not share.

Subclass of core.MibLabels. Attaches a remote or local OME-Zarr label pyramid to an already-open BigData image and serves it slice by slice as the view is read - nothing is downloaded in bulk and obj.data stays empty.

Why a new class rather than a wider ``MibBigDataLabels``. Everything that forces a 63-material ceiling comes from the other branch of the hierarchy:

MibImage
+-- MibLabels63 -- MibBigDataLabels -- MibBigDataLabelsZarr2   (packed byte, 63 max, editable store)
+-- MibLabels    -- MibBigDataLabelsIndex                      (separate layers, 65535+, read-only)

MibLabels63 packs six bits of material, bit 7 of mask and bit 8 of selection into one byte, so an instance segmentation’s object ids cannot survive it - 255 arrives as material 63 with the mask and selection bits set. This branch has no packing, so ids pass through untouched and the “no suitable class” problem dissolves. Subclassing MibLabels63 instead would also fire every isa(..., 'core.MibLabels63') branch in the codebase wrongly, starting with MibImage.getData:66.

The one thing that must not be got wrong. The label pyramid does not start at the image’s resolution. jrc_mus-kidney’s nuc has 5 levels from 128 nm while the EM it segments has 12 from 8 nm, so nuc’s own level 0 is the EM’s s4. modelScaleFactors therefore holds each level’s scale in the image’s level-0 voxels ([16 32 64 128 256] here), registered by io.loaders.OmeZarrMetadataUtils.registerLevelScales at open time. Numbering the levels from the store’s own level 0 - which is what MibBigDataLabelsZarr2.openStore does, correctly, for a store that shares the image’s resolution - would put every label at one-sixteenth of its true size with nothing to warn about it.

Serving a fine view from a coarse level is then not a resize: the coarse block does not begin where the view begins. getData() gathers one source voxel per screen pixel through OmeZarrMetadataUtils.screenGridForRange / levelReadWindow; see those for the arithmetic and why a plain imresize produces plausible, misplaced labels.

Read-only, and deliberately so. A remote label store belongs to another tool, its values are that tool’s labelling scheme, and MIB has no business writing into it. setData() and setDataFast() are blocked; to segment on the dataset, create a new model, which MIB writes to a store of its own.

Read-only is not the same as un-exportable, though. “Save model as…” writes one chosen pyramid level to any of MIB’s model formats, streamed a slice at a time by io.savers.MibImageSliceProvider through core.MibLabels.save, so the volume is never gathered in memory. The level has to be chosen because these labels have no full-resolution level to default to - that is the whole reason this class exists - and writing the one the image is showing would mean upsampling the entire volume to a resolution the labels never had.

Store access follows ``MibBigDataLabelsZarr2``, not ``MibVirtualImage``. Reads go through io.zarr.ChunkCache keyed on the level path plus the store version (io.zarr.ChunkCache.storeKey), so a store opened both as an image and as labels shares decoded chunks, nothing here writes, and a store replaced on disk is never served from the old one’s chunks. MibVirtualImage.getDataZarr would have been the other candidate, but its defaults assume levelImageSizes(1,:) is the full-resolution extent, which is untrue for an offset pyramid.

Constructor Summary
MibBigDataLabelsIndex(img, meta)

MIBBIGDATALABELSINDEX - Construct an empty read-only label container.

Syntax:
obj = core.MibBigDataLabelsIndex([], meta)

Same construction contract as core.MibBigDataLabels: pass [] for img and attach a store afterwards with openStore(). Until then obj.exists is false and reads return []. There is no createStore counterpart - a store MIB creates is always MIB’s own editable packed format, which core.MibBigDataLabels owns.

Input Arguments:
  • img (optional) - [empty] pass []; pixel data is never held in memory for this class.

  • meta (optional) - [dictionary] metadata dictionary used by the parent constructor chain to set dimensions. Default: empty MibImage info.

Property Summary
imageScaleFactors

[char] the store’s own declared C-order (e.g. ‘zyx’), from its multiscales.axes. A foreign store is read in whatever order it declared, so readLevel builds the bbox and permutes the result against this rather than assuming [y,x,z].

modelArrayMeta

{1 x nLevels} io.zarr.Array handles, one per level, finest first.

modelArrays

[string] zarr group root path or URL of the label pyramid.

modelAxisOrder

[nLevels x 3] per-level [yScale, xScale, zScale] in the IMAGE’s level-0 voxels - NOT relative to this store’s own level 0. See the class note: this is the whole point of the class and the one field a wrong value in is invisible. Written only by openStore, from registerLevelScales.

modelLevelNames

{1 x nLevels} full path or URL of each level array - the io.zarr.ChunkCache key, and the same one the image loaders use.

modelLevelPaths

{1 x nLevels} io.zarr.Array.info() results, cached beside modelArrays so shape/chunkShape are not re-queried from the engine on every tile read.

modelLevelSizes

{1 x nLevels} relative level paths within the group (‘s0’, ‘s1’, …).

modelScaleFactors

[nLevels x 3] per-level [y, x, z] voxel counts, as the store holds them.

modelStorePath
readOnlyWarningShown
renderPerObject

[nImageLevels x 3] the IMAGE pyramid’s own magnification axis, [y x z], level 0 being [1 1 1]. Needed because the overlay has to come back the size the image layer came back - which follows from the level the IMAGE is showing, not the one the labels are read from. getRGBimage composites the two with labeloverlay, where a one-row disagreement is an error.

Method Summary
countMaterials()

COUNTMATERIALS - Report the object count without reading the volume.

Syntax:
result = obj.countMaterials()

Override of core.MibLabels.countMaterials, which for maxMaterials >= 256 scans every voxel of every time point for the highest index. Here that is a remote volume - jrc_mus-liver-6’s er segmentation is 510 GiB - so the count comes from the single small pyramid level openStore() probed instead, and this method only hands it back. The inherited version would in fact return 0 rather than reading anything, since obj.data is empty, but it would do so by accident; the number it should report is the one already in hand.

Output Arguments:
  • result - [numeric] materialsCount, a lower bound on the object count when the store is an index map (see openStore), and 1 when the values are being collapsed to a single material

getData(layerType, orient, colChannel, options)

GETDATA - Read a displayed label slice from the attached pyramid.

Syntax:
dataset = obj.getData(layerType, orient, colChannel, options)

Override of core.MibImage.getData, and load-bearing: that method dispatches to getData63 only for a core.MibLabels63 and otherwise reads the in-memory obj.data, which for this class is permanently empty. Without this override the overlay is silently blank.

The read is three steps, and the middle one is the whole feature:

  1. Pick the level in the image’s scale space (pickLevel()). At magFactor 1 a pyramid starting at 128 nm has no level 1, so “nearest” returns its finest published level - which is the fallback, arrived at with no special case.

  2. Work out where the view’s pixels land on that level. OmeZarrMetadataUtils.screenGridForRange says where the image layer put each screen pixel in full-resolution space (and how many there are, which must match or labeloverlay errors); levelReadWindow turns that into one source voxel per pixel. Serving a fine view from a level 16x coarser is not a resize - resizing the covering block gives the wrong size AND a shifted origin. See those two methods for the arithmetic.

  3. Read and gather. One indexed read, no intermediate at full resolution.

Input Arguments:
  • layerType (optional) - [char] layer to read:

    • 'labels' - material indices, or a binary map when colChannel is set

    • 'mask' / 'selection' - correctly sized zeros; this class carries neither layer, and a caller asking for one wants an empty overlay, not an error

    • 'image' - treated as 'labels'; the container IS the labels

    • 'everything' - errors. It means “the packed byte with all three layers”, which is structurally unavailable outside the MibLabels63 family (getData3D.m:222 makes the same point)

    Default: 'labels'.

  • orient (optional) - [numeric] viewing orientation, 1 = XZ, 2 = YZ, 3 = YX. Default: 3.

  • colChannel (optional) - [numeric|empty] material index; when non-empty and reading labels, returns a binary uint8 map of that object - the store’s own ids, unaffected by renderPerObject, which is a display setting rather than a property of the data. [] returns all indices, collapsed to 1 when renderPerObject is false.

  • options (optional) - [struct] with fields:

    • .magFactor - [numeric] display magnification. Default: 1

    • .pyramidLevel - [numeric] explicit 1-based label level; overrides magFactor and returns that level’s own voxels with no display resampling, matching getData63’s meaning for the same field. This is the read contract io.savers.MibImageSliceProvider needs, so it is also how a level of these labels is exported to a file

    • .x / .y / .z - [1x2 numeric] screen ranges in full-resolution dataset coordinates. Default: the whole dataset on that axis

Output Arguments:
  • dataset - [ny, nx, nz] of class dataClass (uint8 for a single material or a mask/selection request), at display resolution and the same size the image layer returns for the same request. [] when no store is attached.

static imageReference(image)

IMAGEREFERENCE - Describe an open image in the absolute terms openStore needs.

Syntax:
reference = core.MibBigDataLabelsIndex.imageReference(dataset.image)

Shared by the two places that have to answer “can this label pyramid be placed on the open image”: MibModel.loadModel, which then attaches it, and controllers.SelectFromUrl.resolveLabelRoute, which only needs to know before Open is pressed. Keeping one implementation is what stops the panel promising a route the loader then refuses.

The level voxel sizes come from ``pyramid.levelScaleFactors``, not from pyramid.levelVoxelSizes, deliberately: levelScaleFactors is the table getDataZarr itself picks levels with, so registering against it guarantees the overlay’s idea of the image’s magnification axis is the one the image actually uses. Anisotropic voxels cancel in the division, so it holds for them too.

Input Arguments:
  • image - [core.MibImage] the dataset’s image layer; needs a pyramid with levelScaleFactors and a boundingBox

Output Arguments:
  • reference - [struct] with .ok / .reason, plus the three fields openStore() reads: .shapeYXZ, .voxelSizesXYZ (micrometres) and .outerBoxUm (edge-based, micrometres)

imageScaleForMagFactor(magFactor)

IMAGESCALEFORMAGFACTOR - Which image level the view is being served from.

Syntax:
imageScaleYXZ = obj.imageScaleForMagFactor(magFactor)

Mirrors getDataZarr:65-67 exactly, because the overlay has to be the size the image came back and that size follows from the image’s level, not the label’s. Kept as its own method so the mirroring is one place that can be compared against the original.

Input Arguments:
  • magFactor - [numeric] current display magnification

Output Arguments:
  • imageScaleYXZ - [1x3 numeric] that level’s [y x z] scale factors; [1 1 1] when no image pyramid was registered

openStore(storePath, imageReference)

OPENSTORE - Attach an existing label pyramid, registered against the open image.

Syntax:
obj.openStore(storePath, imageReference)

Opens every level of an OME-Zarr label group as an io.zarr.Array and places the pyramid inside the image’s scale space rather than its own. The store is never written to, and nothing is read here except metadata plus one small pyramid level - the finest that fits a fixed voxel budget - used to estimate the object count.

The registration is the point. A label pyramid published from a coarse level down - which is what a whole-volume inference segmentation is - has its own level 0 somewhere in the middle of the image’s magnification axis: jrc_mus-kidney’s nuc starts at 128 nm, which is the EM’s s4. modelScaleFactors is therefore [16 32 64 128 256] and not [1 2 4 8 16]. io.loaders.OmeZarrMetadataUtils.registerLevelScales derives that and refuses the pairing outright when the two pyramids do not cover the same volume, which is the only thing standing between a scale factor and labels placed at the origin at the wrong size.

The dimensions come from the image, not the store. height/width/ depth are the image’s full-resolution extent, because that is the coordinate system every caller asks in - options.x/y/z are dataset coordinates, and getData() maps them onto whichever level serves the request.

pixSize and boundingBox are deliberately not set here; they belong to the dataset and are applied by the caller through MibDataset.setPixSize, the same way every other model type receives them.

Input Arguments:
  • storePath - [char|string] path or URL of the OME-Zarr label group; v2 (.zattrs) and v3 (zarr.json) are both detected, since the level arrays are opened through io.zarr.Array, which reads either

  • imageReference - [struct] describing the image this attaches to, with fields:

    • .shapeYXZ - [1x3 numeric] the image’s full-resolution [y x z] voxel counts

    • .voxelSizesXYZ - [nImageLevels x 3 numeric] voxel size [x y z] per image level in micrometres, row 1 being full resolution; micrometres because the two stores may declare different units and an absolute one is the only one that cannot be misread

    • .outerBoxUm - [1x6 numeric] the image’s outer physical extent [xmin xmax ymin ymax zmin zmax] in micrometres, edge-based as OmeZarrMetadataUtils.outerBoundingBox returns it

Raises an error rather than half-attaching: an unreadable store, a group with no multiscales, or a pyramid that does not register all throw, because the caller decided this route was available before getting here and a silent fallback would put the labels somewhere plausible and wrong.

pickLevel(options)

PICKLEVEL - Choose which label level serves a read.

Syntax:
levelIndex = obj.pickLevel(options)

Nearest level on the scale axis, the same rule core.MibBigDataLabels.pickLevel and getDataZarr use - but over modelScaleFactors, which is in the image’s scale space, so “nearest to magFactor 1” is the finest level the labels HAVE rather than a level that does not exist. That is what makes the missing fine levels fall back to the finest published one, with no special case.

Input Arguments:
  • options - [struct] with optional .pyramidLevel (explicit 1-based level, wins outright) and .magFactor

Output Arguments:
  • levelIndex - [numeric] 1-based level, clamped to the pyramid

readLevel(levelIndex, Ylim, Xlim, Zlim, keepObjectIds)

READLEVEL - Read a [ny x nx x nz] block of one level, in MIB’s axis order.

Syntax:
block = obj.readLevel(levelIndex, Ylim, Xlim, Zlim)
block = obj.readLevel(levelIndex, Ylim, Xlim, Zlim, keepObjectIds)

The store’s own values, with no bit-unpacking to undo - a foreign label array holds plain indices. Reads are served through io.zarr.ChunkCache on whole decoded chunks, exactly as the image loaders do; safe without invalidation precisely because this class never writes.

The single-material collapse is applied here, after the cache, so the cache keeps holding raw chunks keyed by level path and stays shareable with the same store opened as an image.

keepObjectIds suppresses that collapse. renderPerObject is a display choice - “show this instance segmentation as one material” - and a caller naming a single object id is not displaying, it is asking about that object. Collapsing first makes the question unanswerable: every id has already become 1, so id 1 matches the union of every object in the volume and every other id matches nothing. That is one merged surface instead of 2165, and one merged volume out of “Save model as…” with a material index set.

Input Arguments:
  • levelIndex - [numeric] 1-based level

  • Ylim / Xlim / Zlim - [1x2 numeric] 1-based inclusive ranges in that level’s own voxels

  • keepObjectIds (optional) - [logical] return the store’s own ids even when renderPerObject is false. Default: false

Output Arguments:
  • block - [ny x nx x nz] of class dataClass

setData(dataset, layerType, orient, colChannel, options)

#ok<INUSD> SETDATA - Blocked: an imported label pyramid is read-only.

Syntax:
result = obj.setData(dataset, layerType, orient, colChannel, options)

Override of core.MibImage.setData. Never touches the store or any in-memory state, and returns false so a caller that checks gets an honest answer. The notice is shown once per session, on the first blocked attempt only: a single paint stroke fires setData on every mouse-move.

Output Arguments:
  • result - [logical] always false

setDataFast(dataset, z, colChannel, t)

#ok<INUSD> SETDATAFAST - Blocked: the in-place write path has no target here.

Override of core.MibImage.setDataFast. MibDataset’s fast paths gate on datasetType == 'Standard' and so never reach a BigData buffer, but the inherited version writes straight into obj.data - which is empty here, so a stray call would silently grow a full-resolution array in memory rather than fail. Blocked for that reason rather than for symmetry.