#!/usr/bin/env python3
import pandas as pd
import numpy as np
import html
import argparse
from pathlib import Path
###############################################################################
# COLOR HELPERS
###############################################################################
def color_scale(value, vmin, vmax, invert=False):
if pd.isna(value):
return "#ffffff"
if abs(vmax - vmin) < 1e-9:
return "rgba(0,255,0,0.35)" # All same → green
x = (value - vmin) / (vmax - vmin + 1e-9)
x = max(0.0, min(1.0, x))
if invert:
x = 1.0 - x
r = int(255 * (1 - x))
g = int(255 * x)
return f"rgba({r},{g},0,0.35)"
###############################################################################
# MAIN TABLE FORMATTER
###############################################################################
def dataframe_to_colored_html(df):
"""
Flexible: If AF2 columns exist → include them.
Otherwise show seq-only metrics.
"""
# if "scfvtools_score" in df.columns:
# df = df.sort_values("scfvtools_score", ascending=False)
preferred_order = [
"Accession",
"pLDDT",
"pAE_mean",
"pTM",
"ipTM",
"scfvtools_score", # moved up
"scfvtools_blosum_score",
"scfvtools_blosum_diff_score",
"Prob. of Solubility", # moved down
"log_likelihood_target",
"log_likelihood"
]
# Keep only columns present
cols = [c for c in preferred_order if c in df.columns]
df = df[cols]
numeric_cols = df.select_dtypes(include=[np.number]).columns
col_ranges = {
col: (df[col].min(), df[col].max()) for col in numeric_cols
}
# Build HTML
rows = []
header = "".join(
f"
{html.escape(col)} | "
for col in df.columns
)
rows.append(f"{header}
")
for _, row in df.iterrows():
cells = []
for col, value in row.items():
text = html.escape(str(value))
if col in numeric_cols:
inv = ("pae" in col.lower())
vmin, vmax = col_ranges[col]
bg = color_scale(value, vmin, vmax, invert=inv)
cells.append(f"{text} | ")
else:
cells.append(f"{text} | ")
rows.append(f"{''.join(cells)}
")
return (
""
)
###############################################################################
# SMALL TABLE FORMATTER FOR TOP / BOTTOM / RANDOM
###############################################################################
def simple_table(csv_path, title):
df = pd.read_csv(csv_path)
return (
f"{html.escape(title)}
\n"
+ df.to_html(index=False, escape=True)
)
###############################################################################
# MAIN APPENDER
###############################################################################
def append_scores_to_summary(main_csv, summary_html, out=None,
top_csv=None, bottom_csv=None, random_csv=None):
df = pd.read_csv(main_csv)
main_table = dataframe_to_colored_html(df)
block = (
""
"
Design Ranking Scores
"
"
"
f"{main_table}"
"
"
"
\n"
)
# # Add stacked Top / Bottom / Random if present
# if top_csv and Path(top_csv).exists():
# block += simple_table(top_csv, "Top Designs") + "\n"
# if bottom_csv and Path(bottom_csv).exists():
# block += simple_table(bottom_csv, "Bottom Designs") + "\n"
# if random_csv and Path(random_csv).exists():
# block += simple_table(random_csv, "Random Designs") + "\n"
# Inject block before