ga_metrics
Shared GA metrics computation primitives.
These pure functions are used by both ga_calculation_with_fovea and
ga_calculation_without_fovea steps. Extracted here so that neither step
package depends on the other.
Module
Functions
compact_bscan_index
def compact_bscan_index(bscan_index: float, na_bscan_indices: tuple[int, ...]) ‑> float:Shift an input-space B-scan coordinate into compacted index space.
parse_bscan_predictions gives column_masks a row only for a B-scan that
carried model output, so its first axis is compacted. A coordinate that
arrived in the original input index space — a fovea landmark's slice, for
instance — therefore sits one row too high for every dropped frame below it,
which is slice_thickness of physical error per dropped frame once the
displacement is scaled.
A coordinate whose own B-scan was dropped maps onto the next surviving row. That is the closest surviving sample to a frame that was never imaged; the alternative, landing it half-way between neighbours, would invent a position for tissue no B-scan observed.
Arguments
bscan_index: The B-scan coordinate in the original (uncompacted) input index space.na_bscan_indices: The dropped input indices, fromParsedBScanPredictions.na_bscan_indices.
Returns The corresponding coordinate in compacted index space.
compute_ga_metrics_for_scan
def compute_ga_metrics_for_scan( bscan_predictions: tuple[str, ...], slice_thickness: float, pixel_spacing_column: float, all_segmentation_labels: dict[str, int], ga_area_include_segmentations: list[str], ga_area_exclude_segmentations: list[str], n_scan_biomarker_thresholds: Mapping[str, float] | None = None, include_raw_pathology_probabilities: bool = False,) ‑> GAMetrics:Compute GA metrics for a single scan (without fovea).
This encapsulates the per-file computation loop body from
_WorkerSide.run() (lines 268-343 in the original).
Arguments
bscan_predictions: The raw per-B-scan prediction JSON strings.slice_thickness: The distance between B-scans in mm.pixel_spacing_column: The pixel spacing along the B-scan column axis.all_segmentation_labels: All segmentation label names to class index.ga_area_include_segmentations: Segmentation labels to include when computing the GA area column mask.ga_area_exclude_segmentations: Segmentation labels to exclude when computing the GA area column mask.n_scan_biomarker_thresholds: Per-biomarker>=probability threshold used to computen_scan_run_lengths. Keys define which N-scan biomarkers are evaluated; whenNone, every label inN_SCAN_BIOMARKER_LABELSdefaults to0.5.include_raw_pathology_probabilities: Whether to include the raw per-B-scan pathology probability arrays on the returned metrics.
Raises
Exception: Propagates any exception from prediction parsing or metric computation so that the caller can handle/skip.
compute_n_scan_run_lengths
def compute_n_scan_run_lengths( class_probabilities_by_bscan: Mapping[str, NDArray[Any]], thresholds: Mapping[str, float],) ‑> dict[str, int]:Longest consecutive-B-scan run over the threshold, per biomarker.
For each requested biomarker, mask the per-B-scan probabilities at (>=)
its threshold and return the length of the longest consecutive True run.
The input must be B-scan-indexed — ParsedBScanPredictions. class_probabilities_by_bscan, not class_probabilities. The latter is
appended once per reported detection, so a biomarker seen on B-scans 0, 2
and 4 yields [p, p, p] there and would be misread as a run of 3.
ParsedBScanPredictions rejects a misaligned mapping on construction, so
this only bites a caller that assembles the mapping by hand.
Arguments
class_probabilities_by_bscan: Per-biomarker probability arrays indexed by B-scan, with0.0where the biomarker was not reported on a B-scan (as built byparse_bscan_predictions).thresholds: Per-biomarker>=probability threshold. Keys define which biomarkers to evaluate; the result is dense over these keys.
Returns
A {biomarker: longest_run} map. A biomarker whose array is absent,
empty, or has no B-scan meeting the threshold maps to 0.
convert_nan_to_zero
def convert_nan_to_zero(value: Any) ‑> float:Convert NaN values to 0.
extract_bscan_predictions
def extract_bscan_predictions(ga_inferences: Any) ‑> tuple[str, ...]:Extract ordered B-scan prediction strings from a GA inferences dict.
Arguments
ga_inferences: The value of theinferences_json(orinferences_json_ga) field from a merged row. Expected to be a dict keyed by B-scan index (numeric strings or ints).
Returns Tuple of prediction strings sorted by numeric B-scan index, or an empty tuple if the input is not a non-empty dict.
get_lesion_sizes
def get_lesion_sizes( num_lesions: int, labeled_array: NDArray[Any], slice_thickness: float, pixel_spacing_column: float,) ‑> list[float]:Calculate the size of each lesion in mm^2.
Arguments
num_lesions: Number of lesions in the image.labeled_array: Numpy array of shape (num_bscans, num_cols) where each pixel is labelled with the lesion number it belongs to.slice_thickness: Thickness of each B-scan in mm.pixel_spacing_column: Spacing between columns in mm.
Returns List of lesion sizes in mm^2.
get_max_ga_bscan_index
def get_max_ga_bscan_index(column_masks_arr: NDArray[Any], ga_area: float) ‑> int | None:Return the index of the B-scan with the largest GA area.
Arguments
column_masks_arr: Numpy array mask of shape (num_bscans, num_cols).ga_area: Total GA area in mm^2.
Returns
Index of the B-scan with the largest GA area, or None if no GA.
get_missing_data_reason
def get_missing_data_reason( slice_thickness: float, pixel_spacing_column: float, original_filename: str | float, pixel_spacing_row: float | None = <object object>,) ‑> str | None:Check required per-row fields and return a skip reason if any are missing.
Arguments
slice_thickness: Value from theSlice Thicknessmetadata column. Will befloat('nan')when the datasource has no value for this file.pixel_spacing_column: Value from thePixel Spacing Columnmetadata column. Will befloat('nan')when the datasource has no value for this file.original_filename: Value from the filename metadata column. Will befloat('nan')when the left-join produced no match.pixel_spacing_row: Optional value from thePixel Spacing Rowmetadata column. Only the fluid-volume calculation requires this axis, so it defaults to the_UNSETsentinel (not checked) — GA callers pass nothing and are unaffected. When a value is supplied it is validated like the other fields, including a genuineNone/NaN cell (reported aspixel_spacing_row).
Returns
A "missing_data:<field>,..." string if one or more fields are NaN /
null, or None if all fields are present.
get_shortest_distance_from_image_centre
def get_shortest_distance_from_image_centre( column_masks_arr: NDArray[Any], labeled_array: NDArray[Any], num_lesions: int, slice_thickness: float, pixel_spacing_column: float,) ‑> float:Calculate the distance from the image centre to the nearest lesion.
Image centre is used as a proxy for the fovea.
Arguments
column_masks_arr: Numpy array mask of shape (num_bscans, num_cols).labeled_array: Numpy array of shape (num_bscans, num_cols) where each pixel is labelled with the lesion number it belongs to.num_lesions: Number of lesions in the image.slice_thickness: Thickness of each B-scan in mm.pixel_spacing_column: Spacing between columns in mm.
Returns Distance from the image centre to the nearest lesion in mm.
parse_bscan_predictions
def parse_bscan_predictions( bscan_prediction_strs: tuple[str, ...], slice_thickness: float, pixel_spacing_column: float, all_segmentation_labels: dict[str, int], ga_area_include_segmentations: list[str], ga_area_exclude_segmentations: list[str],) ‑> ParsedBScanPredictions:Parse raw B-scan prediction strings into columnar masks and probabilities.
This function was extracted from _WorkerSide._parse_bscan_predictions in
ga_trial_calculation_algorithm_base.py. The only change is that
configuration values (segmentation labels) are passed as explicit parameters
instead of being read from self.
Arguments
bscan_prediction_strs: Tuple of JSON prediction strings, one per B-scan.slice_thickness: Thickness of each B-scan in mm.pixel_spacing_column: Spacing between columns in mm.all_segmentation_labels: Mapping of segmentation class name → index.ga_area_include_segmentations: Segmentations used to include GA area.ga_area_exclude_segmentations: Segmentations used to exclude GA area.
Returns
A ParsedBScanPredictions containing columnar masks, class
probabilities, and class areas per B-scan.