File size: 3,026 Bytes
6dd9839 | 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 85 86 87 88 89 90 | #!/usr/bin/env python3
"""Render HOMER *.motif PWMs to PNG logos (website display colors)."""
from __future__ import annotations
import argparse
import re
from pathlib import Path
import logomaker
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
# Same palette as create_motif_logo.ipynb (website / paper figures)
COLOR_SCHEME = {
"A": "#65a455",
"C": "#2e45a4",
"G": "#fda562",
"U": "#d54f3f",
}
def parse_homer_motif(path: Path) -> list[tuple[str, np.ndarray]]:
"""Parse HOMER .motif file → [(label, Lx4 ACGU matrix), ...]."""
text = path.read_text(encoding="utf-8", errors="replace")
motifs: list[tuple[str, np.ndarray]] = []
blocks = [b.strip() for b in text.split(">") if b.strip()]
for bi, block in enumerate(blocks):
lines = [ln.strip() for ln in block.splitlines() if ln.strip()]
if not lines:
continue
header = lines[0].split("\t")
label = header[1] if len(header) > 1 else f"{path.stem}_{bi + 1}"
rows: list[list[float]] = []
for ln in lines[1:]:
parts = re.split(r"\s+", ln)
if len(parts) != 4:
continue
try:
rows.append([float(x) for x in parts])
except ValueError:
continue
if rows:
motifs.append((label, np.asarray(rows, dtype=np.float64)))
return motifs
def plot_logo(matrix: np.ndarray, output_path: Path, dpi: int = 200) -> None:
df = pd.DataFrame(matrix, columns=["A", "C", "G", "U"])
df = df.div(df.sum(axis=1).replace(0, np.nan), axis=0).fillna(0.25)
fig, ax = plt.subplots(figsize=(4, 2))
logomaker.Logo(
df,
ax=ax,
color_scheme=COLOR_SCHEME,
baseline_width=0,
show_spines=False,
)
ax.axis("off")
output_path.parent.mkdir(parents=True, exist_ok=True)
fig.savefig(output_path, dpi=dpi, bbox_inches="tight", format="png")
plt.close(fig)
def render_homer_dir(homer_dir: Path, logo_dir: Path, dpi: int = 200) -> list[Path]:
out: list[Path] = []
logo_dir.mkdir(parents=True, exist_ok=True)
for motif_path in sorted(homer_dir.glob("*.motif")):
for i, (label, mat) in enumerate(parse_homer_motif(motif_path), start=1):
safe = re.sub(r"[^\w.\-]+", "_", label)
png = logo_dir / f"{motif_path.stem}_logo{i}_{safe}.png"
plot_logo(mat, png, dpi=dpi)
out.append(png)
print(f"saved {png.name}")
return out
def main() -> None:
ap = argparse.ArgumentParser(description="HOMER .motif → PNG logos")
ap.add_argument("homer_dir", type=Path, help="Directory of *.motif files")
ap.add_argument("logo_dir", type=Path, help="Output directory for PNGs")
ap.add_argument("--dpi", type=int, default=200)
args = ap.parse_args()
paths = render_homer_dir(args.homer_dir, args.logo_dir, dpi=args.dpi)
print(f"wrote {len(paths)} logos -> {args.logo_dir}")
if __name__ == "__main__":
main()
|