January 16, 2026
Alice Brunner
Language: Python
#!pip3 install pandas openpyxl
import pandas as pd # 1.5.3 # numpy backend 1.26.0
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.patches import FancyArrowPatch, Patch
from Bio import SeqIO
import os
import re
The following input is required: Data/Coverage_Isolate_against_Pel.txt.
import numpy as np
def smooth_region(data, deb, end):
"""
Smooths the region between `deb` and `end` in `data` by replacing it with the average of the values at `deb-10` and `end+10`.
Args:
data (np.array): Input data array.
deb (int): Start index of the region to smooth.
end (int): End index of the region to smooth.
Returns:
np.array: Data with the specified region smoothed.
"""
# Calculate the average of the values at deb-10 and end+10
left_val = data[max(0, deb - 10)]
right_val = data[min(len(data) - 1, end + 10)]
avg_val = (left_val + right_val) / 2
# Replace the region between deb and end with the average value
smoothed_data = data.copy()
smoothed_data[deb:end + 1] = avg_val
return smoothed_data
# Output path for the coverage figure
output_path = "Output/Cov_Total.pdf"
# Load per-base coverage file from read mapping to wPipPel
csv_path = "Data/Coverage_Isolate_against_Pel.txt"
df = pd.read_csv(
csv_path,
sep="\t",
header=0,
names=["contig", "position", "coverage"]
)
# Ensure numeric values and remove invalid entries
df["position"] = pd.to_numeric(df["position"], errors="coerce")
df["coverage"] = pd.to_numeric(df["coverage"], errors="coerce")
df = df.dropna()
# Smooth individual peaks of over-copied ribosomal-associated DNA for better visibility of coverage profiles
df["coverage"] = smooth_region(df["coverage"].values, 1135994, 1137497) # Smooth 16S
df["coverage"] = smooth_region(df["coverage"].values, 1235938, 1238876) # Smooth 23S
df["coverage"] = smooth_region(df["coverage"].values, 771502, 771929) # 50S Protein-Binding related sequence
# Downsample positions to reduce figure size and file weight
df = df.iloc[::10, :].copy()
# Generate coverage plot
fig, ax = plt.subplots(figsize=(12, 2))
ax.plot(df["position"], df["coverage"], linewidth=0.7)
ax.fill_between(df["position"], 0, df["coverage"], alpha=0.2)
# Highlight WOPip prophage regions along the genome
regions = {
"WOPip\n1": (246932, 297466, "green"),
"WOPip\n2": (297282, 346089, "orange"),
"WOPip\n3": (346156, 375271, "blue"),
"WOPip4": (429750, 502566, "yellow"),
"WOPip5": (1369259, 1446799, "purple"),
}
for name, (start, end, color) in regions.items():
ax.axvspan(start, end, color=color, alpha=0.2)
ax.text(
(start + end) / 2,
max(df["coverage"]) * 0.95,
name,
ha="center",
va="top",
fontsize=6,
fontweight="bold"
)
# Axis formatting
ax.set_xlim(df["position"].min(), df["position"].max())
ax.set_xlabel("Position (bp)", fontsize=10)
ax.set_ylabel("Coverage", fontsize=10)
ax.set_title("Coverage of isolate reads mapped to wPipPel", fontsize=11)
# Aesthetic adjustments
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
ax.grid(True, linestyle="--", alpha=0.2)
plt.tight_layout()
# Export figure in both raster-safe PDF and lightweight vector SVG formats
fig.savefig(output_path, bbox_inches="tight")
fig.savefig(output_path.replace(".pdf", ".svg"), bbox_inches="tight")
plt.show()
print("Figure saved to:", output_path)

