Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
2ee635a
Feat(datamodel): the format-agnostic Metadata and its vocabulary (#233)
claude Oct 6, 2026
9a6c41e
Feat(io): file-based metadata, the BIDS sidecar and per-format metada…
claude Oct 6, 2026
ffbe850
Docs(metadata): the format author's guide and the API pages (#233)
claude Oct 6, 2026
90c82a4
Feat(metadata): export the vocabulary groups (#233)
claude Oct 6, 2026
3c4238e
Refactor(metadata): build the repr of Metadata with bagof's field pol…
claude Oct 6, 2026
510a2dd
Refactor(metadata): no input or output on Metadata (#233)
claude Oct 6, 2026
8e254fb
Refactor(metadata): the conversion and the record checks are module f…
claude Oct 6, 2026
de4421b
Feat(metadata): directions in a CoordinateSystem, mapped by a Transfo…
claude Oct 6, 2026
3253d81
Fix(metadata): round the scaled values only into an integer type (#233)
claude Oct 6, 2026
2f9493d
Refactor(io): the metadata parser only reads; the HDF5 one is an Hdf5…
claude Oct 6, 2026
30fa2dc
Refactor(metadata): import at the top, except across a cycle (#233)
claude Oct 6, 2026
170af7d
Style(metadata): public code first, then public helpers, then private…
claude Oct 6, 2026
41ceecc
Feat(metadata): operation objects drive the propagation to derived im…
claude Oct 6, 2026
8c3e387
Docs(metadata): the operation objects in the format guide and the mem…
claude Oct 6, 2026
2b6e1cb
Style(metadata): the class of a format hides `format` from its repr (…
claude Oct 6, 2026
0f25da0
Merge the stack base (origin/main with #311) into the metadata system
claude Oct 6, 2026
f1c1c56
Refactor(metadata): drop the `from_bytes` override of the metadata pa…
claude Oct 6, 2026
2390b07
Fix(metadata): `Resampled.geometry` is optional (#233)
claude Oct 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/api/datamodel/metadata-formats.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Metadata for format authors

The public names of [`brainhops.datamodel.metadata`](metadata.md) are
those a user of the library needs, and the vocabulary groups that a
format names in `supports=`. The names below are for whoever adds
the metadata of a file format; the
[format author's guide](../../dev/metadata-formats.md) explains how they
fit together. They are imported from the private modules that define
them.

# ::: brainhops.datamodel.metadata._vocabulary
# ::: brainhops.datamodel.metadata._operations
# ::: brainhops.datamodel.metadata._field
# ::: brainhops.datamodel.metadata._report
# ::: brainhops.datamodel.metadata._dtype
# ::: brainhops.datamodel.metadata._sentinel
# ::: brainhops.io.base._metadata_parser
1 change: 1 addition & 0 deletions docs/api/datamodel/metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# ::: brainhops.datamodel.metadata
1 change: 1 addition & 0 deletions docs/api/io/metadata/base.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# ::: brainhops.io.metadata._base
1 change: 1 addition & 0 deletions docs/api/io/metadata/bids.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# ::: brainhops.io.metadata.bids
1 change: 1 addition & 0 deletions docs/api/io/metadata/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# ::: brainhops.io.metadata
2 changes: 2 additions & 0 deletions docs/api/io/transformations/itk-h5.md
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
# ::: brainhops.io.transformations.itk.h5

# ::: brainhops.io.transformations.itk._metadata
233 changes: 184 additions & 49 deletions docs/design/format-metadata.md

Large diffs are not rendered by default.

349 changes: 349 additions & 0 deletions docs/dev/metadata-formats.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ dependencies = [
# typed inverses declare; 0.1 raises on it. 0.3 registers a subclass
# with every polymorphic level above it and conjoins the constraints
# it inherits, which the axes and coordinate systems rely on.
"bagof-magic >= 0.3.dev0",
"bagof-magic >= 0.3.dev2",
"bagof-paths >= 0.2",
# Units are parsed by pint, imported on first use. 0.21.1 is the last
# release for Python 3.8; 0.24 is the first to support numpy 2.
Expand Down
2 changes: 2 additions & 0 deletions src/brainhops/datamodel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"enums",
"kinds",
"images",
"metadata",
"orientation",
"systems",
"transformations",
Expand All @@ -39,6 +40,7 @@
enums,
images,
kinds,
metadata,
orientation,
systems,
transformations,
Expand Down
100 changes: 100 additions & 0 deletions src/brainhops/datamodel/metadata/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""
Non-spatial metadata, shared across file formats.

Every file format keeps descriptive metadata, such as a description, a
repetition time, a slice timing or a provenance, under its own names
and types. This package gives that metadata one representation, in a
class hierarchy that mirrors the hierarchy of the images (`Image`,
`FileBasedImage`, `NiftiImage`):

- [`Metadata`][brainhops.datamodel.metadata.Metadata] holds the common
vocabulary, one field per concept, named after its BIDS key in snake
case and stored in BIDS units, plus `extra`, a free-form store of
string keys. In-memory images and transformations carry it, and
formats convert through it.
- [`FileBasedMetadata`][brainhops.io.metadata.FileBasedMetadata], which
lives in `brainhops.io.metadata` as `FileBasedImage` lives in
`brainhops.io.images`, is the base of the metadata of a file format,
which reads its fields from the raw record of the format (a `nibabel`
header, the attributes of a Zarr array, ...) and writes them back.
- Each format has its own `<Fmt>Metadata` class, next to its parser
under `brainhops.io`. `FileBasedMetadata.load(path)` reads the
metadata of a file without its data, and `brainhops.io.metadata.bids`
reads and writes BIDS sidecars. The data model does no input or
output.

A vocabulary field holds a value, `None` when the value is unknown, or
[`UNSUPPORTED`][brainhops.datamodel.metadata.UNSUPPORTED] when the format
has no slot for the field. Converting into a format, with
`metadata.to(NiftiMetadata)`, records what the format cannot hold in a
[`ConversionReport`][brainhops.datamodel.metadata.ConversionReport], and
the loss policy (`"ignore"`, `"warn"` or `"raise"`, see
[`metadata_loss_policy`][brainhops.datamodel.metadata.metadata_loss_policy])
decides what happens to the report. A
[`Scope`][brainhops.datamodel.metadata.Scope] says how each field
propagates to a derived image, through the
[`Operation`][brainhops.datamodel.metadata.Operation] an image operation
describes (an [`Indexed`][brainhops.datamodel.metadata.Indexed] image or
a [`Resampled`][brainhops.datamodel.metadata.Resampled] one).

The names exported here are those a user of the library needs, and the
vocabulary groups (`ProvenanceVocabulary`, `MRIVocabulary`, ..., and
their base `Vocabulary`), which a format names in its `supports=`
declaration. What else a format author needs (the field annotations,
the `metadata` field of images, the loss helpers) is imported from the
private modules of this package, which the format author's guide lists
(`docs/dev/metadata-formats.md`). The user guide is
`docs/start/metadata.md`.
"""

__all__ = [
Comment thread
balbasty marked this conversation as resolved.
"Metadata",
"UNSUPPORTED",
"Scope",
"Operation",
"Indexed",
"Resampled",
"GeneratedBy",
"Channel",
"EncodingDirection",
"ConversionReport",
"MetadataLossWarning",
"MetadataLossError",
"metadata_loss_policy",
"Vocabulary",
"ProvenanceVocabulary",
"MRIVocabulary",
"DiffusionVocabulary",
"DisplayVocabulary",
"StorageVocabulary",
"MicroscopyVocabulary",
"TransformVocabulary",
]

from ._base import Metadata
from ._operations import Indexed, Operation, Resampled
from ._report import (
ConversionReport,
MetadataLossError,
MetadataLossWarning,
metadata_loss_policy,
)
from ._sentinel import UNSUPPORTED
from ._terms import Channel, EncodingDirection, GeneratedBy
from ._vocabulary import (
DiffusionVocabulary,
DisplayVocabulary,
MicroscopyVocabulary,
MRIVocabulary,
ProvenanceVocabulary,
Scope,
StorageVocabulary,
TransformVocabulary,
Vocabulary,
)

# The public names keep the `__module__` of the private module that
# defines them: rewriting it to this package's name would break
# `inspect.getsource`, IPython's `??` and doctest discovery, which look
# the source up through `__module__`. Pickles name the private module,
# and load as well.
Loading
Loading