Skip to content

Add WDL implementation for Salmon - #326

Open
PriyankaaXD wants to merge 19 commits into
stjudecloud:mainfrom
PriyankaaXD:add-salmon-wdl
Open

Add WDL implementation for Salmon#326
PriyankaaXD wants to merge 19 commits into
stjudecloud:mainfrom
PriyankaaXD:add-salmon-wdl

Conversation

@PriyankaaXD

Copy link
Copy Markdown

Adds a WDL implementation for Salmon (mapping-mode only), per the "tool wishlist" issue #228.

Tasks added in tools/salmon.wdl:

  • build_salmon_index — wraps salmon index
  • quant — wraps salmon quant

All "important options" from Salmon's docs are exposed as inputs, with defaults verified against salmon quant --help-reads output on Salmon 1.9.0. Parameter documentation is copied from Salmon's official docs, per guidance in the issue. Scoped to mapping-mode only (FASTQ input) — no BAM/alignment-mode support, as requested.

Tests added in tools/salmon.yml using the new Sprocket test framework, covering both tasks with real output assertions. Verified locally: sprocket lint passes cleanly, sprocket dev test passes both tests.

This is my first contribution to this project — happy to make any adjustments you'd like!

Before submitting this PR, please make sure:

  • You have added a few sentences describing the PR here.
  • The code passes all CI tests without any errors or warnings.
  • You have added tests (when appropriate).
  • You have added an entry in any relevant CHANGELOGs (when appropriate).
  • If you have made any changes to the scripts/ or docker/ directories, please ensure any image versions have been incremented accordingly!
  • You have updated the README or other documentation to account for these changes.

@stjudecloud-cloudy

stjudecloud-cloudy commented Aug 13, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Comment thread test/fixtures/salmon/reads_R1.fastq.gz Outdated
Comment thread tools/test/salmon.yaml
Comment thread tools/salmon.wdl Outdated
Comment thread tools/salmon.wdl Outdated
Comment thread tools/salmon.wdl Outdated
Comment on lines +228 to +229
"~{if length(read_twos) == 0 then "--fldMean " + fld_mean else ""}" \
"~{if length(read_twos) == 0 then "--fldSD " + fld_sd else ""}" \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think this will work as the arguments end up quoted in bash. Was this an attempt to address a sprocket lint warning?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed

Comment thread tools/salmon.wdl Outdated
Comment thread tools/salmon.wdl Outdated
Comment thread tools/test/salmon.yaml
Comment thread test/fixtures/salmon/salmon_index.tar.gz
Comment thread tools/salmon.wdl Outdated
@adthrasher

Copy link
Copy Markdown
Member

This also needs a CHANGELOG entry.

@a-frantz a-frantz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is looking really great! Left some comments to address, but this is close to the finish line 🚀

Comment thread test/fixtures/salmon/README.md Outdated
Comment thread tools/test/salmon.yaml
Comment thread tools/salmon.wdl Outdated
Comment thread tools/salmon.wdl Outdated
Comment thread tools/salmon.wdl Outdated
cpu: ncpu
memory: "~{ceil(transcripts_fasta_size * 4) + 4 + modify_memory_gb} GB"
disks: "~{disk_size_gb} GB"
container: "quay.io/biocontainers/salmon:1.9.0--h7e5ed60_0"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can we update to a Salmon-v2 container?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Quick note on the v2 migration: I updated the container and confirmed all our command-line flags remain compatible (both index and quant), and all 4 tests pass. But one thing worth flagging — the new SSHash-based index format is significantly larger than v1's for the same reference: our GRCh38.chrY_chrM.fa-based fixture went from 224MB (v1/pufferfish) to 1.15GB (v2/SSHash). Given the earlier concern about repo/LFS bloat, wanted to surface this before it lands — let me know if you'd still like to proceed with committing this larger fixture, or if you'd prefer a different approach (e.g., a smaller subset reference, or building the index fresh in CI rather than committing it).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

wow, that size explosion is shocking! Pinging @adthrasher as he might find this interesting.

I did some digging this morning, and I think the reason a tiny ref FASTA is creating such a huge index is because we are using subsetted chromosomes from the genome as the reference sequences, where Salmon is expecting individual transcripts from the transcriptome. There is likely an exponential relationship between ref seq length and disk size.

Interestingly, when I tried passing the chr1_chr19 fasta as a decoy to the chrY_chrM build, the resulting index shrank by more than half.

Ultimately though, I think we should commit an appropriate transcriptome reference and use that here, instead of taking the lazy shortcut I suggested earlier 😅 I will create a usable ref FASTA from the human reference transcriptome and commit it to main, then you can rebase this PR and use the file I upload as an input to salmon index for the tests.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

creating- gencode.v50.BCR_ABL1.transcripts.fa.gz — confirmed the index is now genuinely tiny (311KB vs. 1.1GB before), so that fully resolves the size issue!

