Files
PlantAI-dev/scripts/pep_v2/subcellular.py
T
2026-08-03 18:42:47 +08:00

128 lines
5.1 KiB
Python
Executable File

"""WoLF PSORT parse helpers for PEP v2."""
from __future__ import annotations
import re
import subprocess
import sys
from pathlib import Path
from typing import Dict, List, Tuple
PROJECT_ROOT = Path(__file__).resolve().parents[2]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from scripts.pep_v2.common import external_tool_timeout
LOCATION_LABELS = {
"nucl": "Nucleus", "cyto": "Cytoplasm", "chlo": "Chloroplast", "mito": "Mitochondrion",
"plas": "Plasma membrane", "vacu": "Vacuole", "golg": "Golgi apparatus", "extr": "Extracellular",
"pero": "Peroxisome", "e.r.": "Endoplasmic reticulum", "er": "Endoplasmic reticulum",
"cysk": "Cytoskeleton", "cyto_nucl": "Cytoplasm / Nucleus",
}
LOCATION_ORDER = [
"Nucleus", "Cytoplasm", "Cytoplasm / Nucleus", "Chloroplast", "Mitochondrion",
"Plasma membrane", "Endoplasmic reticulum", "Golgi apparatus", "Vacuole",
"Extracellular", "Peroxisome", "Cytoskeleton",
]
def normalize_location_label(label: str) -> str:
clean = re.sub(r"\s+", " ", str(label or "").strip())
if not clean:
return ""
key = clean.lower().replace("_", " ")
if key in LOCATION_LABELS:
return LOCATION_LABELS[key]
if clean.lower() in LOCATION_LABELS:
return LOCATION_LABELS[clean.lower()]
return clean[:1].upper() + clean[1:]
def run_wolfpsort(fasta_file: Path, output_file: Path) -> Dict[str, object]:
script = PROJECT_ROOT / "scripts/wolfpsort_batch.py"
if not script.exists():
return {"success": False, "stderr": f"WoLF PSORT script not found: {script}"}
if output_file.exists():
output_file.unlink()
started_at = __import__("time").time()
timeout_sec = external_tool_timeout(fasta_file, seconds_per_sequence=90)
result = subprocess.run(
[sys.executable, str(script), "--input", str(fasta_file), "--output", str(output_file)],
capture_output=True,
text=True,
timeout=timeout_sec,
)
if result.returncode != 0:
return {"success": False, "stderr": result.stderr or result.stdout or "WoLF PSORT failed"}
if not output_file.exists() or output_file.stat().st_size == 0 or output_file.stat().st_mtime < started_at:
return {"success": False, "stderr": "WoLF PSORT did not generate output"}
return {"success": True, "output_file": output_file, "method": "WoLF PSORT web (Plant)"}
def parse_wolfpsort_scores(path: Path) -> Tuple[Dict[str, Dict[str, float]], Dict[str, str]]:
scores: Dict[str, Dict[str, float]] = {}
primary: Dict[str, str] = {}
pattern = re.compile(r"([^:,]+):\s*([\d.]+)")
with open(path, encoding="utf-8", errors="ignore") as handle:
for raw in handle:
line = raw.strip()
if not line or line.lower().startswith("protein_id"):
continue
parts = line.split("\t")
if len(parts) < 2:
continue
protein_id = parts[0].strip()
loc_scores: Dict[str, float] = {}
for site, value in pattern.findall(parts[1]):
label = normalize_location_label(site.strip())
if label:
loc_scores[label] = loc_scores.get(label, 0.0) + float(value)
if loc_scores:
scores[protein_id] = loc_scores
primary[protein_id] = max(loc_scores.items(), key=lambda item: item[1])[0]
return scores, primary
def short_label(protein_id: str, max_len: int = 28) -> str:
return protein_id if len(protein_id) <= max_len else protein_id[: max_len - 3] + "..."
def write_subcellular_tables(
scores: Dict[str, Dict[str, float]],
primary: Dict[str, str],
analysis_dir: Path,
final_dir: Path,
) -> Path:
analysis_dir.mkdir(parents=True, exist_ok=True)
locations = sorted(
{loc for loc_scores in scores.values() for loc in loc_scores},
key=lambda loc: (LOCATION_ORDER.index(loc) if loc in LOCATION_ORDER else len(LOCATION_ORDER), loc),
)
protein_order = sorted(
scores.keys(),
key=lambda pid: (
LOCATION_ORDER.index(primary[pid]) if primary.get(pid) in LOCATION_ORDER else len(LOCATION_ORDER),
primary.get(pid, ""),
pid,
),
)
scores_tsv = analysis_dir / "subcellular_scores.tsv"
with open(scores_tsv, "w", encoding="utf-8") as handle:
handle.write("protein_id\tprotein_label\tprimary_location\t" + "\t".join(locations) + "\n")
for protein_id in protein_order:
loc_scores = scores[protein_id]
row = [protein_id, short_label(protein_id), primary.get(protein_id, "")]
row.extend(str(loc_scores.get(loc, 0.0)) for loc in locations)
handle.write("\t".join(row) + "\n")
counts: Dict[str, int] = {}
for loc in primary.values():
counts[loc] = counts.get(loc, 0) + 1
summary_tsv = final_dir / "subcellular_data.tsv"
with open(summary_tsv, "w", encoding="utf-8") as handle:
handle.write("location\tcount\tsource\n")
for loc, count in sorted(counts.items(), key=lambda item: item[1], reverse=True):
handle.write(f"{loc}\t{count}\tWoLF PSORT\n")
return scores_tsv