Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,24 @@ repos:
hooks:
- id: prettier
additional_dependencies:
- prettier@3.2.5

- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
rev: "3.0.3"
- prettier@3.6.2
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: editorconfig-checker
alias: ec
- id: trailing-whitespace
args: [--markdown-linebreak-ext=md]
exclude: |
(?x)^(
.*ro-crate-metadata.json$|
modules/nf-core/.*|
subworkflows/nf-core/.*|
.*\.snap$
)$
- id: end-of-file-fixer
exclude: |
(?x)^(
.*ro-crate-metadata.json$|
modules/nf-core/.*|
subworkflows/nf-core/.*|
.*\.snap$
)$
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ testing/
testing*
*.pyc
bin/
.nf-test/
ro-crate-metadata.json
modules/nf-core/
subworkflows/nf-core/
5 changes: 5 additions & 0 deletions .prettierrc.yml
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
printWidth: 120
tabWidth: 4
overrides:
- files: "*.{md,yml,yaml,html,css,scss,js,cff}"
options:
tabWidth: 2
2 changes: 0 additions & 2 deletions docs/output.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ This directory is created during the second series of steps of the pipeline, fea
<summary>Output files</summary>

- `library_QC/`

- `counts_heatmap.pdf`: a complete heatmap of absolute mutant counts, stratified by mutant amino acid (Y-axis) per position (X-axis)
![Count heatmap](images/library_QC_counts_heatmap.png)

Expand Down Expand Up @@ -104,7 +103,6 @@ This directory is created during the final series of steps of the pipeline, feat
<summary>Output files</summary>

- `fitness/`

- `counts_merged.tsv`: summarised gene variant counts across all input and output samples
- `default_results/fitness_estimation_count_correlation.pdf`: pair-wise replicate variant count scatterplots and correlations between all specified samples
![Variant count correlation(s)](images/fitness_estimation_count_correlation.png)
Expand Down
1 change: 0 additions & 1 deletion modules/local/dmsanalysis/templates/aa_seq.R
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,3 @@ writeLines(
f
)
close(f)

58 changes: 29 additions & 29 deletions modules/local/dmsanalysis/templates/possible_mutations.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

# ------------------------------------------------------------------------------
# Script: Generate Programmed Codon Variants
# Description: Generates all possible programmed codon mutations for a given
# Description: Generates all possible programmed codon mutations for a given
# wild-type sequence based on a specified mutagenesis strategy.
# Input:
# Input:
# - wt_seq_input: Wild-type sequence (string or path to FASTA file).
# - start_stop_pos: Target sequence range format "start-stop".
# - mutagenesis_type: Strategy ('nnk', 'nns', 'nnh', 'nnn', 'nnk_nns', 'nnk_nns_nnh', 'custom').
# - custom_codon_library_path: Path to custom library. Automatically detects
# if the file is a global list ("AAA, AAC...") or a position-wise CSV
# - custom_codon_library_path: Path to custom library. Automatically detects
# if the file is a global list ("AAA, AAC...") or a position-wise CSV
# (requires a "Position" header).
# - output_file: Desired name/path for the output CSV.
# Output: A CSV file containing all possible mutated codons per position.
Expand All @@ -20,24 +20,24 @@ suppressMessages(library(methods))