Figure saved to: Output/Cov_Total.pdf
# Coverage WOPip
all_cov = pd.read_csv('Data/Coverage_Isolate_against_Pel.txt', sep='\t', header=None)
wopip1 = all_cov.iloc[246933-1:297466-1]
wopip2 = all_cov.iloc[297283-1:346089-1]
wopip3 = all_cov.iloc[346157-1:375271-1]
wopip4 = all_cov.iloc[429750-1:502566-1]
wopip5 = all_cov.iloc[1369259-1:1446799-1]
wopip1.to_csv('Data/WOPip1.csv', index=None, sep='\t')
wopip2.to_csv('Data/WOPip2.csv', index=None, sep='\t')
wopip3.to_csv('Data/WOPip3.csv', index=None, sep='\t')
wopip4.to_csv('Data/WOPip4.csv', index=None, sep='\t')
wopip5.to_csv('Data/WOPip5.csv', index=None, sep='\t')
The following inputs are required:
# WOPip1: gene synteny (functional categories) + isolate coverage track
# === Inputs / outputs ===
gff_path = "Data/WOPip1_gff.xlsx"
orthogroups_path = "Data/Orthogroups_mod_Pip1.tsv"
coverage_folder = "Data/WOPip1.csv"
output_path = "Output/WOPip1_synt.pdf"
# === Load gene coordinates and orthogroup-derived annotations ===
df = pd.read_excel(gff_path)
orthogroups_df = pd.read_csv(orthogroups_path, sep="\t")
df.columns = [col.strip().lower() for col in df.columns]
df["protein_id"] = df["proteines"].str.extract(r"___(\d+)$")[0]
# Map protein IDs to functional labels (from orthogroup annotation)
protein_function_map = {}
for _, row in orthogroups_df.iterrows():
if pd.notna(row["PROTEINES_WOPip1"]):
for pid in row["PROTEINES_WOPip1"].split(","):
protein_function_map[pid.strip()] = str(row["WOqueries_041124"])
# === Functional category color mapping ===
def get_color(annotations):
"""Assign colors based on the primary functional label; prioritize cif annotations when present."""
if annotations is None:
return "grey"
annotations = annotations.split(",")
annotation = annotations[0]
for a in annotations:
if "cif" in a:
annotation = a
a = annotation
return {
"HEAD": "#660066",
"ANK": "#ff0000",
"CON_": "#0432ff",
"TailFiber": "#ffc1c0",
"Tail_": "#ff6666",
"RECOMB": "#07f256",
"LIGA": "#712ca3",
"TOXIN": "#de8209",
"WOPC1": "#4aeaed",
"cifA": "#ffa4ff",
"cifB": "#ffa4ff",
"TR": "#7340f5",
"PATATIN": "#593601",
"TNP": "#fcfc2d",
"MutL": "#272545",
"METHYL": "#538201",
"REP": "#88d4eb",
}.get(
next(
(k for k in [
"HEAD","ANK","CON_","TailFiber","Tail_","RECOMB","LIGA","TOXIN","WOPC1","cifA","cifB",
"TR","PATATIN","TNP","MutL","METHYL","REP"
] if k in a),
None
),
"#c7c5c5"
)
# === Figure layout: synteny track (top) + coverage track (bottom) ===
fig = plt.figure(figsize=(20, 3))
y_height_syn = 0.6
y_height_cov = 0.3
y_margin = 0.05
# This dataset corresponds to a single contig (WOPip1 region)
contig = df["contigs"].unique()[0]
subset = df[df["contigs"] == contig]
contig_len = subset["fin"].max()
# --- Synteny track ---
ax_syn = fig.add_axes([0.1, y_height_cov + y_margin, 0.8, y_height_syn])
ax_syn.set_xlim(0, contig_len)
ax_syn.set_ylim(-1, 1)
ax_syn.axis("off")
ax_syn.text(-1000, 0, "WOPip1", ha="right", va="center", fontsize=10, fontweight="bold")
ax_syn.hlines(0, 0, contig_len, color="black", linewidth=2, zorder=0)
# Draw gene arrows colored by functional category
for _, row in subset.iterrows():
start, end = row["deb"], row["fin"]
direction = row["sens"]
prot_id = str(row["protein_id"])
annotation = protein_function_map.get(prot_id)
color = get_color(annotation)
arrow = (
FancyArrowPatch((start, 0), (end, 0), arrowstyle="simple", color=color,
mutation_scale=15, linewidth=1.5)
if direction == "+"
else FancyArrowPatch((end, 0), (start, 0), arrowstyle="simple", color=color,
mutation_scale=15, linewidth=1.5)
)
ax_syn.add_patch(arrow)
# --- Coverage track ---
ax_cov = fig.add_axes([0.1, 0, 0.8, y_height_cov])
df_cov = pd.read_csv(coverage_folder, header=0, sep="\t", names=["contig", "position", "coverage"])
df_cov = df_cov.dropna()
df_cov["position"] = pd.to_numeric(df_cov["position"], errors="coerce")
df_cov["coverage"] = pd.to_numeric(df_cov["coverage"], errors="coerce")
# Re-index positions to start at 1 (coverage was computed on wPipPel genomic coordinates)
offset = df_cov["position"].min() - 1
df_cov["position"] = df_cov["position"] - offset
ax_cov.plot(df_cov["position"], df_cov["coverage"], color="blue", linewidth=0.7)
ax_cov.fill_between(df_cov["position"], 0, df_cov["coverage"], color="blue", alpha=0.2)
ax_cov.set_xlim(0, contig_len)
ax_cov.set_ylabel("Cov.", fontsize=6)
ax_cov.tick_params(axis="both", labelsize=6)
ax_cov.spines["top"].set_visible(False)
ax_cov.spines["right"].set_visible(False)
# === Legend ===
legend_elements = [
mpatches.Patch(color="#660066", label="Head"),
mpatches.Patch(color="#ff0000", label="Ankyrin"),
mpatches.Patch(color="#0432ff", label="Connector/Baseplate"),
mpatches.Patch(color="#ffc1c0", label="Tail Fiber"),
mpatches.Patch(color="#ff6666", label="Tail"),
mpatches.Patch(color="#ffa4ff", label="CifA/B"),
mpatches.Patch(color="#07f256", label="Recombinase"),
mpatches.Patch(color="#fcfc2d", label="Transposase"),
mpatches.Patch(color="#4aeaed", label="WOPC1"),
mpatches.Patch(color="#ff6600", label="Toxins"),
mpatches.Patch(color="#712ca3", label="LigA"),
mpatches.Patch(color="#7340f5", label="TR"),
mpatches.Patch(color="#593601", label="Patatin"),
mpatches.Patch(color="#272545", label="MutL"),
mpatches.Patch(color="#88d4eb", label="Replication/Repair"),
mpatches.Patch(color="#538201", label="Methylase"),
]
fig.legend(
handles=legend_elements,
loc="center left",
bbox_to_anchor=(0.91, 0.5),
title="Legend",
fontsize=8,
title_fontsize=9
)
# === Export ===
fig.savefig(output_path, bbox_inches="tight")
plt.show(fig)
print("Figure saved to:", output_path)

