-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgraffiti_run.py
More file actions
361 lines (294 loc) · 14.5 KB
/
Copy pathgraffiti_run.py
File metadata and controls
361 lines (294 loc) · 14.5 KB
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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
GRAFFITI — Unified Pipeline Runner
===================================
Grafting Routine for Automated Fragment Fitting,
Insertion, and Targeted Immunogenic epitopes
Pipeline stages:
Step 1 — Parallel motif grafting (all scaffold × epitope pairs)
Step 2 — Overlap resolution, SASA scoring, top scaffold selection
Step 3 — ProteinMPNN sequence design on selected scaffolds
[Step 4] AlphaFold2 — run externally, results analysed in Step 5
Step 5 — Post-AF2 analysis: pLDDT, RMSD, SASA per region
Usage:
Run full pipeline from scratch:
python graffiti_run.py --steps 1 2 3
Resume from Step 2 (grafting already done):
python graffiti_run.py --steps 2 3
Run only post-AF2 analysis:
python graffiti_run.py --steps 5
Override any config value on the command line:
python graffiti_run.py --steps 1 2 3 --cpus 16 --top_percent 10
Dry run (print config and exit):
python graffiti_run.py --dry_run
"""
import os
import sys
import glob
import argparse
import textwrap
import subprocess
from datetime import datetime
# ══════════════════════════════════════════════════════════════════════
# ██ MASTER CONFIG — edit everything here, nowhere else
# ══════════════════════════════════════════════════════════════════════
CFG = {
# ── Directories ──────────────────────────────────────────────────
"scaffolds_dir": "few_dummies/", # input scaffold PDBs
"epitopes_dir": "Epitopes/", # input epitope PDBs
"grafts_dir": "Grafts_individual/", # step 1 output
"optimized_dir": "Grafts_optimized/", # step 2 output
"mpnn_dir": "MPNN_out/", # step 3 output
"af2_dir": "AlphaFold_results/", # step 4 input (run externally)
"af2_analysis_dir": "AF2_analysis/", # step 5 output
# ── Step 1: Grafting ─────────────────────────────────────────────
"cpus": 8, # parallel workers (None = all available)
# ── Step 2: Overlap resolution + filtering ───────────────────────
"overlap_buffer": 0, # residue margin between graft ranges
"top_percent": 20, # % of top scaffolds to carry forward
"min_grafts": None, # None = auto (max observed); int = hard floor
"n_workers_step2": 8, # workers for parallel PDB generation
# ── Step 3: ProteinMPNN ──────────────────────────────────────────
"mpnn_chain": "A", # chain to design
"mpnn_num_seq": 10, # sequences per scaffold
"mpnn_temp": "0.2", # sampling temperature
"mpnn_model": "v_48_020",
"mpnn_script": "", # path to protein_mpnn_run.py (auto-detected if empty)
# ── Step 5: Post-AF2 analysis ────────────────────────────────────
"rmsd_threshold": 2.0, # Å — epitope RMSD cutoff for quality flag
"plddt_threshold": 70.0, # global pLDDT minimum for quality flag
}
# ══════════════════════════════════════════════════════════════════════
# HELPERS
# ══════════════════════════════════════════════════════════════════════
def banner(title, width=60):
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print()
print("=" * width)
print(f" {title}")
print(f" {ts}")
print("=" * width)
def section(msg):
print(f"\n── {msg}")
def check_dir(path, label):
if not os.path.isdir(path):
print(f" ERROR: {label} directory not found: {path}")
sys.exit(1)
pdbs = glob.glob(os.path.join(path, "*.pdb"))
print(f" {label}: {path} ({len(pdbs)} PDB files)")
return pdbs
def find_latest_csv(directory, pattern):
matches = sorted(glob.glob(os.path.join(directory, pattern)))
if not matches:
return None
return matches[-1]
def run(cmd, label):
"""Run a shell command, streaming output, exit on failure."""
print(f"\n $ {cmd}\n")
ret = subprocess.call(cmd, shell=True)
if ret != 0:
print(f"\n ERROR: {label} exited with code {ret}")
sys.exit(ret)
# ══════════════════════════════════════════════════════════════════════
# PIPELINE STEPS
# ══════════════════════════════════════════════════════════════════════
def step1_grafting(cfg):
banner("STEP 1 — Parallel Motif Grafting")
check_dir(cfg["scaffolds_dir"], "Scaffolds")
check_dir(cfg["epitopes_dir"], "Epitopes")
cpus = cfg["cpus"] or ""
cpus_arg = f"--cpus {cpus}" if cpus else ""
run(
f"python step1_paralel.py "
f"--scaffolds {cfg['scaffolds_dir']} "
f"--epitopes {cfg['epitopes_dir']} "
f"--output {cfg['grafts_dir']} "
f"{cpus_arg}",
"Step 1 — Grafting"
)
csv = find_latest_csv(cfg["grafts_dir"], "graft_map_*.csv")
if csv:
print(f"\n Output CSV: {csv}")
else:
print("\n WARNING: no graft_map CSV found after step 1")
def step2_overlap(cfg):
banner("STEP 2 — Overlap Resolution + Filtering")
csv = find_latest_csv(cfg["grafts_dir"], "graft_map_*.csv")
if not csv:
print(f" ERROR: no graft_map CSV found in {cfg['grafts_dir']}")
print(" Run Step 1 first.")
sys.exit(1)
print(f" Using CSV: {csv}")
min_grafts_arg = f"--min_grafts {cfg['min_grafts']}" if cfg["min_grafts"] else ""
run(
f"python step2.py "
f"--phase1_csv {csv} "
f"--scaffolds_dir {cfg['scaffolds_dir']} "
f"--epitopes_dir {cfg['epitopes_dir']} "
f"--output_dir {cfg['optimized_dir']} "
f"--overlap_buffer {cfg['overlap_buffer']} "
f"--top_percent {cfg['top_percent']} "
f"--n_workers {cfg['n_workers_step2']} "
f"{min_grafts_arg}",
"Step 2 — Overlap + Filtering"
)
top_dir = os.path.join(cfg["optimized_dir"], "top_pdbs/")
pdbs = glob.glob(os.path.join(cfg["optimized_dir"], "**/*.pdb"), recursive=True)
print(f"\n Optimized PDBs generated: {len(pdbs)}")
def step3_mpnn(cfg):
banner("STEP 3 — ProteinMPNN Sequence Design")
# Find the top scaffold PDBs from step 2
top_dirs = sorted(glob.glob(os.path.join(cfg["optimized_dir"], "top*pct*/")))
if top_dirs:
input_dir = top_dirs[-1] # most recently created top% folder
else:
# fallback: use all final PDBs
input_dir = os.path.join(cfg["optimized_dir"], "final_pdbs/")
if not os.path.isdir(input_dir):
print(f" ERROR: could not find input PDBs for MPNN in {cfg['optimized_dir']}")
print(" Run Step 2 first.")
sys.exit(1)
pdbs = glob.glob(os.path.join(input_dir, "*.pdb"))
print(f" Input PDBs: {input_dir} ({len(pdbs)} files)")
mpnn_script_arg = (f"--mpnn_script {cfg['mpnn_script']}"
if cfg["mpnn_script"] else "")
run(
f"python proteinmpnn_design.py "
f"{input_dir} "
f"{cfg['mpnn_dir']} "
f"--epitopes_dir {cfg['epitopes_dir']} "
f"--chain {cfg['mpnn_chain']} "
f"--num_seq {cfg['mpnn_num_seq']} "
f"--temp {cfg['mpnn_temp']} "
f"--model {cfg['mpnn_model']} "
f"{mpnn_script_arg}",
"Step 3 — ProteinMPNN"
)
fas = glob.glob(os.path.join(cfg["mpnn_dir"], "**/*.fa"), recursive=True)
print(f"\n .fa files generated: {len(fas)}")
print(f"\n ┌─ NEXT STEP ────────────────────────────────────────")
print(f" │ Run AlphaFold2 on the sequences in: {cfg['mpnn_dir']}")
print(f" │ Then save the PDB models to: {cfg['af2_dir']}")
print(f" │ Then run: python graffiti_run.py --steps 5")
print(f" └────────────────────────────────────────────────────")
def step5_af2_analysis(cfg):
banner("STEP 5 — Post-AlphaFold2 Analysis")
check_dir(cfg["af2_dir"], "AlphaFold results")
check_dir(cfg["epitopes_dir"], "Epitopes")
run(
f"python post_alphafold_analysis.py "
f"--af2_dir {cfg['af2_dir']} "
f"--epitopes_dir {cfg['epitopes_dir']} "
f"--output_dir {cfg['af2_analysis_dir']}",
"Step 5 — Post-AF2 analysis"
)
csvs = glob.glob(os.path.join(cfg["af2_analysis_dir"], "*.csv"))
print(f"\n Analysis files: {len(csvs)}")
# ══════════════════════════════════════════════════════════════════════
# CLI + MAIN
# ══════════════════════════════════════════════════════════════════════
def parse_args():
parser = argparse.ArgumentParser(
description=textwrap.dedent("""\
GRAFFITI — Unified Pipeline Runner
Steps:
1 Parallel motif grafting (scaffold × epitope)
2 Overlap resolution, SASA scoring, top selection
3 ProteinMPNN sequence design
[4 AlphaFold2 — run externally]
5 Post-AF2 analysis (pLDDT, RMSD, SASA)
"""),
formatter_class=argparse.RawTextHelpFormatter,
)
parser.add_argument(
"--steps", nargs="+", type=int,
default=[1, 2, 3],
metavar="N",
help="Steps to run, e.g. --steps 1 2 3 or --steps 2 (default: 1 2 3)"
)
# override any CFG key from CLI
parser.add_argument("--scaffolds_dir", type=str)
parser.add_argument("--epitopes_dir", type=str)
parser.add_argument("--grafts_dir", type=str)
parser.add_argument("--optimized_dir", type=str)
parser.add_argument("--mpnn_dir", type=str)
parser.add_argument("--af2_dir", type=str)
parser.add_argument("--af2_analysis_dir", type=str)
parser.add_argument("--cpus", type=int)
parser.add_argument("--overlap_buffer", type=int)
parser.add_argument("--top_percent", type=int)
parser.add_argument("--min_grafts", type=int)
parser.add_argument("--n_workers_step2", type=int)
parser.add_argument("--mpnn_num_seq", type=int)
parser.add_argument("--mpnn_temp", type=str)
parser.add_argument("--mpnn_chain", type=str)
parser.add_argument("--mpnn_script", type=str)
parser.add_argument("--dry_run", action="store_true",
help="Print config and exit without running anything")
return parser.parse_args()
def main():
args = parse_args()
# Apply CLI overrides into CFG
for key in CFG:
val = getattr(args, key, None)
if val is not None:
CFG[key] = val
steps = sorted(set(args.steps))
# ── print config ─────────────────────────────────────────────
banner("GRAFFITI — Pipeline Configuration")
print(f" Steps to run : {steps}")
print()
print(f" Directories")
print(f" Scaffolds : {CFG['scaffolds_dir']}")
print(f" Epitopes : {CFG['epitopes_dir']}")
print(f" Grafts (step 1) : {CFG['grafts_dir']}")
print(f" Optimized (step2): {CFG['optimized_dir']}")
print(f" MPNN out (step3) : {CFG['mpnn_dir']}")
print(f" AF2 in (step4) : {CFG['af2_dir']}")
print(f" AF2 out (step5) : {CFG['af2_analysis_dir']}")
print()
print(f" Step 1 — Grafting")
print(f" CPUs : {CFG['cpus'] or 'all available'}")
print()
print(f" Step 2 — Overlap + Filter")
print(f" Overlap buffer : {CFG['overlap_buffer']} residues")
print(f" Top percent : {CFG['top_percent']}%")
print(f" Min grafts : {CFG['min_grafts'] or 'auto (max observed)'}")
print(f" Workers : {CFG['n_workers_step2'] or 'all available'}")
print()
print(f" Step 3 — ProteinMPNN")
print(f" Chain : {CFG['mpnn_chain']}")
print(f" Sequences/target : {CFG['mpnn_num_seq']}")
print(f" Temperature : {CFG['mpnn_temp']}")
print(f" Model : {CFG['mpnn_model']}")
print()
print(f" Step 5 — Post-AF2")
print(f" pLDDT threshold : {CFG['plddt_threshold']}")
print(f" RMSD threshold : {CFG['rmsd_threshold']} Å")
if args.dry_run:
print("\n Dry run — exiting.")
return
t_start = datetime.now()
# ── run steps ─────────────────────────────────────────────────
step_fns = {
1: step1_grafting,
2: step2_overlap,
3: step3_mpnn,
5: step5_af2_analysis,
}
for s in steps:
if s == 4:
banner("STEP 4 — AlphaFold2 [external]")
print(" AlphaFold2 must be run externally.")
print(f" Input sequences : {CFG['mpnn_dir']}")
print(f" Save PDB models : {CFG['af2_dir']}")
continue
if s not in step_fns:
print(f" WARNING: unknown step {s}, skipping")
continue
step_fns[s](CFG)
elapsed = datetime.now() - t_start
banner(f"Pipeline finished ({str(elapsed).split('.')[0]} elapsed)")
if __name__ == "__main__":
main()