Skip to content

Commit 464645b

Browse files
authored
Merge pull request #479 from BU-ISCIII/develop
Develop merge for 2.2.7 release
2 parents 43615a3 + 6addb8d commit 464645b

28 files changed

Lines changed: 1375 additions & 74 deletions

File tree

CHANGELOG.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,54 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [2.2.7] - 2025-04-03 : https://github.com/BU-ISCIII/buisciii-tools/releases/tag/2.2.7
8+
9+
### Credits
10+
11+
- [Jaime Ozáez](https://github.com/jaimeozaezm)
12+
- [Alejandro Bernabeu](https://github.com/Aberdur)
13+
- [Sergio Olmos](https://github.com/OPSergio)
14+
- [Victor Lopez](https://github.com/victor5lm)
15+
- [Sarai Varona](https://github.com/svarona)
16+
- [Pablo Mata](https://github.com/Shettland)
17+
18+
### Template fixes and updates
19+
20+
- Fixed bug in 08_create_quality_control_summary.sh (viralrecon template) [#447](https://github.com/BU-ISCIII/buisciii-tools/pull/447).
21+
- Updated `services.json` file in order to properly delete folders and files when running clean module [#451](https://github.com/BU-ISCIII/buisciii-tools/pull/451).
22+
- Add assets for Updating Lineage-Defining Mutations from outbreak-info [#452](https://github.com/BU-ISCIII/buisciii-tools/pull/452)
23+
- Fix get_percentage_LDM.py to Use Versioned CSV File[#453](https://github.com/BU-ISCIII/buisciii-tools/pull/453).
24+
- Moved `lablog_bam2fq.sh` to `RAW/`** to centralize BAM-to-FASTQ processing [#455](https://github.com/BU-ISCIII/buisciii-tools/pull/455)
25+
- Updated `_01_bam2fq.sh` to correctly detect all BAM files in `RAW/` [#455](https://github.com/BU-ISCIII/buisciii-tools/pull/455)
26+
- Refactored `_02_pgzip.sh` to compress `.fastq` files and remove uncompressed versions [#455](https://github.com/BU-ISCIII/buisciii-tools/pull/455)
27+
- Created `_03_symlink.sh` to manage symbolic links in `ANALYSIS/00-reads/`, preventing broken links [#455](https://github.com/BU-ISCIII/buisciii-tools/pull/455)
28+
- Removed single quotes from sftp_copy in configuration.json [#458](https://github.com/BU-ISCIII/buisciii-tools/pull/458)
29+
- Fixed and enhanced some issues for lablog_viralrecon [#461](https://github.com/BU-ISCIII/buisciii-tools/pull/461)
30+
- Update sftp_users.json with new user for e.abascal [#464](https://github.com/BU-ISCIII/buisciii-tools/pull/464)
31+
- Update sample handling in get_percentage_LDM [#465](https://github.com/BU-ISCIII/buisciii-tools/pull/465)
32+
- Added _02_filter_results.sh script to pikavirus template [#466](https://github.com/BU-ISCIII/buisciii-tools/pull/466)
33+
- Changed short_obx for middle_idx in 02-clean.sh [#468](https://github.com/BU-ISCIII/buisciii-tools/pull/468)
34+
- Update exometrio lablog to Handle Fourth Individual [#469](https://github.com/BU-ISCIII/buisciii-tools/pull/469)
35+
- Update get_percentage_LDM.py to read sample column as string [#470](https://github.com/BU-ISCIII/buisciii-tools/pull/470)
36+
- Updated ivar varsion in viralrecon template to makwe it work with IonTorrent data [#471](https://github.com/BU-ISCIII/buisciii-tools/pull/471)
37+
- Update get_percentage_LDM.py to Handle Cases with No Lineage Found in outbreak.info CSV [#473](https://github.com/BU-ISCIII/buisciii-tools/pull/473)
38+
- Added autorun.sh script for automation of multiple sbatch running in viralrecon pipeline [#474](https://github.com/BU-ISCIII/buisciii-tools/pull/474)
39+
- Modified clean.py in order to properly delete exact matching-name folders and files [#476](https://github.com/BU-ISCIII/buisciii-tools/pull/476)
40+
- Added information on the period of permanence of the results in the sftp folder in email template [#477](https://github.com/BU-ISCIII/buisciii-tools/pull/477)
41+
42+
### Modules
43+
44+
#### Added enhancements
45+
46+
#### Fixes
47+
48+
#### Changed
49+
50+
#### Removed
51+
52+
### Requirements
53+
54+
755
## [2.2.6] - 2025-02-25 : https://github.com/BU-ISCIII/buisciii-tools/releases/tag/2.2.6
856

957
### Credits
@@ -34,6 +82,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
3482
- Fixed bioinfo-doc crashing after resuming [#435](https://github.com/BU-ISCIII/buisciii-tools/pull/435)
3583
- Fixed create_summary_report.sh to latest /data/ucct/bi/ layout [#436](https://github.com/BU-ISCIII/buisciii-tools/pull/436)
3684
- Changed taxprofiler lablog to skip kaiju, centrifuge and metaphlan [#439](https://github.com/BU-ISCIII/buisciii-tools/pull/439)
85+
- Conditional Copy of QC Scripts in lablog_viralrecon and Fixes in 99-stats (SNIPPY) & parse_ariba.py [#449](https://github.com/BU-ISCIII/buisciii-tools/pull/449)
86+
- Add generate_summary_outbreak.py to Complete Template with Outbreak Analysis Results [#450](https://github.com/BU-ISCIII/buisciii-tools/pull/450)
3787

3888
### Modules
3989

buisciii/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#!/usr/bin/env python
2-
""" Main buisciii package file.
3-
"""
2+
"""Main buisciii package file."""
43

54
import pkg_resources
65

buisciii/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def run_buisciii():
5757
)
5858

5959
# stderr.print("[green] `._,._,'\n", highlight=False)
60-
__version__ = "2.2.6"
60+
__version__ = "2.2.7"
6161
stderr.print(
6262
"[grey39] BU-ISCIII-tools version {}".format(__version__), highlight=False
6363
)
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
library(outbreakinfo)
2+
library(optparse)
3+
library(readxl)
4+
library(writexl)
5+
library(openxlsx)
6+
library(crayon)
7+
library(purrr)
8+
library(dplyr)
9+
library(stringr)
10+
options(browser = "xdg-open")
11+
################################################
12+
################################################
13+
## PARSE COMMAND-LINE PARAMETERS ##
14+
################################################
15+
################################################
16+
17+
source("/data/ucct/bi/references/outbreakinfo/setAuthToken.R")
18+
source("/data/ucct/bi/references/outbreakinfo/getAuthToken.R")
19+
20+
manageAuthToken <- function() {
21+
token <- getAuthToken()
22+
if (is.null(token)) {
23+
# If token is not found, request authentication
24+
message(crayon::yellow("No token found. Authenticating the user..."))
25+
outbreakinfo::authenticateUser()
26+
token <- Sys.getenv("OUTBREAK_INFO_TOKEN")
27+
if (nzchar(token)) {
28+
setAuthToken(token)
29+
message(crayon::green("Token authenticated and successfully saved."))
30+
} else {
31+
stop(crayon::red("Could not obtain token after authentication."))
32+
}
33+
} else {
34+
message(crayon::green("Token successfully loaded from file."))
35+
}
36+
Sys.setenv("OUTBREAK_INFO_TOKEN" = token)
37+
}
38+
39+
manageAuthToken()
40+
41+
message(crayon::blue("Continuing the analysis after token authentication."))
42+
43+
cat(cyan$bgRed$bold("########################\nStarting outbreakinfo pipeline\n###############################\n"))
44+
45+
option_list <- list(
46+
make_option(c("-l", "--lineage_list" ), type="character" , default='./Lineages_Mutations.xlsx' , metavar="path" , help="Path to lineage list file"),
47+
make_option(c("-o", "--output_folder" ), type="character" , default=NULL , metavar="path" , help="Path to output directory" )
48+
)
49+
50+
opt_parser <- OptionParser(option_list=option_list)
51+
opt <- parse_args(opt_parser)
52+
53+
if (is.null(opt$lineage_list)){
54+
print_help(opt_parser)
55+
stop("Please provide a lineage list file", call.=FALSE)
56+
}
57+
58+
if (is.null(opt$output_folder)){
59+
print_help(opt_parser)
60+
stop("Please provide a path to output directory", call.=FALSE)
61+
}
62+
63+
cat("########################\nRunning analysis with the following params:\n###############################\n")
64+
cat("-Lineage list file: ") + cat(opt$lineage_list)+cat("\n")
65+
cat("-Output directory: ") + cat(opt$output_folder)+cat("\n")
66+
67+
####LOAD DATA####
68+
lineages_of_interest <- read_excel(opt$lineage_list, sheet = 1, col_names = TRUE)
69+
colnames(lineages_of_interest) = c("Lineages")
70+
lineages_of_interest <- as.vector(lineages_of_interest$Lineages)
71+
72+
73+
getMutationsByLineage <- function(pangolin_lineage, frequency=0.75, logInfo=TRUE){
74+
75+
if(length(pangolin_lineage) > 1) {
76+
# Set frequency to 0 and then filter after the fact.
77+
df <- map_df(pangolin_lineage, function(lineage) getGenomicData(query_url="lineage-mutations", pangolin_lineage = lineage, frequency = 0.75, logInfo = logInfo))
78+
79+
if(!is.null(df) && nrow(df) != 0){
80+
mutations = df %>%
81+
filter(prevalence >= frequency) %>%
82+
pull(mutation) %>%
83+
unique()
84+
85+
df <- df %>%
86+
filter(mutation %in% mutations)
87+
}
88+
89+
} else {
90+
df <- getGenomicData(query_url="lineage-mutations", pangolin_lineage = pangolin_lineage, frequency = frequency, logInfo = logInfo)
91+
}
92+
93+
return(df)
94+
}
95+
96+
##GET LINEAGES DATA###
97+
mutations = getMutationsByLineage(pangolin_lineage=lineages_of_interest, frequency=0.75, logInfo = TRUE)
98+
table_name = paste(str_remove_all(as.Date(Sys.Date(), format = "%Y%m%d"), "-"), "mutaciones_definitorias_linaje.csv", sep = "_")
99+
table_path = paste(opt$output_folder, table_name, sep = "/")
100+
#write_xlsx(mutations, table_path, format_headers = F)
101+
write.csv(mutations, table_path, row.names = FALSE)
102+
103+
####Find if missing mutations###
104+
missing <- setdiff(lineages_of_interest,unique(mutations$lineage))
105+
if (length(missing) > 0) {
106+
cat("There are some missing lineages in the table: ") + cat(missing, sep = ", ")+cat("\n")
107+
}
108+
109+
cat("Excel of defining mutations generated as:", table_name)
110+
save.image()
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/bin/bash
2+
3+
scratch_dir=$(echo $PWD)
4+
5+
# micromamba activate outbreakinfo
6+
7+
echo "python3 update_lineage_data.py" > _00_update_lineage_data.sh
8+
9+
echo "Rscript get_mutations_outbreak_info.R --lineage_list ./lineages_data/latest.xlsx --output_folder /data/ucct/bi/references/outbreakinfo" > _01_get_mutations_outbreakinfo.sh
10+
echo "srun --partition short_idx --time 2:00:00 --chdir ${scratch_dir} --output logs/GETLDM.log --job-name GETLDM bash ./_01_get_mutations_outbreakinfo.sh &" > _01_run_get_mutations_outbreakinfo.sh
11+
12+
cat <<EOF > _02_version_mutations.sh
13+
#!/bin/bash
14+
15+
BASE_DIR="lineages_data"
16+
DATE=$(date +%Y%m%d) # Format YYYYMMDD
17+
OUTPUT_CSV="${DATE}_defining_lineage_mutations.csv"
18+
19+
# Get the current Pango-Designation version from latest.csv
20+
LATEST_VERSION=$(readlink -f "$BASE_DIR/latest.csv" | awk -F'/' '{print $(NF-1)}')
21+
VERSION_DIR="$BASE_DIR/$LATEST_VERSION"
22+
23+
# Verify that the mutation file exists in the current directory
24+
if [[ -f "$OUTPUT_CSV" ]]; then
25+
echo "✅ Mutation file found: $OUTPUT_CSV"
26+
27+
ABSOLUTE_CSV_PATH="$(readlink -f "$OUTPUT_CSV")"
28+
29+
# Move the file to the version directory
30+
mv "$ABSOLUTE_CSV_PATH" "$VERSION_DIR/"
31+
echo "📂 File moved to: $VERSION_DIR/$OUTPUT_CSV"
32+
33+
# Create or update the symbolic link to the latest CSV file
34+
ln -sf "$VERSION_DIR/$OUTPUT_CSV" "$BASE_DIR/latest_mutations.csv"
35+
echo "🔗 Symbolic link updated: $BASE_DIR/latest_mutations.csv -> $VERSION_DIR/$OUTPUT_CSV"
36+
37+
else
38+
echo "❌ ERROR: Mutation file ($OUTPUT_CSV) not found. Make sure to run _01_ first."
39+
exit 1
40+
fi
41+
42+
EOF
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import requests
2+
import os
3+
import pandas as pd
4+
5+
# URL Configuration
6+
TAGS_URL = "https://api.github.com/repos/cov-lineages/pango-designation/tags"
7+
CSV_URL = "https://raw.githubusercontent.com/cov-lineages/pango-designation/master/lineages.csv"
8+
BASE_DIR = "lineages_data"
9+
10+
11+
def get_latest_tag():
12+
"""Fetches the latest published tag from the repository."""
13+
response = requests.get(TAGS_URL)
14+
if response.status_code == 200:
15+
tags = response.json()
16+
if tags:
17+
return tags[0]["name"] # Latest available tag
18+
raise Exception("Failed to fetch repository tags.")
19+
20+
21+
def clean_tag(tag):
22+
"""Removes the leading 'v' if present to avoid duplicate names."""
23+
return tag.lstrip("v")
24+
25+
26+
def get_local_versions():
27+
"""Lists locally stored versions in subdirectories."""
28+
if not os.path.exists(BASE_DIR):
29+
return []
30+
return [d for d in os.listdir(BASE_DIR) if os.path.isdir(os.path.join(BASE_DIR, d))]
31+
32+
33+
def download_new_version(tag):
34+
"""Downloads the CSV file and stores it in a versioned subdirectory."""
35+
clean_version = clean_tag(tag) # Clean the version to avoid "vv"
36+
version_dir = os.path.join(BASE_DIR, f"v{clean_version}") # Version folder
37+
csv_path = os.path.join(version_dir, f"lineages_v{clean_version}.csv")
38+
excel_path = os.path.join(version_dir, f"Lineages_Mutations_v{clean_version}.xlsx")
39+
40+
if clean_version in [clean_tag(v) for v in get_local_versions()]:
41+
print(f"⚠️ The latest version is already stored in {csv_path}.")
42+
update_latest_symlinks(csv_path, excel_path)
43+
return
44+
45+
print(f"📥 Downloading new version to {csv_path}...")
46+
47+
os.makedirs(version_dir, exist_ok=True) # Create directory if it doesn't exist
48+
49+
response = requests.get(CSV_URL)
50+
51+
if response.status_code == 200:
52+
with open(csv_path, "w", encoding="utf-8") as f:
53+
f.write(f"# Version: {tag}\n") # Add version as the first line
54+
f.write(response.text) # Save CSV content
55+
print(f"✅ File saved at {csv_path}.")
56+
57+
# 📌 🔥 Ensure Excel file is correctly generated 🔥 📌
58+
if os.path.exists(csv_path):
59+
print(f"📊 Generating Excel file: {excel_path} ...")
60+
generate_excel(csv_path, excel_path, tag)
61+
else:
62+
print(f"❌ ERROR: CSV file not found at {csv_path}.")
63+
64+
update_latest_symlinks(csv_path, excel_path)
65+
else:
66+
print("❌ Error downloading the file.")
67+
68+
69+
def generate_excel(csv_path, excel_path, version):
70+
"""Generates an Excel file with unique lineages from the CSV and includes the version."""
71+
try:
72+
print(f"📂 Reading CSV: {csv_path}")
73+
74+
df = pd.read_csv(csv_path, comment="#") # Ignore version line
75+
print("📌 Detected columns in CSV:", df.columns)
76+
77+
if "lineage" not in df.columns:
78+
print(
79+
"❌ ERROR: The 'lineage' column is missing in the CSV. Aborting Excel generation."
80+
)
81+
return
82+
83+
unique_lineages = df["lineage"].dropna().unique()
84+
unique_df = pd.DataFrame(unique_lineages, columns=["Unique lineages"])
85+
86+
# Create an Excel workbook with one sheet for lineages and another for the version
87+
with pd.ExcelWriter(excel_path, engine="openpyxl") as writer:
88+
unique_df.to_excel(writer, sheet_name="Lineages", index=False)
89+
90+
# Add the version in a second sheet
91+
wb = writer.book
92+
ws_version = wb.create_sheet(title="Metadata")
93+
ws_version.append(["Version", version])
94+
95+
print(f"✅ Excel file generated at {excel_path} with version included.")
96+
97+
except Exception as e:
98+
print(f"❌ ERROR generating Excel file: {e}")
99+
100+
101+
def update_latest_symlinks(latest_csv_path, latest_excel_path):
102+
"""Updates symbolic links 'latest.csv' and 'latest.xlsx'."""
103+
latest_csv_symlink = os.path.join(BASE_DIR, "latest.csv")
104+
latest_excel_symlink = os.path.join(BASE_DIR, "latest.xlsx")
105+
106+
for symlink, target in [
107+
(latest_csv_symlink, latest_csv_path),
108+
(latest_excel_symlink, latest_excel_path),
109+
]:
110+
if os.path.exists(symlink) or os.path.islink(symlink):
111+
os.unlink(symlink) # Remove old symbolic link
112+
os.symlink(os.path.abspath(target), symlink) # Create new symbolic link
113+
print(f"🔗 Symbolic link '{symlink}' now points to {target}")
114+
115+
116+
if __name__ == "__main__":
117+
try:
118+
latest_tag = get_latest_tag()
119+
download_new_version(latest_tag)
120+
except Exception as e:
121+
print(f"❌ GENERAL ERROR: {e}")

buisciii/clean.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,12 +220,12 @@ def scan_dirs(self, to_find):
220220
# I've tried to continue if found, but I guess there could be several work folders in the project.. Let's see how it goes
221221
for root, dirs, files in os.walk(self.full_path):
222222
for item_to_be_found in to_find:
223-
if root.endswith(item_to_be_found):
223+
if os.path.basename(root) == item_to_be_found:
224224
pathlist.append(root)
225225
found.append(item_to_be_found)
226226
for file in files:
227227
path = os.path.join(root, file)
228-
if path.endswith(item_to_be_found):
228+
if file == item_to_be_found:
229229
pathlist.append(path)
230230
found.append(item_to_be_found)
231231

buisciii/conf/configuration.json

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@
1212
"protocol": "rsync",
1313
"options": ["-rlpv", "--update", "-L", "--inplace"],
1414
"exclusions": [
15-
"'*_NC'",
16-
"'*lablog*'",
17-
"'work'",
18-
"'00-reads'",
19-
"'*.sh'",
20-
"'.nextflow*'",
21-
"'*_DEL'",
22-
"'*.R'",
23-
"'*.py'",
24-
"'*.sbatch'"
15+
"*_NC",
16+
"*lablog*",
17+
"work",
18+
"00-reads",
19+
"*.sh",
20+
".nextflow*",
21+
"*_DEL",
22+
"*.R",
23+
"*.py",
24+
"*.sbatch"
2525
]
2626
},
2727
"xtutatis_api_settings": {

0 commit comments

Comments
 (0)