Initial commit

This commit is contained in:
2026-09-11 08:28:02 +03:00
commit 1f9b2f20bd
64 changed files with 12593 additions and 0 deletions
+860
View File
@@ -0,0 +1,860 @@
from __future__ import annotations
import os
from pathlib import Path
MPLCONFIGDIR = Path(__file__).resolve().parents[2] / "outputs" / "logs" / "matplotlib"
MPLCONFIGDIR.mkdir(parents=True, exist_ok=True)
os.environ.setdefault("MPLCONFIGDIR", str(MPLCONFIGDIR))
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from sklearn.metrics import ConfusionMatrixDisplay, PrecisionRecallDisplay, RocCurveDisplay, confusion_matrix
from evaluation.metrics import ranking_recall_curve
from utils.project import FIGURES_ROOT, ensure_dir
TASK_LABELS = {
"a41_clean_binary": "A4.1 clean cell",
"a42_strict_binary": "A4.2 strict morphology",
"a43_full_realistic_binary": "A4.3 full realistic",
}
TASK_ORDER = list(TASK_LABELS.keys())
FEATURE_GROUP_ORDER = [
"morphology",
"color",
"texture",
"morphology_color",
"morphology_texture",
"color_texture",
"full",
]
MODEL_ORDER = [
"logistic_regression",
"linear_svm",
"rbf_svm",
"random_forest",
"xgboost",
]
MASK_LABELS = {
"bbox_only": "bbox only",
"mask_only": "mask only",
"bbox_plus_mask": "bbox + mask",
}
MASK_ORDER = list(MASK_LABELS.keys())
REGIME_LABELS = {
"combined_with_class_weight": "combined + class weight",
"combined_without_class_weight": "combined without class weight",
"combined_top30_features": "combined top-30 features",
}
REGIME_ORDER = list(REGIME_LABELS.keys())
AGGREGATION_LABELS = {
"max_object_score": "max object score",
"mean_top3_scores": "mean top-3 scores",
"count_objects_above_threshold": "count > threshold",
"soft_noisy_or": "soft noisy-or",
}
AGGREGATION_ORDER = list(AGGREGATION_LABELS.keys())
METRIC_LABELS = {
"pr_auc": "PR-AUC",
"roc_auc": "ROC-AUC",
"balanced_accuracy": "Balanced accuracy",
"recall_blast": "Blast recall",
"specificity": "Specificity",
"matthews_corrcoef": "MCC",
"brier_score": "Brier score",
"expected_calibration_error": "ECE",
"recall_at_fpr_10pct": "Recall at FPR 10%",
"top_10pct_recall": "Recall in top-10%",
"top_20pct_recall": "Recall in top-20%",
}
def _save(fig: plt.Figure, path: Path) -> str:
ensure_dir(path.parent)
fig.tight_layout()
fig.savefig(path, dpi=180, bbox_inches="tight")
plt.close(fig)
return str(path)
def _figure_path(hypothesis: str, date_tag: str, index: int, slug: str) -> Path:
ensure_dir(FIGURES_ROOT)
return FIGURES_ROOT / f"{hypothesis}_{date_tag}_{index:02d}_{slug}.png"
def _labeled_categories(values: list[str], mapping: dict[str, str]) -> list[str]:
return [mapping.get(value, value) for value in values]
def _prepare_metric_frame(
df: pd.DataFrame,
category_col: str,
value_col: str,
order: list[str] | None = None,
) -> pd.DataFrame:
frame = df[[category_col, value_col]].copy()
frame[value_col] = frame[value_col].astype(float)
if order is not None:
frame[category_col] = pd.Categorical(frame[category_col], categories=order, ordered=True)
frame = frame.sort_values(category_col)
return frame
def _save_simple_bar(
df: pd.DataFrame,
category_col: str,
value_col: str,
path: Path,
title: str,
y_label: str,
order: list[str] | None = None,
label_map: dict[str, str] | None = None,
color: str = "#4c72b0",
) -> str:
frame = _prepare_metric_frame(df, category_col, value_col, order=order)
categories = frame[category_col].astype(str).tolist()
labels = _labeled_categories(categories, label_map or {})
values = frame[value_col].astype(float).to_numpy()
fig, ax = plt.subplots(figsize=(10, 6))
bars = ax.bar(labels, values, color=color)
ax.set_title(title)
ax.set_ylabel(y_label)
ax.tick_params(axis="x", rotation=20)
for bar, value in zip(bars, values, strict=False):
vertical_align = "bottom" if value >= 0 else "top"
ax.text(bar.get_x() + bar.get_width() / 2.0, value, f"{value:.3f}", ha="center", va=vertical_align, fontsize=8)
return _save(fig, path)
def _save_grouped_bar(
df: pd.DataFrame,
category_col: str,
group_col: str,
value_col: str,
path: Path,
title: str,
y_label: str,
category_order: list[str] | None = None,
group_order: list[str] | None = None,
category_labels: dict[str, str] | None = None,
group_labels: dict[str, str] | None = None,
) -> str:
frame = df[[category_col, group_col, value_col]].copy()
frame[value_col] = frame[value_col].astype(float)
pivot = frame.pivot(index=category_col, columns=group_col, values=value_col)
if category_order is not None:
pivot = pivot.reindex(category_order)
if group_order is not None:
pivot = pivot.reindex(columns=group_order)
categories = [value for value in pivot.index.tolist() if value is not None]
groups = [value for value in pivot.columns.tolist() if value is not None]
x_positions = np.arange(len(categories))
width = 0.8 / max(len(groups), 1)
fig, ax = plt.subplots(figsize=(11, 6))
for idx, group in enumerate(groups):
offsets = x_positions + (idx - (len(groups) - 1) / 2.0) * width
values = pivot[group].astype(float).to_numpy()
bars = ax.bar(offsets, values, width=width, label=(group_labels or {}).get(group, group))
for bar, value in zip(bars, values, strict=False):
vertical_align = "bottom" if value >= 0 else "top"
ax.text(bar.get_x() + bar.get_width() / 2.0, value, f"{value:.3f}", ha="center", va=vertical_align, fontsize=7)
ax.set_xticks(x_positions)
ax.set_xticklabels(_labeled_categories(categories, category_labels or {}), rotation=15)
ax.set_title(title)
ax.set_ylabel(y_label)
ax.legend(fontsize=8)
return _save(fig, path)
def _save_heatmap(
frame: pd.DataFrame,
path: Path,
title: str,
x_label: str,
y_label: str,
value_fmt: str = ".3f",
cmap: str = "Blues",
) -> str:
matrix = frame.astype(float).to_numpy()
fig, ax = plt.subplots(figsize=(11, 6))
image = ax.imshow(matrix, cmap=cmap, aspect="auto")
ax.set_xticks(np.arange(frame.shape[1]))
ax.set_yticks(np.arange(frame.shape[0]))
ax.set_xticklabels(frame.columns.tolist(), rotation=25, ha="right")
ax.set_yticklabels(frame.index.tolist())
ax.set_title(title)
ax.set_xlabel(x_label)
ax.set_ylabel(y_label)
for row_idx in range(frame.shape[0]):
for col_idx in range(frame.shape[1]):
value = matrix[row_idx, col_idx]
if np.isnan(value):
continue
ax.text(col_idx, row_idx, format(value, value_fmt), ha="center", va="center", fontsize=8)
fig.colorbar(image, ax=ax, fraction=0.046, pad=0.04)
return _save(fig, path)
def _save_metric_heatmap(
df: pd.DataFrame,
index_col: str,
column_col: str,
value_col: str,
path: Path,
title: str,
index_order: list[str] | None = None,
column_order: list[str] | None = None,
index_labels: dict[str, str] | None = None,
column_labels: dict[str, str] | None = None,
) -> str:
pivot = df.pivot(index=index_col, columns=column_col, values=value_col)
if index_order is not None:
pivot = pivot.reindex(index_order)
if column_order is not None:
pivot = pivot.reindex(columns=column_order)
pivot.index = _labeled_categories([str(value) for value in pivot.index.tolist()], index_labels or {})
pivot.columns = _labeled_categories([str(value) for value in pivot.columns.tolist()], column_labels or {})
return _save_heatmap(
pivot,
path=path,
title=title,
x_label=column_col,
y_label=index_col,
)
def _save_pr_or_roc_curves(
prediction_df: pd.DataFrame,
run_rows: pd.DataFrame,
path: Path,
curve_type: str,
title: str,
) -> str:
fig, ax = plt.subplots(figsize=(10, 8))
for _, row in run_rows.iterrows():
run_df = prediction_df.loc[prediction_df["run_id"].eq(row["run_id"])].copy()
label = f"{TASK_LABELS.get(str(row['task_name']), row['task_name'])} | {row['model_name']} | {row['feature_group']}"
if curve_type == "pr":
PrecisionRecallDisplay.from_predictions(run_df["y_true"], run_df["y_score"], name=label, ax=ax)
else:
RocCurveDisplay.from_predictions(run_df["y_true"], run_df["y_score"], name=label, ax=ax)
ax.set_title(title)
return _save(fig, path)
def _save_confusion_from_predictions(prediction_df: pd.DataFrame, path: Path, title: str) -> str:
fig, ax = plt.subplots(figsize=(5, 5))
ConfusionMatrixDisplay(
confusion_matrix(prediction_df["y_true"], prediction_df["y_pred"], labels=[0, 1]),
display_labels=["non-blast", "blast"],
).plot(ax=ax, colorbar=False)
ax.set_title(title)
return _save(fig, path)
def _save_metric_pair_bar(
df: pd.DataFrame,
category_col: str,
metric_a: str,
metric_b: str,
path: Path,
title: str,
category_order: list[str] | None = None,
category_labels: dict[str, str] | None = None,
) -> str:
frame = df[[category_col, metric_a, metric_b]].copy()
if category_order is not None:
frame[category_col] = pd.Categorical(frame[category_col], categories=category_order, ordered=True)
frame = frame.sort_values(category_col)
categories = frame[category_col].astype(str).tolist()
labels = _labeled_categories(categories, category_labels or {})
values_a = frame[metric_a].astype(float).to_numpy()
values_b = frame[metric_b].astype(float).to_numpy()
x_positions = np.arange(len(labels))
width = 0.35
fig, ax = plt.subplots(figsize=(10, 6))
bars_a = ax.bar(x_positions - width / 2.0, values_a, width=width, label=METRIC_LABELS.get(metric_a, metric_a))
bars_b = ax.bar(x_positions + width / 2.0, values_b, width=width, label=METRIC_LABELS.get(metric_b, metric_b))
for bars, values in [(bars_a, values_a), (bars_b, values_b)]:
for bar, value in zip(bars, values, strict=False):
vertical_align = "bottom" if value >= 0 else "top"
ax.text(bar.get_x() + bar.get_width() / 2.0, value, f"{value:.3f}", ha="center", va=vertical_align, fontsize=8)
ax.set_xticks(x_positions)
ax.set_xticklabels(labels, rotation=15)
ax.set_title(title)
ax.set_ylabel("Score")
ax.legend(fontsize=8)
return _save(fig, path)
def _save_feature_barh(
df: pd.DataFrame,
value_col: str,
path: Path,
title: str,
x_label: str,
) -> str:
plot_df = df.head(20).iloc[::-1].copy()
values = plot_df[value_col].astype(float).to_numpy()
fig, ax = plt.subplots(figsize=(10, 8))
ax.barh(plot_df["feature_name"], values, color="#55a868")
ax.set_title(title)
ax.set_xlabel(x_label)
return _save(fig, path)
def _save_delta_vs_reference(
df: pd.DataFrame,
category_col: str,
baseline_label: str,
compare_labels: list[str],
value_col: str,
path: Path,
title: str,
category_order: list[str] | None = None,
category_labels: dict[str, str] | None = None,
compare_label_map: dict[str, str] | None = None,
) -> str:
pivot = df.pivot(index=category_col, columns="label", values=value_col)
if category_order is not None:
pivot = pivot.reindex(category_order)
delta_df = pd.DataFrame(index=pivot.index)
for label in compare_labels:
delta_df[label] = pivot[label] - pivot[baseline_label]
delta_df = delta_df.reset_index().melt(id_vars=category_col, var_name="delta_label", value_name="delta_value")
return _save_grouped_bar(
delta_df,
category_col=category_col,
group_col="delta_label",
value_col="delta_value",
path=path,
title=title,
y_label=f"Delta {METRIC_LABELS.get(value_col, value_col)}",
category_order=category_order,
group_order=compare_labels,
category_labels=category_labels,
group_labels=compare_label_map,
)
def _save_ranking_curve_panel(
score_df: pd.DataFrame,
target_column: str,
path: Path,
title: str,
) -> str:
fig, ax = plt.subplots(figsize=(8, 6))
for method in AGGREGATION_ORDER:
method_df = score_df.loc[score_df["aggregation_method"].eq(method)].copy()
if method_df.empty:
continue
screened_fraction, cumulative_recall = ranking_recall_curve(
method_df[target_column].astype(int).to_numpy(),
method_df["score"].astype(float).to_numpy(),
)
ax.plot(screened_fraction, cumulative_recall, label=AGGREGATION_LABELS.get(method, method))
ax.set_xlabel("Screened fraction")
ax.set_ylabel("Recall of positive groups")
ax.set_title(title)
ax.legend(fontsize=8)
return _save(fig, path)
def _save_best_score_distributions(
image_scores_df: pd.DataFrame,
patient_scores_df: pd.DataFrame,
image_method: str,
patient_method: str,
path: Path,
) -> str:
image_frame = image_scores_df.loc[image_scores_df["aggregation_method"].eq(image_method)].copy()
patient_frame = patient_scores_df.loc[patient_scores_df["aggregation_method"].eq(patient_method)].copy()
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
for axis, frame, target_col, method, panel_title in [
(axes[0], image_frame, "image_contains_blast", image_method, "Best image-level method"),
(axes[1], patient_frame, "patient_contains_blast", patient_method, "Best patient-level method"),
]:
negatives = frame.loc[frame[target_col].eq(0), "score"].astype(float).to_numpy()
positives = frame.loc[frame[target_col].eq(1), "score"].astype(float).to_numpy()
axis.hist(negatives, bins=20, alpha=0.65, label="negative", color="#c44e52")
axis.hist(positives, bins=20, alpha=0.65, label="positive", color="#4c72b0")
axis.set_title(f"{panel_title}\n{AGGREGATION_LABELS.get(method, method)}")
axis.set_xlabel("Aggregated score")
axis.set_ylabel("Count")
axis.legend(fontsize=8)
return _save(fig, path)
def _save_training_curves(history_payload: dict[str, list[dict[str, float]]], path: Path) -> str:
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
for model_name, history in history_payload.items():
history_df = pd.DataFrame(history)
if history_df.empty:
continue
axes[0].plot(history_df["epoch"], history_df["train_loss"], label=f"{model_name} train")
axes[0].plot(history_df["epoch"], history_df["val_loss"], linestyle="--", label=f"{model_name} val")
axes[1].plot(history_df["epoch"], history_df["val_pr_auc"], label=model_name)
axes[0].set_title("Deep baseline loss")
axes[0].set_xlabel("Epoch")
axes[1].set_title("Validation PR-AUC")
axes[1].set_xlabel("Epoch")
for axis in axes:
axis.legend(fontsize=8)
return _save(fig, path)
def _save_deep_pr_curve_comparison(
handcrafted_predictions: pd.DataFrame,
deep_predictions: pd.DataFrame,
path: Path,
title: str,
) -> str:
fig, ax = plt.subplots(figsize=(10, 8))
PrecisionRecallDisplay.from_predictions(
handcrafted_predictions["y_true"],
handcrafted_predictions["y_score"],
name="handcrafted best",
ax=ax,
)
for model_name, frame in deep_predictions.groupby("model_name", sort=False):
PrecisionRecallDisplay.from_predictions(frame["y_true"], frame["y_score"], name=str(model_name), ax=ax)
ax.set_title(title)
return _save(fig, path)
def generate_h1_figures(
leaderboard_df: pd.DataFrame,
predictions_df: pd.DataFrame,
top_features_df: pd.DataFrame,
date_tag: str,
) -> list[str]:
best_rows = (
leaderboard_df.sort_values(["task_name", "validation_pr_auc", "test_pr_auc"], ascending=[True, False, False])
.groupby("task_name", sort=False)
.head(1)
.reset_index(drop=True)
)
top_runs = (
leaderboard_df.sort_values(["task_name", "validation_pr_auc", "test_pr_auc"], ascending=[True, False, False])
.groupby("task_name", sort=False)
.head(2)
.reset_index(drop=True)
)
best_full_row = leaderboard_df.loc[leaderboard_df["task_name"].eq("a43_full_realistic_binary")].sort_values(
["validation_pr_auc", "test_pr_auc"],
ascending=[False, False],
).iloc[0]
figure_paths: list[str] = []
figure_paths.append(
_save_pr_or_roc_curves(
prediction_df=predictions_df,
run_rows=top_runs,
path=_figure_path("H1", date_tag, 1, "pr_curves_top_runs"),
curve_type="pr",
title="H1: precision-recall curves for top object-level runs",
)
)
figure_paths.append(
_save_pr_or_roc_curves(
prediction_df=predictions_df,
run_rows=top_runs,
path=_figure_path("H1", date_tag, 2, "roc_curves_top_runs"),
curve_type="roc",
title="H1: ROC curves for top object-level runs",
)
)
for idx, task_name in enumerate(TASK_ORDER, start=3):
task_frame = leaderboard_df.loc[leaderboard_df["task_name"].eq(task_name)].copy()
pivot = task_frame.pivot(index="model_name", columns="feature_group", values="test_pr_auc")
pivot = pivot.reindex(index=MODEL_ORDER, columns=FEATURE_GROUP_ORDER)
pivot.index = [str(value) for value in pivot.index.tolist()]
pivot.columns = [str(value) for value in pivot.columns.tolist()]
figure_paths.append(
_save_heatmap(
pivot,
path=_figure_path("H1", date_tag, idx, f"{task_name}_pr_auc_heatmap"),
title=f"H1: test PR-AUC for {TASK_LABELS.get(task_name, task_name)}",
x_label="Feature group",
y_label="Model",
)
)
figure_paths.append(
_save_simple_bar(
df=best_rows,
category_col="task_name",
value_col="test_pr_auc",
path=_figure_path("H1", date_tag, 6, "best_pr_auc_by_task"),
title="H1: best object-level PR-AUC by task",
y_label="PR-AUC",
order=TASK_ORDER,
label_map=TASK_LABELS,
color="#8172b3",
)
)
figure_paths.append(
_save_metric_pair_bar(
df=best_rows,
category_col="task_name",
metric_a="test_recall_blast",
metric_b="test_specificity",
path=_figure_path("H1", date_tag, 7, "best_recall_specificity_by_task"),
title="H1: recall and specificity for the best run in each task",
category_order=TASK_ORDER,
category_labels=TASK_LABELS,
)
)
best_full_predictions = predictions_df.loc[
predictions_df["run_id"].eq(best_full_row["run_id"]) & predictions_df["split_name"].eq("test")
].copy()
figure_paths.append(
_save_confusion_from_predictions(
prediction_df=best_full_predictions,
path=_figure_path("H1", date_tag, 8, "best_full_realistic_confusion"),
title=f"H1: confusion matrix for {best_full_row['model_name']} + {best_full_row['feature_group']}",
)
)
figure_paths.append(
_save_feature_barh(
df=top_features_df,
value_col="top20_frequency",
path=_figure_path("H1", date_tag, 9, "top_feature_frequency"),
title="H1: stability of top features",
x_label="Top-20 frequency",
)
)
figure_paths.append(
_save_feature_barh(
df=top_features_df.sort_values(["mean_importance", "top20_frequency"], ascending=[False, False]),
value_col="mean_importance",
path=_figure_path("H1", date_tag, 10, "top_feature_importance"),
title="H1: mean permutation importance of top features",
x_label="Mean importance",
)
)
return figure_paths
def generate_h2_figures(mask_ablation_df: pd.DataFrame, date_tag: str) -> list[str]:
metric_specs = [
(1, "pr_auc", "H2: PR-AUC for bbox and mask feature variants", "PR-AUC"),
(2, "roc_auc", "H2: ROC-AUC for bbox and mask feature variants", "ROC-AUC"),
(3, "balanced_accuracy", "H2: balanced accuracy for bbox and mask feature variants", "Balanced accuracy"),
(4, "recall_blast", "H2: blast recall for bbox and mask feature variants", "Blast recall"),
(5, "specificity", "H2: specificity for bbox and mask feature variants", "Specificity"),
(6, "matthews_corrcoef", "H2: MCC for bbox and mask feature variants", "MCC"),
(7, "brier_score", "H2: Brier score for bbox and mask feature variants", "Brier score"),
(8, "expected_calibration_error", "H2: calibration error for bbox and mask feature variants", "ECE"),
]
figure_paths: list[str] = []
for index, metric, title, y_label in metric_specs:
figure_paths.append(
_save_grouped_bar(
df=mask_ablation_df,
category_col="task_name",
group_col="label",
value_col=metric,
path=_figure_path("H2", date_tag, index, metric),
title=title,
y_label=y_label,
category_order=TASK_ORDER,
group_order=MASK_ORDER,
category_labels=TASK_LABELS,
group_labels=MASK_LABELS,
)
)
figure_paths.append(
_save_delta_vs_reference(
df=mask_ablation_df,
category_col="task_name",
baseline_label="bbox_only",
compare_labels=["mask_only", "bbox_plus_mask"],
value_col="pr_auc",
path=_figure_path("H2", date_tag, 9, "pr_auc_delta_vs_bbox"),
title="H2: PR-AUC gain relative to bbox-only features",
category_order=TASK_ORDER,
category_labels=TASK_LABELS,
compare_label_map={
"mask_only": "mask only - bbox only",
"bbox_plus_mask": "bbox + mask - bbox only",
},
)
)
figure_paths.append(
_save_metric_heatmap(
df=mask_ablation_df,
index_col="task_name",
column_col="label",
value_col="pr_auc",
path=_figure_path("H2", date_tag, 10, "pr_auc_heatmap"),
title="H2: PR-AUC heatmap for mask ablation",
index_order=TASK_ORDER,
column_order=MASK_ORDER,
index_labels=TASK_LABELS,
column_labels=MASK_LABELS,
)
)
return figure_paths
def generate_h3_figures(dataset_ablation_df: pd.DataFrame, date_tag: str) -> list[str]:
primary_df = dataset_ablation_df.loc[dataset_ablation_df["label"].eq("combined_with_class_weight")].copy()
extra_df = dataset_ablation_df.loc[dataset_ablation_df["task_name"].eq("a43_full_realistic_binary")].copy()
metric_specs = [
(1, "pr_auc", "H3: PR-AUC across corpus regimes", "PR-AUC"),
(2, "roc_auc", "H3: ROC-AUC across corpus regimes", "ROC-AUC"),
(3, "balanced_accuracy", "H3: balanced accuracy across corpus regimes", "Balanced accuracy"),
(4, "recall_blast", "H3: blast recall across corpus regimes", "Blast recall"),
(5, "specificity", "H3: specificity across corpus regimes", "Specificity"),
(6, "matthews_corrcoef", "H3: MCC across corpus regimes", "MCC"),
(7, "brier_score", "H3: Brier score across corpus regimes", "Brier score"),
(8, "expected_calibration_error", "H3: calibration error across corpus regimes", "ECE"),
]
figure_paths: list[str] = []
for index, metric, title, y_label in metric_specs:
figure_paths.append(
_save_simple_bar(
df=primary_df,
category_col="task_name",
value_col=metric,
path=_figure_path("H3", date_tag, index, metric),
title=title,
y_label=y_label,
order=TASK_ORDER,
label_map=TASK_LABELS,
color="#64b5cd",
)
)
heatmap_df = primary_df.set_index("task_name")[
["pr_auc", "roc_auc", "balanced_accuracy", "recall_blast", "specificity"]
].reindex(TASK_ORDER)
heatmap_df.index = _labeled_categories([str(value) for value in heatmap_df.index.tolist()], TASK_LABELS)
heatmap_df.columns = [METRIC_LABELS.get(column, column) for column in heatmap_df.columns.tolist()]
figure_paths.append(
_save_heatmap(
heatmap_df,
path=_figure_path("H3", date_tag, 9, "primary_metrics_heatmap"),
title="H3: metric heatmap for the main corpus regimes",
x_label="Metric",
y_label="Task",
)
)
figure_paths.append(
_save_simple_bar(
df=extra_df,
category_col="label",
value_col="pr_auc",
path=_figure_path("H3", date_tag, 10, "full_realistic_variants_pr_auc"),
title="H3: full-realistic ablation variants",
y_label="PR-AUC",
order=REGIME_ORDER,
label_map=REGIME_LABELS,
color="#dd8452",
)
)
return figure_paths
def generate_h4_figures(
image_metrics_df: pd.DataFrame,
patient_metrics_df: pd.DataFrame,
image_scores_df: pd.DataFrame,
patient_scores_df: pd.DataFrame,
date_tag: str,
) -> list[str]:
best_image_method = (
image_metrics_df.sort_values(["pr_auc", "roc_auc"], ascending=[False, False]).iloc[0]["aggregation_method"]
)
best_patient_method = (
patient_metrics_df.sort_values(["pr_auc", "roc_auc"], ascending=[False, False]).iloc[0]["aggregation_method"]
)
figure_paths: list[str] = []
figure_paths.append(
_save_simple_bar(
df=image_metrics_df,
category_col="aggregation_method",
value_col="pr_auc",
path=_figure_path("H4", date_tag, 1, "image_pr_auc"),
title="H4: image-level PR-AUC by aggregation method",
y_label="PR-AUC",
order=AGGREGATION_ORDER,
label_map=AGGREGATION_LABELS,
color="#4c72b0",
)
)
figure_paths.append(
_save_simple_bar(
df=image_metrics_df,
category_col="aggregation_method",
value_col="roc_auc",
path=_figure_path("H4", date_tag, 2, "image_roc_auc"),
title="H4: image-level ROC-AUC by aggregation method",
y_label="ROC-AUC",
order=AGGREGATION_ORDER,
label_map=AGGREGATION_LABELS,
color="#55a868",
)
)
figure_paths.append(
_save_simple_bar(
df=image_metrics_df,
category_col="aggregation_method",
value_col="balanced_accuracy",
path=_figure_path("H4", date_tag, 3, "image_balanced_accuracy"),
title="H4: image-level balanced accuracy by aggregation method",
y_label="Balanced accuracy",
order=AGGREGATION_ORDER,
label_map=AGGREGATION_LABELS,
color="#c44e52",
)
)
figure_paths.append(
_save_simple_bar(
df=image_metrics_df,
category_col="aggregation_method",
value_col="recall_at_fpr_10pct",
path=_figure_path("H4", date_tag, 4, "image_recall_at_fpr10"),
title="H4: image-level recall at FPR 10%",
y_label="Recall at FPR 10%",
order=AGGREGATION_ORDER,
label_map=AGGREGATION_LABELS,
color="#8172b3",
)
)
figure_paths.append(
_save_ranking_curve_panel(
score_df=image_scores_df,
target_column="image_contains_blast",
path=_figure_path("H4", date_tag, 5, "image_ranking_curve"),
title="H4: image-level ranking recall curves",
)
)
figure_paths.append(
_save_simple_bar(
df=patient_metrics_df,
category_col="aggregation_method",
value_col="pr_auc",
path=_figure_path("H4", date_tag, 6, "patient_pr_auc"),
title="H4: patient-level PR-AUC by aggregation method",
y_label="PR-AUC",
order=AGGREGATION_ORDER,
label_map=AGGREGATION_LABELS,
color="#4c72b0",
)
)
figure_paths.append(
_save_simple_bar(
df=patient_metrics_df,
category_col="aggregation_method",
value_col="roc_auc",
path=_figure_path("H4", date_tag, 7, "patient_roc_auc"),
title="H4: patient-level ROC-AUC by aggregation method",
y_label="ROC-AUC",
order=AGGREGATION_ORDER,
label_map=AGGREGATION_LABELS,
color="#55a868",
)
)
figure_paths.append(
_save_simple_bar(
df=patient_metrics_df,
category_col="aggregation_method",
value_col="balanced_accuracy",
path=_figure_path("H4", date_tag, 8, "patient_balanced_accuracy"),
title="H4: patient-level balanced accuracy by aggregation method",
y_label="Balanced accuracy",
order=AGGREGATION_ORDER,
label_map=AGGREGATION_LABELS,
color="#c44e52",
)
)
figure_paths.append(
_save_simple_bar(
df=patient_metrics_df,
category_col="aggregation_method",
value_col="recall_at_fpr_10pct",
path=_figure_path("H4", date_tag, 9, "patient_recall_at_fpr10"),
title="H4: patient-level recall at FPR 10%",
y_label="Recall at FPR 10%",
order=AGGREGATION_ORDER,
label_map=AGGREGATION_LABELS,
color="#8172b3",
)
)
figure_paths.append(
_save_best_score_distributions(
image_scores_df=image_scores_df,
patient_scores_df=patient_scores_df,
image_method=str(best_image_method),
patient_method=str(best_patient_method),
path=_figure_path("H4", date_tag, 10, "best_score_distributions"),
)
)
return figure_paths
def generate_h5_figures(
comparison_df: pd.DataFrame,
handcrafted_predictions: pd.DataFrame,
deep_predictions: pd.DataFrame,
training_history: dict[str, list[dict[str, float]]],
date_tag: str,
) -> list[str]:
metric_specs = [
(1, "test_pr_auc", "H5: PR-AUC of handcrafted and deep baselines", "PR-AUC"),
(2, "test_roc_auc", "H5: ROC-AUC of handcrafted and deep baselines", "ROC-AUC"),
(3, "test_balanced_accuracy", "H5: balanced accuracy of handcrafted and deep baselines", "Balanced accuracy"),
(4, "test_recall_blast", "H5: blast recall of handcrafted and deep baselines", "Blast recall"),
(5, "test_specificity", "H5: specificity of handcrafted and deep baselines", "Specificity"),
(6, "test_matthews_corrcoef", "H5: MCC of handcrafted and deep baselines", "MCC"),
(7, "test_brier_score", "H5: Brier score of handcrafted and deep baselines", "Brier score"),
(8, "test_expected_calibration_error", "H5: calibration error of handcrafted and deep baselines", "ECE"),
]
figure_paths: list[str] = []
model_order = comparison_df["model_name"].astype(str).tolist()
label_map = {
"handcrafted_best_full_realistic": "handcrafted best",
"cnn_bbox": "CNN bbox",
"cnn_mask": "CNN mask",
"cnn_late_fusion": "CNN late fusion",
}
for index, metric, title, y_label in metric_specs:
figure_paths.append(
_save_simple_bar(
df=comparison_df.rename(columns={metric: "metric_value"}),
category_col="model_name",
value_col="metric_value",
path=_figure_path("H5", date_tag, index, metric.replace("test_", "")),
title=title,
y_label=y_label,
order=model_order,
label_map=label_map,
color="#937860",
)
)
figure_paths.append(
_save_training_curves(
history_payload=training_history,
path=_figure_path("H5", date_tag, 9, "training_curves"),
)
)
figure_paths.append(
_save_deep_pr_curve_comparison(
handcrafted_predictions=handcrafted_predictions,
deep_predictions=deep_predictions,
path=_figure_path("H5", date_tag, 10, "precision_recall_curves"),
title="H5: precision-recall comparison of handcrafted and deep baselines",
)
)
return figure_paths