Extending HABIT
This page is the v1 habitat plugin path: implement a protocol, register it, optionally ship it as an entry point. Third parties should not need to fork HABIT.
For built-in Spec names and constructor parameters, see
Habitat Spec component catalog. For discovery APIs, see
Plugin introspection API. For arithmetic formulas without a plugin, use the
built-in expression voxel extractor
(Custom features).
v1 registry and entry points
Domain string = snake_case singular protocol name (e.g.
voxel_feature_extractor,habitat_model_fitter)Entry-point group =
habit.<domain>Discover with
list_plugins("voxel_feature_extractor")/load_plugins()Create with
Registry.create(name, **params)and reference the name fromHabitatSpec
Habitat domains you typically extend:
Voxel feature extractors:
VoxelFeatureExtractorRegistrySupervoxelizers / supervoxel features
Habitat model fitters / assigners / habitat feature extractors
Three contracts apply to every plugin:
Behavior — implement the matching protocol (callable with
spec, usually__call__(subject)).Registration —
@Registry.register("name")in-process, or an entry point that imports the module which registers.Parameters — live on the component constructor (and therefore on
Spec("name", {…})). Look them up withRegistry.constructor_signature("name").
Important
Registration decorators run only when the Python module is imported.
External plugins must be imported (or loaded via load_plugins())
before the workflow starts. For in-tree development, import the module
from the corresponding subpackage __init__.py.
v1 custom voxel feature extractors
Use this path for DIY formulas that need neighbourhoods, embeddings, or
logic beyond the built-in expression DSL. The runnable Custom
features gallery uses three liver DCE maps (arterial relative
enhancement, arterial-to-portal wash-out, arterial-to-delayed wash-out).
Step 1: Implement the protocol and register
import numpy as np
from habit.contracts import VoxelFeatureField
from habit.contracts.subject import Subject
from habit.voxel_features import (
VoxelFeatureExtractorRegistry,
aligned_image,
build_voxel_field,
roi_voxels,
)
from habit.spec import Spec
PHASES = ("pre_contrast", "LAP", "PVP", "delay_3min")
NAMES = (
"relative_enhancement_lap",
"relative_washout_pvp",
"relative_washout_delay",
)
@VoxelFeatureExtractorRegistry.register("dce_hemodynamics")
class DCEHemodynamics:
def __init__(self, phases=PHASES, roi=None, eps=1e-8):
self.phases = tuple(phases)
self.roi = roi
self.eps = float(eps)
@property
def spec(self) -> Spec:
return Spec(
name="dce_hemodynamics",
params={"phases": list(self.phases), "roi": self.roi, "eps": self.eps},
)
def __call__(self, subject: Subject) -> VoxelFeatureField:
mask, inside, index = roi_voxels(subject, self.roi)
pre, lap, pvp, delay = (
aligned_image(subject, phase, mask, owner="dce_hemodynamics")[inside]
for phase in self.phases
)
values = np.column_stack(
[
(lap - pre) / (pre + self.eps),
(lap - pvp) / (lap - pre + self.eps),
(lap - delay) / (lap - pre + self.eps),
]
)
return build_voxel_field(
subject, mask, index, NAMES, np.asarray(values), self.spec,
)
Step 2: Use it from a HabitatSpec
from habit.spec import HabitatSpec, Spec, Stage
import habit.recipes as recipes
spec = HabitatSpec(
name="diy",
stages=(
Stage(
"extract_voxel_features",
Spec(
"dce_hemodynamics",
{"phases": ["pre_contrast", "LAP", "PVP", "delay_3min"]},
),
),
Stage("preprocess", Spec("zscore", {"across_features": False})),
Stage(
"fit",
Spec(
"kmeans",
{
"min_habitats": 2,
"max_habitats": 5,
"validation": "elbow",
"n_init": 3,
},
),
),
Stage("assign", Spec("nearest_centroid")),
Stage("quantify", Spec("volume")),
),
random_seed=42,
)
result = recipes.Study(spec=spec).fit_predict(cohort)
Step 3 (optional): ship as a third-party package
In the plugin’s pyproject.toml:
[project.entry-points."habit.voxel_feature_extractor"]
dce_hemodynamics = "my_package.features:register"
where register() performs the @VoxelFeatureExtractorRegistry.register
call (or imports the module that does). Users then:
from habit.plugins import load_plugins
load_plugins()
See Custom features for a runnable demo covering both
expression and a custom plugin, and Plugin introspection API for discovery.
Other habitat domains follow the same pattern: implement the protocol,
register on the matching registry, reference the name from
HabitatSpec. Catalogs and constructor signatures:
Habitat Spec component catalog.
Extension principles
Follow the protocol. Custom habitat components carry a spec and
accept the typed contract for that domain (Subject, feature field,
habitat map, …). Do not add I/O inside L0–L3 plugins.
Register a stable name. Use a descriptive snake_case name
(dce_hemodynamics, not dcehemo). Keep it unique in that domain.
Document parameters. Constructor arguments are the public contract. Give defaults and units in the docstring.
Test. Unit-test the plugin on a small Subject; then run it through
recipes.Study(spec=…).fit_predict on one demo case.
Image preprocessing and tabular ML
Image-preprocessing steps and tabular-ML models / selectors are not
the public extension story. Those v0.1 factory tours
(PreprocessorFactory, ModelFactory, SelectorRegistry, …) are
frozen. Maintainers who still need them should read the archived pages in
the repository:
developer/sphinx_archive/extension_points.rstdeveloper/sphinx_archive/customization_index.rst
FAQ
How do I debug a custom component? Enable logging, write a unit test
against a synthetic Subject, and call the plugin directly before
wiring it into a HabitatSpec.
How do I share a custom component? Ship a small package with a
habit.<domain> entry point, or contribute it upstream with tests.
How do I handle extra dependencies? Document them; put non-core stacks in your plugin’s extras, not in HABIT’s required dependencies.
Next steps
Architecture — layers and invariants
Contributing — tests and pull requests
Habitat Spec component catalog — built-in component catalog
Plugin introspection API —
list_plugins/load_pluginsUpstream Dependencies and Documentation Links — third-party library notes