Skip to main content

functions

CST/CRT-specific thickness calculation functions.

The shared retinal-layer primitives (parse_center_coordinates, build_thickness_map, layer selection, etc.) live in bitfount.steps.data_utils.thickness_metrics and are used by both the CST and GCC steps. The shared per-file orchestration loop lives in bitfount.steps.data_utils.thickness_calculation.

This module provides the CST/CRT-specific region statistics (compute_average_thickness_around_center) and the high-level per-file compute_cst_metrics_for_scan — ported from _CSTWorkerSide in federated/algorithms/ophthalmology/cst_calculation_algorithm.py (_compute_average_thickness_around_center lines 180-308 and _calculate_metric lines 88-178).

It also hosts the EZ-integrity measurement (compute_ez_integrity), which cst_calculation v2 adds. This module is shared by both versions, so the EZ block is gated on an attenuation threshold being configured; v1 never passes one and its path through compute_cst_metrics_for_scan is unchanged, down to the log lines.

Module

Functions

compute_average_thickness_around_center

def compute_average_thickness_around_center(    thickness_um: NDArray[Any],    center_coords: tuple[float, float, float],    slice_thickness_mm: float,    pixel_spacing_x_mm: float,    radius_mm: float = 0.5,)> dict[str, float | None]:

Compute CST and/or CRT based on radius setting.

  • If radius_mm == 0: This measures the thickness of single center point. (In CST calculations, this measurement is specifically referred to as CRT)
  • If radius_mm > 0: We measure the average thickness, using a circular region within the provided radius. If using thickness of ILM to RPE, this is referred to as CST.

Arguments

  • thickness_um: Thickness map in micrometers, shape (num_slices, width).
  • center_coords: Center coordinates (slice, x, y). Only slice and x are used.
  • slice_thickness_mm: Slice thickness in mm.
  • pixel_spacing_x_mm: Pixel spacing in x direction in mm.
  • radius_mm: radius of circular region in mm. If 0, uses single center point.

Returns Dictionary with mean_um, median_um, std_um, n_samples, and radius_mm.

compute_cst_metrics_for_scan

def compute_cst_metrics_for_scan(    layer_pred: pd.Series,    file_row: pd.Series,    *,    inner_layer: RetinalLayer,    outer_layer: RetinalLayer,    strict_measurement: bool,    landmark_idx: int,    radius_mm: float,    center_landmark_type: str = 'fovea',    metric_name: str = 'CST/CRT',    ez_inner_layer: RetinalLayer | None = None,    ez_outer_layer: RetinalLayer | None = None,    ez_attenuation_threshold_um: float | None = None,)> CSTMetrics:

Calculate CST or CRT for a single file.

Ports _CSTWorkerSide._calculate_metric. Per-instance configuration that the legacy worker read from self is passed as explicit parameters.

Arguments

  • layer_pred: Layer segmentation predictions for one file.
  • file_row: Row containing DICOM metadata and fovea predictions.
  • inner_layer: Desired inner layer for the thickness measurement.
  • outer_layer: Desired outer layer for the thickness measurement.
  • strict_measurement: If True, only calculate if both desired inner and outer layers are available (no fallback).
  • landmark_idx: Index of the landmark to use (0=start, 1=end, 2=middle).
  • radius_mm: Radius of the circular region in mm (0 for CRT single point).
  • center_landmark_type: Nature of the center landmark ("fovea"/"macula").
  • metric_name: Name of the metric being calculated (log text only).
  • ez_inner_layer: Inner layer of the EZ-integrity pair.
  • ez_outer_layer: Outer layer of the EZ-integrity pair.
  • ez_attenuation_threshold_um: Separation above which the EZ counts as intact. None switches the EZ measurement off entirely, which is what cst_calculation v1 does — its path here is then unchanged.

Returns CSTMetrics object with calculated values.

compute_ez_integrity

def compute_ez_integrity(    boundaries: Mapping[RetinalLayer, NDArray[Any]],    *,    ez_layer: RetinalLayer,    rpe_layer: RetinalLayer,    reference_bscan_index: int,    pixel_spacing_row_mm: float,    pixel_spacing_column_mm: float,    threshold_um: float,)> EZIntegrity | None:

Measure EZ integrity along one B-scan.

Every A-scan on the reference B-scan is classified into exactly one of three states, and the two spans are the longest contiguous run of the first two:

  • intact: both layers measured here and separated by more than threshold_um.
  • attenuated: RPE measured here, but the separation does not exceed the threshold — including where EZ is absent entirely. That inclusion is the point. attenuated = ~intact would count unmeasurable columns as attenuated, and separation <= threshold alone would report zero attenuation exactly where attenuation is total, because a missing EZ makes the comparison false rather than true.
  • unmeasurable: no RPE here, so nothing can be said. Columns padded onto a narrow B-scan land here too, which is why they do not inflate the attenuated span.

Arguments

  • boundaries: Boundary grids from build_layer_boundaries, which must include both layers.
  • ez_layer: The inner layer of the pair.
  • rpe_layer: The outer layer of the pair.
  • reference_bscan_index: Row of the grid to measure.
  • pixel_spacing_row_mm: Axial spacing, for the separation.
  • pixel_spacing_column_mm: Lateral spacing, for the spans.
  • threshold_um: Separation above which the EZ counts as intact.

Returns The measurement, or None if the reference B-scan is outside the grid.

Classes

EZIntegrity

class EZIntegrity(    intact_max_span_um: float,    attenuated_max_span_um: float,    measured_width_um: float,    layers_measured: bool,):

The EZ-integrity measurement on one reference B-scan.

Attributes

  • intact_max_span_um: Widest contiguous run of A-scans whose EZ-to-RPE separation exceeds the threshold. The criterion gates on this.
  • attenuated_max_span_um: Widest contiguous run that was measurable but did not exceed it.
  • measured_width_um: How much of the B-scan was measurable at all. Zero makes both spans absence of evidence rather than a finding.
  • layers_measured: Whether both layers appear anywhere in the volume.

Variables

  • static attenuated_max_span_um : float
  • static intact_max_span_um : float
  • static layers_measured : bool
  • static measured_width_um : float