One thing I've hit: our existing quant tests use the shared fastqs/test_R1.fq.gz/test_R2.fq.gz fixtures, but those were simulated from chrY_chrM — Salmon (correctly) reports 0 fragments mapping against the new BCR/ABL1 transcriptome, since they're from completely different genes. Do you have or know of any existing fixture reads that actually originate from BCR/ABL1, or would it make sense for me to generate a small synthetic FASTQ pair from within gencode.v50.BCR_ABL1.transcripts.fa.gz itself (similar to how I originally built a synthetic transcript, but this time going the other direction — deriving reads from a real transcript)?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think we need to care that Salmon reports 0 mappings for the purposes of this test. Its a good thought, and maybe something we want to incorporate in the future, but IMO the purpose of these tests is to ensure our WDL API is fully working with all the bells and whistles; we don't actually care about the data coming in or out (aside from whether it is well-formed or not)

Comment thread tools/salmon.wdl
@a-frantz a-frantz mentioned this pull request Aug 25, 2026
6 tasks
a-frantz added a commit that referenced this pull request Aug 25, 2026
_Describe the problem or feature in addition to a link to the issues._

This is needed for writing decent tests in #326 

BCR and ABL1 were chosen somewhat arbitrarily. We do have other test
fixtures referencing these genes specifically (tests are for Arriba),
but this doesn't interact with those tests

v50 release chosen as its latest. Other fixtures use v31; I could use
that release if we want

Before submitting this PR, please make sure:

- [x] You have added a few sentences describing the PR here.
- [x] The code passes all CI tests without any errors or warnings.
- [ ] You have added tests (when appropriate).
- [ ] You have added an entry in any relevant CHANGELOGs (when
appropriate).
- [ ] If you have made any changes to the `scripts/` or `docker/`
directories, please ensure any image versions have been incremented
accordingly!
- [x] You have updated the README or other documentation to account for
these changes (when appropriate).
@PriyankaaXD
PriyankaaXD requested a review from a-frantz August 31, 2026 07:26

@a-frantz a-frantz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

looking really good 🚂

