Files
E01_textural_features_blast…/src/evaluation/subject_figure_selection.py
T
2026-09-11 08:28:02 +03:00

1344 lines
58 KiB
Python

from __future__ import annotations
from typing import Any
import pandas as pd
from evaluation.subject_figure_data import (
best_image_aggregation_method,
best_patient_aggregation_method,
hypothesis_slug,
load_best_baseline_subject_frame,
load_h2_subject_frame,
load_h3_subject_frames,
load_h4_subject_bundle,
load_h5_subject_frame,
load_object_roi_frame,
)
VARIANT_COUNT = 5
OBJECT_KEY_COLUMNS = ["patient_id", "image_id", "object_id_within_image"]
AGGREGATION_METHOD_ORDER = [
"max_object_score",
"mean_top3_scores",
"count_objects_above_threshold",
"soft_noisy_or",
]
def _base_spec(
*,
article_id: str,
figure_id: str,
figure_basename: str,
figure_kind: str,
hypothesis: str,
title: str,
variant_index: int,
) -> dict[str, Any]:
return {
"article_id": article_id,
"figure_id": figure_id,
"figure_variant_id": f"{figure_id}_v{variant_index:02d}",
"figure_basename": figure_basename,
"figure_kind": figure_kind,
"hypothesis": hypothesis,
"hypothesis_slug": hypothesis_slug(hypothesis),
"title": title,
"variant_index": variant_index,
}
def _panel_row(
*,
panel_id: str,
role: str,
source_table: str,
source_path: str,
patient_id: str | None = None,
image_id: str | None = None,
object_id_within_image: int | None = None,
class_name: str | None = None,
target_binary: int | None = None,
split_name: str | None = None,
notes: str = "",
) -> dict[str, Any]:
return {
"panel_id": panel_id,
"role": role,
"patient_id": patient_id,
"image_id": image_id,
"object_id_within_image": object_id_within_image,
"class_name": class_name,
"target_binary": target_binary,
"split_name": split_name,
"source_table": source_table,
"source_path": source_path,
"notes": notes,
}
def _rotate_frame(frame: pd.DataFrame, variant_index: int) -> pd.DataFrame:
ordered = frame.reset_index(drop=True)
if ordered.empty:
return ordered.copy()
offset = (variant_index - 1) % len(ordered)
if offset == 0:
return ordered.copy()
return pd.concat([ordered.iloc[offset:], ordered.iloc[:offset]], ignore_index=True)
def _take_rows(
frame: pd.DataFrame,
*,
n_items: int,
variant_index: int,
sort_columns: list[str],
ascending: bool | list[bool],
unique_columns: tuple[str, ...] | None = ("patient_id", "image_id"),
extra_distinct_column: str | None = None,
) -> pd.DataFrame:
if frame.empty or n_items <= 0:
return frame.head(0).copy()
ordered = frame.drop_duplicates(subset=OBJECT_KEY_COLUMNS).sort_values(sort_columns, ascending=ascending).reset_index(drop=True)
rotated = _rotate_frame(ordered, variant_index)
selected: list[dict[str, Any]] = []
seen_objects: set[tuple[Any, ...]] = set()
seen_unique: set[tuple[Any, ...]] = set()
seen_extra: set[Any] = set()
def consider(row: pd.Series, enforce_unique: bool, enforce_extra: bool) -> bool:
object_key = tuple(row[column] for column in OBJECT_KEY_COLUMNS)
if object_key in seen_objects:
return False
if enforce_unique and unique_columns is not None:
unique_key = tuple(row[column] for column in unique_columns)
if unique_key in seen_unique:
return False
if enforce_extra and extra_distinct_column is not None:
extra_value = row[extra_distinct_column]
if extra_value in seen_extra:
return False
selected.append(row.to_dict())
seen_objects.add(object_key)
if unique_columns is not None:
seen_unique.add(tuple(row[column] for column in unique_columns))
if extra_distinct_column is not None:
seen_extra.add(row[extra_distinct_column])
return len(selected) >= n_items
for enforce_unique, enforce_extra in [(True, True), (True, False), (False, False)]:
for _, row in rotated.iterrows():
if consider(row, enforce_unique=enforce_unique, enforce_extra=enforce_extra):
return pd.DataFrame(selected)
return pd.DataFrame(selected)
def _take_group_rows(
frame: pd.DataFrame,
*,
n_items: int,
variant_index: int,
sort_columns: list[str],
ascending: bool | list[bool],
dedupe_columns: list[str],
) -> pd.DataFrame:
if frame.empty or n_items <= 0:
return frame.head(0).copy()
ordered = frame.sort_values(sort_columns, ascending=ascending).drop_duplicates(subset=dedupe_columns).reset_index(drop=True)
rotated = _rotate_frame(ordered, variant_index)
return rotated.head(n_items).copy()
def _pick_single_row(
frame: pd.DataFrame,
*,
variant_index: int,
sort_columns: list[str],
ascending: bool | list[bool],
dedupe_columns: list[str],
) -> dict[str, Any]:
selected = _take_group_rows(
frame,
n_items=1,
variant_index=variant_index,
sort_columns=sort_columns,
ascending=ascending,
dedupe_columns=dedupe_columns,
)
if selected.empty:
raise ValueError("Expected at least one image candidate.")
return selected.iloc[0].to_dict()
def _target_binary(row: pd.Series | dict[str, Any]) -> int | None:
value = row.get("target_binary") if isinstance(row, dict) else row.get("target_binary")
if value is None and "y_true" in row:
value = row.get("y_true")
if value is None and "is_blast" in row:
value = row.get("is_blast")
if pd.isna(value):
return None
return int(value)
def _note_score(score: float | None, prefix: str = "score") -> str:
if score is None or pd.isna(score):
return prefix
return f"{prefix}={float(score):.3f}"
def _group_rows(image_rows: pd.DataFrame, top_n: int) -> list[dict[str, Any]]:
return image_rows.sort_values(["y_score", "visual_priority"], ascending=[False, False]).head(top_n).to_dict("records")
def _select_smear_story_objects(image_objects: pd.DataFrame, variant_index: int) -> list[dict[str, Any]]:
positive = _take_rows(
image_objects.loc[image_objects["target_binary"].eq(1)].copy(),
n_items=min(2, int(image_objects["target_binary"].sum())),
variant_index=variant_index,
sort_columns=["visual_priority", "bbox_area_px", "object_id_within_image"],
ascending=[False, False, True],
unique_columns=None,
)
negative = _take_rows(
image_objects.loc[image_objects["target_binary"].eq(0)].copy(),
n_items=min(4, max(0, len(image_objects) - len(positive))),
variant_index=variant_index + 1,
sort_columns=["visual_priority", "bbox_area_px", "object_id_within_image"],
ascending=[False, False, True],
unique_columns=None,
extra_distinct_column="class_name",
)
selected = pd.concat([positive, negative], ignore_index=True)
if len(selected) < min(6, len(image_objects)):
remaining = image_objects.loc[
~image_objects["object_id_within_image"].isin(selected["object_id_within_image"].tolist())
].copy()
filler = _take_rows(
remaining,
n_items=min(6, len(image_objects)) - len(selected),
variant_index=variant_index,
sort_columns=["visual_priority", "bbox_area_px", "object_id_within_image"],
ascending=[False, False, True],
unique_columns=None,
)
selected = pd.concat([selected, filler], ignore_index=True)
selected = selected.head(min(6, len(image_objects))).copy()
return selected.to_dict("records")
def _build_a01_f1_variants(variant_count: int) -> list[dict[str, Any]]:
object_roi_df = load_object_roi_frame()
class_plan = [
"blast",
"Lymphocyte",
"Monocyte",
"Myelocyte",
"Segmentonuclear neutrophil",
"normoblasts: oxyfilny" if int(object_roi_df["class_name"].eq("normoblasts: oxyfilny").sum()) >= 4 else "Normoblasts",
]
variants: list[dict[str, Any]] = []
for variant_index in range(1, variant_count + 1):
panel_rows: list[dict[str, Any]] = []
class_rows: list[dict[str, Any]] = []
for row_index, class_name in enumerate(class_plan, start=1):
selected = _take_rows(
object_roi_df.loc[object_roi_df["class_name"].eq(class_name)].copy(),
n_items=4,
variant_index=variant_index + row_index - 1,
sort_columns=["visual_priority", "bbox_area_px", "patient_id", "image_id", "object_id_within_image"],
ascending=[False, False, True, True, True],
unique_columns=("patient_id", "image_id"),
)
records = selected.to_dict("records")
class_rows.append({"class_name": class_name, "rows": records})
for col_index, row in enumerate(records, start=1):
panel_rows.append(
_panel_row(
panel_id=f"r{row_index:02d}c{col_index:02d}",
role="bbox_roi",
patient_id=str(row["patient_id"]),
image_id=str(row["image_id"]),
object_id_within_image=int(row["object_id_within_image"]),
class_name=str(row["class_name"]),
target_binary=_target_binary(row),
split_name=str(row.get("split_name") or ""),
source_table="roi_manifest.parquet",
source_path=str(row["bbox_crop_path"]),
notes="class gallery bbox crop",
)
)
variants.append(
{
**_base_spec(
article_id="A01",
figure_id="A01-F1",
figure_basename="a01_subject_classes_gallery",
figure_kind="a01_f1_class_gallery",
hypothesis="H1",
title="A01-F1. Object classes in the blast versus non-blast task",
variant_index=variant_index,
),
"class_rows": class_rows,
"panel_rows": panel_rows,
}
)
return variants
def _build_a01_f2_variants(variant_count: int) -> list[dict[str, Any]]:
object_roi_df = load_object_roi_frame()
image_candidates = (
object_roi_df.groupby(["patient_id", "image_id", "image_path"], as_index=False)
.agg(
n_objects=("object_id_within_image", "size"),
n_blasts=("target_binary", "sum"),
class_diversity=("class_name", "nunique"),
mean_priority=("visual_priority", "mean"),
)
)
image_candidates["n_non_blasts"] = image_candidates["n_objects"] - image_candidates["n_blasts"]
image_candidates["readability_penalty"] = (image_candidates["n_objects"] - 8).abs()
image_candidates = image_candidates.loc[
image_candidates["n_objects"].between(6, 18)
& image_candidates["n_blasts"].ge(1)
& image_candidates["n_non_blasts"].ge(2)
].copy()
variants: list[dict[str, Any]] = []
for variant_index in range(1, variant_count + 1):
image_row = _pick_single_row(
image_candidates,
variant_index=variant_index,
sort_columns=["class_diversity", "readability_penalty", "mean_priority", "patient_id", "image_id"],
ascending=[False, True, False, True, True],
dedupe_columns=["patient_id", "image_id"],
)
image_objects = object_roi_df.loc[
object_roi_df["patient_id"].eq(image_row["patient_id"]) & object_roi_df["image_id"].eq(image_row["image_id"])
].copy()
selected_objects = _select_smear_story_objects(image_objects, variant_index=variant_index)
panel_rows = [
_panel_row(
panel_id="A",
role="smear_image",
patient_id=str(image_row["patient_id"]),
image_id=str(image_row["image_id"]),
target_binary=None,
split_name="",
source_table="image_index.parquet",
source_path=str(image_row["image_path"]),
notes="original smear image",
),
_panel_row(
panel_id="B",
role="smear_with_boxes",
patient_id=str(image_row["patient_id"]),
image_id=str(image_row["image_id"]),
target_binary=None,
split_name="",
source_table="roi_manifest.parquet",
source_path=str(image_row["image_path"]),
notes="smear with selected bounding boxes",
),
]
for slot, row in enumerate(selected_objects, start=1):
panel_rows.append(
_panel_row(
panel_id=f"C{slot}",
role="bbox_roi",
patient_id=str(row["patient_id"]),
image_id=str(row["image_id"]),
object_id_within_image=int(row["object_id_within_image"]),
class_name=str(row["class_name"]),
target_binary=_target_binary(row),
split_name=str(row.get("split_name") or ""),
source_table="roi_manifest.parquet",
source_path=str(row["bbox_crop_path"]),
notes=f"selected ROI #{slot}",
)
)
variants.append(
{
**_base_spec(
article_id="A01",
figure_id="A01-F2",
figure_basename="a01_from_smear_to_objects",
figure_kind="a01_f2_smear_to_objects",
hypothesis="H1",
title="A01-F2. From a smear image to object-level cells",
variant_index=variant_index,
),
"image_row": image_row,
"object_rows": selected_objects,
"panel_rows": panel_rows,
}
)
return variants
def _build_a01_f3_variants(variant_count: int) -> list[dict[str, Any]]:
h2_df = load_h2_subject_frame(split_name="test")
full_df = h2_df.loc[h2_df["task_name"].eq("a43_full_realistic_binary")].copy()
pivot_df = full_df.pivot_table(
index=[
"patient_id",
"image_id",
"object_id_within_image",
"class_name",
"split_name",
"target_binary",
"bbox_crop_path",
"mask_crop_path",
"mask_tight_crop_path",
"bbox_area_px",
"visual_priority",
],
columns="label",
values=["y_score", "y_pred"],
aggfunc="first",
)
pivot_df.columns = [f"{label}_{value_name}" for value_name, label in pivot_df.columns]
pivot_df = pivot_df.reset_index()
pivot_df = pivot_df.dropna(subset=["bbox_crop_path", "mask_crop_path", "mask_tight_crop_path"]).copy()
pivot_df["score_spread"] = pivot_df[
["bbox_only_y_score", "mask_only_y_score", "bbox_plus_mask_y_score"]
].max(axis=1) - pivot_df[
["bbox_only_y_score", "mask_only_y_score", "bbox_plus_mask_y_score"]
].min(axis=1)
pivot_df["combined_gain"] = pivot_df["bbox_plus_mask_y_score"] - pivot_df["bbox_only_y_score"]
variants: list[dict[str, Any]] = []
for variant_index in range(1, variant_count + 1):
positive = _take_rows(
pivot_df.loc[pivot_df["target_binary"].eq(1)].copy(),
n_items=3,
variant_index=variant_index,
sort_columns=["score_spread", "combined_gain", "visual_priority", "patient_id", "image_id", "object_id_within_image"],
ascending=[False, False, False, True, True, True],
unique_columns=("patient_id", "image_id"),
)
negative = _take_rows(
pivot_df.loc[pivot_df["target_binary"].eq(0)].copy(),
n_items=3,
variant_index=variant_index + 1,
sort_columns=["score_spread", "combined_gain", "visual_priority", "patient_id", "image_id", "object_id_within_image"],
ascending=[False, False, False, True, True, True],
unique_columns=("patient_id", "image_id"),
extra_distinct_column="class_name",
)
selected = pd.concat([positive, negative], ignore_index=True)
records = selected.to_dict("records")
panel_rows: list[dict[str, Any]] = []
for row_index, row in enumerate(records, start=1):
for label, source_column in [
("bbox", "bbox_crop_path"),
("mask", "mask_crop_path"),
("mask_tight", "mask_tight_crop_path"),
]:
panel_rows.append(
_panel_row(
panel_id=f"r{row_index:02d}_{label}",
role=label,
patient_id=str(row["patient_id"]),
image_id=str(row["image_id"]),
object_id_within_image=int(row["object_id_within_image"]),
class_name=str(row["class_name"]),
target_binary=_target_binary(row),
split_name=str(row.get("split_name") or ""),
source_table="h2_mask_usage_predictions.parquet",
source_path=str(row[source_column]),
notes=(
f"bbox={float(row['bbox_only_y_score']):.3f}; "
f"mask={float(row['mask_only_y_score']):.3f}; "
f"combined={float(row['bbox_plus_mask_y_score']):.3f}"
),
)
)
variants.append(
{
**_base_spec(
article_id="A01",
figure_id="A01-F3",
figure_basename="a01_mask_representations_comparison",
figure_kind="a01_f3_mask_representations",
hypothesis="H2",
title="A01-F3. Bounding-box, mask, and mask-tight cell views",
variant_index=variant_index,
),
"object_rows": records,
"panel_rows": panel_rows,
}
)
return variants
def _build_a01_f4_variants(variant_count: int) -> list[dict[str, Any]]:
subset_frames = load_h3_subject_frames()
clean_df = subset_frames["clean_cell"].copy()
strict_df = subset_frames["strict_morphology"].copy()
full_df = subset_frames["full_realistic"].copy()
strict_keys = set(map(tuple, strict_df[OBJECT_KEY_COLUMNS].itertuples(index=False, name=None)))
clean_only_df = clean_df.loc[
[tuple(values) not in strict_keys for values in clean_df[OBJECT_KEY_COLUMNS].itertuples(index=False, name=None)]
].copy()
gumpricht_df = clean_only_df.loc[clean_only_df["class_name"].eq("gumpricht shadows")].copy()
variants: list[dict[str, Any]] = []
for variant_index in range(1, variant_count + 1):
clean_reference_rows: list[dict[str, Any]] = []
for offset, class_name in enumerate(["blast", "Lymphocyte", "Monocyte", "Myelocyte"], start=0):
selected = _take_rows(
clean_df.loc[clean_df["class_name"].eq(class_name)].copy(),
n_items=1,
variant_index=variant_index + offset,
sort_columns=["visual_priority", "bbox_area_px", "patient_id", "image_id", "object_id_within_image"],
ascending=[False, False, True, True, True],
unique_columns=("patient_id", "image_id"),
)
clean_reference_rows.extend(selected.to_dict("records"))
gumpricht_rows = _take_rows(
gumpricht_df,
n_items=4,
variant_index=variant_index,
sort_columns=["visual_priority", "bbox_area_px", "patient_id", "image_id", "object_id_within_image"],
ascending=[False, False, True, True, True],
unique_columns=("patient_id", "image_id"),
).to_dict("records")
noise_rows = _take_rows(
full_df.loc[full_df["class_name"].isin(["Bad cells", "artifacts"])].copy(),
n_items=2,
variant_index=variant_index,
sort_columns=["visual_priority", "bbox_area_px", "patient_id", "image_id", "object_id_within_image"],
ascending=[False, False, True, True, True],
unique_columns=("patient_id", "image_id"),
).to_dict("records")
hard_non_blast_rows = _take_rows(
full_df.loc[
full_df["class_name"].isin(
[
"Lymphocyte",
"Monocyte",
"Myelocyte",
"Segmentonuclear neutrophil",
"Normoblasts",
"normoblasts: oxyfilny",
]
)
].copy(),
n_items=2,
variant_index=variant_index + 2,
sort_columns=["visual_priority", "bbox_area_px", "patient_id", "image_id", "object_id_within_image"],
ascending=[False, False, True, True, True],
unique_columns=("patient_id", "image_id"),
extra_distinct_column="class_name",
).to_dict("records")
full_problem_rows = (noise_rows + hard_non_blast_rows)[:4]
grid_rows = [
{
"clean": clean_reference_rows[index],
"strict_removed": gumpricht_rows[index],
"full_problem": full_problem_rows[index],
}
for index in range(4)
]
panel_rows: list[dict[str, Any]] = []
for row_index, row_group in enumerate(grid_rows, start=1):
for column_index, (role, row) in enumerate(row_group.items(), start=1):
panel_rows.append(
_panel_row(
panel_id=f"r{row_index:02d}c{column_index:02d}",
role=role,
patient_id=str(row["patient_id"]),
image_id=str(row["image_id"]),
object_id_within_image=int(row["object_id_within_image"]),
class_name=str(row["class_name"]),
target_binary=_target_binary(row),
split_name=str(row.get("split_name") or ""),
source_table="roi_manifest.parquet",
source_path=str(row["bbox_crop_path"]),
notes=role.replace("_", " "),
)
)
variants.append(
{
**_base_spec(
article_id="A01",
figure_id="A01-F4",
figure_basename="a01_dataset_regimes_subject_gallery",
figure_kind="a01_f4_dataset_regimes",
hypothesis="H3",
title="A01-F4. Subject differences between clean, strict, and full-realistic corpora",
variant_index=variant_index,
),
"grid_rows": grid_rows,
"panel_rows": panel_rows,
}
)
return variants
def _build_a01_f5_variants(variant_count: int) -> list[dict[str, Any]]:
_, baseline_df = load_best_baseline_subject_frame(split_name="test")
baseline_df = baseline_df.copy()
baseline_df["decision_margin"] = (baseline_df["y_score"] - baseline_df["threshold"]).abs()
outcome_sort = {
"TP": (["decision_margin", "y_score", "visual_priority", "patient_id", "image_id", "object_id_within_image"], [False, False, False, True, True, True]),
"TN": (["decision_margin", "y_score", "visual_priority", "patient_id", "image_id", "object_id_within_image"], [False, True, False, True, True, True]),
"FP": (["decision_margin", "y_score", "visual_priority", "patient_id", "image_id", "object_id_within_image"], [False, False, False, True, True, True]),
"FN": (["decision_margin", "y_score", "visual_priority", "patient_id", "image_id", "object_id_within_image"], [False, True, False, True, True, True]),
}
variants: list[dict[str, Any]] = []
for variant_index in range(1, variant_count + 1):
outcome_rows: dict[str, list[dict[str, Any]]] = {}
panel_rows: list[dict[str, Any]] = []
for row_index, outcome_label in enumerate(["TP", "TN", "FP", "FN"], start=1):
sort_columns, ascending = outcome_sort[outcome_label]
selected = _take_rows(
baseline_df.loc[baseline_df["outcome_label"].eq(outcome_label)].copy(),
n_items=4,
variant_index=variant_index + row_index - 1,
sort_columns=sort_columns,
ascending=ascending,
unique_columns=("patient_id", "image_id"),
extra_distinct_column="class_name" if outcome_label in {"TN", "FP"} else None,
)
records = selected.to_dict("records")
outcome_rows[outcome_label] = records
for col_index, row in enumerate(records, start=1):
panel_rows.append(
_panel_row(
panel_id=f"{outcome_label}{col_index}",
role=outcome_label,
patient_id=str(row["patient_id"]),
image_id=str(row["image_id"]),
object_id_within_image=int(row["object_id_within_image"]),
class_name=str(row["class_name"]),
target_binary=_target_binary(row),
split_name=str(row.get("split_name") or ""),
source_table="object_level_baseline_predictions.parquet",
source_path=str(row["bbox_crop_path"]),
notes=(
f"true={row['true_label']}; pred={row['predicted_label']}; "
f"score={float(row['y_score']):.3f}"
),
)
)
variants.append(
{
**_base_spec(
article_id="A01",
figure_id="A01-F5",
figure_basename="a01_best_handcrafted_tp_tn_fp_fn",
figure_kind="a01_f5_confusion_gallery",
hypothesis="H1",
title="A01-F5. True and false decisions of the best handcrafted model",
variant_index=variant_index,
),
"outcome_rows": outcome_rows,
"panel_rows": panel_rows,
}
)
return variants
def _build_a01_f6_variants(variant_count: int) -> list[dict[str, Any]]:
frame = load_h5_subject_frame(split_name="test").copy()
score_columns = [
"handcrafted_y_score",
"cnn_bbox_y_score",
"cnn_mask_y_score",
"cnn_late_fusion_y_score",
]
pred_columns = [
"handcrafted_y_pred",
"cnn_bbox_y_pred",
"cnn_mask_y_pred",
"cnn_late_fusion_y_pred",
]
frame["score_spread"] = frame[score_columns].max(axis=1) - frame[score_columns].min(axis=1)
frame["prediction_disagreement"] = frame[pred_columns].nunique(axis=1)
frame["handcrafted_margin"] = (frame["handcrafted_y_score"] - 0.5).abs()
variants: list[dict[str, Any]] = []
for variant_index in range(1, variant_count + 1):
confident_positive = _take_rows(
frame.loc[frame["y_true"].eq(1) & frame["handcrafted_y_pred"].eq(frame["y_true"])].copy(),
n_items=2,
variant_index=variant_index,
sort_columns=["handcrafted_margin", "visual_priority", "patient_id", "image_id", "object_id_within_image"],
ascending=[False, False, True, True, True],
unique_columns=("patient_id", "image_id"),
)
confident_negative = _take_rows(
frame.loc[frame["y_true"].eq(0) & frame["handcrafted_y_pred"].eq(frame["y_true"])].copy(),
n_items=2,
variant_index=variant_index + 1,
sort_columns=["handcrafted_margin", "visual_priority", "patient_id", "image_id", "object_id_within_image"],
ascending=[False, False, True, True, True],
unique_columns=("patient_id", "image_id"),
extra_distinct_column="class_name",
)
disagreement_positive = _take_rows(
frame.loc[frame["y_true"].eq(1) & frame["prediction_disagreement"].gt(1)].copy(),
n_items=2,
variant_index=variant_index,
sort_columns=["prediction_disagreement", "score_spread", "visual_priority", "patient_id", "image_id", "object_id_within_image"],
ascending=[False, False, False, True, True, True],
unique_columns=("patient_id", "image_id"),
)
disagreement_negative = _take_rows(
frame.loc[frame["y_true"].eq(0) & frame["prediction_disagreement"].gt(1)].copy(),
n_items=2,
variant_index=variant_index + 2,
sort_columns=["prediction_disagreement", "score_spread", "visual_priority", "patient_id", "image_id", "object_id_within_image"],
ascending=[False, False, False, True, True, True],
unique_columns=("patient_id", "image_id"),
extra_distinct_column="class_name",
)
confident_rows = pd.concat([confident_positive, confident_negative], ignore_index=True).to_dict("records")
disagreement_rows = pd.concat([disagreement_positive, disagreement_negative], ignore_index=True).to_dict("records")
panel_rows: list[dict[str, Any]] = []
for row_index, row in enumerate(confident_rows, start=1):
panel_rows.append(
_panel_row(
panel_id=f"confident_{row_index}",
role="confident",
patient_id=str(row["patient_id"]),
image_id=str(row["image_id"]),
object_id_within_image=int(row["object_id_within_image"]),
class_name=str(row["class_name"]),
target_binary=_target_binary(row),
split_name=str(row.get("split_name") or ""),
source_table="object_level_baseline_predictions.parquet",
source_path=str(row["bbox_crop_path"]),
notes=(
f"hcf={float(row['handcrafted_y_score']):.3f}; "
f"bbox={float(row['cnn_bbox_y_score']):.3f}; "
f"mask={float(row['cnn_mask_y_score']):.3f}; "
f"fusion={float(row['cnn_late_fusion_y_score']):.3f}"
),
)
)
for row_index, row in enumerate(disagreement_rows, start=1):
panel_rows.append(
_panel_row(
panel_id=f"disagreement_{row_index}",
role="disagreement",
patient_id=str(row["patient_id"]),
image_id=str(row["image_id"]),
object_id_within_image=int(row["object_id_within_image"]),
class_name=str(row["class_name"]),
target_binary=_target_binary(row),
split_name=str(row.get("split_name") or ""),
source_table="object_level_deep_predictions.parquet",
source_path=str(row["bbox_crop_path"]),
notes=(
f"hcf={float(row['handcrafted_y_score']):.3f}; "
f"bbox={float(row['cnn_bbox_y_score']):.3f}; "
f"mask={float(row['cnn_mask_y_score']):.3f}; "
f"fusion={float(row['cnn_late_fusion_y_score']):.3f}"
),
)
)
variants.append(
{
**_base_spec(
article_id="A01",
figure_id="A01-F6",
figure_basename="a01_handcrafted_vs_deep_same_cells",
figure_kind="a01_f6_same_cells_model_comparison",
hypothesis="H5",
title="A01-F6. Handcrafted and deep scores on the same test cells",
variant_index=variant_index,
),
"confident_rows": confident_rows,
"disagreement_rows": disagreement_rows,
"panel_rows": panel_rows,
}
)
return variants
def _build_a02_f1_variants(variant_count: int) -> list[dict[str, Any]]:
bundle = load_h4_subject_bundle()
image_scores_df = bundle["image_scores_df"].copy()
best_prediction_frame = bundle["best_prediction_frame"].copy()
pivot = image_scores_df.pivot_table(index=["patient_id", "image_id"], columns="aggregation_method", values="score").reset_index()
image_meta = image_scores_df[
["patient_id", "image_id", "image_path", "blast_object_count", "non_blast_object_count", "n_objects"]
].drop_duplicates()
pivot = pivot.merge(image_meta, on=["patient_id", "image_id"], how="left", validate="one_to_one")
pivot["method_spread"] = pivot[AGGREGATION_METHOD_ORDER].max(axis=1) - pivot[AGGREGATION_METHOD_ORDER].min(axis=1)
pivot = pivot.loc[
pivot["n_objects"].between(6, 18)
& pivot["blast_object_count"].ge(1)
& pivot["non_blast_object_count"].ge(1)
].copy()
variants: list[dict[str, Any]] = []
for variant_index in range(1, variant_count + 1):
image_row = _pick_single_row(
pivot,
variant_index=variant_index,
sort_columns=["method_spread", "n_objects", "max_object_score", "patient_id", "image_id"],
ascending=[False, True, False, True, True],
dedupe_columns=["patient_id", "image_id"],
)
top_object_rows = _group_rows(
best_prediction_frame.loc[
best_prediction_frame["patient_id"].eq(image_row["patient_id"])
& best_prediction_frame["image_id"].eq(image_row["image_id"])
].copy(),
top_n=6,
)
method_rows = (
image_scores_df.loc[
image_scores_df["patient_id"].eq(image_row["patient_id"])
& image_scores_df["image_id"].eq(image_row["image_id"])
]
.copy()
.set_index("aggregation_method")
.reindex(AGGREGATION_METHOD_ORDER)
.reset_index()
.to_dict("records")
)
panel_rows = [
_panel_row(
panel_id="A",
role="smear_image",
patient_id=str(image_row["patient_id"]),
image_id=str(image_row["image_id"]),
split_name="test",
source_table="image_index.parquet",
source_path=str(image_row["image_path"]),
notes="original smear image",
),
_panel_row(
panel_id="B",
role="smear_with_boxes",
patient_id=str(image_row["patient_id"]),
image_id=str(image_row["image_id"]),
split_name="test",
source_table="object_level_baseline_predictions.parquet",
source_path=str(image_row["image_path"]),
notes="test image with top scored boxes",
),
]
for slot, row in enumerate(top_object_rows, start=1):
panel_rows.append(
_panel_row(
panel_id=f"C{slot}",
role="top_object",
patient_id=str(row["patient_id"]),
image_id=str(row["image_id"]),
object_id_within_image=int(row["object_id_within_image"]),
class_name=str(row["class_name"]),
target_binary=_target_binary(row),
split_name=str(row.get("split_name") or "test"),
source_table="object_level_baseline_predictions.parquet",
source_path=str(row["bbox_crop_path"]),
notes=_note_score(float(row["y_score"])),
)
)
for method_row in method_rows:
panel_rows.append(
_panel_row(
panel_id=f"D_{method_row['aggregation_method']}",
role="aggregation_score",
patient_id=str(method_row["patient_id"]),
image_id=str(method_row["image_id"]),
split_name="test",
source_table="image_level_scores.parquet",
source_path="image_level_scores.parquet",
notes=f"{method_row['aggregation_method']}={float(method_row['score']):.3f}",
)
)
variants.append(
{
**_base_spec(
article_id="A02",
figure_id="A02-F1",
figure_basename="a02_from_cells_to_image_score",
figure_kind="a02_f1_cells_to_image_score",
hypothesis="H4",
title="A02-F1. From cell scores to an image-level score",
variant_index=variant_index,
),
"image_row": image_row,
"top_object_rows": top_object_rows,
"method_rows": method_rows,
"panel_rows": panel_rows,
}
)
return variants
def _image_case_outcomes(image_scores_df: pd.DataFrame, best_method: str) -> pd.DataFrame:
frame = image_scores_df.loc[image_scores_df["aggregation_method"].eq(best_method)].copy()
frame["y_true"] = frame["image_contains_blast"].astype(int)
frame["y_pred"] = frame["score"].ge(0.5).astype(int)
mapping = {(1, 1): "TP", (0, 0): "TN", (0, 1): "FP", (1, 0): "FN"}
frame["outcome_label"] = frame.apply(lambda row: mapping[(int(row["y_true"]), int(row["y_pred"]))], axis=1)
return frame
def _build_a02_f2_variants(variant_count: int) -> list[dict[str, Any]]:
bundle = load_h4_subject_bundle()
image_scores_df = bundle["image_scores_df"].copy()
best_prediction_frame = bundle["best_prediction_frame"].copy()
best_method = best_image_aggregation_method()
case_df = _image_case_outcomes(image_scores_df, best_method).copy()
outcome_sort = {
"TP": (["score", "n_objects", "patient_id", "image_id"], [False, True, True, True]),
"TN": (["score", "n_objects", "patient_id", "image_id"], [True, True, True, True]),
"FP": (["score", "n_objects", "patient_id", "image_id"], [False, True, True, True]),
"FN": (["score", "n_objects", "patient_id", "image_id"], [True, True, True, True]),
}
variants: list[dict[str, Any]] = []
for variant_index in range(1, variant_count + 1):
cases: list[dict[str, Any]] = []
panel_rows: list[dict[str, Any]] = []
for outcome_label in ["TP", "TN", "FP", "FN"]:
sort_columns, ascending = outcome_sort[outcome_label]
selected_case = _pick_single_row(
case_df.loc[case_df["outcome_label"].eq(outcome_label)].copy(),
variant_index=variant_index,
sort_columns=sort_columns,
ascending=ascending,
dedupe_columns=["patient_id", "image_id"],
)
top_objects = _group_rows(
best_prediction_frame.loc[
best_prediction_frame["patient_id"].eq(selected_case["patient_id"])
& best_prediction_frame["image_id"].eq(selected_case["image_id"])
].copy(),
top_n=3,
)
cases.append({"outcome_label": outcome_label, "image_row": selected_case, "top_object_rows": top_objects})
panel_rows.append(
_panel_row(
panel_id=f"{outcome_label}_image",
role="case_image",
patient_id=str(selected_case["patient_id"]),
image_id=str(selected_case["image_id"]),
target_binary=int(selected_case["image_contains_blast"]),
split_name="test",
source_table="image_level_scores.parquet",
source_path=str(selected_case["image_path"]),
notes=f"{outcome_label}; {best_method}={float(selected_case['score']):.3f}",
)
)
for slot, row in enumerate(top_objects, start=1):
panel_rows.append(
_panel_row(
panel_id=f"{outcome_label}_roi_{slot}",
role="case_object",
patient_id=str(row["patient_id"]),
image_id=str(row["image_id"]),
object_id_within_image=int(row["object_id_within_image"]),
class_name=str(row["class_name"]),
target_binary=_target_binary(row),
split_name=str(row.get("split_name") or "test"),
source_table="object_level_baseline_predictions.parquet",
source_path=str(row["bbox_crop_path"]),
notes=_note_score(float(row["y_score"])),
)
)
variants.append(
{
**_base_spec(
article_id="A02",
figure_id="A02-F2",
figure_basename="a02_image_level_case_studies",
figure_kind="a02_f2_image_case_studies",
hypothesis="H4",
title="A02-F2. Image-level true and false cases",
variant_index=variant_index,
),
"best_method": best_method,
"cases": cases,
"panel_rows": panel_rows,
}
)
return variants
def _build_a02_f3_variants(variant_count: int) -> list[dict[str, Any]]:
bundle = load_h4_subject_bundle()
image_scores_df = bundle["image_scores_df"].copy()
best_prediction_frame = bundle["best_prediction_frame"].copy()
pivot = image_scores_df.pivot_table(index=["patient_id", "image_id"], columns="aggregation_method", values="score").reset_index()
image_meta = image_scores_df[
["patient_id", "image_id", "image_path", "blast_object_count", "non_blast_object_count", "n_objects"]
].drop_duplicates()
pivot = pivot.merge(image_meta, on=["patient_id", "image_id"], how="left", validate="one_to_one")
pivot["method_spread"] = pivot[AGGREGATION_METHOD_ORDER].max(axis=1) - pivot[AGGREGATION_METHOD_ORDER].min(axis=1)
pivot = pivot.loc[pivot["n_objects"].between(6, 18)].copy()
variants: list[dict[str, Any]] = []
for variant_index in range(1, variant_count + 1):
image_row = _pick_single_row(
pivot,
variant_index=variant_index + 1,
sort_columns=["method_spread", "n_objects", "patient_id", "image_id"],
ascending=[False, True, True, True],
dedupe_columns=["patient_id", "image_id"],
)
top_object_rows = _group_rows(
best_prediction_frame.loc[
best_prediction_frame["patient_id"].eq(image_row["patient_id"])
& best_prediction_frame["image_id"].eq(image_row["image_id"])
].copy(),
top_n=4,
)
method_rows = (
image_scores_df.loc[
image_scores_df["patient_id"].eq(image_row["patient_id"])
& image_scores_df["image_id"].eq(image_row["image_id"])
]
.copy()
.set_index("aggregation_method")
.reindex(AGGREGATION_METHOD_ORDER)
.reset_index()
.to_dict("records")
)
panel_rows = [
_panel_row(
panel_id="A",
role="smear_with_boxes",
patient_id=str(image_row["patient_id"]),
image_id=str(image_row["image_id"]),
split_name="test",
source_table="object_level_baseline_predictions.parquet",
source_path=str(image_row["image_path"]),
notes="same image for four aggregation methods",
)
]
for slot, row in enumerate(top_object_rows, start=1):
panel_rows.append(
_panel_row(
panel_id=f"C{slot}",
role="top_object",
patient_id=str(row["patient_id"]),
image_id=str(row["image_id"]),
object_id_within_image=int(row["object_id_within_image"]),
class_name=str(row["class_name"]),
target_binary=_target_binary(row),
split_name=str(row.get("split_name") or "test"),
source_table="object_level_baseline_predictions.parquet",
source_path=str(row["bbox_crop_path"]),
notes=_note_score(float(row["y_score"])),
)
)
for method_row in method_rows:
panel_rows.append(
_panel_row(
panel_id=f"D_{method_row['aggregation_method']}",
role="aggregation_score",
patient_id=str(method_row["patient_id"]),
image_id=str(method_row["image_id"]),
split_name="test",
source_table="image_level_scores.parquet",
source_path="image_level_scores.parquet",
notes=f"{method_row['aggregation_method']}={float(method_row['score']):.3f}",
)
)
variants.append(
{
**_base_spec(
article_id="A02",
figure_id="A02-F3",
figure_basename="a02_four_aggregation_methods_same_image",
figure_kind="a02_f3_same_image_four_aggregators",
hypothesis="H4",
title="A02-F3. One image under four aggregation methods",
variant_index=variant_index,
),
"image_row": image_row,
"top_object_rows": top_object_rows,
"method_rows": method_rows,
"panel_rows": panel_rows,
}
)
return variants
def _best_image_method_rows(bundle: dict[str, Any], patient_id: str) -> pd.DataFrame:
best_method = best_image_aggregation_method()
image_scores_df = bundle["image_scores_df"].copy()
return image_scores_df.loc[
image_scores_df["patient_id"].eq(patient_id) & image_scores_df["aggregation_method"].eq(best_method)
].copy()
def _top_patient_images(bundle: dict[str, Any], patient_id: str, max_images: int) -> list[dict[str, Any]]:
patient_images = _best_image_method_rows(bundle, patient_id).sort_values(
["score", "n_objects", "image_id"], ascending=[False, False, True]
)
if len(patient_images) >= 4:
selected = patient_images.head(min(max_images, 4))
else:
selected = patient_images.head(max_images)
return selected.to_dict("records")
def _build_a02_f4_variants(variant_count: int) -> list[dict[str, Any]]:
bundle = load_h4_subject_bundle()
patient_scores_df = bundle["patient_scores_df"].copy()
best_prediction_frame = bundle["best_prediction_frame"].copy()
best_patient_method = best_patient_aggregation_method()
patient_candidates = patient_scores_df.loc[
patient_scores_df["aggregation_method"].eq(best_patient_method) & patient_scores_df["patient_contains_blast"].eq(1)
].copy()
best_image_method = best_image_aggregation_method()
image_counts = (
bundle["image_scores_df"]
.loc[bundle["image_scores_df"]["aggregation_method"].eq(best_image_method)]
.groupby("patient_id", as_index=False)
.agg(n_images=("image_id", "size"), max_image_score=("score", "max"))
)
patient_candidates = patient_candidates.merge(image_counts, on="patient_id", how="left", validate="one_to_one")
patient_candidates = patient_candidates.loc[patient_candidates["n_images"].between(3, 25)].copy()
variants: list[dict[str, Any]] = []
for variant_index in range(1, variant_count + 1):
patient_row = _pick_single_row(
patient_candidates,
variant_index=variant_index,
sort_columns=["score", "n_images", "patient_id"],
ascending=[False, True, True],
dedupe_columns=["patient_id"],
)
image_rows = _top_patient_images(bundle, str(patient_row["patient_id"]), max_images=4)
image_panels: list[dict[str, Any]] = []
panel_rows: list[dict[str, Any]] = []
for image_slot, image_row in enumerate(image_rows, start=1):
top_objects = _group_rows(
best_prediction_frame.loc[
best_prediction_frame["patient_id"].eq(image_row["patient_id"])
& best_prediction_frame["image_id"].eq(image_row["image_id"])
].copy(),
top_n=2,
)
image_panels.append({"image_row": image_row, "top_object_rows": top_objects})
panel_rows.append(
_panel_row(
panel_id=f"image_{image_slot}",
role="patient_image",
patient_id=str(image_row["patient_id"]),
image_id=str(image_row["image_id"]),
target_binary=int(image_row["image_contains_blast"]),
split_name="test",
source_table="image_level_scores.parquet",
source_path=str(image_row["image_path"]),
notes=f"{best_image_method}={float(image_row['score']):.3f}",
)
)
for roi_slot, row in enumerate(top_objects, start=1):
panel_rows.append(
_panel_row(
panel_id=f"image_{image_slot}_roi_{roi_slot}",
role="patient_image_object",
patient_id=str(row["patient_id"]),
image_id=str(row["image_id"]),
object_id_within_image=int(row["object_id_within_image"]),
class_name=str(row["class_name"]),
target_binary=_target_binary(row),
split_name=str(row.get("split_name") or "test"),
source_table="object_level_baseline_predictions.parquet",
source_path=str(row["bbox_crop_path"]),
notes=_note_score(float(row["y_score"])),
)
)
panel_rows.append(
_panel_row(
panel_id="patient_score",
role="patient_score",
patient_id=str(patient_row["patient_id"]),
split_name="test",
source_table="patient_level_scores.parquet",
source_path="patient_level_scores.parquet",
notes=f"{best_patient_method}={float(patient_row['score']):.3f}",
)
)
variants.append(
{
**_base_spec(
article_id="A02",
figure_id="A02-F4",
figure_basename="a02_from_images_to_patient_score",
figure_kind="a02_f4_images_to_patient_score",
hypothesis="H4",
title="A02-F4. From image-level scores to a patient-level score",
variant_index=variant_index,
),
"patient_row": patient_row,
"best_image_method": best_image_method,
"best_patient_method": best_patient_method,
"image_panels": image_panels,
"panel_rows": panel_rows,
}
)
return variants
def _patient_case_outcomes(patient_scores_df: pd.DataFrame, best_method: str) -> pd.DataFrame:
frame = patient_scores_df.loc[patient_scores_df["aggregation_method"].eq(best_method)].copy()
frame["y_true"] = frame["patient_contains_blast"].astype(int)
frame["y_pred"] = frame["score"].ge(0.5).astype(int)
mapping = {(1, 1): "TP", (0, 0): "TN", (0, 1): "FP", (1, 0): "FN"}
frame["outcome_label"] = frame.apply(lambda row: mapping[(int(row["y_true"]), int(row["y_pred"]))], axis=1)
return frame
def _build_a02_f5_variants(variant_count: int) -> list[dict[str, Any]]:
bundle = load_h4_subject_bundle()
patient_scores_df = bundle["patient_scores_df"].copy()
best_prediction_frame = bundle["best_prediction_frame"].copy()
best_patient_method = best_patient_aggregation_method()
best_image_method = best_image_aggregation_method()
patient_cases = _patient_case_outcomes(patient_scores_df, best_patient_method)
variants: list[dict[str, Any]] = []
for variant_index in range(1, variant_count + 1):
tp_row = _pick_single_row(
patient_cases.loc[patient_cases["outcome_label"].eq("TP")].copy(),
variant_index=variant_index,
sort_columns=["score", "n_objects", "patient_id"],
ascending=[False, False, True],
dedupe_columns=["patient_id"],
)
fp_row = _pick_single_row(
patient_cases.loc[patient_cases["outcome_label"].eq("FP")].copy(),
variant_index=variant_index,
sort_columns=["score", "n_objects", "patient_id"],
ascending=[False, False, True],
dedupe_columns=["patient_id"],
)
case_panels: list[dict[str, Any]] = []
panel_rows: list[dict[str, Any]] = []
for case_label, patient_row in [("TP", tp_row), ("FP", fp_row)]:
image_rows = _top_patient_images(bundle, str(patient_row["patient_id"]), max_images=3)
image_panels: list[dict[str, Any]] = []
for image_slot, image_row in enumerate(image_rows, start=1):
top_objects = _group_rows(
best_prediction_frame.loc[
best_prediction_frame["patient_id"].eq(image_row["patient_id"])
& best_prediction_frame["image_id"].eq(image_row["image_id"])
].copy(),
top_n=2,
)
image_panels.append({"image_row": image_row, "top_object_rows": top_objects})
panel_rows.append(
_panel_row(
panel_id=f"{case_label}_image_{image_slot}",
role=f"{case_label.lower()}_patient_image",
patient_id=str(image_row["patient_id"]),
image_id=str(image_row["image_id"]),
target_binary=int(image_row["image_contains_blast"]),
split_name="test",
source_table="image_level_scores.parquet",
source_path=str(image_row["image_path"]),
notes=f"{best_image_method}={float(image_row['score']):.3f}",
)
)
for roi_slot, row in enumerate(top_objects, start=1):
panel_rows.append(
_panel_row(
panel_id=f"{case_label}_image_{image_slot}_roi_{roi_slot}",
role=f"{case_label.lower()}_patient_object",
patient_id=str(row["patient_id"]),
image_id=str(row["image_id"]),
object_id_within_image=int(row["object_id_within_image"]),
class_name=str(row["class_name"]),
target_binary=_target_binary(row),
split_name=str(row.get("split_name") or "test"),
source_table="object_level_baseline_predictions.parquet",
source_path=str(row["bbox_crop_path"]),
notes=_note_score(float(row["y_score"])),
)
)
panel_rows.append(
_panel_row(
panel_id=f"{case_label}_patient_score",
role=f"{case_label.lower()}_patient_score",
patient_id=str(patient_row["patient_id"]),
split_name="test",
source_table="patient_level_scores.parquet",
source_path="patient_level_scores.parquet",
notes=f"{best_patient_method}={float(patient_row['score']):.3f}",
)
)
case_panels.append({"case_label": case_label, "patient_row": patient_row, "image_panels": image_panels})
variants.append(
{
**_base_spec(
article_id="A02",
figure_id="A02-F5",
figure_basename="a02_patient_level_case_studies",
figure_kind="a02_f5_patient_case_studies",
hypothesis="H4",
title="A02-F5. Patient-level true-positive and false-positive case studies",
variant_index=variant_index,
),
"best_image_method": best_image_method,
"best_patient_method": best_patient_method,
"case_panels": case_panels,
"panel_rows": panel_rows,
}
)
return variants
def build_subject_figure_specs(variant_count: int = VARIANT_COUNT) -> list[dict[str, Any]]:
specs: list[dict[str, Any]] = []
specs.extend(_build_a01_f1_variants(variant_count))
specs.extend(_build_a01_f2_variants(variant_count))
specs.extend(_build_a01_f3_variants(variant_count))
specs.extend(_build_a01_f4_variants(variant_count))
specs.extend(_build_a01_f5_variants(variant_count))
specs.extend(_build_a01_f6_variants(variant_count))
specs.extend(_build_a02_f1_variants(variant_count))
specs.extend(_build_a02_f2_variants(variant_count))
specs.extend(_build_a02_f3_variants(variant_count))
specs.extend(_build_a02_f4_variants(variant_count))
specs.extend(_build_a02_f5_variants(variant_count))
return specs
def flatten_subject_specs(specs: list[dict[str, Any]]) -> pd.DataFrame:
rows: list[dict[str, Any]] = []
for spec in specs:
for panel_row in spec.get("panel_rows", []):
rows.append(
{
"article_id": spec["article_id"],
"figure_id": spec["figure_id"],
"figure_variant_id": spec["figure_variant_id"],
"figure_basename": spec["figure_basename"],
"hypothesis": spec["hypothesis"],
"hypothesis_slug": spec["hypothesis_slug"],
"variant_index": spec["variant_index"],
"figure_kind": spec["figure_kind"],
"title": spec["title"],
**panel_row,
}
)
return pd.DataFrame(rows)