Figure saved to: Output/WOPip1_synt.pdf
The following inputs are required:
# WOPip2: gene synteny (functional categories) + isolate coverage track
# === Inputs / outputs ===
gff_path = "Data/WOPip2_gff.xlsx"
orthogroups_path = "Data/Orthogroups_mod_Pip2.tsv"
coverage_folder = "Data/WOPip2.csv"
output_path = "Output/WOPip2_synt.pdf"
# === Load gene coordinates and orthogroup-derived annotations ===
df = pd.read_excel(gff_path)
orthogroups_df = pd.read_csv(orthogroups_path, sep="\t")
df.columns = [col.strip().lower() for col in df.columns]
df["protein_id"] = df["proteines"].str.extract(r"___(\d+)$")[0]
# Map protein IDs to functional labels (from orthogroup annotation)
protein_function_map = {}
for _, row in orthogroups_df.iterrows():
if pd.notna(row["PROTEINES_WOPip2"]):
for pid in row["PROTEINES_WOPip2"].split(","):
protein_function_map[pid.strip()] = str(row["WOqueries_041124"])
# === Functional category color mapping ===
def get_color(annotations):
"""Assign colors based on the primary functional label; prioritize cif annotations when present."""
if annotations is None:
return "grey"
annotations = annotations.split(",")
annotation = annotations[0]
for a in annotations:
if "cif" in a:
annotation = a
a = annotation
return {
"HEAD": "#660066",
"ANK": "#ff0000",
"CON_": "#0432ff",
"TailFiber": "#ffc1c0",
"Tail_": "#ff6666",
"RECOMB": "#07f256",
"LIGA": "#712ca3",
"TOXIN": "#de8209",
"WOPC1": "#4aeaed",
"cifA": "#ffa4ff",
"cifB_": "#ffa4ff",
"TR": "#7340f5",
"PATATIN": "#593601",
"TNP": "#fcfc2d",
"MutL": "#272545",
"METHYL": "#538201",
"REP": "#88d4eb",
}.get(
next(
(k for k in [
"HEAD","ANK","CON_","TailFiber","Tail_","RECOMB","LIGA","TOXIN","WOPC1","cifA","cifB_",
"TR","PATATIN","TNP","MutL","METHYL","REP"
] if k in a),
None
),
"#c7c5c5"
)
# === Figure layout: synteny track (top) + coverage track (bottom) ===
fig = plt.figure(figsize=(20, 3))
y_height_syn = 0.6
y_height_cov = 0.3
y_margin = 0.05
# This dataset corresponds to a single contig (WOPip2 region)
contig = df["contigs"].unique()[0]
subset = df[df["contigs"] == contig]
contig_len = subset["fin"].max()
# --- Synteny track ---
ax_syn = fig.add_axes([0.1, y_height_cov + y_margin, 0.8, y_height_syn])
ax_syn.set_xlim(0, contig_len)
ax_syn.set_ylim(-1, 1)
ax_syn.axis("off")
ax_syn.text(-1000, 0, "WOPip2", ha="right", va="center", fontsize=10, fontweight="bold")
ax_syn.hlines(0, 0, contig_len, color="black", linewidth=2, zorder=0)
# Draw gene arrows colored by functional category
for _, row in subset.iterrows():
start, end = row["deb"], row["fin"]
direction = row["sens"]
prot_id = str(row["protein_id"])
annotation = protein_function_map.get(prot_id)
color = get_color(annotation)
arrow = (
FancyArrowPatch((start, 0), (end, 0), arrowstyle="simple", color=color,
mutation_scale=15, linewidth=1.5)
if direction == "+"
else FancyArrowPatch((end, 0), (start, 0), arrowstyle="simple", color=color,
mutation_scale=15, linewidth=1.5)
)
ax_syn.add_patch(arrow)
# --- Coverage track ---
ax_cov = fig.add_axes([0.1, 0, 0.8, y_height_cov])
df_cov = pd.read_csv(coverage_folder, header=0, sep="\t", names=["contig", "position", "coverage"])
df_cov = df_cov.dropna()
df_cov["position"] = pd.to_numeric(df_cov["position"], errors="coerce")
df_cov["coverage"] = pd.to_numeric(df_cov["coverage"], errors="coerce")
# Re-index positions to start at 1 (coverage was computed on wPipPel genomic coordinates)
offset = df_cov["position"].min() - 1
df_cov["position"] = (len(df_cov['position']) - (df_cov["position"] - offset) + 1) # reverse region
ax_cov.plot(df_cov["position"], df_cov["coverage"], color='blue', linewidth=0.7)
ax_cov.fill_between(df_cov["position"], 0, df_cov["coverage"], color='blue', alpha=0.2)
ax_cov.set_xlim(0, contig_len)
ax_cov.set_ylabel("Cov.", fontsize=6)
ax_cov.tick_params(axis='both', labelsize=6)
ax_cov.spines['top'].set_visible(False)
ax_cov.spines['right'].set_visible(False)
# === Legend ===
legend_elements = [
mpatches.Patch(color="#660066", label="Head"),
mpatches.Patch(color="#ff0000", label="Ankyrin"),
mpatches.Patch(color="#0432ff", label="Connector/Baseplate"),
mpatches.Patch(color="#ffc1c0", label="Tail Fiber"),
mpatches.Patch(color="#ff6666", label="Tail"),
mpatches.Patch(color="#ffa4ff", label="CifA/B"),
mpatches.Patch(color="#07f256", label="Recombinase"),
mpatches.Patch(color="#fcfc2d", label="Transposase"),
mpatches.Patch(color="#4aeaed", label="WOPC1"),
mpatches.Patch(color="#ff6600", label="Toxins"),
mpatches.Patch(color="#712ca3", label="LigA"),
mpatches.Patch(color="#7340f5", label="TR"),
mpatches.Patch(color="#593601", label="Patatin"),
mpatches.Patch(color="#272545", label="MutL"),
mpatches.Patch(color="#88d4eb", label="Replication/Repair"),
mpatches.Patch(color="#538201", label="Methylase"),
]
fig.legend(
handles=legend_elements,
loc="center left",
bbox_to_anchor=(0.91, 0.5),
title="Legend",
fontsize=8,
title_fontsize=9
)
# === Export ===
fig.savefig(output_path, bbox_inches="tight")
plt.show(fig)
print("Figure saved to:", output_path)

