Bring up to date

This commit is contained in:
Chris Wright 2026-04-17 12:29:57 +00:00
parent c7bebf6428
commit abbd3537ec
19 changed files with 310 additions and 270 deletions

View File

@ -21,11 +21,14 @@ macos-run:
- macos - macos
- x86 - x86
aws-run:
artifacts:
when: always
# run all our test in AWS, cos EKS runners don't work
docker-run: docker-run:
rules:
- when: never
aws-run:
stage: test
artifacts: artifacts:
when: always when: always
paths: paths:
@ -42,6 +45,7 @@ docker-run:
- ${CI_PROJECT_NAME}/**/*.fasta - ${CI_PROJECT_NAME}/**/*.fasta
- ${CI_PROJECT_NAME}/**/*.mmi - ${CI_PROJECT_NAME}/**/*.mmi
# Define a 1D job matrix to inject a variable named MATRIX_NAME into # Define a 1D job matrix to inject a variable named MATRIX_NAME into
# the CI environment, we can use the value of MATRIX_NAME to determine # the CI environment, we can use the value of MATRIX_NAME to determine
# which options to apply as part of the rules block below # which options to apply as part of the rules block below
@ -220,22 +224,22 @@ docker-run:
NF_IGNORE_PROCESSES: > NF_IGNORE_PROCESSES: >
preprocess_reads,validate_ref_annotation,merge_transcriptomes,assemble_transcripts, preprocess_reads,validate_ref_annotation,merge_transcriptomes,assemble_transcripts,
build_minimap_index,get_transcriptome,merge_gff_bundles,run_gffcompare,build_minimap_index,split_bam,decompress_transcriptome,preprocess_ref_transcriptome,faidx,gz_faidx build_minimap_index,get_transcriptome,merge_gff_bundles,run_gffcompare,build_minimap_index,split_bam,decompress_transcriptome,preprocess_ref_transcriptome,faidx,gz_faidx
- if: $MATRIX_NAME == "mismatch_seqid_error" #- if: $MATRIX_NAME == "mismatch_seqid_error"
variables: # variables:
NF_BEFORE_SCRIPT: "mkdir -p ${CI_PROJECT_NAME}/data/ && wget -O ${CI_PROJECT_NAME}/data/differential_expression_ncbi.tar.gz https://ont-exd-int-s3-euwst1-epi2me-labs.s3.amazonaws.com/wf-isoforms/differential_expression_ncbi.tar.gz && tar -xzvf ${CI_PROJECT_NAME}/data/differential_expression_ncbi.tar.gz -C ${CI_PROJECT_NAME}/data/ && wget -O ${CI_PROJECT_NAME}/data/demo.nextflow.config https://ont-exd-int-s3-euwst1-epi2me-labs.s3.amazonaws.com/wf-isoforms/demo.nextflow.config;" # NF_BEFORE_SCRIPT: "mkdir -p ${CI_PROJECT_NAME}/data/ && wget -O ${CI_PROJECT_NAME}/data/differential_expression_ncbi.tar.gz https://ont-exd-int-s3-euwst1-epi2me-labs.s3.amazonaws.com/wf-isoforms/differential_expression_ncbi.tar.gz && tar -xzvf ${CI_PROJECT_NAME}/data/differential_expression_ncbi.tar.gz -C ${CI_PROJECT_NAME}/data/ && wget -O ${CI_PROJECT_NAME}/data/demo.nextflow.config https://ont-exd-int-s3-euwst1-epi2me-labs.s3.amazonaws.com/wf-isoforms/demo.nextflow.config;"
NF_WORKFLOW_OPTS: # NF_WORKFLOW_OPTS:
"--fastq ${CI_PROJECT_NAME}/data/differential_expression_ncbi/differential_expression_fastq \ # "--fastq ${CI_PROJECT_NAME}/data/differential_expression_ncbi/differential_expression_fastq \
--de_analysis \ # --de_analysis \
--ref_genome ${CI_PROJECT_NAME}/data/differential_expression_ncbi/hg38_chr20.fa \ # --ref_genome ${CI_PROJECT_NAME}/data/differential_expression_ncbi/hg38_chr20.fa \
--ref_annotation ${CI_PROJECT_NAME}/data/differential_expression_ncbi/GRCh38.p14_NCBI_test.gtf.gz \ # --ref_annotation ${CI_PROJECT_NAME}/data/differential_expression_ncbi/GRCh38.p14_NCBI_test.gtf.gz \
--direct_rna \ # --direct_rna \
--sample_sheet test_data/sample_sheet.csv \ # --sample_sheet test_data/sample_sheet.csv \
-c ${CI_PROJECT_NAME}/data/demo.nextflow.config " # -c ${CI_PROJECT_NAME}/data/demo.nextflow.config "
NF_IGNORE_PROCESSES: > # NF_IGNORE_PROCESSES: >
preprocess_reads,faidx,gz_faidx,merge_transcriptomes,assemble_transcripts, # preprocess_reads,faidx,gz_faidx,merge_transcriptomes,assemble_transcripts,
build_minimap_index,get_transcriptome,merge_gff_bundles,run_gffcompare,build_minimap_index,split_bam,decompress_transcriptome,preprocess_ref_transcriptome # build_minimap_index,get_transcriptome,merge_gff_bundles,run_gffcompare,build_minimap_index,split_bam,decompress_transcriptome,preprocess_ref_transcriptome
ASSERT_NEXTFLOW_FAILURE: 1 # ASSERT_NEXTFLOW_FAILURE: 1
ASSERT_NEXTFLOW_FAILURE_REXP: Seqid mismatch found between the provided ref_annotation # ASSERT_NEXTFLOW_FAILURE_REXP: Seqid mismatch found between the provided ref_annotation
- if: $MATRIX_NAME == "mismatch_seqid_warn" - if: $MATRIX_NAME == "mismatch_seqid_warn"
variables: variables:
NF_BEFORE_SCRIPT: "mkdir -p ${CI_PROJECT_NAME}/data/ && wget -O ${CI_PROJECT_NAME}/data/differential_expression_ncbi.tar.gz https://ont-exd-int-s3-euwst1-epi2me-labs.s3.amazonaws.com/wf-isoforms/differential_expression_ncbi.tar.gz && tar -xzvf ${CI_PROJECT_NAME}/data/differential_expression_ncbi.tar.gz -C ${CI_PROJECT_NAME}/data/ && wget -O ${CI_PROJECT_NAME}/data/demo.nextflow.config https://ont-exd-int-s3-euwst1-epi2me-labs.s3.amazonaws.com/wf-isoforms/demo.nextflow.config;" NF_BEFORE_SCRIPT: "mkdir -p ${CI_PROJECT_NAME}/data/ && wget -O ${CI_PROJECT_NAME}/data/differential_expression_ncbi.tar.gz https://ont-exd-int-s3-euwst1-epi2me-labs.s3.amazonaws.com/wf-isoforms/differential_expression_ncbi.tar.gz && tar -xzvf ${CI_PROJECT_NAME}/data/differential_expression_ncbi.tar.gz -C ${CI_PROJECT_NAME}/data/ && wget -O ${CI_PROJECT_NAME}/data/demo.nextflow.config https://ont-exd-int-s3-euwst1-epi2me-labs.s3.amazonaws.com/wf-isoforms/demo.nextflow.config;"
@ -253,4 +257,3 @@ docker-run:
AFTER_NEXTFLOW_CMD: "grep 'are only present in the reference genome' '.nextflow.log'" AFTER_NEXTFLOW_CMD: "grep 'are only present in the reference genome' '.nextflow.log'"

View File

