Storage backends & formats#
KonfAI reads and writes datasets through pluggable backends in
konfai/utils/dataset.py (inner classes of Dataset). You rarely name a backend
directly — you pick a format token in a dataset_filenames spec
(./Dataset:a:mha), and the token is dispatched to a backend. See
Datasets and groups for the grouped case/group layout these backends
serve; the DICOM and OME-Zarr reader APIs are detailed below.
Backends#
Backend |
Format token(s) |
Kind |
Optional extra |
|---|---|---|---|
|
|
Directory of per-case image files (default) |
|
|
|
Single monolithic HDF5 file |
|
|
|
OME-Zarr pyramid directory |
|
|
|
DICOM series directory |
|
Tip
pip install "konfai[imaging]" installs all four backends at once
(SimpleITK, h5py, pydicom, zarr, ngff-zarr).
The extras column above is the summary; Installation is the canonical home for the optional-extras table.
The SitkFile default backend also handles sidecars#
Beyond images, the SITK backend reads/writes several sidecar payloads by
extension: .itk.txt (SimpleITK transforms), .fcsv (Slicer landmarks), .xml
(attribute trees), .vtk (VTK PolyData points), .npy (raw NumPy, memory-mapped
on the slice path). It supports true partial reads (reading only the
requested spatial window) for streaming.
API details: DICOM series#
A DICOM acquisition is not a folder of independent images: a CT or MRI series
is a set of .dcm files that together define one 3-D volume. The reader
(konfai/utils/dicom.py) handles series discovery, slice ordering, geometry
extraction, and CT intensity rescale.
read_dicom_series#
from konfai.utils.dicom import read_dicom_series
volume, origin, spacing, direction = read_dicom_series("path/to/series")
konfai.utils.dicom.read_dicom_series(directory, *, series_uid=None, apply_rescale=True)
returns a four-tuple:
Returned |
Shape |
Meaning |
|---|---|---|
|
|
channel-first voxel data |
|
|
physical position of the first voxel, mm |
|
|
voxel size |
|
|
row-major 3×3 direction-cosine matrix, flattened |
The origin / spacing / direction triple maps directly onto an Attribute
(Origin, Spacing, Direction; see Datasets and groups), so a
DICOM series travels through the pipeline under the same geometry contract as
any other format.
Key behaviors:
Slice ordering uses
ImagePositionPatientprojected onto the slice normal derived fromImageOrientationPatient, not filename orInstanceNumber.apply_rescale=True(the default) appliesRescaleSlope/RescaleInterceptto convert stored values to Hounsfield Units for CT. Set it toFalseto keep raw integers (for example for label maps).Missing geometry tags, inconsistent slice shapes, and unreadable pixel data all raise
DatasetManagerErrorwith an actionable message.
Multi-series folders: series_uid#
A single folder can hold more than one series (for example a T1 and a T2 from the
same session). read_dicom_series resolves this as follows:
one series present → it is used automatically;
multiple series with
series_uid=None→DatasetManagerErrorlisting the availableSeriesInstanceUIDs;pass
series_uid="1.2.840…"to select one explicitly.
Use konfai.utils.dicom.discover_series(directory) to list the available UIDs
first:
from konfai.utils.dicom import discover_series, read_dicom_series
series = discover_series("path/to/study") # {uid: [Path, ...]}
uid = next(iter(series))
volume, origin, spacing, direction = read_dicom_series("path/to/study", series_uid=uid)
write_dicom_series writes one uncompressed scalar DICOM series. Integer data
round-trips exactly; floating-point data is stored as signed 16-bit pixels with
RescaleSlope and RescaleIntercept.
API details: OME-Zarr#
Unlike a NIfTI file, an OME-Zarr array is already lazy: it is stored as
chunked Zarr, so reading a sub-region only fetches the chunks it touches. This
maps naturally onto KonfAI’s patch-based loading — the reader
(konfai/utils/ome_zarr.py) never materializes the whole volume.
Multiscale levels#
OME-NGFF stores a multiscale pyramid: the same image at several resolutions,
level 0 being full resolution and each higher level a downsampled copy. Each
level carries its own physical scale (spacing) and translation (origin) in the
.zattrs metadata, so geometry stays correct at every level.
read_ome_zarr_slice#
from konfai.utils.ome_zarr import read_ome_zarr_slice
patch, axes, scale, translation = read_ome_zarr_slice(
"image.zarr",
(slice(0, 64), slice(0, 256), slice(0, 256)), # (Z, Y, X)
level=0,
)
konfai.utils.ome_zarr.read_ome_zarr_slice(store_path, slices, *, level=0, channel=None, timepoint=0)
reads one spatial patch and returns:
Returned |
Meaning |
|---|---|
|
channel-first |
|
axis names from the OME-NGFF metadata (e.g. |
|
voxel spacing for the selected level |
|
origin translation for the selected level |
The reader inspects the stored axes to place the spatial slices on the right
dimensions and to index optional T (time) and C (channel) axes, so the same
call works for ZYX, CZYX, and TCZYX arrays. Local .zarr directories and
remote stores (s3://, gs://) are both supported.
Choosing a resolution: select_level#
konfai.utils.ome_zarr.select_level(zattrs, target_spacing_mm=None) picks the
pyramid level whose voxel spacing is closest to a target:
import zarr
from konfai.utils.ome_zarr import select_level, read_ome_zarr_slice
zattrs = dict(zarr.open("image.zarr", mode="r").attrs)
level = select_level(zattrs, target_spacing_mm=1.0) # nearest level to 1 mm
patch, *_ = read_ome_zarr_slice("image.zarr", (slice(0, 64),) * 3, level=level)
With target_spacing_mm=None it returns level 0 (full resolution). Otherwise it
compares the median spatial scale of each level against the target and returns the
closest — letting you trade resolution for field of view at a fixed patch size.
Use konfai.utils.ome_zarr.get_ome_zarr_info(store_path, level=0) for a metadata
summary (axes, shape, chunks, dtype, scale, translation, n_levels)
without reading any pixels.
write_ome_zarr writes a single-level OME-NGFF store with channel/spatial axes,
chunking, scale, translation, and the original KonfAI attributes.
Why ngff-zarr over ome-zarr#
Two Python libraries read OME-NGFF: the OME consortium’s ome-zarr and
ngff-zarr. KonfAI’s omezarr extra depends on ngff-zarr (alongside raw
zarr) for one decisive reason:
ngff-zarrexposes per-scale physical coordinates — thescaleandtranslationof each pyramid level — as plain numeric arrays that convert directly to SimpleITK geometry. That is exactly the(Origin, Spacing, Direction)triple KonfAI stores in anAttribute, so OME-Zarr input lines up with every other format without a bespoke geometry adapter.
It also handles multiscale selection transparently and adds helpers tuned for 3-D
medical/bioimage workflows. When ngff-zarr is unavailable, the reader falls back
to parsing the .zattrs JSON with zarr alone.
Use as a KonfAI dataset#
Both formats use the normal grouped Dataset API:
from konfai.utils.dataset import Dataset
dicom_dataset = Dataset("DatasetDicom", "dicom")
ome_dataset = Dataset("DatasetOme", "omezarr") # aliases: ome-zarr, ome_zarr, zarr
dicom_dataset.write("CT", "CASE_001", volume, attributes)
patch, attributes = dicom_dataset.read_data_slice(
"CT", "CASE_001", (slice(None), slice(10, 20), slice(32, 96), slice(32, 96))
)
ome_dataset.write("CT", "CASE_001", volume, attributes)
names = ome_dataset.get_names("CT")
The layouts are <root>/<case>/<group>/*.dcm and
<root>/<case>/<group>.ome.zarr. get_infos reads only metadata, OME patch
reads touch only selected chunks, and DICOM patch reads decode only selected
slices. In workflow YAML use ./Dataset:dicom or ./Dataset:omezarr in
dataset_filenames.
Patching, streaming & reassembly#
The data layer (konfai/data/patching.py, konfai/data/data_manager.py) never
loads a whole volume when it can avoid it:
DatasetPatch(thePatch:config block) —patch_size(default[128,128,128]),overlap(None→ auto-tiling),pad_value(None→ pad withdata.min()),extend_slice(2.5-D context, only whenpatch_size[0]==1).ModelPatch— patching applied inside a model graph, with apatch_combineblender (MeanorCosinus) for overlap reassembly.Streaming reads only one patch’s window from disk (
read_data_slice). It is opt-in and conservative: only the base (non-augmented) patch, and only when every trailing transform is on a stream-safe allow-list (TensorCast, andNormalize/Standardize/Clipwithout masks, using precomputed statistics). Anything else falls back to a full in-RAM load.Accumulatorreassembles patches with overlap blending, correcting border voxels covered by fewer patches. Patch read order must match write order — a load-bearing invariant.
Important
For PREDICTION / EVALUATION, all patches of a case stay on the same DDP rank (the whole volume is reassembled per rank). For TRAIN, shards are truncated to the shortest rank so DDP all-reduces stay balanced.
Honest caveats#
Warning
dcm≠dicom. The tokendcmreads a single file through SITK; only the literal tokendicomuses the DICOM-series backend. Easy to trip over.vtkis an ungated import —.vtkI/O raisesImportErrorifvtkisn’t installed, and there is no dedicated extra that declares it (konfai[vtk]installs it, but the sidecar path doesn’t advertise the requirement).Attributeonly round-trips scalars and 1-D arrays. The geometry sidecar stringifies values and reparses withnp.fromstringafter stripping the outer brackets, so anything multi-dimensional will not survive a read. Geometry is safe only becauseOrigin/Spacingare 1-D andDirectionis stored flattened. Store only flat scalars / 1-D arrays in anAttribute.
Next steps#
Datasets and groups — grouped dataset layout, selectors, patching
Transforms — the stream-safe transform allow-list