Figure saved to: Output/WOPip2_synt.pdf
The following inputs are required:
# WOPip3: gene synteny (functional categories) + isolate coverage track
# === Inputs / outputs ===
gff_path = "Data/WOPip3_gff.xlsx"
orthogroups_path = "Data/Orthogroups_mod_Pip3.tsv"
coverage_folder = "Data/WOPip3.csv"
output_path = "Output/WOPip3_synt.pdf"
# === Load gene coordinates and orthogroup-derived annotations ===
df = pd.read_excel(gff_path)
orthogroups_df = pd.read_csv(orthogroups_path, sep="\t")
df.columns = [col.strip().lower() for col in df.columns]
df["protein_id"] = df["proteines"].str.extract(r"___(\d+)$")[0]
# Map protein IDs to functional labels (from orthogroup annotation)
protein_function_map = {}
for _, row in orthogroups_df.iterrows():
if pd.notna(row["PROTEINES_WOPip3"]):
for pid in row["PROTEINES_WOPip3"].split(","):
protein_function_map[pid.strip()] = str(row["WOqueries_041124"])
# === Functional category color mapping ===
def get_color(annotations):
"""Assign colors based on the primary functional label; prioritize cif annotations when present."""
if annotations is None:
return "grey"
annotations = annotations.split(",")
annotation = annotations[0]
for a in annotations:
if "cif" in a:
annotation = a
a = annotation
return {
"HEAD": "#660066",
"ANK": "#ff0000",
"CON_": "#0432ff",
"TailFiber": "#ffc1c0",
"Tail_": "#ff6666",
"RECOMB": "#07f256",
"LIGA": "#712ca3",
"TOXIN": "#de8209",
"WOPC1": "#4aeaed",
"cifA": "#ffa4ff",
"cifB": "#ffa4ff",
"TR": "#7340f5",
"PATATIN": "#593601",
"TNP": "#fcfc2d",
"MutL": "#272545",
"METHYL": "#538201",
"REP": "#88d4eb",
}.get(
next(
(k for k in [
"HEAD","ANK","CON_","TailFiber","Tail_","RECOMB","LIGA","TOXIN","WOPC1","cifA","cifB",
"TR","PATATIN","TNP","MutL","METHYL","REP"
] if k in a),
None
),
"#c7c5c5"
)
# === Figure layout: synteny track (top) + coverage track (bottom) ===
fig = plt.figure(figsize=(20, 3))
y_height_syn = 0.6
y_height_cov = 0.3
y_margin = 0.05
# This dataset corresponds to a single contig (WOPip3 region)
contig = df["contigs"].unique()[0]
subset = df[df["contigs"] == contig]
contig_len = subset["fin"].max()
# --- Synteny track ---
ax_syn = fig.add_axes([0.1, y_height_cov + y_margin, 0.8, y_height_syn])
ax_syn.set_xlim(0, contig_len)
ax_syn.set_ylim(-1, 1)
ax_syn.axis("off")
ax_syn.text(-1000, 0, "WOPip3", ha="right", va="center", fontsize=10, fontweight="bold")
ax_syn.hlines(0, 0, contig_len, color="black", linewidth=2, zorder=0)
# Draw gene arrows colored by functional category
for _, row in subset.iterrows():
start, end = row["deb"], row["fin"]
direction = row["sens"]
prot_id = str(row["protein_id"])
annotation = protein_function_map.get(prot_id)
color = get_color(annotation)
arrow = (
FancyArrowPatch((start, 0), (end, 0), arrowstyle="simple", color=color,
mutation_scale=15, linewidth=1.5)
if direction == "+"
else FancyArrowPatch((end, 0), (start, 0), arrowstyle="simple", color=color,
mutation_scale=15, linewidth=1.5)
)
ax_syn.add_patch(arrow)
# --- Coverage track ---
ax_cov = fig.add_axes([0.1, 0, 0.8, y_height_cov])
df_cov = pd.read_csv(coverage_folder, header=0, sep="\t", names=["contig", "position", "coverage"])
df_cov = df_cov.dropna()
df_cov["position"] = pd.to_numeric(df_cov["position"], errors="coerce")
df_cov["coverage"] = pd.to_numeric(df_cov["coverage"], errors="coerce")
# Re-index positions to start at 1 (coverage was computed on wPipPel genomic coordinates)
offset = df_cov["position"].min() - 1
df_cov["position"] = df_cov["position"] - offset
ax_cov.plot(df_cov["position"], df_cov["coverage"], color="blue", linewidth=0.7)
ax_cov.fill_between(df_cov["position"], 0, df_cov["coverage"], color="blue", alpha=0.2)
ax_cov.set_xlim(0, contig_len)
ax_cov.set_ylabel("Cov.", fontsize=6)
ax_cov.tick_params(axis="both", labelsize=6)
ax_cov.spines["top"].set_visible(False)
ax_cov.spines["right"].set_visible(False)
# === Legend ===
legend_elements = [
mpatches.Patch(color="#660066", label="Head"),
mpatches.Patch(color="#ff0000", label="Ankyrin"),
mpatches.Patch(color="#0432ff", label="Connector/Baseplate"),
mpatches.Patch(color="#ffc1c0", label="Tail Fiber"),
mpatches.Patch(color="#ff6666", label="Tail"),
mpatches.Patch(color="#ffa4ff", label="CifA/B"),
mpatches.Patch(color="#07f256", label="Recombinase"),
mpatches.Patch(color="#fcfc2d", label="Transposase"),
mpatches.Patch(color="#4aeaed", label="WOPC1"),
mpatches.Patch(color="#ff6600", label="Toxins"),
mpatches.Patch(color="#712ca3", label="LigA"),
mpatches.Patch(color="#7340f5", label="TR"),
mpatches.Patch(color="#593601", label="Patatin"),
mpatches.Patch(color="#272545", label="MutL"),
mpatches.Patch(color="#88d4eb", label="Replication/Repair"),
mpatches.Patch(color="#538201", label="Methylase"),
]
fig.legend(
handles=legend_elements,
loc="center left",
bbox_to_anchor=(0.91, 0.5),
title="Legend",
fontsize=8,
title_fontsize=9
)
# === Export ===
fig.savefig(output_path, bbox_inches="tight")
plt.show(fig)
print("Figure saved to:", output_path)

