File size: 4,324 Bytes
e8fa066 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | """Descriptive analysis of the ORION tri-modal (+markers) data. Writes results/data_summary.json + figures."""
import json
import os
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
import tifffile
from PIL import Image
from common import ORION, ROOT, load_all_tiles, marker_columns
out = os.path.join(ROOT, "results")
os.makedirs(out, exist_ok=True)
df = load_all_tiles()
prop, cnt = marker_columns(df)
summary = {
"n_tiles": len(df),
"n_slides": int(df.in_slide_name.nunique()),
"n_patients": int(df.patient.nunique()),
"official_split_tiles": df.orion_split.value_counts().to_dict(),
"official_split_slides": df.groupby("orion_split").in_slide_name.nunique().to_dict(),
"if_batches_tiles": df.if_batch.value_counts().to_dict(),
"if_batches_patients": df.groupby("if_batch").patient.nunique().to_dict(),
"tiles_per_slide": df.groupby("in_slide_name").size().describe().round(1).to_dict(),
}
# per-tile stats from images (all tiles; small set)
he_mean, if_ch_mean, n_nuc, nuc_cov = [], [], [], []
for _, r in df.iterrows():
he = np.asarray(Image.open(os.path.join(ORION, r.image_path)).convert("RGB"))
iff = tifffile.imread(os.path.join(ORION, r.target_path))
nuc = tifffile.imread(os.path.join(ORION, r.nuclei_path))
he_mean.append(he.mean())
if_ch_mean.append(iff.reshape(-1, iff.shape[-1]).mean(0))
n_nuc.append(len(np.unique(nuc)) - 1)
nuc_cov.append((nuc > 0).mean())
df["he_mean"], df["n_nuclei"], df["nuc_cov"] = he_mean, n_nuc, nuc_cov
if_ch_mean = np.stack(if_ch_mean)
summary["shapes"] = {"he": list(he.shape), "if": list(iff.shape), "nuclei": list(nuc.shape),
"markers": len(prop) + len(cnt)}
summary["if_channel_names_from_prop_cols"] = [c[:-5] for c in prop]
summary["n_nuclei_per_tile"] = df.n_nuclei.describe().round(1).to_dict()
summary["frac_tiles_lt20_nuclei"] = float((df.n_nuclei < 20).mean())
summary["he_mean_vs_n_nuclei_spearman"] = float(df[["he_mean", "n_nuclei"]].corr("spearman").iloc[0, 1])
summary["marker_prop_mean"] = df[prop].mean().round(4).to_dict()
summary["marker_count_mean"] = df[cnt].mean().round(2).to_dict()
summary["if_channel_mean_intensity"] = dict(zip([c[:-5] for c in prop], if_ch_mean.mean(0).round(2).tolist()))
df[["tile_id", "in_slide_name", "patient", "if_batch", "orion_split", "n_nuclei", "nuc_cov", "he_mean"]].to_csv(
os.path.join(out, "tile_stats.csv"), index=False)
json.dump(summary, open(os.path.join(out, "data_summary.json"), "w"), indent=1)
print(json.dumps(summary, indent=1))
# figure: example quadruplets
rng = np.random.default_rng(0)
ex = df[df.n_nuclei > 100].sample(4, random_state=0)
fig, ax = plt.subplots(4, 5, figsize=(15, 12))
for i, (_, r) in enumerate(ex.iterrows()):
he = Image.open(os.path.join(ORION, r.image_path)).convert("RGB")
iff = tifffile.imread(os.path.join(ORION, r.target_path))
nuc = tifffile.imread(os.path.join(ORION, r.nuclei_path))
ax[i, 0].imshow(he); ax[i, 0].set_title(f"H&E {r.orion_slide_id}")
# Hoechst / Pan-CK / CD45 as RGB-ish composite
names = [c[:-5] for c in prop]
comp = np.stack([iff[..., names.index(n)] for n in ["Pan-CK", "CD45", "Hoechst"]], -1)
ax[i, 1].imshow(comp); ax[i, 1].set_title("IF: Pan-CK/CD45/Hoechst")
ax[i, 2].imshow(iff[..., names.index("SMA")], cmap="magma"); ax[i, 2].set_title("IF: SMA")
ax[i, 3].imshow(nuc > 0, cmap="gray"); ax[i, 3].set_title(f"nuclei ({r.n_nuclei})")
ax[i, 4].barh(range(len(cnt)), np.log1p(r[cnt].values.astype(float)))
ax[i, 4].set_yticks(range(len(cnt))); ax[i, 4].set_yticklabels([c[:-6] for c in cnt], fontsize=6)
ax[i, 4].set_title("markers log1p(count)")
for a in ax[i, :4]:
a.axis("off")
plt.tight_layout(); plt.savefig(os.path.join(out, "example_quadruplets.png"), dpi=80)
fig, ax = plt.subplots(1, 3, figsize=(15, 4))
ax[0].hist(df.n_nuclei, bins=50); ax[0].set_title("nuclei per tile"); ax[0].set_yscale("log")
df.groupby("if_batch").size().plot.bar(ax=ax[1], title="tiles per IF batch")
ax[2].imshow(np.corrcoef(np.concatenate([df[prop].values, np.log1p(df[cnt].values)], 1).T), cmap="RdBu_r", vmin=-1, vmax=1)
ax[2].set_title("marker vector correlation (17 prop + 16 log count)")
plt.tight_layout(); plt.savefig(os.path.join(out, "data_overview.png"), dpi=80)
|