functions
Pure biomarker tabulation logic + Granite biomarker constants.
Ported verbatim from the v8 Granite protocol
(federated/protocols/ophthalmology/generic_biomarker_protocol_granite.py):
the per-metric-dict → DataFrame converters, the drusen / per-label
segmentation-area derivations, and the biomarker_groups/biomarker_names
COLUMN filter. The merge step itself now goes through the shared
bitfount.steps.data_utils.assemble_scan_frame (also used by
criteria_matching), called directly from v1/task.py.
The filter operates on the TECHNICAL (internal) column names — display renaming
via GRANITE_CSV_RENAME_COLS is the downstream csv_report step's job, NOT this
one. self.biomarker_groups / self.biomarker_names became explicit parameters
and ProtocolError became a plain ValueError (fail-loud on unknown group/name
or an empty final selection).
Module
Functions
add_drusen_columns
def add_drusen_columns(merged_df: pd.DataFrame) ‑> pandas.core.frame.DataFrame:Derive drusen presence + aggregate columns from the per-label drusen areas.
Reads the three drusen <label>_area columns (hard_drusen_area /
soft_drusen_area / confluent_drusen_area, produced by
add_segmentation_area_columns) and derives _DRUSEN_DERIVED_COLUMNS:
per-type presence flags, an any-present flag, a type count, and the largest /
combined areas. drusen_largest_area is the largest of the three per-subtype
TOTAL areas (e.g. all hard drusen vs. all soft drusen combined); it is not the
largest individual lesion and cannot answer "is there at least one large
druse". The lesion_calculation step's drusen_largest_lesion_area is the
per-lesion figure. Run add_segmentation_area_columns first — when any
drusen area column is absent (no segmentation data), every derived column is
pd.NA.
Arguments
merged_df: The merged biomarker DataFrame carrying the drusen<label>_areacolumns.
Returns The same DataFrame with the derived drusen columns appended in-place.
add_segmentation_area_columns
def add_segmentation_area_columns(merged_df: pd.DataFrame) ‑> pandas.core.frame.DataFrame:Flatten the raw segmentation area mapping into per-label columns.
The sole producer of the <label>_area columns: one per
AREA_ELIGIBLE_SEGMENTATION_LABELS entry, including the three drusen labels
(add_drusen_columns then derives the drusen presence/aggregate columns from
these). Fluid labels are excluded: fluid is quantified by volume, so a
projected area for them is meaningless. When the segmentation_areas column
is absent (no segmentation data), every <label>_area column is pd.NA.
Arguments
merged_df: The merged biomarker DataFrame, potentially carrying asegmentation_areascolumn of per-file area dicts.
backfill_metrics
def backfill_metrics( metrics: Mapping[str, _MetricT], filenames: list[str],) ‑> dict[str, typing.Optional[~_MetricT]]:Ensure every selected filename has an entry in a metric mapping.
Adds any filenames missing from metrics with a None value (which the
downstream convert_*_to_df helpers turn into an all-NA row) so a scan that
a calculation skipped still appears in the tabulation. Existing values —
including str/None error sentinels — are left untouched, and keys not in
filenames are preserved rather than dropped.
build_cst_df_from_cache
def build_cst_df_from_cache( cache_df: pd.DataFrame, filenames: list[str],) ‑> pandas.core.frame.DataFrame:Build the (canonically-named) CST metrics DataFrame from the cache frame.
Reconstructs CSTMetrics from each cache row's metrics_json blob,
backfills filenames absent from the cache with None (an all-NA row), and
delegates to convert_metrics_to_df + apply_canonical_scan_metric_names
for the CST/GCC shared-descriptor rename (cst_*).
Arguments
cache_df: Thecst_calculationstep's cache partition frame.filenames: The selected filenames (drives the backfill).
Returns The CST metrics DataFrame, filename column first.
build_fluid_df_from_cache
def build_fluid_df_from_cache( cache_df: pd.DataFrame, filenames: list[str],) ‑> pandas.core.frame.DataFrame:Build the fluid metrics DataFrame from the fluid_calculation cache frame.
Reconstructs FluidVolumeMetrics from each cache row's metrics_json blob,
backfills filenames absent from the cache with None (an all-NA row), and
delegates to convert_metrics_to_df + apply_canonical_scan_metric_names:
every scalar field is prefixed with FLUID_PREFIX, the raw
segmentation_volumes dict is flattened into the unprefixed
FLUID_VOLUME_COLUMNS and then dropped (not surfaced as a column).
Arguments
cache_df: Thefluid_calculationstep's cache partition frame.filenames: The selected filenames (drives the backfill).
Returns The fluid metrics DataFrame, filename column first.
build_ga_df_from_cache
def build_ga_df_from_cache( cache_df: pd.DataFrame, filenames: list[str],) ‑> pandas.core.frame.DataFrame:Build the GA metrics DataFrame from the ga_calculation cache frame.
Reconstructs GAMetricsWithFovea from each cache row's metrics_json blob,
backfills filenames absent from the cache with None (an all-NA row), and
delegates to convert_ga_metrics_to_df for the flatten: the scalar +
segmentation_areas fields (retained raw — the downstream
add_segmentation_area_columns / add_drusen_columns derive from it) plus
a max_<pathology>_probability column per Granite pathology (NaN when a
pathology is absent from a file's max_pathology_probabilities map, or the
whole row is backfilled/uncomputable).
Arguments
cache_df: Thega_calculationstep's cache partition frame.filenames: The selected filenames (drives the backfill).
Returns The GA metrics DataFrame, filename column first.
build_gcc_df_from_cache
def build_gcc_df_from_cache( cache_df: pd.DataFrame, filenames: list[str],) ‑> pandas.core.frame.DataFrame:Build the (canonically-named) GCC metrics DataFrame from the cache frame.
Reconstructs GCCMetrics from each cache row's metrics_json blob,
backfills filenames absent from the cache with None (an all-NA row), and
delegates to convert_metrics_to_df + apply_canonical_scan_metric_names
for the CST/GCC shared-descriptor rename (gcc_*).
Arguments
cache_df: Thegcc_calculationstep's cache partition frame.filenames: The selected filenames (drives the backfill).
Returns The GCC metrics DataFrame, filename column first.
build_lesion_df_from_cache
def build_lesion_df_from_cache( cache_df: pd.DataFrame, filenames: list[str],) ‑> pandas.core.frame.DataFrame:Build the area metrics DataFrame from the lesion_calculation cache frame.
Reconstructs LesionMetrics from each cache row's metrics_json blob,
backfills filenames absent from the cache with None (an all-NA row),
and delegates to convert_metrics_to_df. No canonical rename applies: the
group columns are already flat and group-prefixed
(LesionMetrics.to_record), because the group set is config-defined and so
cannot be spelled out in a static rename map the way fluid's is.
Arguments
cache_df: Thelesion_calculationstep's cache partition frame.filenames: The selected filenames (drives the backfill).
Returns The area metrics DataFrame, filename column first.
convert_fluid_metrics_to_df
def convert_fluid_metrics_to_df( fluid_metrics: Mapping[str, FluidVolumeMetrics | str | None],) ‑> pandas.core.frame.DataFrame:Convert a filename→FluidVolumeMetrics mapping into a flat DataFrame.
Each key becomes one row. Metric field names are prefixed with
FLUID_PREFIX (fluid_) so they do not collide with columns from other
biomarker DataFrames during the outer merge. When a file's metrics are
None (or a str error/missing-data sentinel), the corresponding columns
are filled with pd.NA.
Arguments
fluid_metrics: Mapping of filename →FluidVolumeMetrics(orNone/ astrsentinel for files with no fluid data).
Returns DataFrame with one row per file and prefixed fluid metric columns. If the mapping is empty, returns an empty DataFrame with only the filename column.
get_biomarker_group_columns
def get_biomarker_group_columns(df: pd.DataFrame) ‑> dict[str, set[str]]:Build a mapping from biomarker group names to their available columns.
Inspects the columns present in df and partitions them into the biomarker
groups (ga_metrics, fluid_metrics, cst_metrics, gcc_metrics,
classification_metrics, drusen_metrics, segmentation_areas,
lesion_metrics). Only columns that actually exist in df are included, so
groups may be empty if the corresponding calculation step failed or was
skipped.
Arguments
df: The merged biomarker DataFrame after all calculation steps.
Returns Dict mapping each group name to the set of column names belonging to that group that are present in df.
lesion_rename_columns
def lesion_rename_columns(*, roi_radius_mm: float | None) ‑> dict[str, str]:Build the technical-to-display rename map for every lesion group column.
Emits both the plain <group>_<suffix> header and the ROI-qualified
<group>_roi_<suffix> header for every group in _LESION_GROUP_DISPLAY_NAMES
and every suffix in _LESION_GROUP_METRIC_DISPLAY_NAMES. The ROI-qualified
header names the region measured, so a whole-scan measurement and a
central-region one are not indistinguishable in the report. roi_radius_mm
is a runtime value (a per-step config field), so this map cannot be a
static module-level constant the way GRANITE_CSV_RENAME_COLS is.
Arguments
roi_radius_mm: Radius in mm of the configured region of interest, orNonewhen no ROI is configured for the step. Named in every ROI-qualified header.
Returns Technical column name to display header, for every group/suffix pair, both plain and ROI-qualified.
rename_cst_metric_columns
def rename_cst_metric_columns( cst_metrics_df: pd.DataFrame,) ‑> pandas.core.frame.DataFrame:Expose explicit CST-prefixed names for shared thickness fields.
rename_gcc_metric_columns
def rename_gcc_metric_columns( gcc_metrics_df: pd.DataFrame,) ‑> pandas.core.frame.DataFrame:Avoid GCC/CST collisions for shared thickness metric field names.
select_biomarker_columns
def select_biomarker_columns( df: pd.DataFrame, biomarker_groups: list[str], biomarker_names: list[str],) ‑> pandas.core.frame.DataFrame:Filter the merged DataFrame to only the requested biomarker columns.
Resolves the biomarker_groups and biomarker_names selection into a
concrete set of columns. When neither is set (both empty), all available
biomarker columns are included. Fails loudly with ValueError on an unknown
group, an unknown column name, or an empty final selection (never silently
broadens). The filename column is always retained and the source column
order preserved.
Arguments
df: The merged biomarker DataFrame containing all computed columns.biomarker_groups: Group names to include (empty ⇒ all groups).biomarker_names: Individual column names to include (empty ⇒ none extra).
Returns A copy of df containing only the selected biomarker columns, in the original column order.