Skip to content

Commit 212709b

Browse files
committed
Merge remote-tracking branch 'origin/dev'
2 parents 17fd34b + 8c6c220 commit 212709b

59 files changed

Lines changed: 1590 additions & 582 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.pre-commit-config.yaml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,17 @@ repos:
44
hooks:
55
- id: prettier
66
additional_dependencies:
7-
- prettier@3.2.5
7+
- prettier@3.5.3
8+
9+
- repo: https://github.com/pre-commit/pre-commit-hooks
10+
rev: v5.0.0
11+
hooks:
12+
- id: trailing-whitespace
13+
- id: end-of-file-fixer
14+
- id: check-added-large-files
815

916
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
10-
rev: "3.1.2"
17+
rev: "3.2.1"
1118
hooks:
1219
- id: editorconfig-checker
1320
alias: ec

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020
- [[#235](https://github.com/nf-core/proteinfold/issues/235)] - Update samplesheet to new version (switch from `sequence` column to `id`).
2121
- [[#239](https://github.com/nf-core/proteinfold/issues/239)] - Update alphafold2 standard mode Dockerfile.
2222
- [[#240](https://github.com/nf-core/proteinfold/issues/240)] - Separate download and input of pdb `mmcif` files and `obsolete` database.
23+
- [[#229](https://github.com/nf-core/proteinfold/issues/229)] - Add Boltz pipeline [PR #230](https://github.com/nf-core/proteinfold/pull/227).
2324
- [[PR #249](https://github.com/nf-core/proteinfold/pull/249)] - Update pipeline template to [nf-core/tools 3.2.0](https://github.com/nf-core/tools/releases/tag/3.2.0).
2425
- [[PR #271](https://github.com/nf-core/proteinfold/pull/271)] - Update RFAA and HF3 dockerfiles for quicker building and reduction in image size.
2526
- [[PR #274](https://github.com/nf-core/proteinfold/pull/274)] - Simplify run_helixfold3 module and move arguments to `modules.config`.
@@ -32,6 +33,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3233
- [[PR #294](https://github.com/nf-core/proteinfold/pull/294)] - Temporary downgrade of schema for passing CI tests with Nextflow edge version.
3334
- [[#272](https://github.com/nf-core/proteinfold/issues/272)] - Colouring scheme conforming to AlphaFold2 confidence bands in html report.
3435
- [[PR #297](https://github.com/nf-core/proteinfold/pull/297)] - Update pipeline template to [nf-core/tools 3.2.1](https://github.com/nf-core/tools/releases/tag/3.2.1).
36+
- [[#273](https://github.com/nf-core/proteinfold/issues/273)] - Fixes comparison report to correctly label msa coverage plots with corresponding method label.
37+
- [[#290](https://github.com/nf-core/proteinfold/issues/290)] - Update Alphafold2 split images to make them compatible Hopper gpus.
38+
- [[PR #302](https://github.com/nf-core/proteinfold/pull/302)] - Fix HF3 dbs and max_template_date.
39+
- [[#305](https://github.com/nf-core/proteinfold/pull/305)] - Stop RFAA and HF3 symlinking scripts into workdir.
40+
- [[#307](https://github.com/nf-core/proteinfold/pull/307)] - Update Boltz-1 boilerplate and formatting.
3541

3642
### Parameters
3743

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ On release, automated continuous integration tests run the pipeline on a full-si
4545

4646
vii. [HelixFold3](https://github.com/PaddlePaddle/PaddleHelix/tree/dev/apps/protein_folding/helixfold3) - Regular HF3
4747

48+
viii. [Boltz](https://github.com/jwohlwend/boltz/) - Regular Boltz-1
49+
4850
## Usage
4951

5052
> [!NOTE]
@@ -166,6 +168,19 @@ The pipeline takes care of downloading the databases and parameters required by
166168
-profile <docker/singularity/podman/shifter/charliecloud/conda/institute>
167169
```
168170

171+
- The boltz mode can be run using the command below:
172+
173+
```console
174+
nextflow run nf-core/proteinfold \
175+
--input samplesheet.csv \
176+
--outdir <OUTDIR> \
177+
--mode boltz \
178+
--boltz_ccd_path <null (default) | PATH> \
179+
--boltz_model_path <null (default) | PATH> \
180+
--use_gpu <true/false> \
181+
-profile <docker/singularity/podman/shifter/charliecloud/conda/institute>
182+
```
183+
169184
> [!WARNING]
170185
> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/usage/getting_started/configuration#custom-configuration-files).
171186

assets/comparison_template.html

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,7 @@
441441

442442
const LDDT_AVERAGES = [];
443443
const SEQ_COV_IMGS = [];
444+
const SEQ_COV_METHODS = [];
444445
const MODELS = [];
445446

446447
const MODELS_DATA = [
@@ -800,13 +801,7 @@
800801

801802
const label = document.createElement("div");
802803
label.className = "font-semibold mb-2";
803-
804-
if (MODELS[index].includes("colabfold")) {
805-
if (!MODELS[index + 1]) return;
806-
label.textContent = MODELS[index + 1].replace(".pdb", "");
807-
} else {
808-
label.textContent = MODELS[index].replace(".pdb", "");
809-
}
804+
label.textContent = SEQ_COV_METHODS[index];
810805

811806
const imgContainer = document.createElement("div");
812807
imgContainer.className = "w-[660px] h-auto p-6 bg-white shadow-md rounded items-center";

bin/generate_comparison_report.py

Lines changed: 75 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -105,88 +105,68 @@ def generate_output(plddt_data, name, out_dir, generate_tsv, pdb):
105105
) as out_file:
106106
out_file.write(html_content)
107107

108+
def align_structures(structures):
108109

109-
def align_structures_old(structures):
110-
parser = PDB.PDBParser(QUIET=True)
111-
structures = [
112-
parser.get_structure(f"Structure_{i}", pdb) for i, pdb in enumerate(structures)
113-
]
114-
115-
ref_structure = structures[0]
116-
ref_atoms = [atom for atom in ref_structure.get_atoms()]
110+
if not structures:
111+
raise ValueError("No structures provided for alignment.")
117112

113+
if structures[0].endswith(".pdb"):
114+
parser = PDB.PDBParser(QUIET=True)
115+
elif structures[0].endswith(".cif"):
116+
parser = PDB.MMCIFParser(QUIET=True)
117+
else:
118+
raise ValueError(f"{structure} is neither a PDB or mmCIF file!")
119+
120+
parsed_structures = [parser.get_structure(f"structure-{idx}", structure) for idx, structure in enumerate(structures)]
121+
ref_structure = parsed_structures[0]
122+
123+
def get_atom_ids(structure):
124+
# Note: this is a *set* of atom_ids due to the {} surrounding the comprehension
125+
return {(atom.get_parent().get_id(), atom.name) for atom in structure.get_atoms()}
126+
127+
# TODO: do we want to raise and error if the structures are not identical atomically, or keep the ability to sub-align?
128+
# Update the atoms shared between structures with progressive intersections
129+
common_atoms = get_atom_ids(ref_structure)
130+
print("commons: ", len(common_atoms))
131+
for structure in parsed_structures[1:]:
132+
common_atoms.intersection_update(get_atom_ids(structure))
133+
print("commons: ", len(common_atoms))
134+
135+
if not common_atoms:
136+
raise ValueError("No common atoms found between structures.")
137+
#print(common_atoms)
138+
def extract_atoms(structure, atom_ids):
139+
# Note: this comprehension returns an atom *object* for each atom in the structure
140+
return [atom for atom in structure.get_atoms() if (atom.get_parent().get_id(), atom.name) in atom_ids]
141+
142+
ref_atoms = extract_atoms(ref_structure, common_atoms)
143+
# The aligned structures will be the parsed structures aligned to the common atoms of the reference structure
118144
super_imposer = PDB.Superimposer()
119-
aligned_structures = [structures[0]] # Include the reference structure in the list
120-
121-
for i, structure in enumerate(structures[1:], start=1):
122-
target_atoms = [atom for atom in structure.get_atoms()]
123-
145+
aligned_structures = []
146+
for idx, structure in enumerate(parsed_structures):
147+
# The reference structure doesn't need to be aligned so can be skipped
148+
if idx == 0:
149+
aligned_structures.append(structure)
150+
continue
151+
target_atoms = extract_atoms(structure, common_atoms)
152+
print(len(ref_atoms), len(target_atoms), len(common_atoms))
124153
super_imposer.set_atoms(ref_atoms, target_atoms)
125154
super_imposer.apply(structure.get_atoms())
126155

127-
aligned_structure = f"aligned_structure_{i}.pdb"
128156
io = PDB.PDBIO()
129157
io.set_structure(structure)
130-
io.save(aligned_structure)
131-
aligned_structures.append(aligned_structure)
158+
io.save(f"aligned_structure_{idx}.pdb")
159+
aligned_structures.append(f"aligned_structure_{idx}.pdb")
132160

161+
# Technically, parsed_structures now also points to the same aligned structures, but I've kept for readability
133162
return aligned_structures
134163

135164

136-
def align_structures(structures):
137-
parser = PDB.PDBParser(QUIET=True)
138-
structures = [
139-
parser.get_structure(f"Structure_{i}", pdb) for i, pdb in enumerate(structures)
140-
]
141-
ref_structure = structures[0]
142-
143-
common_atoms = set(
144-
f"{atom.get_parent().get_id()[1]}-{atom.name}"
145-
for atom in ref_structure.get_atoms()
146-
)
147-
for i, structure in enumerate(structures[1:], start=1):
148-
common_atoms = common_atoms.intersection(
149-
set(
150-
f"{atom.get_parent().get_id()[1]}-{atom.name}"
151-
for atom in structure.get_atoms()
152-
)
153-
)
154-
155-
ref_atoms = [
156-
atom
157-
for atom in ref_structure.get_atoms()
158-
if f"{atom.get_parent().get_id()[1]}-{atom.name}" in common_atoms
159-
]
160-
# print(ref_atoms)
161-
super_imposer = PDB.Superimposer()
162-
aligned_structures = [structures[0]] # Include the reference structure in the list
163-
164-
for i, structure in enumerate(structures[1:], start=1):
165-
target_atoms = [
166-
atom
167-
for atom in structure.get_atoms()
168-
if f"{atom.get_parent().get_id()[1]}-{atom.name}" in common_atoms
169-
]
170-
171-
super_imposer.set_atoms(ref_atoms, target_atoms)
172-
super_imposer.apply(structure.get_atoms())
173-
174-
aligned_structure = f"aligned_structure_{i}.pdb"
175-
io = PDB.PDBIO()
176-
io.set_structure(structure)
177-
io.save(aligned_structure)
178-
aligned_structures.append(aligned_structure)
179-
180-
return aligned_structures
181-
182165
def pdb_to_lddt(struct_files, generate_tsv):
183-
struct_files_sorted = struct_files
184-
struct_files_sorted.sort()
185-
186166
output_lddt = []
187167
averages = []
188168

189-
for struct_file in struct_files_sorted:
169+
for struct_file in struct_files:
190170
plddt_values = []
191171

192172
if struct_file.endswith('.pdb'):
@@ -254,21 +234,18 @@ def pdb_to_lddt(struct_files, generate_tsv):
254234

255235
print("generating html report...")
256236

257-
# structures = args.pdb
258-
# # structures.sort()
259-
# aligned_structures = align_structures(structures)
260-
261237
# Preprocess "esmfold" PDB files, to reset residues on additional chains
262238
processed_pdbs = [
263-
(pdb_file.replace(".pdb", "_align_residues.pdb") if "esmfold" in pdb_file else pdb_file)
264-
for pdb_file in args.pdb
239+
pdb_file.replace(".pdb", "_aligned.pdb") for pdb_file in args.pdb
265240
]
266241

267-
for pdb_file, output_pdb in zip(args.pdb, processed_pdbs):
268-
if "esmfold" in pdb_file:
269-
reset_residue_numbers(pdb_file, output_pdb)
242+
for pdb_file in args.pdb:
243+
print("Reseting", pdb_file, " into ", pdb_file.replace(".pdb", "_aligned.pdb"))
244+
reset_residue_numbers(pdb_file, pdb_file.replace(".pdb", "_aligned.pdb"))
270245

271246
structures = processed_pdbs # Use the final processed list
247+
print("reference structure:", processed_pdbs[0])
248+
print("target structures:", ",".join(processed_pdbs[1:]))
272249
aligned_structures = align_structures(structures)
273250

274251
io = PDB.PDBIO()
@@ -277,38 +254,49 @@ def pdb_to_lddt(struct_files, generate_tsv):
277254
io.save(ref_structure_path)
278255
aligned_structures[0] = ref_structure_path
279256

280-
alphafold_template = open(args.html_template, "r").read()
281-
alphafold_template = alphafold_template.replace("*sample_name*", args.name)
282-
alphafold_template = alphafold_template.replace("*prog_name*", args.in_type)
257+
comparision_template = open(args.html_template, "r").read()
258+
comparision_template = comparision_template.replace("*sample_name*", args.name)
259+
comparision_template = comparision_template.replace("*prog_name*", args.in_type)
283260

284261
args_pdb_array_js = (
285262
"const MODELS = [" + ",\n".join([f'"{model}"' for model in structures]) + "];"
286263
)
287-
alphafold_template = alphafold_template.replace("const MODELS = [];", args_pdb_array_js)
264+
comparision_template = comparision_template.replace("const MODELS = [];", args_pdb_array_js)
288265

289266
seq_cov_imgs = []
290-
for item in args.msa:
291-
if item != "NO_FILE":
292-
image_path = item
267+
seq_cov_methods = []
268+
for msa, pdb in zip(args.msa, args.pdb):
269+
if msa != "NO_FILE":
270+
image_path = msa
271+
method = pdb.split(".pdb")[0]
272+
seq_cov_methods.append(method)
293273
with open(image_path, "rb") as in_file:
294274
encoded_image = base64.b64encode(in_file.read()).decode("utf-8")
295275
seq_cov_imgs.append(f"data:image/png;base64,{encoded_image}")
296276

277+
#MSA IMAGES
297278
args_msa_array_js = (
298279
f"""const SEQ_COV_IMGS = [{", ".join([f'"{img}"' for img in seq_cov_imgs])}];"""
299280
)
300-
alphafold_template = alphafold_template.replace(
281+
comparision_template = comparision_template.replace(
301282
"const SEQ_COV_IMGS = [];", args_msa_array_js
302283
)
284+
#MSA IMAGE LABELS
285+
args_msa_method_array_js = (
286+
f"""const SEQ_COV_METHODS = [{", ".join([f'"{method}"' for method in seq_cov_methods])}];"""
287+
)
288+
comparision_template = comparision_template.replace(
289+
"const SEQ_COV_METHODS = [];", args_msa_method_array_js
290+
)
303291

304292
averages_js_array = f"const LDDT_AVERAGES = {lddt_averages};"
305-
alphafold_template = alphafold_template.replace(
293+
comparision_template = comparision_template.replace(
306294
"const LDDT_AVERAGES = [];", averages_js_array
307295
)
308296

309297
i = 0
310298
for structure in aligned_structures:
311-
alphafold_template = alphafold_template.replace(
299+
comparision_template = comparision_template.replace(
312300
f"*_data_ranked_{i}.pdb*", open(structure, "r").read().replace("\n", "\\n")
313301
)
314302
i += 1
@@ -318,11 +306,11 @@ def pdb_to_lddt(struct_files, generate_tsv):
318306
"r",
319307
) as in_file:
320308
lddt_html = in_file.read()
321-
alphafold_template = alphafold_template.replace(
309+
comparision_template = comparision_template.replace(
322310
'<div id="lddt_placeholder"></div>', lddt_html
323311
)
324312

325313
with open(
326314
f"{args.output_dir}/{args.name}_{args.in_type.lower()}_report.html", "w"
327315
) as out_file:
328-
out_file.write(alphafold_template)
316+
out_file.write(comparision_template)

bin/generate_report.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,8 @@ def pdb_to_lddt(struct_files, generate_tsv):
316316
"alphafold2": "AlphaFold2",
317317
"colabfold": "ColabFold",
318318
"rosettafold_all_atom": "Rosettafold_All_Atom",
319-
"helixfold3": "HelixFold3"
319+
"helixfold3": "HelixFold3",
320+
"boltz": "Boltz-1"
320321
}
321322

322323
parser = argparse.ArgumentParser()

0 commit comments

Comments
 (0)