@ -3,12 +3,12 @@ repos:
hooks: hooks:
- id: docs_readme - id: docs_readme
name: docs_readme name: docs_readme
entry: parse_docs -p docs -e .md -s 01_brief_description 02_introduction 03_compute_requirements 04_install_and_run 05_related_protocols 06_input_example 07_pipeline_overview 08_input_parameters 09_outputs 10_troubleshooting 11_FAQ 12_other -ot README.md -od output_definition.json -ns nextflow_schema.json entry: parse_docs -p docs -e .md -s 01_brief_description 02_introduction 03_compute_requirements 04_install_and_run 05_input_example 06_pipeline_overview 07_input_parameters 08_outputs 09_related_protocols 10_troubleshooting 11_FAQ 12_other -ot README.md -od output_definition.json -ns nextflow_schema.json
language: python language: python
always_run: true always_run: true
pass_filenames: false pass_filenames: false
additional_dependencies: additional_dependencies:
- epi2melabs==0.0.59 - epi2melabs==0.0.62
- repo: https://github.com/pycqa/flake8 - repo: https://github.com/pycqa/flake8
rev: 5.0.4 rev: 5.0.4
hooks: hooks:

View File

@ -62,8 +62,6 @@ This patch release of wf-transcriptomes updates internal workflow naming, and do
- Bug where incorrect gene_id assigned in the DE tables. - Bug where incorrect gene_id assigned in the DE tables.
## [v1.5.0] ## [v1.5.0]
### Updated
- Workflow report updated to use `ezcharts`.
### Fixed ### Fixed
- Exons per isoforms histogram reporting incorrect numbers. - Exons per isoforms histogram reporting incorrect numbers.
- Output the `results_dexseq.tsv` file when `--de_analysis` enabled. - Output the `results_dexseq.tsv` file when `--de_analysis` enabled.
@ -71,8 +69,8 @@ This patch release of wf-transcriptomes updates internal workflow naming, and do
- per-class gffcompare tracking files as there exists a combine tracking file. - per-class gffcompare tracking files as there exists a combine tracking file.
## [v1.4.0] ## [v1.4.0]
## Added ### Added
- `--igv` parameter (default: false) for outputting IGV config allowing visualisation of read alignments in the EPI2ME App. - Added `--igv` parameter (default: false) for outputting IGV config allowing visualisation of read alignments in the EPI2ME App.
- If required for IGV, reference indexes are output in to a `igv_reference` directory - If required for IGV, reference indexes are output in to a `igv_reference` directory
### Changed ### Changed
- BAMS are output in to a BAMS directory. - BAMS are output in to a BAMS directory.
@ -83,8 +81,8 @@ This patch release of wf-transcriptomes updates internal workflow naming, and do
- Fusion detection subworkflow, as the functionality is not robust enough for general use at this time. - Fusion detection subworkflow, as the functionality is not robust enough for general use at this time.
### Changed ### Changed
- Updated pychopper to 2.7.10 - Updated pychopper to 2.7.10
## Added ### Added
- new `cdna_kit` options: PCS114 and PCB111/114 - New `--cdna_kit` options: PCS114 and PCB111/114
## [v1.2.1] ## [v1.2.1]
### Changed ### Changed

View File

