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
2 changes: 1 addition & 1 deletion conf/test.config
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ params {
fasta = params.pipelines_testdata_base_path + 'deepmutscan/testdata/GID1A.fasta'
reading_frame = 352-1383
min_counts = 2
mutagenesis_type = max_diff_to_wt
mutagenesis_type = nnk_nns
run_seqdepth = true
fitness = true
}
4 changes: 2 additions & 2 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Note that the pipeline will create the following files in your working directory

```console title="working directory"
work # Directory containing the nextflow working files
results # Finished results in specified location (defined with --outdir)
results # Finished results in specified location (defined with --outdir); needs full writing access
.nextflow_log # Log file from Nextflow
```

Expand Down Expand Up @@ -82,7 +82,7 @@ Several optional parameters are available for `nf-core/deepmutscan`, some of whi
| `--run_seqdepth` | `false` | Estimate sequencing saturation by rarefaction |
| `--fitness` | `false` | Default fitness inference module |
| `--dimsum` | `false` | Optional fitness inference module _(AMD/x86_64 systems only)_ |
| `--mutagenesis` | `nnk` | Deep mutational scanning strategy used _(in development)_ |
| `--mutagenesis` | `nnk` | Deep mutational scanning strategy used |
| `--error-estimation` | `wt_sequencing` | Error model used to correct 1nt counts _(in development)_ |
| `--read-align` | `bwa-mem` | Customised read aligner _(in development)_ |

Expand Down
2 changes: 1 addition & 1 deletion modules/local/dmsanalysis/templates/possible_mutations.R
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# 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', 'max_diff_to_wt', 'custom').
# - mutagenesis_type: Strategy ('nnk', 'nns', 'nnh', 'nnn', 'nnk_nns', 'nnk_nns_nnh', 'custom').

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this reflected in nextlfow_schema.json? or not relevant?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, that's already been accounted for – thanks for checking

# - 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).
Expand Down
68 changes: 48 additions & 20 deletions modules/local/fitness/templates/fitness_calculation.R
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env Rscript

## default fitness estimation for nf-core/deepmutscan
## 27.10.2025
## 18.02.2026
## maximilian.stammnitz@crg.eu

## 0. Libraries ##
Expand Down Expand Up @@ -110,6 +110,22 @@ aggregate_by_aa <- function(merged.counts) {
merged.counts
}

# calculate bimodal fitness distribution
density_peaks <- function(x, adjust = 1, ...) {

## obtain density distribution
d <- density(x, adjust = adjust, n = 5000, na.rm = T, ...)
y <- d$y
idx <- which(diff(sign(diff(y))) == -2) + 1
if (length(idx) == 0) return(numeric(0))

## order density peaks by height (y value); take top 2
idx <- idx[order(y[idx], decreasing = TRUE)]
peaks_x <- d$x[idx]
peaks_x[seq_len(min(2, length(peaks_x)))]

}

