Expression voxel features

Background. Clinicians often read DCE images as ratios (how much a voxel enhances, how fast it washes out) rather than raw signal. An expression extractor computes such ratios per voxel from a formula string, without writing a class.

Purpose. You get a voxel-feature table with four derived columns and a map of arterial relative enhancement inside the ROI.

When to use. When each new column is simple arithmetic on your modalities; for anything more, see Custom features.

Key terms.

  • voxel feature / extract – see Extracting voxel intensities.

  • derived map – a voxel-wise image computed from other images, such as relative enhancement (LAP - pre_contrast) / pre_contrast.

ExpressionVoxelFeatures builds one column per formula. Names in the formula are modality keys on the subject (LAP, pre_contrast, …). eps keeps a zero denominator from breaking a ratio.

Clustering those columns is a later page (Clustering habitats from a derived map).

Change DATA / MODALITIES / ROI to your preprocessed layout. sphinx_gallery_thumbnail_number = 1

from pathlib import Path

import matplotlib.pyplot as plt

from habit.contracts import cohort_from_directory
from habit.datasets import fetch_demo
from habit.viz import plot_voxel_texture_slice
from habit.voxel_features import ExpressionVoxelFeatures

DATA = fetch_demo()
# All four DCE phases: unenhanced, arterial, portal-venous, delayed.
MODALITIES = ("pre_contrast", "LAP", "PVP", "delay_3min")
ROI = "LAP"
subject = cohort_from_directory(DATA, modalities=MODALITIES, roi=ROI)[0]
print(subject.subject_id, sorted(subject.images))
HABIT demo data (cached)
DATA (preprocessed root): C:\Users\dongm\.habit_data\demo-data-v1\preprocessed

On-disk inventory of this folder:
  subjects (5): subj001, subj002, subj003, subj004, subj005
  image series: LAP, PVP, delay_3min, pre_contrast
  mask keys:    LAP, PVP, delay_3min, pre_contrast
  example image: images/subj001/delay_3min/WATER__BH_Ax_LAVA_Flex_3min_Series0012.nrrd
  example mask:  masks/subj001/delay_3min/WATER__BH_Ax_LAVA_Flex_10min_Series0017_mask.nrrd

Your own data must use the same folder tree (change IDs / series names):

  DATA/
    images/<subject_id>/<modality>/<one image file>
    masks/<subject_id>/<roi>/<one mask file>

Then load it with the same call the demos use:

  cohort = cohort_from_directory(DATA, modalities=("LAP",), roi="LAP")

Swap DATA / modalities / roi to match your tree. Mask key is often the
same as one image series (here LAP).
subj001 ['LAP', 'PVP', 'delay_3min', 'pre_contrast']

Each key becomes one column of the voxel field. Only ROI voxels are evaluated; the formulas use the modality keys above.

extractor = ExpressionVoxelFeatures(
    features={
        "rel_enh_lap": "(LAP - pre_contrast) / (pre_contrast + eps)",
        "rel_enh_pvp": "(PVP - pre_contrast) / (pre_contrast + eps)",
        "rel_enh_delay": "(delay_3min - pre_contrast) / (pre_contrast + eps)",
        "washout": "(LAP - delay_3min) / (LAP + eps)",
    },
    roi=ROI,
)
field = extractor(subject)
print(f"{field.values.shape[0]} voxels, columns: {list(field.feature_names)}")
print(field.feature_frame().head())
34694 voxels, columns: ['rel_enh_lap', 'rel_enh_pvp', 'rel_enh_delay', 'washout']
   rel_enh_lap  rel_enh_pvp  rel_enh_delay   washout
0     1.388759     1.412178       1.330211  0.024510
1     1.324201     1.438356       1.269406  0.023576
2     1.453303     1.478360       1.243736  0.085422
3     1.353070     1.350877       1.234649  0.050326
4     1.188559     1.398305       1.175847  0.005808

Arterial relative enhancement inside the ROI.

Path("out").mkdir(exist_ok=True)
fig = plot_voxel_texture_slice(
    field,
    feature=0,
    anatomy=subject.image(ROI),
    roi_mask=subject.mask(ROI),
    title="relative enhancement (LAP)",
    crop_to="roi",
)
fig.savefig("out/expression_relative_enhancement.png", dpi=150, bbox_inches="tight")
plt.show()
relative enhancement (LAP), rel_enh_lap - Axis 0 (axial-like) @ 21, rel_enh_lap - Axis 1 (coronal-like) @ 34, rel_enh_lap - Axis 2 (sagittal-like) @ 28
F:\work\habit_project\habit\viz\voxel_texture.py:1019: UserWarning: Display geometry conflict: image/anatomy direction does not match mask/label direction. Using the mask/label direction so coronal/sagittal superior-up follows the labelled anatomy. Pass direction= to override.
  direction, spacing = resolve_display_geometry(

Total running time of the script: (0 minutes 2.629 seconds)

Gallery generated by Sphinx-Gallery