
Nextflow Development
- 1.7k installs
- 23.1k repo stars
- Updated July 28, 2026
- anthropics/knowledge-work-plugins
Run nf-core bioinformatics pipelines (rnaseq, sarek, atacseq) on sequencing data. Use when analyzing RNA-seq, WGS/WES, or ATAC-seq data—either local FASTQs or public datasets from GEO/SRA. Triggers on
About
The nextflow development skill Run nf-core bioinformatics pipelines (rnaseq, sarek, atacseq) on sequencing data. Use when analyzing RNA-seq, WGS/WES, or ATAC-seq data - either local FASTQs or public datasets from GEO/SRA. Triggers on nf-core, Nextflow, FASTQ analysis, variant calling, gene expression, differential expression, GEO reanalysis, GSE/GSM/SRR accessions, or samplesheet creation. Documentation covers workflows, commands, and guardrails agents should follow when users invoke this capability. Key documented areas include [ ] Step 0: Acquire data (if from GEO/SRA); [ ] Step 1: Environment check (MUST pass); [ ] Step 2: Select pipeline (confirm with user); [ ] Step 3: Run test profile (MUST pass). Reference commands include - [ ] Step 0: Acquire data (if from GEO/SRA); - [ ] Step 1: Environment check (MUST pass). Use when developers or agents need structured guidance for nextflow development tasks with evidence grounded in the bundled SKILL.md rather than generic advice.
- [ ] Step 0: Acquire data (if from GEO/SRA)
- [ ] Step 1: Environment check (MUST pass)
- [ ] Step 2: Select pipeline (confirm with user)
- [ ] Step 3: Run test profile (MUST pass)
- [ ] Step 4: Create samplesheet
Nextflow Development by the numbers
- 1,705 all-time installs (skills.sh)
- +85 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #98 of 2,066 Data Science & ML skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
nextflow-development capabilities & compatibility
- Capabilities
- [ ] step 0: acquire data (if from geo/sra) · [ ] step 1: environment check (must pass) · [ ] step 2: select pipeline (confirm with user) · [ ] step 3: run test profile (must pass) · [ ] step 4: create samplesheet
npx skills add https://github.com/anthropics/knowledge-work-plugins --skill nextflow-developmentAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.7k |
|---|---|
| repo stars | ★ 23.1k |
| Security audit | 2 / 3 scanners passed |
| Last updated | July 28, 2026 |
| Repository | anthropics/knowledge-work-plugins ↗ |
How do I handle nextflow development tasks with agent guidance?
Run nf-core bioinformatics pipelines (rnaseq, sarek, atacseq) on sequencing data. Use when analyzing RNA-seq, WGS/WES, or ATAC-seq data—either local FASTQs or public datasets from GEO/SRA. Triggers on
Who is it for?
Teams needing documented nextflow development workflows.
Skip if: Simple one-file edits, bioinformatics Nextflow pipeline DSL work, or projects outside the knowledge-work-plugins ecosystem.
When should I use this skill?
Run nf-core bioinformatics pipelines (rnaseq, sarek, atacseq) on sequencing data. Use when analyzing RNA-seq, WGS/WES, or ATAC-seq data—either local FASTQs or public datasets from GEO/SRA. Triggers on
What you get
Structured workflow from nextflow development documentation applied to the user request.
- Multi-step agent workflow definitions
- Plugin orchestration plans
Files
nf-core Pipeline Deployment
Run nf-core bioinformatics pipelines on local or public sequencing data.
Target users: Bench scientists and researchers without specialized bioinformatics training who need to run large-scale omics analyses—differential expression, variant calling, or chromatin accessibility analysis.
Workflow Checklist
- [ ] Step 0: Acquire data (if from GEO/SRA)
- [ ] Step 1: Environment check (MUST pass)
- [ ] Step 2: Select pipeline (confirm with user)
- [ ] Step 3: Run test profile (MUST pass)
- [ ] Step 4: Create samplesheet
- [ ] Step 5: Configure & run (confirm genome with user)
- [ ] Step 6: Verify outputs---
Step 0: Acquire Data (GEO/SRA Only)
Skip this step if user has local FASTQ files.
For public datasets, fetch from GEO/SRA first. See references/geo-sra-acquisition.md for the full workflow.
Quick start:
# 1. Get study info
python scripts/sra_geo_fetch.py info GSE110004
# 2. Download (interactive mode)
python scripts/sra_geo_fetch.py download GSE110004 -o ./fastq -i
# 3. Generate samplesheet
python scripts/sra_geo_fetch.py samplesheet GSE110004 --fastq-dir ./fastq -o samplesheet.csvDECISION POINT: After fetching study info, confirm with user:
- Which sample subset to download (if multiple data types)
- Suggested genome and pipeline
Then continue to Step 1.
---
Step 1: Environment Check
Run first. Pipeline will fail without passing environment.
python scripts/check_environment.pyAll critical checks must pass. If any fail, provide fix instructions:
Docker issues
| Problem | Fix |
|---|---|
| Not installed | Install from https://docs.docker.com/get-docker/ |
| Permission denied | sudo usermod -aG docker $USER then re-login |
| Daemon not running | sudo systemctl start docker |
Nextflow issues
| Problem | Fix |
|---|---|
| Not installed | `curl -s https://get.nextflow.io \ |
| Version < 23.04 | nextflow self-update |
Java issues
| Problem | Fix |
|---|---|
| Not installed / < 11 | sudo apt install openjdk-11-jdk |
Do not proceed until all checks pass. For HPC/Singularity, see references/troubleshooting.md.
---
Step 2: Select Pipeline
DECISION POINT: Confirm with user before proceeding.
| Data Type | Pipeline | Version | Goal |
|---|---|---|---|
| RNA-seq | rnaseq | 3.22.2 | Gene expression |
| WGS/WES | sarek | 3.7.1 | Variant calling |
| ATAC-seq | atacseq | 2.1.2 | Chromatin accessibility |
Auto-detect from data:
python scripts/detect_data_type.py /path/to/dataFor pipeline-specific details:
- references/pipelines/rnaseq.md
- references/pipelines/sarek.md
- references/pipelines/atacseq.md
---
Step 3: Run Test Profile
Validates environment with small data. MUST pass before real data.
nextflow run nf-core/<pipeline> -r <version> -profile test,docker --outdir test_output| Pipeline | Command |
|---|---|
| rnaseq | nextflow run nf-core/rnaseq -r 3.22.2 -profile test,docker --outdir test_rnaseq |
| sarek | nextflow run nf-core/sarek -r 3.7.1 -profile test,docker --outdir test_sarek |
| atacseq | nextflow run nf-core/atacseq -r 2.1.2 -profile test,docker --outdir test_atacseq |
Verify:
ls test_output/multiqc/multiqc_report.html
grep "Pipeline completed successfully" .nextflow.logIf test fails, see references/troubleshooting.md.
---
Step 4: Create Samplesheet
Generate automatically
python scripts/generate_samplesheet.py /path/to/data <pipeline> -o samplesheet.csvThe script:
- Discovers FASTQ/BAM/CRAM files
- Pairs R1/R2 reads
- Infers sample metadata
- Validates before writing
For sarek: Script prompts for tumor/normal status if not auto-detected.
Validate existing samplesheet
python scripts/generate_samplesheet.py --validate samplesheet.csv <pipeline>Samplesheet formats
rnaseq:
sample,fastq_1,fastq_2,strandedness
SAMPLE1,/abs/path/R1.fq.gz,/abs/path/R2.fq.gz,autosarek:
patient,sample,lane,fastq_1,fastq_2,status
patient1,tumor,L001,/abs/path/tumor_R1.fq.gz,/abs/path/tumor_R2.fq.gz,1
patient1,normal,L001,/abs/path/normal_R1.fq.gz,/abs/path/normal_R2.fq.gz,0atacseq:
sample,fastq_1,fastq_2,replicate
CONTROL,/abs/path/ctrl_R1.fq.gz,/abs/path/ctrl_R2.fq.gz,1---
Step 5: Configure & Run
5a. Check genome availability
python scripts/manage_genomes.py check <genome>
# If not installed:
python scripts/manage_genomes.py download <genome>Common genomes: GRCh38 (human), GRCh37 (legacy), GRCm39 (mouse), R64-1-1 (yeast), BDGP6 (fly)
5b. Decision points
DECISION POINT: Confirm with user:
1. Genome: Which reference to use 2. Pipeline-specific options:
- rnaseq: aligner (star_salmon recommended, hisat2 for low memory)
- sarek: tools (haplotypecaller for germline, mutect2 for somatic)
- atacseq: read_length (50, 75, 100, or 150)
5c. Run pipeline
nextflow run nf-core/<pipeline> \
-r <version> \
-profile docker \
--input samplesheet.csv \
--outdir results \
--genome <genome> \
-resumeKey flags:
-r: Pin version-profile docker: Use Docker (orsingularityfor HPC)--genome: iGenomes key-resume: Continue from checkpoint
Resource limits (if needed):
--max_cpus 8 --max_memory '32.GB' --max_time '24.h'---
Step 6: Verify Outputs
Check completion
ls results/multiqc/multiqc_report.html
grep "Pipeline completed successfully" .nextflow.logKey outputs by pipeline
rnaseq:
results/star_salmon/salmon.merged.gene_counts.tsv- Gene countsresults/star_salmon/salmon.merged.gene_tpm.tsv- TPM values
sarek:
results/variant_calling/*/- VCF filesresults/preprocessing/recalibrated/- BAM files
atacseq:
results/macs2/narrowPeak/- Peak callsresults/bwa/mergedLibrary/bigwig/- Coverage tracks
---
Quick Reference
For common exit codes and fixes, see references/troubleshooting.md.
Resume failed run
nextflow run nf-core/<pipeline> -resume---
References
- references/geo-sra-acquisition.md - Downloading public GEO/SRA data
- references/troubleshooting.md - Common issues and fixes
- references/installation.md - Environment setup
- references/pipelines/rnaseq.md - RNA-seq pipeline details
- references/pipelines/sarek.md - Variant calling details
- references/pipelines/atacseq.md - ATAC-seq details
---
Disclaimer
This skill is provided as a prototype example demonstrating how to integrate nf-core bioinformatics pipelines into Claude Code for automated analysis workflows. The current implementation supports three pipelines (rnaseq, sarek, and atacseq), serving as a foundation that enables the community to expand support to the full set of nf-core pipelines.
It is intended for educational and research purposes and should not be considered production-ready without appropriate validation for your specific use case. Users are responsible for ensuring their computing environment meets pipeline requirements and for verifying analysis results.
Anthropic does not guarantee the accuracy of bioinformatics outputs, and users should follow standard practices for validating computational analyses. This integration is not officially endorsed by or affiliated with the nf-core community.
Attribution
When publishing results, cite the appropriate pipeline. Citations are available in each nf-core repository's CITATIONS.md file (e.g., https://github.com/nf-core/rnaseq/blob/3.22.2/CITATIONS.md).
Licenses
- nf-core pipelines: MIT License (https://nf-co.re/about)
- Nextflow: Apache License, Version 2.0 (https://www.nextflow.io/about-us.html)
- NCBI SRA Toolkit: Public Domain (https://github.com/ncbi/sra-tools/blob/master/LICENSE)
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
GEO/SRA Data Acquisition
Download raw sequencing data from NCBI GEO/SRA and prepare it for nf-core pipelines.
Use this when: Reanalyzing published datasets, validating findings, or comparing results against public cohorts.
Table of Contents
- Workflow Overview
- Step 1: Fetch Study Information
- Step 2: Review Sample Groups
- Step 3: Download FASTQ Files
- Step 4: Generate Samplesheet
- Step 5: Run nf-core Pipeline
- Supported Pipelines
- Supported Organisms
- Complete Example
- Troubleshooting
---
Workflow Overview
Example: "Find differentially expressed genes in GSE309891 (drug-treated vs control)"
┌─────────────────────────────────────────────────────────────────┐
│ GEO/SRA DATA ACQUISITION │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────┐
│ Fetch study info │
│ • Query NCBI/SRA │
│ • Get metadata │
│ • Detect organism │
│ • Identify data type │
└────────────────────────┘
│
▼
┌────────────────────────┐
│ Present summary │
│ • Organism: Human │
│ • Genome: GRCh38 │
│ • Type: RNA-Seq │
│ • Pipeline: rnaseq │
│ • Samples: 12 │
│ (6 treated, │
│ 6 control) │
│ • Size: ~24 GB │
└────────────────────────┘
│
▼
┌─────────────────┐
│ USER CONFIRMS │◄──── Decision point
│ genome/pipeline│
└─────────────────┘
│
▼
┌────────────────────────┐
│ Select samples │
│ • Group by condition │
│ • Show treated/ctrl │
└────────────────────────┘
│
▼
┌─────────────────┐
│ USER SELECTS │◄──── Decision point
│ sample subset │
└─────────────────┘
│
▼
┌────────────────────────┐
│ Download FASTQs │
│ • 24 files (R1+R2) │
│ • Parallel transfers │
│ • Auto-resume │
└────────────────────────┘
│
▼
┌────────────────────────┐
│ Generate samplesheet │
│ • Map SRR to files │
│ • Pair R1/R2 │
│ • Assign conditions │
└────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ NF-CORE PIPELINE EXECUTION │
│ (Continue with Step 1 of main workflow) │
└─────────────────────────────────────────────────────────────────┘---
Instructions for Claude
When assisting users with GEO/SRA data acquisition:
1. Always fetch study info first to show the user what data is available 2. Ask for confirmation before downloading - Present the sample groups and sizes, then ask which subset to download using AskUserQuestion 3. Suggest appropriate genome and pipeline based on the organism and data type 4. Return to main SKILL.md workflow after data preparation is complete
Example confirmation question:
Question: "Which sample group would you like to download?"
Options:
- "RNA-Seq:PAIRED (42 samples, ~87 GB)"
- "RNA-Seq:SINGLE (7 samples, ~4.5 GB)"
- "All samples (49 samples, ~92 GB)"---
Step 1: Fetch Study Information
Get metadata about a GEO study before downloading.
python scripts/sra_geo_fetch.py info <GEO_ID>Example:
python scripts/sra_geo_fetch.py info GSE110004Output includes:
- Study title and summary
- Organism (with auto-suggested genome)
- Number of samples and runs
- Data types (RNA-Seq, ATAC-seq, etc.)
- Estimated download size
- Suggested nf-core pipeline
Save info to JSON:
python scripts/sra_geo_fetch.py info GSE110004 -o study_info.json---
Step 2: Review Sample Groups
View sample groups organized by data type and layout. This is useful for studies with mixed data types.
python scripts/sra_geo_fetch.py groups <GEO_ID>Example output:
Sample Group Count Layout GSM Range Est. Size
--------------------------------------------------------------------------------
RNA-Seq 42 PAIRED GSM2879618...(42 samples) 87.4 GB
RNA-Seq 7 SINGLE GSM2976181-GSM2976187 4.5 GB
--------------------------------------------------------------------------------
TOTAL 49 91.9 GB
Available groups for --subset option:
1. "RNA-Seq:PAIRED" - 42 samples (~87.4 GB)
2. "RNA-Seq:SINGLE" - 7 samples (~4.5 GB)List individual runs:
python scripts/sra_geo_fetch.py list <GEO_ID>
# Filter by data type
python scripts/sra_geo_fetch.py list GSE110004 --filter "RNA-Seq:PAIRED"DECISION POINT: Review the sample groups. Decide which subset to download if the study has multiple data types.
---
Step 3: Download FASTQ Files
Download FASTQ files from ENA (faster than SRA).
python scripts/sra_geo_fetch.py download <GEO_ID> -o <OUTPUT_DIR>Options:
-o, --output: Output directory (required)-i, --interactive: Interactively select sample group to download-s, --subset: Filter by data type (e.g., "RNA-Seq:PAIRED")-p, --parallel: Parallel downloads (default: 4)-t, --timeout: Download timeout in seconds (default: 600)
Interactive Mode (Recommended)
Use -i flag for interactive sample selection when the study has multiple data types:
python scripts/sra_geo_fetch.py download GSE110004 -o ./fastq -iInteractive output:
============================================================
SELECT SAMPLE GROUP TO DOWNLOAD
============================================================
[1] RNA-Seq (paired)
Samples: 42
GSM: GSM2879618...(42 samples)
Size: ~87.4 GB
[2] RNA-Seq (single)
Samples: 7
GSM: GSM2976181-GSM2976187
Size: ~4.5 GB
[0] Download ALL (49 samples)
------------------------------------------------------------
Enter selection (0-2):Direct Subset Selection
Alternatively, specify the subset directly:
# Download only RNA-Seq paired-end data
python scripts/sra_geo_fetch.py download GSE110004 -o ./fastq \
--subset "RNA-Seq:PAIRED" --parallel 6Note: Downloads automatically skip existing files. Resume interrupted downloads by re-running the command.
---
Step 4: Generate Samplesheet
Create a samplesheet compatible with nf-core pipelines.
python scripts/sra_geo_fetch.py samplesheet <GEO_ID> \
--fastq-dir <FASTQ_DIR> \
-o samplesheet.csvOptions:
-f, --fastq-dir: Directory containing downloaded FASTQ files (required)-o, --output: Output samplesheet path (default: samplesheet.csv)-p, --pipeline: Target pipeline (auto-detected if not specified)
Example:
python scripts/sra_geo_fetch.py samplesheet GSE110004 \
--fastq-dir ./fastq \
-o samplesheet.csvOutput: The script will: 1. Create samplesheet in the format required by the target pipeline 2. Display suggested genome reference 3. Show suggested nf-core command
---
Step 5: Run nf-core Pipeline
After generating the samplesheet, the script provides a suggested command.
Example output:
Suggested command:
nextflow run nf-core/rnaseq \
--input samplesheet.csv \
--outdir results \
--genome R64-1-1 \
-profile dockerDECISION POINT: Review and confirm: 1. Is the suggested pipeline correct? 2. Is the genome reference correct for your organism? 3. Do you need additional pipeline options?
Then return to the main SKILL.md workflow (Step 1: Environment Check) to proceed with pipeline execution.
---
Supported Pipelines
The skill auto-detects appropriate pipelines based on library strategy. Pipelines marked with ★ are fully supported with configs, samplesheet generation, and documentation. Others are suggested but require manual setup following nf-core documentation.
| Library Strategy | Suggested Pipeline | Support |
|---|---|---|
| RNA-Seq | nf-core/rnaseq | ★ Full |
| ATAC-seq | nf-core/atacseq | ★ Full |
| WGS/WXS | nf-core/sarek | ★ Full |
| ChIP-seq | nf-core/chipseq | Manual |
| Bisulfite-Seq | nf-core/methylseq | Manual |
| miRNA-Seq | nf-core/smrnaseq | Manual |
| Amplicon | nf-core/ampliseq | Manual |
---
Supported Organisms
Common organisms with auto-suggested genomes:
| Organism | Genome | Notes |
|---|---|---|
| Homo sapiens | GRCh38 | Human reference |
| Mus musculus | GRCm39 | Mouse reference |
| Saccharomyces cerevisiae | R64-1-1 | Yeast S288C |
| Drosophila melanogaster | BDGP6 | Fruit fly |
| Caenorhabditis elegans | WBcel235 | C. elegans |
| Danio rerio | GRCz11 | Zebrafish |
| Arabidopsis thaliana | TAIR10 | Arabidopsis |
| Rattus norvegicus | Rnor_6.0 | Rat |
See scripts/config/genomes.yaml for the full list.
---
Complete Example
Reanalyze GSE110004 (yeast RNA-seq):
# 1. Get study info and sample groups
python scripts/sra_geo_fetch.py info GSE110004
# 2. Download with interactive selection
python scripts/sra_geo_fetch.py download GSE110004 -o ./fastq -i
# Select option [1] for RNA-Seq paired-end samples
# 3. Generate samplesheet
python scripts/sra_geo_fetch.py samplesheet GSE110004 \
--fastq-dir ./fastq \
-o samplesheet.csv
# 4. Run nf-core/rnaseq (continue with main SKILL.md workflow)
nextflow run nf-core/rnaseq \
--input samplesheet.csv \
--outdir results \
--genome R64-1-1 \
-profile dockerAlternative: Non-interactive Download
# Review sample groups first
python scripts/sra_geo_fetch.py groups GSE110004
# Download specific subset directly
python scripts/sra_geo_fetch.py download GSE110004 \
--subset "RNA-Seq:PAIRED" \
-o ./fastq \
--parallel 4---
Troubleshooting
ENA Download Fails
If ENA downloads fail, the data may need to be fetched directly from SRA:
# Create SRA tools environment
conda create -n sra_tools -c bioconda sra-tools
# Download with prefetch + fasterq-dump
conda run -n sra_tools prefetch SRR6357070
conda run -n sra_tools fasterq-dump SRR6357070 -O ./fastqNo SRA Runs Found
Some GEO datasets only have processed data, not raw sequencing reads. Check:
python scripts/sra_geo_fetch.py info <GEO_ID>If "Runs: 0", the dataset may not have raw data in SRA.
SuperSeries Support
GEO SuperSeries (which contain multiple SubSeries) are automatically handled. The tool will: 1. Detect that a GEO ID is a SuperSeries 2. Find the linked BioProject accession 3. Fetch all SRA runs from the BioProject
Example: GSE110004 is a SuperSeries that links to BioProject PRJNA432544.
Genome Not Recognized
If the organism is not in the genome mapping, manually specify the genome:
# Check available iGenomes
python scripts/manage_genomes.py list
# Or provide custom reference files to nf-core
nextflow run nf-core/rnaseq --fasta /path/to/genome.fa --gtf /path/to/genes.gtf---
Requirements
- Python 3.8+
requestslibrary (optional but recommended)pyyamllibrary (optional, for genome config)- Network access to NCBI and ENA
Install optional dependencies:
pip install requests pyyamlInstallation
Contents
- Quick install
- Docker setup
- Singularity setup (HPC)
- nf-core tools (optional)
- Verify installation
- Common issues
Quick install
# Nextflow
curl -s https://get.nextflow.io | bash
mv nextflow ~/bin/
export PATH="$HOME/bin:$PATH"
# Verify
nextflow -version
java -version # Requires 11+Docker setup
Linux
sudo apt-get update && sudo apt-get install docker.io
sudo systemctl enable --now docker
sudo usermod -aG docker $USER
# Log out and back inmacOS
Download Docker Desktop: https://docker.com/products/docker-desktop
Verify
docker run hello-worldSingularity setup (HPC)
# Ubuntu/Debian
sudo apt-get install singularity-container
# Or via conda
conda install -c conda-forge singularityConfigure cache
export NXF_SINGULARITY_CACHEDIR="$HOME/.singularity/cache"
mkdir -p $NXF_SINGULARITY_CACHEDIR
echo 'export NXF_SINGULARITY_CACHEDIR="$HOME/.singularity/cache"' >> ~/.bashrcnf-core tools (optional)
pip install nf-coreUseful commands:
nf-core list # Available pipelines
nf-core launch rnaseq # Interactive parameter selection
nf-core download rnaseq -r 3.14.0 # Download for offline useVerify installation
nextflow run nf-core/demo -profile test,docker --outdir test_demo
ls test_demo/Common issues
Java version wrong:
export JAVA_HOME=/path/to/java11Docker permission denied:
sudo usermod -aG docker $USER
# Log out and back inNextflow not found:
echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc
source ~/.bashrcnf-core/atacseq
Version: 2.1.2
Official Documentation: https://nf-co.re/atacseq/2.1.2/ GitHub: https://github.com/nf-core/atacseq
Note: When updating to a new version, check the releases page for breaking changes and update the version in commands below.
Contents
Test command
nextflow run nf-core/atacseq -r 2.1.2 -profile test,docker --outdir test_atacseqExpected: ~15 min, creates peaks and BigWig tracks.
Samplesheet format
sample,fastq_1,fastq_2,replicate
CONTROL,/path/to/ctrl_rep1_R1.fq.gz,/path/to/ctrl_rep1_R2.fq.gz,1
CONTROL,/path/to/ctrl_rep2_R1.fq.gz,/path/to/ctrl_rep2_R2.fq.gz,2
TREATMENT,/path/to/treat_rep1_R1.fq.gz,/path/to/treat_rep1_R2.fq.gz,1
TREATMENT,/path/to/treat_rep2_R1.fq.gz,/path/to/treat_rep2_R2.fq.gz,2| Column | Required | Description |
|---|---|---|
| sample | Yes | Condition/group identifier |
| fastq_1 | Yes | Absolute path to R1 |
| fastq_2 | Yes | Absolute path to R2 (paired-end required) |
| replicate | Yes | Replicate number (integer) |
Design file for differential analysis
sample,condition
CONTROL,control
TREATMENT,treatmentUse with --deseq2_design design.csv.
Parameters
Minimal run
nextflow run nf-core/atacseq -r 2.1.2 -profile docker \
--input samplesheet.csv --outdir results --genome GRCh38 --read_length 50Common parameters
| Parameter | Default | Description |
|---|---|---|
--genome | - | GRCh38, GRCh37, mm10 |
--read_length | 50 | Read length for MACS2 optimization |
--narrow_peak | true | Narrow peaks (false for broad) |
--mito_name | chrM | Mitochondrial chromosome name |
--keep_mito | false | Keep mitochondrial reads |
--min_reps_consensus | 1 | Min replicates for consensus peaks |
Differential accessibility
--deseq2_design design.csvOutput files
results/
├── bwa/mergedLibrary/
│ ├── *.mLb.mkD.sorted.bam # Filtered, deduplicated alignments
│ └── bigwig/
│ └── *.bigWig # Coverage tracks
├── macs2/narrowPeak/
│ ├── *.narrowPeak # Peak calls
│ └── consensus/
│ └── consensus_peaks.bed # Merged peaks across replicates
├── deeptools/
│ ├── plotFingerprint/ # Library complexity
│ └── plotProfile/ # TSS enrichment
├── deseq2/ # If --deseq2_design provided
└── multiqc/Key outputs:
*.mLb.mkD.sorted.bam: Analysis-ready alignments*.narrowPeak: MACS2 peak calls (BED format)consensus_peaks.bed: Consensus peaks across replicates*.bigWig: Genome browser tracks
Quality metrics
| Metric | Good | Acceptable | Poor |
|---|---|---|---|
| Mapped reads | >80% | 60-80% | <60% |
| Mitochondrial | <20% | 20-40% | >40% |
| Duplicates | <30% | 30-50% | >50% |
| FRiP | >30% | 15-30% | <15% |
| TSS enrichment | >6 | 4-6 | <4 |
Fragment size: Should show nucleosomal periodicity (~50bp nucleosome-free, ~200bp mono-nucleosome).
Downstream analysis
library(ChIPseeker)
library(GenomicRanges)
peaks <- import("consensus_peaks.bed")
peakAnno <- annotatePeak(peaks, TxDb = TxDb.Hsapiens.UCSC.hg38.knownGene)Motif analysis:
findMotifsGenome.pl consensus_peaks.bed hg38 motifs/ -size 200Troubleshooting
Low FRiP: Check library complexity in plotFingerprint/. May indicate over-transposition.
Few peaks: Lower threshold with --macs_qvalue 0.1 or use --narrow_peak false for broader peaks.
High duplicates: Normal for low-input; pipeline removes by default.
More Information
- Full parameter list: https://nf-co.re/atacseq/2.1.2/parameters/
- Output documentation: https://nf-co.re/atacseq/2.1.2/docs/output/
- Usage documentation: https://nf-co.re/atacseq/2.1.2/docs/usage/
nf-core/rnaseq
Version: 3.22.2
Official Documentation: https://nf-co.re/rnaseq/3.22.2/ GitHub: https://github.com/nf-core/rnaseq
Note: When updating to a new version, check the releases page for breaking changes and update the version in commands below.
Contents
Test command
nextflow run nf-core/rnaseq -r 3.22.2 -profile test,docker --outdir test_rnaseqExpected: ~15 min, creates multiqc/multiqc_report.html.
Samplesheet format
sample,fastq_1,fastq_2,strandedness
CONTROL_REP1,/path/to/ctrl1_R1.fq.gz,/path/to/ctrl1_R2.fq.gz,auto
CONTROL_REP2,/path/to/ctrl2_R1.fq.gz,/path/to/ctrl2_R2.fq.gz,auto
TREATMENT_REP1,/path/to/treat1_R1.fq.gz,/path/to/treat1_R2.fq.gz,auto| Column | Required | Values |
|---|---|---|
| sample | Yes | Alphanumeric, underscores allowed |
| fastq_1 | Yes | Absolute path to R1 |
| fastq_2 | No | Absolute path to R2 (empty for single-end) |
| strandedness | Yes | auto, forward, reverse, unstranded |
Strandedness guide:
auto: Inferred from data (recommended)forward: TruSeq Stranded, dUTP protocolsreverse: Ligation-based protocolsunstranded: Non-stranded protocols
Parameters
Minimal run
nextflow run nf-core/rnaseq -r 3.22.2 -profile docker \
--input samplesheet.csv --outdir results --genome GRCh38Common parameters
| Parameter | Default | Description |
|---|---|---|
--aligner | star_salmon | Options: star_salmon, star_rsem, hisat2 |
--genome | - | GRCh38, GRCh37, mm10, BDGP6 |
--pseudo_aligner | - | Set to salmon for pseudo-alignment only |
--skip_trimming | false | Skip adapter trimming |
--skip_alignment | false | Pseudo-alignment only |
Custom reference
--fasta /path/to/genome.fa \
--gtf /path/to/annotation.gtf \
--star_index /path/to/star/ # Optional, builds if absentOutput files
results/
├── star_salmon/
│ ├── salmon.merged.gene_counts.tsv # Raw counts for DESeq2
│ ├── salmon.merged.gene_tpm.tsv # TPM values
│ └── *.bam # Alignments
├── multiqc/
│ └── multiqc_report.html # QC summary
└── pipeline_info/Key outputs:
salmon.merged.gene_counts.tsv: Input for DESeq2/edgeRsalmon.merged.gene_tpm.tsv: Normalized expression
Downstream analysis
library(DESeq2)
counts <- read.delim("salmon.merged.gene_counts.tsv", row.names=1)
coldata <- data.frame(
condition = factor(c("control", "control", "treatment", "treatment"))
)
dds <- DESeqDataSetFromMatrix(
countData = round(counts),
colData = coldata,
design = ~ condition
)
dds <- DESeq(dds)
res <- results(dds, contrast = c("condition", "treatment", "control"))Troubleshooting
STAR index fails: Increase memory with --max_memory '64.GB' or provide pre-built --star_index.
Low alignment rate: Verify genome matches species; check FastQC for adapter contamination.
Strandedness detection fails: Specify explicitly with --strandedness reverse.
More Information
- Full parameter list: https://nf-co.re/rnaseq/3.22.2/parameters/
- Output documentation: https://nf-co.re/rnaseq/3.22.2/docs/output/
- Usage documentation: https://nf-co.re/rnaseq/3.22.2/docs/usage/
nf-core/sarek
Version: 3.7.1
Official Documentation: https://nf-co.re/sarek/3.7.1/ GitHub: https://github.com/nf-core/sarek
Note: When updating to a new version, check the releases page for breaking changes and update the version in commands below.
Contents
Test command
nextflow run nf-core/sarek -r 3.7.1 -profile test,docker --outdir test_sarekExpected: ~20 min, creates aligned BAMs and variant calls.
Samplesheet format
From FASTQ
patient,sample,lane,fastq_1,fastq_2
patient1,tumor,L001,/path/to/tumor_L001_R1.fq.gz,/path/to/tumor_L001_R2.fq.gz
patient1,tumor,L002,/path/to/tumor_L002_R1.fq.gz,/path/to/tumor_L002_R2.fq.gz
patient1,normal,L001,/path/to/normal_R1.fq.gz,/path/to/normal_R2.fq.gzFrom BAM/CRAM
patient,sample,bam,bai
patient1,tumor,/path/to/tumor.bam,/path/to/tumor.bam.bai
patient1,normal,/path/to/normal.bam,/path/to/normal.bam.baiWith tumor/normal status
patient,sample,lane,fastq_1,fastq_2,status
patient1,tumor,L001,tumor_R1.fq.gz,tumor_R2.fq.gz,1
patient1,normal,L001,normal_R1.fq.gz,normal_R2.fq.gz,0status: 0 = normal, 1 = tumor
Variant calling modes
Germline (single sample)
nextflow run nf-core/sarek -r 3.7.1 -profile docker \
--input samplesheet.csv --outdir results --genome GRCh38 \
--tools haplotypecaller,snpeffSomatic (tumor-normal pair)
nextflow run nf-core/sarek -r 3.7.1 -profile docker \
--input samplesheet.csv --outdir results --genome GRCh38 \
--tools mutect2,strelka,snpeffWES (exome)
nextflow run nf-core/sarek -r 3.7.1 -profile docker \
--input samplesheet.csv --outdir results --genome GRCh38 \
--wes --intervals /path/to/targets.bed \
--tools haplotypecaller,snpeffJoint germline (cohort)
--tools haplotypecaller --joint_germlineParameters
Available tools
Germline callers:
haplotypecaller: GATK HaplotypeCallerfreebayes: FreeBayesdeepvariant: DeepVariant (GPU optional)strelka: Strelka2 germline
Somatic callers:
mutect2: GATK Mutect2strelka: Strelka2 somaticmanta: Structural variants
CNV callers:
ascat: Copy numbercontrolfreec: CNV detectiontiddit: SV calling
Annotation:
snpeff: Functional annotationvep: Variant Effect Predictor
Key parameters
| Parameter | Default | Description |
|---|---|---|
--tools | - | Comma-separated list of tools |
--genome | - | GRCh38, GRCh37 |
--wes | false | Exome mode (requires --intervals) |
--intervals | - | BED file for targeted regions |
--joint_germline | false | Joint calling for cohorts |
--skip_bqsr | false | Skip base quality recalibration |
Output files
results/
├── preprocessing/
│ └── recalibrated/ # Analysis-ready BAMs
│ └── *.recal.bam
├── variant_calling/
│ ├── haplotypecaller/ # Germline VCFs
│ ├── mutect2/ # Somatic VCFs (filtered)
│ └── strelka/
├── annotation/
│ └── snpeff/ # Annotated VCFs
└── multiqc/Troubleshooting
BQSR fails: Check known sites available for genome. Skip with --skip_bqsr for non-standard references.
Mutect2 no variants: Verify tumor/normal pairing in samplesheet (check status column).
Out of memory: --max_memory '128.GB' for WGS.
DeepVariant GPU: Ensure NVIDIA Docker runtime configured.
More Information
- Full parameter list: https://nf-co.re/sarek/3.7.1/parameters/
- Output documentation: https://nf-co.re/sarek/3.7.1/docs/output/
- Usage documentation: https://nf-co.re/sarek/3.7.1/docs/usage/
Troubleshooting
Quick fixes for common nf-core pipeline issues.
Contents
- Exit Codes
- HPC/Singularity Issues
- Pipeline Failures
- RNA-seq Specific
- Sarek Specific
- ATAC-seq Specific
- Resource Management
- Getting Help
Exit Codes
Common exit codes indicating resource issues (per nf-core docs):
| Code | Cause | Fix |
|---|---|---|
| 137 | Out of memory | --max_memory '32.GB' or '64.GB' for WGS |
| 143 | Out of memory | --max_memory '32.GB' or '64.GB' for WGS |
| 104, 134, 139, 247 | Out of memory | Increase --max_memory |
| 1 | General error | Check .nextflow.log for details |
Most pipelines auto-retry with 2x then 3x resources before failing.
HPC/Singularity Issues
Singularity cache issues
export NXF_SINGULARITY_CACHEDIR="$HOME/.singularity/cache"
mkdir -p $NXF_SINGULARITY_CACHEDIRUsing Singularity instead of Docker
On HPC systems without Docker, use Singularity:
nextflow run nf-core/<pipeline> -profile singularity ...Note: For basic environment setup (Docker, Nextflow, Java installation), see the inline instructions in Step 1 of SKILL.md.
Pipeline Failures
Container pull failed
- Check network connectivity
- Try:
-profile singularityinstead of docker - For offline:
nf-core download <pipeline> -r <version>
"No such file" errors
- Use absolute paths in samplesheet
- Verify files exist:
ls /path/to/file
Resume not working
# Check work directory exists
ls -la work/
# Force clean restart (loses cache)
rm -rf work/ .nextflow*
nextflow run nf-core/<pipeline> ...RNA-seq Specific
STAR index fails
- Increase memory:
--max_memory '64.GB' - Or provide pre-built:
--star_index /path/to/star/
Low alignment rate
- Verify genome matches species
- Check FastQC for adapter contamination
- Try different aligner:
--aligner hisat2
Strandedness detection fails
- Specify explicitly:
--strandedness reverse - Common values:
forward,reverse,unstranded
Sarek Specific
BQSR fails
- Check known sites for genome
- Skip for non-standard references:
--skip_bqsr
Mutect2 no variants
- Verify tumor/normal pairing
- Check samplesheet
statuscolumn: 0=normal, 1=tumor
Out of memory for WGS
--max_memory '128.GB' --max_cpus 16DeepVariant GPU issues
- Ensure NVIDIA Docker runtime configured
- Or use CPU mode (slower)
ATAC-seq Specific
Low FRiP score
- Check library complexity in
plotFingerprint/ - May indicate over-transposition
Few peaks called
- Lower threshold:
--macs_qvalue 0.1 - Use broad peaks:
--narrow_peak false
High duplicates
- Normal for low-input samples
- Pipeline removes by default
- Consider deeper sequencing
Resource Management
Set resource limits
--max_cpus 8 --max_memory '32.GB' --max_time '24.h'Check available resources
# CPUs
nproc
# Memory
free -h
# Disk
df -h .Getting Help
1. Check .nextflow.log for error details 2. Search nf-core Slack: https://nf-co.re/join 3. Open issue on GitHub: https://github.com/nf-core/<pipeline>/issues
#!/usr/bin/env python3
"""
Pre-flight environment validation for nf-core pipelines.
Checks Docker, Nextflow, Java, system resources, and network connectivity.
Run this BEFORE attempting any pipeline execution.
Usage:
python check_environment.py
python check_environment.py --json
"""
import json
import os
import shutil
import subprocess
import sys
from dataclasses import dataclass, field, asdict
from typing import List, Optional
@dataclass
class CheckResult:
"""Result of a single environment check."""
name: str
passed: bool
message: str
details: Optional[str] = None
fix: Optional[str] = None
@dataclass
class EnvironmentReport:
"""Complete environment validation report."""
ready: bool
checks: List[CheckResult] = field(default_factory=list)
recommendations: List[str] = field(default_factory=list)
def to_dict(self):
return {
"ready": self.ready,
"checks": [asdict(c) for c in self.checks],
"recommendations": self.recommendations
}
def check_docker() -> CheckResult:
"""Check Docker availability, daemon status, and permissions."""
if not shutil.which("docker"):
return CheckResult(
name="Docker",
passed=False,
message="Docker not found in PATH",
fix="Install Docker: https://docs.docker.com/get-docker/"
)
try:
result = subprocess.run(
["docker", "info"],
capture_output=True,
text=True,
timeout=15
)
if result.returncode != 0:
stderr_lower = result.stderr.lower()
if "permission denied" in stderr_lower:
return CheckResult(
name="Docker",
passed=False,
message="Docker permission denied",
details="Cannot connect to Docker daemon",
fix="sudo usermod -aG docker $USER && newgrp docker"
)
elif "cannot connect" in stderr_lower or "is the docker daemon running" in stderr_lower:
return CheckResult(
name="Docker",
passed=False,
message="Docker daemon not running",
details=result.stderr[:200] if result.stderr else None,
fix="sudo systemctl start docker"
)
else:
return CheckResult(
name="Docker",
passed=False,
message="Docker error",
details=result.stderr[:200] if result.stderr else None,
fix="Check Docker installation and daemon status"
)
return CheckResult(
name="Docker",
passed=True,
message="Docker is available and running"
)
except subprocess.TimeoutExpired:
return CheckResult(
name="Docker",
passed=False,
message="Docker command timed out",
fix="Check Docker daemon status: sudo systemctl status docker"
)
except Exception as e:
return CheckResult(
name="Docker",
passed=False,
message=f"Docker check failed: {str(e)}"
)
def check_nextflow() -> CheckResult:
"""Check Nextflow installation and version (requires >= 23.04)."""
if not shutil.which("nextflow"):
return CheckResult(
name="Nextflow",
passed=False,
message="Nextflow not found in PATH",
fix="curl -s https://get.nextflow.io | bash && mv nextflow ~/bin/ && export PATH=$HOME/bin:$PATH"
)
try:
result = subprocess.run(
["nextflow", "-version"],
capture_output=True,
text=True,
timeout=30
)
output = result.stdout + result.stderr
version_line = output.strip().split('\n')[0] if output else ""
import re
match = re.search(r'(\d+)\.(\d+)\.(\d+)', version_line)
if match:
major, minor, patch = int(match.group(1)), int(match.group(2)), int(match.group(3))
version_str = f"{major}.{minor}.{patch}"
# Require version >= 23.04
if major > 23 or (major == 23 and minor >= 4):
return CheckResult(
name="Nextflow",
passed=True,
message=f"Nextflow {version_str} installed",
details=version_line
)
else:
return CheckResult(
name="Nextflow",
passed=False,
message=f"Nextflow {version_str} is outdated (requires >= 23.04)",
details=version_line,
fix="nextflow self-update"
)
return CheckResult(
name="Nextflow",
passed=True,
message="Nextflow installed (version unknown)",
details=version_line
)
except subprocess.TimeoutExpired:
return CheckResult(
name="Nextflow",
passed=False,
message="Nextflow command timed out",
fix="Check Nextflow installation"
)
except Exception as e:
return CheckResult(
name="Nextflow",
passed=False,
message=f"Nextflow check failed: {str(e)}"
)
def check_java() -> CheckResult:
"""Check Java version (requires >= 11)."""
if not shutil.which("java"):
return CheckResult(
name="Java",
passed=False,
message="Java not found in PATH",
fix="Install Java 11+: sudo apt install openjdk-11-jdk"
)
try:
result = subprocess.run(
["java", "-version"],
capture_output=True,
text=True,
timeout=10
)
# Java version is typically in stderr
output = result.stderr or result.stdout
import re
match = re.search(r'version "(\d+)', output)
if match:
version = int(match.group(1))
version_line = output.strip().split('\n')[0]
if version >= 11:
return CheckResult(
name="Java",
passed=True,
message=f"Java {version} installed",
details=version_line
)
else:
return CheckResult(
name="Java",
passed=False,
message=f"Java {version} is too old (requires >= 11)",
details=version_line,
fix="Install Java 11+: sudo apt install openjdk-11-jdk"
)
return CheckResult(
name="Java",
passed=True,
message="Java installed",
details=output.strip().split('\n')[0] if output else None
)
except Exception as e:
return CheckResult(
name="Java",
passed=False,
message=f"Java check failed: {str(e)}"
)
def check_resources() -> CheckResult:
"""Check system resources (CPU, memory, disk)."""
try:
# CPU cores
cpu_count = os.cpu_count() or 1
# Memory
mem_gb = 0
try:
# Linux: read from /proc/meminfo
with open('/proc/meminfo', 'r') as f:
for line in f:
if line.startswith('MemTotal:'):
mem_kb = int(line.split()[1])
mem_gb = mem_kb / (1024 * 1024)
break
except (FileNotFoundError, PermissionError):
# macOS: use sysctl
try:
result = subprocess.run(
['sysctl', '-n', 'hw.memsize'],
capture_output=True, text=True, timeout=5
)
if result.returncode == 0:
mem_gb = int(result.stdout.strip()) / (1024**3)
except Exception:
pass
# Disk space (current directory)
disk_gb = 0
try:
statvfs = os.statvfs('.')
disk_gb = (statvfs.f_frsize * statvfs.f_bavail) / (1024**3)
except Exception:
pass
details = f"CPUs: {cpu_count}, Memory: {mem_gb:.1f}GB, Disk: {disk_gb:.1f}GB available"
# Check minimums
warnings = []
if cpu_count < 4:
warnings.append(f"Low CPU count ({cpu_count}). Consider --max_cpus {cpu_count}")
if 0 < mem_gb < 8:
warnings.append(f"Low memory ({mem_gb:.1f}GB). Use --max_memory '{int(mem_gb)}GB'")
if 0 < disk_gb < 50:
warnings.append(f"Low disk space ({disk_gb:.1f}GB). Pipelines need ~100GB for human data")
if warnings:
return CheckResult(
name="Resources",
passed=True,
message="Resources available (with warnings)",
details=details,
fix="; ".join(warnings)
)
return CheckResult(
name="Resources",
passed=True,
message="Sufficient resources available",
details=details
)
except Exception as e:
return CheckResult(
name="Resources",
passed=True, # Don't fail on resource check errors
message=f"Could not fully check resources: {str(e)}"
)
def check_network() -> CheckResult:
"""Check network connectivity to Docker Hub and nf-core."""
try:
import urllib.request
# User-Agent header to avoid 403 from sites that block default Python agent
headers = {'User-Agent': 'nf-core-helper/1.0'}
# Try Docker Hub
try:
req = urllib.request.Request("https://hub.docker.com", headers=headers)
urllib.request.urlopen(req, timeout=10)
docker_hub_ok = True
except Exception:
docker_hub_ok = False
# Try nf-core (for pipeline downloads)
try:
req = urllib.request.Request("https://nf-co.re", headers=headers)
urllib.request.urlopen(req, timeout=10)
nfcore_ok = True
except Exception:
nfcore_ok = False
if docker_hub_ok and nfcore_ok:
return CheckResult(
name="Network",
passed=True,
message="Network connectivity OK (Docker Hub & nf-core reachable)"
)
elif docker_hub_ok:
return CheckResult(
name="Network",
passed=True,
message="Docker Hub reachable (nf-core.re not reachable)",
details="Pipeline downloads may still work via GitHub"
)
else:
return CheckResult(
name="Network",
passed=False,
message="Cannot reach Docker Hub",
fix="Check network connection. Containers require Docker Hub access."
)
except Exception as e:
return CheckResult(
name="Network",
passed=False,
message=f"Network check failed: {str(e)}",
fix="Check network connection and proxy settings"
)
def run_all_checks() -> EnvironmentReport:
"""Run all environment checks and return comprehensive report."""
checks = [
check_docker(),
check_nextflow(),
check_java(),
check_resources(),
check_network(),
]
# Critical checks that must pass
critical_checks = ["Docker", "Nextflow", "Java"]
ready = all(c.passed for c in checks if c.name in critical_checks)
# Build recommendations
recommendations = []
for check in checks:
if not check.passed and check.fix:
recommendations.append(f"{check.name}: {check.fix}")
elif check.passed and check.fix: # Warnings
recommendations.append(f"{check.name} (warning): {check.fix}")
return EnvironmentReport(
ready=ready,
checks=checks,
recommendations=recommendations
)
def print_report(report: EnvironmentReport):
"""Print human-readable report to stdout."""
print("\n" + "=" * 50)
print(" nf-core Environment Check")
print("=" * 50 + "\n")
for check in report.checks:
status = "\033[92m[PASS]\033[0m" if check.passed else "\033[91m[FAIL]\033[0m"
print(f"{status} {check.name}: {check.message}")
if check.details:
print(f" {check.details}")
if not check.passed and check.fix:
print(f" \033[93mFix:\033[0m {check.fix}")
elif check.passed and check.fix: # Warning
print(f" \033[93mWarning:\033[0m {check.fix}")
print()
if report.ready:
print("\033[92m✓ Environment is READY for nf-core pipelines.\033[0m")
else:
print("\033[91m✗ Environment is NOT READY. Please address the issues above.\033[0m")
if report.recommendations:
print("\n--- Recommendations ---")
for i, rec in enumerate(report.recommendations, 1):
print(f" {i}. {rec}")
print()
def main():
import argparse
parser = argparse.ArgumentParser(
description="Check environment for nf-core pipeline execution",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python check_environment.py # Human-readable output
python check_environment.py --json # JSON output for parsing
"""
)
parser.add_argument("--json", action="store_true",
help="Output results as JSON")
args = parser.parse_args()
report = run_all_checks()
if args.json:
print(json.dumps(report.to_dict(), indent=2))
else:
print_report(report)
sys.exit(0 if report.ready else 1)
if __name__ == "__main__":
main()
# Organism to Genome Mapping for nf-core Pipelines
# Maps organism names (as they appear in GEO/SRA) to iGenomes keys
organisms:
# Human
"Homo sapiens":
genome: "GRCh38"
taxid: 9606
aliases: ["human", "hg38", "GRCh38"]
notes: "Primary human reference genome"
"Homo sapiens (legacy)":
genome: "GRCh37"
taxid: 9606
aliases: ["hg19", "GRCh37"]
notes: "Legacy human reference, still used for some clinical data"
# Mouse
"Mus musculus":
genome: "GRCm39"
taxid: 10090
aliases: ["mouse", "mm39", "GRCm39"]
notes: "Current mouse reference genome"
"Mus musculus (legacy)":
genome: "GRCm38"
taxid: 10090
aliases: ["mm10", "GRCm38"]
notes: "Legacy mouse reference"
# Yeast
"Saccharomyces cerevisiae":
genome: "R64-1-1"
taxid: 4932
aliases: ["yeast", "sacCer3", "S288C", "budding yeast"]
notes: "S288C reference strain"
# Fruit fly
"Drosophila melanogaster":
genome: "BDGP6"
taxid: 7227
aliases: ["fly", "dm6", "fruit fly", "Dmel"]
notes: "Berkeley Drosophila Genome Project release 6"
# Worm
"Caenorhabditis elegans":
genome: "WBcel235"
taxid: 6239
aliases: ["worm", "ce11", "C. elegans", "Cele"]
notes: "WormBase reference"
# Zebrafish
"Danio rerio":
genome: "GRCz11"
taxid: 7955
aliases: ["zebrafish", "danRer11", "Drer"]
notes: "Genome Reference Consortium Zebrafish Build 11"
# Arabidopsis
"Arabidopsis thaliana":
genome: "TAIR10"
taxid: 3702
aliases: ["arabidopsis", "thale cress", "Atha"]
notes: "The Arabidopsis Information Resource v10"
# Rat
"Rattus norvegicus":
genome: "Rnor_6.0"
taxid: 10116
aliases: ["rat", "rn6", "Rnor"]
notes: "Rnor 6.0 reference"
# Chicken
"Gallus gallus":
genome: "GRCg6a"
taxid: 9031
aliases: ["chicken", "galGal6", "Ggal"]
notes: "Genome Reference Consortium Chicken Build 6a"
# Pig
"Sus scrofa":
genome: "Sscrofa11.1"
taxid: 9823
aliases: ["pig", "susScr11", "Sscr"]
notes: "Swine genome assembly 11.1"
# Cow
"Bos taurus":
genome: "ARS-UCD1.2"
taxid: 9913
aliases: ["cow", "bosTau9", "cattle", "Btau"]
notes: "USDA ARS assembly"
# Dog
"Canis lupus familiaris":
genome: "CanFam3.1"
taxid: 9615
aliases: ["dog", "canFam3", "Clup"]
notes: "Broad Institute CanFam3.1"
# Frog
"Xenopus tropicalis":
genome: "JGI_4.2"
taxid: 8364
aliases: ["frog", "xenTro9", "Xtro"]
notes: "JGI assembly version 4.2"
# Maize/Corn
"Zea mays":
genome: "Zm-B73-REFERENCE-NAM-5.0"
taxid: 4577
aliases: ["maize", "corn", "Zmay"]
notes: "B73 reference genome v5"
# Rice
"Oryza sativa":
genome: "IRGSP-1.0"
taxid: 39947
aliases: ["rice", "Osat"]
notes: "International Rice Genome Sequencing Project"
# E. coli (common bacterial model)
"Escherichia coli":
genome: null
taxid: 562
aliases: ["E. coli", "Ecol"]
notes: "Use specific strain reference; K-12 MG1655 common"
# Fission yeast
"Schizosaccharomyces pombe":
genome: "ASM294v2"
taxid: 4896
aliases: ["fission yeast", "S. pombe", "Spom"]
notes: "PomBase reference"
# Pipeline mapping based on library strategy
pipeline_suggestions:
"RNA-SEQ": "rnaseq"
"ATAC-SEQ": "atacseq"
"CHIP-SEQ": "chipseq"
"WGS": "sarek"
"WXS": "sarek"
"EXOME": "sarek"
"AMPLICON": "ampliseq"
"BISULFITE-SEQ": "methylseq"
"HI-C": "hic"
"MIRNA-SEQ": "smrnaseq"
"RRBS": "methylseq"
name: atacseq
version: "2.1.2"
description: "Chromatin accessibility analysis and peak calling"
# Documentation and source - NOTE: Update version in URLs when upgrading pipeline
urls:
documentation: "https://nf-co.re/atacseq/{version}/"
parameters: "https://nf-co.re/atacseq/{version}/parameters/"
output_docs: "https://nf-co.re/atacseq/{version}/docs/output/"
github: "https://github.com/nf-core/atacseq"
releases: "https://github.com/nf-core/atacseq/releases"
data_types:
- ATAC-seq
- chromatin accessibility
- open chromatin
detection_hints:
filename:
- atac
- atacseq
- chromatin
- accessibility
directory:
- atac
- atacseq
- chromatin
- epigenome
- epigenetics
samplesheet:
input_types:
- fastq
columns:
- name: sample
required: true
type: string
inference: filename
description: "Condition/group identifier (replicates share same name)"
- name: fastq_1
required: true
type: path
inference: auto
description: "Absolute path to R1 FASTQ"
- name: fastq_2
required: true
type: path
inference: auto
description: "Absolute path to R2 FASTQ (paired-end required)"
- name: replicate
required: true
type: integer
default: 1
inference: filename
description: "Replicate number (integer)"
decision_points:
- parameter: genome
prompt: "Which reference genome matches your organism?"
options:
- value: GRCh38
label: "Human GRCh38/hg38 (recommended)"
description: "Latest human reference"
- value: GRCh37
label: "Human GRCh37/hg19 (legacy)"
description: "Older human reference"
- value: mm10
label: "Mouse mm10"
description: "Mouse reference genome"
default: GRCh38
recommendation: "Default to GRCh38 for human samples"
- parameter: read_length
prompt: "What is the read length of your sequencing data?"
options:
- value: 50
label: "50 bp"
description: "Short reads"
- value: 75
label: "75 bp"
description: "Standard length"
- value: 100
label: "100 bp"
description: "Common for modern sequencers"
- value: 150
label: "150 bp"
description: "Long reads"
default: 50
recommendation: "Check FASTQ files or sequencing report for exact length"
- parameter: narrow_peak
prompt: "What type of peaks are you expecting?"
options:
- value: "true"
label: "Narrow peaks (default for ATAC-seq)"
description: "Standard ATAC-seq open chromatin regions"
- value: "false"
label: "Broad peaks"
description: "For histone marks or broader accessibility regions"
default: "true"
recommendation: "Use narrow peaks for standard ATAC-seq"
test_profile:
command: "nextflow run nf-core/atacseq -r 2.1.2 -profile test,docker --outdir test_atacseq"
duration: "15 minutes"
success_indicators:
- "test_atacseq/multiqc/multiqc_report.html"
log_pattern: "Pipeline completed successfully"
run_command:
template: |
nextflow run nf-core/atacseq \
-r 2.1.2 \
-profile docker \
--input {samplesheet} \
--outdir {outdir} \
--genome {genome} \
--read_length {read_length} \
-resume
outputs:
primary:
- path: "bwa/mergedLibrary/*.mLb.mkD.sorted.bam"
description: "Filtered, deduplicated alignments"
- path: "bwa/mergedLibrary/bigwig/*.bigWig"
description: "Coverage tracks for genome browsers"
- path: "macs2/narrowPeak/*.narrowPeak"
description: "Peak calls (BED format)"
- path: "macs2/narrowPeak/consensus/consensus_peaks.bed"
description: "Consensus peaks across replicates"
validation:
- file: "multiqc/multiqc_report.html"
check: exists
description: "QC report must exist"
- file: "macs2/narrowPeak"
check: exists
description: "Peak calls directory"
quality_metrics:
- name: mapped_reads
good: ">80%"
acceptable: "60-80%"
poor: "<60%"
- name: mitochondrial
good: "<20%"
acceptable: "20-40%"
poor: ">40%"
- name: duplicates
good: "<30%"
acceptable: "30-50%"
poor: ">50%"
- name: frip
good: ">30%"
acceptable: "15-30%"
poor: "<15%"
- name: tss_enrichment
good: ">6"
acceptable: "4-6"
poor: "<4"
resources:
min_memory: "8.GB"
recommended_memory: "32.GB"
min_cpus: 4
recommended_cpus: 8
disk_space: "100.GB"
troubleshooting:
- error: "Low FRiP score"
fix: "Check library complexity in plotFingerprint. May indicate over-transposition or low quality"
- error: "Few peaks called"
fix: "Lower threshold with --macs_qvalue 0.1 or use --narrow_peak false for broader peaks"
- error: "High duplicates"
fix: "Normal for low-input samples. Pipeline removes by default. Consider deeper sequencing"
- error: "High mitochondrial reads"
fix: "Sample quality issue. Pipeline filters mito by default (--keep_mito false)"
replicate_patterns:
- "_rep(\\d+)"
- "_R(\\d+)_"
- "_(\\d+)$"
- "_replicate(\\d+)"
name: rnaseq
version: "3.22.2"
description: "Gene expression quantification and differential expression analysis"
# Documentation and source - NOTE: Update version in URLs when upgrading pipeline
urls:
documentation: "https://nf-co.re/rnaseq/{version}/"
parameters: "https://nf-co.re/rnaseq/{version}/parameters/"
output_docs: "https://nf-co.re/rnaseq/{version}/docs/output/"
github: "https://github.com/nf-core/rnaseq"
releases: "https://github.com/nf-core/rnaseq/releases"
data_types:
- RNA-seq
- mRNA-seq
- bulk RNA-seq
detection_hints:
filename:
- rna
- rnaseq
- mrna
- expression
directory:
- rnaseq
- rna
- expression
- transcriptome
samplesheet:
input_types:
- fastq
columns:
- name: sample
required: true
type: string
inference: filename
description: "Sample identifier"
- name: fastq_1
required: true
type: path
inference: auto
description: "Absolute path to R1 FASTQ"
- name: fastq_2
required: false
type: path
inference: auto
description: "Absolute path to R2 FASTQ (empty for single-end)"
- name: strandedness
required: true
type: enum
allowed:
- auto
- forward
- reverse
- unstranded
default: "auto"
inference: default
description: "Library strandedness (auto recommended)"
decision_points:
- parameter: genome
prompt: "Which reference genome matches your organism?"
options:
- value: GRCh38
label: "Human GRCh38/hg38 (recommended for human)"
description: "Latest human reference assembly"
- value: GRCh37
label: "Human GRCh37/hg19 (legacy)"
description: "Older human reference for compatibility"
- value: mm10
label: "Mouse mm10/GRCm38"
description: "Mouse reference genome"
- value: BDGP6
label: "Drosophila BDGP6"
description: "Fruit fly reference"
default: GRCh38
recommendation: "Default to GRCh38 for human samples"
- parameter: aligner
prompt: "Which alignment strategy would you prefer?"
options:
- value: star_salmon
label: "STAR + Salmon (recommended)"
description: "Most accurate, standard for differential expression"
- value: star_rsem
label: "STAR + RSEM"
description: "Better for isoform-level quantification"
- value: hisat2
label: "HISAT2"
description: "Lower memory requirements, faster"
default: star_salmon
recommendation: "Use star_salmon unless memory-constrained or need isoforms"
test_profile:
command: "nextflow run nf-core/rnaseq -r 3.22.2 -profile test,docker --outdir test_rnaseq"
duration: "15 minutes"
success_indicators:
- "test_rnaseq/multiqc/multiqc_report.html"
log_pattern: "Pipeline completed successfully"
run_command:
template: |
nextflow run nf-core/rnaseq \
-r 3.22.2 \
-profile docker \
--input {samplesheet} \
--outdir {outdir} \
--genome {genome} \
--aligner {aligner} \
-resume
outputs:
primary:
- path: "star_salmon/salmon.merged.gene_counts.tsv"
description: "Raw gene counts for DESeq2/edgeR"
- path: "star_salmon/salmon.merged.gene_tpm.tsv"
description: "TPM normalized expression values"
- path: "star_salmon/*.bam"
description: "Aligned reads"
validation:
- file: "multiqc/multiqc_report.html"
check: exists
description: "QC report must exist"
- file: "star_salmon/salmon.merged.gene_counts.tsv"
check: non_empty
description: "Count matrix must have data"
resources:
min_memory: "8.GB"
recommended_memory: "32.GB"
min_cpus: 4
recommended_cpus: 8
disk_space: "100.GB"
troubleshooting:
- error: "STAR index fails"
fix: "Increase memory with --max_memory '64.GB' or provide pre-built --star_index"
- error: "Low alignment rate"
fix: "Verify genome matches species; check FastQC for adapter contamination"
- error: "Strandedness detection fails"
fix: "Specify explicitly with --strandedness reverse (or forward/unstranded)"
name: sarek
version: "3.7.1"
description: "Variant calling for WGS/WES data (germline and somatic)"
# Documentation and source - NOTE: Update version in URLs when upgrading pipeline
urls:
documentation: "https://nf-co.re/sarek/{version}/"
parameters: "https://nf-co.re/sarek/{version}/parameters/"
output_docs: "https://nf-co.re/sarek/{version}/docs/output/"
github: "https://github.com/nf-core/sarek"
releases: "https://github.com/nf-core/sarek/releases"
data_types:
- WGS
- WES
- whole genome sequencing
- whole exome sequencing
- tumor-normal
- germline
- somatic
detection_hints:
filename:
- tumor
- normal
- germline
- wgs
- wes
- exome
- dna
- variant
directory:
- variant
- wgs
- wes
- exome
- germline
- somatic
samplesheet:
input_types:
- fastq
- bam
- cram
columns:
- name: patient
required: true
type: string
inference: filename
description: "Patient/subject identifier for grouping samples"
- name: sample
required: true
type: string
inference: filename
description: "Sample identifier (e.g., tumor, normal)"
- name: lane
required: false
type: string
default: "L001"
inference: filename
description: "Sequencing lane"
- name: fastq_1
required: true
type: path
inference: auto
condition: "input_type == 'fastq'"
description: "Absolute path to R1 FASTQ"
- name: fastq_2
required: false
type: path
inference: auto
condition: "input_type == 'fastq'"
description: "Absolute path to R2 FASTQ"
- name: bam
required: true
type: path
inference: auto
condition: "input_type in ['bam', 'cram']"
description: "Absolute path to BAM/CRAM file"
- name: bai
required: true
type: path
inference: auto
condition: "input_type in ['bam', 'cram']"
description: "Absolute path to BAM/CRAM index"
- name: status
required: false
type: integer
allowed:
- 0
- 1
default: 0
inference: filename
description: "0=normal, 1=tumor (critical for somatic calling)"
decision_points:
- parameter: genome
prompt: "Which reference genome should be used?"
options:
- value: GRCh38
label: "Human GRCh38/hg38 (recommended)"
description: "Latest human reference with most annotation support"
- value: GRCh37
label: "Human GRCh37/hg19 (legacy)"
description: "For compatibility with older datasets"
- value: mm10
label: "Mouse mm10"
description: "Mouse reference genome"
default: GRCh38
recommendation: "Default to GRCh38 for human data"
- parameter: tools
prompt: "What type of variant calling do you need?"
options:
- value: "haplotypecaller,snpeff"
label: "Germline variants (single samples)"
description: "For finding inherited variants in normal samples"
condition: "no tumor samples detected"
- value: "mutect2,strelka,snpeff"
label: "Somatic variants (tumor-normal pairs)"
description: "For finding cancer mutations with matched normal"
condition: "tumor-normal pairs detected"
- value: "haplotypecaller,deepvariant,snpeff"
label: "Germline with DeepVariant"
description: "Higher accuracy germline calling (requires GPU)"
- value: "mutect2,manta,snpeff"
label: "Somatic with structural variants"
description: "Comprehensive tumor analysis including SVs"
default: "haplotypecaller,snpeff"
recommendation: "Use somatic tools if tumor/normal pairs detected, otherwise germline"
- parameter: wes
prompt: "Is this whole exome sequencing (WES) data?"
options:
- value: "false"
label: "No - Whole Genome Sequencing (WGS)"
description: "Full genome coverage"
- value: "true"
label: "Yes - Whole Exome Sequencing (WES)"
description: "Requires --intervals BED file"
default: "false"
recommendation: "If WES, user must provide intervals BED file"
test_profile:
command: "nextflow run nf-core/sarek -r 3.7.1 -profile test,docker --outdir test_sarek"
duration: "20 minutes"
success_indicators:
- "test_sarek/multiqc/multiqc_report.html"
log_pattern: "Pipeline completed successfully"
run_command:
template: |
nextflow run nf-core/sarek \
-r 3.7.1 \
-profile docker \
--input {samplesheet} \
--outdir {outdir} \
--genome {genome} \
--tools {tools} \
-resume
wes_template: |
nextflow run nf-core/sarek \
-r 3.7.1 \
-profile docker \
--input {samplesheet} \
--outdir {outdir} \
--genome {genome} \
--tools {tools} \
--wes \
--intervals {intervals} \
-resume
outputs:
primary:
- path: "preprocessing/recalibrated/*.recal.bam"
description: "Analysis-ready BAM files"
- path: "variant_calling/*/*.vcf.gz"
description: "Variant call files"
- path: "annotation/snpeff/*.ann.vcf.gz"
description: "Annotated variants"
validation:
- file: "multiqc/multiqc_report.html"
check: exists
description: "QC report must exist"
- file: "preprocessing/recalibrated"
check: exists
description: "Recalibrated BAMs directory"
resources:
min_memory: "16.GB"
recommended_memory: "64.GB"
wgs_memory: "128.GB"
min_cpus: 4
recommended_cpus: 16
disk_space: "500.GB"
troubleshooting:
- error: "BQSR fails"
fix: "Check known sites available for genome. Skip with --skip_bqsr for non-standard references"
- error: "Mutect2 no variants"
fix: "Verify tumor/normal pairing in samplesheet (check status column: 0=normal, 1=tumor)"
- error: "Out of memory"
fix: "--max_memory '128.GB' for WGS data"
- error: "DeepVariant GPU issues"
fix: "Ensure NVIDIA Docker runtime configured, or use CPU mode"
tumor_normal_keywords:
tumor:
- tumor
- tumour
- met
- metastasis
- primary
- cancer
- malignant
normal:
- normal
- germline
- blood
- pbmc
- control
- healthy
- matched
#!/usr/bin/env python3
"""
Auto-detect appropriate nf-core pipeline from data directory.
Analyzes filenames, directory structure, and file content hints to suggest
the most appropriate pipeline for the data.
Usage:
python detect_data_type.py /path/to/data
python detect_data_type.py /path/to/data --json
"""
import argparse
import json
import os
import sys
from pathlib import Path
from typing import Dict, List, Tuple
import yaml
def load_all_pipeline_configs() -> Dict[str, Dict]:
"""Load all pipeline configurations."""
config_dir = Path(__file__).parent / "config" / "pipelines"
configs = {}
for config_file in config_dir.glob("*.yaml"):
if config_file.stem.startswith("_"):
continue
with open(config_file) as f:
configs[config_file.stem] = yaml.safe_load(f)
return configs
def scan_directory(directory: str) -> Dict:
"""Scan directory and collect file information."""
info = {
'fastq_count': 0,
'bam_count': 0,
'cram_count': 0,
'filenames': [],
'directories': [],
'total_size_gb': 0,
}
directory = os.path.abspath(directory)
for root, dirs, files in os.walk(directory):
# Collect directory names
rel_root = os.path.relpath(root, directory)
if rel_root != '.':
info['directories'].append(rel_root.lower())
for filename in files:
filename_lower = filename.lower()
# Count file types
if any(filename_lower.endswith(ext) for ext in ['.fastq.gz', '.fq.gz', '.fastq', '.fq']):
info['fastq_count'] += 1
elif filename_lower.endswith('.bam'):
info['bam_count'] += 1
elif filename_lower.endswith('.cram'):
info['cram_count'] += 1
# Collect filenames for pattern matching
info['filenames'].append(filename_lower)
# Sum file sizes
try:
size = os.path.getsize(os.path.join(root, filename))
info['total_size_gb'] += size / (1024**3)
except Exception:
pass
return info
def calculate_pipeline_scores(scan_info: Dict, configs: Dict) -> Dict[str, Dict]:
"""Calculate confidence scores for each pipeline."""
scores = {}
for pipeline_name, config in configs.items():
score = 0
matches = []
# Check detection hints
hints = config.get('detection_hints', {})
# Filename hints
filename_hints = hints.get('filename', [])
for hint in filename_hints:
hint_lower = hint.lower()
for filename in scan_info['filenames']:
if hint_lower in filename:
score += 10
matches.append(f"Filename contains '{hint}'")
break
# Directory hints
directory_hints = hints.get('directory', [])
for hint in directory_hints:
hint_lower = hint.lower()
for dirname in scan_info['directories']:
if hint_lower in dirname:
score += 15
matches.append(f"Directory contains '{hint}'")
break
# Check data type compatibility
data_types = config.get('data_types', [])
input_types = config.get('samplesheet', {}).get('input_types', ['fastq'])
# Prefer pipelines that support the available file types
if 'fastq' in input_types and scan_info['fastq_count'] > 0:
score += 5
if 'bam' in input_types and scan_info['bam_count'] > 0:
score += 5
if 'cram' in input_types and scan_info['cram_count'] > 0:
score += 5
# Pipeline-specific boosts
if pipeline_name == 'sarek':
# Check for tumor/normal indicators
tumor_indicators = ['tumor', 'tumour', 'cancer', 'met', 'primary']
normal_indicators = ['normal', 'germline', 'blood', 'control']
has_tumor = any(ind in ' '.join(scan_info['filenames']) for ind in tumor_indicators)
has_normal = any(ind in ' '.join(scan_info['filenames']) for ind in normal_indicators)
if has_tumor or has_normal:
score += 20
if has_tumor:
matches.append("Found tumor sample indicators")
if has_normal:
matches.append("Found normal sample indicators")
# DNA-related hints
dna_hints = ['wgs', 'wes', 'exome', 'dna', 'variant', 'snp', 'indel']
for hint in dna_hints:
if hint in ' '.join(scan_info['filenames'] + scan_info['directories']):
score += 10
matches.append(f"Found DNA/variant indicator: '{hint}'")
break
elif pipeline_name == 'rnaseq':
# RNA-related hints
rna_hints = ['rna', 'rnaseq', 'mrna', 'expression', 'transcript', 'counts']
for hint in rna_hints:
if hint in ' '.join(scan_info['filenames'] + scan_info['directories']):
score += 15
matches.append(f"Found RNA indicator: '{hint}'")
break
elif pipeline_name == 'atacseq':
# ATAC-related hints
atac_hints = ['atac', 'atacseq', 'chromatin', 'accessibility', 'peak', 'macs']
for hint in atac_hints:
if hint in ' '.join(scan_info['filenames'] + scan_info['directories']):
score += 20
matches.append(f"Found ATAC-seq indicator: '{hint}'")
break
scores[pipeline_name] = {
'score': score,
'matches': matches,
'description': config.get('description', ''),
'version': config.get('version', 'unknown'),
}
return scores
def detect_pipeline(directory: str) -> Tuple[str, Dict]:
"""
Detect the most appropriate pipeline for the data.
Args:
directory: Path to data directory
Returns:
Tuple of (recommended_pipeline, all_scores)
"""
if not os.path.isdir(directory):
raise ValueError(f"Not a directory: {directory}")
configs = load_all_pipeline_configs()
scan_info = scan_directory(directory)
# Check if any sequencing files found
total_files = scan_info['fastq_count'] + scan_info['bam_count'] + scan_info['cram_count']
if total_files == 0:
raise ValueError(f"No sequencing files (FASTQ/BAM/CRAM) found in {directory}")
scores = calculate_pipeline_scores(scan_info, configs)
# Find highest scoring pipeline
best_pipeline = max(scores.keys(), key=lambda k: scores[k]['score'])
return best_pipeline, scores
def print_results(
directory: str,
recommended: str,
scores: Dict,
scan_info: Dict,
output_json: bool = False
):
"""Print detection results."""
if output_json:
result = {
'recommended': recommended,
'scores': scores,
'scan_info': {
'fastq_count': scan_info['fastq_count'],
'bam_count': scan_info['bam_count'],
'cram_count': scan_info['cram_count'],
'total_size_gb': round(scan_info['total_size_gb'], 2),
}
}
print(json.dumps(result, indent=2))
return
print("\n" + "=" * 50)
print(" nf-core Pipeline Detection")
print("=" * 50)
print(f"\nDirectory: {directory}")
print(f"Files found: {scan_info['fastq_count']} FASTQ, "
f"{scan_info['bam_count']} BAM, {scan_info['cram_count']} CRAM")
print(f"Total size: {scan_info['total_size_gb']:.1f} GB")
print("\n--- Pipeline Scores ---")
sorted_pipelines = sorted(scores.keys(), key=lambda k: scores[k]['score'], reverse=True)
for pipeline in sorted_pipelines:
info = scores[pipeline]
indicator = "→" if pipeline == recommended else " "
print(f"\n{indicator} {pipeline} (score: {info['score']})")
print(f" {info['description']}")
if info['matches']:
print(f" Matches: {', '.join(info['matches'][:3])}")
print(f"\n{'=' * 50}")
print(f"\n\033[92mRecommended: {recommended}\033[0m")
print(f"Version: {scores[recommended]['version']}")
# Print suggested next steps
print(f"\n--- Next Steps ---")
print(f"1. Run environment check:")
print(f" python scripts/check_environment.py")
print(f"\n2. Run test profile:")
config = load_all_pipeline_configs().get(recommended, {})
test_cmd = config.get('test_profile', {}).get('command', '')
if test_cmd:
print(f" {test_cmd}")
print(f"\n3. Generate samplesheet:")
print(f" python scripts/generate_samplesheet.py {directory} {recommended}")
def main():
parser = argparse.ArgumentParser(
description='Detect appropriate nf-core pipeline for data',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s ./data
%(prog)s ./fastqs --json
"""
)
parser.add_argument('directory', help='Directory containing sequencing data')
parser.add_argument('--json', action='store_true', help='Output as JSON')
args = parser.parse_args()
try:
scan_info = scan_directory(args.directory)
recommended, scores = detect_pipeline(args.directory)
print_results(args.directory, recommended, scores, scan_info, args.json)
sys.exit(0)
except ValueError as e:
if args.json:
print(json.dumps({'error': str(e)}))
else:
print(f"Error: {e}")
sys.exit(1)
except Exception as e:
if args.json:
print(json.dumps({'error': str(e)}))
else:
print(f"Error: {e}")
sys.exit(1)
if __name__ == '__main__':
main()
#!/usr/bin/env python3
"""
Enhanced nf-core samplesheet generator.
Features:
- FASTQ, BAM, and CRAM support
- Tumor/normal status inference for sarek
- Robust R1/R2 matching with scoring
- Pre-write validation with clear error messages
- Pipeline config-driven column generation
Usage:
python generate_samplesheet.py /path/to/data rnaseq -o samplesheet.csv
python generate_samplesheet.py /path/to/bams sarek --input-type bam
python generate_samplesheet.py --validate samplesheet.csv rnaseq
"""
import argparse
import os
import sys
from pathlib import Path
from typing import Dict, List, Optional, Tuple
import yaml
# Add parent directory to path for utils import
sys.path.insert(0, str(Path(__file__).parent))
from utils.file_discovery import discover_files, detect_input_type, find_index_file
from utils.sample_inference import (
extract_sample_info,
infer_tumor_normal_status,
match_read_pairs,
extract_replicate_number
)
from utils.validators import validate_samplesheet, ValidationResult
def load_pipeline_config(pipeline: str) -> Dict:
"""Load pipeline configuration from YAML."""
config_dir = Path(__file__).parent / "config" / "pipelines"
config_file = config_dir / f"{pipeline}.yaml"
if not config_file.exists():
available = [f.stem for f in config_dir.glob("*.yaml") if not f.stem.startswith("_")]
raise ValueError(f"Unknown pipeline '{pipeline}'. Available: {', '.join(available)}")
with open(config_file) as f:
return yaml.safe_load(f)
def generate_samplesheet(
input_dir: str,
pipeline: str,
output_file: Optional[str] = None,
input_type: str = "auto",
single_end: bool = False,
interactive: bool = True
) -> Tuple[Optional[str], ValidationResult]:
"""
Generate samplesheet for specified pipeline.
Args:
input_dir: Directory containing sequencing files
pipeline: Pipeline name (rnaseq, sarek, atacseq)
output_file: Output CSV path (default: samplesheet_{pipeline}.csv)
input_type: File type (auto, fastq, bam, cram)
single_end: Suppress pairing warnings for single-end data
interactive: Prompt for missing info
Returns:
Tuple of (output_path, validation_result)
"""
config = load_pipeline_config(pipeline)
samplesheet_config = config.get("samplesheet", {})
supported_types = samplesheet_config.get("input_types", ["fastq"])
# Determine input type
if input_type == "auto":
input_type = detect_input_type(input_dir)
print(f"Auto-detected input type: {input_type.upper()}")
if input_type not in supported_types:
return None, ValidationResult(
valid=False,
errors=[f"Pipeline '{pipeline}' does not support {input_type.upper()} input. "
f"Supported: {supported_types}"]
)
# Discover files
try:
files = discover_files(input_dir, input_type)
except ValueError as e:
return None, ValidationResult(valid=False, errors=[str(e)])
if not files:
return None, ValidationResult(
valid=False,
errors=[f"No {input_type.upper()} files found in {input_dir}"],
suggestions=[
"Check directory path is correct",
"Verify file extensions (.fastq.gz, .fq.gz, .bam, .cram)",
f"Run: ls {input_dir}"
]
)
print(f"Found {len(files)} {input_type.upper()} files")
# Process based on input type
if input_type == "fastq":
rows = _process_fastq_files(files, config, single_end)
else:
rows = _process_alignment_files(files, config, input_type)
if not rows:
return None, ValidationResult(
valid=False,
errors=["Could not generate any samplesheet rows from files"]
)
print(f"Generated {len(rows)} samplesheet rows")
# Pipeline-specific processing
if pipeline == "sarek":
rows = _process_sarek_samples(rows, interactive)
elif pipeline == "atacseq":
rows = _process_atacseq_samples(rows)
# Validate before writing
validation = validate_samplesheet(rows, pipeline, config)
if not validation.valid:
print("\nValidation errors:")
for error in validation.errors:
print(f" - {error}")
if interactive:
response = input("\nProceed anyway? [y/N]: ").strip().lower()
if response != 'y':
return None, validation
elif validation.warnings:
print("\nWarnings:")
for warning in validation.warnings:
print(f" - {warning}")
# Determine output path
output_path = output_file or f"samplesheet_{pipeline}.csv"
# Write samplesheet
_write_samplesheet(rows, config, output_path)
print(f"\nGenerated: {output_path}")
print(f" Pipeline: {pipeline} v{config.get('version', 'unknown')}")
print(f" Samples: {len(set(r.get('sample', r.get('patient', '')) for r in rows))}")
print(f" Rows: {len(rows)}")
# Preview
_print_preview(rows, config)
return output_path, validation
def _process_fastq_files(files, config: Dict, single_end: bool) -> List[Dict]:
"""Process FASTQ files into samplesheet rows."""
pairs = match_read_pairs(files)
if not pairs:
return []
# Check for unpaired files
unpaired = [k for k, v in pairs.items() if v.get('r1') and not v.get('r2')]
if unpaired and not single_end:
print(f"\nNote: {len(unpaired)} samples appear to be single-end (no R2)")
rows = []
columns = config.get("samplesheet", {}).get("columns", [])
for sample_key, pair_info in sorted(pairs.items()):
if not pair_info.get('r1'):
continue # Skip entries with only R2
info = pair_info.get('info', {})
row = {
'sample': info.get('sample', sample_key),
'fastq_1': str(Path(pair_info['r1']).absolute()),
'fastq_2': str(Path(pair_info['r2']).absolute()) if pair_info.get('r2') else '',
}
# Add additional info from filename
if 'patient' in [c['name'] for c in columns]:
row['patient'] = info.get('patient', info.get('sample', sample_key))
if 'lane' in [c['name'] for c in columns]:
row['lane'] = info.get('lane', 'L001')
# Apply defaults from config
for col in columns:
if col['name'] not in row and 'default' in col:
row[col['name']] = col['default']
rows.append(row)
return rows
def _process_alignment_files(files, config: Dict, input_type: str) -> List[Dict]:
"""Process BAM/CRAM files into samplesheet rows."""
rows = []
columns = config.get("samplesheet", {}).get("columns", [])
for file_info in files:
# Find index file
index_path = find_index_file(file_info.path)
info = extract_sample_info(file_info.path)
row = {
'sample': info.get('sample', file_info.stem),
'bam': str(Path(file_info.path).absolute()),
'bai': str(Path(index_path).absolute()) if index_path else '',
}
# Add patient for sarek
if 'patient' in [c['name'] for c in columns]:
row['patient'] = info.get('patient', info.get('sample', file_info.stem))
# Apply defaults
for col in columns:
if col['name'] not in row and 'default' in col:
row[col['name']] = col['default']
# Warn if no index found
if not index_path:
print(f" Warning: No index found for {file_info.name}")
rows.append(row)
return rows
def _process_sarek_samples(rows: List[Dict], interactive: bool) -> List[Dict]:
"""Process sarek samples: infer and confirm tumor/normal status."""
# Auto-infer status from sample names
for row in rows:
sample_name = row.get('sample', '')
inferred = infer_tumor_normal_status(sample_name)
if inferred is not None:
row['status'] = inferred
# Report inference results
inferred_tumor = [r for r in rows if r.get('status') == 1]
inferred_normal = [r for r in rows if r.get('status') == 0]
unknown = [r for r in rows if 'status' not in r]
if inferred_tumor or inferred_normal:
print(f"\nTumor/normal inference:")
print(f" Tumor samples: {len(inferred_tumor)}")
print(f" Normal samples: {len(inferred_normal)}")
# Handle unknown samples
if unknown and interactive:
print(f"\n{len(unknown)} sample(s) with unknown status:")
for r in unknown:
print(f" - {r.get('sample')}")
print("\nSpecify status for each (0=normal, 1=tumor, Enter=skip):")
for r in unknown:
response = input(f" {r.get('sample')} [0/1/Enter]: ").strip()
if response in ['0', '1']:
r['status'] = int(response)
else:
r['status'] = 0 # Default to normal
print(f" Defaulting to normal (0)")
elif unknown:
# Non-interactive: default to normal
for r in unknown:
r['status'] = 0
return rows
def _process_atacseq_samples(rows: List[Dict]) -> List[Dict]:
"""Process ATAC-seq samples: ensure replicate numbers."""
# Group by sample name
sample_counts = {}
for row in rows:
sample = row.get('sample', '')
if sample not in sample_counts:
sample_counts[sample] = 0
sample_counts[sample] += 1
# Assign replicate numbers if not present
sample_rep = {}
for row in rows:
sample = row.get('sample', '')
if 'replicate' not in row or not row['replicate']:
# Try to extract from filename
extracted = extract_replicate_number(row.get('fastq_1', ''))
if extracted:
row['replicate'] = extracted
else:
# Auto-assign sequential
if sample not in sample_rep:
sample_rep[sample] = 0
sample_rep[sample] += 1
row['replicate'] = sample_rep[sample]
return rows
def _write_samplesheet(rows: List[Dict], config: Dict, output_path: str):
"""Write samplesheet to CSV file."""
columns = config.get("samplesheet", {}).get("columns", [])
column_names = [c['name'] for c in columns]
# Filter to columns that have data
active_columns = [c for c in column_names if any(c in row and row[c] for row in rows)]
# Ensure fastq_1/fastq_2 or bam/bai are included
for required in ['fastq_1', 'bam']:
if required in column_names and required not in active_columns:
if any(required in row for row in rows):
active_columns.append(required)
# Maintain original column order
active_columns = [c for c in column_names if c in active_columns]
with open(output_path, 'w') as f:
f.write(','.join(active_columns) + '\n')
for row in rows:
values = [str(row.get(col, '')) for col in active_columns]
f.write(','.join(values) + '\n')
def _print_preview(rows: List[Dict], config: Dict):
"""Print preview of generated samplesheet."""
columns = config.get("samplesheet", {}).get("columns", [])
column_names = [c['name'] for c in columns]
active_columns = [c for c in column_names if any(c in row for row in rows)]
print(f"\nPreview (first 3 rows):")
print(','.join(active_columns))
for row in rows[:3]:
values = [str(row.get(col, ''))[:40] for col in active_columns] # Truncate long paths
print(','.join(values))
if len(rows) > 3:
print(f"... ({len(rows) - 3} more rows)")
def validate_existing_samplesheet(csv_path: str, pipeline: str) -> ValidationResult:
"""Validate an existing samplesheet file."""
import csv
if not os.path.exists(csv_path):
return ValidationResult(valid=False, errors=[f"File not found: {csv_path}"])
try:
with open(csv_path, 'r') as f:
reader = csv.DictReader(f)
rows = list(reader)
except Exception as e:
return ValidationResult(valid=False, errors=[f"Failed to read CSV: {e}"])
if not rows:
return ValidationResult(valid=False, errors=["Samplesheet is empty"])
config = load_pipeline_config(pipeline)
return validate_samplesheet(rows, pipeline, config)
def main():
parser = argparse.ArgumentParser(
description='Generate nf-core samplesheet from data directory',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Generate samplesheet for RNA-seq
%(prog)s ./fastqs rnaseq -o samples.csv
# Generate samplesheet for sarek from BAM files
%(prog)s ./bams sarek --input-type bam
# Validate existing samplesheet
%(prog)s --validate samplesheet.csv rnaseq
Supported pipelines: rnaseq, sarek, atacseq
"""
)
parser.add_argument('input', help='Directory with data files, or CSV path for --validate')
parser.add_argument('pipeline', help='Pipeline name (rnaseq, sarek, atacseq)')
parser.add_argument('-o', '--output', help='Output CSV filename')
parser.add_argument('--input-type', choices=['auto', 'fastq', 'bam', 'cram'],
default='auto', help='Input file type (default: auto-detect)')
parser.add_argument('--single-end', action='store_true',
help='Treat as single-end data (suppress pairing warnings)')
parser.add_argument('--validate', action='store_true',
help='Validate existing samplesheet instead of generating')
parser.add_argument('--no-interactive', action='store_true',
help='Non-interactive mode (use defaults)')
args = parser.parse_args()
try:
if args.validate:
# Validate existing samplesheet
result = validate_existing_samplesheet(args.input, args.pipeline)
if result.valid:
print(f"✓ Samplesheet is valid for {args.pipeline}")
if result.warnings:
print("\nWarnings:")
for w in result.warnings:
print(f" - {w}")
sys.exit(0)
else:
print(f"✗ Samplesheet validation failed")
print(result.summary())
sys.exit(1)
else:
# Generate new samplesheet
if not os.path.isdir(args.input):
print(f"Error: Not a directory: {args.input}")
sys.exit(1)
output_path, result = generate_samplesheet(
args.input,
args.pipeline,
args.output,
args.input_type,
args.single_end,
interactive=not args.no_interactive
)
if output_path is None:
print("\nFailed to generate samplesheet.")
if result.suggestions:
print("\nSuggestions:")
for s in result.suggestions:
print(f" - {s}")
sys.exit(1)
sys.exit(0)
except ValueError as e:
print(f"Error: {e}")
sys.exit(1)
except KeyboardInterrupt:
print("\nAborted.")
sys.exit(1)
if __name__ == '__main__':
main()
#!/usr/bin/env python3
"""
Genome reference management for nf-core pipelines.
Manages downloading, caching, and accessing genome references from iGenomes.
Supports auto-download when references aren't available locally.
Usage:
python manage_genomes.py list
python manage_genomes.py check GRCh38
python manage_genomes.py download GRCh38
python manage_genomes.py params GRCh38
"""
import argparse
import json
import os
import subprocess
import sys
from pathlib import Path
from typing import Dict, List, Optional
# iGenomes reference configuration
IGENOMES = {
# Human
'GRCh38': {
'display_name': 'Human GRCh38/hg38',
'species': 'Homo sapiens',
'aliases': ['hg38', 'GRCh38.p14'],
's3_base': 's3://ngi-igenomes/igenomes/Homo_sapiens/NCBI/GRCh38',
'files': {
'fasta': 'Sequence/WholeGenomeFasta/genome.fa',
'gtf': 'Annotation/Genes/genes.gtf',
'bwa_index': 'Sequence/BWAIndex/',
'star_index': 'Sequence/STARIndex/',
}
},
'GRCh37': {
'display_name': 'Human GRCh37/hg19',
'species': 'Homo sapiens',
'aliases': ['hg19', 'GRCh37.p13'],
's3_base': 's3://ngi-igenomes/igenomes/Homo_sapiens/NCBI/GRCh37',
'files': {
'fasta': 'Sequence/WholeGenomeFasta/genome.fa',
'gtf': 'Annotation/Genes/genes.gtf',
'bwa_index': 'Sequence/BWAIndex/',
'star_index': 'Sequence/STARIndex/',
}
},
# Mouse
'GRCm39': {
'display_name': 'Mouse GRCm39/mm39',
'species': 'Mus musculus',
'aliases': ['mm39', 'GRCm39'],
's3_base': 's3://ngi-igenomes/igenomes/Mus_musculus/Ensembl/GRCm39',
'files': {
'fasta': 'Sequence/WholeGenomeFasta/genome.fa',
'gtf': 'Annotation/Genes/genes.gtf',
'bwa_index': 'Sequence/BWAIndex/',
'star_index': 'Sequence/STARIndex/',
}
},
'GRCm38': {
'display_name': 'Mouse GRCm38/mm10',
'species': 'Mus musculus',
'aliases': ['mm10', 'GRCm38'],
's3_base': 's3://ngi-igenomes/igenomes/Mus_musculus/NCBI/GRCm38',
'files': {
'fasta': 'Sequence/WholeGenomeFasta/genome.fa',
'gtf': 'Annotation/Genes/genes.gtf',
'bwa_index': 'Sequence/BWAIndex/',
'star_index': 'Sequence/STARIndex/',
}
},
# Yeast
'R64-1-1': {
'display_name': 'Yeast R64-1-1/sacCer3',
'species': 'Saccharomyces cerevisiae',
'aliases': ['sacCer3', 'S288C', 'yeast'],
's3_base': 's3://ngi-igenomes/igenomes/Saccharomyces_cerevisiae/Ensembl/R64-1-1',
'files': {
'fasta': 'Sequence/WholeGenomeFasta/genome.fa',
'gtf': 'Annotation/Genes/genes.gtf',
'bwa_index': 'Sequence/BWAIndex/',
'star_index': 'Sequence/STARIndex/',
}
},
# Fruit fly
'BDGP6': {
'display_name': 'Drosophila BDGP6/dm6',
'species': 'Drosophila melanogaster',
'aliases': ['dm6', 'BDGP6', 'fly'],
's3_base': 's3://ngi-igenomes/igenomes/Drosophila_melanogaster/Ensembl/BDGP6',
'files': {
'fasta': 'Sequence/WholeGenomeFasta/genome.fa',
'gtf': 'Annotation/Genes/genes.gtf',
}
},
# C. elegans
'WBcel235': {
'display_name': 'C. elegans WBcel235/ce11',
'species': 'Caenorhabditis elegans',
'aliases': ['ce11', 'worm'],
's3_base': 's3://ngi-igenomes/igenomes/Caenorhabditis_elegans/Ensembl/WBcel235',
'files': {
'fasta': 'Sequence/WholeGenomeFasta/genome.fa',
'gtf': 'Annotation/Genes/genes.gtf',
'bwa_index': 'Sequence/BWAIndex/',
'star_index': 'Sequence/STARIndex/',
}
},
# Zebrafish
'GRCz11': {
'display_name': 'Zebrafish GRCz11/danRer11',
'species': 'Danio rerio',
'aliases': ['danRer11', 'zebrafish'],
's3_base': 's3://ngi-igenomes/igenomes/Danio_rerio/Ensembl/GRCz11',
'files': {
'fasta': 'Sequence/WholeGenomeFasta/genome.fa',
'gtf': 'Annotation/Genes/genes.gtf',
'bwa_index': 'Sequence/BWAIndex/',
'star_index': 'Sequence/STARIndex/',
}
},
'GRCz10': {
'display_name': 'Zebrafish GRCz10/danRer10',
'species': 'Danio rerio',
'aliases': ['danRer10'],
's3_base': 's3://ngi-igenomes/igenomes/Danio_rerio/Ensembl/GRCz10',
'files': {
'fasta': 'Sequence/WholeGenomeFasta/genome.fa',
'gtf': 'Annotation/Genes/genes.gtf',
}
},
# Rat
'Rnor_6.0': {
'display_name': 'Rat Rnor_6.0/rn6',
'species': 'Rattus norvegicus',
'aliases': ['rn6', 'Rnor6', 'rat'],
's3_base': 's3://ngi-igenomes/igenomes/Rattus_norvegicus/Ensembl/Rnor_6.0',
'files': {
'fasta': 'Sequence/WholeGenomeFasta/genome.fa',
'gtf': 'Annotation/Genes/genes.gtf',
'bwa_index': 'Sequence/BWAIndex/',
'star_index': 'Sequence/STARIndex/',
}
},
# Arabidopsis
'TAIR10': {
'display_name': 'Arabidopsis TAIR10',
'species': 'Arabidopsis thaliana',
'aliases': ['arabidopsis'],
's3_base': 's3://ngi-igenomes/igenomes/Arabidopsis_thaliana/Ensembl/TAIR10',
'files': {
'fasta': 'Sequence/WholeGenomeFasta/genome.fa',
'gtf': 'Annotation/Genes/genes.gtf',
'bwa_index': 'Sequence/BWAIndex/',
'star_index': 'Sequence/STARIndex/',
}
},
# Chicken
'GRCg6a': {
'display_name': 'Chicken GRCg6a/galGal6',
'species': 'Gallus gallus',
'aliases': ['galGal6', 'chicken'],
's3_base': 's3://ngi-igenomes/igenomes/Gallus_gallus/Ensembl/GRCg6a',
'files': {
'fasta': 'Sequence/WholeGenomeFasta/genome.fa',
'gtf': 'Annotation/Genes/genes.gtf',
}
},
# Dog
'CanFam3.1': {
'display_name': 'Dog CanFam3.1/canFam3',
'species': 'Canis lupus familiaris',
'aliases': ['canFam3', 'dog'],
's3_base': 's3://ngi-igenomes/igenomes/Canis_familiaris/Ensembl/CanFam3.1',
'files': {
'fasta': 'Sequence/WholeGenomeFasta/genome.fa',
'gtf': 'Annotation/Genes/genes.gtf',
}
},
# Pig
'Sscrofa11.1': {
'display_name': 'Pig Sscrofa11.1/susScr11',
'species': 'Sus scrofa',
'aliases': ['susScr11', 'pig'],
's3_base': 's3://ngi-igenomes/igenomes/Sus_scrofa/Ensembl/Sscrofa11.1',
'files': {
'fasta': 'Sequence/WholeGenomeFasta/genome.fa',
'gtf': 'Annotation/Genes/genes.gtf',
}
},
}
def get_cache_dir() -> Path:
"""Get genome cache directory."""
cache_dir = os.environ.get(
'NF_CORE_GENOME_CACHE',
os.path.expanduser('~/.nf-core/genomes')
)
return Path(cache_dir)
def resolve_genome_id(genome: str) -> Optional[str]:
"""Resolve genome ID from name or alias."""
# Direct match
if genome in IGENOMES:
return genome
# Check aliases
genome_lower = genome.lower()
for gid, info in IGENOMES.items():
if genome_lower in [a.lower() for a in info.get('aliases', [])]:
return gid
return None
def is_genome_installed(genome_id: str) -> bool:
"""Check if genome is installed locally."""
cache_dir = get_cache_dir()
genome_dir = cache_dir / genome_id
# Check for fasta as minimum requirement
fasta_path = genome_dir / 'genome.fa'
return fasta_path.exists()
def get_genome_path(genome_id: str) -> Optional[Path]:
"""Get local path to genome if installed."""
if not is_genome_installed(genome_id):
return None
return get_cache_dir() / genome_id
def list_genomes(installed_only: bool = False) -> List[Dict]:
"""List available genomes."""
result = []
for genome_id, info in IGENOMES.items():
installed = is_genome_installed(genome_id)
if installed_only and not installed:
continue
genome_path = get_genome_path(genome_id) if installed else None
result.append({
'id': genome_id,
'display_name': info['display_name'],
'species': info['species'],
'aliases': info.get('aliases', []),
'installed': installed,
'path': str(genome_path) if genome_path else None,
})
return result
def download_genome(
genome_id: str,
components: Optional[List[str]] = None,
force: bool = False
) -> bool:
"""
Download genome reference files from iGenomes.
Args:
genome_id: Genome identifier (e.g., GRCh38)
components: Specific components to download (fasta, gtf, etc.)
force: Overwrite existing files
Returns:
True if successful
"""
# Resolve genome ID
resolved = resolve_genome_id(genome_id)
if not resolved:
print(f"Unknown genome: {genome_id}")
print(f"Available: {', '.join(IGENOMES.keys())}")
return False
genome_id = resolved
info = IGENOMES[genome_id]
# Check for AWS CLI
aws_available = subprocess.run(
['which', 'aws'],
capture_output=True
).returncode == 0
if not aws_available:
print("AWS CLI not found. Required for iGenomes download.")
print("Install with: pip install awscli")
print("\nAlternative: Use --genome flag with nf-core pipelines")
print("which will auto-download references (slower, per-run).")
return False
# Create cache directory
cache_dir = get_cache_dir()
genome_dir = cache_dir / genome_id
genome_dir.mkdir(parents=True, exist_ok=True)
# Determine components to download
if components is None:
components = ['fasta', 'gtf'] # Minimum required
print(f"Downloading {info['display_name']} to {genome_dir}")
print(f"Components: {', '.join(components)}")
success = True
for component in components:
if component not in info.get('files', {}):
print(f" Skipping {component}: not available for {genome_id}")
continue
remote_path = info['files'][component]
s3_path = f"{info['s3_base']}/{remote_path}"
# Determine local path
if remote_path.endswith('/'):
# Directory (e.g., index)
local_path = genome_dir / component
else:
# File
filename = Path(remote_path).name
local_path = genome_dir / filename
if local_path.exists() and not force:
print(f" {component}: Already exists (use --force to overwrite)")
continue
print(f" Downloading {component}...")
# Build AWS command
cmd = ['aws', 's3', 'cp', '--no-sign-request']
if remote_path.endswith('/'):
cmd.extend(['--recursive', s3_path, str(local_path)])
else:
cmd.extend([s3_path, str(local_path)])
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f" ERROR downloading {component}:")
print(f" {result.stderr[:200]}")
success = False
else:
print(f" {component}: Downloaded successfully")
if success:
print(f"\nGenome {genome_id} ready at: {genome_dir}")
else:
print(f"\nSome components failed to download.")
return success
def get_nextflow_params(genome_id: str) -> Dict[str, str]:
"""
Get Nextflow parameters for a genome.
Returns dict with --fasta, --gtf if local,
or just --genome if using iGenomes key.
"""
resolved = resolve_genome_id(genome_id)
if not resolved:
return {'error': f'Unknown genome: {genome_id}'}
genome_id = resolved
# Check if installed locally
genome_path = get_genome_path(genome_id)
if genome_path:
params = {}
# Check for local files
fasta = genome_path / 'genome.fa'
if fasta.exists():
params['fasta'] = str(fasta)
gtf = genome_path / 'genes.gtf'
if gtf.exists():
params['gtf'] = str(gtf)
if params:
return params
# Fall back to iGenomes key
return {'genome': genome_id}
def print_genome_list(genomes: List[Dict], output_json: bool = False):
"""Print genome list."""
if output_json:
print(json.dumps(genomes, indent=2))
return
print("\n" + "=" * 50)
print(" Available Genomes")
print("=" * 50 + "\n")
for g in genomes:
status = "\033[92m[installed]\033[0m" if g['installed'] else ""
print(f" {g['id']}: {g['display_name']} {status}")
print(f" Species: {g['species']}")
print(f" Aliases: {', '.join(g['aliases'])}")
if g['path']:
print(f" Path: {g['path']}")
print()
def main():
parser = argparse.ArgumentParser(
description='Manage genome references for nf-core pipelines',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Commands:
list List available genomes
check <genome> Check if genome is installed
download <genome> Download genome from iGenomes
params <genome> Get Nextflow parameters for genome
Examples:
%(prog)s list
%(prog)s list --installed
%(prog)s check GRCh38
%(prog)s download GRCh38
%(prog)s download GRCh38 --components fasta gtf star_index
%(prog)s params GRCh38
"""
)
subparsers = parser.add_subparsers(dest='command', help='Commands')
# List command
list_parser = subparsers.add_parser('list', help='List available genomes')
list_parser.add_argument('--installed', action='store_true',
help='Show only installed genomes')
list_parser.add_argument('--json', action='store_true',
help='Output as JSON')
# Check command
check_parser = subparsers.add_parser('check', help='Check if genome is installed')
check_parser.add_argument('genome', help='Genome ID (e.g., GRCh38)')
check_parser.add_argument('--json', action='store_true',
help='Output as JSON')
# Download command
dl_parser = subparsers.add_parser('download', help='Download genome from iGenomes')
dl_parser.add_argument('genome', help='Genome ID (e.g., GRCh38)')
dl_parser.add_argument('--components', nargs='+',
help='Specific components (fasta, gtf, bwa_index, star_index)')
dl_parser.add_argument('--force', action='store_true',
help='Overwrite existing files')
# Params command
params_parser = subparsers.add_parser('params', help='Get Nextflow params for genome')
params_parser.add_argument('genome', help='Genome ID')
params_parser.add_argument('--json', action='store_true',
help='Output as JSON')
args = parser.parse_args()
if args.command == 'list':
genomes = list_genomes(installed_only=args.installed)
print_genome_list(genomes, args.json)
elif args.command == 'check':
resolved = resolve_genome_id(args.genome)
if not resolved:
print(f"Unknown genome: {args.genome}")
sys.exit(1)
installed = is_genome_installed(resolved)
path = get_genome_path(resolved) if installed else None
if args.json:
print(json.dumps({
'genome': resolved,
'installed': installed,
'path': str(path) if path else None
}))
else:
if installed:
print(f"✓ Genome {resolved} is installed at: {path}")
else:
print(f"✗ Genome {resolved} is not installed locally")
print(f" Download with: python {sys.argv[0]} download {resolved}")
sys.exit(0 if installed else 1)
elif args.command == 'download':
success = download_genome(args.genome, args.components, args.force)
sys.exit(0 if success else 1)
elif args.command == 'params':
params = get_nextflow_params(args.genome)
if args.json:
print(json.dumps(params))
else:
if 'error' in params:
print(f"Error: {params['error']}")
sys.exit(1)
for key, value in params.items():
print(f"--{key} {value}")
else:
parser.print_help()
sys.exit(1)
if __name__ == '__main__':
main()
"""
Utility modules for nf-core pipeline deployment.
Modules:
ncbi_utils: NCBI/GEO/SRA data fetching and download utilities
file_discovery: Find FASTQ, BAM, and CRAM files
sample_inference: Extract sample info, detect tumor/normal
validators: Validate samplesheets before writing
"""
# NCBI utilities for GEO/SRA data acquisition
from .ncbi_utils import (
check_network_access,
fetch_geo_metadata,
fetch_sra_study_accession,
fetch_sra_run_info,
fetch_sra_run_info_detailed,
fetch_bioproject_from_geo,
fetch_ena_fastq_urls,
download_file,
fetch_pubmed_metadata,
format_file_size,
estimate_download_size,
group_samples_by_type,
format_sample_groups_table,
)
# File discovery utilities
from .file_discovery import discover_files, FileInfo, count_files_by_type
# Sample inference utilities
from .sample_inference import (
extract_sample_info,
infer_tumor_normal_status,
match_read_pairs,
extract_replicate_number
)
# Validation utilities
from .validators import validate_samplesheet, ValidationResult
__all__ = [
# ncbi_utils
'check_network_access',
'fetch_geo_metadata',
'fetch_sra_study_accession',
'fetch_sra_run_info',
'fetch_sra_run_info_detailed',
'fetch_bioproject_from_geo',
'fetch_ena_fastq_urls',
'download_file',
'fetch_pubmed_metadata',
'format_file_size',
'estimate_download_size',
'group_samples_by_type',
'format_sample_groups_table',
# file_discovery
'discover_files',
'FileInfo',
'count_files_by_type',
# sample_inference
'extract_sample_info',
'infer_tumor_normal_status',
'match_read_pairs',
'extract_replicate_number',
# validators
'validate_samplesheet',
'ValidationResult',
]
Related skills
How it compares
Use for plugin-chained agent workflows rather than single-task generator or checker skills.
FAQ
What does nextflow development do?
Run nf-core bioinformatics pipelines (rnaseq, sarek, atacseq) on sequencing data. Use when analyzing RNA-seq, WGS/WES, or ATAC-seq data—either local FASTQs or public datasets from GEO/SRA. Triggers on
When should I invoke nextflow development?
Run nf-core bioinformatics pipelines (rnaseq, sarek, atacseq) on sequencing data. Use when analyzing RNA-seq, WGS/WES, or ATAC-seq data—either local FASTQs or public datasets from GEO/SRA. Triggers on
What are key capabilities?
[ ] Step 0: Acquire data (if from GEO/SRA)
Is Nextflow Development safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.