Figure saved to: Output/WOPip3_synt.pdf
The following inputs are required:
# WOPip4: gene synteny (functional categories) + isolate coverage track
# === Inputs / outputs ===
gff_path = "Data/WOPip4_gff.xlsx"
orthogroups_path = "Data/Orthogroups_Pip4.tsv"
coverage_folder = "Data/WOPip4.csv"
output_path = "Output/WOPip4_synt.pdf"
# === Load gene coordinates and orthogroup-derived annotations ===
df = pd.read_excel(gff_path)
orthogroups_df = pd.read_csv(orthogroups_path, sep="\t")
df.columns = [col.strip().lower() for col in df.columns]
df["protein_id"] = df["proteines"].str.extract(r"___(\d+)$")[0]
# Map protein IDs to functional labels (from orthogroup annotation)
protein_function_map = {}
for _, row in orthogroups_df.iterrows():
if pd.notna(row["PROTEINES_WOPip4"]):
for pid in row["PROTEINES_WOPip4"].split(","):
protein_function_map[pid.strip()] = str(row["WOqueries_041124"])
# === Functional category color mapping ===
def get_color(annotations):
"""Assign colors based on the primary functional label; prioritize cif annotations when present."""
if annotations is None:
return "grey"
annotations = annotations.split(",")
annotation = annotations[0]
for a in annotations:
if "cif" in a:
annotation = a
a = annotation
return {
"HEAD": "#660066",
"ANK": "#ff0000",
"CON_": "#0432ff",
"TailFiber": "#ffc1c0",
"Tail_": "#ff6666",
"RECOMB": "#07f256",
"LIGA": "#712ca3",
"TOXIN": "#de8209",
"WOPC1": "#4aeaed",
"cifA": "#ffa4ff",
"cifB": "#ffa4ff",
"TR": "#7340f5",
"PATATIN": "#593601",
"TNP": "#fcfc2d",
"MutL": "#272545",
"METHYL": "#538201",
"REP": "#88d4eb",
}.get(
next(
(k for k in [
"HEAD","ANK","CON_","TailFiber","Tail_","RECOMB","LIGA","TOXIN","WOPC1","cifA","cifB",
"TR","PATATIN","TNP","MutL","METHYL","REP"
] if k in a),
None
),
"#c7c5c5"
)
# === Figure layout: synteny track (top) + coverage track (bottom) ===
fig = plt.figure(figsize=(20, 3))
y_height_syn = 0.6
y_height_cov = 0.3
y_margin = 0.05
# This dataset corresponds to a single contig (WOPip4 region)
contig = df["contigs"].unique()[0]
subset = df[df["contigs"] == contig]
contig_len = subset["fin"].max()
# --- Synteny track ---
ax_syn = fig.add_axes([0.1, y_height_cov + y_margin, 0.8, y_height_syn])
ax_syn.set_xlim(0, contig_len)
ax_syn.set_ylim(-1, 1)
ax_syn.axis("off")
ax_syn.text(-1000, 0, "WOPip4", ha="right", va="center", fontsize=10, fontweight="bold")
ax_syn.hlines(0, 0, contig_len, color="black", linewidth=2, zorder=0)
# Draw gene arrows colored by functional category
for _, row in subset.iterrows():
start, end = row["deb"], row["fin"]
direction = row["sens"]
prot_id = str(row["protein_id"])
annotation = protein_function_map.get(prot_id)
color = get_color(annotation)
arrow = (
FancyArrowPatch((start, 0), (end, 0), arrowstyle="simple", color=color,
mutation_scale=15, linewidth=1.5)
if direction == "+"
else FancyArrowPatch((end, 0), (start, 0), arrowstyle="simple", color=color,
mutation_scale=15, linewidth=1.5)
)
ax_syn.add_patch(arrow)
# --- Coverage track ---
ax_cov = fig.add_axes([0.1, 0, 0.8, y_height_cov])
df_cov = pd.read_csv(coverage_folder, header=0, sep="\t", names=["contig", "position", "coverage"])
df_cov = df_cov.dropna()
df_cov["position"] = pd.to_numeric(df_cov["position"], errors="coerce")
df_cov["coverage"] = pd.to_numeric(df_cov["coverage"], errors="coerce")
# Re-index positions to start at 1 (coverage was computed on wPipPel genomic coordinates)
offset = df_cov["position"].min() - 1
df_cov["position"] = df_cov["position"] - offset
ax_cov.plot(df_cov["position"], df_cov["coverage"], color="blue", linewidth=0.7)
ax_cov.fill_between(df_cov["position"], 0, df_cov["coverage"], color="blue", alpha=0.2)
ax_cov.set_xlim(0, contig_len)
ax_cov.set_ylabel("Cov.", fontsize=6)
ax_cov.tick_params(axis="both", labelsize=6)
ax_cov.spines["top"].set_visible(False)
ax_cov.spines["right"].set_visible(False)
# === Legend ===
legend_elements = [
mpatches.Patch(color="#660066", label="Head"),
mpatches.Patch(color="#ff0000", label="Ankyrin"),
mpatches.Patch(color="#0432ff", label="Connector/Baseplate"),
mpatches.Patch(color="#ffc1c0", label="Tail Fiber"),
mpatches.Patch(color="#ff6666", label="Tail"),
mpatches.Patch(color="#ffa4ff", label="CifA/B"),
mpatches.Patch(color="#07f256", label="Recombinase"),
mpatches.Patch(color="#fcfc2d", label="Transposase"),
mpatches.Patch(color="#4aeaed", label="WOPC1"),
mpatches.Patch(color="#ff6600", label="Toxins"),
mpatches.Patch(color="#712ca3", label="LigA"),
mpatches.Patch(color="#7340f5", label="TR"),
mpatches.Patch(color="#593601", label="Patatin"),
mpatches.Patch(color="#272545", label="MutL"),
mpatches.Patch(color="#88d4eb", label="Replication/Repair"),
mpatches.Patch(color="#538201", label="Methylase"),
]
fig.legend(
handles=legend_elements,
loc="center left",
bbox_to_anchor=(0.91, 0.5),
title="Legend",
fontsize=8,
title_fontsize=9
)
# === Export ===
fig.savefig(output_path, bbox_inches="tight")
plt.show(fig)
print("Figure saved to:", output_path)