@ -40,7 +40,7 @@ ARM processor support: False
These are instructions to install and run the workflow on command line. These are instructions to install and run the workflow on command line.
You can also access the workflow via the You can also access the workflow via the
[EPI2ME Desktop application](https://labs.epi2me.io/downloads/). [EPI2ME Desktop application](https://epi2me.nanoporetech.com/downloads/).
The workflow uses [Nextflow](https://www.nextflow.io/) to manage The workflow uses [Nextflow](https://www.nextflow.io/) to manage
compute and software resources, compute and software resources,
@ -60,7 +60,8 @@ parameter as exemplified below.
It is not required to clone or download the git repository It is not required to clone or download the git repository
in order to run the workflow. in order to run the workflow.
More information on running EPI2ME workflows can More information on running EPI2ME workflows can
be found on our [website](https://labs.epi2me.io/wfindex). be found in the
[documentation](https://epi2me.nanoporetech.com/epi2me-docs/wfquickstart/).
The following command can be used to obtain the workflow. The following command can be used to obtain the workflow.
This will pull the repository in to the assets folder of This will pull the repository in to the assets folder of
@ -95,17 +96,6 @@ nextflow run epi2me-labs/wf-transcriptomes \
-profile standard -profile standard
``` ```
For further information about running a workflow on
the command line see https://labs.epi2me.io/wfquickstart/
## Related protocols
This workflow is designed to take input sequences that have been produced from [Oxford Nanopore Technologies](https://nanoporetech.com/) devices.
Find related protocols in the [Nanopore community](https://community.nanoporetech.com/docs/).
@ -320,6 +310,16 @@ Output files may be aggregated including information for all samples or provided
## Related protocols
This workflow is designed to take input sequences that have been produced from [Oxford Nanopore Technologies](https://nanoporetech.com/) devices.
Find related RNA and cDNA sequencing protocols in the
[Nanopore community](https://community.nanoporetech.com/docs/).
## Troubleshooting ## Troubleshooting
+ If the workflow fails please run it with the demo data set to ensure the workflow itself is working. This will help us determine if the issue is related to the environment, input parameters or a bug. + If the workflow fails please run it with the demo data set to ensure the workflow itself is working. This will help us determine if the issue is related to the environment, input parameters or a bug.

View File

@ -1,5 +1,6 @@
"""Common model classes used across all workflows.""" """Common model classes used across all workflows."""
from dataclasses import asdict, dataclass, field from dataclasses import asdict, dataclass, field
from decimal import Decimal
from enum import Enum from enum import Enum
import json import json
from pathlib import Path from pathlib import Path
@ -34,7 +35,8 @@ class WorkflowBaseModel:
field_name: str, field_name: str,
*, *,
decimal_places: int = None, decimal_places: int = None,
default_value: str = "N/A") -> Optional[str]: default_value: str = "N/A",
show_unit: bool = True) -> Optional[str]:
"""Get the value of a value and make it reportable.""" """Get the value of a value and make it reportable."""
# Get the field info using the field name # Get the field info using the field name
field_info = self.__dataclass_fields__.get(field_name) field_info = self.__dataclass_fields__.get(field_name)
@ -48,11 +50,14 @@ class WorkflowBaseModel:
if value is None: if value is None:
return default_value return default_value
if isinstance(value, (int, float)): if isinstance(value, (int, float, Decimal)):
if decimal_places: # Apply scientific notation for Decimal or small/large int/float
if isinstance(value, Decimal) or value < 0.0001 or value > 99999999:
precision = decimal_places if decimal_places is not None else 2
value = f"{value:.{precision}E}"
# Otherwise, apply rounding
elif decimal_places is not None:
value = round(value, decimal_places) value = round(value, decimal_places)
if value < 0.0001 or value > 99999999:
value = f"{value:.2E}"
else: else:
if decimal_places: if decimal_places:
raise TypeError( raise TypeError(
@ -60,7 +65,7 @@ class WorkflowBaseModel:
unit = field_info.metadata.get('unit') unit = field_info.metadata.get('unit')
if unit: if unit and show_unit:
return f"{value} {unit}" return f"{value} {unit}"
return str(value) return str(value)
@ -113,6 +118,10 @@ class CheckResult:
metadata={ metadata={
"title": "Check pass", "title": "Check pass",
"description": "If true the check has passed"}) "description": "If true the check has passed"})
check_messages: list | None = field(
default=None, metadata={
"title": "Check messages",
"description": "A list of messages about the check performed"})
check_threshold: str | None = field( check_threshold: str | None = field(
default=None, metadata={ default=None, metadata={
"title": "Check threshold", "title": "Check threshold",
@ -245,11 +254,11 @@ class RunStats:
total_reads: int | None = field( total_reads: int | None = field(
default=None, metadata={ default=None, metadata={
"title": "Total reads", "title": "Total reads",
"description": "Total number of reads on run"}) "description": """Total number of reads after basecalling. This value is
total_ambiguous_reads: int | None = field( calculated from the full BAM file generated by the workflow, includes both
default=None, metadata={ aligned and unaligned reads, and excludes non-primary alignments to prevent
"title": "Total ambiguous reads", double counting. It may differ from the estimate shown in the technical
"description": "Number of reads of unknown provenance"}) run report."""})
total_unaligned_reads: int | None = field( total_unaligned_reads: int | None = field(
default=None, metadata={ default=None, metadata={
"title": "Total unaligned reads", "title": "Total unaligned reads",

View File

@ -64,38 +64,49 @@ def main(args):
) )
sys.exit() sys.exit()
csv_reader = csv.DictReader(f) csv_reader = csv.DictReader(f)
n_row = 0 columns = csv_reader.fieldnames
for row in csv_reader: alias_field = "alias"
n_row += 1 required_fields = ["barcode"]
if n_row == 1: prohibited_fields = []
n_cols = len(row)
else: if args.no_barcode:
# check we got the same number of fields alias_field = "sample_name"
if len(row) != n_cols: required_fields = []
prohibited_fields = ["alias", "barcode"]
required_fields.append(alias_field)
for field in prohibited_fields:
if field in columns:
sys.stdout.write(
f"'{field}' column must not be present with --no_barcode"
)
sys.exit()
for field in required_fields:
if field not in columns:
sys.stdout.write(f"'{field}' column missing")
sys.exit()
# Skip header row for n_row
for n_row, row in enumerate(csv_reader, start=1):
if len(row) != len(columns):
sys.stdout.write( sys.stdout.write(
f"Unexpected number of cells in row number {n_row}" f"Unexpected number of cells in row number {n_row}"
) )
sys.exit() sys.exit()
try: if not args.no_barcode:
barcodes.append(row["barcode"]) barcodes.append(row.get("barcode"))
except KeyError: aliases.append(row.get(alias_field))
sys.stdout.write("'barcode' column missing")
sys.exit() # Optional fields check for not None, empty strings are falsey
try: sample_type = row.get("type")
aliases.append(row["alias"]) if sample_type is not None:
except KeyError: sample_types.append(sample_type)
sys.stdout.write("'alias' column missing") analysis_group = row.get("analysis_group")
sys.exit() if analysis_group is not None:
try: analysis_groups.append(analysis_group)
sample_types.append(row["type"])
except KeyError:
pass
try:
analysis_groups.append(row["analysis_group"])
except KeyError:
pass
except Exception as e: except Exception as e:
sys.stdout.write(f"Parsing error: {e}") sys.stdout.write(f"Parsing error: {e}")
sys.exit() sys.exit()
@ -110,10 +121,13 @@ def main(args):
# for now we have decided they may not start with "barcode" # for now we have decided they may not start with "barcode"
for alias in aliases: for alias in aliases:
if alias.startswith("barcode"): if alias.startswith("barcode"):
sys.stdout.write("values in 'alias' column must not begin with 'barcode'") sys.stdout.write(
f"values in '{alias_field}' column must "
"not begin with 'barcode'")
sys.exit() sys.exit()
# check barcodes are all the same length # check barcodes are all the same length
if barcodes:
first_length = len(barcodes[0]) first_length = len(barcodes[0])
for barcode in barcodes[1:]: for barcode in barcodes[1:]:
if len(barcode) != first_length: if len(barcode) != first_length:
@ -125,7 +139,7 @@ def main(args):
sys.stdout.write("values in 'barcode' column not unique") sys.stdout.write("values in 'barcode' column not unique")
sys.exit() sys.exit()
if len(aliases) > len(set(aliases)): if len(aliases) > len(set(aliases)):
sys.stdout.write("values in 'alias' column not unique") sys.stdout.write(f"values in '{alias_field}' column not unique")
sys.exit() sys.exit()
if sample_types: if sample_types:
@ -170,4 +184,10 @@ def argparser():
"appear at least once in the sample sheet", "appear at least once in the sample sheet",
nargs="*" nargs="*"
) )
parser.add_argument(
"--no_barcode",
action="store_true",
help="Allow sample sheets without a barcode column "
"and match rows by sample_name only",
)
return parser return parser

View File

@ -18,6 +18,7 @@ DATA_TYPES_LISTS = {
"vcf_idx": ["vcf.gz.tbi", "vcf.gz.csi"], "vcf_idx": ["vcf.gz.tbi", "vcf.gz.csi"],
"bcf": ["bcf"], "bcf": ["bcf"],
"bcf_idx": ["bcf.csi"], "bcf_idx": ["bcf.csi"],
"bigwig": ["bw", "bigwig"],
"gtf": ["gtf", "gtf.gz"], "gtf": ["gtf", "gtf.gz"],
"gtf_idx": ["gtf.gz.tbi"], "gtf_idx": ["gtf.gz.tbi"],
"gff": ["gff", "gff.gz", "gff3", "gff3.gz"], "gff": ["gff", "gff.gz", "gff3", "gff3.gz"],
@ -43,6 +44,7 @@ INDEX_PAIRS = {
"cram": ("crai",), "cram": ("crai",),
"vcf": ("tbi", "csi"), "vcf": ("tbi", "csi"),
"bcf": ("csi",), "bcf": ("csi",),
"bigwig": (),
"bed": ("tbi",), "bed": ("tbi",),
"bedmethyl": ("tbi",), "bedmethyl": ("tbi",),
"gff": ("tbi",), "gff": ("tbi",),
@ -68,6 +70,7 @@ class TrackBuilder:
"cram": "alignment", "cram": "alignment",
"bcf": "variant", "bcf": "variant",
"vcf": "variant", "vcf": "variant",
"bigwig": "wig",
"bedmethyl": "annotation", "bedmethyl": "annotation",
"bed": "annotation", "bed": "annotation",
"gtf": "annotation", "gtf": "annotation",
@ -82,6 +85,7 @@ class TrackBuilder:
"cram": {}, "cram": {},
"bcf": {}, "bcf": {},
"vcf": {}, "vcf": {},
"bigwig": {},
"bed": {}, "bed": {},
"bedmethyl": {}, "bedmethyl": {},
"gtf": {}, "gtf": {},

View File

@ -1,7 +1,7 @@
These are instructions to install and run the workflow on command line. These are instructions to install and run the workflow on command line.
You can also access the workflow via the You can also access the workflow via the
[EPI2ME Desktop application](https://labs.epi2me.io/downloads/). [EPI2ME Desktop application](https://epi2me.nanoporetech.com/downloads/).
The workflow uses [Nextflow](https://www.nextflow.io/) to manage The workflow uses [Nextflow](https://www.nextflow.io/) to manage
compute and software resources, compute and software resources,
@ -21,7 +21,8 @@ parameter as exemplified below.
It is not required to clone or download the git repository It is not required to clone or download the git repository
in order to run the workflow. in order to run the workflow.
More information on running EPI2ME workflows can More information on running EPI2ME workflows can
be found on our [website](https://labs.epi2me.io/wfindex). be found in the
[documentation](https://epi2me.nanoporetech.com/epi2me-docs/wfquickstart/).
The following command can be used to obtain the workflow. The following command can be used to obtain the workflow.
This will pull the repository in to the assets folder of This will pull the repository in to the assets folder of
@ -55,6 +56,3 @@ nextflow run epi2me-labs/wf-transcriptomes \
--sample_sheet 'wf-transcriptomes-demo/sample_sheet.csv' \ --sample_sheet 'wf-transcriptomes-demo/sample_sheet.csv' \
-profile standard -profile standard
``` ```
For further information about running a workflow on
the command line see https://labs.epi2me.io/wfquickstart/

View File

@ -0,0 +1,65 @@
### Input Options
| Nextflow parameter name | Type | Description | Help | Default |
|--------------------------|------|-------------|------|---------|
| fastq | string | FASTQ files to use in the analysis. | This accepts one of three cases: (i) the path to a single FASTQ file; (ii) the path to a top-level directory containing FASTQ files; (iii) the path to a directory containing one level of sub-directories which in turn contain FASTQ files. In the first and second case, a sample name can be supplied with `--sample`. In the last case, the data is assumed to be multiplexed with the names of the sub-directories as barcodes. In this case, a sample sheet can be provided with `--sample_sheet`. | |
| bam | string | BAM or unaligned BAM (uBAM) files to use in the analysis. | This accepts one of three cases: (i) the path to a single BAM file; (ii) the path to a top-level directory containing BAM files; (iii) the path to a directory containing one level of sub-directories which in turn contain BAM files. In the first and second case, a sample name can be supplied with `--sample`. In the last case, the data is assumed to be multiplexed with the names of the sub-directories as barcodes. In this case, a sample sheet can be provided with `--sample_sheet`. | |
| transcriptome_source | string | Select how the transcriptome used for analysis should be prepared. | For differential expression analysis, use of an existing transcriptome may be preferred and so 'precomputed' should be selected. In this case the 'ref_transcriptome' parameter should be specified. To create a reference transcriptome using an existing reference genome, select 'reference guided' and specify the 'ref_genome' parameter. | reference-guided |
| ref_genome | string | Path to reference genome sequence [.fa/.fq/.fa.gz/fq.gz]. Required for reference-based workflow. | A reference genome is required for reference-based assembly of a transcriptome. | |
| ref_transcriptome | string | Transcriptome reference file. Required for precomputed transcriptome calculation and for differential expression analysis. | A reference transcriptome related to the sample under study. Must be supplied when the 'Transcriptome source' parameter has been set to 'precomputed' or to perform differential expression. | |
| ref_annotation | string | A reference annotation in GFF2 or GFF3 format (extensions .gtf(.gz), .gff(.gz), .gff3(.gz)). Only annotation files from [Encode](https://www.encodeproject.org), [Ensembl](https://www.ensembl.org/index.html) and [NCBI](https://www.ncbi.nlm.nih.gov/) are supported. | This will be used for guiding the transcriptome assembly and to label transcripts with their corresponding gene identifiers. Note: If in de_analysis mode transcript strands must be only + or -. | |
| direct_rna | boolean | Set to true for direct RNA sequencing. | Omits the pychopper step. | False |
| analyse_unclassified | boolean | Analyse unclassified reads from input directory. By default the workflow will not process reads in the unclassified directory. | If selected and if the input is a multiplex directory the workflow will also process the unclassified directory. | False |
### Output Options
| Nextflow parameter name | Type | Description | Help | Default |
|--------------------------|------|-------------|------|---------|
| out_dir | string | Directory for output of all user-facing files. | | output |
| igv | boolean | Visualize outputs in the EPI2ME IGV visualizer. | Enabling this option will visualize the output alignment files in the EPI2ME Desktop App IGV visualizer. | False |
### Sample Options
| Nextflow parameter name | Type | Description | Help | Default |
|--------------------------|------|-------------|------|---------|
| sample_sheet | string | A CSV file used to map barcodes to sample aliases. The sample sheet can be provided when the input data is a directory containing sub-directories with FASTQ files. If you are running the differential expression workflow, there must be an additional column `condition` with two labels, one of which must be `control` (e.g. `control` and `treated`). Control will indicate which samples will be used as the reference. There should be at least 3 repeats for each condition. | The sample sheet is a CSV file with, minimally, columns named `barcode` and `alias`. Extra columns are allowed. | |
| sample | string | A single sample name for non-multiplexed data. Permissible if passing a single .fastq(.gz) file or directory of .fastq(.gz) files. | | |
### Options for reference-based workflow
| Nextflow parameter name | Type | Description | Help | Default |
|--------------------------|------|-------------|------|---------|
| plot_gffcmp_stats | boolean | Create a PDF of plots from showing gffcompare results | If set to true, a PDF file containing detailed gffcompare reults will be output | True |
| gffcompare_opts | string | Extra command-line options to give to gffcompare -r | For a list of possible options see [gffcompare](https://ccb.jhu.edu/software/stringtie/gffcompare.shtml). | -R |
| minimap2_index_opts | string | Extra command-line options for minimap2 indexing. | See [minimap2 index options](https://lh3.github.io/minimap2/minimap2.html#4) for more information. These will only be relevant in the reference based transcriptome assembly. | -k 14 |
| minimap2_opts | string | Additional command-line options for minimap2 alignment. | See [minimap2 options](https://lh3.github.io/minimap2/minimap2.html#5) for further information. These will only be relevant in the reference based transcriptome assembly. | -uf |
| minimum_mapping_quality | integer | filter aligned reads by MAPQ quality. | Reads that do not meet this mapping quality after minimap2 alignment, will be filtered out. | 40 |
| stringtie_opts | string | Extra command-line options for stringtie transcript assembly. | For additional String tie options see [here](https://github.com/gpertea/stringtie#stringtie-options). | --conservative |
### Differential Expression Options
| Nextflow parameter name | Type | Description | Help | Default |
|--------------------------|------|-------------|------|---------|
| de_analysis | boolean | Run DE anaylsis | Running this requires you to provide at least two replicates for a control and treated sample as well as a sample sheet param. | False |
| min_gene_expr | integer | The minimum number of total mapped sequence reads required for a gene to be considered in differential transcript usage analysis. | Filtering at the gene level ensures that the observed transcript ratios are calculated with a minimum number of counts per gene. | 10 |
| min_feature_expr | integer | The minimum number of reads assigned to a transcript for it to be considered in differential transcript usage analysis. | Filter out transcripts that do not have this minimum number of transcript expression, reducing noise. | 3 |
| min_samps_gene_expr | integer | Set the minimum number of samples in which a gene is expressed to be included in the differential transcript usage analysis. | A gene must be expressed in at least this number of samples for the gene be included in the differential transcript usage analysis. Filtering at the gene level improves the reliability of the observed transcript ratios. | 3 |
| min_samps_feature_expr | integer | Set the minimum number of samples in which a transcript is expressed to be included in the differential transcript usage analysis. | A transcript must expressed in at least this minimum number of samples to be included in the analysis. Should be equal to the number of replicates per sample you have. | 1 |
### Advanced Options
| Nextflow parameter name | Type | Description | Help | Default |
|--------------------------|------|-------------|------|---------|
| threads | integer | Number of CPU threads. | Only provided to processes including alignment and and assembly that benefit from multiple threads. | 4 |
| cdna_kit | string | If cDNA reads are used, select the kit used. | This will be used by pychopper to preprocess the reads for downstream analysis. | SQK-PCS109 |
| pychopper_backend | string | Pychopper can use one of two available backends for identifying primers in the raw reads | 'edlib' is set by default due to its high performance. However, it may be less sensitive than 'phmm'. | edlib |
| pychopper_opts | string | Extra pychopper opts | See available options (here)[https://github.com/epi2me-labs/pychopper#usage] | |
| bundle_min_reads | integer | Minimum size of bam bundle for parallel processing. | | 50000 |
| isoform_table_nrows | integer | Maximum rows to dispay in the isoform report table | | 5000 |

33
docs/08_outputs.md Normal file
View File

@ -0,0 +1,33 @@
Output files may be aggregated including information for all samples or provided per sample. Per-sample files will be prefixed with respective aliases and represented below as {{ alias }}.
| Title | File path | Description | Per sample or aggregated |
|-------|-----------|-------------|--------------------------|
| workflow report | wf-transcriptomes-report.html | a HTML report document detailing the primary findings of the workflow | aggregated |
| Per file read stats | fastq_ingress_results/{{ alias }}//reads/fastcat_stats/per-file-stats.tsv | A TSV with per file read stats, including all samples. | aggregated |
| Read stats | fastq_ingress_results/{{ alias }}//reads/fastcat_stats/per-read-stats.tsv | A TSV with per read stats, including all samples. | aggregated |
| Run ID's | fastq_ingress_results/{{ alias }}//reads/fastcat_stats/run_ids | List of run IDs present in reads. | aggregated |
| Meta map json | fastq_ingress_results/{{ alias }}//reads/metamap.json | Metadata used in workflow presented in a JSON. | aggregated |
| Concatenated sequence data | fastq_ingress_results/{{ alias }}//reads/{{ alias }}.fastq.gz | Per sample reads concatenated in to one FASTQ file. | per-sample |
| Assembled transcriptome | {{ alias }}_transcriptome.fas | Per sample assembled transcriptome. Not output if a reference annotation was supplied | per-sample |
| Annotated assembled transcriptome | {{ alias }}_merged_transcriptome.fas | Per sample annotated assembled transcriptome. Only output if a reference annotation was supplied | per-sample |
| Alignment summary statistics | {{ alias }}_read_aln_stats.tsv | Per sample alignment summary statistics. | per-sample |
| GFF compare results. | {{ alias }}_gffcompare | All GFF compare output files. | per-sample |
| Differential gene expression results | de_analysis/results_dge.tsv | This is a gene-level result file that describes genes and their probability of showing differential expression between experimental conditions. | aggregated |
| Differential gene expression report | de_analysis/results_dge.pdf | Summary report of differential gene expression analysis as a PDF. | aggregated |
| Differential transcript usage gene TSV | de_analysis/results_dtu_gene.tsv | This is a gene-level result file from DEXSeq that lists annotated genes and their probabilities of differential expression. | aggregated |
| Differential transcript usage report | de_analysis/results_dtu.pdf | Summary report of differential transcript usage results as a PDF. | aggregated |
| Differential transcript usage TSV | de_analysis/results_dtu_transcript.tsv | This is a transcript-level result file from DEXSeq that lists annotated genes and their probabilities of differential expression. | aggregated |
| Differential transcript usage stageR TSV | de_analysis/results_dtu_stageR.tsv | This is the output from StageR and it shows both gene and transcript probabilities of differential expression | aggregated |
| Differential transcript usage DEXSeq TSV | de_analysis/results_dexseq.tsv | The complete output from the DEXSeq-analysis, shows both gene and transcript probabilities of differential expression. | aggregated |
| Gene counts | de_analysis/all_gene_counts.tsv | Raw gene counts created by the Salmon tool, before filtering. | aggregated |
| Gene counts per million | de_analysis/cpm_gene_counts.tsv | This file shows counts per million (CPM) of the raw gene counts to facilitate comparisons across samples. | aggregated |
| Transcript counts | de_analysis/unfiltered_transcript_counts_with_genes.tsv | Raw transcript counts created by the Salmon tool, before filtering. Includes reference to the associated gene ID. | aggregated |
| Transcript per million counts | de_analysis/unfiltered_tpm_transcript_counts.tsv | This file shows transcripts per million (TPM) of the raw counts to facilitate comparisons across samples. | aggregated |
| Transcript counts filtered | de_analysis/filtered_transcript_counts_with_genes.tsv | Filtered transcript counts, used for differential transcript usage analysis. Includes a reference to the associated gene ID. | aggregated |
| Transcript info table | {{ alias }}_transcripts_table.tsv | This file details each isoform that was reconstructed from the input reads. It contains a subset of columns from the .tmap output from [gffcompare](https://ccb.jhu.edu/software/stringtie/gffcompare.shtml) | per-sample |
| Final non redundant transcriptome | de_analysis/final_non_redundant_transcriptome.fasta | Transcripts that were used for differential expression analysis including novel transcripts with the identifiers used for DE analysis. Only applicable when the ref_transcriptome parameter is not provided. | aggregated |
| Index of reference FASTA file | igv_reference/{{ ref_genome_file }}.fai | Reference genome index of the FASTA file required for IGV config. | aggregated |
| GZI index of the reference FASTA file | igv_reference/{{ ref_genome_file }}.gzi | GZI Index of the reference FASTA file. | aggregated |
| JSON configuration file for IGV browser | igv.json | JSON configuration file to be loaded in IGV for visualising alignments against the reference. | aggregated |
| BAM file (minimap2) | BAMS/{{ alias }}.reads_aln_sorted.bam | BAM file generated from mapping input reads to the reference. | per-sample |
| BAM index file (minimap2) | BAMS/{{ alias }}.reads_aln_sort.bam.bai | Index file generated from mapping input reads to the reference. | per-sample |

View File

@ -0,0 +1,4 @@
This workflow is designed to take input sequences that have been produced from [Oxford Nanopore Technologies](https://nanoporetech.com/) devices.
Find related RNA and cDNA sequencing protocols in the
[Nanopore community](https://community.nanoporetech.com/docs/).

View File

@ -23,4 +23,28 @@ class CWUtil {
nf_params.allowNames(s) nf_params.allowNames(s)
nf_params.put0(key, value) nf_params.put0(key, value)
} }
/* Safely get the workflow entrypoint from the Nextflow params map
*
* Our workflows support alternative "entrypoints". For example, a workflow
* may expose an entrypoint that can be used to validate parameters and
* dry run the workflow, or to automatically set different defaults or
* behaviours. This is controlled by setting `params.wf.entrypoint`.
*
* This util safely checks and extracts this parameter if set and avoids
* "Access to undefined parameter" errors otherwise raised during strict
* Nextflow execution.
*
* Note that entrypoint is expected to be null for the "main" entrypoint.
*/
public static def getEntrypoint(nf_params) {
def entrypoint = null
if( nf_params.containsKey('wf')
&& nf_params.wf instanceof Map
&& nf_params.wf.containsKey('entrypoint') )
{
entrypoint = nf_params.wf.entrypoint
}
return entrypoint
}
} }

View File

@ -40,8 +40,14 @@ class NfcoreSchema {
// //
// Resolve Schema path relative to main workflow directory // Resolve Schema path relative to main workflow directory
// //
public static String getSchemaPath(workflow, schema_filename='nextflow_schema.json') { public static String getSchemaPath(workflow, params, schema_filename='nextflow_schema.json') {
return "${workflow.projectDir}/${schema_filename}" String entrypoint_prefix = "" // default "main" prefix
// if our params.wf namespace defines an entrypoint, override the schema location
def wf_entrypoint = CWUtil.getEntrypoint(params)
if (wf_entrypoint) {
entrypoint_prefix = "entrypoints/${wf_entrypoint}/"
}
return "${workflow.projectDir}/${entrypoint_prefix}${schema_filename}"
} }
// //
@ -53,7 +59,7 @@ class NfcoreSchema {
def has_error = false def has_error = false
//=====================================================================// //=====================================================================//
// Check for nextflow core params and unexpected params // Check for nextflow core params and unexpected params
def json = new File(getSchemaPath(workflow, schema_filename=schema_filename)).text def json = new File(getSchemaPath(workflow, params, schema_filename=schema_filename)).text
def Map schemaParams = (Map) new JsonSlurper().parseText(json).get('definitions') def Map schemaParams = (Map) new JsonSlurper().parseText(json).get('definitions')
def nf_params = [ def nf_params = [
// Options for base `nextflow` command // Options for base `nextflow` command
@ -160,7 +166,7 @@ class NfcoreSchema {
//=====================================================================// //=====================================================================//
// Validate parameters against the schema // Validate parameters against the schema
InputStream input_stream = new File(getSchemaPath(workflow, schema_filename=schema_filename)).newInputStream() InputStream input_stream = new File(getSchemaPath(workflow, params, schema_filename=schema_filename)).newInputStream()
JSONObject raw_schema = new JSONObject(new JSONTokener(input_stream)) JSONObject raw_schema = new JSONObject(new JSONTokener(input_stream))
// Remove anything that's in params.schema_ignore_params // Remove anything that's in params.schema_ignore_params
@ -215,7 +221,7 @@ class NfcoreSchema {
String output = '' String output = ''
output += 'Typical pipeline command:\n\n' output += 'Typical pipeline command:\n\n'
output += " ${colors.cyan}${command}${colors.reset}\n\n" output += " ${colors.cyan}${command}${colors.reset}\n\n"
Map params_map = paramsLoad(getSchemaPath(workflow, schema_filename=schema_filename)) Map params_map = paramsLoad(getSchemaPath(workflow, params, schema_filename=schema_filename))
Integer max_chars = paramsMaxChars(params_map) + 1 Integer max_chars = paramsMaxChars(params_map) + 1
Integer type_pad = 10 Integer type_pad = 10
String param_prefix = " --" String param_prefix = " --"
@ -296,7 +302,7 @@ class NfcoreSchema {
// Get pipeline parameters defined in JSON Schema // Get pipeline parameters defined in JSON Schema
def Map params_summary = [:] def Map params_summary = [:]
def blacklist = ['hostnames'] def blacklist = ['hostnames']
def params_map = paramsLoad(getSchemaPath(workflow, schema_filename=schema_filename)) def params_map = paramsLoad(getSchemaPath(workflow, params, schema_filename=schema_filename))
for (group in params_map.keySet()) { for (group in params_map.keySet()) {
def sub_params = new LinkedHashMap() def sub_params = new LinkedHashMap()
def group_params = params_map.get(group) // This gets the parameters of that particular group def group_params = params_map.get(group) // This gets the parameters of that particular group

View File

@ -33,7 +33,7 @@ process configure_igv {
script: script:
// the locus argument just makes sure that the initial view in IGV shows something // the locus argument just makes sure that the initial view in IGV shows something
// interesting // interesting
String locus_arg = locus_str ? "--locus $locus_str" : "" String locus_arg = locus_str ? "--locus '$locus_str'" : ""
// extra options for alignment tracks // extra options for alignment tracks
def aln_opts_json_str = \ def aln_opts_json_str = \
aln_extra_opts ? new JsonBuilder(aln_extra_opts).toPrettyString() : "" aln_extra_opts ? new JsonBuilder(aln_extra_opts).toPrettyString() : ""

View File

@ -43,7 +43,8 @@ def is_excluded(Path p, Map margs) {
* Take a channel of the shape `[meta, reads, path-to-stats-dir | null]` (or * Take a channel of the shape `[meta, reads, path-to-stats-dir | null]` (or
* `[meta, [reads, index], path-to-stats-dir | null]` in the case of XAM) and extract the * `[meta, [reads, index], path-to-stats-dir | null]` in the case of XAM) and extract the
* run IDs and basecall model, from the `run_ids` and `basecaller` files in the stats * run IDs and basecall model, from the `run_ids` and `basecaller` files in the stats
* directory, into the metamap. If the path to the stats dir is `null`, add an empty list. * directory, into the metamap. If the path to the stats dir is `null` due to samples with no reads,
* add an empty list.
* *
* @param ch: input channel of shape `[meta, reads, path-to-stats-dir | null]` * @param ch: input channel of shape `[meta, reads, path-to-stats-dir | null]`
* @param allow_multiple_basecall_models: Boolean. If true, emit any sample to have been basecalled * @param allow_multiple_basecall_models: Boolean. If true, emit any sample to have been basecalled
@ -58,6 +59,7 @@ def add_run_IDs_and_basecall_models_to_meta(ch, boolean allow_multiple_basecall_
// extract run_ids from fastcat stats / bamstats results and add to metadata as well // extract run_ids from fastcat stats / bamstats results and add to metadata as well
// as `ingressed_run_ids` // as `ingressed_run_ids`
ch = ch | map { meta, reads, stats -> ch = ch | map { meta, reads, stats ->
// stats will be null only if the sample had no reads (empty BAM/FASTQ)
if (stats) { if (stats) {
def run_ids = stats.resolve("run_ids").splitText().collect { it.strip() } def run_ids = stats.resolve("run_ids").splitText().collect { it.strip() }
ingressed_run_ids += run_ids ingressed_run_ids += run_ids
@ -112,6 +114,7 @@ def add_number_of_reads_to_meta(ch, String input_type_format) {
// extract reads from fastcat stats / bamstats results and add to metadata // extract reads from fastcat stats / bamstats results and add to metadata
ch = ch | map { meta, reads, stats -> ch = ch | map { meta, reads, stats ->
// Check that stats directory is present. // Check that stats directory is present.
// stats will be null only if the sample had no reads (empty BAM/FASTQ)
if (stats) { if (stats) {
if (input_type_format == "fastq") { if (input_type_format == "fastq") {
// Stats from fastcat // Stats from fastcat
@ -148,7 +151,7 @@ def add_number_of_reads_to_meta(ch, String input_type_format) {
* with elements of `[metamap, seqs.fastq.gz | null, path-to-fastcat-stats | null]`. * with elements of `[metamap, seqs.fastq.gz | null, path-to-fastcat-stats | null]`.
* The second item is `null` for sample sheet entries without a matching barcode * The second item is `null` for sample sheet entries without a matching barcode
* directory. The last item is `null` if `fastcat` was not run (it is only run on * directory. The last item is `null` if `fastcat` was not run (it is only run on
* directories containing more than one FASTQ file or when `stats: true`). * directories containing one or more FASTQ files).
* *
* @param arguments: map with arguments containing * @param arguments: map with arguments containing
* - "input": path to either: (i) input FASTQ file, (ii) top-level directory containing * - "input": path to either: (i) input FASTQ file, (ii) top-level directory containing
@ -159,13 +162,11 @@ def add_number_of_reads_to_meta(ch, String input_type_format) {
* - "analyse_unclassified": boolean. Whether to ingress unclassified (failed to demux) reads * - "analyse_unclassified": boolean. Whether to ingress unclassified (failed to demux) reads
* - "analyse_fail": boolean. Whether to ingress any sequence files contained in `*_fail` * - "analyse_fail": boolean. Whether to ingress any sequence files contained in `*_fail`
* directories. * directories.
* - "stats": boolean whether to write the `fastcat` stats
* - "fastcat_extra_args": string with extra arguments to pass to `fastcat` * - "fastcat_extra_args": string with extra arguments to pass to `fastcat`
* - "required_sample_types": list of zero or more required sample types expected to be present * - "required_sample_types": list of zero or more required sample types expected to be present
* in the sample sheet * in the sample sheet
* - "watch_path": boolean whether to use `watchPath` and run in streaming mode
* - "per_read_stats": boolean. If true, output a bgzipped TSV containing a summary * - "per_read_stats": boolean. If true, output a bgzipped TSV containing a summary
of each read to fastcat_stats/per-read-stats.tsv.gz. * of each read to fastcat_stats/per-read-stats.tsv.gz.
* - "fastq_chunk": null or a number of reads to place into chunked FASTQ files * - "fastq_chunk": null or a number of reads to place into chunked FASTQ files
* - "allow_multiple_basecall_models": emit data of samples that had more than one * - "allow_multiple_basecall_models": emit data of samples that had more than one
* basecall model; if this is `false`, such samples will be emitted as `[meta, null, * basecall model; if this is `false`, such samples will be emitted as `[meta, null,
@ -175,7 +176,7 @@ def add_number_of_reads_to_meta(ch, String input_type_format) {
* `.fastq.gz` file with the (potentially concatenated) sequences and the third is * `.fastq.gz` file with the (potentially concatenated) sequences and the third is
* the path to the directory with the `fastcat` statistics. The second element is * the path to the directory with the `fastcat` statistics. The second element is
* `null` for sample sheet entries for which no corresponding barcode directory was * `null` for sample sheet entries for which no corresponding barcode directory was
* found. The third element is `null` if `fastcat` was not run. * found. The third element is `null` if there were no reads.
*/ */
def fastq_ingress(Map arguments) def fastq_ingress(Map arguments)
{ {
@ -191,27 +192,10 @@ def fastq_ingress(Map arguments)
ArrayList fq_extensions = [".fastq", ".fastq.gz", ".fq", ".fq.gz"] ArrayList fq_extensions = [".fastq", ".fastq.gz", ".fq", ".fq.gz"]
// `watch_path` will be handled within `get_valid_inputs()`
def input = get_valid_inputs(margs, fq_extensions) def input = get_valid_inputs(margs, fq_extensions)
def ch_result def ch_result = fastcat(input.files.mix(input.dirs), margs, "FASTQ")
if (margs.stats) {
// run fastcat regardless of input type
ch_result = fastcat(input.files.mix(input.dirs), margs, "FASTQ")
} else {
// run `fastcat` only on directories and rename / compress single files
ch_dir = fastcat(input.dirs, margs, "FASTQ")
.map { meta, path, stats -> [meta, path] }
def ch_file
if (margs["fastq_chunk"] > 0) {
ch_file = split_fq_file(input.files, margs["fastq_chunk"])
} else {
ch_file = move_or_compress_fq_file(input.files)
}
ch_result = ch_dir
| mix(ch_file)
| map { meta, path -> [meta, path, null] }
}
// TODO: xam_ingress mixes in a .no_files channel here. Do we need to do the same? // TODO: xam_ingress mixes in a .no_files channel here. Do we need to do the same?
// The above may have returned a channel with multiple fastqs if chunking // The above may have returned a channel with multiple fastqs if chunking
@ -252,10 +236,9 @@ def fastq_ingress(Map arguments)
/** /**
* Take a map of input arguments, find valid (u)BAM inputs, and return a channel * Take a map of input arguments, find valid (u)BAM inputs, and return a channel
* with elements of `[metamap, reads.bam | null, path-to-bamstats-results | null]`. * with elements of `[metamap, reads.bam | null, path-to-bamstats-results | null]`.
* The second item is `null` for sample sheet entries without a matching barcode * The second and last items are `null` for sample sheet entries without a matching barcode
* directory or samples containing only uBAM files when `keep_unaligned` is `false`. * directory or samples containing only uBAM files when `keep_unaligned` is `false`.
* The last item is `null` if `bamstats` was not run (it is only run when `stats: * The last item is `null` if `bamstats` was not run due to no reads for the sample.
* true`).
* *
* @param arguments: map with arguments containing * @param arguments: map with arguments containing
* - "input": path to either: (i) input (u)BAM file, (ii) top-level directory * - "input": path to either: (i) input (u)BAM file, (ii) top-level directory
@ -266,14 +249,12 @@ def fastq_ingress(Map arguments)
* - "analyse_unclassified": boolean. Whether to ingress unclassified (failed to demux) reads * - "analyse_unclassified": boolean. Whether to ingress unclassified (failed to demux) reads
* - "analyse_fail": boolean. Whether to ingress any sequence files contained in `*_fail` * - "analyse_fail": boolean. Whether to ingress any sequence files contained in `*_fail`
* directories. * directories.
* - "stats": boolean whether to run `bamstats`
* - "keep_unaligned": boolean whether to include uBAM files * - "keep_unaligned": boolean whether to include uBAM files
* - "return_fastq": boolean whether to convert to FASTQ (this will always run * - "return_fastq": boolean whether to convert to FASTQ (this will always run
* `fastcat`) * `fastcat`)
* - "fastcat_extra_args": string with extra arguments to pass to `fastcat` * - "fastcat_extra_args": string with extra arguments to pass to `fastcat`
* - "required_sample_types": list of zero or more required sample types expected to be present * - "required_sample_types": list of zero or more required sample types expected to be present
* in the sample sheet * in the sample sheet
* - "watch_path": boolean whether to use `watchPath` and run in streaming mode
* - "per_read_stats": boolean. If true, output a bgzipped TSV containing a summary * - "per_read_stats": boolean. If true, output a bgzipped TSV containing a summary
of each read to fastcat_stats/per-read-stats.tsv.gz. of each read to fastcat_stats/per-read-stats.tsv.gz.
* - "fastq_chunk": null or a number of reads to place into chunked FASTQ files * - "fastq_chunk": null or a number of reads to place into chunked FASTQ files
@ -511,9 +492,6 @@ def xam_ingress(Map arguments)
ch_catsorted, ch_catsorted,
) )
// run `bamstats` if requested
if (margs["stats"]) {
// branch and run `bamstats` only on the non-`null` paths
ch_result = ch_result.branch { meta, path, index -> ch_result = ch_result.branch { meta, path, index ->
has_reads: path has_reads: path
is_null: true is_null: true
@ -535,10 +513,6 @@ def xam_ingress(Map arguments)
| mix( | mix(
ch_result.is_null.map{it + [null]} ch_result.is_null.map{it + [null]}
) )
} else {
// add `null` instead of path to `bamstats` results dir
ch_result = ch_result | map { meta, bam, bai -> [meta, bam, bai, null] }
}
// Remove metadata that are unnecessary downstream: // Remove metadata that are unnecessary downstream:
// meta.src_xai: not needed, as it will be part of the channel as a file // meta.src_xai: not needed, as it will be part of the channel as a file
@ -790,97 +764,6 @@ process bamstats {
' bamstats_results/bamstats.basecallers.tsv | sort | uniq > bamstats_results/basecallers ' bamstats_results/bamstats.basecallers.tsv | sort | uniq > bamstats_results/basecallers
""" """
} }
/**
* Run `watchPath` on the input directory and return a channel of shape [metamap,
* path-to-target-file]. The meta data is taken from the sample sheet in case one was
* provided. Otherwise it only contains the `alias` (either `margs["sample"]` or the
* name of the parent directory of the file).
*
* @param input: path to a directory to watch
* @param margs: Map with parsed input arguments
* @param extensions: list of valid extensions for the target file type
* @return: Channel of [metamap, path-to-target-file]
*/
def watch_path(Path input, Map margs, ArrayList extensions) {
// we have two cases to consider: (i) files being generated in the top-level
// directory and (ii) files being generated in sub-directories. If we find files of
// both kinds, throw an error.
if (input.isFile()) {
error "Input ($input) must be a folder when using `watch_path`."
}
// get existing target files first (look for relevant files in the top-level dir and
// all sub-dirs)
def ch_existing_input = Channel.fromPath(input)
| concat(Channel.fromPath("$input/*", type: 'dir'))
| map { get_target_files_in_dir(it, extensions, margs, recursive=false) }
| flatten
// now get channel with files found by `watchPath`
def ch_watched = Channel.watchPath("$input/**").until { it.name.startsWith('STOP') }
// only keep target files
| filter { is_target_file(it, extensions) && !is_excluded(it, margs) }
// merge the channels
ch_watched = ch_existing_input | concat(ch_watched)
// check if input is as expected; start by throwing an error when finding files in
// top-level dir and sub-directories
String prev_input_type
ch_watched
| map {
String input_type = (it.parent == input) ? "top-level" : "sub-dir"
if (prev_input_type && (input_type != prev_input_type)) {
error "`watchPath` found input files in the top-level folder " +
"as well as in sub-directories."
}
// if file is in a sub-dir, make sure it's not a sub-sub-dir
if ((input_type == "sub-dir") && (it.parent.parent != input)) {
error "`watchPath` found an input file more than one level of " +
"sub-directories deep ('$it')."
}
// we also don't want files in the top-level dir when we got a sample sheet
if ((input_type == "top-level") && margs["sample_sheet"]) {
error "`watchPath` found input files in top-level folder even though " +
"a sample sheet was provided ('${margs["sample_sheet"]}')."
}
prev_input_type = input_type
}
if (margs.sample_sheet) {
// add metadata from sample sheet (we can't use join here since it does not work
// with repeated keys; we therefore need to transform the sample sheet data into
// a map with the barcodes as keys)
def ch_sample_sheet = get_sample_sheet(file(margs.sample_sheet), margs.required_sample_types)
| collect
| map { it.collectEntries { [(it["barcode"]): it] } }
// now we can use this channel to annotate all files with the corresponding info
// from the sample sheet
ch_watched = ch_watched
| combine(ch_sample_sheet)
| map { file_path, sample_sheet_map ->
String barcode = file_path.parent.name
Map sample_sheet_entry = sample_sheet_map[barcode]
// throw error if the barcode was not in the sample sheet
if (!sample_sheet_entry) {
error "Sub-folder $barcode was not found in the sample sheet."
}
[create_metamap(sample_sheet_entry), file_path]
}
} else {
ch_watched = ch_watched
| map {
// This file could be in the top-level dir or a sub-dir. In the first case
// check if a sample name was provided. In the second case, the alias is
// always the name of the sub-dir.
String alias
if (it.parent == input) {
// top-level dir
alias = margs["sample"] ?: it.parent.name
} else {
// sub-dir
alias = it.parent.name
}
[create_metamap([alias: alias]), it]
}
}
return ch_watched
}
process move_or_compress_fq_file { process move_or_compress_fq_file {
@ -950,9 +833,7 @@ Map parse_arguments(String func_name, Map arguments, Map extra_kwargs=[:]) {
"sample_sheet": null, "sample_sheet": null,
"analyse_unclassified": false, "analyse_unclassified": false,
"analyse_fail": false, "analyse_fail": false,
"stats": true,
"required_sample_types": [], "required_sample_types": [],
"watch_path": false,
"per_read_stats": false, "per_read_stats": false,
"allow_multiple_basecall_models": false, "allow_multiple_basecall_models": false,
] ]
@ -970,7 +851,7 @@ Map parse_arguments(String func_name, Map arguments, Map extra_kwargs=[:]) {
* null]` (with `input_path` pointing to a target file or a directory containing target * null]` (with `input_path` pointing to a target file or a directory containing target
* files, respectively). `missing` contains sample sheet entries for which no * files, respectively). `missing` contains sample sheet entries for which no
* corresponding barcodes were found. * corresponding barcodes were found.
* Unless `watchPath` was requested, the function checks whether the input is a single * Checks whether the input is a single
* target file, a top-level directory with target files, or a directory containing * target file, a top-level directory with target files, or a directory containing
* sub-directories (usually barcodes) with target files. * sub-directories (usually barcodes) with target files.
* *
@ -992,12 +873,8 @@ def get_valid_inputs(Map margs, ArrayList extensions){
// declare resulting input channel // declare resulting input channel
def ch_input def ch_input
// run `watchPath` if requested
if (margs["watch_path"]) {
ch_input = watch_path(input, margs, extensions)
// otherwise, easy case is this a file? if (input.isFile()) {
} else if (input.isFile()) {
if (!is_target_file(input, extensions)) { if (!is_target_file(input, extensions)) {
error "Input file is not of required file type." error "Input file is not of required file type."
} }
@ -1270,7 +1147,8 @@ def get_sample_sheet(Path sample_sheet, ArrayList required_sample_types) {
// in STDOUT. Thus, we use the somewhat clunky construct with `concat` and `last` // in STDOUT. Thus, we use the somewhat clunky construct with `concat` and `last`
// below. This lets the CSV channel only start to emit once the error checking is // below. This lets the CSV channel only start to emit once the error checking is
// done. // done.
ch_err = validate_sample_sheet(sample_sheet, required_sample_types).map { stdoutput, sample_sheet_file -> boolean no_barcode_mode = false // CW-7025
ch_err = validate_sample_sheet(sample_sheet, required_sample_types, no_barcode_mode).map { stdoutput, sample_sheet_file ->
// check if there was an error message // check if there was an error message
if (stdoutput) error "Invalid sample sheet: ${stdoutput}." if (stdoutput) error "Invalid sample sheet: ${stdoutput}."
stdoutput stdoutput
@ -1324,12 +1202,14 @@ process validate_sample_sheet {
input: input:
path "sample_sheet.csv" path "sample_sheet.csv"
val required_sample_types val required_sample_types
val no_barcode
output: output:
tuple stdout, path("sample_sheet.csv") tuple stdout, path("sample_sheet.csv")
script: script:
String req_types_arg = required_sample_types ? "--required_sample_types "+required_sample_types.join(" ") : "" String req_types_arg = required_sample_types ? "--required_sample_types "+required_sample_types.join(" ") : ""
String no_barcode_arg = no_barcode ? "--no_barcode" : ""
""" """
workflow-glue check_sample_sheet sample_sheet.csv $req_types_arg workflow-glue check_sample_sheet sample_sheet.csv $req_types_arg $no_barcode_arg
""" """
} }

View File

@ -450,8 +450,6 @@ process makeReport {
output: output:
path ("wf-transcriptomes-*.html"), emit: report path ("wf-transcriptomes-*.html"), emit: report
path ("results_dge.tsv"), emit: results_dge, optional: true
path ("unfiltered_tpm_transcript_counts.tsv"), emit: tpm, optional: true
path ("unfiltered_transcript_counts_with_genes.tsv"), emit: unfiltered, optional: true path ("unfiltered_transcript_counts_with_genes.tsv"), emit: unfiltered, optional: true
path ("filtered_transcript_counts_with_genes.tsv"), emit: filtered, optional: true path ("filtered_transcript_counts_with_genes.tsv"), emit: filtered, optional: true
path ("all_gene_counts.tsv"), emit: gene_counts, optional: true path ("all_gene_counts.tsv"), emit: gene_counts, optional: true
@ -835,9 +833,11 @@ workflow pipeline {
results = results.map{ [it, null] }.concat(fastq_ingress_results.map { [it, "fastq_ingress_results"] }) results = results.map{ [it, null] }.concat(fastq_ingress_results.map { [it, "fastq_ingress_results"] })
if (params.de_analysis){ if (params.de_analysis){
// `makeReport` stages copies of some DE files into its work dir; only publish
// the original `results_dge.tsv` and `unfiltered_tpm_transcript_counts.tsv`
// from `de_outputs` to avoid duplicate publish targets under `de_analysis/`.
de_results = report.concat( de_results = report.concat(
transcriptome, de_outputs.flatten(), transcriptome, de_outputs.flatten(),
makeReport.out.results_dge, makeReport.out.tpm,
makeReport.out.filtered, makeReport.out.unfiltered, makeReport.out.filtered, makeReport.out.unfiltered,
makeReport.out.gene_counts) makeReport.out.gene_counts)
// Output de_analysis results in the dedicated directory. // Output de_analysis results in the dedicated directory.
@ -1004,7 +1004,6 @@ workflow {
"sample":params.sample, "sample":params.sample,
"sample_sheet":params.sample_sheet, "sample_sheet":params.sample_sheet,
"analyse_unclassified":params.analyse_unclassified, "analyse_unclassified":params.analyse_unclassified,
"stats": true,
"fastcat_extra_args": "", "fastcat_extra_args": "",
"per_read_stats": true]) "per_read_stats": true])
} else { } else {
@ -1015,7 +1014,6 @@ workflow {
"analyse_unclassified":params.analyse_unclassified, "analyse_unclassified":params.analyse_unclassified,
"keep_unaligned": true, "keep_unaligned": true,
"return_fastq": true, "return_fastq": true,
"stats": true,
"per_read_stats": true]) "per_read_stats": true])
} }

View File

@ -72,10 +72,8 @@ process deAnalysis {
path "de_analysis/results_dge.tsv", emit: dge path "de_analysis/results_dge.tsv", emit: dge
path "de_analysis/results_dexseq.tsv", emit: dexseq path "de_analysis/results_dexseq.tsv", emit: dexseq
path "de_analysis/results_dge.pdf", emit: dge_pdf path "de_analysis/results_dge.pdf", emit: dge_pdf
path "de_analysis/results_dge.tsv", emit: dge_tsv
path "de_analysis/results_dtu_gene.tsv", emit: dtu_gene path "de_analysis/results_dtu_gene.tsv", emit: dtu_gene
path "de_analysis/results_dtu_transcript.tsv", emit: dtu_transcript path "de_analysis/results_dtu_transcript.tsv", emit: dtu_transcript
path "de_analysis/results_dtu_stageR.tsv", emit: dtu_stageR
path "de_analysis/results_dtu.pdf", emit: dtu_pdf path "de_analysis/results_dtu.pdf", emit: dtu_pdf
path "de_analysis/cpm_gene_counts.tsv", emit: cpm path "de_analysis/cpm_gene_counts.tsv", emit: cpm
script: script:
@ -195,8 +193,8 @@ workflow differential_expression {
analysis.gene_counts, analysis.dge, analysis.dexseq, analysis.gene_counts, analysis.dge, analysis.dexseq,
analysis.stageR, sample_sheet, merged, ref_annotation, merged_TPM, analysis.unflt_counts).collect() analysis.stageR, sample_sheet, merged, ref_annotation, merged_TPM, analysis.unflt_counts).collect()
// Concat files required to be output to user without any changes // Concat files required to be output to user without any changes
de_outputs_concat = analysis.cpm.concat(analysis.dexseq, plotResults.out.dtu_plots, analysis.dge_pdf, analysis.dge_tsv, de_outputs_concat = analysis.cpm.concat(analysis.dexseq, plotResults.out.dtu_plots, analysis.dge_pdf, analysis.dge,
analysis.dtu_gene, analysis.dtu_transcript, analysis.dtu_stageR, analysis.dtu_pdf, merged_TPM).collect() analysis.dtu_gene, analysis.dtu_transcript, analysis.stageR, analysis.dtu_pdf, merged_TPM).collect()
collected_de_alignment_stats = mapped.align_stats.collect() collected_de_alignment_stats = mapped.align_stats.collect()
emit: emit:
all_de = de_report all_de = de_report