vessel_classifier#

Iterative artery/vein classification on a reduced vascular graph.

The algorithm has four phases:

  1. Score — each edge gets a continuous artery score and vein score in [0, 1] based on radius, marker intensity, and binary mask.

  2. Seed — high-confidence edges are assigned initial labels (artery, vein, or capillary).

  3. Propagate — labels spread outward from seeds along the graph. Artery and vein labels propagate simultaneously and compete for ambiguous edges. Propagation uses a decaying score threshold.

  4. Cleanup — remove small connected components (fragments).

All thresholds are collected in ClassificationConfig.

See also

VesselGraphProcessor

Orchestrator that builds the graph and calls this classifier.

[Kirst2020].

class ClassificationConfig(vein_intensity_range: tuple[float, float], restrictive_vein_radius: float, permissive_vein_radius: float, final_vein_radius: float, arteries_min_noise_edges: int, artery_intensity_min: float, artery_trace_radius: float, vein_trace_radius: float, vein_intensity_min: float, distance_to_surface_min: float, max_artery_trace_iter: int, max_vein_trace_iter: int, artery_seed_threshold: float = 0.7, vein_seed_threshold: float = 0.6, min_artery_component_edges: int = 30, min_vein_component_edges: int = 30)[source]#

Bases: object

All thresholds for vessel classification.

Radius thresholds are unit-agnostic: they match whatever units VesselSignals.radii uses (µm for current graphs, voxels for legacy).

The from_config factory resolves the correct source.

vein_intensity_range#

(low, high) intensity range on the artery channel that characterises veins. Edges within this range AND with large radius are classified as restrictive veins.

Type:

tuple[float, float]

restrictive_vein_radius#

Minimum radius for an edge to qualify as a restrictive (high-confidence) vein.

Type:

float

arteries_min_noise_edges#

Minimum number of connected artery edges to survive initial noise removal (step 1).

Type:

int

artery_intensity_min#

Minimum artery marker intensity for tracing to continue.

Type:

float

artery_trace_radius#

Minimum edge radius to continue artery tracing.

Type:

float

vein_trace_radius#

Minimum edge radius to continue vein tracing.

Type:

float

vein_intensity_min#

Minimum vein marker intensity to continue vein tracing. Only used when a dedicated vein channel exists.

Type:

float

distance_to_surface_min#

Minimum distance to brain surface (atlas voxels) for tracing to continue.

Type:

float

max_artery_trace_iter#

Maximum hysteresis iterations for artery tracing.

Type:

int

max_vein_trace_iter#

Maximum hysteresis iterations for vein tracing.

Type:

int

min_artery_component_edges#

Minimum edges in a connected artery component to survive final cleanup (step 7).

Type:

int

min_vein_component_edges#

Minimum edges in a connected vein component to survive final cleanup (step 7).

Type:

int

classmethod from_config(cfg: dict, legacy_thresholds: dict = None, use_legacy: bool = False) ClassificationConfig[source]#

Build from the vessel_type_postprocessing config subtree.

Parameters:
  • cfg (dict) – The vessel_type_postprocessing config subtree.

  • legacy_thresholds (dict or None) – VesselGraphProcessor._LEGACY_THRESHOLDS dict. Required when use_legacy=True.

  • use_legacy (bool) – If True, read voxel-space thresholds from legacy_thresholds instead of µm values from config.

arteries_min_noise_edges: int#
artery_intensity_min: float#
artery_seed_threshold: float = 0.7#
artery_trace_radius: float#
distance_to_surface_min: float#
final_vein_radius: float#
property initial_threshold: float#

Starting score threshold for propagation.

max_artery_trace_iter: int#
property max_propagation_iter: int#

Total propagation budget derived from existing tracing iterations.

max_vein_trace_iter: int#
min_artery_component_edges: int = 30#
property min_component_edges: dict[VesselType, int]#

Per-type minimum component sizes for cleanup.

min_vein_component_edges: int = 30#
permissive_vein_radius: float#
property propagation_floor: float#

Score floor — propagation never accepts below this.

restrictive_vein_radius: float#
property seed_thresholds: dict[VesselType, float]#

Per-type seed thresholds.

property threshold_decay: float#

Per-iteration decay. Reaches floor at last iteration.

vein_intensity_min: float#
vein_intensity_range: tuple[float, float]#
vein_seed_threshold: float = 0.6#
vein_trace_radius: float#
class ClassificationMetrics(n_edges: int = 0, counts: dict = None, fractions: dict = None, seed_counts: dict = None, propagation_iterations: int = 0, propagated_counts: dict = None, n_seed_conflicts: int = 0, n_direct_contacts: int = 0, fragments_removed: dict = None, score_stats: dict = None)[source]#

Bases: object

Diagnostic metrics produced after classification.

Intended for logging, QA dashboards, and sanity checks.

record_final_counts(labels: ndarray, scores: dict[VesselType, ndarray]) None[source]#

Compute final distribution, propagated counts, and score statistics.

record_seed_counts(labels: ndarray) None[source]#

Snapshot label distribution after seeding phase.

summary() str[source]#

Human-readable summary for logging.

counts: dict = None#
fractions: dict = None#
fragments_removed: dict = None#
n_direct_contacts: int = 0#
n_edges: int = 0#
n_seed_conflicts: int = 0#
propagated_counts: dict = None#
propagation_iterations: int = 0#
property sanity_warnings: list[str]#

Return a list of human-readable warnings if metrics look suspicious.

score_stats: dict = None#
seed_counts: dict = None#
class VesselClassifier(graph: Graph, config: ClassificationConfig, signals: VesselSignals)[source]#

Bases: object

Score-then-propagate vessel classifier for reduced vascular graphs.

Usage#

signals = VesselSignals(...)
cfg     = ClassificationConfig.from_config(config_dict)
vc      = VesselClassifier(graph, cfg, signals)
metrics = vc.classify()
print(metrics.summary())
param graph:

The annotated, reduced graph. Modified in place.

type graph:

Graph

param config:

All classification thresholds.

type config:

ClassificationConfig

param signals:

Pre-collected per-edge signal arrays.

type signals:

VesselSignals

classify() ClassificationMetrics[source]#

Run the full classification pipeline: score → seed → propagate → cleanup.

After this call the graph has two boolean edge properties:

  • 'artery' — True for edges classified as artery

  • 'vein' — True for edges classified as vein

Edges that are neither artery nor vein are implicitly capillaries.

Returns

ClassificationMetrics

Diagnostic metrics and sanity check results.

class VesselSignals(radii: ndarray, artery_binary: ndarray | None = None, artery_intensity: ndarray | None = None, vein_binary: ndarray | None = None, vein_intensity: ndarray | None = None, distance_to_surface: ndarray | None = None)[source]#

Bases: object

All per-edge arrays needed by the classifier.

radii and all ClassificationConfig radius thresholds are guaranteed to be in the same units. The caller resolves units before constructing this object.

Fields are None when the corresponding channel does not exist.

artery_binary: ndarray | None = None#
artery_intensity: ndarray | None = None#
distance_to_surface: ndarray | None = None#
radii: ndarray#
vein_binary: ndarray | None = None#
vein_intensity: ndarray | None = None#
class VesselType(value)[source]#

Bases: IntEnum

Edge classification labels for vessel type assignment.

IntEnum so labels can be used directly as numpy array values and in boolean comparisons without casting.

classmethod competing_labels() tuple[VesselType, ...][source]#

The two vessel types that compete during propagation.

competitor() VesselType[source]#

Return the competing vessel type.

ARTERY = 1#
CAPILLARY = 3#
UNLABELLED = 0#
VEIN = 2#