Figure saved to: Output/WOPip4_synt.pdf
The following inputs are required:
# WOPip5: gene synteny (functional categories) + isolate coverage track
# === Inputs / outputs ===
gff_path = "Data/WOPip5_gff.xlsx"
orthogroups_path = "Data/Orthogroups_Pip5.tsv"
coverage_folder = "Data/WOPip5.csv"
output_path = "Output/WOPip5_synt.pdf"
# === Load gene coordinates and orthogroup-derived annotations ===
df = pd.read_excel(gff_path)
orthogroups_df = pd.read_csv(orthogroups_path, sep="\t")
df.columns = [col.strip().lower() for col in df.columns]
df["protein_id"] = df["proteines"].str.extract(r"___(\d+)$")[0]
# Map protein IDs to functional labels (from orthogroup annotation)
protein_function_map = {}
for _, row in orthogroups_df.iterrows():
if pd.notna(row["PROTEINES_WOPip5"]):
for pid in row["PROTEINES_WOPip5"].split(","):
protein_function_map[pid.strip()] = str(row["WOqueries_041124"])
# === Functional category color mapping ===
def get_color(annotations):
"""Assign colors based on the primary functional label; prioritize cif annotations when present."""
if annotations is None:
return "grey"
annotations = annotations.split(",")
annotation = annotations[0]
for a in annotations:
if "cif" in a:
annotation = a
a = annotation
return {
"HEAD": "#660066",
"ANK": "#ff0000",
"CON_": "#0432ff",
"TailFiber": "#ffc1c0",
"Tail_": "#ff6666",
"RECOMB": "#07f256",
"LIGA": "#712ca3",
"TOXIN": "#de8209",
"WOPC1": "#4aeaed",
"cifA": "#ffa4ff",
"cifB_": "#ffa4ff",
"TR": "#7340f5",
"PATATIN": "#593601",
"TNP": "#fcfc2d",
"MutL": "#272545",
"METHYL": "#538201",
"REP": "#88d4eb",
}.get(
next(
(k for k in [
"HEAD","ANK","CON_","TailFiber","Tail_","RECOMB","LIGA","TOXIN","WOPC1","cifA","cifB_",
"TR","PATATIN","TNP","MutL","METHYL","REP"
] if k in a),
None
),
"#c7c5c5"
)
# === Figure layout: synteny track (top) + coverage track (bottom) ===
fig = plt.figure(figsize=(20, 3))
y_height_syn = 0.6
y_height_cov = 0.3
y_margin = 0.05
# This dataset corresponds to a single contig (WOPip5 region)
contig = df["contigs"].unique()[0]
subset = df[df["contigs"] == contig]
contig_len = subset["fin"].max()
# --- Synteny track ---
ax_syn = fig.add_axes([0.1, y_height_cov + y_margin, 0.8, y_height_syn])
ax_syn.set_xlim(0, contig_len)
ax_syn.set_ylim(-1, 1)
ax_syn.axis("off")
ax_syn.text(-1000, 0, "WOPip5", ha="right", va="center", fontsize=10, fontweight="bold")
ax_syn.hlines(0, 0, contig_len, color="black", linewidth=2, zorder=0)
# Draw gene arrows colored by functional category
for _, row in subset.iterrows():
start, end = row["deb"], row["fin"]
direction = row["sens"]
prot_id = str(row["protein_id"])
annotation = protein_function_map.get(prot_id)
color = get_color(annotation)
arrow = (
FancyArrowPatch((start, 0), (end, 0), arrowstyle="simple", color=color,
mutation_scale=15, linewidth=1.5)
if direction == "+"
else FancyArrowPatch((end, 0), (start, 0), arrowstyle="simple", color=color,
mutation_scale=15, linewidth=1.5)
)
ax_syn.add_patch(arrow)
# --- Coverage track ---
ax_cov = fig.add_axes([0.1, 0, 0.8, y_height_cov])
df_cov = pd.read_csv(coverage_folder, header=0, sep="\t", names=["contig", "position", "coverage"])
df_cov = df_cov.dropna()
df_cov["position"] = pd.to_numeric(df_cov["position"], errors="coerce")
df_cov["coverage"] = pd.to_numeric(df_cov["coverage"], errors="coerce")
# Re-index positions to start at 1 (coverage was computed on wPipPel genomic coordinates)
offset = df_cov["position"].min() - 1
df_cov["position"] = (len(df_cov['position']) - (df_cov["position"] - offset) + 1) # reverse region
ax_cov.plot(df_cov["position"], df_cov["coverage"], color='blue', linewidth=0.7)
ax_cov.fill_between(df_cov["position"], 0, df_cov["coverage"], color='blue', alpha=0.2)
ax_cov.set_xlim(0, contig_len)
ax_cov.set_ylabel("Cov.", fontsize=6)
ax_cov.tick_params(axis='both', labelsize=6)
ax_cov.spines['top'].set_visible(False)
ax_cov.spines['right'].set_visible(False)
# === Legend ===
legend_elements = [
mpatches.Patch(color="#660066", label="Head"),
mpatches.Patch(color="#ff0000", label="Ankyrin"),
mpatches.Patch(color="#0432ff", label="Connector/Baseplate"),
mpatches.Patch(color="#ffc1c0", label="Tail Fiber"),
mpatches.Patch(color="#ff6666", label="Tail"),
mpatches.Patch(color="#ffa4ff", label="CifA/B"),
mpatches.Patch(color="#07f256", label="Recombinase"),
mpatches.Patch(color="#fcfc2d", label="Transposase"),
mpatches.Patch(color="#4aeaed", label="WOPC1"),
mpatches.Patch(color="#ff6600", label="Toxins"),
mpatches.Patch(color="#712ca3", label="LigA"),
mpatches.Patch(color="#7340f5", label="TR"),
mpatches.Patch(color="#593601", label="Patatin"),
mpatches.Patch(color="#272545", label="MutL"),
mpatches.Patch(color="#88d4eb", label="Replication/Repair"),
mpatches.Patch(color="#538201", label="Methylase"),
]
fig.legend(
handles=legend_elements,
loc="center left",
bbox_to_anchor=(0.91, 0.5),
title="Legend",
fontsize=8,
title_fontsize=9
)
# === Export ===
fig.savefig(output_path, bbox_inches="tight")
plt.show(fig)
print("Figure saved to:", output_path)

Figure saved to: Output/WOPip5_synt.pdf