# 3. Raw fitness calculations ##
calc_raw_fitness <- function(merged.counts, exp.design) {
## how many fitness replicates are there
Expand Down Expand Up @@ -164,24 +180,36 @@ rescale_and_summarize <- function(merged.counts, reps) {
### rescale
tmp.wt.fitness.med <- median(tmp.wt.fitness, na.rm = TRUE)
tmp.stop.fitness.med <- median(tmp.stop.fitness, na.rm = TRUE)
if(tmp.stop.fitness.med >= tmp.wt.fitness.med){

tmp.wt.fitness.mean <- mean(tmp.wt.fitness, na.rm = TRUE)
tmp.stop.fitness.mean <- mean(tmp.stop.fitness, na.rm = TRUE)
lm.rescale <- lm(c(0, -1) ~ c(tmp.wt.fitness.mean, tmp.stop.fitness.mean))
merged.counts[,ncol(merged.counts)] <- merged.counts[,ncol(merged.counts) - reps] * lm.rescale\$coefficients[[2]] + lm.rescale\$coefficients[[1]]
rm(tmp.wt.fitness, tmp.stop.fitness,
tmp.wt.fitness.mean, tmp.stop.fitness.mean,
tmp.wt.fitness.med, tmp.stop.fitness.med, lm.rescale)
next

}else{

## if both WT and STOP mutants are available
if(!is.na(tmp.wt.fitness.med) & !is.na(tmp.stop.fitness.med)){
lm.rescale <- lm(c(0, -1) ~ c(tmp.wt.fitness.med, tmp.stop.fitness.med))
merged.counts[,ncol(merged.counts)] <- merged.counts[,ncol(merged.counts) - reps] * lm.rescale\$coefficients[[2]] + lm.rescale\$coefficients[[1]]
rm(tmp.wt.fitness, tmp.stop.fitness,
tmp.wt.fitness.med, tmp.stop.fitness.med, lm.rescale)
next
merged.counts[,ncol(merged.counts)] <- merged.counts[,ncol(merged.counts) - reps] * lm.rescale$coefficients[[2]] + lm.rescale$coefficients[[1]]
rm(tmp.wt.fitness, tmp.stop.fitness,
tmp.wt.fitness.med, tmp.stop.fitness.med, lm.rescale)

## if only WT mutants are available: lower peak determined by bimodal distribution fitting
}else if(!is.na(tmp.wt.fitness.med) & is.na(tmp.stop.fitness.med)){
tmp.peaks <- sort(density_peaks(x = merged.counts[,ncol(merged.counts) - reps]))
lm.rescale <- lm(c(0, -1) ~ c(tmp.wt.fitness.med, tmp.peaks[1]))
merged.counts[,ncol(merged.counts)] <- merged.counts[,ncol(merged.counts) - reps] * lm.rescale$coefficients[[2]] + lm.rescale$coefficients[[1]]
rm(tmp.wt.fitness, tmp.stop.fitness,
tmp.wt.fitness.med, tmp.stop.fitness.med, lm.rescale, tmp.peaks)

## if only STOP mutants are available: higher peak determined by bimodal distribution fitting
}else if(is.na(tmp.wt.fitness.med) & !is.na(tmp.stop.fitness.med)){
tmp.peaks <- sort(density_peaks(x = merged.counts[,ncol(merged.counts) - reps]))
lm.rescale <- lm(c(0, -1) ~ c(tmp.peaks[2], tmp.stop.fitness.med))
merged.counts[,ncol(merged.counts)] <- merged.counts[,ncol(merged.counts) - reps] * lm.rescale$coefficients[[2]] + lm.rescale$coefficients[[1]]
rm(tmp.wt.fitness, tmp.stop.fitness,
tmp.wt.fitness.med, tmp.stop.fitness.med, lm.rescale, tmp.peaks)

## if neither WT nor STOP mutants are available: both peak determined by bimodal distribution fitting
}else if(is.na(tmp.wt.fitness.med) & is.na(tmp.stop.fitness.med)){
tmp.peaks <- sort(density_peaks(x = merged.counts[,ncol(merged.counts) - reps]))
lm.rescale <- lm(c(0, -1) ~ c(tmp.peaks[2], tmp.peaks[1]))
merged.counts[,ncol(merged.counts)] <- merged.counts[,ncol(merged.counts) - reps] * lm.rescale$coefficients[[2]] + lm.rescale$coefficients[[1]]
rm(tmp.wt.fitness, tmp.stop.fitness,
tmp.wt.fitness.med, tmp.stop.fitness.med, lm.rescale, tmp.peaks)

}
}
Expand All @@ -197,11 +225,11 @@ rescale_and_summarize <- function(merged.counts, reps) {

}else if(reps > 1){

merged.counts\$`mean fitness` <- apply(merged.counts[,c(ncol(merged.counts) - 2*reps + 1, ncol(merged.counts) - reps)],
merged.counts\$`mean fitness` <- apply(merged.counts[,c(ncol(merged.counts) - 1 - reps):c(ncol(merged.counts) - 2)],
1,
mean,
na.rm = TRUE)
merged.counts\$`fitness sd` <- apply(merged.counts[,c(ncol(merged.counts) - 2*reps + 1, ncol(merged.counts) - reps)],
merged.counts\$`fitness sd` <- apply(merged.counts[,c(ncol(merged.counts) - 1 - reps):c(ncol(merged.counts) - 2)],
1,
sd,
na.rm = TRUE)
Expand Down
Loading