Domain protocols and registries
The v2 capability packages implement the operator surface: subject-level callables, one cohort-level habitat fitter, table-ML components, and registries that construct them by name.
Import style
Import every public protocol, component, and pipeline from its canonical capability package, for example
from habit.pipeline import SubjectPipeline.Registries are not top-level exports:
from habit.supervoxel import SupervoxelizerRegistry from habit.habitat_model import HabitatModelFitterRegistry
Importing the v2 capability packages registers built-in components.
Single-subject operators
Each subject-level operator is a one-argument callable. No cohort, backend, or YAML is required to process one subject:
from habit.voxel_features import RawVoxelFeatures
from habit.supervoxel import SlicSupervoxelizer
voxel_fx = RawVoxelFeatures(modalities=["T1", "T2"])
svx = SlicSupervoxelizer(n_supervoxels=50)
field = voxel_fx(subject) # Subject -> VoxelFeatureField
unit = svx(field) # VoxelFeatureField -> Supervoxelization
Registry construction (same pattern for every domain):
from habit.supervoxel import SupervoxelizerRegistry
from habit.habitat_model import HabitatModelFitterRegistry
svx = SupervoxelizerRegistry.create("slic", n_supervoxels=50)
fitter = HabitatModelFitterRegistry.create("kmeans", n_habitats=4, n_init=10)
Five habitat protocols
Protocol |
Call shape |
Level |
|---|---|---|
|
|
Subject |
|
|
Subject |
|
|
Cohort |
|
|
Subject |
|
|
Subject |
Only fitting the habitat model is cohort-level. After that, mapping uses
model.assigner() (or a registry-built assigner).
Hand-assembled two-step chain
from habit.habitat_model import HabitatModelFitterRegistry
from habit.voxel_features import RawVoxelFeatures
from habit.supervoxel import SlicSupervoxelizer
voxel_fx = RawVoxelFeatures(modalities=["T1", "T2"])
svx = SlicSupervoxelizer(n_supervoxels=50)
fields = [voxel_fx(s) for s in cohort]
units = [svx(f) for f in fields]
fitter = HabitatModelFitterRegistry.create("kmeans", n_habitats=4)
fitter.set_random_state(42)
model = fitter.fit(units, cohort=cohort)
maps = [model.assigner()(u) for u in units]
SubjectPipeline
Compose the subject-level chain into one callable (HABIT’s typed Compose):
from habit.habitat_features import GraphHabitatFeatures, HabitatVolumeFeatures, IthHabitatFeatures, MsiHabitatFeatures, NonRadiomicsHabitatFeatures
from habit.pipeline import SubjectPipeline
pipe = SubjectPipeline(
voxel_feature_extractor=voxel_fx,
supervoxelizer=svx,
habitat_assigner=model.assigner(),
)
# Positional form also works: SubjectPipeline(voxel_fx, svx, model.assigner())
habitat_map = pipe(subject)
table = pipe.extract_features(
subject,
[
HabitatVolumeFeatures(),
MsiHabitatFeatures(),
IthHabitatFeatures(),
NonRadiomicsHabitatFeatures(),
GraphHabitatFeatures(),
# Heavy PyRadiomics families (opt-in; require pyradiomics):
# TraditionalRadiomicsHabitatFeatures(),
# WholeHabitatRadiomicsFeatures(),
# EachHabitatRadiomicsFeatures(),
],
)
maps = cohort.map(pipe)
Set supervoxelizer=None for direct voxel → habitat designs (no SLIC step).
Train / fit recipes that already hold clustering units should call
pipe.assign(units) or pipe.label_and_describe(subject, units, extractors)
instead of pipe(subject) / extract_features, so Stage-1 voxel features
are not recomputed.
HabitatComponents (spec → live objects)
build_habitat_components() is the single
construction site that turns a HabitatSpec into live
operators. Attribute names on
HabitatComponents match the corresponding
HabitatSpec fields and SubjectPipeline
parameters (assembled preprocessor chains use the singular form, as on the
pipeline):
|
Spec / pipeline counterpart |
|---|---|
|
|
|
|
|
|
|
assembled from |
|
assembled from |
|
assembled from |
|
|
|
|
from habit.pipeline.assembly import build_habitat_components
components = build_habitat_components(spec)
# Same name as on the Spec — the live VoxelFeatureExtractor instance:
field = components.voxel_feature_extractor(subject)
# Fit-time units (subject-level chains only; no assigner / cohort chain):
units = components.pipeline(assigner=None).units(subject)
Factory helpers build_voxel_extractor / build_supervoxel_extractor /
build_habitat_extractor build a single tree node; they are not
HabitatComponents attributes.
Composing features: trees, combiners, and statistics
Every extraction stage accepts one node abstraction — a recursive
Spec tree built from two shapes:
Leaf — one extraction form over one or more modalities. The single-modality form takes
modality="T1"; the multi-modality stacking form keepsmodalities=[...](rawonly).Combiner node — a
Combinerimplementation with child nodes underparams["children"]. Combiners merge child blocks column-wise and know nothing aboutSubjector files, which keeps them trivially testable and reusable at any level.
from habit.voxel_features import build_voxel_extractor
from habit.supervoxel import build_supervoxel_extractor
from habit.spec import parse_feature_expression
voxel_fx = build_voxel_extractor(
parse_feature_expression(
'concat(raw("T1"), ratio(raw("T1"), raw("T2"), as_="t1_over_t2"))'
)
)
field = voxel_fx(subject) # still a one-argument atomic call
build_voxel_extractor / build_supervoxel_extractor /
build_habitat_extractor route a plain leaf Spec to the registry and
a tree Spec to a wrapper that implements the stage’s protocol — the
pipeline consumes both transparently. SubjectPipeline binds the working
and original voxel fields to supervoxel extractors that declare
bind_fields (the statistics extractors mean / std /
percentile aggregate one modality’s voxel signal per supervoxel;
source="original" selects the pre-preprocessing signal).
Built-in combiners: concat, weighted_concat / average
(per-child weights), ratio / difference (exactly two children),
kinetic (DCE slope pairs), expression (dataframe arithmetic).
Column naming: single-column nodes keep their source label
(modality > as_ > name); as_ renames only single-output nodes.
See Feature composition for a runnable tour.
Precision screen: perturbations and precise features
Precision screening compares voxel-texture maps; it does not extract
them. Maps come from extract_voxel_texture() or
VoxelRadiomicsFeatures (see Voxel feature
extractors). perturb_image() builds the simulated retest.
Neither atom needs a Cohort or YAML.
import numpy as np
from habit.precision import perturb_image
from habit.voxel_features import extract_voxel_texture
retest_rng = np.random.default_rng(7)
noisy = perturb_image(image, method="gaussian_noise", rng=retest_rng)
shifted = perturb_image(noisy, method="translation", shift_fraction=0.5, rng=retest_rng)
perturbed = perturb_image(shifted, method="rotation", angle_degrees=0.5, rng=retest_rng)
feat_r1 = extract_voxel_texture(image, mask, kernel_radius=1, bin_width=12)
feat_r3 = extract_voxel_texture(image, mask, kernel_radius=3, bin_width=12)
perturb_image wraps the registered image_perturbation methods
that return a perturbed intensity volume (gaussian_noise,
translation, rotation, rigid, bspline_deform).
Mask-only contour methods (morphological, gradient_weighted,
slice_extent) must be called on a
Subject via
ImagePerturbationRegistry — see
Precise features. extract_voxel_texture is one
voxel-radiomics pass; paper combinations are repeated calls (R1 vs R3,
B12 vs B25, original vs perturbed). Then compose ICC panels:
from habit.precision import (
aggregate_panels,
identify_precise_features,
precision_panel,
)
panel = precision_panel(
{"original": field, "perturbed": perturbed_field},
agreement="absolute", # ICC(3A,1); "consistency" is ICC(3C,1)
)
cohort_panel = aggregate_panels([panel_s1, panel_s2]) # median across subjects
precise = identify_precise_features(
{"repeatability": cohort_panel}, lcl_threshold=0.5
)
precise.save("precise_features.json")
The ninth protocol, ImagePerturbation, is the Subject-level form of
the same methods (component(subject, rng=...)). The paper default
chain is prior2024_retest_perturbation()
(Appendix S2 / MIRP 1.2.0). Optional bspline_deform (MONAI
Rand3DElastic; extra monai) is not in that chain.
PreciseFeatureSet.preprocessor() returns a FeatureWhitelist.
The one-call recipe is
habit.recipes.identify_precise_voxel_features(); the runnable tour
is Precise features.
Randomness (Seedable)
Components that participate in study seeding implement set_random_state
(Seedable), including supervoxelizers. Default seed is 0 unless
HabitatSpec.random_seed / an explicit call overrides it. A backend may
still be numerically deterministic (current skimage SLIC); the method remains
so every stage shares one seeding surface.
from habit.habitat_model import HabitatModelFitterRegistry
from habit.supervoxel import SupervoxelizerRegistry
slic = SupervoxelizerRegistry.create("slic", n_supervoxels=50)
km = SupervoxelizerRegistry.create("kmeans", n_supervoxels=40)
slic.set_random_state(42)
km.set_random_state(42)
fitter = HabitatModelFitterRegistry.create("kmeans", n_habitats=4)
fitter.set_random_state(42)
Built-in registry domains
Discover names at runtime with Plugin introspection API (list_plugins(domain)).
Registry |
Domain string |
Examples |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TablePipeline composes table preprocessors / selectors / classifiers for
tabular ML outside the imaging path.
See also
Protocol and registry source: the canonical
habit.<capability>packagesIntrospection: Plugin introspection API
In-memory types: Data model (habit.contracts)