Skip to content

Commit 067bd5f

Browse files
committed
Merge branch 'dev'
2 parents cf808d7 + 23d693b commit 067bd5f

27 files changed

+484
-403
lines changed

README.md

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# OneRoof: A Pipeline Prototype for Base-, Variant-, and Consensus-calling under One Proverbial Roof
2-
32
[![Nextflow](https://img.shields.io/badge/nextflow%20DSL2-%E2%89%A522.10.1-23aa62.svg)](https://www.nextflow.io/) [![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/) [![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/) [![Docker CI](https://github.com/nrminor/oneroof/actions/workflows/docker-image.yaml/badge.svg)](https://github.com/nrminor/oneroof/actions/workflows/docker-image.yaml)
43

4+
55
- [Overview](#overview)
66
- [Quick Start](#quick-start)
77
- [Detailed Setup Instructions](#detailed-setup-instructions)
@@ -87,6 +87,10 @@ Most users should configure `oneroof` through the command line via the following
8787
| `--secondary` | None | Whether to turn on secondary alignments for each amplicon. Secondary alignments can increase depth at the cost of more reads potentially mapping to the wrong locations. By default, secondary alignments are off. |
8888
| `--min_consensus_freq` | 0.5 | The minimum required frequency of a variant base to be included in a consensu sequence. |
8989
| `--min_depth_coverage` | 20 | Minimum required depth of coverage to call a variant. |
90+
| `--min_variant_frequency` | 0.05 (illumina) or 0.10 (nanopore) | Minimum variant frequency to call a variant. |
91+
| `--meta_ref` | None | Dataset, either a local FASTA file or a pre-built dataset built by Sylph, to use for metagenomic profiling. Can download prebuilt ones here: https://github.com/bluenote-1577/sylph/wiki/Pre%E2%80%90built-databases. |
92+
| `--sylph_tax_db` | None | The taxonomic annotation for the sylph database specified with `--meta_ref`. The pipeline automaticially downloads the databases so only the identifier is needed here. |
93+
| `--nextclade_dataset` | None | The name of the dataset to run nextclade with. To see all dataset options run `nextclade dataset list --only-names`. |
9094
| `--results` | results/ | Where to place results. |
9195
| `--cleanup` | false | Whether to cleanup work directory after a successful run. |
9296

@@ -136,4 +140,39 @@ Contributions, feature requests, improvement suggestions, and bug reports via Gi
136140

137141
## Citation
138142

139-
Coming soon!
143+
> Lail, Andrew J., William C. Vuyk, Heather Machkovech, Nicholas R. Minor, Nura R. Hassan, Rhea Dalvie, Isla E. Emmen et al. “Amplicon sequencing of pasteurized retail dairy enables genomic surveillance of H5N1 avian influenza virus in United States cattle.” PloS one 20, no. 6 (2025): <https://doi.org/10.1371/journal.pone.0325203>.
144+
145+
``` bibtex
146+
@article{Lail2025-xf,
147+
title = "Amplicon sequencing of pasteurized retail dairy enables genomic surveillance of {H5N1} avian influenza virus in United States cattle",
148+
author = "Lail, Andrew J and Vuyk, William C and Machkovech, Heather and Minor, Nicholas R and Hassan, Nura R and Dalvie, Rhea and Emmen, Isla E and Wolf, Sydney and Kalweit, Annabelle and Wilson, Nancy and Newman, Christina M and Tiburcio, Patrick Barros and Weiler, Andrea and Friedrich, Thomas C and O'Connor, David H",
149+
journal = "PLoS One",
150+
publisher = "Public Library of Science (PLoS)",
151+
volume = 20,
152+
number = 6,
153+
pages = "e0325203",
154+
month = jun,
155+
year = 2025,
156+
copyright = "http://creativecommons.org/licenses/by/4.0/",
157+
language = "en"
158+
}
159+
```
160+
161+
> Kwon, Taeyong, Jessie D. Trujillo, Mariano Carossino, Heather M. Machkovech, Konner Cool, Eu Lim Lyoo, Gagandeep Singh et al. “Pathogenicity and transmissibility of bovine-derived HPAI H5N1 B3. 13 virus in pigs.” Emerging Microbes & Infections just-accepted (2025): <https://doi.org/10.1080/22221751.2025.2509742>.
162+
163+
``` bibtex
164+
@ARTICLE{Kwon2025-yq,
165+
title = "Pathogenicity and transmissibility of bovine-derived {HPAI} {H5N1} B3.13 virus in pigs",
166+
author = "Kwon, Taeyong and Trujillo, Jessie D and Carossino, Mariano and Machkovech, Heather M and Cool, Konner and Lyoo, Eu Lim and Singh, Gagandeep and Kafle, Sujan and Elango, Shanmugasundaram and Vediyappan, Govindsamy and Wei, Wanting and Minor, Nicholas and Matias-Ferreyra, Franco S and Morozov, Igor and Gaudreault, Natasha N and Balasuriya, Udeni B R and Hensley, Lisa E and Diel, Diego G and Ma, Wenjun and Friedrich, Thomas C and Richt, Juergen A",
167+
journal = "Emerg. Microbes Infect.",
168+
publisher = "Informa UK Limited",
169+
volume = 14,
170+
number = 1,
171+
pages = "2509742",
172+
month = dec,
173+
year = 2025,
174+
keywords = "Cattle; H5N1 genotype B3.13; highly pathogenic avian influenza; mammalian-like mutation; pathogenicity; pig; transmissibility",
175+
copyright = "http://creativecommons.org/licenses/by-nc/4.0/",
176+
language = "en"
177+
}
178+
```

bin/slack_alerts.py

Lines changed: 45 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
from pathlib import Path
77

88
import pandas as pd
9-
import requests
9+
from slack_sdk import WebClient
10+
from slack_sdk.errors import SlackApiError
1011

1112

1213
def parse_command_line_args() -> argparse.Namespace:
@@ -52,6 +53,8 @@ def passing_samples(df: pd.DataFrame, coverage_threshold) -> tuple[str, int]:
5253
count += 1
5354
passing_line = f"{sample_id}: {proportion}\n"
5455
passing_message += passing_line
56+
if passing_message == "":
57+
passing_message = "No passing samples."
5558
return (passing_message, count)
5659

5760

@@ -65,6 +68,8 @@ def failing_samples(df: pd.DataFrame, coverage_threshold) -> str:
6568
if proportion < threshold:
6669
failing_line = f"{sample_id}: {proportion}\n"
6770
failing_message += failing_line
71+
if failing_message == "":
72+
failing_message = "No failing samples."
6873
return failing_message
6974

7075

@@ -112,36 +117,44 @@ def send_slack_notification(
112117
passing, count_passing = passing_samples(stats_df, coverage_threshold)
113118
failing = failing_samples(stats_df, coverage_threshold)
114119

115-
# creating the return message
116-
message = (
117-
f"Oneroof has finished successfully for experiment {run_label}, "
118-
f"with {count_passing} samples passing. Below is a breakdown of "
119-
f"which samples had greater than or equal to {coverage_threshold}X coverage."
120-
)
121-
122-
results = f"PASSING\n-------\n{passing}\n\nFAILING\n-------\n{failing}"
123-
124-
complete_message = f"{message}\n```{results}```"
125-
126120
user_id_list = get_user_ids()
127121
slack_token = get_slack_token()
128122

123+
block = [
124+
{
125+
"type": "section",
126+
"text": {
127+
"type": "plain_text",
128+
"text": f"Oneroof has finished successfully for experiment {run_label}, with {count_passing} samples passing. Below is a breakdown of which samples had greater than or equal to {coverage_threshold}X coverage.",
129+
"emoji": True,
130+
},
131+
},
132+
{
133+
"type": "section",
134+
"fields": [
135+
{
136+
"type": "mrkdwn",
137+
"text": f"*PASSING* \n ----------- \n {passing} ",
138+
},
139+
{
140+
"type": "mrkdwn",
141+
"text": f"*FAILING* \n ----------- \n {failing}",
142+
},
143+
],
144+
},
145+
]
146+
129147
for user_id in user_id_list:
130-
resp = requests.post(
131-
"https://slack.com/api/conversations.open",
132-
headers={"Authorization": f"Bearer {slack_token}"},
133-
json={"users": user_id},
134-
)
135-
channel_id = resp.json().get("channel", {}).get("id")
136-
if not channel_id:
137-
raise RuntimeError("Failed to open conversation.")
138-
139-
# Send the message
140-
msg_resp = requests.post(
141-
"https://slack.com/api/chat.postMessage",
142-
headers={"Authorization": f"Bearer {slack_token}"},
143-
json={"channel": channel_id, "text": complete_message},
144-
)
148+
try:
149+
client = WebClient(token=slack_token)
150+
# Send the message to a channel
151+
response = client.chat_postMessage(
152+
channel=user_id, # or a user ID like "U12345678"
153+
blocks=block,
154+
)
155+
print("Message sent successfully!")
156+
except SlackApiError as e:
157+
print(f"Error sending message: {e.response['error']}")
145158

146159

147160
def main() -> None:
@@ -151,7 +164,11 @@ def main() -> None:
151164
coverage_tsv_dir_path = args.input_tsv_dir
152165
coverage_depth = args.depth
153166

154-
send_slack_notification(run_label, coverage_tsv_dir_path, coverage_depth)
167+
send_slack_notification(
168+
run_label,
169+
coverage_tsv_dir_path,
170+
coverage_depth,
171+
)
155172

156173

157174
if __name__ == "__main__":

conf/illumina.config

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,50 @@
11
params {
22

33
// set platform
4-
platform = "illumina"
4+
platform = "illumina"
55

66
// Minimum variant frequency to call a variant.
7-
min_variant_frequency = 0.05
7+
min_variant_frequency = 0.05
88

99
// the minimum acceptable average quality for a given read
10-
min_qual = 20
10+
min_qual = 20
1111

1212
// split off ont results
1313
params.illumina_results = params.results + "/illumina"
1414

1515
// Where to place the Nanopore workflow run command
16-
run_command = params.illumina_results
16+
run_command = params.illumina_results
1717

1818
// paired read merging
19-
merged = params.illumina_results + "/01_merged_reads"
19+
merged = params.illumina_results + "/01_merged_reads"
2020

2121
// primer handling results
22-
primer_handling = params.illumina_results + "/02_primer_handling"
23-
respliced = params.primer_handling + "/01_respliced_primers"
24-
complete_amplicons = params.primer_handling + "/02_complete_amplicons"
25-
merged_by_sample = params.primer_handling + "/03_merged_by_sampe"
22+
primer_handling = params.illumina_results + "/02_primer_handling"
23+
respliced = params.primer_handling + "/01_respliced_primers"
24+
complete_amplicons = params.primer_handling + "/02_complete_amplicons"
25+
merged_by_sample = params.primer_handling + "/03_merged_by_sampe"
2626

2727
// alignment results
28-
alignment = params.illumina_results + "/03_alignments"
29-
mosdepth = params.alignment + "/01_alignments"
30-
cov_plots = params.alignment + "/02_coverage_plots"
28+
alignment = params.illumina_results + "/03_alignments"
29+
mosdepth = params.alignment + "/01_alignments"
30+
cov_plots = params.alignment + "/02_coverage_plots"
3131

3232
// consensus results
33-
consensus = params.illumina_results + "/04_consensus_seqs"
33+
consensus = params.illumina_results + "/04_consensus_seqs"
34+
35+
// metagenomic results
36+
metagenomics = params.illumina_results + "/metagenomics"
3437

3538
// variant results
36-
variants = params.illumina_results + "/05_variants"
37-
ivar = params.variants + "/01_ivar_tables"
38-
vcf = params.variants + "/02_annotated_vcfs"
39-
variant_tsv = params.variants + "/03_variant_tsv"
39+
variants = params.illumina_results + "/05_variants"
40+
ivar = params.variants + "/01_ivar_tables"
41+
vcf = params.variants + "/02_annotated_vcfs"
42+
variant_tsv = params.variants + "/03_variant_tsv"
4043

4144
// qc results
42-
qc = params.illumina_results + "/06_QC"
45+
qc = params.illumina_results + "/06_QC"
4346

4447
// phylo results
45-
phylo = params.illumina_results + "07_phylo"
46-
nextclade = params.phylo + "01_nextclade"
47-
48+
phylo = params.illumina_results + "07_phylo"
49+
nextclade = params.phylo + "01_nextclade"
4850
}

conf/nanopore.config

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,56 @@
11
params {
22

33
// set platform
4-
platform = "ont"
4+
platform = "ont"
55

66
// Minimum variant frequency to call a variant.
77
min_variant_frequency = 0.2
88

99
// the minimum acceptable average quality for a given read
10-
min_qual = 10
10+
min_qual = 10
1111

1212
// split off ont results
13-
params.ont_results = params.results + "/nanopore"
13+
params.ont_results = params.results + "/nanopore"
1414

1515
// Where to place the Nanopore workflow run command
16-
run_command = params.ont_results
16+
run_command = params.ont_results
1717

1818
// basecalling and demultiplexing results
19-
basecall_results = params.ont_results + "/01_basecalled_demuxed"
20-
basecall_bams = params.basecall_results + "/bams"
21-
basecall_fastqs = params.basecall_results + "/fastqs"
19+
basecall_results = params.ont_results + "/01_basecalled_demuxed"
20+
basecall_bams = params.basecall_results + "/bams"
21+
basecall_fastqs = params.basecall_results + "/fastqs"
2222

2323
// primer handling results
24-
primer_handling = params.ont_results + "/02_primer_handling"
25-
respliced = params.primer_handling + "/01_respliced_primers"
26-
complete_amplicons = params.primer_handling + "/02_complete_amplicons"
27-
merged_by_sample = params.primer_handling + "/03_merged_by_sampe"
24+
primer_handling = params.ont_results + "/02_primer_handling"
25+
respliced = params.primer_handling + "/01_respliced_primers"
26+
complete_amplicons = params.primer_handling + "/02_complete_amplicons"
27+
merged_by_sample = params.primer_handling + "/03_merged_by_sampe"
2828

2929
// alignment results
30-
alignment = params.ont_results + "/03_alignments"
31-
mosdepth = params.alignment + "/01_mosdepth"
32-
cov_plots = params.alignment + "/02_coverage_plots"
30+
alignment = params.ont_results + "/03_alignments"
31+
mosdepth = params.alignment + "/01_mosdepth"
32+
cov_plots = params.alignment + "/02_coverage_plots"
3333

3434
// consensus results
35-
consensus = params.ont_results + "/04_consensus_seqs"
35+
consensus = params.ont_results + "/04_consensus_seqs"
36+
37+
// metagenomic results
38+
metagenomics = params.ont_results + "/metagenomics"
39+
40+
//haplyotyping results
41+
haplotyping = params.ont_results + "/haplotyping"
3642

3743
// variant results
38-
variants = params.ont_results + "/05_variants"
39-
ivar = params.variants + "/01_ivar_tables"
40-
vcf = params.variants + "/02_annotated_vcfs"
41-
variant_tsv = params.variants + "/03_variant_tsv"
42-
haplo = params.variants + "/04_haplotypes"
44+
variants = params.ont_results + "/05_variants"
45+
ivar = params.variants + "/01_ivar_tables"
46+
vcf = params.variants + "/02_annotated_vcfs"
47+
variant_tsv = params.variants + "/03_variant_tsv"
48+
haplo = params.variants + "/04_haplotypes"
4349

4450
// qc results
45-
qc = params.ont_results + "/06_QC"
51+
qc = params.ont_results + "/06_QC"
4652

4753
// phylo results
48-
phylo = params.ont_results + "/07_phylo"
49-
nextclade = params.phylo + "/01_nextclade"
50-
54+
phylo = params.ont_results + "/07_phylo"
55+
nextclade = params.phylo + "/01_nextclade"
5156
}

docs/developer.html.gz

8.04 KB
Binary file not shown.

docs/developer.pdf

-2.86 KB
Binary file not shown.

docs/index.html.gz

10.2 KB
Binary file not shown.

0 commit comments

Comments
 (0)