From 68133645355c2cb8178aaa333115d9c2e6766a96 Mon Sep 17 00:00:00 2001 From: tlitfin Date: Fri, 13 Mar 2026 10:30:13 +1100 Subject: [PATCH 1/3] wip max_traj settings --- modules/local/bindcraft/main.nf | 2 +- modules/local/generate_report.nf | 6 ++-- modules/local/jsonmanager/main.nf | 37 +++++++++++++++++--- subworkflows/local/run_bindcraft.nf | 54 +++++++++++++++++------------ 4 files changed, 68 insertions(+), 31 deletions(-) diff --git a/modules/local/bindcraft/main.nf b/modules/local/bindcraft/main.nf index 7abf03e..827d102 100644 --- a/modules/local/bindcraft/main.nf +++ b/modules/local/bindcraft/main.nf @@ -22,7 +22,7 @@ process BINDCRAFT { def args = task.ext.args ?: '' """ - python /work/FreeBindCraft/bindcraft.py \\ + python -u /work/FreeBindCraft/bindcraft.py \\ --settings ${target_file} \\ --filters ${filters} \\ --advanced ${advanced_settings} \\ diff --git a/modules/local/generate_report.nf b/modules/local/generate_report.nf index 83eef42..54b24a1 100644 --- a/modules/local/generate_report.nf +++ b/modules/local/generate_report.nf @@ -1,10 +1,10 @@ process GENERATE_REPORT { tag "${meta.id}" label 'process_single' + conda "bioconda::multiqc=1.25" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/multiqc:1.21--pyhdfd78af_0' : - 'biocontainers/multiqc:1.21--pyhdfd78af_0' }" - conda "bioconda::multiqc=1.21" + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/24/241f0746484727a3633f544c3747bfb77932e1c8c252e769640bd163232d9112/data' : + 'community.wave.seqera.io/library/biopython_matplotlib_pip_plotly:35975fa0fc54b2d3' }" input: tuple val(meta), val(info) diff --git a/modules/local/jsonmanager/main.nf b/modules/local/jsonmanager/main.nf index 6310db4..802dc6c 100644 --- a/modules/local/jsonmanager/main.nf +++ b/modules/local/jsonmanager/main.nf @@ -11,7 +11,8 @@ process JSONMANAGER { val batches output: - path "*.json" , emit: json + path "target_json/*.json" , emit: json + path "advanced_json/*.json", emit: advanced_json path "versions.yml", emit: versions when: @@ -19,6 +20,7 @@ process JSONMANAGER { script: def quote_char = task.ext.args2 ?: "\"" + def default_advanced = params.settings_advanced ?: "${projectDir}/assets/bindcraft/default_4stage_multimer.json" """ #!/usr/bin/env python3 import csv @@ -26,6 +28,18 @@ process JSONMANAGER { import os, sys import math + sample_dir = os.path.dirname(os.path.abspath("${samplesheet}")) + os.makedirs("target_json", exist_ok=True) + os.makedirs("advanced_json", exist_ok=True) + + def resolve_path(raw_path): + if not raw_path: + return raw_path + if os.path.isabs(raw_path): + return raw_path + candidate = os.path.join(sample_dir, raw_path) + return candidate if os.path.exists(candidate) else raw_path + with open("${samplesheet}", 'r') as csvfile: reader = csv.DictReader(csvfile, quotechar="\\${quote_char}") for row in reader: @@ -40,6 +54,13 @@ process JSONMANAGER { number_of_final_designs = int(row['number_of_final_designs']) if batches > number_of_final_designs: batches = number_of_final_designs + + settings_advanced = (row.get('settings_advanced') or '').strip() + if not settings_advanced: + settings_advanced = "${default_advanced}" + settings_advanced = resolve_path(settings_advanced) + with open(settings_advanced, 'r') as advanced_file: + advanced_template = json.load(advanced_file) batch_size = math.ceil(number_of_final_designs / batches) row['number_of_final_designs'] = batch_size @@ -47,8 +68,13 @@ process JSONMANAGER { if batch_id == batches - 1: row['number_of_final_designs'] = number_of_final_designs - batch_id * batch_size row['design_path'] = f"{sample_id}_{batch_id}_output" - with open(f"{sample_id}-{batch_id}.json", 'w') as jsonfile: + with open(f"target_json/{sample_id}-{batch_id}.json", 'w') as jsonfile: json.dump(row, jsonfile, indent=2) + + advanced_settings = dict(advanced_template) + advanced_settings['max_trajectories'] = row['number_of_final_designs'] + with open(f"advanced_json/{sample_id}-{batch_id}-advanced.json", 'w') as advanced_json_file: + json.dump(advanced_settings, advanced_json_file, indent=2) with open ("versions.yml", "w") as version_file: version_file.write("\\"${task.process}\\":\\n python: {}\\n".format(sys.version.split()[0].strip())) @@ -56,8 +82,11 @@ process JSONMANAGER { stub: """ - touch s1-0.json - touch s1-1.json + mkdir -p target_json advanced_json + touch target_json/s1-0.json + touch target_json/s1-1.json + touch advanced_json/s1-0-advanced.json + touch advanced_json/s1-1-advanced.json cat <<-END_VERSIONS > versions.yml "${task.process}": diff --git a/subworkflows/local/run_bindcraft.nf b/subworkflows/local/run_bindcraft.nf index e67a55d..5bcd946 100644 --- a/subworkflows/local/run_bindcraft.nf +++ b/subworkflows/local/run_bindcraft.nf @@ -30,16 +30,15 @@ workflow RUN_BINDCRAFT { ch_versions = Channel.empty() - input - .splitCsv(header: true, quote : quote_char) - .map {row -> - [ - ["id": row.id], - file(row.starting_pdb, checkIfExists: true), - get_file(row.settings_filters, params.settings_filters, "${projectDir}/assets/bindcraft/default_filters.json"), - get_file(row.settings_advanced, params.settings_advanced, "${projectDir}/assets/bindcraft/default_4stage_multimer.json"), - ] - } + input + .splitCsv(header: true, quote : quote_char) + .map {row -> + [ + ["id": row.id], + file(row.starting_pdb, checkIfExists: true), + get_file(row.settings_filters, params.settings_filters, "${projectDir}/assets/bindcraft/default_filters.json") + ] + } .set { ch_settings } input .splitCsv(header: true, quote : quote_char) @@ -50,18 +49,27 @@ workflow RUN_BINDCRAFT { ] }.set{ch_final_designs} - JSONMANAGER( - input, - batches - ) - - JSONMANAGER.out.json - .flatten() - .map{[["id": it.baseName.split('-')[0..-2].join('-')], it.baseName.split('-')[-1].replace(".json", ""), it]} - .combine(ch_settings) - .filter{it[0].id == it[3].id} - .map{[["id": it[0].id, "batch": it[1]], it[2], it[4], it[5], it[6]]} - .set {ch_bindcraft_input} + JSONMANAGER( + input, + batches + ) + + JSONMANAGER.out.json + .flatten() + .map{[["id": it.baseName.split('-')[0..-2].join('-'), "batch": it.baseName.split('-')[-1].replace(".json", "")], it]} + .join( + JSONMANAGER.out.advanced_json + .flatten() + .map{ + def stripped_base = it.baseName.replaceFirst(/-advanced$/, '') + [["id": stripped_base.split('-')[0..-2].join('-'), "batch": stripped_base.split('-')[-1]], it] + } + ) + .map{[["id": it[0].id], it[0].batch, it[1], it[2]]} + .combine(ch_settings) + .filter{it[0].id == it[4].id} + .map{[["id": it[0].id, "batch": it[1]], it[2], it[5], it[6], it[3]]} + .set {ch_bindcraft_input} BINDCRAFT( ch_bindcraft_input.map {[it[0], it[1]]}, @@ -114,4 +122,4 @@ def get_file(String sheet_file, String general_file, String assets_file) { } return file(sheet_file) -} \ No newline at end of file +} From ad494bcc0279520d8d5fd11ea23ea6eeaa356d22 Mon Sep 17 00:00:00 2001 From: Kisaru Liyanage Date: Sun, 9 Aug 2026 16:13:35 +1000 Subject: [PATCH 2/3] refine max_traj and expose in samplesheet --- README.md | 5 +++-- assets/samplesheet.csv | 4 ++-- assets/schema_input.json | 9 ++++++++- docs/usage.md | 5 +++-- modules/local/jsonmanager/main.nf | 17 +++++++++++------ 5 files changed, 27 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index c78da61..3b18d4f 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,8 @@ First, prepare a samplesheet with your input data that looks as follows: `samplesheet.csv`: ```csv -id,binder_name,starting_pdb,chains,target_hotspot_residues,min_length,max_length,number_of_final_designs,settings_advanced,settings_filters -demo,PDL1,PDL1.pdb,A,"56",65,150,10,default_4stage_multimer.json,default_filters.json +id,binder_name,starting_pdb,chains,target_hotspot_residues,min_length,max_length,number_of_final_designs,max_trajectories,settings_advanced,settings_filters +demo,PDL1,PDL1.pdb,A,"56",65,150,10,100,default_4stage_multimer.json,default_filters.json ``` Each row represents a single design instance. Detailed documentation describing job parameters can be found in the BindCraft [documentation](https://github.com/martinpacesa/BindCraft). @@ -76,6 +76,7 @@ Briefly: - **min_length** defines the minimum length of the designed binder. - **max_length** defines the maximum length of the designed binder. - **number_of_final_designs** defines the number of binders required to pass QC criteria before the job is complete. +- **max_trajectories** defines the maximum number of design trajectories (if provided in **settings_advanced** as well, it will be overridden). - **settings_advanced** defines advanced BindCraft settings (JSON format). - **settings_filters** defines advanced BindCraft filter settings (JSON format). diff --git a/assets/samplesheet.csv b/assets/samplesheet.csv index 1553128..7c3956e 100644 --- a/assets/samplesheet.csv +++ b/assets/samplesheet.csv @@ -1,2 +1,2 @@ -id,binder_name,starting_pdb,chains,target_hotspot_residues,min_length,max_length,number_of_final_designs,settings_filters,settings_advanced -s1,PDL1,https://raw.githubusercontent.com/Australian-Structural-Biology-Computing/bindflow/refs/heads/main/assets/bindcraft/PDL1.pdb,A,56,65,150,4,https://raw.githubusercontent.com/Australian-Structural-Biology-Computing/bindflow/refs/heads/main/assets/bindcraft/default_filters.json,https://raw.githubusercontent.com/Australian-Structural-Biology-Computing/bindflow/refs/heads/main/assets/bindcraft/default_4stage_multimer.json +id,binder_name,starting_pdb,chains,target_hotspot_residues,min_length,max_length,number_of_final_designs,max_trajectories,settings_filters,settings_advanced +s1,PDL1,https://raw.githubusercontent.com/Australian-Structural-Biology-Computing/bindflow/refs/heads/main/assets/bindcraft/PDL1.pdb,A,56,65,150,2,4,https://raw.githubusercontent.com/Australian-Structural-Biology-Computing/bindflow/refs/heads/main/assets/bindcraft/default_filters.json,https://raw.githubusercontent.com/Australian-Structural-Biology-Computing/bindflow/refs/heads/main/assets/bindcraft/default_4stage_multimer.json diff --git a/assets/schema_input.json b/assets/schema_input.json index f976bed..31e7b89 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -56,6 +56,13 @@ "errorMessage": "", "meta": ["number_of_final_designs"] }, + "max_trajectories": { + "type": "integer", + "minimum": 1, + "default": 1000, + "errorMessage": "", + "meta": ["max_trajectories"] + }, "settings_filters": { "type": "string", "format": "file-path", @@ -70,6 +77,6 @@ } }, "required": ["id", "binder_name", "starting_pdb", "chains", "target_hotspot_residues", - "min_length", "max_length", "number_of_final_designs"] + "min_length", "max_length", "number_of_final_designs", "max_trajectories"] } } diff --git a/docs/usage.md b/docs/usage.md index 96e41bb..752bd13 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -45,8 +45,8 @@ You will need to create a samplesheet with information about the binder design j A final samplesheet file will look something like the one below. This is for a single design job aiming to produce 10 binders that pass QC between lengths 65-150, targeting the PDL1 protein at hotspot residue 56. ```csv title="samplesheet.csv" -id,binder_name,starting_pdb,chains,target_hotspot_residues,min_length,max_length,number_of_final_designs,settings_advanced,settings_filters -demo,PDL1,PDL1.pdb,A,"56",65,150,10,default_4stage_multimer.json,default_filters.json +id,binder_name,starting_pdb,chains,target_hotspot_residues,min_length,max_length,number_of_final_designs,max_trajectories,settings_advanced,settings_filters +demo,PDL1,PDL1.pdb,A,"56",65,150,10,100,default_4stage_multimer.json,default_filters.json ``` | Column | Description | @@ -59,6 +59,7 @@ demo,PDL1,PDL1.pdb,A,"56",65,150,10,default_4stage_multimer.json,default_filters | `min_length` | The minimum length to sample from potential binders. | | `max_length` | The maximum length to sample for potential binders. | | `number_of_final_designs` | The number of designs that pass all QC criteria before the pipeline will complete. | +| `max_trajectories` | The maximum number of design trajectories (if provided in `settings_advanced` as well, it will be overridden). | | `settings_advanced` | Full path to json file defining bindcraft advanced settings (Optional: Default is `default_4stage_multimer.json`) | | `settings_filters` | Full path to json file defining bindcraft QC filter settings (Optional: Default is `default_filters.json`) | diff --git a/modules/local/jsonmanager/main.nf b/modules/local/jsonmanager/main.nf index 802dc6c..0ef6feb 100644 --- a/modules/local/jsonmanager/main.nf +++ b/modules/local/jsonmanager/main.nf @@ -52,8 +52,9 @@ process JSONMANAGER { row['starting_pdb'] = os.path.basename(row['starting_pdb']) row['lengths'] = [int(row['min_length']), int(row['max_length'])] number_of_final_designs = int(row['number_of_final_designs']) - if batches > number_of_final_designs: - batches = number_of_final_designs + max_trajectories = int(row['max_trajectories']) + if batches > max_trajectories: + batches = max_trajectories settings_advanced = (row.get('settings_advanced') or '').strip() if not settings_advanced: @@ -62,17 +63,21 @@ process JSONMANAGER { with open(settings_advanced, 'r') as advanced_file: advanced_template = json.load(advanced_file) - batch_size = math.ceil(number_of_final_designs / batches) - row['number_of_final_designs'] = batch_size + max_trajectories_per_batch = math.ceil(max_trajectories / batches) + number_of_final_designs_per_batch = math.ceil(number_of_final_designs / batches) + row['number_of_final_designs'] = number_of_final_designs_per_batch + row.pop('max_trajectories', None) for batch_id in range(0, batches): if batch_id == batches - 1: - row['number_of_final_designs'] = number_of_final_designs - batch_id * batch_size + row['number_of_final_designs'] = number_of_final_designs - batch_id * number_of_final_designs_per_batch row['design_path'] = f"{sample_id}_{batch_id}_output" with open(f"target_json/{sample_id}-{batch_id}.json", 'w') as jsonfile: json.dump(row, jsonfile, indent=2) advanced_settings = dict(advanced_template) - advanced_settings['max_trajectories'] = row['number_of_final_designs'] + advanced_settings['max_trajectories'] = max_trajectories_per_batch + if batch_id == batches - 1: + advanced_settings['max_trajectories'] = max_trajectories - batch_id * max_trajectories_per_batch with open(f"advanced_json/{sample_id}-{batch_id}-advanced.json", 'w') as advanced_json_file: json.dump(advanced_settings, advanced_json_file, indent=2) From 52af9425738095251f64457d676fcc30a00f527d Mon Sep 17 00:00:00 2001 From: Kisaru Liyanage Date: Mon, 10 Aug 2026 11:40:29 +1000 Subject: [PATCH 3/3] make accepted output optional --- modules/local/bindcraft/main.nf | 2 +- subworkflows/local/run_bindcraft.nf | 65 +++++++++++++++-------------- 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/modules/local/bindcraft/main.nf b/modules/local/bindcraft/main.nf index 827d102..70115cf 100644 --- a/modules/local/bindcraft/main.nf +++ b/modules/local/bindcraft/main.nf @@ -10,7 +10,7 @@ process BINDCRAFT { output: tuple val(meta), path("*_final_design_stats.csv"), emit: stats tuple val(meta), path("*_output/Accepted/Ranked"), emit: accepted_ranked - tuple val(meta), path("*_output/Accepted/*pdb"), emit: accepted + tuple val(meta), path("*_output/Accepted/*pdb"), emit: accepted, optional: true tuple val(meta), path("*_output"), emit: output_dir path "versions.yml", emit: versions diff --git a/subworkflows/local/run_bindcraft.nf b/subworkflows/local/run_bindcraft.nf index 5bcd946..d0686fe 100644 --- a/subworkflows/local/run_bindcraft.nf +++ b/subworkflows/local/run_bindcraft.nf @@ -30,15 +30,15 @@ workflow RUN_BINDCRAFT { ch_versions = Channel.empty() - input - .splitCsv(header: true, quote : quote_char) - .map {row -> - [ - ["id": row.id], - file(row.starting_pdb, checkIfExists: true), - get_file(row.settings_filters, params.settings_filters, "${projectDir}/assets/bindcraft/default_filters.json") - ] - } + input + .splitCsv(header: true, quote : quote_char) + .map {row -> + [ + ["id": row.id], + file(row.starting_pdb, checkIfExists: true), + get_file(row.settings_filters, params.settings_filters, "${projectDir}/assets/bindcraft/default_filters.json") + ] + } .set { ch_settings } input .splitCsv(header: true, quote : quote_char) @@ -49,27 +49,27 @@ workflow RUN_BINDCRAFT { ] }.set{ch_final_designs} - JSONMANAGER( - input, - batches - ) - - JSONMANAGER.out.json - .flatten() - .map{[["id": it.baseName.split('-')[0..-2].join('-'), "batch": it.baseName.split('-')[-1].replace(".json", "")], it]} - .join( - JSONMANAGER.out.advanced_json - .flatten() - .map{ - def stripped_base = it.baseName.replaceFirst(/-advanced$/, '') - [["id": stripped_base.split('-')[0..-2].join('-'), "batch": stripped_base.split('-')[-1]], it] - } - ) - .map{[["id": it[0].id], it[0].batch, it[1], it[2]]} - .combine(ch_settings) - .filter{it[0].id == it[4].id} - .map{[["id": it[0].id, "batch": it[1]], it[2], it[5], it[6], it[3]]} - .set {ch_bindcraft_input} + JSONMANAGER( + input, + batches + ) + + JSONMANAGER.out.json + .flatten() + .map{[["id": it.baseName.split('-')[0..-2].join('-'), "batch": it.baseName.split('-')[-1].replace(".json", "")], it]} + .join( + JSONMANAGER.out.advanced_json + .flatten() + .map{ + def stripped_base = it.baseName.replaceFirst(/-advanced$/, '') + [["id": stripped_base.split('-')[0..-2].join('-'), "batch": stripped_base.split('-')[-1]], it] + } + ) + .map{[["id": it[0].id], it[0].batch, it[1], it[2]]} + .combine(ch_settings) + .filter{it[0].id == it[4].id} + .map{[["id": it[0].id, "batch": it[1]], it[2], it[5], it[6], it[3]]} + .set {ch_bindcraft_input} BINDCRAFT( ch_bindcraft_input.map {[it[0], it[1]]}, @@ -81,7 +81,8 @@ workflow RUN_BINDCRAFT { BINDCRAFT.out.accepted .map{[["id": it[0].id], it[1]]} .groupTuple() - .join(ch_final_designs) + .join(ch_final_designs, remainder: true) + .map { [it[0], it[1] ?: [], it[2]] } .subscribe{ if (it[1].size() < it[2]){ log.warn "Sample: ${it[0].id}: The pipeline was unable to generate the target number of successful designs (${it[1].size()} of ${it[2]}) in the allocated time. Please consider changing hotspot residues or design configuration to increase design success rates" @@ -122,4 +123,4 @@ def get_file(String sheet_file, String general_file, String assets_file) { } return file(sheet_file) -} +}