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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@

### Added

- Latest stable containers for the PacBio full-length isoform sequencing
note's transcript-visualization and isoform-classification/quantification
tools: Swan 3.2, ggtranscript 1.0.0, SQANTI3 6.0.2, TALON 6.0.1, Cerberus
1.1, LAPA 0.0.5, FLAIR 3.0.1, lr-kallisto 0.52.0, IsoQuant 4.0.0, Bambu
3.14.0, and Oarfish 0.10.3. Each image includes a build-time functional
verification and a versioned GHCR publishing target.
- Latest stable containers for all tools in the PacBio full-length isoform
sequencing note's differential-analysis sections: tappAS 1.1.3, DESeq2
1.52.0, DRIMSeq 1.40.0, DEXSeq 1.58.0, and SUPPA2 2.4. Each image includes a
build-time version and functional smoke test and is published under its
versioned GHCR tag.
- HOMER 5.1 (`images/homer/5.1`) with bedtools 2.31.1 and Python 3.12 for
FASTA-mode motif discovery, published as `ghcr.io/yeolab/homer:5.1`.
- skipper-clipper-compare 1.0.0 (`images/skipper-clipper-compare/1.0.0`): the
Expand Down
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,58 @@ write`, already declared in the workflow. If organization policy prevents a
publish, allow GitHub Actions to create packages for this repository or replace
the token with an approved package-write token.

## PacBio full-length isoform analysis

The tools listed under **Differential transcript analysis** and **Differential
splicing usage** in PacBio's full-length isoform sequencing application note
are available as individually versioned images:

| Application-note section | Tool | Image |
| --- | --- | --- |
| Differential transcript analysis | tappAS 1.1.3 | `ghcr.io/yeolab/tappas:1.1.3` |
| Differential transcript analysis | DESeq2 1.52.0 | `ghcr.io/yeolab/deseq2:1.52.0` |
| Differential transcript analysis | DRIMSeq 1.40.0 | `ghcr.io/yeolab/drimseq:1.40.0` |
| Differential splicing usage | DEXSeq 1.58.0 | `ghcr.io/yeolab/dexseq:1.58.0` |
| Differential splicing usage | SUPPA2 2.4 | `ghcr.io/yeolab/suppa2:2.4` |

DESeq2, DRIMSeq, and DEXSeq use the Bioconductor 3.23 release on R 4.6.
SUPPA2 is a command-line image whose arguments are passed directly to
`suppa.py`. tappAS is an x86-64 GUI image and needs an X11 display; see each
image directory's README for launch examples and resource requirements. Every
Dockerfile contains a build-time smoke test, so GitHub Actions publishes an
image only after its installed version and a representative operation pass.

The application note's transcript-visualization tools are also available:

| Application-note section | Tool | Image |
| --- | --- | --- |
| Transcript visualization | Swan 3.2 | `ghcr.io/yeolab/swan:3.2` |
| Transcript visualization | ggtranscript 1.0.0 | `ghcr.io/yeolab/ggtranscript:1.0.0` |

The latest stable releases of the note's isoform classification and
quantification tools are packaged as:

| Tool | Image |
| --- | --- |
| SQANTI3 6.0.2 | `ghcr.io/yeolab/sqanti3:6.0.2` |
| TALON 6.0.1 | `ghcr.io/yeolab/talon:6.0.1` |
| Cerberus 1.1 | `ghcr.io/yeolab/cerberus:1.1` |
| LAPA 0.0.5 | `ghcr.io/yeolab/lapa:0.0.5` |
| FLAIR 3.0.1 | `ghcr.io/yeolab/flair:3.0.1` |
| lr-kallisto / kallisto LongKmer 0.52.0 | `ghcr.io/yeolab/lr-kallisto:0.52.0` |
| IsoQuant 4.0.0 | `ghcr.io/yeolab/isoquant:4.0.0` |
| Bambu 3.14.0 | `ghcr.io/yeolab/bambu:3.14.0` |
| Oarfish 0.10.3 | `ghcr.io/yeolab/oarfish:0.10.3` |

Swan and the Python command-line tools use checksum-pinned release artifacts;
ggtranscript is pinned to the current upstream commit because the project has
not published GitHub releases. SQANTI3 and TALON use exact Bioconda builds,
Bambu uses Bioconductor 3.23, and lr-kallisto uses kallisto's official
LongKmer binary. The per-image READMEs document entrypoints, architecture, and
upstream version caveats. Build-time tests exercise transcript parsing,
annotation conversion, database creation, indexing, or quantification as
appropriate instead of checking only `--version` output.

## Pulling a Singularity/Apptainer image from GHCR

Install [Apptainer](https://apptainer.org/) (or SingularityCE), then pull a
Expand Down
19 changes: 19 additions & 0 deletions images/SQANTI3/6.0.2/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM quay.io/biocontainers/sqanti3:6.0.2--hdfd78af_1@sha256:3ebeb658fb2e0e308e73cba50dd3b97c63963d8225f303f298ad8ee76264939d

LABEL software="SQANTI3" \
software.version="6.0.2" \
about.summary="Quality control and classification of long-read transcript isoforms" \
about.url="https://github.com/ConesaLab/SQANTI3" \
about.license="GPL-3.0-only" \
about.tags="Genomics,long-read-RNA,isoform-classification"

ENV MPLBACKEND=Agg \
PYTHONPATH=/usr/local/share/sqanti3-6.0.2-1 \
PYTHONWARNINGS=ignore::DeprecationWarning

COPY verify.py /tmp/verify.py
RUN python /tmp/verify.py && rm /tmp/verify.py

WORKDIR /work
ENTRYPOINT []
CMD ["bash"]
15 changes: 15 additions & 0 deletions images/SQANTI3/6.0.2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# SQANTI3 6.0.2

This image is pinned to the official BioContainers image for SQANTI3's exact
Bioconda 6.0.2 build and includes the complete aligner, command-line, Python,
R, and report-generation stack.

```bash
docker run --rm -it -v "$PWD:/work" ghcr.io/yeolab/sqanti3:6.0.2
```

The build verifies both SQANTI3's reported version and R dependencies, checks
the external tools, and parses a synthetic two-exon genePred transcript through
SQANTI3's own parser.

Upstream: <https://github.com/ConesaLab/SQANTI3>
31 changes: 31 additions & 0 deletions images/SQANTI3/6.0.2/verify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import subprocess
import tempfile
from pathlib import Path

from src.config import __version__
from src.parsers import isoforms_parser


assert __version__ == "6.0.2"
assert "SQANTI3 6.0.2" in subprocess.check_output(
["sqanti3_qc.py", "--version"], text=True
)

Path("/test/logs").mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory() as directory:
gene_pred = Path(directory) / "isoforms.genePred"
gene_pred.write_text(
"tx1\tchr1\t+\t0\t200\t0\t200\t2\t0,100,\t50,200,\t0\tgene1\tcmpl\tcmpl\t0,0,\n"
)
parsed = isoforms_parser(str(gene_pred))
assert len(parsed["chr1"]) == 1
assert parsed["chr1"][0].junctions == [(50, 100)]

for executable in ("gffread", "gmap", "minimap2", "samtools", "STAR"):
subprocess.run(["bash", "-c", f"command -v {executable}"], check=True)

subprocess.run(
["Rscript", "-e", "stopifnot(as.character(packageVersion('RColorConesa')) == '1.0.0')"],
check=True,
)
print("SQANTI3 6.0.2 smoke test passed")
16 changes: 16 additions & 0 deletions images/bambu/3.14.0/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM bioconductor/bioconductor_docker:RELEASE_3_23@sha256:821dbf9ac119eac41f177531c7ca8fc7084c99c04eb218a1aa7f52cb63bad5d9

LABEL software="Bambu" \
software.version="3.14.0" \
about.summary="Long-read transcript discovery and quantification" \
about.url="https://bioconductor.org/packages/3.23/bioc/html/bambu.html" \
about.license="GPL-3.0-only" \
about.tags="Genomics,long-read-RNA,isoform-quantification"

RUN Rscript -e 'BiocManager::install("bambu", ask = FALSE, update = FALSE)'

COPY verify.R /tmp/verify.R
RUN Rscript /tmp/verify.R && rm /tmp/verify.R

WORKDIR /work
CMD ["R"]
12 changes: 12 additions & 0 deletions images/bambu/3.14.0/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Bambu 3.14.0

This image installs Bambu 3.14.0 from Bioconductor 3.23 on R 4.6.

```bash
docker run --rm -it -v "$PWD:/work" ghcr.io/yeolab/bambu:3.14.0
```

The build verifies the package version and converts a synthetic two-exon GTF
into Bambu's prepared annotation representation.

Upstream: <https://bioconductor.org/packages/3.23/bioc/html/bambu.html>
12 changes: 12 additions & 0 deletions images/bambu/3.14.0/verify.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
suppressPackageStartupMessages(library(bambu))
stopifnot(as.character(packageVersion("bambu")) == "3.14.0")

gtf <- tempfile(fileext = ".gtf")
writeLines(c(
'chr1\tsmoke\ttranscript\t1\t200\t.\t+\t.\tgene_id "g1"; transcript_id "t1";',
'chr1\tsmoke\texon\t1\t50\t.\t+\t.\tgene_id "g1"; transcript_id "t1"; exon_id "e1";',
'chr1\tsmoke\texon\t101\t200\t.\t+\t.\tgene_id "g1"; transcript_id "t1"; exon_id "e2";'
), gtf)
annotations <- prepareAnnotations(gtf)
stopifnot(length(annotations) == 1L)
message("Bambu 3.14.0 smoke test passed")
65 changes: 65 additions & 0 deletions images/cerberus/1.1/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
FROM python:3.10-slim@sha256:9d53d8d4c0e882f61913025db53b3aec4ef74336082a9ab47d8a14e9e8329b00

LABEL software="Cerberus" \
software.version="1.1" \
about.summary="Transcript start, end, and intron-chain annotation" \
about.url="https://github.com/mortazavilab/cerberus" \
about.license="MIT" \
about.tags="Genomics,long-read-RNA,isoform-classification"

ARG LAPA_URL="https://files.pythonhosted.org/packages/64/cc/e9059f9e9108f5a9a22e08164e29be3a937bab6c7636a8c2e51bf5b85ceb/lapa-0.0.5.tar.gz"
ARG LAPA_SHA256="3240405cbdd0e68c344676bc780e62368359024726f0ef2c04ef487d49793669"
ARG SWAN_URL="https://files.pythonhosted.org/packages/39/8e/153cc2311f3b7e3888540a18689673b312b3e72c68ef65cce9f7c482f8ed/swan_vis-3.2.tar.gz"
ARG SWAN_SHA256="b7da08a52cc37592378428f72a22a7e994af8d63118c8949c2b758b5dfc7c1db"
ARG CERBERUS_URL="https://github.com/mortazavilab/cerberus/archive/refs/tags/v1.1.tar.gz"
ARG CERBERUS_SHA256="c1693ef8ab48cdf841e769eb49d5d957b1d426635a6f7fdfe162914673d7de9f"

RUN apt-get update \
&& apt-get install -y --no-install-recommends build-essential ca-certificates curl patch \
&& rm -rf /var/lib/apt/lists/* \
&& curl -fsSL "$LAPA_URL" -o /tmp/lapa.tar.gz \
&& echo "$LAPA_SHA256 /tmp/lapa.tar.gz" | sha256sum -c - \
&& curl -fsSL "$SWAN_URL" -o /tmp/swan.tar.gz \
&& echo "$SWAN_SHA256 /tmp/swan.tar.gz" | sha256sum -c - \
&& pip install --no-cache-dir \
"numpy==1.23.0" \
"pandas==1.5.3" \
"scipy==1.10.1" \
"matplotlib==3.7.5" \
"h5py==3.8.0" \
"blosc2==2.0.0" \
"tables==3.8.0" \
"pandarallel==1.6.5" \
"anndata==0.9.2" \
"scanpy==1.9.8" \
"numba==0.57.1" \
"scikit-learn==1.3.2" \
"umap-learn==0.5.5" \
"sparse==0.14.0" \
"python-ternary==1.0.8" \
"cython<3" \
&& pip install --no-cache-dir --no-build-isolation "sorted-nearest==0.0.33" \
&& pip install --no-cache-dir "pyranges==0.1.4" \
&& pip install --no-cache-dir /tmp/lapa.tar.gz \
&& pip install --no-cache-dir /tmp/swan.tar.gz \
&& rm -rf /tmp/lapa.tar.gz /tmp/swan.tar.gz \
&& apt-get purge -y --auto-remove build-essential

COPY pyranges-compat.patch /tmp/pyranges-compat.patch

RUN curl -fsSL "$CERBERUS_URL" -o /tmp/cerberus.tar.gz \
&& echo "$CERBERUS_SHA256 /tmp/cerberus.tar.gz" | sha256sum -c - \
&& mkdir /tmp/cerberus \
&& tar -xzf /tmp/cerberus.tar.gz --strip-components=1 -C /tmp/cerberus \
&& sed -i "s/version='0.0.idk'/version='1.1'/" /tmp/cerberus/setup.py \
&& patch -d /tmp/cerberus -p1 < /tmp/pyranges-compat.patch \
&& pip install --no-cache-dir --no-deps /tmp/cerberus \
&& rm -rf /tmp/cerberus.tar.gz /tmp/cerberus /tmp/pyranges-compat.patch \
&& apt-get purge -y --auto-remove curl patch

COPY verify.sh /tmp/verify.sh
RUN bash /tmp/verify.sh && rm /tmp/verify.sh

WORKDIR /work
ENTRYPOINT ["cerberus"]
CMD ["--help"]
16 changes: 16 additions & 0 deletions images/cerberus/1.1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Cerberus 1.1

This image installs the latest Cerberus tag from a checksum-pinned source
archive. Upstream's placeholder package version is normalized to 1.1 so the
installed metadata agrees with the immutable tag.
The image also carries a compatibility patch that preserves strand
metadata when Cerberus reconstructs a PyRanges object with PyRanges 0.1.4.

```bash
docker run --rm -v "$PWD:/work" ghcr.io/yeolab/cerberus:1.1 --help
```

Arguments are passed directly to `cerberus`. The build converts a synthetic
two-exon GTF to both TSS BED and intron-chain TSV outputs.

Upstream: <https://github.com/mortazavilab/cerberus>
25 changes: 25 additions & 0 deletions images/cerberus/1.1/pyranges-compat.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
diff --git a/cerberus/cerberus.py b/cerberus/cerberus.py
index 35a87f3..ad34177 100644
--- a/cerberus/cerberus.py
+++ b/cerberus/cerberus.py
@@ -232,0 +233 @@ def number_gtf_ends(bed, gtf, mode):
+ gr_gtf['Strand'] = gr_gtf['Strand'].astype(str)
@@ -237,0 +239 @@ def number_gtf_ends(bed, gtf, mode):
+ bed['Strand'] = bed['Strand'].astype(str)
@@ -245 +247 @@ def number_gtf_ends(bed, gtf, mode):
- temp = pr.PyRanges(gr_gtf.features.tss().df[cols])
+ temp = gr_gtf.features.tss().df[cols]
@@ -247 +249,3 @@ def number_gtf_ends(bed, gtf, mode):
- temp = pr.PyRanges(gr_gtf.features.tes().df[cols])
+ temp = gr_gtf.features.tes().df[cols]
+ temp['Strand'] = temp['Strand'].astype(str)
+ temp = pr.PyRanges(temp)
@@ -1665,0 +1670 @@ def get_ends_from_gtf(gtf, mode, dist, slack):
+ gr_gtf['Strand'] = gr_gtf['Strand'].astype(str)
@@ -1678 +1683,3 @@ def get_ends_from_gtf(gtf, mode, dist, slack):
- bed = pr.PyRanges(bed.rename({'ThickStart': 'gene_id'}, axis=1))
+ bed = bed.rename({'ThickStart': 'gene_id'}, axis=1)
+ bed['Strand'] = bed['Strand'].astype(str)
+ bed = pr.PyRanges(bed)
@@ -1778,0 +1786 @@ def get_ics_from_gtf(gtf):
+ df['Strand'] = df['Strand'].astype(str)
26 changes: 26 additions & 0 deletions images/cerberus/1.1/verify.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -euo pipefail

python - <<'PY'
from importlib.metadata import version
assert version("cerberus") == "1.1"
import cerberus.cerberus
PY
python -m pip check
cerberus --help >/dev/null

work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
printf '%b\n' \
'chr1\tsmoke\tgene\t1\t200\t.\t+\t.\tgene_id "g1"; gene_name "G1";' \
'chr1\tsmoke\ttranscript\t1\t200\t.\t+\t.\tgene_id "g1"; transcript_id "t1"; gene_name "G1"; transcript_name "T1";' \
'chr1\tsmoke\texon\t1\t50\t.\t+\t.\tgene_id "g1"; transcript_id "t1"; exon_number "1";' \
'chr1\tsmoke\texon\t101\t200\t.\t+\t.\tgene_id "g1"; transcript_id "t1"; exon_number "2";' \
> "$work/input.gtf"

cerberus gtf_to_bed --gtf "$work/input.gtf" --mode tss -o "$work/tss.bed"
cerberus gtf_to_ics --gtf "$work/input.gtf" -o "$work/ics.tsv"
test -s "$work/tss.bed"
test -s "$work/ics.tsv"
grep -q 'g1_1' "$work/ics.tsv"
echo "Cerberus 1.1 smoke test passed"
15 changes: 15 additions & 0 deletions images/deseq2/1.52.0/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM bioconductor/bioconductor_docker:RELEASE_3_23@sha256:821dbf9ac119eac41f177531c7ca8fc7084c99c04eb218a1aa7f52cb63bad5d9

LABEL software="DESeq2" \
software.version="1.52.0" \
about.summary="Differential gene expression analysis based on the negative binomial distribution" \
about.url="https://bioconductor.org/packages/3.23/bioc/html/DESeq2.html" \
about.license="LGPL-3.0-or-later" \
about.tags="Genomics,RNA-seq,differential-expression"

RUN Rscript -e 'BiocManager::install("DESeq2", ask = FALSE, update = FALSE)'

COPY verify.R /tmp/verify.R
RUN Rscript /tmp/verify.R && rm /tmp/verify.R

CMD ["R"]
15 changes: 15 additions & 0 deletions images/deseq2/1.52.0/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# DESeq2 1.52.0

DESeq2 1.52.0 from Bioconductor 3.23 on R 4.6. The image build loads the
package and runs a small two-condition differential-expression analysis.

```bash
docker run --rm -it -v "$PWD:/work" -w /work ghcr.io/yeolab/deseq2:1.52.0
```

The default command starts R. Run an analysis script directly with:

```bash
docker run --rm -v "$PWD:/work" -w /work \
ghcr.io/yeolab/deseq2:1.52.0 Rscript analysis.R
```
20 changes: 20 additions & 0 deletions images/deseq2/1.52.0/verify.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
suppressPackageStartupMessages(library(DESeq2))

stopifnot(packageVersion("DESeq2") == "1.52.0")

set.seed(52)
counts <- matrix(rnbinom(600, mu = 100, size = 2), nrow = 100)
counts[1:10, 4:6] <- counts[1:10, 4:6] + 150L
storage.mode(counts) <- "integer"
col_data <- data.frame(
condition = factor(rep(c("control", "treated"), each = 3)),
row.names = paste0("sample", seq_len(ncol(counts)))
)
colnames(counts) <- row.names(col_data)

dds <- DESeqDataSetFromMatrix(counts, col_data, design = ~ condition)
dds <- DESeq(dds, quiet = TRUE)
res <- results(dds)

stopifnot(nrow(res) == nrow(counts), "log2FoldChange" %in% colnames(res))
message("DESeq2 ", packageVersion("DESeq2"), " smoke test passed")
15 changes: 15 additions & 0 deletions images/dexseq/1.58.0/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM bioconductor/bioconductor_docker:RELEASE_3_23@sha256:821dbf9ac119eac41f177531c7ca8fc7084c99c04eb218a1aa7f52cb63bad5d9

LABEL software="DEXSeq" \
software.version="1.58.0" \
about.summary="Inference of differential exon usage in RNA-seq" \
about.url="https://bioconductor.org/packages/3.23/bioc/html/DEXSeq.html" \
about.license="GPL-3.0-or-later" \
about.tags="Genomics,RNA-seq,differential-exon-usage"

RUN Rscript -e 'BiocManager::install("DEXSeq", ask = FALSE, update = FALSE)'

COPY verify.R /tmp/verify.R
RUN Rscript /tmp/verify.R && rm /tmp/verify.R

CMD ["R"]
Loading
Loading