Comment thread tools/salmon.wdl
Comment on lines +12 to +17
transcripts_fasta: "FASTA format file containing the reference transcriptome to index"
decoys_fasta: {
description: "Optional FASTA file containing decoy genome sequences to improve mapping specificity.",
help: "Per Salmon's decoy-aware indexing workflow.",
group: "Common",
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Both of these should support using a GZIPPED fasta as input

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed — both inputs now go through gzip -dcf with a fallback to plain cp if the file isn't actually gzipped, so either format works transparently.

Comment thread tools/salmon.wdl
@PriyankaaXD

Copy link
Copy Markdown
Author

since I iterated on the index fixture several times (custom → v1 → v2 → reverted → BCR/ABL1), my branch's LFS history now carries a few superseded large blobs (~1.3GB total across old versions) even though the current file is tiny. If you squash-merge, this shouldn't carry into main's history — let me know if you'd prefer I try to clean this up

@PriyankaaXD
PriyankaaXD requested a review from a-frantz September 4, 2026 05:49
@a-frantz

a-frantz commented Sep 4, 2026

Copy link
Copy Markdown
Member

If you squash-merge, this shouldn't carry into main's history — let me know if you'd prefer I try to clean this up

we'll do a squash-merge 👍 no action needed on your end

@a-frantz a-frantz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Good news: we're at the nitpicking stage of the review 🤣 Left a bunch of comments, but all of them are about very minor things. This looks great 🚀

Comment thread test/fixtures/salmon/README.md Outdated
@@ -0,0 +1,3 @@
# Salmon test fixtures

`salmon_index.tar.gz` — built with Salmon 2.6.0 by running the `build_salmon_index` task against the existing `reference/GRCh38.chrY_chrM.fa` fixture (chosen because the shared `fastqs/test_R1.fq.gz`/`fastqs/test_R2.fq.gz` reads were simulated from this reference, per their FASTQ headers).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this needs to reflect the latest build

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done — updated to reflect the current build using gencode.v50.BCR_ABL1.transcripts.fa.gz and Salmon 2.6.0.

Comment thread tools/CHANGELOG.md
Comment on lines +12 to +13
### Added
- Added WDL implementation for Salmon (`build_salmon_index` and `quant` tasks) [#326](https://github.com/stjudecloud/workflows/pull/326)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: can we bump this up to a new ## 2026 September header? And make sure there are the empty lines on either side of each header

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

done

Comment thread tools/salmon.wdl Outdated
@@ -0,0 +1,293 @@
version 1.1

task build_salmon_index {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit:

Suggested change
task build_salmon_index {
task index {

and make sure this name change gets reflected in the tests and changelog etc

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done — renamed throughout, including tests and CHANGELOG.

Comment thread tools/salmon.wdl Outdated
meta {
description: "Builds a Salmon index from a transcriptome FASTA file, for use in quantification"
outputs: {
salmon_index_tar_gz: "A gzipped TAR file containing the Salmon index files."

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
salmon_index_tar_gz: "A gzipped TAR file containing the Salmon index files."
index_tar_gz: "A gzipped TAR file containing the Salmon index files."

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done — updated to use the same regex pattern from star.wdl for stripping the FASTQ read-number/extension suffix, matching the convention used elsewhere for handling non-standardized FASTQ naming.

Comment thread tools/salmon.wdl Outdated
n_cores=$(nproc)
fi

(gzip -dcf "~{transcripts_fasta}" > transcripts.fasta 2>/dev/null) || cp "~{transcripts_fasta}" transcripts.fasta

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

let's keep this consistent with how we do this operation in other tasks. Example pulled from the STAR file

        gtf_name=~{basename(gtf, ".gz")}
        gunzip -c "~{gtf}" > "$gtf_name" || ln -sf "~{gtf}" "$gtf_name"

the unzip calls are equivalent here, and no real reason to prefer one over the other, but we definitely want to create a symlink instead of copying the whole file in the second case.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done — switched to this exact pattern, matching STAR's style.

Comment thread tools/salmon.wdl Outdated
(gzip -dcf "~{transcripts_fasta}" > transcripts.fasta 2>/dev/null) || cp "~{transcripts_fasta}" transcripts.fasta
fasta="transcripts.fasta"

~{if defined(decoys_fasta) then "(gzip -dcf " + select_first([decoys_fasta]) + " > decoys.fasta 2>/dev/null) || cp " + select_first([decoys_fasta]) + " decoys.fasta" else ""}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this can be rewritten thanks to a quirk of the WDL language. If an expression within a string interpolation evals to None, the whole string is replaced with the empty string. So we can chop off a lot of this wrapping:

Suggested change
~{if defined(decoys_fasta) then "(gzip -dcf " + select_first([decoys_fasta]) + " > decoys.fasta 2>/dev/null) || cp " + select_first([decoys_fasta]) + " decoys.fasta" else ""}
~{"(gzip -dcf " + decoys_fasta + " > decoys.fasta 2>/dev/null) || cp " + decoys_fasta + " decoys.fasta"}

if decoys_fasta == None the whole thing turns into an empty string no-op.

Please update all the following lines to use this less verbose form, and also use symlinks instead of copying

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Used this simplified form for the lines that reference decoys_fasta directly (the gunzip/ln -sf line). The grep, cat, and fasta=combined.fasta lines needed to stay as explicit if defined(decoys_fasta) then ... else "", since they only reference the derived $decoys_name bash variable rather than decoys_fasta itself — there's nothing for WDL to collapse on there. Confirmed by testing: without the explicit wrapping, those lines ran unconditionally even when no decoys were supplied, which broke the plain (non-decoy) index build. Also switched to symlinks as requested.

Comment thread tools/salmon.wdl

runtime {
cpu: ncpu
memory: "~{ceil(transcripts_fasta_size * 4) + 4 + modify_memory_gb} GB"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think we need the ceil() call here. WDL engines/backends should all handle fractional/float memory requests fine

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Tried removing this, but Sprocket rejected the resulting fractional value (4.000064 GB) as an invalid runtime.memory requirement — reverted, keeping ceil()

Comment thread tools/salmon.wdl Outdated
description: "Runs Salmon quant in mapping-based mode to quantify transcript-level expression from RNA-Seq reads, using a pre-built Salmon index"
outputs: {
quant_results_tar_gz: "A gzipped TAR file containing the Salmon quantification output directory, including `quant.sf`.",
quant_sf: "The raw `quant.sf` transcript quantification file, provided directly in addition to the tarballed output for convenience."

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

let's clarify here that the quant.sf file gets renamed to <prefix>.quant.sf

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

done

Comment thread tools/salmon.wdl Outdated
Array[File]+ read_one_fastqs_gz
Array[File]? read_two_fastqs_gz
String lib_type = "A"
String prefix = basename(read_one_fastqs_gz[0], ".fastq.gz")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this should be calculated the same way we do for other FASTQs (whose naming conventions in the wild are frustratingly non-standardized 🤣 ). See the README for an explanation, and just copy+paste the RE from another task.

@PriyankaaXD

Copy link
Copy Markdown
Author

Just a quick note - builds_index_with_decoys fails locally for me on Windows with Unsupported reparse point type when reading back a symlink created inside the container. Manually confirmed the symlink itself is created correctly on the Linux side (via docker run ... ln -sf ...), so this looks like a Windows-host/Docker-volume limitation on my end, not an actual bug — should be fine in your CI, but flagging in case you want to verify.

@PriyankaaXD
PriyankaaXD requested a review from a-frantz September 9, 2026 05:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants