skin-lesion-api / src /eda.py
Ranjith445's picture
Initial HF Space deploy
e4ee3e8
Raw
History Blame Contribute Delete
6.18 kB
import os
import sys
import json
import numpy as np
import pandas as pd
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from PIL import Image
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from config import OUTPUT_DIR, CLASS_LABELS, CLASS_NAMES, IDX_TO_CLASS
from src.data_loader import load_and_clean, split_dataset, compute_weights
os.makedirs(OUTPUT_DIR, exist_ok=True)
COLORS = ["#4e79a7","#f28e2b","#e15759","#76b7b2","#59a14f","#edc948","#b07aa1"]
def plot_class_distribution(df: pd.DataFrame):
counts = df["dx"].value_counts()
labels = [f"{k}\n({v})" for k, v in counts.items()]
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
fig.suptitle("HAM10000 — Class Distribution", fontsize=14, fontweight="bold")
# Bar chart
axes[0].bar(range(len(counts)), counts.values, color=COLORS, edgecolor="white")
axes[0].set_xticks(range(len(counts)))
axes[0].set_xticklabels(counts.index, rotation=30, ha="right")
axes[0].set_ylabel("Number of images")
axes[0].set_title("Absolute counts")
for i, v in enumerate(counts.values):
axes[0].text(i, v + 30, str(v), ha="center", fontsize=9)
# Pie chart
axes[1].pie(
counts.values,
labels=labels,
colors=COLORS,
autopct="%1.1f%%",
startangle=140,
textprops={"fontsize": 8},
)
axes[1].set_title("Proportion")
plt.tight_layout()
path = os.path.join(OUTPUT_DIR, "class_distribution.png")
plt.savefig(path, dpi=150, bbox_inches="tight")
plt.close()
print(f"[EDA] Saved: {path}")
def plot_sample_images(df: pd.DataFrame, n_per_class: int = 3):
classes = list(CLASS_LABELS.keys())
fig, axes = plt.subplots(len(classes), n_per_class, figsize=(n_per_class * 3, len(classes) * 3))
fig.suptitle("HAM10000 — Sample Images per Class", fontsize=13, fontweight="bold")
for row_i, cls in enumerate(classes):
subset = df[df["dx"] == cls].sample(
n=min(n_per_class, len(df[df["dx"] == cls])), random_state=42
)
for col_i in range(n_per_class):
ax = axes[row_i][col_i]
ax.axis("off")
if col_i < len(subset):
img = Image.open(subset.iloc[col_i]["img_path"]).convert("RGB")
ax.imshow(img)
if col_i == 0:
ax.set_ylabel(cls, fontsize=10, fontweight="bold", rotation=0,
labelpad=60, va="center")
plt.tight_layout()
path = os.path.join(OUTPUT_DIR, "sample_images.png")
plt.savefig(path, dpi=120, bbox_inches="tight")
plt.close()
print(f"[EDA] Saved: {path}")
def plot_class_weights(weights):
labels = [IDX_TO_CLASS[i] for i in range(len(weights))]
vals = weights.numpy()
fig, ax = plt.subplots(figsize=(9, 4))
bars = ax.barh(labels, vals, color=COLORS, edgecolor="white")
ax.set_xlabel("Weight")
ax.set_title("Class Weights (used in CrossEntropyLoss)", fontweight="bold")
ax.axvline(1.0, color="gray", linestyle="--", linewidth=1, label="weight=1")
for bar, val in zip(bars, vals):
ax.text(val + 0.1, bar.get_y() + bar.get_height() / 2,
f"{val:.2f}", va="center", fontsize=9)
ax.legend()
plt.tight_layout()
path = os.path.join(OUTPUT_DIR, "class_weights.png")
plt.savefig(path, dpi=150, bbox_inches="tight")
plt.close()
print(f"[EDA] Saved: {path}")
def plot_split_distribution(train_df, val_df, test_df):
splits = {"Train": train_df, "Val": val_df, "Test": test_df}
classes = list(CLASS_LABELS.keys())
x = np.arange(len(classes))
width = 0.25
fig, ax = plt.subplots(figsize=(13, 5))
for i, (split_name, sdf) in enumerate(splits.items()):
counts = [len(sdf[sdf["dx"] == c]) for c in classes]
ax.bar(x + i * width, counts, width, label=split_name, color=COLORS[i], edgecolor="white")
ax.set_xticks(x + width)
ax.set_xticklabels(classes, rotation=30, ha="right")
ax.set_ylabel("Number of images")
ax.set_title("Stratified Split — Class Distribution per Subset", fontweight="bold")
ax.legend()
plt.tight_layout()
path = os.path.join(OUTPUT_DIR, "split_distribution.png")
plt.savefig(path, dpi=150, bbox_inches="tight")
plt.close()
print(f"[EDA] Saved: {path}")
def save_eda_summary(df, train_df, val_df, test_df, weights):
summary = {
"total_images": len(df),
"num_classes": len(CLASS_LABELS),
"class_counts": df["dx"].value_counts().to_dict(),
"split": {
"train": len(train_df),
"val": len(val_df),
"test": len(test_df),
},
"class_weights": {
IDX_TO_CLASS[i]: round(float(weights[i]), 4)
for i in range(len(weights))
},
"image_size": "variable (resized to 224x224 during training)",
}
path = os.path.join(OUTPUT_DIR, "eda_summary.json")
with open(path, "w") as f:
json.dump(summary, f, indent=2)
print(f"[EDA] Saved: {path}")
return summary
def run_eda():
print("=" * 50)
print(" EDA — HAM10000 Skin Lesion Dataset")
print("=" * 50)
df = load_and_clean(verbose=True)
train_df, val_df, test_df = split_dataset(df, verbose=True)
weights = compute_weights(train_df)
print("\n[EDA] Generating plots...")
plot_class_distribution(df)
plot_sample_images(df)
plot_class_weights(weights)
plot_split_distribution(train_df, val_df, test_df)
summary = save_eda_summary(df, train_df, val_df, test_df, weights)
print("\n[EDA] Summary:")
print(f" Total images : {summary['total_images']}")
print(f" Classes : {summary['num_classes']}")
print(f" Train/Val/Test: {summary['split']['train']} / {summary['split']['val']} / {summary['split']['test']}")
print(f" Class weights: { {k: v for k, v in summary['class_weights'].items()} }")
print("\n[EDA] All outputs saved to:", OUTPUT_DIR)
print("=" * 50)
return summary
if __name__ == "__main__":
run_eda()