Writing a plugin¶
A format is a subclass of FormatPlugin with a reader and a
writer that translate between the file and an Image. MedVol does the rest: format
detection, policies, capability checks, reorientation, history and reports.
from pathlib import Path
import numpy as np
from mlarray import Meta
import medvol
from medvol.plugins.base import Capabilities, FormatPlugin
from medvol.report import Report
class NpyPlugin(FormatPlugin):
"""A bare NumPy array: no geometry, no metadata."""
name = "npy"
extensions = (".npy",)
capabilities = Capabilities(max_space_axes=3, min_space_axes=0, oblique=False, shear=False)
def sniff(self, path):
path = Path(path)
if not path.is_file():
return 0.0
with open(path, "rb") as file:
return 1.0 if file.read(6) == b"\x93NUMPY" else 0.0
def read(self, path, policy, **options):
array = np.load(path)
meta = Meta(axes=[f"axis_{n}" for n in range(array.ndim)], shape=array.shape,
source=[{"format": self.name, "files": [Path(path).name]}])
return [medvol.Image(array, meta)], Report("read", self.name, str(path))
def write(self, image, path, policy, **options):
report = Report("write", self.name, str(path))
if image.meta.spaces:
report.drop("spaces", "the format has no geometry", policy.on_loss)
np.save(path, image.array)
return report
medvol.registry.register(NpyPlugin())
medvol.convert("ct.nii.gz", "ct.npy") # reports the dropped geometry
image, report = medvol.read("ct.npy")
Guidelines, which the built-in plugins follow:
- Read the stored values in the stored order (C order, fastest file axis last) and describe them with
axes,spacesandvalues. Record the file's index order insource[].index_map. - Compute geometry yourself from the raw header fields, with index = voxel centre, and build patient
spaces with
medvol.geometry.patient_space(matrix_lps, array_axes, policy.world_frame). - Keep the complete header in
source[].header, readable, and use it only when writing the same format. Classify its fields with aFieldTable: core-owned fields are regenerated from the core, data-dependent fields are kept only whilesource[].fingerprintmatches. - Declare capabilities honestly; MedVol drops or refuses what they exclude before
writeis called. Anything else you cannot write:report.drop(...)withpolicy.on_loss. - Report every choice with
report.choose(name, value)and every doubt withreport.warn(message).
Registering through an entry point¶
A package registers its plugins in the medvol.formats entry point group, so they are found when MedVol is
imported:
[project.entry-points."medvol.formats"]
npy = "my_package.npy_plugin:NpyPlugin"
A built-in plugin of the same name takes precedence. medvol.registry.register(plugin) replaces any plugin at
runtime.