pocketscribe 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pocketscribe/__init__.py +23 -0
- pocketscribe/citations.py +251 -0
- pocketscribe/cli.py +315 -0
- pocketscribe/confidence.py +296 -0
- pocketscribe/config.py +111 -0
- pocketscribe/consensus.py +767 -0
- pocketscribe/data/__init__.py +68 -0
- pocketscribe/errors.py +63 -0
- pocketscribe/md_setup.py +731 -0
- pocketscribe/models.py +461 -0
- pocketscribe/narrative.py +269 -0
- pocketscribe/parsing.py +349 -0
- pocketscribe/pipeline.py +238 -0
- pocketscribe/pockets.py +679 -0
- pocketscribe/report/__init__.py +5 -0
- pocketscribe/report/plots.py +245 -0
- pocketscribe/report/render.py +145 -0
- pocketscribe/report/structure_image.py +178 -0
- pocketscribe/report/templates/base.html +35 -0
- pocketscribe/report/templates/report.html +669 -0
- pocketscribe/report/templates/style.css +405 -0
- pocketscribe/sources.py +733 -0
- pocketscribe-0.1.0.dist-info/METADATA +524 -0
- pocketscribe-0.1.0.dist-info/RECORD +28 -0
- pocketscribe-0.1.0.dist-info/WHEEL +5 -0
- pocketscribe-0.1.0.dist-info/entry_points.txt +2 -0
- pocketscribe-0.1.0.dist-info/licenses/LICENSE +21 -0
- pocketscribe-0.1.0.dist-info/top_level.txt +1 -0
pocketscribe/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Pocketscribe: confidence-aware binding-pocket triage for predicted protein structures.
|
|
2
|
+
|
|
3
|
+
Takes a predicted structure from AlphaFold2, OpenFold, ColabFold, ESMFold or Boltz and
|
|
4
|
+
produces one self-contained report covering structure QC, pocket detection,
|
|
5
|
+
confidence-caveated druggability, optional cross-model consensus, and ready-to-run
|
|
6
|
+
GROMACS input files.
|
|
7
|
+
|
|
8
|
+
Pocketscribe is an integration contribution. It does not implement a new
|
|
9
|
+
pocket-detection algorithm, druggability model or force field -- fpocket, GROMACS,
|
|
10
|
+
CHARMM36m and the prediction models do that work, and each is cited in
|
|
11
|
+
:mod:`pocketscribe.citations`. The one piece of original method here is the cross-model
|
|
12
|
+
consensus module in :mod:`pocketscribe.consensus`.
|
|
13
|
+
|
|
14
|
+
Research use only. See the disclaimer in every generated report.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
__version__ = "0.1.0"
|
|
20
|
+
__author__ = "John Adeyemo Adedeji"
|
|
21
|
+
__license__ = "MIT"
|
|
22
|
+
|
|
23
|
+
__all__ = ["__version__", "__author__", "__license__"]
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""Citations for every external tool and method Pocketscribe wraps.
|
|
2
|
+
|
|
3
|
+
Pocketscribe invents no science. fpocket does the cavity geometry and druggability
|
|
4
|
+
scoring, GROMACS and CHARMM36m do the simulation physics, and the structure-prediction
|
|
5
|
+
models do the folding. A report that gets cited in someone's methods section must point
|
|
6
|
+
at those primary sources, so they live here in one place, shared by the README, the
|
|
7
|
+
rendered report and ``pocketscribe sources``.
|
|
8
|
+
|
|
9
|
+
Keeping this list correct is a research-integrity obligation, not a formatting detail.
|
|
10
|
+
If you add an adapter, add its citation here in the same commit.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class Citation:
|
|
20
|
+
"""One primary reference."""
|
|
21
|
+
|
|
22
|
+
key: str
|
|
23
|
+
name: str
|
|
24
|
+
reference: str
|
|
25
|
+
url: str = ""
|
|
26
|
+
role: str = ""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
CITATIONS: dict[str, Citation] = {
|
|
30
|
+
"fpocket": Citation(
|
|
31
|
+
key="fpocket",
|
|
32
|
+
name="fpocket",
|
|
33
|
+
reference=(
|
|
34
|
+
"Le Guilloux, V., Schmidtke, P. & Tuffery, P. Fpocket: an open source "
|
|
35
|
+
"platform for ligand pocket detection. BMC Bioinformatics 10, 168 (2009). "
|
|
36
|
+
"doi:10.1186/1471-2105-10-168"
|
|
37
|
+
),
|
|
38
|
+
url="https://github.com/Discngine/fpocket",
|
|
39
|
+
role="Cavity detection and alpha-sphere descriptors.",
|
|
40
|
+
),
|
|
41
|
+
"fpocket_druggability": Citation(
|
|
42
|
+
key="fpocket_druggability",
|
|
43
|
+
name="fpocket druggability score",
|
|
44
|
+
reference=(
|
|
45
|
+
"Schmidtke, P. & Barril, X. Understanding and predicting druggability. A "
|
|
46
|
+
"high-throughput method for detection of drug binding sites. Journal of "
|
|
47
|
+
"Medicinal Chemistry 53, 5858-5867 (2010). doi:10.1021/jm100574m"
|
|
48
|
+
),
|
|
49
|
+
url="https://doi.org/10.1021/jm100574m",
|
|
50
|
+
role="The trained druggability model reported in the pocket table.",
|
|
51
|
+
),
|
|
52
|
+
"gromacs": Citation(
|
|
53
|
+
key="gromacs",
|
|
54
|
+
name="GROMACS",
|
|
55
|
+
reference=(
|
|
56
|
+
"Abraham, M. J., Murtola, T., Schulz, R., Pall, S., Smith, J. C., Hess, B. "
|
|
57
|
+
"& Lindahl, E. GROMACS: High performance molecular simulations through "
|
|
58
|
+
"multi-level parallelism from laptops to supercomputers. SoftwareX 1-2, "
|
|
59
|
+
"19-25 (2015). doi:10.1016/j.softx.2015.06.001"
|
|
60
|
+
),
|
|
61
|
+
url="https://www.gromacs.org/",
|
|
62
|
+
role="The MD engine the generated input files target.",
|
|
63
|
+
),
|
|
64
|
+
"charmm36m": Citation(
|
|
65
|
+
key="charmm36m",
|
|
66
|
+
name="CHARMM36m force field",
|
|
67
|
+
reference=(
|
|
68
|
+
"Huang, J., Rauscher, S., Nawrocki, G., Ran, T., Feig, M., de Groot, B. L., "
|
|
69
|
+
"Grubmuller, H. & MacKerell, A. D. Jr. CHARMM36m: an improved force field "
|
|
70
|
+
"for folded and intrinsically disordered proteins. Nature Methods 14, "
|
|
71
|
+
"71-73 (2017). doi:10.1038/nmeth.4067"
|
|
72
|
+
),
|
|
73
|
+
url="https://doi.org/10.1038/nmeth.4067",
|
|
74
|
+
role="Default force field in the generated MD setup.",
|
|
75
|
+
),
|
|
76
|
+
"jumper2021": Citation(
|
|
77
|
+
key="jumper2021",
|
|
78
|
+
name="AlphaFold2",
|
|
79
|
+
reference=(
|
|
80
|
+
"Jumper, J., Evans, R., Pritzel, A. et al. Highly accurate protein structure "
|
|
81
|
+
"prediction with AlphaFold. Nature 596, 583-589 (2021). "
|
|
82
|
+
"doi:10.1038/s41586-021-03819-2"
|
|
83
|
+
),
|
|
84
|
+
url="https://github.com/google-deepmind/alphafold",
|
|
85
|
+
role="Tier 1 structure source.",
|
|
86
|
+
),
|
|
87
|
+
"varadi2024": Citation(
|
|
88
|
+
key="varadi2024",
|
|
89
|
+
name="AlphaFold Protein Structure Database",
|
|
90
|
+
reference=(
|
|
91
|
+
"Varadi, M., Bertoni, D., Magana, P. et al. AlphaFold Protein Structure "
|
|
92
|
+
"Database in 2024: providing structure coverage for over 214 million protein "
|
|
93
|
+
"sequences. Nucleic Acids Research 52, D368-D375 (2024). "
|
|
94
|
+
"doi:10.1093/nar/gkad1011"
|
|
95
|
+
),
|
|
96
|
+
url="https://alphafold.ebi.ac.uk/",
|
|
97
|
+
role="Source of AlphaFold DB models, if that is where the input came from.",
|
|
98
|
+
),
|
|
99
|
+
"ahdritz2024": Citation(
|
|
100
|
+
key="ahdritz2024",
|
|
101
|
+
name="OpenFold",
|
|
102
|
+
reference=(
|
|
103
|
+
"Ahdritz, G., Bouatta, N., Floristean, C. et al. OpenFold: retraining "
|
|
104
|
+
"AlphaFold2 yields new insights into its learning mechanisms and capacity "
|
|
105
|
+
"for generalization. Nature Methods 21, 1514-1524 (2024). "
|
|
106
|
+
"doi:10.1038/s41592-024-02272-z"
|
|
107
|
+
),
|
|
108
|
+
url="https://github.com/aqlaboratory/openfold",
|
|
109
|
+
role="Tier 1 structure source.",
|
|
110
|
+
),
|
|
111
|
+
"mirdita2022": Citation(
|
|
112
|
+
key="mirdita2022",
|
|
113
|
+
name="ColabFold",
|
|
114
|
+
reference=(
|
|
115
|
+
"Mirdita, M., Schutze, K., Moriwaki, Y., Heo, L., Ovchinnikov, S. & "
|
|
116
|
+
"Steinegger, M. ColabFold: making protein folding accessible to all. "
|
|
117
|
+
"Nature Methods 19, 679-682 (2022). doi:10.1038/s41592-022-01488-1"
|
|
118
|
+
),
|
|
119
|
+
url="https://github.com/sokrypton/ColabFold",
|
|
120
|
+
role="Tier 1 structure source.",
|
|
121
|
+
),
|
|
122
|
+
"lin2023": Citation(
|
|
123
|
+
key="lin2023",
|
|
124
|
+
name="ESMFold / ESM-2",
|
|
125
|
+
reference=(
|
|
126
|
+
"Lin, Z., Akin, H., Rao, R. et al. Evolutionary-scale prediction of "
|
|
127
|
+
"atomic-level protein structure with a language model. Science 379, "
|
|
128
|
+
"1123-1130 (2023). doi:10.1126/science.ade2574"
|
|
129
|
+
),
|
|
130
|
+
url="https://github.com/facebookresearch/esm",
|
|
131
|
+
role="Tier 1 structure source; the single-sequence family in consensus.",
|
|
132
|
+
),
|
|
133
|
+
"wohlwend2024": Citation(
|
|
134
|
+
key="wohlwend2024",
|
|
135
|
+
name="Boltz-1 / Boltz-2",
|
|
136
|
+
reference=(
|
|
137
|
+
"Wohlwend, J., Corso, G., Passaro, S. et al. Boltz-1: democratizing "
|
|
138
|
+
"biomolecular interaction modeling. bioRxiv (2024). "
|
|
139
|
+
"doi:10.1101/2024.11.19.624167"
|
|
140
|
+
),
|
|
141
|
+
url="https://github.com/jwohlwend/boltz",
|
|
142
|
+
role="Tier 2 reference structure source.",
|
|
143
|
+
),
|
|
144
|
+
"protenix2025": Citation(
|
|
145
|
+
key="protenix2025",
|
|
146
|
+
name="Protenix",
|
|
147
|
+
reference=(
|
|
148
|
+
"ByteDance AML AI4Science Team. Protenix: advancing structure prediction "
|
|
149
|
+
"through a comprehensive AlphaFold3 reproduction. bioRxiv (2025). "
|
|
150
|
+
"doi:10.1101/2025.01.08.631967"
|
|
151
|
+
),
|
|
152
|
+
url="https://github.com/bytedance/Protenix",
|
|
153
|
+
role="Tier 2 stub; adapter not yet implemented.",
|
|
154
|
+
),
|
|
155
|
+
"abramson2024": Citation(
|
|
156
|
+
key="abramson2024",
|
|
157
|
+
name="AlphaFold3",
|
|
158
|
+
reference=(
|
|
159
|
+
"Abramson, J., Adler, J., Dunger, J. et al. Accurate structure prediction of "
|
|
160
|
+
"biomolecular interactions with AlphaFold 3. Nature 630, 493-500 (2024). "
|
|
161
|
+
"doi:10.1038/s41586-024-07487-w"
|
|
162
|
+
),
|
|
163
|
+
url="https://github.com/google-deepmind/alphafold3",
|
|
164
|
+
role="Tier 2 stub; adapter not yet implemented.",
|
|
165
|
+
),
|
|
166
|
+
"baek2021": Citation(
|
|
167
|
+
key="baek2021",
|
|
168
|
+
name="RoseTTAFold",
|
|
169
|
+
reference=(
|
|
170
|
+
"Baek, M., DiMaio, F., Anishchenko, I. et al. Accurate prediction of protein "
|
|
171
|
+
"structures and interactions using a three-track neural network. Science "
|
|
172
|
+
"373, 871-876 (2021). doi:10.1126/science.abj8754"
|
|
173
|
+
),
|
|
174
|
+
url="https://github.com/RosettaCommons/RoseTTAFold",
|
|
175
|
+
role="Tier 2 stub; deprioritised relative to newer models.",
|
|
176
|
+
),
|
|
177
|
+
"biopython": Citation(
|
|
178
|
+
key="biopython",
|
|
179
|
+
name="Biopython",
|
|
180
|
+
reference=(
|
|
181
|
+
"Cock, P. J. A., Antao, T., Chang, J. T. et al. Biopython: freely available "
|
|
182
|
+
"Python tools for computational molecular biology and bioinformatics. "
|
|
183
|
+
"Bioinformatics 25, 1422-1423 (2009). doi:10.1093/bioinformatics/btp163"
|
|
184
|
+
),
|
|
185
|
+
url="https://biopython.org/",
|
|
186
|
+
role="Structure parsing and pairwise sequence alignment.",
|
|
187
|
+
),
|
|
188
|
+
"kabsch1976": Citation(
|
|
189
|
+
key="kabsch1976",
|
|
190
|
+
name="Kabsch superposition",
|
|
191
|
+
reference=(
|
|
192
|
+
"Kabsch, W. A solution for the best rotation to relate two sets of vectors. "
|
|
193
|
+
"Acta Crystallographica A 32, 922-923 (1976). doi:10.1107/S0567739476001873"
|
|
194
|
+
),
|
|
195
|
+
url="https://doi.org/10.1107/S0567739476001873",
|
|
196
|
+
role="Structural superposition in the consensus module.",
|
|
197
|
+
),
|
|
198
|
+
"hendlich1997": Citation(
|
|
199
|
+
key="hendlich1997",
|
|
200
|
+
name="LIGSITE",
|
|
201
|
+
reference=(
|
|
202
|
+
"Hendlich, M., Rippmann, F. & Barnickel, G. LIGSITE: automatic and efficient "
|
|
203
|
+
"detection of potential small molecule-binding sites in proteins. Journal of "
|
|
204
|
+
"Molecular Graphics and Modelling 15, 359-363 (1997). "
|
|
205
|
+
"doi:10.1016/S1093-3263(98)00002-3"
|
|
206
|
+
),
|
|
207
|
+
url="https://doi.org/10.1016/S1093-3263(98)00002-3",
|
|
208
|
+
role=(
|
|
209
|
+
"The grid buriedness scan the built-in offline fallback backend follows. "
|
|
210
|
+
"Only relevant when fpocket is unavailable."
|
|
211
|
+
),
|
|
212
|
+
),
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def core_citations() -> list[Citation]:
|
|
217
|
+
"""The references every run depends on, in reading order."""
|
|
218
|
+
return [
|
|
219
|
+
CITATIONS["fpocket"],
|
|
220
|
+
CITATIONS["fpocket_druggability"],
|
|
221
|
+
CITATIONS["gromacs"],
|
|
222
|
+
CITATIONS["charmm36m"],
|
|
223
|
+
CITATIONS["biopython"],
|
|
224
|
+
]
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def citations_for_report(
|
|
228
|
+
source_keys: list[str], used_fpocket: bool, used_consensus: bool
|
|
229
|
+
) -> list[Citation]:
|
|
230
|
+
"""Assemble the 'Built on' list for one report, without duplicates."""
|
|
231
|
+
selected: list[Citation] = []
|
|
232
|
+
|
|
233
|
+
def add(citation: Citation | None) -> None:
|
|
234
|
+
if citation is not None and citation not in selected:
|
|
235
|
+
selected.append(citation)
|
|
236
|
+
|
|
237
|
+
if used_fpocket:
|
|
238
|
+
add(CITATIONS["fpocket"])
|
|
239
|
+
add(CITATIONS["fpocket_druggability"])
|
|
240
|
+
else:
|
|
241
|
+
add(CITATIONS["hendlich1997"])
|
|
242
|
+
|
|
243
|
+
for key in source_keys:
|
|
244
|
+
add(CITATIONS.get(key))
|
|
245
|
+
|
|
246
|
+
add(CITATIONS["gromacs"])
|
|
247
|
+
add(CITATIONS["charmm36m"])
|
|
248
|
+
add(CITATIONS["biopython"])
|
|
249
|
+
if used_consensus:
|
|
250
|
+
add(CITATIONS["kabsch1976"])
|
|
251
|
+
return selected
|
pocketscribe/cli.py
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"""Command-line interface.
|
|
2
|
+
|
|
3
|
+
Four commands:
|
|
4
|
+
|
|
5
|
+
``run``
|
|
6
|
+
Analyse one structure, or several structures of the same protein for cross-model
|
|
7
|
+
consensus.
|
|
8
|
+
``demo``
|
|
9
|
+
Run end to end on bundled example structures, with no network access and no
|
|
10
|
+
external downloads.
|
|
11
|
+
``sources``
|
|
12
|
+
List every structure-prediction source and its support tier.
|
|
13
|
+
``version``
|
|
14
|
+
Print the version.
|
|
15
|
+
|
|
16
|
+
Errors are printed as plain, specific messages rather than tracebacks: the target user
|
|
17
|
+
is a bench scientist, and a Python traceback is not an error message.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import sys
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
import typer
|
|
26
|
+
|
|
27
|
+
from . import __version__
|
|
28
|
+
from .citations import CITATIONS
|
|
29
|
+
from .config import RunConfig
|
|
30
|
+
from .errors import PocketscribeError
|
|
31
|
+
from .models import SupportTier
|
|
32
|
+
from .pipeline import StructureInput, run_pipeline
|
|
33
|
+
from .report.render import render_report
|
|
34
|
+
from .sources import all_sources
|
|
35
|
+
|
|
36
|
+
app = typer.Typer(
|
|
37
|
+
name="pocketscribe",
|
|
38
|
+
help=(
|
|
39
|
+
"Confidence-aware binding-pocket triage and MD setup for predicted protein "
|
|
40
|
+
"structures. Research use only."
|
|
41
|
+
),
|
|
42
|
+
add_completion=False,
|
|
43
|
+
no_args_is_help=True,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
_TIER_TEXT = {
|
|
47
|
+
SupportTier.TIER_1: "Tier 1 (full support)",
|
|
48
|
+
SupportTier.TIER_2_REFERENCE: "Tier 2 (reference adapter)",
|
|
49
|
+
SupportTier.TIER_2_STUB: "Tier 2 (stub - not implemented)",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _echo(message: str) -> None:
|
|
54
|
+
typer.echo(message)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _fail(error: Exception) -> None:
|
|
58
|
+
"""Print a clean error and exit non-zero."""
|
|
59
|
+
typer.secho(f"\nError: {error}", fg=typer.colors.RED, err=True)
|
|
60
|
+
raise typer.Exit(code=1)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@app.command()
|
|
64
|
+
def run(
|
|
65
|
+
pdb: list[str] = typer.Option(
|
|
66
|
+
...,
|
|
67
|
+
"--pdb",
|
|
68
|
+
help=(
|
|
69
|
+
"Structure file. Optionally tag it with its source as PATH:SOURCE, e.g. "
|
|
70
|
+
"model.pdb:esmfold. Pass --pdb more than once, with 2+ structures of the "
|
|
71
|
+
"SAME protein, to enable cross-model consensus."
|
|
72
|
+
),
|
|
73
|
+
),
|
|
74
|
+
source: str = typer.Option(
|
|
75
|
+
"auto",
|
|
76
|
+
"--source",
|
|
77
|
+
help=(
|
|
78
|
+
"Structure source when it is not tagged per file: alphafold2, openfold, "
|
|
79
|
+
"colabfold, esmfold, boltz, or auto to detect it from the file."
|
|
80
|
+
),
|
|
81
|
+
),
|
|
82
|
+
output: Path = typer.Option(Path("report.html"), "--output", "-o", help="Report path."),
|
|
83
|
+
top_pockets: int = typer.Option(3, "--top-pockets", min=1, max=50),
|
|
84
|
+
md_setup: bool = typer.Option(
|
|
85
|
+
False, "--md-setup", help="Generate GROMACS input files for the top pocket."
|
|
86
|
+
),
|
|
87
|
+
md_dir: Path = typer.Option(
|
|
88
|
+
Path("md_setup"), "--md-dir", help="Directory for the generated MD files."
|
|
89
|
+
),
|
|
90
|
+
pocket_backend: str = typer.Option(
|
|
91
|
+
"fpocket",
|
|
92
|
+
"--pocket-backend",
|
|
93
|
+
help=(
|
|
94
|
+
"'fpocket' (default, recommended) or 'builtin' (offline geometric fallback "
|
|
95
|
+
"whose scores are a heuristic, not fpocket druggability scores)."
|
|
96
|
+
),
|
|
97
|
+
),
|
|
98
|
+
min_identity: float = typer.Option(
|
|
99
|
+
0.95,
|
|
100
|
+
"--min-identity",
|
|
101
|
+
min=0.0,
|
|
102
|
+
max=1.0,
|
|
103
|
+
help="Sequence identity required between structures in consensus mode.",
|
|
104
|
+
),
|
|
105
|
+
no_narrative: bool = typer.Option(
|
|
106
|
+
False, "--no-narrative", help="Skip the optional AI narrative section entirely."
|
|
107
|
+
),
|
|
108
|
+
config_file: Path | None = typer.Option(
|
|
109
|
+
None, "--config", help="YAML configuration file; CLI options override it."
|
|
110
|
+
),
|
|
111
|
+
) -> None:
|
|
112
|
+
"""Analyse a predicted structure and write an HTML report."""
|
|
113
|
+
try:
|
|
114
|
+
config = RunConfig.from_yaml(config_file) if config_file else RunConfig()
|
|
115
|
+
|
|
116
|
+
config.pockets.backend = pocket_backend
|
|
117
|
+
config.pockets.top_pockets = top_pockets
|
|
118
|
+
config.consensus.min_sequence_identity = min_identity
|
|
119
|
+
config.md.enabled = md_setup
|
|
120
|
+
if no_narrative:
|
|
121
|
+
config.narrative.enabled = False
|
|
122
|
+
|
|
123
|
+
inputs: list[StructureInput] = []
|
|
124
|
+
for raw in pdb:
|
|
125
|
+
item = StructureInput.parse(raw)
|
|
126
|
+
if item.source_id == "auto" and source != "auto":
|
|
127
|
+
item.source_id = source
|
|
128
|
+
item.label = item.path.name
|
|
129
|
+
inputs.append(item)
|
|
130
|
+
|
|
131
|
+
if len(inputs) > 1:
|
|
132
|
+
_echo(
|
|
133
|
+
f"Cross-model consensus mode: {len(inputs)} structures. They must be "
|
|
134
|
+
"independent predictions of the same protein."
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
result, parsed = run_pipeline(
|
|
138
|
+
inputs=inputs,
|
|
139
|
+
config=config,
|
|
140
|
+
md_output_dir=md_dir,
|
|
141
|
+
allow_backend_fallback=False,
|
|
142
|
+
progress=_echo,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
_echo(f"Rendering report to {output}")
|
|
146
|
+
path = render_report(result, parsed, output, max_images=min(top_pockets, 3))
|
|
147
|
+
|
|
148
|
+
_echo("")
|
|
149
|
+
_summarise(result)
|
|
150
|
+
typer.secho(f"Report written to {path}", fg=typer.colors.GREEN)
|
|
151
|
+
|
|
152
|
+
except PocketscribeError as exc:
|
|
153
|
+
_fail(exc)
|
|
154
|
+
except KeyboardInterrupt:
|
|
155
|
+
typer.secho("\nInterrupted.", fg=typer.colors.YELLOW, err=True)
|
|
156
|
+
raise typer.Exit(code=130) from None
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@app.command()
|
|
160
|
+
def demo(
|
|
161
|
+
output: Path = typer.Option(Path("demo_report.html"), "--output", "-o"),
|
|
162
|
+
consensus: bool = typer.Option(
|
|
163
|
+
False,
|
|
164
|
+
"--consensus",
|
|
165
|
+
help="Run the cross-model consensus path on two bundled example models.",
|
|
166
|
+
),
|
|
167
|
+
md_setup: bool = typer.Option(False, "--md-setup", help="Also generate MD inputs."),
|
|
168
|
+
) -> None:
|
|
169
|
+
"""Run end to end on bundled example structures. No network access needed."""
|
|
170
|
+
from .data import demo_consensus_structures, demo_structure
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
if consensus:
|
|
174
|
+
paths = demo_consensus_structures()
|
|
175
|
+
inputs = [
|
|
176
|
+
StructureInput(path=path, source_id=source_id, label=path.name)
|
|
177
|
+
for path, source_id in paths
|
|
178
|
+
]
|
|
179
|
+
_echo(
|
|
180
|
+
"Demo (consensus): "
|
|
181
|
+
+ ", ".join(f"{path.name} [{source_id}]" for path, source_id in paths)
|
|
182
|
+
)
|
|
183
|
+
else:
|
|
184
|
+
path, source_id = demo_structure()
|
|
185
|
+
inputs = [StructureInput(path=path, source_id=source_id, label=path.name)]
|
|
186
|
+
_echo(f"Demo: {path.name} [{source_id}]")
|
|
187
|
+
|
|
188
|
+
_echo(
|
|
189
|
+
"These are synthetic structures with known geometry, bundled so the demo "
|
|
190
|
+
"runs offline. They are not real proteins."
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
config = RunConfig()
|
|
194
|
+
config.md.enabled = md_setup
|
|
195
|
+
# The demo is allowed to fall back to the built-in backend so it runs on a
|
|
196
|
+
# machine without fpocket. A real analysis run never does this silently.
|
|
197
|
+
result, parsed = run_pipeline(
|
|
198
|
+
inputs=inputs,
|
|
199
|
+
config=config,
|
|
200
|
+
allow_backend_fallback=True,
|
|
201
|
+
progress=_echo,
|
|
202
|
+
)
|
|
203
|
+
path = render_report(result, parsed, output, max_images=3)
|
|
204
|
+
|
|
205
|
+
_echo("")
|
|
206
|
+
_summarise(result)
|
|
207
|
+
typer.secho(f"Demo report written to {path}", fg=typer.colors.GREEN)
|
|
208
|
+
|
|
209
|
+
except PocketscribeError as exc:
|
|
210
|
+
_fail(exc)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@app.command()
|
|
214
|
+
def sources() -> None:
|
|
215
|
+
"""List supported structure-prediction sources and their support tiers."""
|
|
216
|
+
_echo("")
|
|
217
|
+
_echo("Pocketscribe structure sources")
|
|
218
|
+
_echo("=" * 78)
|
|
219
|
+
|
|
220
|
+
for adapter in all_sources():
|
|
221
|
+
info = adapter.info
|
|
222
|
+
_echo("")
|
|
223
|
+
_echo(f" {info.id}")
|
|
224
|
+
_echo(f" name {info.display_name}")
|
|
225
|
+
_echo(f" support {_TIER_TEXT[info.tier]}")
|
|
226
|
+
_echo(f" family {info.family.value}")
|
|
227
|
+
_echo(f" confidence {info.metric.value}")
|
|
228
|
+
_echo(f" uses MSA {'yes' if info.uses_msa else 'no (single sequence)'}")
|
|
229
|
+
citation = CITATIONS.get(info.citation_key)
|
|
230
|
+
if citation:
|
|
231
|
+
_echo(f" cite {citation.reference}")
|
|
232
|
+
if info.notes:
|
|
233
|
+
for line in _wrap(info.notes, 70):
|
|
234
|
+
_echo(f" {line}")
|
|
235
|
+
|
|
236
|
+
_echo("")
|
|
237
|
+
_echo("-" * 78)
|
|
238
|
+
_echo(
|
|
239
|
+
"Tier 2 stubs fail with a specific 'not yet implemented' message rather than\n"
|
|
240
|
+
"attempting a parse that would be wrong. Adding one is the most useful\n"
|
|
241
|
+
"contribution you can make: see CONTRIBUTING.md."
|
|
242
|
+
)
|
|
243
|
+
_echo("")
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@app.command()
|
|
247
|
+
def version() -> None:
|
|
248
|
+
"""Print the Pocketscribe version."""
|
|
249
|
+
_echo(f"pocketscribe {__version__}")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _summarise(result) -> None:
|
|
253
|
+
"""Terminal summary of what the report contains."""
|
|
254
|
+
pockets = result.primary_pockets
|
|
255
|
+
_echo("Summary")
|
|
256
|
+
_echo("-" * 60)
|
|
257
|
+
_echo(f" structure {result.primary_structure.label}")
|
|
258
|
+
_echo(f" source {result.primary_structure.source.display_name}")
|
|
259
|
+
_echo(
|
|
260
|
+
f" mean pLDDT {result.primary_structure.confidence.mean:.1f} "
|
|
261
|
+
f"({result.primary_structure.confidence.fraction_below_70:.0%} below 70)"
|
|
262
|
+
)
|
|
263
|
+
_echo(f" pockets {len(pockets.pockets)} (backend: {pockets.backend})")
|
|
264
|
+
|
|
265
|
+
for pocket in pockets.top(result.top_pockets_requested):
|
|
266
|
+
score = (
|
|
267
|
+
f"{pocket.druggability_score:.3f}"
|
|
268
|
+
if pocket.druggability_score is not None
|
|
269
|
+
else "n/a"
|
|
270
|
+
)
|
|
271
|
+
mean_confidence = (
|
|
272
|
+
f"{pocket.confidence.mean_confidence:.0f}"
|
|
273
|
+
if pocket.confidence.mean_confidence is not None
|
|
274
|
+
else "n/a"
|
|
275
|
+
)
|
|
276
|
+
volume = f"{pocket.volume:.0f} A^3" if pocket.volume is not None else "n/a"
|
|
277
|
+
_echo(f" #{pocket.rank} score {score} volume {volume}")
|
|
278
|
+
_echo(
|
|
279
|
+
f" mean pLDDT {mean_confidence} "
|
|
280
|
+
f"caveat: {pocket.confidence.level.value}"
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
if result.consensus is not None:
|
|
284
|
+
_echo(
|
|
285
|
+
f" consensus {len(result.consensus.groups)} group(s); "
|
|
286
|
+
f"{result.consensus.n_cross_family_pockets} cross-family, "
|
|
287
|
+
f"{result.consensus.n_same_family_only_pockets} same-family only, "
|
|
288
|
+
f"{result.consensus.n_single_model_pockets} single-model"
|
|
289
|
+
)
|
|
290
|
+
if result.md_setup is not None:
|
|
291
|
+
_echo(f" MD setup {len(result.md_setup.files)} files in {result.md_setup.output_dir}")
|
|
292
|
+
_echo(
|
|
293
|
+
" narrative "
|
|
294
|
+
+ ("included" if result.narrative.enabled else "skipped (optional)")
|
|
295
|
+
)
|
|
296
|
+
_echo("")
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _wrap(text: str, width: int) -> list[str]:
|
|
300
|
+
import textwrap
|
|
301
|
+
|
|
302
|
+
return textwrap.wrap(text, width=width)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def main() -> None:
|
|
306
|
+
"""Entry point used by the console script."""
|
|
307
|
+
try:
|
|
308
|
+
app()
|
|
309
|
+
except PocketscribeError as exc: # safety net; commands handle their own errors
|
|
310
|
+
typer.secho(f"Error: {exc}", fg=typer.colors.RED, err=True)
|
|
311
|
+
sys.exit(1)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
if __name__ == "__main__":
|
|
315
|
+
main()
|