generate_possible_variants <- function(wt_seq_input, start_stop_pos, mutagenesis_type,
custom_codon_library_path, output_file) {

# Parse the start and stop positions from the input format "start-stop"
positions <- unlist(strsplit(start_stop_pos, "-"))
start_pos <- as.numeric(positions[1])
stop_pos <- as.numeric(positions[2])

# Load sequence from file or process as a direct string
if (file.exists(wt_seq_input)) {
seq_data <- Biostrings::readDNAStringSet(filepath = wt_seq_input)
wt_seq <- seq_data[[1]]
} else {
wt_seq <- Biostrings::DNAString(wt_seq_input)
}

# Extract the target coding sequence
coding_seq <- Biostrings::subseq(wt_seq, start = start_pos, end = stop_pos)
coding_seq <- as.character(coding_seq)

# Predefined codon dictionaries
nnk_codons <- c('AAG', 'AAT', 'ATG', 'ATT', 'AGG', 'AGT', 'ACG', 'ACT', 'TAG', 'TAT', 'TTG', 'TTT', 'TGG', 'TGT', 'TCG', 'TCT', 'GAG', 'GAT', 'GTG', 'GTT', 'GGG', 'GGT', 'GCG', 'GCT', 'CAG', 'CAT', 'CTG', 'CTT', 'CGG', 'CGT', 'CCG', 'CCT')
nns_codons <- c('AAG', 'AAC', 'ATG', 'ATC', 'AGG', 'AGC', 'ACG', 'ACC', 'TAG', 'TAC', 'TTG', 'TTC', 'TGG', 'TGC', 'TCG', 'TCC', 'GAG', 'GAC', 'GTG', 'GTC', 'GGG', 'GGC', 'GCG', 'GCC', 'CAG', 'CAC', 'CTG', 'CTC', 'CGG', 'CGC', 'CCG', 'CCC')
Expand All @@ -50,37 +50,37 @@ generate_possible_variants <- function(wt_seq_input, start_stop_pos, mutagenesis
is_position_wise <- FALSE
position_lookup <- list()
custom_codons <- NULL

if (mutagenesis_type == "custom") {
if (!file.exists(custom_codon_library_path) || is.null(custom_codon_library_path)) {
stop("Custom codons file must be provided and valid when using 'custom' mutagenesis_type.")
}

# Auto-detect format by inspecting the first line
first_line <- readLines(custom_codon_library_path, n = 1)

if (grepl("Position", first_line, ignore.case = TRUE)) {
# Format 1: Position-wise CSV file
is_position_wise <- TRUE

# Read line-by-line instead of read.csv to avoid strict column matching errors
lines <- readLines(custom_codon_library_path)

# Loop through lines, skipping the header (index 1)
for (line in lines[-1]) {
# Skip empty lines
if (trimws(line) == "") next
if (trimws(line) == "") next

# Split the line by commas
parts <- trimws(unlist(strsplit(line, ",")))

# The first part is the position, everything else are the codons
pos_idx <- parts[1]
codon_vec <- parts[-1]

# Remove any accidental empty strings (e.g., from trailing commas)
codon_vec <- codon_vec[codon_vec != ""]

position_lookup[[pos_idx]] <- codon_vec
}
} else {
Expand All @@ -89,19 +89,19 @@ generate_possible_variants <- function(wt_seq_input, start_stop_pos, mutagenesis
custom_codons <- trimws(custom_codons)
}
}

# Helper function to split a DNA sequence into nucleotide triplets
split_into_codons <- function(seq) {
# Note: Double escaping is required for Perl regular expressions here
return(strsplit(seq, "(?<=.{3})", perl = TRUE)[[1]])
}

wt_codons <- split_into_codons(coding_seq)

# Initialize dataframe to store final variant results
# Note: \$ escaping is maintained for Nextflow compatibility
result <- data.frame(Codon_Number = integer(), wt_codon = character(), Variant = character(), stringsAsFactors = FALSE)

# Helper function to determine the target codon list per position
get_codon_list <- function(wt_codon, codon_index) {
if (mutagenesis_type == "nnk") {
Expand Down Expand Up @@ -137,32 +137,32 @@ generate_possible_variants <- function(wt_seq_input, start_stop_pos, mutagenesis
stop("Invalid mutagenesis_type. Choose from 'nnk', 'nns', 'nnh', 'nnn', 'nnk_nns', 'nnk_nns_nnh', or 'custom'.")
}
}

# Iterate over each wild-type codon to assign programmed variants
for (i in seq_along(wt_codons)) {
wt_codon <- wt_codons[i]
codon_list <- get_codon_list(wt_codon, i)

# Skip iteration if no custom codons were assigned to this specific position
if (is.null(codon_list)) next
if (is.null(codon_list)) next

# Filter out the wild-type codon from the mutation list
possible_variants <- codon_list[codon_list != wt_codon]

for (variant in possible_variants) {
# Note: \$ escaping is maintained for Nextflow compatibility
result <- rbind(result, data.frame(Codon_Number = i, wt_codon = wt_codon, Variant = variant, stringsAsFactors = FALSE))
}
}

write.csv(result, output_file, row.names = FALSE)
}

# ------------------------------------------------------------------------------
# Main Execution Block (Nextflow variable substitution)
# ------------------------------------------------------------------------------

# Replaces bash if/else logic. If Nextflow omits the optional file,
# Replaces bash if/else logic. If Nextflow omits the optional file,
# it passes "/NULL", which R translates to an actual NULL object.
custom_lib_arg <- if ("$custom_codon_library" == "/NULL") {
NULL
Expand Down
1 change: 0 additions & 1 deletion modules/local/dmsanalysis/templates/process_gatk.R
Original file line number Diff line number Diff line change
Expand Up @@ -362,4 +362,3 @@ writeLines(
f
)
close(f)

2 changes: 1 addition & 1 deletion modules/local/fitness/fitness_experimental_design.nf
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ process EXPDESIGN_FITNESS {

script:
template 'dimsum_experimentalDesign.R'
}
}
9 changes: 4 additions & 5 deletions modules/local/fitness/templates/dimsum_experimentalDesign.R
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ make_dimsum_experimental_design <- function(samplesheet_csv, out_path = "experim

# tolerate missing file2 column (single-end)
if (!"file2" %in% names(ss)) ss\$file2 <- ""

required <- c("sample", "type", "replicate", "file1", "file2")
missing <- setdiff(required, names(ss))
if (length(missing) > 0) stop("Samplesheet missing columns: ", paste(missing, collapse = ", "))

# coerce types
ss\$replicate <- as.integer(ss\$replicate)

# ---- derive sample_name strategy ----
# If only one biological sample present (e.g. one protein), use "input1", "output2", ...
# If multiple biological samples present, prefix with 'sample' to avoid collisions:
Expand All @@ -38,11 +38,11 @@ make_dimsum_experimental_design <- function(samplesheet_csv, out_path = "experim
selection_replicate <- ifelse(ss\$type == "output", 1L, NA_integer_)
# assume one technical batch
technical_replicate <- rep(1L, nrow(ss))

pair1 <- basename(ss\$file1)
# keep empty string for single-end / missing file2
pair2 <- ifelse(is.na(ss\$file2) | ss\$file2 == "", "", basename(ss\$file2))

ed <- data.frame(
sample_name = sample_name,
experiment_replicate = experiment_replicate,
Expand Down Expand Up @@ -93,4 +93,3 @@ writeLines(
f
)
close(f)

2 changes: 1 addition & 1 deletion modules/local/fitness/templates/find_syn_mutation.R
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pick_synonymous_wt_from_range <- function(wt_fasta, counts_merged_tsv, pos_range
df <- utils::read.delim(counts_merged_tsv, sep = "\\t", header = TRUE,
stringsAsFactors = FALSE, check.names = FALSE)
if (!"nt_seq" %in% names(df)) stop("counts_merged_tsv must have a 'nt_seq' column.")

df\$nt_seq <- toupper(df\$nt_seq)
keep_len <- nchar(df\$nt_seq) == wt_len
if (!any(keep_len)) stop("No sequences match WT window length (", wt_len, ").")
Expand Down
Loading
Loading