Spaces:
Sleeping
Sleeping
File size: 12,097 Bytes
eb23a18 | 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 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 | """Command-line interface for the Proteoform Analyzer.
Usage:
python -m proteoform_analyzer.cli run --fast --protein hemoglobin
python -m proteoform_analyzer.cli run --fast --protein ttr
python -m proteoform_analyzer.cli run --fast --protein p53
python -m proteoform_analyzer.cli run --uniprot P02766 --n-subunits 4 ...
python -m proteoform_analyzer.cli list-steps
python -m proteoform_analyzer.cli gui
"""
from __future__ import annotations
import argparse
import json
import sys
import logging
import os
from .core.config import (
AnalysisConfig, EngineChoice, ProteoformMode, BindingSiteMethod,
PTMConfig, MDConfig, Boltz2Config, AntibodyConfig, HotspotSource,
hemoglobin_fast_config, ttr_fast_config, p53_fast_config,
hemoglobin_standard_config, ttr_standard_config, p53_standard_config,
hemoglobin_production_config, ttr_production_config, p53_production_config,
PRESETS,
)
from .core.pipeline import run_analysis, STEP_REGISTRY
def _print_callback(step: str, status: str, message: str):
flag = {"ok": "[+]", "skipped": "[~]", "failed": "[!]"}.get(status, "[?]")
print(f"{flag} {step}: {message}", flush=True)
def cmd_list_steps(args):
print("Available pipeline steps:")
for name, (desc, _) in STEP_REGISTRY.items():
print(f" {name:16s} - {desc}")
def cmd_gui(args):
from .gui import launch
launch()
# Use the PRESETS dict from config.py (9 presets)
_PRESETS = PRESETS
# Legacy mapping for backward compat (old --fast --protein style)
_PRESETS_LEGACY = {
"hemoglobin": hemoglobin_fast_config,
"ttr": ttr_fast_config,
"p53": p53_fast_config,
}
def cmd_run(args):
"""Run the pipeline from CLI flags."""
if args.preset and args.preset in _PRESETS:
config = _PRESETS[args.preset]()
elif args.fast and args.protein in _PRESETS_LEGACY:
config = _PRESETS_LEGACY[args.protein]()
else:
uniprot_ids = args.uniprot.split(",") if args.uniprot else []
mutations = []
if args.mutations:
mut_lists = [m.strip().split() for m in args.mutations.split("|")]
mutations = mut_lists
# Parse stoichiometry: "2,2" -> [2,2]
stoich = [int(x) for x in args.stoichiometry.split(",")] if args.stoichiometry else [args.n_subunits]
# Parse chain map if provided: "P69905:A,C;P68871:B,D"
chain_map = {}
if args.chain_map:
for entry in args.chain_map.split(";"):
if ":" in entry:
uid, chains = entry.split(":", 1)
chain_map[uid] = chains.split(",")
# Parse ligand SDFs
ligand_sdfs = args.ligand_sdfs.split(",") if args.ligand_sdfs else None
# Boltz-2 config (structure folding + optional local binary)
boltz2_cfg = Boltz2Config(
local_binary=args.boltz2_local_binary,
prefer_local=bool(args.boltz2_local_binary),
)
# Antibody (RFAntibody) config
hotspots = [h.strip() for h in args.antibody_hotspots.split(",")
if h.strip()] if args.antibody_hotspots else []
antibody_cfg = AntibodyConfig(
enabled=args.antibody,
framework=args.antibody_framework,
hotspot_source=args.antibody_hotspot_source,
hotspot_residues=hotspots,
num_designs=args.antibody_num_designs,
local_rfantibody_dir=args.antibody_rfantibody_dir,
local_weights_dir=args.antibody_weights_dir,
)
# Determine steps; auto-append 'antibody' if enabled and not already listed.
steps = args.steps.split(",") if args.steps else None
if args.antibody:
if steps is None:
steps = list(AnalysisConfig.default_steps()) + ["antibody"]
elif "antibody" not in steps:
steps = steps + ["antibody"]
config = AnalysisConfig(
uniprot_ids=uniprot_ids,
n_subunits=args.n_subunits,
subunit_stoichiometry=stoich,
chain_map=chain_map,
mutations=mutations,
max_mutations=args.max_mutations,
proteoform_mode=args.proteoform_mode,
proteoform_cap=args.proteoform_cap,
structure_source=args.structure_source,
local_pdb_id=args.local_pdb_id,
boltz2=boltz2_cfg,
antibody=antibody_cfg,
run_ptm=not args.no_ptm,
run_md=not args.no_md,
md=MDConfig(engine=args.md_engine, production_steps=args.md_steps),
docking_engine=args.docking_engine,
ligand_design_engine=args.ligand_design_engine,
binding_site_method=args.binding_site_method,
ligand_sdfs=ligand_sdfs,
ensemble_docking=args.ensemble_docking,
ensemble_n_snapshots=args.ensemble_n_snapshots,
thermompnn_dir=args.thermompnn_dir,
thermompnn_checkpoint=args.thermompnn_checkpoint,
steps=steps,
work_dir=args.work_dir,
)
config.progress_callback = _print_callback
print(f"=== Proteoform Analyzer: {config.name} ===", flush=True)
print(f"Subunits: {config.n_subunits} | Stoichiometry: {config.subunit_stoichiometry} | "
f"{'hetero' if config.is_hetero else 'homo'}", flush=True)
print(f"UniProt IDs: {config.uniprot_ids}", flush=True)
print(f"Proteoform mode: {config.proteoform_mode} | Binding site: {config.binding_site_method}", flush=True)
print(f"Engines: MD={config.md.engine} | docking={config.docking_engine} | "
f"ligand_design={config.ligand_design_engine}", flush=True)
print(f"Steps: {config.steps}", flush=True)
print(flush=True)
results = run_analysis(config)
print("\n=== Summary ===", flush=True)
for r in results:
flag = {"ok": "OK", "skipped": "SKIP", "failed": "FAIL"}.get(r.status, "?")
print(f" [{flag}] {r.step:16s} {r.message}", flush=True)
status_csv = os.path.join(config.results_dir(), "step_status.csv")
import pandas as pd
pd.DataFrame([{"step": r.step, "status": r.status, "message": r.message,
"elapsed_s": r.elapsed_s, "n_outputs": len(r.outputs)}
for r in results]).to_csv(status_csv, index=False)
print(f"\nStep-status table saved to {status_csv}", flush=True)
def build_parser():
p = argparse.ArgumentParser(
prog="proteoform_analyzer",
description="Analyze mutations, PTMs, and proteoforms in oligomeric proteins.",
)
sub = p.add_subparsers(dest="command", required=True)
# run
pr = sub.add_parser("run", help="Run the pipeline")
pr.add_argument("--preset", default=None,
choices=list(PRESETS.keys()),
help="Preset configuration (e.g. 'Hemoglobin — Fast')")
pr.add_argument("--fast", action="store_true", help="Use fast preset (legacy)")
pr.add_argument("--protein", default="hemoglobin",
choices=["hemoglobin", "ttr", "p53"],
help="Preset protein (legacy, with --fast)")
pr.add_argument("--uniprot", help="UniProt ID(s), comma-separated")
pr.add_argument("--mutations", help="Mutations per subunit, pipe-separated (e.g. 'E7V D95H|D75A')")
pr.add_argument("--n-subunits", type=int, default=4, help="Number of subunits (1=monomer, 4=tetramer)")
pr.add_argument("--stoichiometry", default=None, help="Comma-separated copy counts (e.g. '2,2')")
pr.add_argument("--chain-map", default=None, help="Chain map: 'P69905:A,C;P68871:B,D'")
pr.add_argument("--max-mutations", type=int, default=10)
pr.add_argument("--proteoform-mode", default="pairwise", choices=["single", "pairwise"])
pr.add_argument("--proteoform-cap", type=int, default=100)
pr.add_argument("--structure-source", default="boltz2", choices=["boltz2"],
help="Structure source (Boltz-2 folding via the Boltz API, "
"a local binary via --boltz2-local-binary, or a "
"backbone-identical graft fallback)")
pr.add_argument("--boltz2-local-binary", default=None,
help="Path to a local 'boltz' binary; if set, fold locally "
"(also used for Boltz-2 docking)")
pr.add_argument("--local-pdb-id", default=None,
help="Reference PDB ID for binding-site alignment ONLY "
"(not a structure source)")
pr.add_argument("--md-engine", default="openmm", choices=["openmm", "gromacs"])
pr.add_argument("--md-steps", type=int, default=5000)
pr.add_argument("--docking-engine", default="vina", choices=["vina", "boltz2", "diffdock"])
pr.add_argument("--ligand-design-engine", default="diffsbdd", choices=["diffsbdd", "boltzgen"])
pr.add_argument("--binding-site-method", default="auto",
choices=["auto", "reference", "p2rank", "alphasphere", "user"])
pr.add_argument("--ligand-sdfs", default=None, help="Comma-separated SDF paths for multi-ligand screening")
pr.add_argument("--ensemble-docking", action="store_true", help="Dock against MD snapshots")
pr.add_argument("--ensemble-n-snapshots", type=int, default=3)
# --- Antibody design (RFAntibody; opt-in) ---
pr.add_argument("--antibody", action="store_true",
help="Enable de novo antibody/nanobody design (RFAntibody). "
"Adds the 'antibody' step automatically.")
pr.add_argument("--antibody-framework", default="nanobody",
choices=["nanobody", "scfv"],
help="Antibody framework class; resolved from the local "
"RFAntibody checkout's example inputs")
pr.add_argument("--antibody-hotspots", default=None,
help="Comma-separated target hotspot residues for hotspot-source "
"'user' (e.g. '305,456' or 'A305,A456')")
pr.add_argument("--antibody-hotspot-source", default="user",
choices=["user", "mhc_i", "mhc_ii", "bcell"],
help="How to choose epitope hotspots: user-provided, or AI "
"prediction (bcell available; mhc_i/mhc_ii are stubs)")
pr.add_argument("--antibody-num-designs", type=int, default=20,
help="Number of RFdiffusion designs to generate")
pr.add_argument("--antibody-rfantibody-dir", default=None,
help="Path to a local RFAntibody checkout (scripts/ + weights/). "
"Required to run antibody design; otherwise the step skips.")
pr.add_argument("--antibody-weights-dir", default=None,
help="Dir with the RFdiffusion_Ab.pt checkpoint "
"(defaults to <rfantibody-dir>/weights)")
# --- ddG / ThermoMPNN (local; optional, falls back to ESM2 zero-shot) ---
pr.add_argument("--thermompnn-dir", default=None,
help="Path to a local ThermoMPNN checkout (expects "
"custom_inference.py under it). If unset, ddG Tier-1 "
"falls back to the ESM2 zero-shot CPU scorer.")
pr.add_argument("--thermompnn-checkpoint", default=None,
help="Path to a ThermoMPNN model checkpoint (required to run "
"ThermoMPNN locally; otherwise ESM2 zero-shot is used)")
pr.add_argument("--no-ptm", action="store_true")
pr.add_argument("--no-md", action="store_true")
pr.add_argument("--steps", default=None, help="Comma-separated step names (default: all)")
pr.add_argument("--work-dir", default="results")
pr.set_defaults(func=cmd_run)
pls = sub.add_parser("list-steps", help="List available pipeline steps")
pls.set_defaults(func=cmd_list_steps)
pg = sub.add_parser("gui", help="Launch the Gradio web GUI")
pg.set_defaults(func=cmd_gui)
return p
def main(argv=None):
parser = build_parser()
args = parser.parse_args(argv)
args.func(args)
if __name__ == "__main__":
main()
|