From a2594dfe86787fa102cd1ad3a6518fba426e92c3 Mon Sep 17 00:00:00 2001 From: nf-core-bot Date: Thu, 20 Nov 2025 09:32:20 +0000 Subject: [PATCH 1/4] Template update for nf-core/tools version 3.5.1 --- .github/workflows/awsfulltest.yml | 2 +- .github/workflows/awstest.yml | 2 +- .github/workflows/download_pipeline.yml | 2 +- .github/workflows/fix_linting.yml | 2 +- .github/workflows/linting.yml | 6 +-- .github/workflows/nf-test.yml | 4 +- .github/workflows/release-announcements.yml | 9 ++--- .../workflows/template-version-comment.yml | 2 +- .nf-core.yml | 3 +- .prettierignore | 2 + README.md | 4 +- modules.json | 4 +- modules/nf-core/multiqc/environment.yml | 2 +- modules/nf-core/multiqc/main.nf | 4 +- .../nf-core/multiqc/tests/main.nf.test.snap | 24 ++++++------ nextflow.config | 2 - ro-crate-metadata.json | 14 +++---- .../utils_nfcore_spatialxe_pipeline/main.nf | 6 +-- .../nf-core/utils_nfcore_pipeline/main.nf | 2 +- workflows/spatialxe.nf | 38 ++++++++++++++----- 20 files changed, 75 insertions(+), 59 deletions(-) diff --git a/.github/workflows/awsfulltest.yml b/.github/workflows/awsfulltest.yml index d45b8ecf..78fdaf9d 100644 --- a/.github/workflows/awsfulltest.yml +++ b/.github/workflows/awsfulltest.yml @@ -40,7 +40,7 @@ jobs: } profiles: test_full - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: Seqera Platform debug log file path: | diff --git a/.github/workflows/awstest.yml b/.github/workflows/awstest.yml index 566a8d9b..47dfba52 100644 --- a/.github/workflows/awstest.yml +++ b/.github/workflows/awstest.yml @@ -25,7 +25,7 @@ jobs: } profiles: test - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: Seqera Platform debug log file path: | diff --git a/.github/workflows/download_pipeline.yml b/.github/workflows/download_pipeline.yml index 6d94bcbf..45884ff9 100644 --- a/.github/workflows/download_pipeline.yml +++ b/.github/workflows/download_pipeline.yml @@ -127,7 +127,7 @@ jobs: fi - name: Upload Nextflow logfile for debugging purposes - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: nextflow_logfile.txt path: .nextflow.log* diff --git a/.github/workflows/fix_linting.yml b/.github/workflows/fix_linting.yml index 5a1c68cf..8394ba2f 100644 --- a/.github/workflows/fix_linting.yml +++ b/.github/workflows/fix_linting.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: # Use the @nf-core-bot token to check out so we can push later - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 with: token: ${{ secrets.nf_core_bot_auth_token }} diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 30e66026..7a527a34 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -11,7 +11,7 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 - name: Set up Python 3.14 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 @@ -28,7 +28,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out pipeline code - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 - name: Install Nextflow uses: nf-core/setup-nextflow@v2 @@ -71,7 +71,7 @@ jobs: - name: Upload linting log file artifact if: ${{ always() }} - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: linting-logs path: | diff --git a/.github/workflows/nf-test.yml b/.github/workflows/nf-test.yml index e20bf6d0..c98d76ec 100644 --- a/.github/workflows/nf-test.yml +++ b/.github/workflows/nf-test.yml @@ -40,7 +40,7 @@ jobs: rm -rf ./* || true rm -rf ./.??* || true ls -la ./ - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 with: fetch-depth: 0 @@ -85,7 +85,7 @@ jobs: TOTAL_SHARDS: ${{ needs.nf-test-changes.outputs.total_shards }} steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 with: fetch-depth: 0 diff --git a/.github/workflows/release-announcements.yml b/.github/workflows/release-announcements.yml index e64cebd6..431d3d44 100644 --- a/.github/workflows/release-announcements.yml +++ b/.github/workflows/release-announcements.yml @@ -15,10 +15,9 @@ jobs: echo "topics=$(curl -s https://nf-co.re/pipelines.json | jq -r '.remote_workflows[] | select(.full_name == "${{ github.repository }}") | .topics[]' | awk '{print "#"$0}' | tr '\n' ' ')" | sed 's/-//g' >> $GITHUB_OUTPUT - name: get description - id: get_topics + id: get_description run: | - echo "description=$(curl -s https://nf-co.re/pipelines.json | jq -r '.remote_workflows[] | select(.full_name == "${{ github.repository }}") | .description' >> $GITHUB_OUTPUT - + echo "description=$(curl -s https://nf-co.re/pipelines.json | jq -r '.remote_workflows[] | select(.full_name == "${{ github.repository }}") | .description')" >> $GITHUB_OUTPUT - uses: rzr/fediverse-action@master with: access-token: ${{ secrets.MASTODON_ACCESS_TOKEN }} @@ -27,9 +26,7 @@ jobs: # https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#release message: | Pipeline release! ${{ github.repository }} v${{ github.event.release.tag_name }} - ${{ github.event.release.name }}! - - ${{ steps.get_topics.outputs.description }} - + ${{ steps.get_description.outputs.description }} Please see the changelog: ${{ github.event.release.html_url }} ${{ steps.get_topics.outputs.topics }} #nfcore #openscience #nextflow #bioinformatics diff --git a/.github/workflows/template-version-comment.yml b/.github/workflows/template-version-comment.yml index c5988af9..e8560fc7 100644 --- a/.github/workflows/template-version-comment.yml +++ b/.github/workflows/template-version-comment.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out pipeline code - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 with: ref: ${{ github.event.pull_request.head.sha }} diff --git a/.nf-core.yml b/.nf-core.yml index b916095a..79809cf6 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -7,7 +7,8 @@ lint: - assets/nf-core-spatialxe_logo_light.png - docs/images/nf-core-spatialxe_logo_dark.png - docs/images/nf-core-spatialxe_logo_light.png -nf_core_version: 3.4.1 + - .github/PULL_REQUEST_TEMPLATE.md +nf_core_version: 3.5.1 repository_type: pipeline template: author: Sameesh Kher, Florian Heyl diff --git a/.prettierignore b/.prettierignore index 2255e3e3..dd749d43 100644 --- a/.prettierignore +++ b/.prettierignore @@ -12,3 +12,5 @@ testing* bin/ .nf-test/ ro-crate-metadata.json +modules/nf-core/ +subworkflows/nf-core/ diff --git a/README.md b/README.md index f2386926..09cb2108 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,13 @@ -[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new/nf-core/spatialxe) +[![Open in GitHub Codespaces](https://img.shields.io/badge/Open_In_GitHub_Codespaces-black?labelColor=grey&logo=github)](https://github.com/codespaces/new/nf-core/spatialxe) [![GitHub Actions CI Status](https://github.com/nf-core/spatialxe/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/spatialxe/actions/workflows/nf-test.yml) [![GitHub Actions Linting Status](https://github.com/nf-core/spatialxe/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/spatialxe/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/spatialxe/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX) [![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com) [![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.04.0-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/) -[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.4.1-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.4.1) +[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.5.1-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.5.1) [![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/) [![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/) [![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/) diff --git a/modules.json b/modules.json index 100427cf..f5682e24 100644 --- a/modules.json +++ b/modules.json @@ -12,7 +12,7 @@ }, "multiqc": { "branch": "master", - "git_sha": "e10b76ca0c66213581bec2833e30d31f239dec0b", + "git_sha": "af27af1be706e6a2bb8fe454175b0cdf77f47b49", "installed_by": ["modules"] } } @@ -26,7 +26,7 @@ }, "utils_nfcore_pipeline": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "271e7fc14eb1320364416d996fb077421f3faed2", "installed_by": ["subworkflows"] }, "utils_nfschema_plugin": { diff --git a/modules/nf-core/multiqc/environment.yml b/modules/nf-core/multiqc/environment.yml index dd513cbd..d02016a0 100644 --- a/modules/nf-core/multiqc/environment.yml +++ b/modules/nf-core/multiqc/environment.yml @@ -4,4 +4,4 @@ channels: - conda-forge - bioconda dependencies: - - bioconda::multiqc=1.31 + - bioconda::multiqc=1.32 diff --git a/modules/nf-core/multiqc/main.nf b/modules/nf-core/multiqc/main.nf index 5288f5cc..c1158fb0 100644 --- a/modules/nf-core/multiqc/main.nf +++ b/modules/nf-core/multiqc/main.nf @@ -3,8 +3,8 @@ process MULTIQC { conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/ef/eff0eafe78d5f3b65a6639265a16b89fdca88d06d18894f90fcdb50142004329/data' : - 'community.wave.seqera.io/library/multiqc:1.31--1efbafd542a23882' }" + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/8c/8c6c120d559d7ee04c7442b61ad7cf5a9e8970be5feefb37d68eeaa60c1034eb/data' : + 'community.wave.seqera.io/library/multiqc:1.32--d58f60e4deb769bf' }" input: path multiqc_files, stageAs: "?/*" diff --git a/modules/nf-core/multiqc/tests/main.nf.test.snap b/modules/nf-core/multiqc/tests/main.nf.test.snap index 17881d15..a88bafd6 100644 --- a/modules/nf-core/multiqc/tests/main.nf.test.snap +++ b/modules/nf-core/multiqc/tests/main.nf.test.snap @@ -2,14 +2,14 @@ "multiqc_versions_single": { "content": [ [ - "versions.yml:md5,8968b114a3e20756d8af2b80713bcc4f" + "versions.yml:md5,737bb2c7cad54ffc2ec020791dc48b8f" ] ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "24.10.4" }, - "timestamp": "2025-09-08T20:57:36.139055243" + "timestamp": "2025-10-27T13:33:24.356715" }, "multiqc_stub": { "content": [ @@ -17,25 +17,25 @@ "multiqc_report.html", "multiqc_data", "multiqc_plots", - "versions.yml:md5,8968b114a3e20756d8af2b80713bcc4f" + "versions.yml:md5,737bb2c7cad54ffc2ec020791dc48b8f" ] ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "24.10.4" }, - "timestamp": "2025-09-08T20:59:15.142230631" + "timestamp": "2025-10-27T13:34:11.103619" }, "multiqc_versions_config": { "content": [ [ - "versions.yml:md5,8968b114a3e20756d8af2b80713bcc4f" + "versions.yml:md5,737bb2c7cad54ffc2ec020791dc48b8f" ] ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "24.10.4" }, - "timestamp": "2025-09-08T20:58:29.629087066" + "timestamp": "2025-10-27T13:34:04.615233" } } \ No newline at end of file diff --git a/nextflow.config b/nextflow.config index b131a86d..b9c8f64f 100644 --- a/nextflow.config +++ b/nextflow.config @@ -170,8 +170,6 @@ profiles { test_full { includeConfig 'conf/test_full.config' } } -// Set AWS client to anonymous when using the default igenomes_base -aws.client.anonymous = !params.igenomes_ignore && params.igenomes_base?.startsWith('s3://ngi-igenomes/igenomes/') ?: false // Load nf-core custom profiles from different institutions // If params.custom_config_base is set AND either the NXF_OFFLINE environment variable is not set or params.custom_config_base is a local path, the nfcore_custom.config file from the specified base path is included. diff --git a/ro-crate-metadata.json b/ro-crate-metadata.json index d10d3c54..fd557197 100644 --- a/ro-crate-metadata.json +++ b/ro-crate-metadata.json @@ -22,8 +22,8 @@ "@id": "./", "@type": "Dataset", "creativeWorkStatus": "InProgress", - "datePublished": "2025-10-16T13:39:03+00:00", - "description": "

\n \n \n \"nf-core/spatialxe\"\n \n

\n\n[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new/nf-core/spatialxe)\n[![GitHub Actions CI Status](https://github.com/nf-core/spatialxe/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/spatialxe/actions/workflows/nf-test.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-core/spatialxe/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/spatialxe/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/spatialxe/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX)\n[![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com)\n\n[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.04.0-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/)\n[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.4.1-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.4.1)\n[![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/)\n[![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/)\n[![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/)\n[![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://cloud.seqera.io/launch?pipeline=https://github.com/nf-core/spatialxe)\n\n[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23spatialxe-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/spatialxe)[![Follow on Bluesky](https://img.shields.io/badge/bluesky-%40nf__core-1185fe?labelColor=000000&logo=bluesky)](https://bsky.app/profile/nf-co.re)[![Follow on Mastodon](https://img.shields.io/badge/mastodon-nf__core-6364ff?labelColor=FFFFFF&logo=mastodon)](https://mstdn.science/@nf_core)[![Watch on YouTube](http://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core)\n\n## Introduction\n\n**nf-core/spatialxe** is a bioinformatics pipeline that ...\n\n\n\n\n1. Read QC ([`FastQC`](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/))2. Present QC for raw reads ([`MultiQC`](http://multiqc.info/))\n\n## Usage\n\n> [!NOTE]\n> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data.\n\n\n\nNow, you can run the pipeline using:\n\n\n\n```bash\nnextflow run nf-core/spatialxe \\\n -profile \\\n --input samplesheet.csv \\\n --outdir \n```\n\n> [!WARNING]\n> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/usage/getting_started/configuration#custom-configuration-files).\n\nFor more details and further functionality, please refer to the [usage documentation](https://nf-co.re/spatialxe/usage) and the [parameter documentation](https://nf-co.re/spatialxe/parameters).\n\n## Pipeline output\n\nTo see the results of an example test run with a full size dataset refer to the [results](https://nf-co.re/spatialxe/results) tab on the nf-core website pipeline page.\nFor more details about the output files and reports, please refer to the\n[output documentation](https://nf-co.re/spatialxe/output).\n\n## Credits\n\nnf-core/spatialxe was originally written by Sameesh Kher, Florian Heyl.\n\nWe thank the following people for their extensive assistance in the development of this pipeline:\n\n\n\n## Contributions and Support\n\nIf you would like to contribute to this pipeline, please see the [contributing guidelines](.github/CONTRIBUTING.md).\n\nFor further information or help, don't hesitate to get in touch on the [Slack `#spatialxe` channel](https://nfcore.slack.com/channels/spatialxe) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## Citations\n\n\n\n\n\n\nAn extensive list of references for the tools used by the pipeline can be found in the [`CITATIONS.md`](CITATIONS.md) file.\n\nYou can cite the `nf-core` publication as follows:\n\n> **The nf-core framework for community-curated bioinformatics pipelines.**\n>\n> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen.\n>\n> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x).\n", + "datePublished": "2025-11-20T09:32:15+00:00", + "description": "

\n \n \n \"nf-core/spatialxe\"\n \n

\n\n[![Open in GitHub Codespaces](https://img.shields.io/badge/Open_In_GitHub_Codespaces-black?labelColor=grey&logo=github)](https://github.com/codespaces/new/nf-core/spatialxe)\n[![GitHub Actions CI Status](https://github.com/nf-core/spatialxe/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/spatialxe/actions/workflows/nf-test.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-core/spatialxe/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/spatialxe/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/spatialxe/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX)\n[![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com)\n\n[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.04.0-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/)\n[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.5.1-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.5.1)\n[![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/)\n[![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/)\n[![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/)\n[![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://cloud.seqera.io/launch?pipeline=https://github.com/nf-core/spatialxe)\n\n[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23spatialxe-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/spatialxe)[![Follow on Bluesky](https://img.shields.io/badge/bluesky-%40nf__core-1185fe?labelColor=000000&logo=bluesky)](https://bsky.app/profile/nf-co.re)[![Follow on Mastodon](https://img.shields.io/badge/mastodon-nf__core-6364ff?labelColor=FFFFFF&logo=mastodon)](https://mstdn.science/@nf_core)[![Watch on YouTube](http://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core)\n\n## Introduction\n\n**nf-core/spatialxe** is a bioinformatics pipeline that ...\n\n\n\n\n1. Read QC ([`FastQC`](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/))2. Present QC for raw reads ([`MultiQC`](http://multiqc.info/))\n\n## Usage\n\n> [!NOTE]\n> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data.\n\n\n\nNow, you can run the pipeline using:\n\n\n\n```bash\nnextflow run nf-core/spatialxe \\\n -profile \\\n --input samplesheet.csv \\\n --outdir \n```\n\n> [!WARNING]\n> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/usage/getting_started/configuration#custom-configuration-files).\n\nFor more details and further functionality, please refer to the [usage documentation](https://nf-co.re/spatialxe/usage) and the [parameter documentation](https://nf-co.re/spatialxe/parameters).\n\n## Pipeline output\n\nTo see the results of an example test run with a full size dataset refer to the [results](https://nf-co.re/spatialxe/results) tab on the nf-core website pipeline page.\nFor more details about the output files and reports, please refer to the\n[output documentation](https://nf-co.re/spatialxe/output).\n\n## Credits\n\nnf-core/spatialxe was originally written by Sameesh Kher, Florian Heyl.\n\nWe thank the following people for their extensive assistance in the development of this pipeline:\n\n\n\n## Contributions and Support\n\nIf you would like to contribute to this pipeline, please see the [contributing guidelines](.github/CONTRIBUTING.md).\n\nFor further information or help, don't hesitate to get in touch on the [Slack `#spatialxe` channel](https://nfcore.slack.com/channels/spatialxe) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## Citations\n\n\n\n\n\n\nAn extensive list of references for the tools used by the pipeline can be found in the [`CITATIONS.md`](CITATIONS.md) file.\n\nYou can cite the `nf-core` publication as follows:\n\n> **The nf-core framework for community-curated bioinformatics pipelines.**\n>\n> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen.\n>\n> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x).\n", "hasPart": [ { "@id": "main.nf" @@ -99,7 +99,7 @@ }, "mentions": [ { - "@id": "#caf41f1d-dd5a-4285-b696-7160443f0131" + "@id": "#ee2c0a4f-0aee-4f4b-9e9d-044fdc833c27" } ], "name": "nf-core/spatialxe" @@ -123,7 +123,7 @@ "@id": "main.nf", "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], "dateCreated": "", - "dateModified": "2025-10-16T13:39:03Z", + "dateModified": "2025-11-20T09:32:15Z", "dct:conformsTo": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE/", "keywords": [ "nf-core", @@ -160,11 +160,11 @@ "version": "!>=25.04.0" }, { - "@id": "#caf41f1d-dd5a-4285-b696-7160443f0131", + "@id": "#ee2c0a4f-0aee-4f4b-9e9d-044fdc833c27", "@type": "TestSuite", "instance": [ { - "@id": "#90d899e6-968c-4be9-87db-a2595ede39d4" + "@id": "#ec41083f-c8e9-4bd2-818d-5be534486d2b" } ], "mainEntity": { @@ -173,7 +173,7 @@ "name": "Test suite for nf-core/spatialxe" }, { - "@id": "#90d899e6-968c-4be9-87db-a2595ede39d4", + "@id": "#ec41083f-c8e9-4bd2-818d-5be534486d2b", "@type": "TestInstance", "name": "GitHub Actions workflow for testing nf-core/spatialxe", "resource": "repos/nf-core/spatialxe/actions/workflows/nf-test.yml", diff --git a/subworkflows/local/utils_nfcore_spatialxe_pipeline/main.nf b/subworkflows/local/utils_nfcore_spatialxe_pipeline/main.nf index b3b6bdb9..4e8dde6f 100644 --- a/subworkflows/local/utils_nfcore_spatialxe_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_spatialxe_pipeline/main.nf @@ -39,7 +39,7 @@ workflow PIPELINE_INITIALISATION { main: - ch_versions = Channel.empty() + ch_versions = channel.empty() // // Print version and exit if required and dump pipeline parameters to JSON file @@ -64,7 +64,7 @@ workflow PIPELINE_INITIALISATION { \033[0;35m nf-core/spatialxe ${workflow.manifest.version}\033[0m -\033[2m----------------------------------------------------\033[0m- """ - after_text = """${workflow.manifest.doi ? "\n* The pipeline\n" : ""}${workflow.manifest.doi.tokenize(",").collect { " https://doi.org/${it.trim().replace('https://doi.org/','')}"}.join("\n")}${workflow.manifest.doi ? "\n" : ""} + after_text = """${workflow.manifest.doi ? "\n* The pipeline\n" : ""}${workflow.manifest.doi.tokenize(",").collect { doi -> " https://doi.org/${doi.trim().replace('https://doi.org/','')}"}.join("\n")}${workflow.manifest.doi ? "\n" : ""} * The nf-core framework https://doi.org/10.1038/s41587-020-0439-x @@ -101,7 +101,7 @@ workflow PIPELINE_INITIALISATION { // Create channel from input file provided through params.input // - Channel + channel .fromList(samplesheetToList(params.input, "${projectDir}/assets/schema_input.json")) .map { meta, fastq_1, fastq_2 -> diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/main.nf b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf index bfd25876..2f30e9a4 100644 --- a/subworkflows/nf-core/utils_nfcore_pipeline/main.nf +++ b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf @@ -98,7 +98,7 @@ def workflowVersionToYAML() { // Get channel of software versions used in pipeline in YAML format // def softwareVersionsToYAML(ch_versions) { - return ch_versions.unique().map { version -> processVersionsFromYAML(version) }.unique().mix(Channel.of(workflowVersionToYAML())) + return ch_versions.unique().map { version -> processVersionsFromYAML(version) }.unique().mix(channel.of(workflowVersionToYAML())) } // diff --git a/workflows/spatialxe.nf b/workflows/spatialxe.nf index d6cfb66f..957483fd 100644 --- a/workflows/spatialxe.nf +++ b/workflows/spatialxe.nf @@ -22,8 +22,8 @@ workflow SPATIALXE { ch_samplesheet // channel: samplesheet read in from --input main: - ch_versions = Channel.empty() - ch_multiqc_files = Channel.empty() + ch_versions = channel.empty() + ch_multiqc_files = channel.empty() // // MODULE: Run FastQC // @@ -36,7 +36,25 @@ workflow SPATIALXE { // // Collate and save software versions // - softwareVersionsToYAML(ch_versions) + def topic_versions = Channel.topic("versions") + .distinct() + .branch { entry -> + versions_file: entry instanceof Path + versions_tuple: true + } + + def topic_versions_string = topic_versions.versions_tuple + .map { process, tool, version -> + [ process[process.lastIndexOf(':')+1..-1], " ${tool}: ${version}" ] + } + .groupTuple(by:0) + .map { process, tool_versions -> + tool_versions.unique().sort() + "${process}:\n${tool_versions.join('\n')}" + } + + softwareVersionsToYAML(ch_versions.mix(topic_versions.versions_file)) + .mix(topic_versions_string) .collectFile( storeDir: "${params.outdir}/pipeline_info", name: 'nf_core_' + 'spatialxe_software_' + 'mqc_' + 'versions.yml', @@ -48,24 +66,24 @@ workflow SPATIALXE { // // MODULE: MultiQC // - ch_multiqc_config = Channel.fromPath( + ch_multiqc_config = channel.fromPath( "$projectDir/assets/multiqc_config.yml", checkIfExists: true) ch_multiqc_custom_config = params.multiqc_config ? - Channel.fromPath(params.multiqc_config, checkIfExists: true) : - Channel.empty() + channel.fromPath(params.multiqc_config, checkIfExists: true) : + channel.empty() ch_multiqc_logo = params.multiqc_logo ? - Channel.fromPath(params.multiqc_logo, checkIfExists: true) : - Channel.empty() + channel.fromPath(params.multiqc_logo, checkIfExists: true) : + channel.empty() summary_params = paramsSummaryMap( workflow, parameters_schema: "nextflow_schema.json") - ch_workflow_summary = Channel.value(paramsSummaryMultiqc(summary_params)) + ch_workflow_summary = channel.value(paramsSummaryMultiqc(summary_params)) ch_multiqc_files = ch_multiqc_files.mix( ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) ch_multiqc_custom_methods_description = params.multiqc_methods_description ? file(params.multiqc_methods_description, checkIfExists: true) : file("$projectDir/assets/methods_description_template.yml", checkIfExists: true) - ch_methods_description = Channel.value( + ch_methods_description = channel.value( methodsDescriptionText(ch_multiqc_custom_methods_description)) ch_multiqc_files = ch_multiqc_files.mix(ch_collated_versions) From 2ef69a3d3e47d4c1f684ae86f70f61624fb80a05 Mon Sep 17 00:00:00 2001 From: nf-core-bot Date: Thu, 30 Apr 2026 13:33:31 +0000 Subject: [PATCH 2/4] Template update for nf-core/tools version 4.0.2 --- .devcontainer/devcontainer.json | 1 + .github/CONTRIBUTING.md | 125 -- .github/PULL_REQUEST_TEMPLATE.md | 4 +- .github/actions/get-shards/action.yml | 2 +- .github/actions/nf-test/action.yml | 10 +- .github/workflows/awsfulltest.yml | 25 +- .github/workflows/awstest.yml | 4 +- .github/workflows/branch.yml | 2 +- .github/workflows/clean-up.yml | 2 +- .github/workflows/download_pipeline.yml | 16 +- .github/workflows/fix_linting.yml | 22 +- .github/workflows/linting.yml | 34 +- .github/workflows/linting_comment.yml | 4 +- .github/workflows/nf-test.yml | 8 +- .github/workflows/release-announcements.yml | 4 +- .../workflows/template-version-comment.yml | 6 +- .gitignore | 1 + .nf-core.yml | 4 +- .pre-commit-config.yaml | 16 +- .prettierignore | 2 - CHANGELOG.md | 2 +- README.md | 12 +- assets/adaptivecard.json | 67 - assets/multiqc_config.yml | 4 +- assets/slackreport.json | 34 - conf/base.config | 2 +- conf/containers_conda_lock_files_amd64.config | 2 + conf/containers_conda_lock_files_arm64.config | 2 + conf/containers_docker_amd64.config | 2 + conf/containers_docker_arm64.config | 2 + .../containers_singularity_https_amd64.config | 2 + .../containers_singularity_https_arm64.config | 2 + conf/containers_singularity_oras_amd64.config | 2 + conf/containers_singularity_oras_arm64.config | 2 + docs/CONTRIBUTING.md | 185 ++ docs/usage.md | 8 +- main.nf | 7 +- modules.json | 8 +- .../linux_amd64-bd-5cb1a2fa2f18c7c2_1.txt | 822 +++++++++ .../linux_arm64-bd-e455e32f745abe68_1.txt | 769 ++++++++ modules/nf-core/fastqc/main.nf | 43 +- modules/nf-core/fastqc/meta.yml | 51 +- modules/nf-core/fastqc/tests/main.nf.test | 12 +- .../nf-core/fastqc/tests/main.nf.test.snap | 228 ++- .../linux_amd64-bd-c1f4a7982b743963_1.txt | 1552 +++++++++++++++++ .../linux_amd64-bd-db7c73dae76bc9e6_1.txt | 126 ++ .../linux_arm64-bd-40bf3b435e89dc22_1.txt | 1502 ++++++++++++++++ .../linux_arm64-bd-d167b8012595a136_1.txt | 125 ++ modules/nf-core/multiqc/environment.yml | 2 +- modules/nf-core/multiqc/main.nf | 51 +- modules/nf-core/multiqc/meta.yml | 165 +- .../multiqc/tests/custom_prefix.config | 5 + modules/nf-core/multiqc/tests/main.nf.test | 191 +- .../nf-core/multiqc/tests/main.nf.test.snap | 435 ++++- modules/nf-core/multiqc/tests/nextflow.config | 1 + nextflow.config | 10 +- nextflow_schema.json | 8 - nf-test.config | 26 +- ro-crate-metadata.json | 44 +- .../utils_nfcore_spatialxe_pipeline/main.nf | 17 +- .../nf-core/utils_nfcore_pipeline/main.nf | 66 +- .../utils_nfcore_pipeline/tests/main.nf.test | 29 + .../tests/main.nf.test.snap | 19 + .../nf-core/utils_nfschema_plugin/main.nf | 3 +- .../tests/nextflow.config | 2 +- tests/default.nf.test | 14 +- tests/nextflow.config | 2 +- workflows/spatialxe.nf | 84 +- 68 files changed, 6294 insertions(+), 747 deletions(-) delete mode 100644 .github/CONTRIBUTING.md delete mode 100644 assets/adaptivecard.json delete mode 100644 assets/slackreport.json create mode 100644 conf/containers_conda_lock_files_amd64.config create mode 100644 conf/containers_conda_lock_files_arm64.config create mode 100644 conf/containers_docker_amd64.config create mode 100644 conf/containers_docker_arm64.config create mode 100644 conf/containers_singularity_https_amd64.config create mode 100644 conf/containers_singularity_https_arm64.config create mode 100644 conf/containers_singularity_oras_amd64.config create mode 100644 conf/containers_singularity_oras_arm64.config create mode 100644 docs/CONTRIBUTING.md create mode 100644 modules/nf-core/fastqc/.conda-lock/linux_amd64-bd-5cb1a2fa2f18c7c2_1.txt create mode 100644 modules/nf-core/fastqc/.conda-lock/linux_arm64-bd-e455e32f745abe68_1.txt create mode 100644 modules/nf-core/multiqc/.conda-lock/linux_amd64-bd-c1f4a7982b743963_1.txt create mode 100644 modules/nf-core/multiqc/.conda-lock/linux_amd64-bd-db7c73dae76bc9e6_1.txt create mode 100644 modules/nf-core/multiqc/.conda-lock/linux_arm64-bd-40bf3b435e89dc22_1.txt create mode 100644 modules/nf-core/multiqc/.conda-lock/linux_arm64-bd-d167b8012595a136_1.txt create mode 100644 modules/nf-core/multiqc/tests/custom_prefix.config create mode 100644 subworkflows/nf-core/utils_nfcore_pipeline/tests/main.nf.test create mode 100644 subworkflows/nf-core/utils_nfcore_pipeline/tests/main.nf.test.snap diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 97c8c97f..237c9ed0 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,4 +1,5 @@ { + "$schema": "https://raw.githubusercontent.com/devcontainers/spec/main/schemas/devContainer.schema.json", "name": "nfcore", "image": "nfcore/devcontainer:latest", diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index 6264525d..00000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,125 +0,0 @@ -# `nf-core/spatialxe`: Contributing Guidelines - -Hi there! -Many thanks for taking an interest in improving nf-core/spatialxe. - -We try to manage the required tasks for nf-core/spatialxe using GitHub issues, you probably came to this page when creating one. -Please use the pre-filled template to save time. - -However, don't be put off by this template - other more general issues and suggestions are welcome! -Contributions to the code are even more welcome ;) - -> [!NOTE] -> If you need help using or modifying nf-core/spatialxe then the best place to ask is on the nf-core Slack [#spatialxe](https://nfcore.slack.com/channels/spatialxe) channel ([join our Slack here](https://nf-co.re/join/slack)). - -## Contribution workflow - -If you'd like to write some code for nf-core/spatialxe, the standard workflow is as follows: - -1. Check that there isn't already an issue about your idea in the [nf-core/spatialxe issues](https://github.com/nf-core/spatialxe/issues) to avoid duplicating work. If there isn't one already, please create one so that others know you're working on this -2. [Fork](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) the [nf-core/spatialxe repository](https://github.com/nf-core/spatialxe) to your GitHub account -3. Make the necessary changes / additions within your forked repository following [Pipeline conventions](#pipeline-contribution-conventions) -4. Use `nf-core pipelines schema build` and add any new parameters to the pipeline JSON schema (requires [nf-core tools](https://github.com/nf-core/tools) >= 1.10). -5. Submit a Pull Request against the `dev` branch and wait for the code to be reviewed and merged - -If you're not used to this workflow with git, you can start with some [docs from GitHub](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests) or even their [excellent `git` resources](https://try.github.io/). - -## Tests - -You have the option to test your changes locally by running the pipeline. For receiving warnings about process selectors and other `debug` information, it is recommended to use the debug profile. Execute all the tests with the following command: - -```bash -nf-test test --profile debug,test,docker --verbose -``` - -When you create a pull request with changes, [GitHub Actions](https://github.com/features/actions) will run automatic tests. -Typically, pull-requests are only fully reviewed when these tests are passing, though of course we can help out before then. - -There are typically two types of tests that run: - -### Lint tests - -`nf-core` has a [set of guidelines](https://nf-co.re/developers/guidelines) which all pipelines must adhere to. -To enforce these and ensure that all pipelines stay in sync, we have developed a helper tool which runs checks on the pipeline code. This is in the [nf-core/tools repository](https://github.com/nf-core/tools) and once installed can be run locally with the `nf-core pipelines lint ` command. - -If any failures or warnings are encountered, please follow the listed URL for more documentation. - -### Pipeline tests - -Each `nf-core` pipeline should be set up with a minimal set of test-data. -`GitHub Actions` then runs the pipeline on this data to ensure that it exits successfully. -If there are any failures then the automated tests fail. -These tests are run both with the latest available version of `Nextflow` and also the minimum required version that is stated in the pipeline code. - -## Patch - -:warning: Only in the unlikely and regretful event of a release happening with a bug. - -- On your own fork, make a new branch `patch` based on `upstream/main` or `upstream/master`. -- Fix the bug, and bump version (X.Y.Z+1). -- Open a pull-request from `patch` to `main`/`master` with the changes. - -## Getting help - -For further information/help, please consult the [nf-core/spatialxe documentation](https://nf-co.re/spatialxe/usage) and don't hesitate to get in touch on the nf-core Slack [#spatialxe](https://nfcore.slack.com/channels/spatialxe) channel ([join our Slack here](https://nf-co.re/join/slack)). - -## Pipeline contribution conventions - -To make the `nf-core/spatialxe` code and processing logic more understandable for new contributors and to ensure quality, we semi-standardise the way the code and other contributions are written. - -### Adding a new step - -If you wish to contribute a new step, please use the following coding standards: - -1. Define the corresponding input channel into your new process from the expected previous process channel. -2. Write the process block (see below). -3. Define the output channel if needed (see below). -4. Add any new parameters to `nextflow.config` with a default (see below). -5. Add any new parameters to `nextflow_schema.json` with help text (via the `nf-core pipelines schema build` tool). -6. Add sanity checks and validation for all relevant parameters. -7. Perform local tests to validate that the new code works as expected. -8. If applicable, add a new test in the `tests` directory. -9. Update MultiQC config `assets/multiqc_config.yml` so relevant suffixes, file name clean up and module plots are in the appropriate order. If applicable, add a [MultiQC](https://https://multiqc.info/) module. -10. Add a description of the output files and if relevant any appropriate images from the MultiQC report to `docs/output.md`. - -### Default values - -Parameters should be initialised / defined with default values within the `params` scope in `nextflow.config`. - -Once there, use `nf-core pipelines schema build` to add to `nextflow_schema.json`. - -### Default processes resource requirements - -Sensible defaults for process resource requirements (CPUs / memory / time) for a process should be defined in `conf/base.config`. These should generally be specified generic with `withLabel:` selectors so they can be shared across multiple processes/steps of the pipeline. A nf-core standard set of labels that should be followed where possible can be seen in the [nf-core pipeline template](https://github.com/nf-core/tools/blob/main/nf_core/pipeline-template/conf/base.config), which has the default process as a single core-process, and then different levels of multi-core configurations for increasingly large memory requirements defined with standardised labels. - -The process resources can be passed on to the tool dynamically within the process with the `${task.cpus}` and `${task.memory}` variables in the `script:` block. - -### Naming schemes - -Please use the following naming schemes, to make it easy to understand what is going where. - -- initial process channel: `ch_output_from_` -- intermediate and terminal channels: `ch__for_` - -### Nextflow version bumping - -If you are using a new feature from core Nextflow, you may bump the minimum required version of nextflow in the pipeline with: `nf-core pipelines bump-version --nextflow . [min-nf-version]` - -### Images and figures - -For overview images and other documents we follow the nf-core [style guidelines and examples](https://nf-co.re/developers/design_guidelines). - -## GitHub Codespaces - -This repo includes a devcontainer configuration which will create a GitHub Codespaces for Nextflow development! This is an online developer environment that runs in your browser, complete with VSCode and a terminal. - -To get started: - -- Open the repo in [Codespaces](https://github.com/nf-core/spatialxe/codespaces) -- Tools installed - - nf-core - - Nextflow - -Devcontainer specs: - -- [DevContainer config](.devcontainer/devcontainer.json) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 22dd1e1c..6a378da4 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -8,14 +8,14 @@ These are the most common things requested on pull requests (PRs). Remember that PRs should be made against the dev branch, unless you're preparing a pipeline release. -Learn more about contributing: [CONTRIBUTING.md](https://github.com/nf-core/spatialxe/tree/master/.github/CONTRIBUTING.md) +Learn more about contributing: [CONTRIBUTING.md](https://github.com/nf-core/spatialxe/tree/master/docs/CONTRIBUTING.md) --> ## PR checklist - [ ] This comment contains a description of changes (with reason). - [ ] If you've fixed a bug or added code that should be tested, add tests! -- [ ] If you've added a new tool - have you followed the pipeline conventions in the [contribution docs](https://github.com/nf-core/spatialxe/tree/master/.github/CONTRIBUTING.md) +- [ ] If you've added a new tool - have you followed the pipeline conventions in the [contribution docs](https://github.com/nf-core/spatialxe/tree/master/docs/CONTRIBUTING.md) - [ ] If necessary, also make a PR on the nf-core/spatialxe _branch_ on the [nf-core/test-datasets](https://github.com/nf-core/test-datasets) repository. - [ ] Make sure your code lints (`nf-core pipelines lint`). - [ ] Ensure the test suite passes (`nextflow run . -profile test,docker --outdir `). diff --git a/.github/actions/get-shards/action.yml b/.github/actions/get-shards/action.yml index 34085279..e2833ee9 100644 --- a/.github/actions/get-shards/action.yml +++ b/.github/actions/get-shards/action.yml @@ -21,7 +21,7 @@ runs: using: "composite" steps: - name: Install nf-test - uses: nf-core/setup-nf-test@v1 + uses: nf-core/setup-nf-test@4069fbbaabe94c08faba4ad261bfa88225ba133f # v2 with: version: ${{ env.NFT_VER }} - name: Get number of shards diff --git a/.github/actions/nf-test/action.yml b/.github/actions/nf-test/action.yml index 3b9724c7..ad686e8e 100644 --- a/.github/actions/nf-test/action.yml +++ b/.github/actions/nf-test/action.yml @@ -20,24 +20,24 @@ runs: using: "composite" steps: - name: Setup Nextflow - uses: nf-core/setup-nextflow@v2 + uses: nf-core/setup-nextflow@b4ec1bc7c16a94435159de94a05253542fddf6ef # v3 with: version: "${{ env.NXF_VERSION }}" - name: Set up Python - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: "3.14" - name: Install nf-test - uses: nf-core/setup-nf-test@v1 + uses: nf-core/setup-nf-test@4069fbbaabe94c08faba4ad261bfa88225ba133f # v2 with: version: "${{ env.NFT_VER }}" install-pdiff: true - name: Setup apptainer if: contains(inputs.profile, 'singularity') - uses: eWaterCycle/setup-apptainer@main + uses: eWaterCycle/setup-apptainer@3f706d898c9db585b1d741b4692e66755f3a1b40 # v2 - name: Set up Singularity if: contains(inputs.profile, 'singularity') @@ -48,7 +48,7 @@ runs: - name: Conda setup if: contains(inputs.profile, 'conda') - uses: conda-incubator/setup-miniconda@505e6394dae86d6a5c7fbb6e3fb8938e3e863830 # v3 + uses: conda-incubator/setup-miniconda@8ee1f361103df19b6f8c8655fd3967a8ecb162d5 # v4 with: auto-update-conda: true conda-solver: libmamba diff --git a/.github/workflows/awsfulltest.yml b/.github/workflows/awsfulltest.yml index 78fdaf9d..5eb53aee 100644 --- a/.github/workflows/awsfulltest.yml +++ b/.github/workflows/awsfulltest.yml @@ -23,7 +23,7 @@ jobs: echo "revision=${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'release') && github.sha || 'dev' }}" >> "$GITHUB_OUTPUT" - name: Launch workflow via Seqera Platform - uses: seqeralabs/action-tower-launch@v2 + uses: seqeralabs/action-tower-launch@51565b514bff1827cf34620de25d0055759f1fc9 # v2 # TODO nf-core: You can customise AWS full pipeline tests as required # Add full size test data (but still relatively small datasets for few samples) # on the `test_full.config` test runs with only one set of parameters @@ -33,14 +33,33 @@ jobs: compute_env: ${{ vars.TOWER_COMPUTE_ENV }} revision: ${{ steps.revision.outputs.revision }} workdir: s3://${{ vars.AWS_S3_BUCKET }}/work/spatialxe/work-${{ steps.revision.outputs.revision }} + nextflow_config: | + plugins { + id 'nf-slack@0.5.0' + } + slack { + enabled = true + bot { + token = '${{ secrets.NFSLACK_BOT_TOKEN }}' + channel = 'spatialxe' + } + onStart { + enabled = false + } + onComplete { + message = ':white_check_mark: *spatialxe/test_full* completed successfully! :tada:' + } + onError { + message = ':x: *spatialxe/test_full* failed :crying_cat_face:' + } + } parameters: | { - "hook_url": "${{ secrets.MEGATESTS_ALERTS_SLACK_HOOK_URL }}", "outdir": "s3://${{ vars.AWS_S3_BUCKET }}/spatialxe/results-${{ steps.revision.outputs.revision }}" } profiles: test_full - - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: Seqera Platform debug log file path: | diff --git a/.github/workflows/awstest.yml b/.github/workflows/awstest.yml index 47dfba52..632a6a5e 100644 --- a/.github/workflows/awstest.yml +++ b/.github/workflows/awstest.yml @@ -12,7 +12,7 @@ jobs: steps: # Launch workflow using Seqera Platform CLI tool action - name: Launch workflow via Seqera Platform - uses: seqeralabs/action-tower-launch@v2 + uses: seqeralabs/action-tower-launch@51565b514bff1827cf34620de25d0055759f1fc9 # v2 with: workspace_id: ${{ vars.TOWER_WORKSPACE_ID }} access_token: ${{ secrets.TOWER_ACCESS_TOKEN }} @@ -25,7 +25,7 @@ jobs: } profiles: test - - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: Seqera Platform debug log file path: | diff --git a/.github/workflows/branch.yml b/.github/workflows/branch.yml index 4f79e0f9..873c6700 100644 --- a/.github/workflows/branch.yml +++ b/.github/workflows/branch.yml @@ -21,7 +21,7 @@ jobs: # NOTE - this doesn't currently work if the PR is coming from a fork, due to limitations in GitHub actions secrets - name: Post PR comment if: failure() - uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2 + uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3 with: message: | ## This PR is against the `${{github.event.pull_request.base.ref}}` branch :x: diff --git a/.github/workflows/clean-up.yml b/.github/workflows/clean-up.yml index 6adb0fff..172de6f3 100644 --- a/.github/workflows/clean-up.yml +++ b/.github/workflows/clean-up.yml @@ -10,7 +10,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10 + - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10 with: stale-issue-message: "This issue has been tagged as awaiting-changes or awaiting-feedback by an nf-core contributor. Remove stale label or add a comment otherwise this issue will be closed in 20 days." stale-pr-message: "This PR has been tagged as awaiting-changes or awaiting-feedback by an nf-core contributor. Remove stale label or add a comment if it is still useful." diff --git a/.github/workflows/download_pipeline.yml b/.github/workflows/download_pipeline.yml index 45884ff9..a7bf4fc2 100644 --- a/.github/workflows/download_pipeline.yml +++ b/.github/workflows/download_pipeline.yml @@ -38,13 +38,16 @@ jobs: runs-on: ubuntu-latest needs: configure steps: + - name: Check out pipeline code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - name: Install Nextflow - uses: nf-core/setup-nextflow@v2 + uses: nf-core/setup-nextflow@b4ec1bc7c16a94435159de94a05253542fddf6ef # v3 - name: Disk space cleanup uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: "3.14" architecture: "x64" @@ -54,10 +57,15 @@ jobs: with: apptainer-version: 1.3.4 + - name: Read .nf-core.yml + id: read_yml + run: | + echo "nf_core_version=$(yq '.nf_core_version' ${{ github.workspace }}/.nf-core.yml)" >> "$GITHUB_OUTPUT" + - name: Install dependencies run: | python -m pip install --upgrade pip - pip install git+https://github.com/nf-core/tools.git + pip install nf-core==${{ steps.read_yml.outputs['nf_core_version'] }} - name: Make a cache directory for the container images run: | @@ -127,7 +135,7 @@ jobs: fi - name: Upload Nextflow logfile for debugging purposes - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: nextflow_logfile.txt path: .nextflow.log* diff --git a/.github/workflows/fix_linting.yml b/.github/workflows/fix_linting.yml index 8394ba2f..e9c90ca8 100644 --- a/.github/workflows/fix_linting.yml +++ b/.github/workflows/fix_linting.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: # Use the @nf-core-bot token to check out so we can push later - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: token: ${{ secrets.nf_core_bot_auth_token }} @@ -31,22 +31,18 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.nf_core_bot_auth_token }} - # Install and run pre-commit - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 - with: - python-version: "3.14" - - - name: Install pre-commit - run: pip install pre-commit + - name: Install Nextflow + uses: nf-core/setup-nextflow@b4ec1bc7c16a94435159de94a05253542fddf6ef # v3 - - name: Run pre-commit - id: pre-commit - run: pre-commit run --all-files + # Install and run prek + - name: Run prek + id: prek + uses: j178/prek-action@6ad80277337ad479fe43bd70701c3f7f8aa74db3 # v2 continue-on-error: true # indication that the linting has finished - name: react if linting finished succesfully - if: steps.pre-commit.outcome == 'success' + if: steps.prek.outcome == 'success' uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5 with: comment-id: ${{ github.event.comment.id }} @@ -54,7 +50,7 @@ jobs: - name: Commit & push changes id: commit-and-push - if: steps.pre-commit.outcome == 'failure' + if: steps.prek.outcome == 'failure' run: | git config user.email "core@nf-co.re" git config user.name "nf-core-bot" diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 7a527a34..8738ffc9 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -11,33 +11,31 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Set up Python 3.14 - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 - with: - python-version: "3.14" - - - name: Install pre-commit - run: pip install pre-commit + - name: Install Nextflow + uses: nf-core/setup-nextflow@b4ec1bc7c16a94435159de94a05253542fddf6ef # v3 - - name: Run pre-commit - run: pre-commit run --all-files + - name: Run prek + uses: j178/prek-action@6ad80277337ad479fe43bd70701c3f7f8aa74db3 # v2 nf-core: runs-on: ubuntu-latest steps: - name: Check out pipeline code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install Nextflow - uses: nf-core/setup-nextflow@v2 + uses: nf-core/setup-nextflow@b4ec1bc7c16a94435159de94a05253542fddf6ef # v3 - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: "3.14" architecture: "x64" + - name: Setup uv + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + - name: read .nf-core.yml uses: pietrobolcato/action-read-yaml@9f13718d61111b69f30ab4ac683e67a56d254e1d # 1.1.0 id: read_yml @@ -45,12 +43,10 @@ jobs: config: ${{ github.workspace }}/.nf-core.yml - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install nf-core==${{ steps.read_yml.outputs['nf_core_version'] }} + run: uv tool install nf-core==${{ steps.read_yml.outputs['nf_core_version'] }} - name: Run nf-core pipelines lint - if: ${{ github.base_ref != 'master' }} + if: ${{ github.base_ref != 'master' || github.base_ref != 'main' }} env: GITHUB_COMMENTS_URL: ${{ github.event.pull_request.comments_url }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -58,7 +54,7 @@ jobs: run: nf-core -l lint_log.txt pipelines lint --dir ${GITHUB_WORKSPACE} --markdown lint_results.md - name: Run nf-core pipelines lint --release - if: ${{ github.base_ref == 'master' }} + if: ${{ github.base_ref == 'master' || github.base_ref == 'main' }} env: GITHUB_COMMENTS_URL: ${{ github.event.pull_request.comments_url }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -71,7 +67,7 @@ jobs: - name: Upload linting log file artifact if: ${{ always() }} - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: linting-logs path: | diff --git a/.github/workflows/linting_comment.yml b/.github/workflows/linting_comment.yml index e6e9bc26..5b0c24f7 100644 --- a/.github/workflows/linting_comment.yml +++ b/.github/workflows/linting_comment.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download lint results - uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 # v11 + uses: dawidd6/action-download-artifact@b6e2e70617bc3265edd6dab6c906732b2f1ae151 # v21 with: workflow: linting.yml workflow_conclusion: completed @@ -21,7 +21,7 @@ jobs: run: echo "pr_number=$(cat linting-logs/PR_number.txt)" >> $GITHUB_OUTPUT - name: Post PR comment - uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2 + uses: marocchino/sticky-pull-request-comment@70d2764d1a7d5d9560b100cbea0077fc8f633987 # v3 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} number: ${{ steps.pr_number.outputs.pr_number }} diff --git a/.github/workflows/nf-test.yml b/.github/workflows/nf-test.yml index c98d76ec..efd72d65 100644 --- a/.github/workflows/nf-test.yml +++ b/.github/workflows/nf-test.yml @@ -18,7 +18,7 @@ concurrency: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NFT_VER: "0.9.3" + NFT_VER: "0.9.4" NFT_WORKDIR: "~" NXF_ANSI_LOG: false NXF_SINGULARITY_CACHEDIR: ${{ github.workspace }}/.singularity @@ -40,7 +40,7 @@ jobs: rm -rf ./* || true rm -rf ./.??* || true ls -la ./ - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 @@ -78,14 +78,14 @@ jobs: - isMain: false profile: "singularity" NXF_VER: - - "25.04.0" + - "25.10.4" - "latest-everything" env: NXF_ANSI_LOG: false TOTAL_SHARDS: ${{ needs.nf-test-changes.outputs.total_shards }} steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 diff --git a/.github/workflows/release-announcements.yml b/.github/workflows/release-announcements.yml index 431d3d44..78d5dbe0 100644 --- a/.github/workflows/release-announcements.yml +++ b/.github/workflows/release-announcements.yml @@ -18,7 +18,7 @@ jobs: id: get_description run: | echo "description=$(curl -s https://nf-co.re/pipelines.json | jq -r '.remote_workflows[] | select(.full_name == "${{ github.repository }}") | .description')" >> $GITHUB_OUTPUT - - uses: rzr/fediverse-action@master + - uses: rzr/fediverse-action@563159eb8d45f70ab6aaba36ed55cd037e51f441 # master with: access-token: ${{ secrets.MASTODON_ACCESS_TOKEN }} host: "mstdn.science" # custom host if not "mastodon.social" (default) @@ -34,7 +34,7 @@ jobs: bsky-post: runs-on: ubuntu-latest steps: - - uses: zentered/bluesky-post-action@6461056ea355ea43b977e149f7bf76aaa572e5e8 # v0.3.0 + - uses: zentered/bluesky-post-action@5a91cc2ad10a304a4e96c16182dbe4918710bcf6 # v0.4.0 with: post: | Pipeline release! ${{ github.repository }} v${{ github.event.release.tag_name }} - ${{ github.event.release.name }}! diff --git a/.github/workflows/template-version-comment.yml b/.github/workflows/template-version-comment.yml index e8560fc7..ea30827e 100644 --- a/.github/workflows/template-version-comment.yml +++ b/.github/workflows/template-version-comment.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out pipeline code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ github.event.pull_request.head.sha }} @@ -29,7 +29,7 @@ jobs: run: echo "OUTPUT=$(pip list --outdated | grep nf-core)" >> ${GITHUB_ENV} - name: Post nf-core template version comment - uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2 + uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3 if: | contains(env.OUTPUT, 'nf-core') with: @@ -42,5 +42,5 @@ jobs: > Your pipeline is using an old version of the nf-core template: ${{ steps.read_yml.outputs['nf_core_version'] }}. > Please update your pipeline to the latest version. > - > For more documentation on how to update your pipeline, please see the [nf-core documentation](https://github.com/nf-core/tools?tab=readme-ov-file#sync-a-pipeline-with-the-template) and [Synchronisation documentation](https://nf-co.re/docs/contributing/sync). + > For more documentation on how to update your pipeline, please see the [Synchronisation documentation](https://nf-co.re/docs/developing/template-syncs/overview). # diff --git a/.gitignore b/.gitignore index a42ce016..cc2b1a77 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ testing/ testing* *.pyc null/ +.lineage/ diff --git a/.nf-core.yml b/.nf-core.yml index 79809cf6..ae563bd5 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -8,7 +8,7 @@ lint: - docs/images/nf-core-spatialxe_logo_dark.png - docs/images/nf-core-spatialxe_logo_light.png - .github/PULL_REQUEST_TEMPLATE.md -nf_core_version: 3.5.1 +nf_core_version: 4.0.2 repository_type: pipeline template: author: Sameesh Kher, Florian Heyl @@ -18,4 +18,4 @@ template: name: spatialxe org: nf-core outdir: . - version: 1.0dev + version: 1.0.0 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d06777a8..f51e1a28 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: hooks: - id: prettier additional_dependencies: - - prettier@3.6.2 + - prettier@3.8.3 - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: @@ -13,15 +13,21 @@ repos: exclude: | (?x)^( .*ro-crate-metadata.json$| - modules/nf-core/.*| - subworkflows/nf-core/.*| + modules/(?!local/).*| + subworkflows/(?!local/).*| .*\.snap$ )$ - id: end-of-file-fixer exclude: | (?x)^( .*ro-crate-metadata.json$| - modules/nf-core/.*| - subworkflows/nf-core/.*| + modules/(?!local/).*| + subworkflows/(?!local/).*| .*\.snap$ )$ + - repo: https://github.com/seqeralabs/nf-lint-pre-commit + rev: v0.3.0 + hooks: + - id: nextflow-lint + files: '\.nf$|nextflow\.config$' + args: ["-output", "json"] diff --git a/.prettierignore b/.prettierignore index dd749d43..63cde500 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,4 @@ email_template.html -adaptivecard.json -slackreport.json .nextflow* work/ data/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 86cab63a..9f0d1258 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## v1.0dev - [date] +## v1.0.0 - [date] Initial release of nf-core/spatialxe, created with the [nf-core](https://nf-co.re/) template. diff --git a/README.md b/README.md index 09cb2108..f3f2cbde 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ [![GitHub Actions Linting Status](https://github.com/nf-core/spatialxe/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/spatialxe/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/spatialxe/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX) [![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com) -[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.04.0-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/) -[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.5.1-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.5.1) +[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.10.4-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/) +[![nf-core template version](https://img.shields.io/badge/nf--core_template-4.0.2-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/4.0.2) [![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/) [![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/) [![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/) @@ -30,13 +30,13 @@ --> + workflows use the "tube map" design for that. See https://nf-co.re/docs/community/brand/workflow-schematics#examples for examples. --> 1. Read QC ([`FastQC`](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/))2. Present QC for raw reads ([`MultiQC`](http://multiqc.info/)) ## Usage > [!NOTE] -> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data. +> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/get_started/environment_setup/overview) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/get_started/run-your-first-pipeline) with `-profile test` before running the workflow on actual data. diff --git a/docs/usage.md b/docs/usage.md index 10742d8b..9050ce93 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -76,7 +76,7 @@ If you wish to repeatedly use the same parameters for multiple runs, rather than Pipeline settings can be provided in a `yaml` or `json` file via `-params-file `. > [!WARNING] -> Do not use `-c ` to specify parameters as this will result in errors. Custom config files specified with `-c` must only be used for [tuning process resource specifications](https://nf-co.re/docs/usage/configuration#tuning-workflow-resources), other infrastructural tweaks (such as output directories), or module arguments (args). +> Do not use `-c ` to specify parameters as this will result in errors. Custom config files specified with `-c` must only be used for [tuning process resource specifications](https://nf-co.re/docs/running/run-pipelines#configuring-pipelines), other infrastructural tweaks (such as output directories), or module arguments (args). The above pipeline run specified with a params file in yaml format: @@ -173,19 +173,19 @@ Specify the path to a specific config file (this is a core Nextflow command). Se Whilst the default requirements set within the pipeline will hopefully work for most people and with most input data, you may find that you want to customise the compute resources that the pipeline requests. Each step in the pipeline has a default set of requirements for number of CPUs, memory and time. For most of the pipeline steps, if the job exits with any of the error codes specified [here](https://github.com/nf-core/rnaseq/blob/4c27ef5610c87db00c3c5a3eed10b1d161abf575/conf/base.config#L18) it will automatically be resubmitted with higher resources request (2 x original, then 3 x original). If it still fails after the third attempt then the pipeline execution is stopped. -To change the resource requests, please see the [max resources](https://nf-co.re/docs/usage/configuration#max-resources) and [tuning workflow resources](https://nf-co.re/docs/usage/configuration#tuning-workflow-resources) section of the nf-core website. +To change the resource requests, please see the [max resources](https://nf-co.re/docs/running/configuration/nextflow-for-your-system#set-max-resources) and [customise process resources](https://nf-co.re/docs/running/configuration/nextflow-for-your-system#customize-process-resources) section of the nf-core website. ### Custom Containers In some cases, you may wish to change the container or conda environment used by a pipeline steps for a particular tool. By default, nf-core pipelines use containers and software from the [biocontainers](https://biocontainers.pro/) or [bioconda](https://bioconda.github.io/) projects. However, in some cases the pipeline specified version maybe out of date. -To use a different container from the default container or conda environment specified in a pipeline, please see the [updating tool versions](https://nf-co.re/docs/usage/configuration#updating-tool-versions) section of the nf-core website. +To use a different container from the default container or conda environment specified in a pipeline, please see the [updating tool versions](https://nf-co.re/docs/running/configuration/nextflow-for-your-system#update-tool-versions) section of the nf-core website. ### Custom Tool Arguments A pipeline might not always support every possible argument or option of a particular tool used in pipeline. Fortunately, nf-core pipelines provide some freedom to users to insert additional parameters that the pipeline does not include by default. -To learn how to provide additional arguments to a particular tool of the pipeline, please see the [customising tool arguments](https://nf-co.re/docs/usage/configuration#customising-tool-arguments) section of the nf-core website. +To learn how to provide additional arguments to a particular tool of the pipeline, please see the [customising tool arguments](https://nf-co.re/docs/running/configuration/nextflow-for-your-system#modifying-tool-arguments) section of the nf-core website. ### nf-core/configs diff --git a/main.nf b/main.nf index a44ae785..0ed1167a 100644 --- a/main.nf +++ b/main.nf @@ -51,7 +51,11 @@ workflow NFCORE_SPATIALXE { // WORKFLOW: Run pipeline // SPATIALXE ( - samplesheet + samplesheet, + params.multiqc_config, + params.multiqc_logo, + params.multiqc_methods_description, + params.outdir, ) emit: multiqc_report = SPATIALXE.out.multiqc_report // channel: /path/to/multiqc_report.html @@ -95,7 +99,6 @@ workflow { params.plaintext_email, params.outdir, params.monochrome_logs, - params.hook_url, NFCORE_SPATIALXE.out.multiqc_report ) } diff --git a/modules.json b/modules.json index f5682e24..09082eaf 100644 --- a/modules.json +++ b/modules.json @@ -7,12 +7,12 @@ "nf-core": { "fastqc": { "branch": "master", - "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", + "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", "installed_by": ["modules"] }, "multiqc": { "branch": "master", - "git_sha": "af27af1be706e6a2bb8fe454175b0cdf77f47b49", + "git_sha": "008f9d3e61209bf995edac3ba531f54e269e1215", "installed_by": ["modules"] } } @@ -26,12 +26,12 @@ }, "utils_nfcore_pipeline": { "branch": "master", - "git_sha": "271e7fc14eb1320364416d996fb077421f3faed2", + "git_sha": "a3fb7351b1fdb2b1de282b765816bbea190e86a8", "installed_by": ["subworkflows"] }, "utils_nfschema_plugin": { "branch": "master", - "git_sha": "4b406a74dc0449c0401ed87d5bfff4252fd277fd", + "git_sha": "fdc08b8b1ae74f56686ce21f7ea11ad11990ce57", "installed_by": ["subworkflows"] } } diff --git a/modules/nf-core/fastqc/.conda-lock/linux_amd64-bd-5cb1a2fa2f18c7c2_1.txt b/modules/nf-core/fastqc/.conda-lock/linux_amd64-bd-5cb1a2fa2f18c7c2_1.txt new file mode 100644 index 00000000..7770ccd5 --- /dev/null +++ b/modules/nf-core/fastqc/.conda-lock/linux_amd64-bd-5cb1a2fa2f18c7c2_1.txt @@ -0,0 +1,822 @@ + +version: 6 +environments: +default: +channels: +- url: https://conda.anaconda.org/conda-forge/ +- url: https://conda.anaconda.org/bioconda/ +- url: https://conda.anaconda.org/bioconda/ +options: +pypi-prerelease-mode: if-necessary-or-explicit +packages: +linux-64: +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.15.3-hb03c661_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-he90730b_1.conda +- conda: https://conda.anaconda.org/bioconda/noarch/fastqc-0.12.1-hdfd78af_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.17.1-h27c8c51_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.14-hecca717_2.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-13.2.1-h6083320_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.18-h0c24ade_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h7a8fb5f_6.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.3-ha770c72_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.3-h73754d4_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.4-h6548e54_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.55-h421ea60_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/openjdk-25.0.2-ha668962_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/perl-5.32.1-7_hd590300_perl5.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/procps-ng-4.0.6-h18c060e_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.13-he1eb515_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.7-hb03c661_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-6.0.2-hb03c661_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxi-1.8.2-hb9d3cd8_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrandr-1.5.5-hb03c661_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxt-1.3.1-hb9d3cd8_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxtst-1.2.5-hb9d3cd8_3.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda +packages: +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda +build_number: 20 +sha256: 1dd3fffd892081df9726d7eb7e0dea6198962ba775bd88842135a4ddb4deb3c9 +md5: a9f577daf3de00bca7c3c76c0ecbd1de +depends: +- __glibc >=2.17,<3.0.a0 +- libgomp >=7.5.0 +constrains: +- openmp_impl <0.0a0 +license: BSD-3-Clause +license_family: BSD +size: 28948 +timestamp: 1770939786096 +- conda: https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.15.3-hb03c661_0.conda +sha256: d88aa7ae766cf584e180996e92fef2aa7d8e0a0a5ab1d4d49c32390c1b5fff31 +md5: dcdc58c15961dbf17a0621312b01f5cb +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +license: LGPL-2.1-or-later +license_family: GPL +size: 584660 +timestamp: 1768327524772 +- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda +sha256: 0b75d45f0bba3e95dc693336fa51f40ea28c980131fec438afb7ce6118ed05f6 +md5: d2ffd7602c02f2b316fd921d39876885 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +license: bzip2-1.0.6 +license_family: BSD +size: 260182 +timestamp: 1771350215188 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda +sha256: 67cc7101b36421c5913a1687ef1b99f85b5d6868da3abbf6ec1a4181e79782fc +md5: 4492fd26db29495f0ba23f146cd5638d +depends: +- __unix +license: ISC +size: 147413 +timestamp: 1772006283803 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-he90730b_1.conda +sha256: 06525fa0c4e4f56e771a3b986d0fdf0f0fc5a3270830ee47e127a5105bde1b9a +md5: bb6c4808bfa69d6f7f6b07e5846ced37 +depends: +- __glibc >=2.17,<3.0.a0 +- fontconfig >=2.15.0,<3.0a0 +- fonts-conda-ecosystem +- icu >=78.1,<79.0a0 +- libexpat >=2.7.3,<3.0a0 +- libfreetype >=2.14.1 +- libfreetype6 >=2.14.1 +- libgcc >=14 +- libglib >=2.86.3,<3.0a0 +- libpng >=1.6.53,<1.7.0a0 +- libstdcxx >=14 +- libxcb >=1.17.0,<2.0a0 +- libzlib >=1.3.1,<2.0a0 +- pixman >=0.46.4,<1.0a0 +- xorg-libice >=1.1.2,<2.0a0 +- xorg-libsm >=1.2.6,<2.0a0 +- xorg-libx11 >=1.8.12,<2.0a0 +- xorg-libxext >=1.3.6,<2.0a0 +- xorg-libxrender >=0.9.12,<0.10.0a0 +license: LGPL-2.1-only or MPL-1.1 +size: 989514 +timestamp: 1766415934926 +- conda: https://conda.anaconda.org/bioconda/noarch/fastqc-0.12.1-hdfd78af_0.tar.bz2 +sha256: 7cc26225d590540ae95cd24940ff42f2da7479dd4cd22ae9ab9298665d06790c +md5: c9f6a4b12229f7331f79c9a00dd6e240 +depends: +- font-ttf-dejavu-sans-mono +- fontconfig +- openjdk >=8.0.144 +- perl +license: GPL >=3 +size: 11664291 +timestamp: 1677946722445 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 +sha256: 58d7f40d2940dd0a8aa28651239adbf5613254df0f75789919c4e6762054403b +md5: 0c96522c6bdaed4b1566d11387caaf45 +license: BSD-3-Clause +license_family: BSD +size: 397370 +timestamp: 1566932522327 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 +sha256: c52a29fdac682c20d252facc50f01e7c2e7ceac52aa9817aaf0bb83f7559ec5c +md5: 34893075a5c9e55cdafac56607368fc6 +license: OFL-1.1 +license_family: Other +size: 96530 +timestamp: 1620479909603 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 +sha256: 00925c8c055a2275614b4d983e1df637245e19058d79fc7dd1a93b8d9fb4b139 +md5: 4d59c254e01d9cde7957100457e2d5fb +license: OFL-1.1 +license_family: Other +size: 700814 +timestamp: 1620479612257 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda +sha256: 2821ec1dc454bd8b9a31d0ed22a7ce22422c0aef163c59f49dfdf915d0f0ca14 +md5: 49023d73832ef61042f6a237cb2687e7 +license: LicenseRef-Ubuntu-Font-Licence-Version-1.0 +license_family: Other +size: 1620504 +timestamp: 1727511233259 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.17.1-h27c8c51_0.conda +sha256: aa4a44dba97151221100a637c7f4bde619567afade9c0265f8e1c8eed8d7bd8c +md5: 867127763fbe935bab59815b6e0b7b5c +depends: +- __glibc >=2.17,<3.0.a0 +- libexpat >=2.7.4,<3.0a0 +- libfreetype >=2.14.1 +- libfreetype6 >=2.14.1 +- libgcc >=14 +- libuuid >=2.41.3,<3.0a0 +- libzlib >=1.3.1,<2.0a0 +license: MIT +license_family: MIT +size: 270705 +timestamp: 1771382710863 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 +sha256: a997f2f1921bb9c9d76e6fa2f6b408b7fa549edd349a77639c9fe7a23ea93e61 +md5: fee5683a3f04bd15cbd8318b096a27ab +depends: +- fonts-conda-forge +license: BSD-3-Clause +license_family: BSD +size: 3667 +timestamp: 1566974674465 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda +sha256: 54eea8469786bc2291cc40bca5f46438d3e062a399e8f53f013b6a9f50e98333 +md5: a7970cd949a077b7cb9696379d338681 +depends: +- font-ttf-ubuntu +- font-ttf-inconsolata +- font-ttf-dejavu-sans-mono +- font-ttf-source-code-pro +license: BSD-3-Clause +license_family: BSD +size: 4059 +timestamp: 1762351264405 +- conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda +sha256: aac402a8298f0c0cc528664249170372ef6b37ac39fdc92b40601a6aed1e32ff +md5: 3bf7b9fd5a7136126e0234db4b87c8b6 +depends: +- libgcc-ng >=12 +license: MIT +license_family: MIT +size: 77248 +timestamp: 1712692454246 +- conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.14-hecca717_2.conda +sha256: 25ba37da5c39697a77fce2c9a15e48cf0a84f1464ad2aafbe53d8357a9f6cc8c +md5: 2cd94587f3a401ae05e03a6caf09539d +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libstdcxx >=14 +license: LGPL-2.0-or-later +license_family: LGPL +size: 99596 +timestamp: 1755102025473 +- conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-13.2.1-h6083320_0.conda +sha256: 477f2c553f72165020d3c56740ba354be916c2f0b76fd9f535e83d698277d5ec +md5: 14470902326beee192e33719a2e8bb7f +depends: +- __glibc >=2.17,<3.0.a0 +- cairo >=1.18.4,<2.0a0 +- graphite2 >=1.3.14,<2.0a0 +- icu >=78.3,<79.0a0 +- libexpat >=2.7.4,<3.0a0 +- libfreetype >=2.14.2 +- libfreetype6 >=2.14.2 +- libgcc >=14 +- libglib >=2.86.4,<3.0a0 +- libstdcxx >=14 +- libzlib >=1.3.2,<2.0a0 +license: MIT +license_family: MIT +size: 2384060 +timestamp: 1774276284520 +- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda +sha256: fbf86c4a59c2ed05bbffb2ba25c7ed94f6185ec30ecb691615d42342baa1a16a +md5: c80d8a3b84358cb967fa81e7075fbc8a +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libstdcxx >=14 +license: MIT +license_family: MIT +size: 12723451 +timestamp: 1773822285671 +- conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda +sha256: 0960d06048a7185d3542d850986d807c6e37ca2e644342dd0c72feefcf26c2a4 +md5: b38117a3c920364aff79f870c984b4a3 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=13 +license: LGPL-2.1-or-later +size: 134088 +timestamp: 1754905959823 +- conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda +sha256: 3e307628ca3527448dd1cb14ad7bb9d04d1d28c7d4c5f97ba196ae984571dd25 +md5: fb53fb07ce46a575c5d004bbc96032c2 +depends: +- __glibc >=2.17,<3.0.a0 +- keyutils >=1.6.3,<2.0a0 +- libedit >=3.1.20250104,<3.2.0a0 +- libedit >=3.1.20250104,<4.0a0 +- libgcc >=14 +- libstdcxx >=14 +- openssl >=3.5.5,<4.0a0 +license: MIT +license_family: MIT +size: 1386730 +timestamp: 1769769569681 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.18-h0c24ade_0.conda +sha256: 836ec4b895352110335b9fdcfa83a8dcdbe6c5fb7c06c4929130600caea91c0a +md5: 6f2e2c8f58160147c4d1c6f4c14cbac4 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libjpeg-turbo >=3.1.2,<4.0a0 +- libtiff >=4.7.1,<4.8.0a0 +license: MIT +license_family: MIT +size: 249959 +timestamp: 1768184673131 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda +sha256: f84cb54782f7e9cea95e810ea8fef186e0652d0fa73d3009914fa2c1262594e1 +md5: a752488c68f2e7c456bcbd8f16eec275 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libstdcxx >=14 +license: Apache-2.0 +license_family: Apache +size: 261513 +timestamp: 1773113328888 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h7a8fb5f_6.conda +sha256: 205c4f19550f3647832ec44e35e6d93c8c206782bdd620c1d7cf66237580ff9c +md5: 49c553b47ff679a6a1e9fc80b9c5a2d4 +depends: +- __glibc >=2.17,<3.0.a0 +- krb5 >=1.22.2,<1.23.0a0 +- libgcc >=14 +- libstdcxx >=14 +- libzlib >=1.3.1,<2.0a0 +license: Apache-2.0 +license_family: Apache +size: 4518030 +timestamp: 1770902209173 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda +sha256: aa8e8c4be9a2e81610ddf574e05b64ee131fab5e0e3693210c9d6d2fba32c680 +md5: 6c77a605a7a689d17d4819c0f8ac9a00 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +license: MIT +license_family: MIT +size: 73490 +timestamp: 1761979956660 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda +sha256: d789471216e7aba3c184cd054ed61ce3f6dac6f87a50ec69291b9297f8c18724 +md5: c277e0a4d549b03ac1e9d6cbbe3d017b +depends: +- ncurses +- __glibc >=2.17,<3.0.a0 +- libgcc >=13 +- ncurses >=6.5,<7.0a0 +license: BSD-2-Clause +license_family: BSD +size: 134676 +timestamp: 1738479519902 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda +sha256: d78f1d3bea8c031d2f032b760f36676d87929b18146351c4464c66b0869df3f5 +md5: e7f7ce06ec24cfcfb9e36d28cf82ba57 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +constrains: +- expat 2.7.4.* +license: MIT +license_family: MIT +size: 76798 +timestamp: 1771259418166 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda +sha256: 31f19b6a88ce40ebc0d5a992c131f57d919f73c0b92cd1617a5bec83f6e961e6 +md5: a360c33a5abe61c07959e449fa1453eb +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +license: MIT +license_family: MIT +size: 58592 +timestamp: 1769456073053 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.3-ha770c72_0.conda +sha256: 38f014a7129e644636e46064ecd6b1945e729c2140e21d75bb476af39e692db2 +md5: e289f3d17880e44b633ba911d57a321b +depends: +- libfreetype6 >=2.14.3 +license: GPL-2.0-only OR FTL +size: 8049 +timestamp: 1774298163029 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.3-h73754d4_0.conda +sha256: 16f020f96da79db1863fcdd8f2b8f4f7d52f177dd4c58601e38e9182e91adf1d +md5: fb16b4b69e3f1dcfe79d80db8fd0c55d +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libpng >=1.6.55,<1.7.0a0 +- libzlib >=1.3.2,<2.0a0 +constrains: +- freetype >=2.14.3 +license: GPL-2.0-only OR FTL +size: 384575 +timestamp: 1774298162622 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda +sha256: faf7d2017b4d718951e3a59d081eb09759152f93038479b768e3d612688f83f5 +md5: 0aa00f03f9e39fb9876085dee11a85d4 +depends: +- __glibc >=2.17,<3.0.a0 +- _openmp_mutex >=4.5 +constrains: +- libgcc-ng ==15.2.0=*_18 +- libgomp 15.2.0 he0feb66_18 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 1041788 +timestamp: 1771378212382 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda +sha256: e318a711400f536c81123e753d4c797a821021fb38970cebfb3f454126016893 +md5: d5e96b1ed75ca01906b3d2469b4ce493 +depends: +- libgcc 15.2.0 he0feb66_18 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 27526 +timestamp: 1771378224552 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.4-h6548e54_1.conda +sha256: a27e44168a1240b15659888ce0d9b938ed4bdb49e9ea68a7c1ff27bcea8b55ce +md5: bb26456332b07f68bf3b7622ed71c0da +depends: +- __glibc >=2.17,<3.0.a0 +- libffi >=3.5.2,<3.6.0a0 +- libgcc >=14 +- libiconv >=1.18,<2.0a0 +- libzlib >=1.3.1,<2.0a0 +- pcre2 >=10.47,<10.48.0a0 +constrains: +- glib 2.86.4 *_1 +license: LGPL-2.1-or-later +size: 4398701 +timestamp: 1771863239578 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda +sha256: 21337ab58e5e0649d869ab168d4e609b033509de22521de1bfed0c031bfc5110 +md5: 239c5e9546c38a1e884d69effcf4c882 +depends: +- __glibc >=2.17,<3.0.a0 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 603262 +timestamp: 1771378117851 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda +sha256: c467851a7312765447155e071752d7bf9bf44d610a5687e32706f480aad2833f +md5: 915f5995e94f60e9a4826e0b0920ee88 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +license: LGPL-2.1-only +size: 790176 +timestamp: 1754908768807 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda +sha256: cc9aba923eea0af8e30e0f94f2ad7156e2984d80d1e8e7fe6be5a1f257f0eb32 +md5: 8397539e3a0bbd1695584fb4f927485a +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +constrains: +- jpeg <0.0.0a +license: IJG AND BSD-3-Clause AND Zlib +size: 633710 +timestamp: 1762094827865 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda +sha256: 755c55ebab181d678c12e49cced893598f2bab22d582fbbf4d8b83c18be207eb +md5: c7c83eecbb72d88b940c249af56c8b17 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +constrains: +- xz 5.8.2.* +license: 0BSD +size: 113207 +timestamp: 1768752626120 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.55-h421ea60_0.conda +sha256: 36ade759122cdf0f16e2a2562a19746d96cf9c863ffaa812f2f5071ebbe9c03c +md5: 5f13ffc7d30ffec87864e678df9957b4 +depends: +- libgcc >=14 +- __glibc >=2.17,<3.0.a0 +- libzlib >=1.3.1,<2.0a0 +license: zlib-acknowledgement +size: 317669 +timestamp: 1770691470744 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda +sha256: 78668020064fdaa27e9ab65cd2997e2c837b564ab26ce3bf0e58a2ce1a525c6e +md5: 1b08cd684f34175e4514474793d44bcb +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc 15.2.0 he0feb66_18 +constrains: +- libstdcxx-ng ==15.2.0=*_18 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 5852330 +timestamp: 1771378262446 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda +sha256: e5f8c38625aa6d567809733ae04bb71c161a42e44a9fa8227abe61fa5c60ebe0 +md5: cd5a90476766d53e901500df9215e927 +depends: +- __glibc >=2.17,<3.0.a0 +- lerc >=4.0.0,<5.0a0 +- libdeflate >=1.25,<1.26.0a0 +- libgcc >=14 +- libjpeg-turbo >=3.1.0,<4.0a0 +- liblzma >=5.8.1,<6.0a0 +- libstdcxx >=14 +- libwebp-base >=1.6.0,<2.0a0 +- libzlib >=1.3.1,<2.0a0 +- zstd >=1.5.7,<1.6.0a0 +license: HPND +size: 435273 +timestamp: 1762022005702 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda +sha256: 1a7539cfa7df00714e8943e18de0b06cceef6778e420a5ee3a2a145773758aee +md5: db409b7c1720428638e7c0d509d3e1b5 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +license: BSD-3-Clause +license_family: BSD +size: 40311 +timestamp: 1766271528534 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda +sha256: 3aed21ab28eddffdaf7f804f49be7a7d701e8f0e46c856d801270b470820a37b +md5: aea31d2e5b1091feca96fcfe945c3cf9 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +constrains: +- libwebp 1.6.0 +license: BSD-3-Clause +license_family: BSD +size: 429011 +timestamp: 1752159441324 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda +sha256: 666c0c431b23c6cec6e492840b176dde533d48b7e6fb8883f5071223433776aa +md5: 92ed62436b625154323d40d5f2f11dd7 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=13 +- pthread-stubs +- xorg-libxau >=1.0.11,<2.0a0 +- xorg-libxdmcp +license: MIT +license_family: MIT +size: 395888 +timestamp: 1727278577118 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda +sha256: 6ae68e0b86423ef188196fff6207ed0c8195dd84273cb5623b85aa08033a410c +md5: 5aa797f8787fe7a17d1b0821485b5adc +depends: +- libgcc-ng >=12 +license: LGPL-2.1-or-later +size: 100393 +timestamp: 1702724383534 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda +sha256: 55044c403570f0dc26e6364de4dc5368e5f3fc7ff103e867c487e2b5ab2bcda9 +md5: d87ff7921124eccd67248aa483c23fec +depends: +- __glibc >=2.17,<3.0.a0 +constrains: +- zlib 1.3.2 *_2 +license: Zlib +license_family: Other +size: 63629 +timestamp: 1774072609062 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda +sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586 +md5: 47e340acb35de30501a76c7c799c41d7 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=13 +license: X11 AND BSD-3-Clause +size: 891641 +timestamp: 1738195959188 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openjdk-25.0.2-ha668962_0.conda +sha256: 3825a4c84676a8a5cc23b397a2911e4efa4a805daf2af764153bd904e142ec41 +md5: a41092b0177362dbe5eb2a18501e86c0 +depends: +- xorg-libx11 +- xorg-libxext +- xorg-libxi +- xorg-libxrender +- xorg-libxtst +- libstdcxx >=14 +- libgcc >=14 +- __glibc >=2.17,<3.0.a0 +- libfreetype >=2.14.1 +- libfreetype6 >=2.14.1 +- xorg-libxrender >=0.9.12,<0.10.0a0 +- libjpeg-turbo >=3.1.2,<4.0a0 +- giflib >=5.2.2,<5.3.0a0 +- xorg-libxrandr >=1.5.5,<2.0a0 +- harfbuzz >=12.3.2 +- fontconfig >=2.17.1,<3.0a0 +- fonts-conda-ecosystem +- xorg-libxtst >=1.2.5,<2.0a0 +- xorg-libxi >=1.8.2,<2.0a0 +- lcms2 >=2.18,<3.0a0 +- alsa-lib >=1.2.15.3,<1.3.0a0 +- libpng >=1.6.55,<1.7.0a0 +- xorg-libxt >=1.3.1,<2.0a0 +- libzlib >=1.3.1,<2.0a0 +- xorg-libxext >=1.3.7,<2.0a0 +- xorg-libx11 >=1.8.13,<2.0a0 +- libcups >=2.3.3,<2.4.0a0 +license: GPL-2.0-or-later WITH Classpath-exception-2.0 +license_family: GPL +size: 122465031 +timestamp: 1771443671180 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda +sha256: 44c877f8af015332a5d12f5ff0fb20ca32f896526a7d0cdb30c769df1144fb5c +md5: f61eb8cd60ff9057122a3d338b99c00f +depends: +- __glibc >=2.17,<3.0.a0 +- ca-certificates +- libgcc >=14 +license: Apache-2.0 +license_family: Apache +size: 3164551 +timestamp: 1769555830639 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda +sha256: 5e6f7d161356fefd981948bea5139c5aa0436767751a6930cb1ca801ebb113ff +md5: 7a3bff861a6583f1889021facefc08b1 +depends: +- __glibc >=2.17,<3.0.a0 +- bzip2 >=1.0.8,<2.0a0 +- libgcc >=14 +- libzlib >=1.3.1,<2.0a0 +license: BSD-3-Clause +license_family: BSD +size: 1222481 +timestamp: 1763655398280 +- conda: https://conda.anaconda.org/conda-forge/linux-64/perl-5.32.1-7_hd590300_perl5.conda +build_number: 7 +sha256: 9ec32b6936b0e37bcb0ed34f22ec3116e75b3c0964f9f50ecea5f58734ed6ce9 +md5: f2cfec9406850991f4e3d960cc9e3321 +depends: +- libgcc-ng >=12 +- libxcrypt >=4.4.36 +license: GPL-1.0-or-later OR Artistic-1.0-Perl +size: 13344463 +timestamp: 1703310653947 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda +sha256: 43d37bc9ca3b257c5dd7bf76a8426addbdec381f6786ff441dc90b1a49143b6a +md5: c01af13bdc553d1a8fbfff6e8db075f0 +depends: +- libgcc >=14 +- libstdcxx >=14 +- libgcc >=14 +- __glibc >=2.17,<3.0.a0 +license: MIT +license_family: MIT +size: 450960 +timestamp: 1754665235234 +- conda: https://conda.anaconda.org/conda-forge/linux-64/procps-ng-4.0.6-h18c060e_0.conda +sha256: 4ce2e1ee31a6217998f78c31ce7dc0a3e0557d9238b51d49dd20c52d467a126d +md5: f2c23a77b25efcad57d377b34bd84941 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- ncurses >=6.5,<7.0a0 +license: GPL-2.0-or-later AND LGPL-2.0-or-later +license_family: GPL +size: 593603 +timestamp: 1769710381284 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda +sha256: 9c88f8c64590e9567c6c80823f0328e58d3b1efb0e1c539c0315ceca764e0973 +md5: b3c17d95b5a10c6e64a21fa17573e70e +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=13 +license: MIT +license_family: MIT +size: 8252 +timestamp: 1726802366959 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda +sha256: c12396aabb21244c212e488bbdc4abcdef0b7404b15761d9329f5a4a39113c4b +md5: fb901ff28063514abb6046c9ec2c4a45 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=13 +license: MIT +license_family: MIT +size: 58628 +timestamp: 1734227592886 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda +sha256: 277841c43a39f738927145930ff963c5ce4c4dacf66637a3d95d802a64173250 +md5: 1c74ff8c35dcadf952a16f752ca5aa49 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=13 +- libuuid >=2.38.1,<3.0a0 +- xorg-libice >=1.1.2,<2.0a0 +license: MIT +license_family: MIT +size: 27590 +timestamp: 1741896361728 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.13-he1eb515_0.conda +sha256: 516d4060139dbb4de49a4dcdc6317a9353fb39ebd47789c14e6fe52de0deee42 +md5: 861fb6ccbc677bb9a9fb2468430b9c6a +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libxcb >=1.17.0,<2.0a0 +license: MIT +license_family: MIT +size: 839652 +timestamp: 1770819209719 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda +sha256: 6bc6ab7a90a5d8ac94c7e300cc10beb0500eeba4b99822768ca2f2ef356f731b +md5: b2895afaf55bf96a8c8282a2e47a5de0 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +license: MIT +license_family: MIT +size: 15321 +timestamp: 1762976464266 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda +sha256: 25d255fb2eef929d21ff660a0c687d38a6d2ccfbcbf0cc6aa738b12af6e9d142 +md5: 1dafce8548e38671bea82e3f5c6ce22f +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +license: MIT +license_family: MIT +size: 20591 +timestamp: 1762976546182 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.7-hb03c661_0.conda +sha256: 79c60fc6acfd3d713d6340d3b4e296836a0f8c51602327b32794625826bd052f +md5: 34e54f03dfea3e7a2dcf1453a85f1085 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- xorg-libx11 >=1.8.12,<2.0a0 +license: MIT +license_family: MIT +size: 50326 +timestamp: 1769445253162 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-6.0.2-hb03c661_0.conda +sha256: 83c4c99d60b8784a611351220452a0a85b080668188dce5dfa394b723d7b64f4 +md5: ba231da7fccf9ea1e768caf5c7099b84 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- xorg-libx11 >=1.8.12,<2.0a0 +license: MIT +license_family: MIT +size: 20071 +timestamp: 1759282564045 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxi-1.8.2-hb9d3cd8_0.conda +sha256: 1a724b47d98d7880f26da40e45f01728e7638e6ec69f35a3e11f92acd05f9e7a +md5: 17dcc85db3c7886650b8908b183d6876 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=13 +- xorg-libx11 >=1.8.10,<2.0a0 +- xorg-libxext >=1.3.6,<2.0a0 +- xorg-libxfixes >=6.0.1,<7.0a0 +license: MIT +license_family: MIT +size: 47179 +timestamp: 1727799254088 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrandr-1.5.5-hb03c661_0.conda +sha256: 80ed047a5cb30632c3dc5804c7716131d767089f65877813d4ae855ee5c9d343 +md5: e192019153591938acf7322b6459d36e +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- xorg-libx11 >=1.8.12,<2.0a0 +- xorg-libxext >=1.3.6,<2.0a0 +- xorg-libxrender >=0.9.12,<0.10.0a0 +license: MIT +license_family: MIT +size: 30456 +timestamp: 1769445263457 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda +sha256: 044c7b3153c224c6cedd4484dd91b389d2d7fd9c776ad0f4a34f099b3389f4a1 +md5: 96d57aba173e878a2089d5638016dc5e +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=13 +- xorg-libx11 >=1.8.10,<2.0a0 +license: MIT +license_family: MIT +size: 33005 +timestamp: 1734229037766 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxt-1.3.1-hb9d3cd8_0.conda +sha256: a8afba4a55b7b530eb5c8ad89737d60d60bc151a03fbef7a2182461256953f0e +md5: 279b0de5f6ba95457190a1c459a64e31 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=13 +- xorg-libice >=1.1.1,<2.0a0 +- xorg-libsm >=1.2.4,<2.0a0 +- xorg-libx11 >=1.8.10,<2.0a0 +license: MIT +license_family: MIT +size: 379686 +timestamp: 1731860547604 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxtst-1.2.5-hb9d3cd8_3.conda +sha256: 752fdaac5d58ed863bbf685bb6f98092fe1a488ea8ebb7ed7b606ccfce08637a +md5: 7bbe9a0cc0df0ac5f5a8ad6d6a11af2f +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=13 +- xorg-libx11 >=1.8.10,<2.0a0 +- xorg-libxext >=1.3.6,<2.0a0 +- xorg-libxi >=1.7.10,<2.0a0 +license: MIT +license_family: MIT +size: 32808 +timestamp: 1727964811275 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda +sha256: 68f0206ca6e98fea941e5717cec780ed2873ffabc0e1ed34428c061e2c6268c7 +md5: 4a13eeac0b5c8e5b8ab496e6c4ddd829 +depends: +- __glibc >=2.17,<3.0.a0 +- libzlib >=1.3.1,<2.0a0 +license: BSD-3-Clause +license_family: BSD +size: 601375 +timestamp: 1764777111296 diff --git a/modules/nf-core/fastqc/.conda-lock/linux_arm64-bd-e455e32f745abe68_1.txt b/modules/nf-core/fastqc/.conda-lock/linux_arm64-bd-e455e32f745abe68_1.txt new file mode 100644 index 00000000..cdc434ca --- /dev/null +++ b/modules/nf-core/fastqc/.conda-lock/linux_arm64-bd-e455e32f745abe68_1.txt @@ -0,0 +1,769 @@ + +version: 6 +environments: +default: +channels: +- url: https://conda.anaconda.org/conda-forge/ +- url: https://conda.anaconda.org/bioconda/ +- url: https://conda.anaconda.org/bioconda/ +options: +pypi-prerelease-mode: if-necessary-or-explicit +packages: +linux-aarch64: +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-20_gnu.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/alsa-lib-1.2.15.3-he30d5cf_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/bzip2-1.0.8-h4777abc_9.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cairo-1.18.4-h0b6afd8_1.conda +- conda: https://conda.anaconda.org/bioconda/noarch/fastqc-0.12.1-hdfd78af_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/fontconfig-2.17.1-hba86a56_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/giflib-5.2.2-h31becfc_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/graphite2-1.3.14-hfae3067_2.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/harfbuzz-13.2.1-h1134a53_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/icu-78.3-hcab7f73_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/keyutils-1.6.3-h86ecc28_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/krb5-1.22.2-hfd895c2_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lcms2-2.18-h9d5b58d_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lerc-4.1.0-h52b7260_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libcups-2.3.3-h4f2b762_6.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libdeflate-1.25-h1af38f5_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libedit-3.1.20250104-pl5321h976ea20_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libexpat-2.7.4-hfae3067_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libffi-3.5.2-h376a255_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libfreetype-2.14.3-h8af1aa0_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libfreetype6-2.14.3-hdae7a39_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-15.2.0-h8acb6b2_18.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-ng-15.2.0-he9431aa_18.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libglib-2.86.4-hf53f6bf_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgomp-15.2.0-h8acb6b2_18.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libiconv-1.18-h90929bb_2.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libjpeg-turbo-3.1.2-he30d5cf_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/liblzma-5.8.2-he30d5cf_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libpng-1.6.55-h1abf092_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libstdcxx-15.2.0-hef695bb_18.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libtiff-4.7.1-hdb009f0_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libuuid-2.41.3-h1022ec0_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libwebp-base-1.6.0-ha2e29f5_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxcb-1.17.0-h262b8f6_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxcrypt-4.4.36-h31becfc_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libzlib-1.3.2-hdc9db2a_2.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ncurses-6.5-ha32ae93_3.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/openjdk-25.0.2-h488f50d_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/openssl-3.6.1-h546c87b_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pcre2-10.47-hf841c20_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/perl-5.32.1-7_h31becfc_perl5.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pixman-0.46.4-h7ac5ae9_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/procps-ng-4.0.6-h1779866_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pthread-stubs-0.4-h86ecc28_1002.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libice-1.1.2-h86ecc28_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libsm-1.2.6-h0808dbd_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libx11-1.8.13-h63a1b12_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxau-1.0.12-he30d5cf_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxdmcp-1.1.5-he30d5cf_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxext-1.3.7-he30d5cf_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxfixes-6.0.2-he30d5cf_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxi-1.8.2-h57736b2_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxrandr-1.5.5-he30d5cf_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxrender-0.9.12-h86ecc28_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxt-1.3.1-h57736b2_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxtst-1.2.5-h57736b2_3.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.7-h85ac4a6_6.conda +packages: +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-20_gnu.conda +build_number: 20 +sha256: a2527b1d81792a0ccd2c05850960df119c2b6d8f5fdec97f2db7d25dc23b1068 +md5: 468fd3bb9e1f671d36c2cbc677e56f1d +depends: +- libgomp >=7.5.0 +constrains: +- openmp_impl <0.0a0 +license: BSD-3-Clause +license_family: BSD +size: 28926 +timestamp: 1770939656741 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/alsa-lib-1.2.15.3-he30d5cf_0.conda +sha256: ea2233e2db9908c2e5f29d3ca420a546b4583253f4f70abb5494cdd676866d42 +md5: 4a98cbc4ade694520227402ff8880630 +depends: +- libgcc >=14 +license: LGPL-2.1-or-later +license_family: GPL +size: 615729 +timestamp: 1768327548407 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/bzip2-1.0.8-h4777abc_9.conda +sha256: b3495077889dde6bb370938e7db82be545c73e8589696ad0843a32221520ad4c +md5: 840d8fc0d7b3209be93080bc20e07f2d +depends: +- libgcc >=14 +license: bzip2-1.0.6 +license_family: BSD +size: 192412 +timestamp: 1771350241232 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda +sha256: 67cc7101b36421c5913a1687ef1b99f85b5d6868da3abbf6ec1a4181e79782fc +md5: 4492fd26db29495f0ba23f146cd5638d +depends: +- __unix +license: ISC +size: 147413 +timestamp: 1772006283803 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/cairo-1.18.4-h0b6afd8_1.conda +sha256: 675db823f3d6fb6bf747fab3b0170ba99b269a07cf6df1e49fff2f9972be9cd1 +md5: 043c13ed3a18396994be9b4fab6572ad +depends: +- fontconfig >=2.15.0,<3.0a0 +- fonts-conda-ecosystem +- icu >=78.1,<79.0a0 +- libexpat >=2.7.3,<3.0a0 +- libfreetype >=2.14.1 +- libfreetype6 >=2.14.1 +- libgcc >=14 +- libglib >=2.86.3,<3.0a0 +- libpng >=1.6.53,<1.7.0a0 +- libstdcxx >=14 +- libxcb >=1.17.0,<2.0a0 +- libzlib >=1.3.1,<2.0a0 +- pixman >=0.46.4,<1.0a0 +- xorg-libice >=1.1.2,<2.0a0 +- xorg-libsm >=1.2.6,<2.0a0 +- xorg-libx11 >=1.8.12,<2.0a0 +- xorg-libxext >=1.3.6,<2.0a0 +- xorg-libxrender >=0.9.12,<0.10.0a0 +license: LGPL-2.1-only or MPL-1.1 +size: 927045 +timestamp: 1766416003626 +- conda: https://conda.anaconda.org/bioconda/noarch/fastqc-0.12.1-hdfd78af_0.tar.bz2 +sha256: 7cc26225d590540ae95cd24940ff42f2da7479dd4cd22ae9ab9298665d06790c +md5: c9f6a4b12229f7331f79c9a00dd6e240 +depends: +- font-ttf-dejavu-sans-mono +- fontconfig +- openjdk >=8.0.144 +- perl +license: GPL >=3 +size: 11664291 +timestamp: 1677946722445 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 +sha256: 58d7f40d2940dd0a8aa28651239adbf5613254df0f75789919c4e6762054403b +md5: 0c96522c6bdaed4b1566d11387caaf45 +license: BSD-3-Clause +license_family: BSD +size: 397370 +timestamp: 1566932522327 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 +sha256: c52a29fdac682c20d252facc50f01e7c2e7ceac52aa9817aaf0bb83f7559ec5c +md5: 34893075a5c9e55cdafac56607368fc6 +license: OFL-1.1 +license_family: Other +size: 96530 +timestamp: 1620479909603 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 +sha256: 00925c8c055a2275614b4d983e1df637245e19058d79fc7dd1a93b8d9fb4b139 +md5: 4d59c254e01d9cde7957100457e2d5fb +license: OFL-1.1 +license_family: Other +size: 700814 +timestamp: 1620479612257 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda +sha256: 2821ec1dc454bd8b9a31d0ed22a7ce22422c0aef163c59f49dfdf915d0f0ca14 +md5: 49023d73832ef61042f6a237cb2687e7 +license: LicenseRef-Ubuntu-Font-Licence-Version-1.0 +license_family: Other +size: 1620504 +timestamp: 1727511233259 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/fontconfig-2.17.1-hba86a56_0.conda +sha256: 835aff8615dd8d8fff377679710ce81b8a2c47b6404e21a92fb349fda193a15c +md5: 0fed1ff55f4938a65907f3ecf62609db +depends: +- libexpat >=2.7.4,<3.0a0 +- libfreetype >=2.14.1 +- libfreetype6 >=2.14.1 +- libgcc >=14 +- libuuid >=2.41.3,<3.0a0 +- libzlib >=1.3.1,<2.0a0 +license: MIT +license_family: MIT +size: 279044 +timestamp: 1771382728182 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 +sha256: a997f2f1921bb9c9d76e6fa2f6b408b7fa549edd349a77639c9fe7a23ea93e61 +md5: fee5683a3f04bd15cbd8318b096a27ab +depends: +- fonts-conda-forge +license: BSD-3-Clause +license_family: BSD +size: 3667 +timestamp: 1566974674465 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda +sha256: 54eea8469786bc2291cc40bca5f46438d3e062a399e8f53f013b6a9f50e98333 +md5: a7970cd949a077b7cb9696379d338681 +depends: +- font-ttf-ubuntu +- font-ttf-inconsolata +- font-ttf-dejavu-sans-mono +- font-ttf-source-code-pro +license: BSD-3-Clause +license_family: BSD +size: 4059 +timestamp: 1762351264405 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/giflib-5.2.2-h31becfc_0.conda +sha256: a79dc3bd54c4fb1f249942ee2d5b601a76ecf9614774a4cff9af49adfa458db2 +md5: 2f809afaf0ba1ea4135dce158169efac +depends: +- libgcc-ng >=12 +license: MIT +license_family: MIT +size: 82124 +timestamp: 1712692444545 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/graphite2-1.3.14-hfae3067_2.conda +sha256: c9b1781fe329e0b77c5addd741e58600f50bef39321cae75eba72f2f381374b7 +md5: 4aa540e9541cc9d6581ab23ff2043f13 +depends: +- libgcc >=14 +- libstdcxx >=14 +license: LGPL-2.0-or-later +license_family: LGPL +size: 102400 +timestamp: 1755102000043 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/harfbuzz-13.2.1-h1134a53_0.conda +sha256: e22f485fddaaea3ff4b6cae98e0197b9dccd2ed2770337ad6ff38a92afe04e59 +md5: 05d65a2cf410adc331c9ea61f59f1013 +depends: +- cairo >=1.18.4,<2.0a0 +- graphite2 >=1.3.14,<2.0a0 +- icu >=78.3,<79.0a0 +- libexpat >=2.7.4,<3.0a0 +- libfreetype >=2.14.2 +- libfreetype6 >=2.14.2 +- libgcc >=14 +- libglib >=2.86.4,<3.0a0 +- libstdcxx >=14 +- libzlib >=1.3.2,<2.0a0 +license: MIT +license_family: MIT +size: 2345732 +timestamp: 1774281448329 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/icu-78.3-hcab7f73_0.conda +sha256: 49ba6aed2c6b482bb0ba41078057555d29764299bc947b990708617712ef6406 +md5: 546da38c2fa9efacf203e2ad3f987c59 +depends: +- libgcc >=14 +- libstdcxx >=14 +license: MIT +license_family: MIT +size: 12837286 +timestamp: 1773822650615 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/keyutils-1.6.3-h86ecc28_0.conda +sha256: 5ce830ca274b67de11a7075430a72020c1fb7d486161a82839be15c2b84e9988 +md5: e7df0aab10b9cbb73ab2a467ebfaf8c7 +depends: +- libgcc >=13 +license: LGPL-2.1-or-later +size: 129048 +timestamp: 1754906002667 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/krb5-1.22.2-hfd895c2_0.conda +sha256: b53999d888dda53c506b264e8c02b5f5c8e022c781eda0718f007339e6bc90ba +md5: d9ca108bd680ea86a963104b6b3e95ca +depends: +- keyutils >=1.6.3,<2.0a0 +- libedit >=3.1.20250104,<3.2.0a0 +- libedit >=3.1.20250104,<4.0a0 +- libgcc >=14 +- libstdcxx >=14 +- openssl >=3.5.5,<4.0a0 +license: MIT +license_family: MIT +size: 1517436 +timestamp: 1769773395215 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lcms2-2.18-h9d5b58d_0.conda +sha256: 379ef5e91a587137391a6149755d0e929f1a007d2dcb211318ac670a46c8596f +md5: bb960f01525b5e001608afef9d47b79c +depends: +- libgcc >=14 +- libjpeg-turbo >=3.1.2,<4.0a0 +- libtiff >=4.7.1,<4.8.0a0 +license: MIT +license_family: MIT +size: 293039 +timestamp: 1768184778398 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lerc-4.1.0-h52b7260_0.conda +sha256: 8957fd460c1c132c8031f65fd5f56ec3807fd71b7cab2c5e2b0937b13404ab36 +md5: d13423b06447113a90b5b1366d4da171 +depends: +- libgcc >=14 +- libstdcxx >=14 +license: Apache-2.0 +license_family: Apache +size: 240444 +timestamp: 1773114901155 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libcups-2.3.3-h4f2b762_6.conda +sha256: 41b04f995c9f63af8c4065a35931e46cbc2fdd6b9bf7e4c19f90d53cbb2bc8e5 +md5: 67828c963b17db7dc989fe5d509ef04a +depends: +- krb5 >=1.22.2,<1.23.0a0 +- libgcc >=14 +- libstdcxx >=14 +- libzlib >=1.3.1,<2.0a0 +license: Apache-2.0 +license_family: Apache +size: 4553739 +timestamp: 1770903929794 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libdeflate-1.25-h1af38f5_0.conda +sha256: 48814b73bd462da6eed2e697e30c060ae16af21e9fbed30d64feaf0aad9da392 +md5: a9138815598fe6b91a1d6782ca657b0c +depends: +- libgcc >=14 +license: MIT +license_family: MIT +size: 71117 +timestamp: 1761979776756 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libedit-3.1.20250104-pl5321h976ea20_0.conda +sha256: c0b27546aa3a23d47919226b3a1635fccdb4f24b94e72e206a751b33f46fd8d6 +md5: fb640d776fc92b682a14e001980825b1 +depends: +- ncurses +- libgcc >=13 +- ncurses >=6.5,<7.0a0 +license: BSD-2-Clause +license_family: BSD +size: 148125 +timestamp: 1738479808948 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libexpat-2.7.4-hfae3067_0.conda +sha256: 995ce3ad96d0f4b5ed6296b051a0d7b6377718f325bc0e792fbb96b0e369dad7 +md5: 57f3b3da02a50a1be2a6fe847515417d +depends: +- libgcc >=14 +constrains: +- expat 2.7.4.* +license: MIT +license_family: MIT +size: 76564 +timestamp: 1771259530958 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libffi-3.5.2-h376a255_0.conda +sha256: 3df4c539449aabc3443bbe8c492c01d401eea894603087fca2917aa4e1c2dea9 +md5: 2f364feefb6a7c00423e80dcb12db62a +depends: +- libgcc >=14 +license: MIT +license_family: MIT +size: 55952 +timestamp: 1769456078358 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libfreetype-2.14.3-h8af1aa0_0.conda +sha256: 752e4f66283d7deb4c6fd47d88df644d8daa2aaa825a54f3bf350a625190192a +md5: a229e22d4d8814a07702b0919d8e6701 +depends: +- libfreetype6 >=2.14.3 +license: GPL-2.0-only OR FTL +size: 8125 +timestamp: 1774301094057 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libfreetype6-2.14.3-hdae7a39_0.conda +sha256: 8e6b27fe4eec4c2fa7b7769a21973734c8dba1de80086fb0213e58375ac09f4c +md5: b99ed99e42dafb27889483b3098cace7 +depends: +- libgcc >=14 +- libpng >=1.6.55,<1.7.0a0 +- libzlib >=1.3.2,<2.0a0 +constrains: +- freetype >=2.14.3 +license: GPL-2.0-only OR FTL +size: 422941 +timestamp: 1774301093473 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-15.2.0-h8acb6b2_18.conda +sha256: 43df385bedc1cab11993c4369e1f3b04b4ca5d0ea16cba6a0e7f18dbc129fcc9 +md5: 552567ea2b61e3a3035759b2fdb3f9a6 +depends: +- _openmp_mutex >=4.5 +constrains: +- libgcc-ng ==15.2.0=*_18 +- libgomp 15.2.0 h8acb6b2_18 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 622900 +timestamp: 1771378128706 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-ng-15.2.0-he9431aa_18.conda +sha256: 83bb0415f59634dccfa8335d4163d1f6db00a27b36666736f9842b650b92cf2f +md5: 4feebd0fbf61075a1a9c2e9b3936c257 +depends: +- libgcc 15.2.0 h8acb6b2_18 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 27568 +timestamp: 1771378136019 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libglib-2.86.4-hf53f6bf_1.conda +sha256: afc503dbd04a5bf2709aa9d8318a03a8c4edb389f661ff280c3494bfef4341ec +md5: 4ac4372fc4d7f20630a91314cdac8afd +depends: +- libffi >=3.5.2,<3.6.0a0 +- libgcc >=14 +- libiconv >=1.18,<2.0a0 +- libzlib >=1.3.1,<2.0a0 +- pcre2 >=10.47,<10.48.0a0 +constrains: +- glib 2.86.4 *_1 +license: LGPL-2.1-or-later +size: 4512186 +timestamp: 1771863220969 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgomp-15.2.0-h8acb6b2_18.conda +sha256: fc716f11a6a8525e27a5d332ef6a689210b0d2a4dd1133edc0f530659aa9faa6 +md5: 4faa39bf919939602e594253bd673958 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 588060 +timestamp: 1771378040807 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libiconv-1.18-h90929bb_2.conda +sha256: 1473451cd282b48d24515795a595801c9b65b567fe399d7e12d50b2d6cdb04d9 +md5: 5a86bf847b9b926f3a4f203339748d78 +depends: +- libgcc >=14 +license: LGPL-2.1-only +size: 791226 +timestamp: 1754910975665 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libjpeg-turbo-3.1.2-he30d5cf_0.conda +sha256: 84064c7c53a64291a585d7215fe95ec42df74203a5bf7615d33d49a3b0f08bb6 +md5: 5109d7f837a3dfdf5c60f60e311b041f +depends: +- libgcc >=14 +constrains: +- jpeg <0.0.0a +license: IJG AND BSD-3-Clause AND Zlib +size: 691818 +timestamp: 1762094728337 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/liblzma-5.8.2-he30d5cf_0.conda +sha256: 843c46e20519651a3e357a8928352b16c5b94f4cd3d5481acc48be2e93e8f6a3 +md5: 96944e3c92386a12755b94619bae0b35 +depends: +- libgcc >=14 +constrains: +- xz 5.8.2.* +license: 0BSD +size: 125916 +timestamp: 1768754941722 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libpng-1.6.55-h1abf092_0.conda +sha256: c7378c6b79de4d571d00ad1caf0a4c19d43c9c94077a761abb6ead44d891f907 +md5: be4088903b94ea297975689b3c3aeb27 +depends: +- libgcc >=14 +- libzlib >=1.3.1,<2.0a0 +license: zlib-acknowledgement +size: 340156 +timestamp: 1770691477245 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libstdcxx-15.2.0-hef695bb_18.conda +sha256: 31fdb9ffafad106a213192d8319b9f810e05abca9c5436b60e507afb35a6bc40 +md5: f56573d05e3b735cb03efeb64a15f388 +depends: +- libgcc 15.2.0 h8acb6b2_18 +constrains: +- libstdcxx-ng ==15.2.0=*_18 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 5541411 +timestamp: 1771378162499 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libtiff-4.7.1-hdb009f0_1.conda +sha256: 7ff79470db39e803e21b8185bc8f19c460666d5557b1378d1b1e857d929c6b39 +md5: 8c6fd84f9c87ac00636007c6131e457d +depends: +- lerc >=4.0.0,<5.0a0 +- libdeflate >=1.25,<1.26.0a0 +- libgcc >=14 +- libjpeg-turbo >=3.1.0,<4.0a0 +- liblzma >=5.8.1,<6.0a0 +- libstdcxx >=14 +- libwebp-base >=1.6.0,<2.0a0 +- libzlib >=1.3.1,<2.0a0 +- zstd >=1.5.7,<1.6.0a0 +license: HPND +size: 488407 +timestamp: 1762022048105 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libuuid-2.41.3-h1022ec0_0.conda +sha256: c37a8e89b700646f3252608f8368e7eb8e2a44886b92776e57ad7601fc402a11 +md5: cf2861212053d05f27ec49c3784ff8bb +depends: +- libgcc >=14 +license: BSD-3-Clause +license_family: BSD +size: 43453 +timestamp: 1766271546875 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libwebp-base-1.6.0-ha2e29f5_0.conda +sha256: b03700a1f741554e8e5712f9b06dd67e76f5301292958cd3cb1ac8c6fdd9ed25 +md5: 24e92d0942c799db387f5c9d7b81f1af +depends: +- libgcc >=14 +constrains: +- libwebp 1.6.0 +license: BSD-3-Clause +license_family: BSD +size: 359496 +timestamp: 1752160685488 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxcb-1.17.0-h262b8f6_0.conda +sha256: 461cab3d5650ac6db73a367de5c8eca50363966e862dcf60181d693236b1ae7b +md5: cd14ee5cca2464a425b1dbfc24d90db2 +depends: +- libgcc >=13 +- pthread-stubs +- xorg-libxau >=1.0.11,<2.0a0 +- xorg-libxdmcp +license: MIT +license_family: MIT +size: 397493 +timestamp: 1727280745441 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxcrypt-4.4.36-h31becfc_1.conda +sha256: 6b46c397644091b8a26a3048636d10b989b1bf266d4be5e9474bf763f828f41f +md5: b4df5d7d4b63579d081fd3a4cf99740e +depends: +- libgcc-ng >=12 +license: LGPL-2.1-or-later +size: 114269 +timestamp: 1702724369203 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libzlib-1.3.2-hdc9db2a_2.conda +sha256: eb111e32e5a7313a5bf799c7fb2419051fa2fe7eff74769fac8d5a448b309f7f +md5: 502006882cf5461adced436e410046d1 +constrains: +- zlib 1.3.2 *_2 +license: Zlib +license_family: Other +size: 69833 +timestamp: 1774072605429 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ncurses-6.5-ha32ae93_3.conda +sha256: 91cfb655a68b0353b2833521dc919188db3d8a7f4c64bea2c6a7557b24747468 +md5: 182afabe009dc78d8b73100255ee6868 +depends: +- libgcc >=13 +license: X11 AND BSD-3-Clause +size: 926034 +timestamp: 1738196018799 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/openjdk-25.0.2-h488f50d_0.conda +sha256: 6fd2c872b275fa5d42a61a4b6dc28a819cde29f9048adb547363597432e0720e +md5: 27fdd5d67e235c20d23b2d66406497d3 +depends: +- xorg-libx11 +- xorg-libxext +- xorg-libxi +- xorg-libxrender +- xorg-libxtst +- libstdcxx >=14 +- libgcc >=14 +- libzlib >=1.3.1,<2.0a0 +- xorg-libxtst >=1.2.5,<2.0a0 +- libpng >=1.6.55,<1.7.0a0 +- alsa-lib >=1.2.15.3,<1.3.0a0 +- xorg-libx11 >=1.8.13,<2.0a0 +- xorg-libxi >=1.8.2,<2.0a0 +- xorg-libxrandr >=1.5.5,<2.0a0 +- lcms2 >=2.18,<3.0a0 +- xorg-libxrender >=0.9.12,<0.10.0a0 +- libcups >=2.3.3,<2.4.0a0 +- libfreetype >=2.14.1 +- libfreetype6 >=2.14.1 +- harfbuzz >=12.3.2 +- xorg-libxext >=1.3.7,<2.0a0 +- giflib >=5.2.2,<5.3.0a0 +- xorg-libxt >=1.3.1,<2.0a0 +- libjpeg-turbo >=3.1.2,<4.0a0 +- fontconfig >=2.17.1,<3.0a0 +- fonts-conda-ecosystem +license: GPL-2.0-or-later WITH Classpath-exception-2.0 +license_family: GPL +size: 106988620 +timestamp: 1771443741031 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/openssl-3.6.1-h546c87b_1.conda +sha256: 7f8048c0e75b2620254218d72b4ae7f14136f1981c5eb555ef61645a9344505f +md5: 25f5885f11e8b1f075bccf4a2da91c60 +depends: +- ca-certificates +- libgcc >=14 +license: Apache-2.0 +license_family: Apache +size: 3692030 +timestamp: 1769557678657 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pcre2-10.47-hf841c20_0.conda +sha256: 04df2cee95feba440387f33f878e9f655521e69f4be33a0cd637f07d3d81f0f9 +md5: 1a30c42e32ca0ea216bd0bfe6f842f0b +depends: +- bzip2 >=1.0.8,<2.0a0 +- libgcc >=14 +- libzlib >=1.3.1,<2.0a0 +license: BSD-3-Clause +license_family: BSD +size: 1166552 +timestamp: 1763655534263 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/perl-5.32.1-7_h31becfc_perl5.conda +build_number: 7 +sha256: d78296134263b5bf476cad838ded65451e7162db756f9997c5d06b08122572ed +md5: 17d019cb2a6c72073c344e98e40dfd61 +depends: +- libgcc-ng >=12 +- libxcrypt >=4.4.36 +license: GPL-1.0-or-later OR Artistic-1.0-Perl +size: 13338804 +timestamp: 1703310557094 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pixman-0.46.4-h7ac5ae9_1.conda +sha256: e6b0846a998f2263629cfeac7bca73565c35af13251969f45d385db537a514e4 +md5: 1587081d537bd4ae77d1c0635d465ba5 +depends: +- libgcc >=14 +- libstdcxx >=14 +- libgcc >=14 +license: MIT +license_family: MIT +size: 357913 +timestamp: 1754665583353 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/procps-ng-4.0.6-h1779866_0.conda +sha256: e9cbcbc94e151ada3d6dc365380aaaf591f65012c16d9a2abaea4b9b90adc402 +md5: ab7288cc39545556d1bc5e71ab2df9a9 +depends: +- libgcc >=14 +- ncurses >=6.5,<7.0a0 +license: GPL-2.0-or-later AND LGPL-2.0-or-later +license_family: GPL +size: 636733 +timestamp: 1769712412683 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pthread-stubs-0.4-h86ecc28_1002.conda +sha256: 977dfb0cb3935d748521dd80262fe7169ab82920afd38ed14b7fee2ea5ec01ba +md5: bb5a90c93e3bac3d5690acf76b4a6386 +depends: +- libgcc >=13 +license: MIT +license_family: MIT +size: 8342 +timestamp: 1726803319942 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libice-1.1.2-h86ecc28_0.conda +sha256: a2ba1864403c7eb4194dacbfe2777acf3d596feae43aada8d1b478617ce45031 +md5: c8d8ec3e00cd0fd8a231789b91a7c5b7 +depends: +- libgcc >=13 +license: MIT +license_family: MIT +size: 60433 +timestamp: 1734229908988 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libsm-1.2.6-h0808dbd_0.conda +sha256: b86a819cd16f90c01d9d81892155126d01555a20dabd5f3091da59d6309afd0a +md5: 2d1409c50882819cb1af2de82e2b7208 +depends: +- libgcc >=13 +- libuuid >=2.38.1,<3.0a0 +- xorg-libice >=1.1.2,<2.0a0 +license: MIT +license_family: MIT +size: 28701 +timestamp: 1741897678254 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libx11-1.8.13-h63a1b12_0.conda +sha256: cf886160e2ff580d77f7eb8ec1a77c41c2c5b05343e329bc35f0ddf40b8d92ab +md5: 22dd10425ef181e80e130db50675d615 +depends: +- libgcc >=14 +- libxcb >=1.17.0,<2.0a0 +license: MIT +license_family: MIT +size: 869058 +timestamp: 1770819244991 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxau-1.0.12-he30d5cf_1.conda +sha256: e9f6e931feeb2f40e1fdbafe41d3b665f1ab6cb39c5880a1fcf9f79a3f3c84a5 +md5: 1c246e1105000c3660558459e2fd6d43 +depends: +- libgcc >=14 +license: MIT +license_family: MIT +size: 16317 +timestamp: 1762977521691 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxdmcp-1.1.5-he30d5cf_1.conda +sha256: 128d72f36bcc8d2b4cdbec07507542e437c7d67f677b7d77b71ed9eeac7d6df1 +md5: bff06dcde4a707339d66d45d96ceb2e2 +depends: +- libgcc >=14 +license: MIT +license_family: MIT +size: 21039 +timestamp: 1762979038025 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxext-1.3.7-he30d5cf_0.conda +sha256: db2188bc0d844d4e9747bac7f6c1d067e390bd769c5ad897c93f1df759dc5dba +md5: fb42b683034619915863d68dd9df03a3 +depends: +- libgcc >=14 +- xorg-libx11 >=1.8.12,<2.0a0 +license: MIT +license_family: MIT +size: 52409 +timestamp: 1769446753771 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxfixes-6.0.2-he30d5cf_0.conda +sha256: 8cb9c88e25c57e47419e98f04f9ef3154ad96b9f858c88c570c7b91216a64d0e +md5: e8b4056544341daf1d415eaeae7a040c +depends: +- libgcc >=14 +- xorg-libx11 >=1.8.12,<2.0a0 +license: MIT +license_family: MIT +size: 20704 +timestamp: 1759284028146 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxi-1.8.2-h57736b2_0.conda +sha256: 7b587407ecb9ccd2bbaf0fb94c5dbdde4d015346df063e9502dc0ce2b682fb5e +md5: eeee3bdb31c6acde2b81ad1b8c287087 +depends: +- libgcc >=13 +- xorg-libx11 >=1.8.9,<2.0a0 +- xorg-libxext >=1.3.6,<2.0a0 +- xorg-libxfixes >=6.0.1,<7.0a0 +license: MIT +license_family: MIT +size: 48197 +timestamp: 1727801059062 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxrandr-1.5.5-he30d5cf_0.conda +sha256: 9f5196665a8d72f4f119c40dcc4bafeb0b540b102cc7b8b299c2abf599e7919f +md5: 1f64c613f0b8d67e9fb0e165d898fb6b +depends: +- libgcc >=14 +- xorg-libx11 >=1.8.12,<2.0a0 +- xorg-libxext >=1.3.6,<2.0a0 +- xorg-libxrender >=0.9.12,<0.10.0a0 +license: MIT +license_family: MIT +size: 31122 +timestamp: 1769445286951 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxrender-0.9.12-h86ecc28_0.conda +sha256: ffd77ee860c9635a28cfda46163dcfe9224dc6248c62404c544ae6b564a0be1f +md5: ae2c2dd0e2d38d249887727db2af960e +depends: +- libgcc >=13 +- xorg-libx11 >=1.8.10,<2.0a0 +license: MIT +license_family: MIT +size: 33649 +timestamp: 1734229123157 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxt-1.3.1-h57736b2_0.conda +sha256: 7c109792b60720809a580612aba7f8eb2a0bd425b9fc078748a9d6ffc97cbfa8 +md5: a9e4852c8e0b68ee783e7240030b696f +depends: +- libgcc >=13 +- xorg-libice >=1.1.1,<2.0a0 +- xorg-libsm >=1.2.4,<2.0a0 +- xorg-libx11 >=1.8.9,<2.0a0 +license: MIT +license_family: MIT +size: 384752 +timestamp: 1731860572314 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxtst-1.2.5-h57736b2_3.conda +sha256: 6eaffce5a34fc0a16a21ddeaefb597e792a263b1b0c387c1ce46b0a967d558e1 +md5: c05698071b5c8e0da82a282085845860 +depends: +- libgcc >=13 +- xorg-libx11 >=1.8.9,<2.0a0 +- xorg-libxext >=1.3.6,<2.0a0 +- xorg-libxi >=1.7.10,<2.0a0 +license: MIT +license_family: MIT +size: 33786 +timestamp: 1727964907993 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.7-h85ac4a6_6.conda +sha256: 569990cf12e46f9df540275146da567d9c618c1e9c7a0bc9d9cfefadaed20b75 +md5: c3655f82dcea2aa179b291e7099c1fcc +depends: +- libzlib >=1.3.1,<2.0a0 +license: BSD-3-Clause +license_family: BSD +size: 614429 +timestamp: 1764777145593 diff --git a/modules/nf-core/fastqc/main.nf b/modules/nf-core/fastqc/main.nf index 23e16634..10851264 100644 --- a/modules/nf-core/fastqc/main.nf +++ b/modules/nf-core/fastqc/main.nf @@ -1,37 +1,40 @@ process FASTQC { tag "${meta.id}" - label 'process_medium' + label 'process_low' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/fastqc:0.12.1--hdfd78af_0' : - 'biocontainers/fastqc:0.12.1--hdfd78af_0' }" + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://depot.galaxyproject.org/singularity/fastqc:0.12.1--hdfd78af_0' + : 'quay.io/biocontainers/fastqc:0.12.1--hdfd78af_0'}" input: - tuple val(meta), path(reads) + tuple val(meta), path(reads, stageAs: '?/*') output: tuple val(meta), path("*.html"), emit: html - tuple val(meta), path("*.zip") , emit: zip - path "versions.yml" , emit: versions + tuple val(meta), path("*.zip"), emit: zip + tuple val("${task.process}"), val('fastqc'), eval('fastqc --version | sed "/FastQC v/!d; s/.*v//"'), emit: versions_fastqc, topic: versions when: task.ext.when == null || task.ext.when script: - def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" // Make list of old name and new name pairs to use for renaming in the bash while loop - def old_new_pairs = reads instanceof Path || reads.size() == 1 ? [[ reads, "${prefix}.${reads.extension}" ]] : reads.withIndex().collect { entry, index -> [ entry, "${prefix}_${index + 1}.${entry.extension}" ] } - def rename_to = old_new_pairs*.join(' ').join(' ') - def renamed_files = old_new_pairs.collect{ _old_name, new_name -> new_name }.join(' ') + def old_new_pairs = reads instanceof Path || reads.size() == 1 ? [[reads, "${prefix}.${reads.extension}"]] : reads.withIndex().collect { entry, index -> [entry, "${prefix}_${index + 1}.${entry.extension}"] } + def rename_to = old_new_pairs*.join(' ').join(' ') + def renamed_files = old_new_pairs.collect { _old_name, new_name -> new_name }.join(' ') // The total amount of allocated RAM by FastQC is equal to the number of threads defined (--threads) time the amount of RAM defined (--memory) // https://github.com/s-andrews/FastQC/blob/1faeea0412093224d7f6a07f777fad60a5650795/fastqc#L211-L222 - // Dividing the task.memory by task.cpu allows to stick to requested amount of RAM in the label - def memory_in_mb = task.memory ? task.memory.toUnit('MB') / task.cpus : null + // Dividing the task.memory by task.cpus allows to stick to requested amount of RAM in the label + def memory_in_mb = task.memory + ? (task.memory.toUnit('MB') / task.cpus).intValue() + : null // FastQC memory value allowed range (100 - 10000) def fastqc_memory = memory_in_mb > 10000 ? 10000 : (memory_in_mb < 100 ? 100 : memory_in_mb) + def fastqc_memory_arg = fastqc_memory ? "--memory ${fastqc_memory}" : '' """ printf "%s %s\\n" ${rename_to} | while read old_name new_name; do @@ -41,13 +44,8 @@ process FASTQC { fastqc \\ ${args} \\ --threads ${task.cpus} \\ - --memory ${fastqc_memory} \\ + ${fastqc_memory_arg} \\ ${renamed_files} - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - fastqc: \$( fastqc --version | sed '/FastQC v/!d; s/.*v//' ) - END_VERSIONS """ stub: @@ -55,10 +53,5 @@ process FASTQC { """ touch ${prefix}.html touch ${prefix}.zip - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - fastqc: \$( fastqc --version | sed '/FastQC v/!d; s/.*v//' ) - END_VERSIONS """ } diff --git a/modules/nf-core/fastqc/meta.yml b/modules/nf-core/fastqc/meta.yml index c8d9d025..2f6cfef6 100644 --- a/modules/nf-core/fastqc/meta.yml +++ b/modules/nf-core/fastqc/meta.yml @@ -53,13 +53,28 @@ output: description: FastQC report archive pattern: "*_{fastqc.zip}" ontologies: [] + versions_fastqc: + - - ${task.process}: + type: string + description: The process the versions were collected from + - fastqc: + type: string + description: The tool name + - fastqc --version | sed "/FastQC v/!d; s/.*v//": + type: eval + description: The expression to obtain the version of the tool + +topics: versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" - ontologies: - - edam: http://edamontology.org/format_3750 # YAML + - - ${task.process}: + type: string + description: The process the versions were collected from + - fastqc: + type: string + description: The tool name + - fastqc --version | sed "/FastQC v/!d; s/.*v//": + type: eval + description: The expression to obtain the version of the tool authors: - "@drpatelh" - "@grst" @@ -70,3 +85,27 @@ maintainers: - "@grst" - "@ewels" - "@FelixKrueger" +containers: + docker: + linux/arm64: + name: community.wave.seqera.io/library/fastqc:0.12.1--e455e32f745abe68 + build_id: bd-e455e32f745abe68_1 + scan_id: sc-f102f736465af88c_1 + linux/amd64: + name: community.wave.seqera.io/library/fastqc:0.12.1--5cb1a2fa2f18c7c2 + build_id: bd-5cb1a2fa2f18c7c2_1 + scan_id: sc-0c0466326b6b77d2_1 + singularity: + linux/amd64: + name: oras://community.wave.seqera.io/library/fastqc:0.12.1--5c4bd442468d75dd + build_id: bd-5c4bd442468d75dd_1 + https: https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/f2/f20b021476d1d87658820f971ebecc1e8cdbde0f338eb0d9cea2b0a8fc54a54b/data + linux/arm64: + name: oras://community.wave.seqera.io/library/fastqc:0.12.1--127a87fc06499035 + build_id: bd-127a87fc06499035_1 + https: https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/46/46daf2dad0169afd2ae047c3e50ed3776259f664bf07e5e06b045dc23449e994/data + conda: + linux/amd64: + lock_file: modules/nf-core/fastqc/.conda-lock/linux_amd64-bd-5cb1a2fa2f18c7c2_1.txt + linux/arm64: + lock_file: modules/nf-core/fastqc/.conda-lock/linux_arm64-bd-e455e32f745abe68_1.txt diff --git a/modules/nf-core/fastqc/tests/main.nf.test b/modules/nf-core/fastqc/tests/main.nf.test index e9d79a07..66c44da9 100644 --- a/modules/nf-core/fastqc/tests/main.nf.test +++ b/modules/nf-core/fastqc/tests/main.nf.test @@ -30,7 +30,7 @@ nextflow_process { { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, - { assert snapshot(process.out.versions).match() } + { assert snapshot(sanitizeOutput(process.out).findAll { key, val -> key != 'html' && key != 'zip' }).match() } ) } } @@ -58,7 +58,7 @@ nextflow_process { { assert process.out.zip[0][1][1] ==~ ".*/test_2_fastqc.zip" }, { assert path(process.out.html[0][1][0]).text.contains("File typeConventional base calls") }, { assert path(process.out.html[0][1][1]).text.contains("File typeConventional base calls") }, - { assert snapshot(process.out.versions).match() } + { assert snapshot(sanitizeOutput(process.out).findAll { key, val -> key != 'html' && key != 'zip' }).match() } ) } } @@ -82,7 +82,7 @@ nextflow_process { { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, - { assert snapshot(process.out.versions).match() } + { assert snapshot(sanitizeOutput(process.out).findAll { key, val -> key != 'html' && key != 'zip' }).match() } ) } } @@ -106,7 +106,7 @@ nextflow_process { { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, - { assert snapshot(process.out.versions).match() } + { assert snapshot(sanitizeOutput(process.out).findAll { key, val -> key != 'html' && key != 'zip' }).match() } ) } } @@ -142,7 +142,7 @@ nextflow_process { { assert path(process.out.html[0][1][1]).text.contains("File typeConventional base calls") }, { assert path(process.out.html[0][1][2]).text.contains("File typeConventional base calls") }, { assert path(process.out.html[0][1][3]).text.contains("File typeConventional base calls") }, - { assert snapshot(process.out.versions).match() } + { assert snapshot(sanitizeOutput(process.out).findAll { key, val -> key != 'html' && key != 'zip' }).match() } ) } } @@ -166,7 +166,7 @@ nextflow_process { { assert process.out.html[0][1] ==~ ".*/mysample_fastqc.html" }, { assert process.out.zip[0][1] ==~ ".*/mysample_fastqc.zip" }, { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, - { assert snapshot(process.out.versions).match() } + { assert snapshot(sanitizeOutput(process.out).findAll { key, val -> key != 'html' && key != 'zip' }).match() } ) } } diff --git a/modules/nf-core/fastqc/tests/main.nf.test.snap b/modules/nf-core/fastqc/tests/main.nf.test.snap index d5db3092..c8ee120f 100644 --- a/modules/nf-core/fastqc/tests/main.nf.test.snap +++ b/modules/nf-core/fastqc/tests/main.nf.test.snap @@ -1,15 +1,21 @@ { "sarscov2 custom_prefix": { "content": [ - [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ] + { + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" + "nf-test": "0.9.2", + "nextflow": "25.10.0" }, - "timestamp": "2024-07-22T11:02:16.374038" + "timestamp": "2025-10-28T16:39:14.518503" }, "sarscov2 single-end [fastq] - stub": { "content": [ @@ -33,7 +39,11 @@ ] ], "2": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + [ + "FASTQC", + "fastqc", + "0.12.1" + ] ], "html": [ [ @@ -44,8 +54,12 @@ "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "versions": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] ], "zip": [ [ @@ -59,10 +73,10 @@ } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" + "nf-test": "0.9.2", + "nextflow": "25.10.0" }, - "timestamp": "2024-07-22T11:02:24.993809" + "timestamp": "2025-10-28T16:39:19.309008" }, "sarscov2 custom_prefix - stub": { "content": [ @@ -86,7 +100,11 @@ ] ], "2": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + [ + "FASTQC", + "fastqc", + "0.12.1" + ] ], "html": [ [ @@ -97,8 +115,12 @@ "mysample.html:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "versions": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] ], "zip": [ [ @@ -112,58 +134,82 @@ } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" + "nf-test": "0.9.2", + "nextflow": "25.10.0" }, - "timestamp": "2024-07-22T11:03:10.93942" + "timestamp": "2025-10-28T16:39:44.94888" }, "sarscov2 interleaved [fastq]": { "content": [ - [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ] + { + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" + "nf-test": "0.9.2", + "nextflow": "25.10.0" }, - "timestamp": "2024-07-22T11:01:42.355718" + "timestamp": "2025-10-28T16:38:45.168496" }, "sarscov2 paired-end [bam]": { "content": [ - [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ] + { + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" + "nf-test": "0.9.2", + "nextflow": "25.10.0" }, - "timestamp": "2024-07-22T11:01:53.276274" + "timestamp": "2025-10-28T16:38:53.268919" }, "sarscov2 multiple [fastq]": { "content": [ - [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ] + { + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" + "nf-test": "0.9.2", + "nextflow": "25.10.0" }, - "timestamp": "2024-07-22T11:02:05.527626" + "timestamp": "2025-10-28T16:39:05.050305" }, "sarscov2 paired-end [fastq]": { "content": [ - [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ] + { + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" + "nf-test": "0.9.2", + "nextflow": "25.10.0" }, - "timestamp": "2024-07-22T11:01:31.188871" + "timestamp": "2025-10-28T16:38:37.2373" }, "sarscov2 paired-end [fastq] - stub": { "content": [ @@ -187,7 +233,11 @@ ] ], "2": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + [ + "FASTQC", + "fastqc", + "0.12.1" + ] ], "html": [ [ @@ -198,8 +248,12 @@ "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "versions": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] ], "zip": [ [ @@ -213,10 +267,10 @@ } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" + "nf-test": "0.9.2", + "nextflow": "25.10.0" }, - "timestamp": "2024-07-22T11:02:34.273566" + "timestamp": "2025-10-28T16:39:24.450398" }, "sarscov2 multiple [fastq] - stub": { "content": [ @@ -240,7 +294,11 @@ ] ], "2": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + [ + "FASTQC", + "fastqc", + "0.12.1" + ] ], "html": [ [ @@ -251,8 +309,12 @@ "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "versions": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] ], "zip": [ [ @@ -266,22 +328,28 @@ } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" + "nf-test": "0.9.2", + "nextflow": "25.10.0" }, - "timestamp": "2024-07-22T11:03:02.304411" + "timestamp": "2025-10-28T16:39:39.758762" }, "sarscov2 single-end [fastq]": { "content": [ - [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ] + { + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" + "nf-test": "0.9.2", + "nextflow": "25.10.0" }, - "timestamp": "2024-07-22T11:01:19.095607" + "timestamp": "2025-10-28T16:38:29.555068" }, "sarscov2 interleaved [fastq] - stub": { "content": [ @@ -305,7 +373,11 @@ ] ], "2": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + [ + "FASTQC", + "fastqc", + "0.12.1" + ] ], "html": [ [ @@ -316,8 +388,12 @@ "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "versions": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] ], "zip": [ [ @@ -331,10 +407,10 @@ } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" + "nf-test": "0.9.2", + "nextflow": "25.10.0" }, - "timestamp": "2024-07-22T11:02:44.640184" + "timestamp": "2025-10-28T16:39:29.193136" }, "sarscov2 paired-end [bam] - stub": { "content": [ @@ -358,7 +434,11 @@ ] ], "2": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + [ + "FASTQC", + "fastqc", + "0.12.1" + ] ], "html": [ [ @@ -369,8 +449,12 @@ "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "versions": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] ], "zip": [ [ @@ -384,9 +468,9 @@ } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" + "nf-test": "0.9.2", + "nextflow": "25.10.0" }, - "timestamp": "2024-07-22T11:02:53.550742" + "timestamp": "2025-10-28T16:39:34.144919" } } \ No newline at end of file diff --git a/modules/nf-core/multiqc/.conda-lock/linux_amd64-bd-c1f4a7982b743963_1.txt b/modules/nf-core/multiqc/.conda-lock/linux_amd64-bd-c1f4a7982b743963_1.txt new file mode 100644 index 00000000..76190304 --- /dev/null +++ b/modules/nf-core/multiqc/.conda-lock/linux_amd64-bd-c1f4a7982b743963_1.txt @@ -0,0 +1,1552 @@ + +version: 6 +environments: +default: +channels: +- url: https://conda.anaconda.org/conda-forge/ +- url: https://conda.anaconda.org/bioconda/ +- url: https://conda.anaconda.org/bioconda/ +options: +pypi-prerelease-mode: if-necessary-or-explicit +packages: +linux-64: +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py314h3de4e8d_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.1-pyh8f84b5b_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/coloredlogs-15.0.1-pyhd8ed1ab_4.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/colormath-3.0.0-pyhd8ed1ab_4.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.3-py314hd8ed1ab_101.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/expat-2.7.4-hecca717_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.17.1-h27c8c51_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/humanfriendly-10.0-pyh707e725_8.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/humanize-4.15.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/kaleido-core-0.2.1-h3644ca4_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.18-h0c24ade_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.3-ha770c72_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.3-h73754d4_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-5_h47877c9_openblas.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.55-h421ea60_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.10.2-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py314h67df5f8_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/mathjax-2.7.7-ha770c72_3.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/bioconda/noarch/multiqc-1.33-pyhdfd78af_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.18.1-pyhcf101f3_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/natsort-8.4.0-pyhcf101f3_2.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/nspr-4.38-h29cc59b_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/nss-3.118-h445c969_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py314h2b28147_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.1.1-py314h8ec4b1a_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/polars-1.39.3-pyh58ad624_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/polars-lts-cpu-1.34.0.deprecated-hc364b38_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/polars-runtime-32-1.39.3-py310hffdcd12_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/polars-runtime-compat-1.39.3-py310hbcd5346_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/procps-ng-4.0.6-h18c060e_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pyaml-env-1.2.2-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.12.5-pyhcf101f3_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.41.5-py314h2e6c369_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.3-h32b2ec7_101_cp314.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/python-dotenv-1.2.2-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.3-h4df99d1_101.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/python-kaleido-0.2.1-pyhd8ed1ab_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/regex-2026.2.28-py314h5bd0f2a_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.3.3-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.9.7-pyh8f84b5b_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py314h2e6c369_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/spectra-0.0.11-pyhd8ed1ab_2.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.52.0-h04a0ce9_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/tiktoken-0.12.0-py314h67fec18_3.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/typeguard-4.5.1-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hceb46e0_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda +packages: +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda +build_number: 20 +sha256: 1dd3fffd892081df9726d7eb7e0dea6198962ba775bd88842135a4ddb4deb3c9 +md5: a9f577daf3de00bca7c3c76c0ecbd1de +depends: +- __glibc >=2.17,<3.0.a0 +- libgomp >=7.5.0 +constrains: +- openmp_impl <0.0a0 +license: BSD-3-Clause +license_family: BSD +size: 28948 +timestamp: 1770939786096 +- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda +sha256: a3967b937b9abf0f2a99f3173fa4630293979bd1644709d89580e7c62a544661 +md5: aaa2a381ccc56eac91d63b6c1240312f +depends: +- cpython +- python-gil +license: MIT +license_family: MIT +size: 8191 +timestamp: 1744137672556 +- conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda +sha256: e0ea1ba78fbb64f17062601edda82097fcf815012cf52bb704150a2668110d48 +md5: 2934f256a8acfe48f6ebb4fce6cde29c +depends: +- python >=3.9 +- typing-extensions >=4.0.0 +license: MIT +license_family: MIT +size: 18074 +timestamp: 1733247158254 +- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda +sha256: 1b6124230bb4e571b1b9401537ecff575b7b109cc3a21ee019f65e083b8399ab +md5: c6b0543676ecb1fb2d7643941fe375f2 +depends: +- python >=3.10 +- python +license: MIT +license_family: MIT +size: 64927 +timestamp: 1773935801332 +- conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda +noarch: generic +sha256: c31ab719d256bc6f89926131e88ecd0f0c5d003fe8481852c6424f4ec6c7eb29 +md5: a2ac7763a9ac75055b68f325d3255265 +depends: +- python >=3.14 +license: BSD-3-Clause AND MIT AND EPL-2.0 +size: 7514 +timestamp: 1767044983590 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py314h3de4e8d_1.conda +sha256: 3ad3500bff54a781c29f16ce1b288b36606e2189d0b0ef2f67036554f47f12b0 +md5: 8910d2c46f7e7b519129f486e0fe927a +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libstdcxx >=14 +- python >=3.14,<3.15.0a0 +- python_abi 3.14.* *_cp314 +constrains: +- libbrotlicommon 1.2.0 hb03c661_1 +license: MIT +license_family: MIT +size: 367376 +timestamp: 1764017265553 +- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda +sha256: 0b75d45f0bba3e95dc693336fa51f40ea28c980131fec438afb7ce6118ed05f6 +md5: d2ffd7602c02f2b316fd921d39876885 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +license: bzip2-1.0.6 +license_family: BSD +size: 260182 +timestamp: 1771350215188 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda +sha256: 67cc7101b36421c5913a1687ef1b99f85b5d6868da3abbf6ec1a4181e79782fc +md5: 4492fd26db29495f0ba23f146cd5638d +depends: +- __unix +license: ISC +size: 147413 +timestamp: 1772006283803 +- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda +sha256: a6b118fd1ed6099dc4fc03f9c492b88882a780fadaef4ed4f93dc70757713656 +md5: 765c4d97e877cdbbb88ff33152b86125 +depends: +- python >=3.10 +license: ISC +size: 151445 +timestamp: 1772001170301 +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda +sha256: d86dfd428b2e3c364fa90e07437c8405d635aa4ef54b25ab51d9c712be4112a5 +md5: 49ee13eb9b8f44d63879c69b8a40a74b +depends: +- python >=3.10 +license: MIT +license_family: MIT +size: 58510 +timestamp: 1773660086450 +- conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.1-pyh8f84b5b_1.conda +sha256: 38cfe1ee75b21a8361c8824f5544c3866f303af1762693a178266d7f198e8715 +md5: ea8a6c3256897cc31263de9f455e25d9 +depends: +- python >=3.10 +- __unix +- python +license: BSD-3-Clause +license_family: BSD +size: 97676 +timestamp: 1764518652276 +- conda: https://conda.anaconda.org/conda-forge/noarch/coloredlogs-15.0.1-pyhd8ed1ab_4.conda +sha256: 8021c76eeadbdd5784b881b165242db9449783e12ce26d6234060026fd6a8680 +md5: b866ff7007b934d564961066c8195983 +depends: +- humanfriendly >=9.1 +- python >=3.9 +license: MIT +license_family: MIT +size: 43758 +timestamp: 1733928076798 +- conda: https://conda.anaconda.org/conda-forge/noarch/colormath-3.0.0-pyhd8ed1ab_4.conda +sha256: 59c9e29800b483b390467f90e82b0da3a4fbf0612efe1c90813fca232780e160 +md5: 071cf7b0ce333c81718b054066c15102 +depends: +- networkx >=2.0 +- numpy +- python >=3.9 +license: BSD-3-Clause +license_family: BSD +size: 39326 +timestamp: 1735759976140 +- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.3-py314hd8ed1ab_101.conda +noarch: generic +sha256: 91b06300879df746214f7363d6c27c2489c80732e46a369eb2afc234bcafb44c +md5: 3bb89e4f795e5414addaa531d6b1500a +depends: +- python >=3.14,<3.15.0a0 +- python_abi * *_cp314 +license: Python-2.0 +size: 50078 +timestamp: 1770674447292 +- conda: https://conda.anaconda.org/conda-forge/linux-64/expat-2.7.4-hecca717_0.conda +sha256: 0cc345e4dead417996ce9a1f088b28d858f03d113d43c1963d29194366dcce27 +md5: a0535741a4934b3e386051065c58761a +depends: +- __glibc >=2.17,<3.0.a0 +- libexpat 2.7.4 hecca717_0 +- libgcc >=14 +license: MIT +license_family: MIT +size: 145274 +timestamp: 1771259434699 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 +sha256: 58d7f40d2940dd0a8aa28651239adbf5613254df0f75789919c4e6762054403b +md5: 0c96522c6bdaed4b1566d11387caaf45 +license: BSD-3-Clause +license_family: BSD +size: 397370 +timestamp: 1566932522327 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 +sha256: c52a29fdac682c20d252facc50f01e7c2e7ceac52aa9817aaf0bb83f7559ec5c +md5: 34893075a5c9e55cdafac56607368fc6 +license: OFL-1.1 +license_family: Other +size: 96530 +timestamp: 1620479909603 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 +sha256: 00925c8c055a2275614b4d983e1df637245e19058d79fc7dd1a93b8d9fb4b139 +md5: 4d59c254e01d9cde7957100457e2d5fb +license: OFL-1.1 +license_family: Other +size: 700814 +timestamp: 1620479612257 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda +sha256: 2821ec1dc454bd8b9a31d0ed22a7ce22422c0aef163c59f49dfdf915d0f0ca14 +md5: 49023d73832ef61042f6a237cb2687e7 +license: LicenseRef-Ubuntu-Font-Licence-Version-1.0 +license_family: Other +size: 1620504 +timestamp: 1727511233259 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.17.1-h27c8c51_0.conda +sha256: aa4a44dba97151221100a637c7f4bde619567afade9c0265f8e1c8eed8d7bd8c +md5: 867127763fbe935bab59815b6e0b7b5c +depends: +- __glibc >=2.17,<3.0.a0 +- libexpat >=2.7.4,<3.0a0 +- libfreetype >=2.14.1 +- libfreetype6 >=2.14.1 +- libgcc >=14 +- libuuid >=2.41.3,<3.0a0 +- libzlib >=1.3.1,<2.0a0 +license: MIT +license_family: MIT +size: 270705 +timestamp: 1771382710863 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda +sha256: 54eea8469786bc2291cc40bca5f46438d3e062a399e8f53f013b6a9f50e98333 +md5: a7970cd949a077b7cb9696379d338681 +depends: +- font-ttf-ubuntu +- font-ttf-inconsolata +- font-ttf-dejavu-sans-mono +- font-ttf-source-code-pro +license: BSD-3-Clause +license_family: BSD +size: 4059 +timestamp: 1762351264405 +- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda +sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 +md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 +depends: +- python >=3.10 +- hyperframe >=6.1,<7 +- hpack >=4.1,<5 +- python +license: MIT +license_family: MIT +size: 95967 +timestamp: 1756364871835 +- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda +sha256: 6ad78a180576c706aabeb5b4c8ceb97c0cb25f1e112d76495bff23e3779948ba +md5: 0a802cb9888dd14eeefc611f05c40b6e +depends: +- python >=3.9 +license: MIT +license_family: MIT +size: 30731 +timestamp: 1737618390337 +- conda: https://conda.anaconda.org/conda-forge/noarch/humanfriendly-10.0-pyh707e725_8.conda +sha256: fa2071da7fab758c669e78227e6094f6b3608228740808a6de5d6bce83d9e52d +md5: 7fe569c10905402ed47024fc481bb371 +depends: +- __unix +- python >=3.9 +license: MIT +license_family: MIT +size: 73563 +timestamp: 1733928021866 +- conda: https://conda.anaconda.org/conda-forge/noarch/humanize-4.15.0-pyhd8ed1ab_0.conda +sha256: 6c4343b376d0b12a4c75ab992640970d36c933cad1fd924f6a1181fa91710e80 +md5: daddf757c3ecd6067b9af1df1f25d89e +depends: +- python >=3.10 +license: MIT +license_family: MIT +size: 67994 +timestamp: 1766267728652 +- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda +sha256: 77af6f5fe8b62ca07d09ac60127a30d9069fdc3c68d6b256754d0ffb1f7779f8 +md5: 8e6923fc12f1fe8f8c4e5c9f343256ac +depends: +- python >=3.9 +license: MIT +license_family: MIT +size: 17397 +timestamp: 1737618427549 +- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda +sha256: fbf86c4a59c2ed05bbffb2ba25c7ed94f6185ec30ecb691615d42342baa1a16a +md5: c80d8a3b84358cb967fa81e7075fbc8a +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libstdcxx >=14 +license: MIT +license_family: MIT +size: 12723451 +timestamp: 1773822285671 +- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda +sha256: ae89d0299ada2a3162c2614a9d26557a92aa6a77120ce142f8e0109bbf0342b0 +md5: 53abe63df7e10a6ba605dc5f9f961d36 +depends: +- python >=3.10 +license: BSD-3-Clause +license_family: BSD +size: 50721 +timestamp: 1760286526795 +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda +sha256: 82ab2a0d91ca1e7e63ab6a4939356667ef683905dea631bc2121aa534d347b16 +md5: 080594bf4493e6bae2607e65390c520a +depends: +- python >=3.10 +- zipp >=3.20 +- python +license: Apache-2.0 +license_family: APACHE +size: 34387 +timestamp: 1773931568510 +- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda +sha256: fc9ca7348a4f25fed2079f2153ecdcf5f9cf2a0bc36c4172420ca09e1849df7b +md5: 04558c96691bed63104678757beb4f8d +depends: +- markupsafe >=2.0 +- python >=3.10 +- python +license: BSD-3-Clause +license_family: BSD +size: 120685 +timestamp: 1764517220861 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda +sha256: db973a37d75db8e19b5f44bbbdaead0c68dde745407f281e2a7fe4db74ec51d7 +md5: ada41c863af263cc4c5fcbaff7c3e4dc +depends: +- attrs >=22.2.0 +- jsonschema-specifications >=2023.3.6 +- python >=3.10 +- referencing >=0.28.4 +- rpds-py >=0.25.0 +- python +license: MIT +license_family: MIT +size: 82356 +timestamp: 1767839954256 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda +sha256: 0a4f3b132f0faca10c89fdf3b60e15abb62ded6fa80aebfc007d05965192aa04 +md5: 439cd0f567d697b20a8f45cb70a1005a +depends: +- python >=3.10 +- referencing >=0.31.0 +- python +license: MIT +license_family: MIT +size: 19236 +timestamp: 1757335715225 +- conda: https://conda.anaconda.org/conda-forge/linux-64/kaleido-core-0.2.1-h3644ca4_0.tar.bz2 +sha256: 7f243680ca03eba7457b7a48f93a9440ba8181a8eac20a3eb5ef165ab6c96664 +md5: b3723b235b0758abaae8c82ce4d80146 +depends: +- __glibc >=2.17,<3.0.a0 +- expat >=2.2.10,<3.0.0a0 +- fontconfig +- fonts-conda-forge +- libgcc-ng >=9.3.0 +- mathjax 2.7.* +- nspr >=4.29,<5.0a0 +- nss >=3.62,<4.0a0 +- sqlite >=3.34.0,<4.0a0 +license: MIT +license_family: MIT +size: 62099926 +timestamp: 1615199463039 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.18-h0c24ade_0.conda +sha256: 836ec4b895352110335b9fdcfa83a8dcdbe6c5fb7c06c4929130600caea91c0a +md5: 6f2e2c8f58160147c4d1c6f4c14cbac4 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libjpeg-turbo >=3.1.2,<4.0a0 +- libtiff >=4.7.1,<4.8.0a0 +license: MIT +license_family: MIT +size: 249959 +timestamp: 1768184673131 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda +sha256: 3d584956604909ff5df353767f3a2a2f60e07d070b328d109f30ac40cd62df6c +md5: 18335a698559cdbcd86150a48bf54ba6 +depends: +- __glibc >=2.17,<3.0.a0 +- zstd >=1.5.7,<1.6.0a0 +constrains: +- binutils_impl_linux-64 2.45.1 +license: GPL-3.0-only +license_family: GPL +size: 728002 +timestamp: 1774197446916 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda +sha256: f84cb54782f7e9cea95e810ea8fef186e0652d0fa73d3009914fa2c1262594e1 +md5: a752488c68f2e7c456bcbd8f16eec275 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libstdcxx >=14 +license: Apache-2.0 +license_family: Apache +size: 261513 +timestamp: 1773113328888 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda +build_number: 5 +sha256: 18c72545080b86739352482ba14ba2c4815e19e26a7417ca21a95b76ec8da24c +md5: c160954f7418d7b6e87eaf05a8913fa9 +depends: +- libopenblas >=0.3.30,<0.3.31.0a0 +- libopenblas >=0.3.30,<1.0a0 +constrains: +- mkl <2026 +- liblapack 3.11.0 5*_openblas +- libcblas 3.11.0 5*_openblas +- blas 2.305 openblas +- liblapacke 3.11.0 5*_openblas +license: BSD-3-Clause +license_family: BSD +size: 18213 +timestamp: 1765818813880 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda +build_number: 5 +sha256: 0cbdcc67901e02dc17f1d19e1f9170610bd828100dc207de4d5b6b8ad1ae7ad8 +md5: 6636a2b6f1a87572df2970d3ebc87cc0 +depends: +- libblas 3.11.0 5_h4a7cf45_openblas +constrains: +- liblapacke 3.11.0 5*_openblas +- blas 2.305 openblas +- liblapack 3.11.0 5*_openblas +license: BSD-3-Clause +license_family: BSD +size: 18194 +timestamp: 1765818837135 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda +sha256: aa8e8c4be9a2e81610ddf574e05b64ee131fab5e0e3693210c9d6d2fba32c680 +md5: 6c77a605a7a689d17d4819c0f8ac9a00 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +license: MIT +license_family: MIT +size: 73490 +timestamp: 1761979956660 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda +sha256: d78f1d3bea8c031d2f032b760f36676d87929b18146351c4464c66b0869df3f5 +md5: e7f7ce06ec24cfcfb9e36d28cf82ba57 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +constrains: +- expat 2.7.4.* +license: MIT +license_family: MIT +size: 76798 +timestamp: 1771259418166 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda +sha256: 31f19b6a88ce40ebc0d5a992c131f57d919f73c0b92cd1617a5bec83f6e961e6 +md5: a360c33a5abe61c07959e449fa1453eb +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +license: MIT +license_family: MIT +size: 58592 +timestamp: 1769456073053 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.3-ha770c72_0.conda +sha256: 38f014a7129e644636e46064ecd6b1945e729c2140e21d75bb476af39e692db2 +md5: e289f3d17880e44b633ba911d57a321b +depends: +- libfreetype6 >=2.14.3 +license: GPL-2.0-only OR FTL +size: 8049 +timestamp: 1774298163029 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.3-h73754d4_0.conda +sha256: 16f020f96da79db1863fcdd8f2b8f4f7d52f177dd4c58601e38e9182e91adf1d +md5: fb16b4b69e3f1dcfe79d80db8fd0c55d +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libpng >=1.6.55,<1.7.0a0 +- libzlib >=1.3.2,<2.0a0 +constrains: +- freetype >=2.14.3 +license: GPL-2.0-only OR FTL +size: 384575 +timestamp: 1774298162622 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda +sha256: faf7d2017b4d718951e3a59d081eb09759152f93038479b768e3d612688f83f5 +md5: 0aa00f03f9e39fb9876085dee11a85d4 +depends: +- __glibc >=2.17,<3.0.a0 +- _openmp_mutex >=4.5 +constrains: +- libgcc-ng ==15.2.0=*_18 +- libgomp 15.2.0 he0feb66_18 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 1041788 +timestamp: 1771378212382 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda +sha256: e318a711400f536c81123e753d4c797a821021fb38970cebfb3f454126016893 +md5: d5e96b1ed75ca01906b3d2469b4ce493 +depends: +- libgcc 15.2.0 he0feb66_18 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 27526 +timestamp: 1771378224552 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda +sha256: d2c9fad338fd85e4487424865da8e74006ab2e2475bd788f624d7a39b2a72aee +md5: 9063115da5bc35fdc3e1002e69b9ef6e +depends: +- libgfortran5 15.2.0 h68bc16d_18 +constrains: +- libgfortran-ng ==15.2.0=*_18 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 27523 +timestamp: 1771378269450 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda +sha256: 539b57cf50ec85509a94ba9949b7e30717839e4d694bc94f30d41c9d34de2d12 +md5: 646855f357199a12f02a87382d429b75 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=15.2.0 +constrains: +- libgfortran 15.2.0 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 2482475 +timestamp: 1771378241063 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda +sha256: 21337ab58e5e0649d869ab168d4e609b033509de22521de1bfed0c031bfc5110 +md5: 239c5e9546c38a1e884d69effcf4c882 +depends: +- __glibc >=2.17,<3.0.a0 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 603262 +timestamp: 1771378117851 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda +sha256: cc9aba923eea0af8e30e0f94f2ad7156e2984d80d1e8e7fe6be5a1f257f0eb32 +md5: 8397539e3a0bbd1695584fb4f927485a +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +constrains: +- jpeg <0.0.0a +license: IJG AND BSD-3-Clause AND Zlib +size: 633710 +timestamp: 1762094827865 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-5_h47877c9_openblas.conda +build_number: 5 +sha256: c723b6599fcd4c6c75dee728359ef418307280fa3e2ee376e14e85e5bbdda053 +md5: b38076eb5c8e40d0106beda6f95d7609 +depends: +- libblas 3.11.0 5_h4a7cf45_openblas +constrains: +- blas 2.305 openblas +- liblapacke 3.11.0 5*_openblas +- libcblas 3.11.0 5*_openblas +license: BSD-3-Clause +license_family: BSD +size: 18200 +timestamp: 1765818857876 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda +sha256: 755c55ebab181d678c12e49cced893598f2bab22d582fbbf4d8b83c18be207eb +md5: c7c83eecbb72d88b940c249af56c8b17 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +constrains: +- xz 5.8.2.* +license: 0BSD +size: 113207 +timestamp: 1768752626120 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda +sha256: fe171ed5cf5959993d43ff72de7596e8ac2853e9021dec0344e583734f1e0843 +md5: 2c21e66f50753a083cbe6b80f38268fa +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +license: BSD-2-Clause +license_family: BSD +size: 92400 +timestamp: 1769482286018 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda +sha256: 199d79c237afb0d4780ccd2fbf829cea80743df60df4705202558675e07dd2c5 +md5: be43915efc66345cccb3c310b6ed0374 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libgfortran +- libgfortran5 >=14.3.0 +constrains: +- openblas >=0.3.30,<0.3.31.0a0 +license: BSD-3-Clause +license_family: BSD +size: 5927939 +timestamp: 1763114673331 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.55-h421ea60_0.conda +sha256: 36ade759122cdf0f16e2a2562a19746d96cf9c863ffaa812f2f5071ebbe9c03c +md5: 5f13ffc7d30ffec87864e678df9957b4 +depends: +- libgcc >=14 +- __glibc >=2.17,<3.0.a0 +- libzlib >=1.3.1,<2.0a0 +license: zlib-acknowledgement +size: 317669 +timestamp: 1770691470744 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda +sha256: d716847b7deca293d2e49ed1c8ab9e4b9e04b9d780aea49a97c26925b28a7993 +md5: fd893f6a3002a635b5e50ceb9dd2c0f4 +depends: +- __glibc >=2.17,<3.0.a0 +- icu >=78.2,<79.0a0 +- libgcc >=14 +- libzlib >=1.3.1,<2.0a0 +license: blessing +size: 951405 +timestamp: 1772818874251 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda +sha256: 78668020064fdaa27e9ab65cd2997e2c837b564ab26ce3bf0e58a2ce1a525c6e +md5: 1b08cd684f34175e4514474793d44bcb +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc 15.2.0 he0feb66_18 +constrains: +- libstdcxx-ng ==15.2.0=*_18 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 5852330 +timestamp: 1771378262446 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda +sha256: e5f8c38625aa6d567809733ae04bb71c161a42e44a9fa8227abe61fa5c60ebe0 +md5: cd5a90476766d53e901500df9215e927 +depends: +- __glibc >=2.17,<3.0.a0 +- lerc >=4.0.0,<5.0a0 +- libdeflate >=1.25,<1.26.0a0 +- libgcc >=14 +- libjpeg-turbo >=3.1.0,<4.0a0 +- liblzma >=5.8.1,<6.0a0 +- libstdcxx >=14 +- libwebp-base >=1.6.0,<2.0a0 +- libzlib >=1.3.1,<2.0a0 +- zstd >=1.5.7,<1.6.0a0 +license: HPND +size: 435273 +timestamp: 1762022005702 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda +sha256: 1a7539cfa7df00714e8943e18de0b06cceef6778e420a5ee3a2a145773758aee +md5: db409b7c1720428638e7c0d509d3e1b5 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +license: BSD-3-Clause +license_family: BSD +size: 40311 +timestamp: 1766271528534 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda +sha256: 3aed21ab28eddffdaf7f804f49be7a7d701e8f0e46c856d801270b470820a37b +md5: aea31d2e5b1091feca96fcfe945c3cf9 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +constrains: +- libwebp 1.6.0 +license: BSD-3-Clause +license_family: BSD +size: 429011 +timestamp: 1752159441324 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda +sha256: 666c0c431b23c6cec6e492840b176dde533d48b7e6fb8883f5071223433776aa +md5: 92ed62436b625154323d40d5f2f11dd7 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=13 +- pthread-stubs +- xorg-libxau >=1.0.11,<2.0a0 +- xorg-libxdmcp +license: MIT +license_family: MIT +size: 395888 +timestamp: 1727278577118 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda +sha256: 55044c403570f0dc26e6364de4dc5368e5f3fc7ff103e867c487e2b5ab2bcda9 +md5: d87ff7921124eccd67248aa483c23fec +depends: +- __glibc >=2.17,<3.0.a0 +constrains: +- zlib 1.3.2 *_2 +license: Zlib +license_family: Other +size: 63629 +timestamp: 1774072609062 +- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.10.2-pyhcf101f3_0.conda +sha256: 20e0892592a3e7c683e3d66df704a9425d731486a97c34fc56af4da1106b2b6b +md5: ba0a9221ce1063f31692c07370d062f3 +depends: +- importlib-metadata >=4.4 +- python >=3.10 +- python +license: BSD-3-Clause +license_family: BSD +size: 85893 +timestamp: 1770694658918 +- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda +sha256: 7b1da4b5c40385791dbc3cc85ceea9fad5da680a27d5d3cb8bfaa185e304a89e +md5: 5b5203189eb668f042ac2b0826244964 +depends: +- mdurl >=0.1,<1 +- python >=3.10 +license: MIT +license_family: MIT +size: 64736 +timestamp: 1754951288511 +- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py314h67df5f8_1.conda +sha256: c279be85b59a62d5c52f5dd9a4cd43ebd08933809a8416c22c3131595607d4cf +md5: 9a17c4307d23318476d7fbf0fedc0cde +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- python >=3.14,<3.15.0a0 +- python_abi 3.14.* *_cp314 +constrains: +- jinja2 >=3.0.0 +license: BSD-3-Clause +license_family: BSD +size: 27424 +timestamp: 1772445227915 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mathjax-2.7.7-ha770c72_3.tar.bz2 +sha256: 02fef69bde69db264a12f21386612262f545b6e3e68d8f1ccec19f3eaae58edf +md5: 86e69bd82c2a2c6fd29f5ab7e02b3691 +license: Apache-2.0 +license_family: Apache +size: 22281629 +timestamp: 1662784498331 +- conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda +sha256: 78c1bbe1723449c52b7a9df1af2ee5f005209f67e40b6e1d3c7619127c43b1c7 +md5: 592132998493b3ff25fd7479396e8351 +depends: +- python >=3.9 +license: MIT +license_family: MIT +size: 14465 +timestamp: 1733255681319 +- conda: https://conda.anaconda.org/bioconda/noarch/multiqc-1.33-pyhdfd78af_0.conda +sha256: f005760b13093362fc9c997d603dd487de32ab2e821a3cbce52a42bcb8136517 +md5: 698a8a27c2b9d8a542c70cb47099a75e +depends: +- click +- coloredlogs +- humanize +- importlib-metadata +- jinja2 >=3.0.0 +- jsonschema +- markdown +- natsort +- numpy +- packaging +- pillow >=10.2.0 +- plotly >=5.18 +- polars-lts-cpu +- pyaml-env +- pydantic >=2.7.1 +- python >=3.8,!=3.14.1 +- python-dotenv +- python-kaleido 0.2.1 +- pyyaml >=4 +- requests +- rich >=10 +- rich-click +- spectra >=0.0.10 +- tiktoken +- tqdm +- typeguard +license: GPL-3.0-or-later +license_family: GPL3 +size: 4198799 +timestamp: 1765300743879 +- conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.18.1-pyhcf101f3_1.conda +sha256: 541fd4390a0687228b8578247f1536a821d9261389a65585af9d1a6f2a14e1e0 +md5: 30bec5e8f4c3969e2b1bd407c5e52afb +depends: +- python >=3.10 +- python +license: MIT +size: 280459 +timestamp: 1774380620329 +- conda: https://conda.anaconda.org/conda-forge/noarch/natsort-8.4.0-pyhcf101f3_2.conda +sha256: aeb1548eb72e4f198e72f19d242fb695b35add2ac7b2c00e0d83687052867680 +md5: e941e85e273121222580723010bd4fa2 +depends: +- python >=3.9 +- python +license: MIT +license_family: MIT +size: 39262 +timestamp: 1770905275632 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda +sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586 +md5: 47e340acb35de30501a76c7c799c41d7 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=13 +license: X11 AND BSD-3-Clause +size: 891641 +timestamp: 1738195959188 +- conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda +sha256: f6a82172afc50e54741f6f84527ef10424326611503c64e359e25a19a8e4c1c6 +md5: a2c1eeadae7a309daed9d62c96012a2b +depends: +- python >=3.11 +- python +constrains: +- numpy >=1.25 +- scipy >=1.11.2 +- matplotlib-base >=3.8 +- pandas >=2.0 +license: BSD-3-Clause +license_family: BSD +size: 1587439 +timestamp: 1765215107045 +- conda: https://conda.anaconda.org/conda-forge/linux-64/nspr-4.38-h29cc59b_0.conda +sha256: e3664264bd936c357523b55c71ed5a30263c6ba278d726a75b1eb112e6fb0b64 +md5: e235d5566c9cc8970eb2798dd4ecf62f +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libstdcxx >=14 +license: MPL-2.0 +license_family: MOZILLA +size: 228588 +timestamp: 1762348634537 +- conda: https://conda.anaconda.org/conda-forge/linux-64/nss-3.118-h445c969_0.conda +sha256: 44dd98ffeac859d84a6dcba79a2096193a42fc10b29b28a5115687a680dd6aea +md5: 567fbeed956c200c1db5782a424e58ee +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libsqlite >=3.51.0,<4.0a0 +- libstdcxx >=14 +- libzlib >=1.3.1,<2.0a0 +- nspr >=4.38,<5.0a0 +license: MPL-2.0 +license_family: MOZILLA +size: 2057773 +timestamp: 1763485556350 +- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py314h2b28147_0.conda +sha256: f2ba8cb0d86a6461a6bcf0d315c80c7076083f72c6733c9290086640723f79ec +md5: 36f5b7eb328bdc204954a2225cf908e2 +depends: +- python +- libstdcxx >=14 +- libgcc >=14 +- __glibc >=2.17,<3.0.a0 +- python_abi 3.14.* *_cp314 +- libcblas >=3.9.0,<4.0a0 +- liblapack >=3.9.0,<4.0a0 +- libblas >=3.9.0,<4.0a0 +constrains: +- numpy-base <0a0 +license: BSD-3-Clause +license_family: BSD +size: 8927860 +timestamp: 1773839233468 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda +sha256: 3900f9f2dbbf4129cf3ad6acf4e4b6f7101390b53843591c53b00f034343bc4d +md5: 11b3379b191f63139e29c0d19dee24cd +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libpng >=1.6.50,<1.7.0a0 +- libstdcxx >=14 +- libtiff >=4.7.1,<4.8.0a0 +- libzlib >=1.3.1,<2.0a0 +license: BSD-2-Clause +license_family: BSD +size: 355400 +timestamp: 1758489294972 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda +sha256: 44c877f8af015332a5d12f5ff0fb20ca32f896526a7d0cdb30c769df1144fb5c +md5: f61eb8cd60ff9057122a3d338b99c00f +depends: +- __glibc >=2.17,<3.0.a0 +- ca-certificates +- libgcc >=14 +license: Apache-2.0 +license_family: Apache +size: 3164551 +timestamp: 1769555830639 +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda +sha256: c1fc0f953048f743385d31c468b4a678b3ad20caffdeaa94bed85ba63049fd58 +md5: b76541e68fea4d511b1ac46a28dcd2c6 +depends: +- python >=3.8 +- python +license: Apache-2.0 +license_family: APACHE +size: 72010 +timestamp: 1769093650580 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.1.1-py314h8ec4b1a_0.conda +sha256: 9e6ec8f3213e8b7d64b0ad45f84c51a2c9eba4398efda31e196c9a56186133ee +md5: 79678378ae235e24b3aa83cee1b38207 +depends: +- python +- libgcc >=14 +- __glibc >=2.17,<3.0.a0 +- libwebp-base >=1.6.0,<2.0a0 +- zlib-ng >=2.3.3,<2.4.0a0 +- python_abi 3.14.* *_cp314 +- tk >=8.6.13,<8.7.0a0 +- libjpeg-turbo >=3.1.2,<4.0a0 +- libxcb >=1.17.0,<2.0a0 +- openjpeg >=2.5.4,<3.0a0 +- lcms2 >=2.18,<3.0a0 +- libtiff >=4.7.1,<4.8.0a0 +- libfreetype >=2.14.1 +- libfreetype6 >=2.14.1 +license: HPND +size: 1073026 +timestamp: 1770794002408 +- conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda +sha256: c418d325359fc7a0074cea7f081ef1bce26e114d2da8a0154c5d27ecc87a08e7 +md5: 3e9427ee186846052e81fadde8ebe96a +depends: +- narwhals >=1.15.1 +- packaging +- python >=3.10 +constrains: +- ipywidgets >=7.6 +license: MIT +license_family: MIT +size: 5251872 +timestamp: 1772628857717 +- conda: https://conda.anaconda.org/conda-forge/noarch/polars-1.39.3-pyh58ad624_1.conda +sha256: d332c2d5002fc440ae37ed9679ffc21b552f18d20232390005d1dd3bce0888d3 +md5: d5a4e013a30dd8dfde9ab39f45aaf9c1 +depends: +- polars-runtime-32 ==1.39.3 +- python >=3.10 +- python +constrains: +- numpy >=1.16.0 +- pyarrow >=7.0.0 +- fastexcel >=0.9 +- openpyxl >=3.0.0 +- xlsx2csv >=0.8.0 +- connectorx >=0.3.2 +- deltalake >=1.0.0 +- pyiceberg >=0.7.1 +- altair >=5.4.0 +- great_tables >=0.8.0 +- polars-runtime-32 ==1.39.3 +- polars-runtime-64 ==1.39.3 +- polars-runtime-compat ==1.39.3 +license: MIT +license_family: MIT +size: 533495 +timestamp: 1774207987966 +- conda: https://conda.anaconda.org/conda-forge/noarch/polars-lts-cpu-1.34.0.deprecated-hc364b38_0.conda +sha256: e466fb31f67ba9bde18deafeb34263ca5eb25807f39ead0e9d753a8e82c4c4f4 +md5: ef0340e75068ac8ff96462749b5c98e7 +depends: +- polars >=1.34.0 +- polars-runtime-compat >=1.34.0 +license: MIT +license_family: MIT +size: 3902 +timestamp: 1760206808444 +- conda: https://conda.anaconda.org/conda-forge/linux-64/polars-runtime-32-1.39.3-py310hffdcd12_1.conda +noarch: python +sha256: 9744f8086bb0832998f5b01076f57ddc9efbe460e493b14303c3567dc4f401e7 +md5: f9327f9f2cfc4215f55b613e64afd3ba +depends: +- python +- libstdcxx >=14 +- libgcc >=14 +- __glibc >=2.17,<3.0.a0 +- _python_abi3_support 1.* +- cpython >=3.10 +constrains: +- __glibc >=2.17 +license: MIT +license_family: MIT +size: 37570276 +timestamp: 1774207987966 +- conda: https://conda.anaconda.org/conda-forge/linux-64/polars-runtime-compat-1.39.3-py310hbcd5346_1.conda +noarch: python +sha256: bf0b932713f0f27924f42159c98426e0073bb6145ed796eaa4cec79ca05363c7 +md5: 4b9b312453eebd6fbdbbe2a88fa1b5c4 +depends: +- python +- libgcc >=14 +- libstdcxx >=14 +- __glibc >=2.17,<3.0.a0 +- _python_abi3_support 1.* +- cpython >=3.10 +constrains: +- __glibc >=2.17 +license: MIT +license_family: MIT +size: 37224264 +timestamp: 1774207985377 +- conda: https://conda.anaconda.org/conda-forge/linux-64/procps-ng-4.0.6-h18c060e_0.conda +sha256: 4ce2e1ee31a6217998f78c31ce7dc0a3e0557d9238b51d49dd20c52d467a126d +md5: f2c23a77b25efcad57d377b34bd84941 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- ncurses >=6.5,<7.0a0 +license: GPL-2.0-or-later AND LGPL-2.0-or-later +license_family: GPL +size: 593603 +timestamp: 1769710381284 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda +sha256: 9c88f8c64590e9567c6c80823f0328e58d3b1efb0e1c539c0315ceca764e0973 +md5: b3c17d95b5a10c6e64a21fa17573e70e +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=13 +license: MIT +license_family: MIT +size: 8252 +timestamp: 1726802366959 +- conda: https://conda.anaconda.org/conda-forge/noarch/pyaml-env-1.2.2-pyhd8ed1ab_0.conda +sha256: 58994e0d2ea8584cb399546e6f6896d771995e6121d1a7b6a2c9948388358932 +md5: e17be1016bcc3516827b836cd3e4d9dc +depends: +- python >=3.9 +- pyyaml >=5.0,<=7.0 +license: MIT +license_family: MIT +size: 14645 +timestamp: 1736766960536 +- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.12.5-pyhcf101f3_1.conda +sha256: 868569d9505b7fe246c880c11e2c44924d7613a8cdcc1f6ef85d5375e892f13d +md5: c3946ed24acdb28db1b5d63321dbca7d +depends: +- typing-inspection >=0.4.2 +- typing_extensions >=4.14.1 +- python >=3.10 +- typing-extensions >=4.6.1 +- annotated-types >=0.6.0 +- pydantic-core ==2.41.5 +- python +license: MIT +license_family: MIT +size: 340482 +timestamp: 1764434463101 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.41.5-py314h2e6c369_1.conda +sha256: 7e0ae379796e28a429f8e48f2fe22a0f232979d65ec455e91f8dac689247d39f +md5: 432b0716a1dfac69b86aa38fdd59b7e6 +depends: +- python +- typing-extensions >=4.6.0,!=4.7.0 +- libgcc >=14 +- __glibc >=2.17,<3.0.a0 +- python_abi 3.14.* *_cp314 +constrains: +- __glibc >=2.17 +license: MIT +license_family: MIT +size: 1943088 +timestamp: 1762988995556 +- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda +sha256: 5577623b9f6685ece2697c6eb7511b4c9ac5fb607c9babc2646c811b428fd46a +md5: 6b6ece66ebcae2d5f326c77ef2c5a066 +depends: +- python >=3.9 +license: BSD-2-Clause +license_family: BSD +size: 889287 +timestamp: 1750615908735 +- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda +sha256: ba3b032fa52709ce0d9fd388f63d330a026754587a2f461117cac9ab73d8d0d8 +md5: 461219d1a5bd61342293efa2c0c90eac +depends: +- __unix +- python >=3.9 +license: BSD-3-Clause +license_family: BSD +size: 21085 +timestamp: 1733217331982 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.3-h32b2ec7_101_cp314.conda +build_number: 101 +sha256: cb0628c5f1732f889f53a877484da98f5a0e0f47326622671396fb4f2b0cd6bd +md5: c014ad06e60441661737121d3eae8a60 +depends: +- __glibc >=2.17,<3.0.a0 +- bzip2 >=1.0.8,<2.0a0 +- ld_impl_linux-64 >=2.36.1 +- libexpat >=2.7.3,<3.0a0 +- libffi >=3.5.2,<3.6.0a0 +- libgcc >=14 +- liblzma >=5.8.2,<6.0a0 +- libmpdec >=4.0.0,<5.0a0 +- libsqlite >=3.51.2,<4.0a0 +- libuuid >=2.41.3,<3.0a0 +- libzlib >=1.3.1,<2.0a0 +- ncurses >=6.5,<7.0a0 +- openssl >=3.5.5,<4.0a0 +- python_abi 3.14.* *_cp314 +- readline >=8.3,<9.0a0 +- tk >=8.6.13,<8.7.0a0 +- tzdata +- zstd >=1.5.7,<1.6.0a0 +license: Python-2.0 +size: 36702440 +timestamp: 1770675584356 +python_site_packages_path: lib/python3.14/site-packages +- conda: https://conda.anaconda.org/conda-forge/noarch/python-dotenv-1.2.2-pyhcf101f3_0.conda +sha256: 74e417a768f59f02a242c25e7db0aa796627b5bc8c818863b57786072aeb85e5 +md5: 130584ad9f3a513cdd71b1fdc1244e9c +depends: +- python >=3.10 +license: BSD-3-Clause +license_family: BSD +size: 27848 +timestamp: 1772388605021 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.3-h4df99d1_101.conda +sha256: 233aebd94c704ac112afefbb29cf4170b7bc606e22958906f2672081bc50638a +md5: 235765e4ea0d0301c75965985163b5a1 +depends: +- cpython 3.14.3.* +- python_abi * *_cp314 +license: Python-2.0 +size: 50062 +timestamp: 1770674497152 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-kaleido-0.2.1-pyhd8ed1ab_0.tar.bz2 +sha256: e17bf63a30aec33432f1ead86e15e9febde9fc40a7f869c0e766be8d2db44170 +md5: 310259a5b03ff02289d7705f39e2b1d2 +depends: +- kaleido-core 0.2.1.* +- python >=3.5 +license: MIT +license_family: MIT +size: 18320 +timestamp: 1615204747600 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda +build_number: 8 +sha256: ad6d2e9ac39751cc0529dd1566a26751a0bf2542adb0c232533d32e176e21db5 +md5: 0539938c55b6b1a59b560e843ad864a4 +constrains: +- python 3.14.* *_cp314 +license: BSD-3-Clause +license_family: BSD +size: 6989 +timestamp: 1752805904792 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda +sha256: b318fb070c7a1f89980ef124b80a0b5ccf3928143708a85e0053cde0169c699d +md5: 2035f68f96be30dc60a5dfd7452c7941 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- python >=3.14,<3.15.0a0 +- python_abi 3.14.* *_cp314 +- yaml >=0.2.5,<0.3.0a0 +license: MIT +license_family: MIT +size: 202391 +timestamp: 1770223462836 +- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda +sha256: 12ffde5a6f958e285aa22c191ca01bbd3d6e710aa852e00618fa6ddc59149002 +md5: d7d95fc8287ea7bf33e0e7116d2b95ec +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- ncurses >=6.5,<7.0a0 +license: GPL-3.0-only +license_family: GPL +size: 345073 +timestamp: 1765813471974 +- conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda +sha256: 0577eedfb347ff94d0f2fa6c052c502989b028216996b45c7f21236f25864414 +md5: 870293df500ca7e18bedefa5838a22ab +depends: +- attrs >=22.2.0 +- python >=3.10 +- rpds-py >=0.7.0 +- typing_extensions >=4.4.0 +- python +license: MIT +license_family: MIT +size: 51788 +timestamp: 1760379115194 +- conda: https://conda.anaconda.org/conda-forge/linux-64/regex-2026.2.28-py314h5bd0f2a_0.conda +sha256: e085e336f1446f5263a3ec9747df8c719b6996753901181add50dc4fdd8bb2e8 +md5: 3c8b6a8c4d0ff5a264e9831eac4941f4 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- python >=3.14,<3.15.0a0 +- python_abi 3.14.* *_cp314 +license: Apache-2.0 AND CNRI-Python +license_family: PSF +size: 411924 +timestamp: 1772255161535 +- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda +sha256: 7813c38b79ae549504b2c57b3f33394cea4f2ad083f0994d2045c2e24cb538c5 +md5: c65df89a0b2e321045a9e01d1337b182 +depends: +- python >=3.10 +- certifi >=2017.4.17 +- charset-normalizer >=2,<4 +- idna >=2.5,<4 +- urllib3 >=1.21.1,<3 +- python +constrains: +- chardet >=3.0.2,<6 +license: Apache-2.0 +license_family: APACHE +size: 63602 +timestamp: 1766926974520 +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.3.3-pyhcf101f3_0.conda +sha256: b06ce84d6a10c266811a7d3adbfa1c11f13393b91cc6f8a5b468277d90be9590 +md5: 7a6289c50631d620652f5045a63eb573 +depends: +- markdown-it-py >=2.2.0 +- pygments >=2.13.0,<3.0.0 +- python >=3.10 +- typing_extensions >=4.0.0,<5.0.0 +- python +license: MIT +license_family: MIT +size: 208472 +timestamp: 1771572730357 +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.9.7-pyh8f84b5b_0.conda +sha256: aa3fcb167321bae51998de2e94d199109c9024f25a5a063cb1c28d8f1af33436 +md5: 0c20a8ebcddb24a45da89d5e917e6cb9 +depends: +- python >=3.10 +- rich >=12 +- click >=8 +- typing-extensions >=4 +- __unix +- python +license: MIT +license_family: MIT +size: 64356 +timestamp: 1769850479089 +- conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py314h2e6c369_0.conda +sha256: e53b0cbf3b324eaa03ca1fe1a688fdf4ab42cea9c25270b0a7307d8aaaa4f446 +md5: c1c368b5437b0d1a68f372ccf01cb133 +depends: +- python +- libgcc >=14 +- __glibc >=2.17,<3.0.a0 +- python_abi 3.14.* *_cp314 +constrains: +- __glibc >=2.17 +license: MIT +license_family: MIT +size: 376121 +timestamp: 1764543122774 +- conda: https://conda.anaconda.org/conda-forge/noarch/spectra-0.0.11-pyhd8ed1ab_2.conda +sha256: 7c65782d2511738e62c70462e89d65da4fa54d5a7e47c46667bcd27a59f81876 +md5: 472239e4eb7b5a84bb96b3ed7e3a596a +depends: +- colormath >=3.0.0 +- python >=3.9 +license: MIT +license_family: MIT +size: 22284 +timestamp: 1735770589188 +- conda: https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.52.0-h04a0ce9_0.conda +sha256: c9af81e7830d9c4b67a7f48e512d060df2676b29cac59e3b31f09dbfcee29c58 +md5: 7d9d7efe9541d4bb71b5934e8ee348ea +depends: +- __glibc >=2.17,<3.0.a0 +- icu >=78.2,<79.0a0 +- libgcc >=14 +- libsqlite 3.52.0 hf4e2dac_0 +- libzlib >=1.3.1,<2.0a0 +- ncurses >=6.5,<7.0a0 +- readline >=8.3,<9.0a0 +license: blessing +size: 203641 +timestamp: 1772818888368 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tiktoken-0.12.0-py314h67fec18_3.conda +sha256: 7e395d67fd249d901beb1ae269057763c0d8c3ee5f7a348694bdb16d158a37d9 +md5: d705f9d8a1185a2b01cced191177a028 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libstdcxx >=14 +- python >=3.14,<3.15.0a0 +- python_abi 3.14.* *_cp314 +- regex >=2022.1.18 +- requests >=2.26.0 +constrains: +- __glibc >=2.17 +license: MIT +license_family: MIT +size: 939648 +timestamp: 1764028306357 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda +sha256: cafeec44494f842ffeca27e9c8b0c27ed714f93ac77ddadc6aaf726b5554ebac +md5: cffd3bdd58090148f4cfcd831f4b26ab +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libzlib >=1.3.1,<2.0a0 +constrains: +- xorg-libx11 >=1.8.12,<2.0a0 +license: TCL +license_family: BSD +size: 3301196 +timestamp: 1769460227866 +- conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda +sha256: 9ef8e47cf00e4d6dcc114eb32a1504cc18206300572ef14d76634ba29dfe1eb6 +md5: e5ce43272193b38c2e9037446c1d9206 +depends: +- python >=3.10 +- __unix +- python +license: MPL-2.0 and MIT +size: 94132 +timestamp: 1770153424136 +- conda: https://conda.anaconda.org/conda-forge/noarch/typeguard-4.5.1-pyhd8ed1ab_0.conda +sha256: 39d8ae33c43cdb8f771373e149b0b4fae5a08960ac58dcca95b2f1642bb17448 +md5: 260af1b0a94f719de76b4e14094e9a3b +depends: +- importlib-metadata >=3.6 +- python >=3.10 +- typing-extensions >=4.10.0 +- typing_extensions >=4.14.0 +constrains: +- pytest >=7 +license: MIT +license_family: MIT +size: 36838 +timestamp: 1771532971545 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda +sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c +md5: edd329d7d3a4ab45dcf905899a7a6115 +depends: +- typing_extensions ==4.15.0 pyhcf101f3_0 +license: PSF-2.0 +license_family: PSF +size: 91383 +timestamp: 1756220668932 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_1.conda +sha256: 70db27de58a97aeb7ba7448366c9853f91b21137492e0b4430251a1870aa8ff4 +md5: a0a4a3035667fc34f29bfbd5c190baa6 +depends: +- python >=3.10 +- typing_extensions >=4.12.0 +license: MIT +license_family: MIT +size: 18923 +timestamp: 1764158430324 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda +sha256: 032271135bca55aeb156cee361c81350c6f3fb203f57d024d7e5a1fc9ef18731 +md5: 0caa1af407ecff61170c9437a808404d +depends: +- python >=3.10 +- python +license: PSF-2.0 +license_family: PSF +size: 51692 +timestamp: 1756220668932 +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda +sha256: 1d30098909076af33a35017eed6f2953af1c769e273a0626a04722ac4acaba3c +md5: ad659d0a2b3e47e38d829aa8cad2d610 +license: LicenseRef-Public-Domain +size: 119135 +timestamp: 1767016325805 +- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda +sha256: af641ca7ab0c64525a96fd9ad3081b0f5bcf5d1cbb091afb3f6ed5a9eee6111a +md5: 9272daa869e03efe68833e3dc7a02130 +depends: +- backports.zstd >=1.0.0 +- brotli-python >=1.2.0 +- h2 >=4,<5 +- pysocks >=1.5.6,<2.0,!=1.5.7 +- python >=3.10 +license: MIT +license_family: MIT +size: 103172 +timestamp: 1767817860341 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda +sha256: 6bc6ab7a90a5d8ac94c7e300cc10beb0500eeba4b99822768ca2f2ef356f731b +md5: b2895afaf55bf96a8c8282a2e47a5de0 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +license: MIT +license_family: MIT +size: 15321 +timestamp: 1762976464266 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda +sha256: 25d255fb2eef929d21ff660a0c687d38a6d2ccfbcbf0cc6aa738b12af6e9d142 +md5: 1dafce8548e38671bea82e3f5c6ce22f +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +license: MIT +license_family: MIT +size: 20591 +timestamp: 1762976546182 +- conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda +sha256: 6d9ea2f731e284e9316d95fa61869fe7bbba33df7929f82693c121022810f4ad +md5: a77f85f77be52ff59391544bfe73390a +depends: +- libgcc >=14 +- __glibc >=2.17,<3.0.a0 +license: MIT +license_family: MIT +size: 85189 +timestamp: 1753484064210 +- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda +sha256: b4533f7d9efc976511a73ef7d4a2473406d7f4c750884be8e8620b0ce70f4dae +md5: 30cd29cb87d819caead4d55184c1d115 +depends: +- python >=3.10 +- python +license: MIT +license_family: MIT +size: 24194 +timestamp: 1764460141901 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hceb46e0_1.conda +sha256: ea4e50c465d70236408cb0bfe0115609fd14db1adcd8bd30d8918e0291f8a75f +md5: 2aadb0d17215603a82a2a6b0afd9a4cb +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libstdcxx >=14 +license: Zlib +license_family: Other +size: 122618 +timestamp: 1770167931827 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda +sha256: 68f0206ca6e98fea941e5717cec780ed2873ffabc0e1ed34428c061e2c6268c7 +md5: 4a13eeac0b5c8e5b8ab496e6c4ddd829 +depends: +- __glibc >=2.17,<3.0.a0 +- libzlib >=1.3.1,<2.0a0 +license: BSD-3-Clause +license_family: BSD +size: 601375 +timestamp: 1764777111296 diff --git a/modules/nf-core/multiqc/.conda-lock/linux_amd64-bd-db7c73dae76bc9e6_1.txt b/modules/nf-core/multiqc/.conda-lock/linux_amd64-bd-db7c73dae76bc9e6_1.txt new file mode 100644 index 00000000..a55a4d49 --- /dev/null +++ b/modules/nf-core/multiqc/.conda-lock/linux_amd64-bd-db7c73dae76bc9e6_1.txt @@ -0,0 +1,126 @@ + +# This file may be used to create an environment using: +# $ conda create --name --file +# platform: linux-64 +@EXPLICIT +https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda#239c5e9546c38a1e884d69effcf4c882 +https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda#a9f577daf3de00bca7c3c76c0ecbd1de +https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda#0aa00f03f9e39fb9876085dee11a85d4 +https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda#d2ffd7602c02f2b316fd921d39876885 +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda#d87ff7921124eccd67248aa483c23fec +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda#4a13eeac0b5c8e5b8ab496e6c4ddd829 +https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda#18335a698559cdbcd86150a48bf54ba6 +https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda#49f570f3bc4c874a06ea69b7225753af +https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda#a360c33a5abe61c07959e449fa1453eb +https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda#b88d90cad08e6bc8ad540cb310a761fb +https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda#2c21e66f50753a083cbe6b80f38268fa +https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda#1b08cd684f34175e4514474793d44bcb +https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda#c80d8a3b84358cb967fa81e7075fbc8a +https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.0-hf4e2dac_0.conda#810d83373448da85c3f673fbcb7ad3a3 +https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42-h5347b49_0.conda#38ffe67b78c9d4de527be8315e5ada2c +https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda#47e340acb35de30501a76c7c799c41d7 +https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.4.22-hbd8a1cb_0.conda#e18ad67cf881dcadee8b8d9e2f8e5f73 +https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda#da1b85b6a87e141f5140bb9924cecab0 +https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda#0539938c55b6b1a59b560e843ad864a4 +https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda#d7d95fc8287ea7bf33e0e7116d2b95ec +https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda#cffd3bdd58090148f4cfcd831f4b26ab +https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda#ad659d0a2b3e47e38d829aa8cad2d610 +https://conda.anaconda.org/conda-forge/linux-64/python-3.14.4-habeac84_100_cp314.conda#a443f87920815d41bfe611296e507995 +https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.4-py314hd8ed1ab_100.conda#f111d4cfaf1fe9496f386bc98ae94452 +https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.4-h4df99d1_100.conda#e4e60721757979d01d3964122f674959 +https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda#aaa2a381ccc56eac91d63b6c1240312f +https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda#0caa1af407ecff61170c9437a808404d +https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda#edd329d7d3a4ab45dcf905899a7a6115 +https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda#2934f256a8acfe48f6ebb4fce6cde29c +https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda#c6b0543676ecb1fb2d7643941fe375f2 +https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda#a2ac7763a9ac75055b68f325d3255265 +https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py314h3de4e8d_1.conda#8910d2c46f7e7b519129f486e0fe927a +https://conda.anaconda.org/conda-forge/noarch/certifi-2026.4.22-pyhd8ed1ab_0.conda#929471569c93acefb30282a22060dcd5 +https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda#a9167b9571f3baa9d448faa2139d1089 +https://conda.anaconda.org/conda-forge/noarch/click-8.3.2-pyhc90fa1f_0.conda#4d18bc3af7cfcea97bd817164672a08c +https://conda.anaconda.org/conda-forge/noarch/humanfriendly-10.0-pyh707e725_8.conda#7fe569c10905402ed47024fc481bb371 +https://conda.anaconda.org/conda-forge/noarch/coloredlogs-15.0.1-pyhd8ed1ab_4.conda#b866ff7007b934d564961066c8195983 +https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda#a2c1eeadae7a309daed9d62c96012a2b +https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda#646855f357199a12f02a87382d429b75 +https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda#9063115da5bc35fdc3e1002e69b9ef6e +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda#89d61bc91d3f39fda0ca10fcd3c68594 +https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda#6d6d225559bfa6e2f3c90ee9c03d4e2e +https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda#36ae340a916635b97ac8a0655ace2a35 +https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-6_h47877c9_openblas.conda#881d801569b201c2e753f03c84b85e15 +https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py314h2b28147_0.conda#36f5b7eb328bdc204954a2225cf908e2 +https://conda.anaconda.org/conda-forge/noarch/colormath-3.0.0-pyhd8ed1ab_4.conda#071cf7b0ce333c81718b054066c15102 +https://conda.anaconda.org/conda-forge/linux-64/expat-2.7.5-hecca717_0.conda#7de50d165039df32d38be74c1b34a910 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb +https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda#49023d73832ef61042f6a237cb2687e7 +https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.58-h421ea60_0.conda#eba48a68a1a2b9d3c0d9511548db85db +https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.3-h73754d4_0.conda#fb16b4b69e3f1dcfe79d80db8fd0c55d +https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.3-ha770c72_0.conda#e289f3d17880e44b633ba911d57a321b +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.17.1-h27c8c51_0.conda#867127763fbe935bab59815b6e0b7b5c +https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda#a7970cd949a077b7cb9696379d338681 +https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda#0a802cb9888dd14eeefc611f05c40b6e +https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda#8e6923fc12f1fe8f8c4e5c9f343256ac +https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda#164fc43f0b53b6e3a7bc7dce5e4f1dc9 +https://conda.anaconda.org/conda-forge/noarch/humanize-4.15.0-pyhd8ed1ab_0.conda#daddf757c3ecd6067b9af1df1f25d89e +https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda#fb7130c190f9b4ec91219840a05ba3ac +https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda#e1c36c6121a7c9c76f2f148f1e83b983 +https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda#080594bf4493e6bae2607e65390c520a +https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py314h67df5f8_1.conda#9a17c4307d23318476d7fbf0fedc0cde +https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda#04558c96691bed63104678757beb4f8d +https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py314h2e6c369_0.conda#c1c368b5437b0d1a68f372ccf01cb133 +https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda#870293df500ca7e18bedefa5838a22ab +https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda#439cd0f567d697b20a8f45cb70a1005a +https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda#ada41c863af263cc4c5fcbaff7c3e4dc +https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda#d5e96b1ed75ca01906b3d2469b4ce493 +https://conda.anaconda.org/conda-forge/linux-64/mathjax-2.7.7-ha770c72_3.tar.bz2#86e69bd82c2a2c6fd29f5ab7e02b3691 +https://conda.anaconda.org/conda-forge/linux-64/nspr-4.38-h29cc59b_0.conda#e235d5566c9cc8970eb2798dd4ecf62f +https://conda.anaconda.org/conda-forge/linux-64/nss-3.118-h445c969_0.conda#567fbeed956c200c1db5782a424e58ee +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.53.0-h04a0ce9_0.conda#dc540e5bd5616d83a1ec46af8315ff98 +https://conda.anaconda.org/conda-forge/linux-64/kaleido-core-0.2.1-h3644ca4_0.tar.bz2#b3723b235b0758abaae8c82ce4d80146 +https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.4.1-hb03c661_0.conda#6178c6f2fb254558238ef4e6c56fb782 +https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda#a752488c68f2e7c456bcbd8f16eec275 +https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda#6c77a605a7a689d17d4819c0f8ac9a00 +https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda#aea31d2e5b1091feca96fcfe945c3cf9 +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda#cd5a90476766d53e901500df9215e927 +https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.18-h0c24ade_0.conda#6f2e2c8f58160147c4d1c6f4c14cbac4 +https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda#b3c17d95b5a10c6e64a21fa17573e70e +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda#b2895afaf55bf96a8c8282a2e47a5de0 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda#1dafce8548e38671bea82e3f5c6ce22f +https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda#92ed62436b625154323d40d5f2f11dd7 +https://conda.anaconda.org/conda-forge/noarch/markdown-3.10.2-pyhcf101f3_0.conda#ba0a9221ce1063f31692c07370d062f3 +https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda#592132998493b3ff25fd7479396e8351 +https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda#5b5203189eb668f042ac2b0826244964 +https://conda.anaconda.org/conda-forge/noarch/natsort-8.4.0-pyhcf101f3_2.conda#e941e85e273121222580723010bd4fa2 +https://conda.anaconda.org/conda-forge/noarch/packaging-26.1-pyhc364b38_0.conda#b8ae38639d323d808da535fb71e31be8 +https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda#11b3379b191f63139e29c0d19dee24cd +https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hceb46e0_1.conda#2aadb0d17215603a82a2a6b0afd9a4cb +https://conda.anaconda.org/conda-forge/linux-64/pillow-12.2.0-py314h8ec4b1a_0.conda#76c4757c0ec9d11f969e8eb44899307b +https://conda.anaconda.org/conda-forge/noarch/narwhals-2.20.0-pyhcf101f3_0.conda#6cac1a50359219d786453c6fef819f98 +https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda#3e9427ee186846052e81fadde8ebe96a +https://conda.anaconda.org/conda-forge/linux-64/polars-runtime-32-1.40.0-py310hffdcd12_0.conda#8eacf9ff4d4e1ca1b52f8f3ba3e0c993 +https://conda.anaconda.org/conda-forge/noarch/polars-1.40.0-pyh58ad624_0.conda#fd16be490f5403adfbf27dd4901bbe34 +https://conda.anaconda.org/conda-forge/linux-64/polars-runtime-compat-1.40.0-py310hbcd5346_0.conda#03a6899e17bb731c8e21b08212f1a64c +https://conda.anaconda.org/conda-forge/noarch/polars-lts-cpu-1.34.0.deprecated-hc364b38_0.conda#ef0340e75068ac8ff96462749b5c98e7 +https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda#a77f85f77be52ff59391544bfe73390a +https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda#2035f68f96be30dc60a5dfd7452c7941 +https://conda.anaconda.org/conda-forge/noarch/pyaml-env-1.2.2-pyhd8ed1ab_0.conda#e17be1016bcc3516827b836cd3e4d9dc +https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.3-py314h2e6c369_0.conda#1f3fd537f929b8d3236f9f0f0e7f7a32 +https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_1.conda#a0a4a3035667fc34f29bfbd5c190baa6 +https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.3-pyhcf101f3_0.conda#f690e6f204efd2e5c06b57518a383d98 +https://conda.anaconda.org/conda-forge/noarch/python-dotenv-1.2.2-pyhcf101f3_0.conda#130584ad9f3a513cdd71b1fdc1244e9c +https://conda.anaconda.org/conda-forge/noarch/python-kaleido-0.2.1-pyhd8ed1ab_0.tar.bz2#310259a5b03ff02289d7705f39e2b1d2 +https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda#461219d1a5bd61342293efa2c0c90eac +https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda#9272daa869e03efe68833e3dc7a02130 +https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda#10afbb4dbf06ff959ad25a92ccee6e59 +https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda#16c18772b340887160c79a6acc022db0 +https://conda.anaconda.org/conda-forge/noarch/rich-15.0.0-pyhcf101f3_0.conda#0242025a3c804966bf71aa04eee82f66 +https://conda.anaconda.org/conda-forge/noarch/rich-click-1.9.7-pyh8f84b5b_0.conda#0c20a8ebcddb24a45da89d5e917e6cb9 +https://conda.anaconda.org/conda-forge/noarch/spectra-0.0.11-pyhd8ed1ab_2.conda#472239e4eb7b5a84bb96b3ed7e3a596a +https://conda.anaconda.org/conda-forge/linux-64/regex-2026.4.4-py314h5bd0f2a_0.conda#4ffb42385183c854564f1f9adcf80a63 +https://conda.anaconda.org/conda-forge/linux-64/tiktoken-0.12.0-py314h67fec18_3.conda#d705f9d8a1185a2b01cced191177a028 +https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda#e5ce43272193b38c2e9037446c1d9206 +https://conda.anaconda.org/conda-forge/noarch/typeguard-4.5.1-pyhd8ed1ab_0.conda#260af1b0a94f719de76b4e14094e9a3b +https://conda.anaconda.org/bioconda/noarch/multiqc-1.34-pyhdfd78af_0.conda#a7111ab9a6a6146b40cbce16655ac873 +https://conda.anaconda.org/conda-forge/noarch/pip-26.0.1-pyh145f28c_0.conda#09a970fbf75e8ed1aa633827ded6aa4f +https://conda.anaconda.org/conda-forge/linux-64/procps-ng-4.0.6-h18c060e_0.conda#f2c23a77b25efcad57d377b34bd84941 diff --git a/modules/nf-core/multiqc/.conda-lock/linux_arm64-bd-40bf3b435e89dc22_1.txt b/modules/nf-core/multiqc/.conda-lock/linux_arm64-bd-40bf3b435e89dc22_1.txt new file mode 100644 index 00000000..a58231a0 --- /dev/null +++ b/modules/nf-core/multiqc/.conda-lock/linux_arm64-bd-40bf3b435e89dc22_1.txt @@ -0,0 +1,1502 @@ + +version: 6 +environments: +default: +channels: +- url: https://conda.anaconda.org/conda-forge/ +- url: https://conda.anaconda.org/bioconda/ +- url: https://conda.anaconda.org/bioconda/ +options: +pypi-prerelease-mode: if-necessary-or-explicit +packages: +linux-aarch64: +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-20_gnu.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/brotli-python-1.2.0-py314h352cb57_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/bzip2-1.0.8-h4777abc_9.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.1-pyh8f84b5b_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/coloredlogs-15.0.1-pyhd8ed1ab_4.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/colormath-3.0.0-pyhd8ed1ab_4.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.3-py314hd8ed1ab_101.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/expat-2.7.4-hfae3067_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/fontconfig-2.17.1-hba86a56_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/humanfriendly-10.0-pyh707e725_8.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/humanize-4.15.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/icu-78.3-hcab7f73_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/kaleido-core-0.2.1-he5a581e_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lcms2-2.18-h9d5b58d_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ld_impl_linux-aarch64-2.45.1-default_h1979696_102.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lerc-4.1.0-h52b7260_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libblas-3.11.0-5_haddc8a3_openblas.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libcblas-3.11.0-5_hd72aa62_openblas.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libdeflate-1.25-h1af38f5_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libexpat-2.7.4-hfae3067_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libffi-3.5.2-h376a255_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libfreetype-2.14.3-h8af1aa0_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libfreetype6-2.14.3-hdae7a39_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-15.2.0-h8acb6b2_18.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-ng-15.2.0-he9431aa_18.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgfortran-15.2.0-he9431aa_18.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgfortran5-15.2.0-h1b7bec0_18.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgomp-15.2.0-h8acb6b2_18.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libjpeg-turbo-3.1.2-he30d5cf_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/liblapack-3.11.0-5_h88aeb00_openblas.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/liblzma-5.8.2-he30d5cf_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmpdec-4.0.0-he30d5cf_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libopenblas-0.3.30-pthreads_h9d3fd7e_4.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libpng-1.6.55-h1abf092_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libsqlite-3.52.0-h10b116e_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libstdcxx-15.2.0-hef695bb_18.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libtiff-4.7.1-hdb009f0_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libuuid-2.41.3-h1022ec0_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libwebp-base-1.6.0-ha2e29f5_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxcb-1.17.0-h262b8f6_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libzlib-1.3.2-hdc9db2a_2.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.10.2-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/markupsafe-3.0.3-py314hb76de3f_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/mathjax-2.7.7-h8af1aa0_3.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/bioconda/noarch/multiqc-1.33-pyhdfd78af_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.18.1-pyhcf101f3_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/natsort-8.4.0-pyhcf101f3_2.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ncurses-6.5-ha32ae93_3.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/nspr-4.38-h3ad9384_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/nss-3.118-h544fa81_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/numpy-2.4.3-py314haac167e_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/openjpeg-2.5.4-h5da879a_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/openssl-3.6.1-h546c87b_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pillow-12.1.1-py314hac3e5ec_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/polars-1.39.3-pyh58ad624_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/polars-lts-cpu-1.34.0.deprecated-hc364b38_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/polars-runtime-32-1.39.3-py310hff09b76_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/polars-runtime-compat-1.39.3-py310hf00a4a2_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/procps-ng-4.0.6-h1779866_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pthread-stubs-0.4-h86ecc28_1002.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pyaml-env-1.2.2-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.12.5-pyhcf101f3_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pydantic-core-2.41.5-py314h451b6cc_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/python-3.14.3-hb06a95a_101_cp314.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/python-dotenv-1.2.2-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.3-h4df99d1_101.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/python-kaleido-0.2.1-pyhd8ed1ab_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pyyaml-6.0.3-py314h807365f_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/readline-8.3-hb682ff5_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/regex-2026.2.28-py314h51f160d_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.3.3-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.9.7-pyh8f84b5b_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/rpds-py-0.30.0-py314h02b7a91_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/spectra-0.0.11-pyhd8ed1ab_2.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/sqlite-3.52.0-hf1c7be2_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/tiktoken-0.12.0-py314h6a36e60_3.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/tk-8.6.13-noxft_h0dc03b3_103.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/typeguard-4.5.1-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxau-1.0.12-he30d5cf_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxdmcp-1.1.5-he30d5cf_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/yaml-0.2.5-h80f16a2_3.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zlib-ng-2.3.3-ha7cb516_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.7-h85ac4a6_6.conda +packages: +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-20_gnu.conda +build_number: 20 +sha256: a2527b1d81792a0ccd2c05850960df119c2b6d8f5fdec97f2db7d25dc23b1068 +md5: 468fd3bb9e1f671d36c2cbc677e56f1d +depends: +- libgomp >=7.5.0 +constrains: +- openmp_impl <0.0a0 +license: BSD-3-Clause +license_family: BSD +size: 28926 +timestamp: 1770939656741 +- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda +sha256: a3967b937b9abf0f2a99f3173fa4630293979bd1644709d89580e7c62a544661 +md5: aaa2a381ccc56eac91d63b6c1240312f +depends: +- cpython +- python-gil +license: MIT +license_family: MIT +size: 8191 +timestamp: 1744137672556 +- conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda +sha256: e0ea1ba78fbb64f17062601edda82097fcf815012cf52bb704150a2668110d48 +md5: 2934f256a8acfe48f6ebb4fce6cde29c +depends: +- python >=3.9 +- typing-extensions >=4.0.0 +license: MIT +license_family: MIT +size: 18074 +timestamp: 1733247158254 +- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda +sha256: 1b6124230bb4e571b1b9401537ecff575b7b109cc3a21ee019f65e083b8399ab +md5: c6b0543676ecb1fb2d7643941fe375f2 +depends: +- python >=3.10 +- python +license: MIT +license_family: MIT +size: 64927 +timestamp: 1773935801332 +- conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda +noarch: generic +sha256: c31ab719d256bc6f89926131e88ecd0f0c5d003fe8481852c6424f4ec6c7eb29 +md5: a2ac7763a9ac75055b68f325d3255265 +depends: +- python >=3.14 +license: BSD-3-Clause AND MIT AND EPL-2.0 +size: 7514 +timestamp: 1767044983590 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/brotli-python-1.2.0-py314h352cb57_1.conda +sha256: 5a5b0cdcd7ed89c6a8fb830924967f6314a2b71944bc1ebc2c105781ba97aa75 +md5: a1b5c571a0923a205d663d8678df4792 +depends: +- libgcc >=14 +- libstdcxx >=14 +- python >=3.14,<3.15.0a0 +- python >=3.14,<3.15.0a0 *_cp314 +- python_abi 3.14.* *_cp314 +constrains: +- libbrotlicommon 1.2.0 he30d5cf_1 +license: MIT +license_family: MIT +size: 373193 +timestamp: 1764017486851 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/bzip2-1.0.8-h4777abc_9.conda +sha256: b3495077889dde6bb370938e7db82be545c73e8589696ad0843a32221520ad4c +md5: 840d8fc0d7b3209be93080bc20e07f2d +depends: +- libgcc >=14 +license: bzip2-1.0.6 +license_family: BSD +size: 192412 +timestamp: 1771350241232 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda +sha256: 67cc7101b36421c5913a1687ef1b99f85b5d6868da3abbf6ec1a4181e79782fc +md5: 4492fd26db29495f0ba23f146cd5638d +depends: +- __unix +license: ISC +size: 147413 +timestamp: 1772006283803 +- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda +sha256: a6b118fd1ed6099dc4fc03f9c492b88882a780fadaef4ed4f93dc70757713656 +md5: 765c4d97e877cdbbb88ff33152b86125 +depends: +- python >=3.10 +license: ISC +size: 151445 +timestamp: 1772001170301 +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.6-pyhd8ed1ab_0.conda +sha256: d86dfd428b2e3c364fa90e07437c8405d635aa4ef54b25ab51d9c712be4112a5 +md5: 49ee13eb9b8f44d63879c69b8a40a74b +depends: +- python >=3.10 +license: MIT +license_family: MIT +size: 58510 +timestamp: 1773660086450 +- conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.1-pyh8f84b5b_1.conda +sha256: 38cfe1ee75b21a8361c8824f5544c3866f303af1762693a178266d7f198e8715 +md5: ea8a6c3256897cc31263de9f455e25d9 +depends: +- python >=3.10 +- __unix +- python +license: BSD-3-Clause +license_family: BSD +size: 97676 +timestamp: 1764518652276 +- conda: https://conda.anaconda.org/conda-forge/noarch/coloredlogs-15.0.1-pyhd8ed1ab_4.conda +sha256: 8021c76eeadbdd5784b881b165242db9449783e12ce26d6234060026fd6a8680 +md5: b866ff7007b934d564961066c8195983 +depends: +- humanfriendly >=9.1 +- python >=3.9 +license: MIT +license_family: MIT +size: 43758 +timestamp: 1733928076798 +- conda: https://conda.anaconda.org/conda-forge/noarch/colormath-3.0.0-pyhd8ed1ab_4.conda +sha256: 59c9e29800b483b390467f90e82b0da3a4fbf0612efe1c90813fca232780e160 +md5: 071cf7b0ce333c81718b054066c15102 +depends: +- networkx >=2.0 +- numpy +- python >=3.9 +license: BSD-3-Clause +license_family: BSD +size: 39326 +timestamp: 1735759976140 +- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.3-py314hd8ed1ab_101.conda +noarch: generic +sha256: 91b06300879df746214f7363d6c27c2489c80732e46a369eb2afc234bcafb44c +md5: 3bb89e4f795e5414addaa531d6b1500a +depends: +- python >=3.14,<3.15.0a0 +- python_abi * *_cp314 +license: Python-2.0 +size: 50078 +timestamp: 1770674447292 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/expat-2.7.4-hfae3067_0.conda +sha256: 5f087bef054c681edcaae84a8c2230585b938691e371ff92957a30707b7fcdf7 +md5: b304307db639831ad7caabd2eac6fca6 +depends: +- libexpat 2.7.4 hfae3067_0 +- libgcc >=14 +license: MIT +license_family: MIT +size: 137701 +timestamp: 1771259543650 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 +sha256: 58d7f40d2940dd0a8aa28651239adbf5613254df0f75789919c4e6762054403b +md5: 0c96522c6bdaed4b1566d11387caaf45 +license: BSD-3-Clause +license_family: BSD +size: 397370 +timestamp: 1566932522327 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 +sha256: c52a29fdac682c20d252facc50f01e7c2e7ceac52aa9817aaf0bb83f7559ec5c +md5: 34893075a5c9e55cdafac56607368fc6 +license: OFL-1.1 +license_family: Other +size: 96530 +timestamp: 1620479909603 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 +sha256: 00925c8c055a2275614b4d983e1df637245e19058d79fc7dd1a93b8d9fb4b139 +md5: 4d59c254e01d9cde7957100457e2d5fb +license: OFL-1.1 +license_family: Other +size: 700814 +timestamp: 1620479612257 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda +sha256: 2821ec1dc454bd8b9a31d0ed22a7ce22422c0aef163c59f49dfdf915d0f0ca14 +md5: 49023d73832ef61042f6a237cb2687e7 +license: LicenseRef-Ubuntu-Font-Licence-Version-1.0 +license_family: Other +size: 1620504 +timestamp: 1727511233259 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/fontconfig-2.17.1-hba86a56_0.conda +sha256: 835aff8615dd8d8fff377679710ce81b8a2c47b6404e21a92fb349fda193a15c +md5: 0fed1ff55f4938a65907f3ecf62609db +depends: +- libexpat >=2.7.4,<3.0a0 +- libfreetype >=2.14.1 +- libfreetype6 >=2.14.1 +- libgcc >=14 +- libuuid >=2.41.3,<3.0a0 +- libzlib >=1.3.1,<2.0a0 +license: MIT +license_family: MIT +size: 279044 +timestamp: 1771382728182 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda +sha256: 54eea8469786bc2291cc40bca5f46438d3e062a399e8f53f013b6a9f50e98333 +md5: a7970cd949a077b7cb9696379d338681 +depends: +- font-ttf-ubuntu +- font-ttf-inconsolata +- font-ttf-dejavu-sans-mono +- font-ttf-source-code-pro +license: BSD-3-Clause +license_family: BSD +size: 4059 +timestamp: 1762351264405 +- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda +sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 +md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 +depends: +- python >=3.10 +- hyperframe >=6.1,<7 +- hpack >=4.1,<5 +- python +license: MIT +license_family: MIT +size: 95967 +timestamp: 1756364871835 +- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda +sha256: 6ad78a180576c706aabeb5b4c8ceb97c0cb25f1e112d76495bff23e3779948ba +md5: 0a802cb9888dd14eeefc611f05c40b6e +depends: +- python >=3.9 +license: MIT +license_family: MIT +size: 30731 +timestamp: 1737618390337 +- conda: https://conda.anaconda.org/conda-forge/noarch/humanfriendly-10.0-pyh707e725_8.conda +sha256: fa2071da7fab758c669e78227e6094f6b3608228740808a6de5d6bce83d9e52d +md5: 7fe569c10905402ed47024fc481bb371 +depends: +- __unix +- python >=3.9 +license: MIT +license_family: MIT +size: 73563 +timestamp: 1733928021866 +- conda: https://conda.anaconda.org/conda-forge/noarch/humanize-4.15.0-pyhd8ed1ab_0.conda +sha256: 6c4343b376d0b12a4c75ab992640970d36c933cad1fd924f6a1181fa91710e80 +md5: daddf757c3ecd6067b9af1df1f25d89e +depends: +- python >=3.10 +license: MIT +license_family: MIT +size: 67994 +timestamp: 1766267728652 +- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda +sha256: 77af6f5fe8b62ca07d09ac60127a30d9069fdc3c68d6b256754d0ffb1f7779f8 +md5: 8e6923fc12f1fe8f8c4e5c9f343256ac +depends: +- python >=3.9 +license: MIT +license_family: MIT +size: 17397 +timestamp: 1737618427549 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/icu-78.3-hcab7f73_0.conda +sha256: 49ba6aed2c6b482bb0ba41078057555d29764299bc947b990708617712ef6406 +md5: 546da38c2fa9efacf203e2ad3f987c59 +depends: +- libgcc >=14 +- libstdcxx >=14 +license: MIT +license_family: MIT +size: 12837286 +timestamp: 1773822650615 +- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda +sha256: ae89d0299ada2a3162c2614a9d26557a92aa6a77120ce142f8e0109bbf0342b0 +md5: 53abe63df7e10a6ba605dc5f9f961d36 +depends: +- python >=3.10 +license: BSD-3-Clause +license_family: BSD +size: 50721 +timestamp: 1760286526795 +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda +sha256: 82ab2a0d91ca1e7e63ab6a4939356667ef683905dea631bc2121aa534d347b16 +md5: 080594bf4493e6bae2607e65390c520a +depends: +- python >=3.10 +- zipp >=3.20 +- python +license: Apache-2.0 +license_family: APACHE +size: 34387 +timestamp: 1773931568510 +- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda +sha256: fc9ca7348a4f25fed2079f2153ecdcf5f9cf2a0bc36c4172420ca09e1849df7b +md5: 04558c96691bed63104678757beb4f8d +depends: +- markupsafe >=2.0 +- python >=3.10 +- python +license: BSD-3-Clause +license_family: BSD +size: 120685 +timestamp: 1764517220861 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda +sha256: db973a37d75db8e19b5f44bbbdaead0c68dde745407f281e2a7fe4db74ec51d7 +md5: ada41c863af263cc4c5fcbaff7c3e4dc +depends: +- attrs >=22.2.0 +- jsonschema-specifications >=2023.3.6 +- python >=3.10 +- referencing >=0.28.4 +- rpds-py >=0.25.0 +- python +license: MIT +license_family: MIT +size: 82356 +timestamp: 1767839954256 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda +sha256: 0a4f3b132f0faca10c89fdf3b60e15abb62ded6fa80aebfc007d05965192aa04 +md5: 439cd0f567d697b20a8f45cb70a1005a +depends: +- python >=3.10 +- referencing >=0.31.0 +- python +license: MIT +license_family: MIT +size: 19236 +timestamp: 1757335715225 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/kaleido-core-0.2.1-he5a581e_0.tar.bz2 +sha256: d3c7f4797566e6f983d16c2a87063a18e4b2d819a66230190a21584d70042755 +md5: 4f0d284f5d11e04277b552eb1c172c7f +depends: +- __glibc >=2.17,<3.0.a0 +- expat >=2.2.10,<3.0.0a0 +- fontconfig +- fonts-conda-forge +- libgcc-ng >=9.3.0 +- mathjax 2.7.* +- nspr >=4.29,<5.0a0 +- nss >=3.62,<4.0a0 +- sqlite >=3.34.0,<4.0a0 +license: MIT +license_family: MIT +size: 65750397 +timestamp: 1615199465742 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lcms2-2.18-h9d5b58d_0.conda +sha256: 379ef5e91a587137391a6149755d0e929f1a007d2dcb211318ac670a46c8596f +md5: bb960f01525b5e001608afef9d47b79c +depends: +- libgcc >=14 +- libjpeg-turbo >=3.1.2,<4.0a0 +- libtiff >=4.7.1,<4.8.0a0 +license: MIT +license_family: MIT +size: 293039 +timestamp: 1768184778398 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ld_impl_linux-aarch64-2.45.1-default_h1979696_102.conda +sha256: 7abd913d81a9bf00abb699e8987966baa2065f5132e37e815f92d90fc6bba530 +md5: a21644fc4a83da26452a718dc9468d5f +depends: +- zstd >=1.5.7,<1.6.0a0 +constrains: +- binutils_impl_linux-aarch64 2.45.1 +license: GPL-3.0-only +license_family: GPL +size: 875596 +timestamp: 1774197520746 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lerc-4.1.0-h52b7260_0.conda +sha256: 8957fd460c1c132c8031f65fd5f56ec3807fd71b7cab2c5e2b0937b13404ab36 +md5: d13423b06447113a90b5b1366d4da171 +depends: +- libgcc >=14 +- libstdcxx >=14 +license: Apache-2.0 +license_family: Apache +size: 240444 +timestamp: 1773114901155 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libblas-3.11.0-5_haddc8a3_openblas.conda +build_number: 5 +sha256: 700f3c03d0fba8e687a345404a45fbabe781c1cf92242382f62cef2948745ec4 +md5: 5afcea37a46f76ec1322943b3c4dfdc0 +depends: +- libopenblas >=0.3.30,<0.3.31.0a0 +- libopenblas >=0.3.30,<1.0a0 +constrains: +- mkl <2026 +- libcblas 3.11.0 5*_openblas +- liblapack 3.11.0 5*_openblas +- liblapacke 3.11.0 5*_openblas +- blas 2.305 openblas +license: BSD-3-Clause +license_family: BSD +size: 18369 +timestamp: 1765818610617 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libcblas-3.11.0-5_hd72aa62_openblas.conda +build_number: 5 +sha256: 3fad5c9de161dccb4e42c8b1ae8eccb33f4ed56bccbcced9cbb0956ae7869e61 +md5: 0b2f1143ae2d0aa4c991959d0daaf256 +depends: +- libblas 3.11.0 5_haddc8a3_openblas +constrains: +- liblapack 3.11.0 5*_openblas +- liblapacke 3.11.0 5*_openblas +- blas 2.305 openblas +license: BSD-3-Clause +license_family: BSD +size: 18371 +timestamp: 1765818618899 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libdeflate-1.25-h1af38f5_0.conda +sha256: 48814b73bd462da6eed2e697e30c060ae16af21e9fbed30d64feaf0aad9da392 +md5: a9138815598fe6b91a1d6782ca657b0c +depends: +- libgcc >=14 +license: MIT +license_family: MIT +size: 71117 +timestamp: 1761979776756 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libexpat-2.7.4-hfae3067_0.conda +sha256: 995ce3ad96d0f4b5ed6296b051a0d7b6377718f325bc0e792fbb96b0e369dad7 +md5: 57f3b3da02a50a1be2a6fe847515417d +depends: +- libgcc >=14 +constrains: +- expat 2.7.4.* +license: MIT +license_family: MIT +size: 76564 +timestamp: 1771259530958 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libffi-3.5.2-h376a255_0.conda +sha256: 3df4c539449aabc3443bbe8c492c01d401eea894603087fca2917aa4e1c2dea9 +md5: 2f364feefb6a7c00423e80dcb12db62a +depends: +- libgcc >=14 +license: MIT +license_family: MIT +size: 55952 +timestamp: 1769456078358 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libfreetype-2.14.3-h8af1aa0_0.conda +sha256: 752e4f66283d7deb4c6fd47d88df644d8daa2aaa825a54f3bf350a625190192a +md5: a229e22d4d8814a07702b0919d8e6701 +depends: +- libfreetype6 >=2.14.3 +license: GPL-2.0-only OR FTL +size: 8125 +timestamp: 1774301094057 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libfreetype6-2.14.3-hdae7a39_0.conda +sha256: 8e6b27fe4eec4c2fa7b7769a21973734c8dba1de80086fb0213e58375ac09f4c +md5: b99ed99e42dafb27889483b3098cace7 +depends: +- libgcc >=14 +- libpng >=1.6.55,<1.7.0a0 +- libzlib >=1.3.2,<2.0a0 +constrains: +- freetype >=2.14.3 +license: GPL-2.0-only OR FTL +size: 422941 +timestamp: 1774301093473 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-15.2.0-h8acb6b2_18.conda +sha256: 43df385bedc1cab11993c4369e1f3b04b4ca5d0ea16cba6a0e7f18dbc129fcc9 +md5: 552567ea2b61e3a3035759b2fdb3f9a6 +depends: +- _openmp_mutex >=4.5 +constrains: +- libgcc-ng ==15.2.0=*_18 +- libgomp 15.2.0 h8acb6b2_18 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 622900 +timestamp: 1771378128706 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-ng-15.2.0-he9431aa_18.conda +sha256: 83bb0415f59634dccfa8335d4163d1f6db00a27b36666736f9842b650b92cf2f +md5: 4feebd0fbf61075a1a9c2e9b3936c257 +depends: +- libgcc 15.2.0 h8acb6b2_18 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 27568 +timestamp: 1771378136019 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgfortran-15.2.0-he9431aa_18.conda +sha256: 7dcd7dff2505d56fd5272a6e712ec912f50a46bf07dc6873a7e853694304e6e4 +md5: 41f261f5e4e2e8cbd236c2f1f15dae1b +depends: +- libgfortran5 15.2.0 h1b7bec0_18 +constrains: +- libgfortran-ng ==15.2.0=*_18 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 27587 +timestamp: 1771378169244 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgfortran5-15.2.0-h1b7bec0_18.conda +sha256: 85347670dfb4a8d4c13cd7cae54138dcf2b1606b6bede42eef5507bf5f9660c6 +md5: 574d88ce3348331e962cfa5ed451b247 +depends: +- libgcc >=15.2.0 +constrains: +- libgfortran 15.2.0 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 1486341 +timestamp: 1771378148102 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgomp-15.2.0-h8acb6b2_18.conda +sha256: fc716f11a6a8525e27a5d332ef6a689210b0d2a4dd1133edc0f530659aa9faa6 +md5: 4faa39bf919939602e594253bd673958 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 588060 +timestamp: 1771378040807 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libjpeg-turbo-3.1.2-he30d5cf_0.conda +sha256: 84064c7c53a64291a585d7215fe95ec42df74203a5bf7615d33d49a3b0f08bb6 +md5: 5109d7f837a3dfdf5c60f60e311b041f +depends: +- libgcc >=14 +constrains: +- jpeg <0.0.0a +license: IJG AND BSD-3-Clause AND Zlib +size: 691818 +timestamp: 1762094728337 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/liblapack-3.11.0-5_h88aeb00_openblas.conda +build_number: 5 +sha256: 692222d186d3ffbc99eaf04b5b20181fd26aee1edec1106435a0a755c57cce86 +md5: 88d1e4133d1182522b403e9ba7435f04 +depends: +- libblas 3.11.0 5_haddc8a3_openblas +constrains: +- liblapacke 3.11.0 5*_openblas +- blas 2.305 openblas +- libcblas 3.11.0 5*_openblas +license: BSD-3-Clause +license_family: BSD +size: 18392 +timestamp: 1765818627104 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/liblzma-5.8.2-he30d5cf_0.conda +sha256: 843c46e20519651a3e357a8928352b16c5b94f4cd3d5481acc48be2e93e8f6a3 +md5: 96944e3c92386a12755b94619bae0b35 +depends: +- libgcc >=14 +constrains: +- xz 5.8.2.* +license: 0BSD +size: 125916 +timestamp: 1768754941722 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmpdec-4.0.0-he30d5cf_1.conda +sha256: 57c0dd12d506e84541c4e877898bd2a59cca141df493d34036f18b2751e0a453 +md5: 7b9813e885482e3ccb1fa212b86d7fd0 +depends: +- libgcc >=14 +license: BSD-2-Clause +license_family: BSD +size: 114056 +timestamp: 1769482343003 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libopenblas-0.3.30-pthreads_h9d3fd7e_4.conda +sha256: 794a7270ea049ec931537874cd8d2de0ef4b3cef71c055cfd8b4be6d2f4228b0 +md5: 11d7d57b7bdd01da745bbf2b67020b2e +depends: +- libgcc >=14 +- libgfortran +- libgfortran5 >=14.3.0 +constrains: +- openblas >=0.3.30,<0.3.31.0a0 +license: BSD-3-Clause +license_family: BSD +size: 4959359 +timestamp: 1763114173544 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libpng-1.6.55-h1abf092_0.conda +sha256: c7378c6b79de4d571d00ad1caf0a4c19d43c9c94077a761abb6ead44d891f907 +md5: be4088903b94ea297975689b3c3aeb27 +depends: +- libgcc >=14 +- libzlib >=1.3.1,<2.0a0 +license: zlib-acknowledgement +size: 340156 +timestamp: 1770691477245 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libsqlite-3.52.0-h10b116e_0.conda +sha256: 1ddaf91b44fae83856276f4cb7ce544ffe41d4b55c1e346b504c6b45f19098d6 +md5: 77891484f18eca74b8ad83694da9815e +depends: +- icu >=78.2,<79.0a0 +- libgcc >=14 +- libzlib >=1.3.1,<2.0a0 +license: blessing +size: 952296 +timestamp: 1772818881550 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libstdcxx-15.2.0-hef695bb_18.conda +sha256: 31fdb9ffafad106a213192d8319b9f810e05abca9c5436b60e507afb35a6bc40 +md5: f56573d05e3b735cb03efeb64a15f388 +depends: +- libgcc 15.2.0 h8acb6b2_18 +constrains: +- libstdcxx-ng ==15.2.0=*_18 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 5541411 +timestamp: 1771378162499 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libtiff-4.7.1-hdb009f0_1.conda +sha256: 7ff79470db39e803e21b8185bc8f19c460666d5557b1378d1b1e857d929c6b39 +md5: 8c6fd84f9c87ac00636007c6131e457d +depends: +- lerc >=4.0.0,<5.0a0 +- libdeflate >=1.25,<1.26.0a0 +- libgcc >=14 +- libjpeg-turbo >=3.1.0,<4.0a0 +- liblzma >=5.8.1,<6.0a0 +- libstdcxx >=14 +- libwebp-base >=1.6.0,<2.0a0 +- libzlib >=1.3.1,<2.0a0 +- zstd >=1.5.7,<1.6.0a0 +license: HPND +size: 488407 +timestamp: 1762022048105 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libuuid-2.41.3-h1022ec0_0.conda +sha256: c37a8e89b700646f3252608f8368e7eb8e2a44886b92776e57ad7601fc402a11 +md5: cf2861212053d05f27ec49c3784ff8bb +depends: +- libgcc >=14 +license: BSD-3-Clause +license_family: BSD +size: 43453 +timestamp: 1766271546875 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libwebp-base-1.6.0-ha2e29f5_0.conda +sha256: b03700a1f741554e8e5712f9b06dd67e76f5301292958cd3cb1ac8c6fdd9ed25 +md5: 24e92d0942c799db387f5c9d7b81f1af +depends: +- libgcc >=14 +constrains: +- libwebp 1.6.0 +license: BSD-3-Clause +license_family: BSD +size: 359496 +timestamp: 1752160685488 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxcb-1.17.0-h262b8f6_0.conda +sha256: 461cab3d5650ac6db73a367de5c8eca50363966e862dcf60181d693236b1ae7b +md5: cd14ee5cca2464a425b1dbfc24d90db2 +depends: +- libgcc >=13 +- pthread-stubs +- xorg-libxau >=1.0.11,<2.0a0 +- xorg-libxdmcp +license: MIT +license_family: MIT +size: 397493 +timestamp: 1727280745441 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libzlib-1.3.2-hdc9db2a_2.conda +sha256: eb111e32e5a7313a5bf799c7fb2419051fa2fe7eff74769fac8d5a448b309f7f +md5: 502006882cf5461adced436e410046d1 +constrains: +- zlib 1.3.2 *_2 +license: Zlib +license_family: Other +size: 69833 +timestamp: 1774072605429 +- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.10.2-pyhcf101f3_0.conda +sha256: 20e0892592a3e7c683e3d66df704a9425d731486a97c34fc56af4da1106b2b6b +md5: ba0a9221ce1063f31692c07370d062f3 +depends: +- importlib-metadata >=4.4 +- python >=3.10 +- python +license: BSD-3-Clause +license_family: BSD +size: 85893 +timestamp: 1770694658918 +- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda +sha256: 7b1da4b5c40385791dbc3cc85ceea9fad5da680a27d5d3cb8bfaa185e304a89e +md5: 5b5203189eb668f042ac2b0826244964 +depends: +- mdurl >=0.1,<1 +- python >=3.10 +license: MIT +license_family: MIT +size: 64736 +timestamp: 1754951288511 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/markupsafe-3.0.3-py314hb76de3f_1.conda +sha256: 383c188496d13a55658c06e61e7d4cdff2c9f9d5a0648769fca8250bece7e0ef +md5: e5de3c36dd548b35ff2a8aa49208dcb3 +depends: +- libgcc >=14 +- python >=3.14,<3.15.0a0 +- python_abi 3.14.* *_cp314 +constrains: +- jinja2 >=3.0.0 +license: BSD-3-Clause +license_family: BSD +size: 27913 +timestamp: 1772446407659 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/mathjax-2.7.7-h8af1aa0_3.tar.bz2 +sha256: 8fd4c79d6eda3d4cba73783114305a53a154ada4d1e334d4e02cb3521429599b +md5: 7b08314a6867a9d5648a1c3265e9eb8e +license: Apache-2.0 +license_family: Apache +size: 22257008 +timestamp: 1662784555011 +- conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda +sha256: 78c1bbe1723449c52b7a9df1af2ee5f005209f67e40b6e1d3c7619127c43b1c7 +md5: 592132998493b3ff25fd7479396e8351 +depends: +- python >=3.9 +license: MIT +license_family: MIT +size: 14465 +timestamp: 1733255681319 +- conda: https://conda.anaconda.org/bioconda/noarch/multiqc-1.33-pyhdfd78af_0.conda +sha256: f005760b13093362fc9c997d603dd487de32ab2e821a3cbce52a42bcb8136517 +md5: 698a8a27c2b9d8a542c70cb47099a75e +depends: +- click +- coloredlogs +- humanize +- importlib-metadata +- jinja2 >=3.0.0 +- jsonschema +- markdown +- natsort +- numpy +- packaging +- pillow >=10.2.0 +- plotly >=5.18 +- polars-lts-cpu +- pyaml-env +- pydantic >=2.7.1 +- python >=3.8,!=3.14.1 +- python-dotenv +- python-kaleido 0.2.1 +- pyyaml >=4 +- requests +- rich >=10 +- rich-click +- spectra >=0.0.10 +- tiktoken +- tqdm +- typeguard +license: GPL-3.0-or-later +license_family: GPL3 +size: 4198799 +timestamp: 1765300743879 +- conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.18.1-pyhcf101f3_1.conda +sha256: 541fd4390a0687228b8578247f1536a821d9261389a65585af9d1a6f2a14e1e0 +md5: 30bec5e8f4c3969e2b1bd407c5e52afb +depends: +- python >=3.10 +- python +license: MIT +size: 280459 +timestamp: 1774380620329 +- conda: https://conda.anaconda.org/conda-forge/noarch/natsort-8.4.0-pyhcf101f3_2.conda +sha256: aeb1548eb72e4f198e72f19d242fb695b35add2ac7b2c00e0d83687052867680 +md5: e941e85e273121222580723010bd4fa2 +depends: +- python >=3.9 +- python +license: MIT +license_family: MIT +size: 39262 +timestamp: 1770905275632 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ncurses-6.5-ha32ae93_3.conda +sha256: 91cfb655a68b0353b2833521dc919188db3d8a7f4c64bea2c6a7557b24747468 +md5: 182afabe009dc78d8b73100255ee6868 +depends: +- libgcc >=13 +license: X11 AND BSD-3-Clause +size: 926034 +timestamp: 1738196018799 +- conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda +sha256: f6a82172afc50e54741f6f84527ef10424326611503c64e359e25a19a8e4c1c6 +md5: a2c1eeadae7a309daed9d62c96012a2b +depends: +- python >=3.11 +- python +constrains: +- numpy >=1.25 +- scipy >=1.11.2 +- matplotlib-base >=3.8 +- pandas >=2.0 +license: BSD-3-Clause +license_family: BSD +size: 1587439 +timestamp: 1765215107045 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/nspr-4.38-h3ad9384_0.conda +sha256: 78a06e89285fef242e272998b292c1e621e3ee3dd4fba62ec014e503c7ec118f +md5: 6dd4f07147774bf720075a210f8026b9 +depends: +- libgcc >=14 +- libstdcxx >=14 +license: MPL-2.0 +license_family: MOZILLA +size: 235140 +timestamp: 1762350120355 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/nss-3.118-h544fa81_0.conda +sha256: 48942696889367ffd448f8dccfc080fb7e130b9938a4a3b6b20ef8e6af856463 +md5: 4540f9570d12db2150f42ba036154552 +depends: +- libgcc >=14 +- libsqlite >=3.51.0,<4.0a0 +- libstdcxx >=14 +- libzlib >=1.3.1,<2.0a0 +- nspr >=4.38,<5.0a0 +license: MPL-2.0 +license_family: MOZILLA +size: 2061869 +timestamp: 1763490303490 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/numpy-2.4.3-py314haac167e_0.conda +sha256: a6d42fd88afc57c3b0a57b21a12eff7492dfc419bb61ee3f74e9ba6261dabc88 +md5: 25d896c331481145720a21e5145fad65 +depends: +- python +- libgcc >=14 +- python 3.14.* *_cp314 +- libstdcxx >=14 +- libcblas >=3.9.0,<4.0a0 +- liblapack >=3.9.0,<4.0a0 +- python_abi 3.14.* *_cp314 +- libblas >=3.9.0,<4.0a0 +constrains: +- numpy-base <0a0 +license: BSD-3-Clause +license_family: BSD +size: 8008045 +timestamp: 1773839355275 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/openjpeg-2.5.4-h5da879a_0.conda +sha256: bd1bc8bdde5e6c5cbac42d462b939694e40b59be6d0698f668515908640c77b8 +md5: cea962410e327262346d48d01f05936c +depends: +- libgcc >=14 +- libpng >=1.6.50,<1.7.0a0 +- libstdcxx >=14 +- libtiff >=4.7.1,<4.8.0a0 +- libzlib >=1.3.1,<2.0a0 +license: BSD-2-Clause +license_family: BSD +size: 392636 +timestamp: 1758489353577 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/openssl-3.6.1-h546c87b_1.conda +sha256: 7f8048c0e75b2620254218d72b4ae7f14136f1981c5eb555ef61645a9344505f +md5: 25f5885f11e8b1f075bccf4a2da91c60 +depends: +- ca-certificates +- libgcc >=14 +license: Apache-2.0 +license_family: Apache +size: 3692030 +timestamp: 1769557678657 +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda +sha256: c1fc0f953048f743385d31c468b4a678b3ad20caffdeaa94bed85ba63049fd58 +md5: b76541e68fea4d511b1ac46a28dcd2c6 +depends: +- python >=3.8 +- python +license: Apache-2.0 +license_family: APACHE +size: 72010 +timestamp: 1769093650580 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pillow-12.1.1-py314hac3e5ec_0.conda +sha256: 1ca2d1616baad9bccb7ebc425ef2dcd6cebe742fbe91edf226fb606ad371ca0f +md5: d3c959c7efe560b2d7da459d69121fe9 +depends: +- python +- python 3.14.* *_cp314 +- libgcc >=14 +- zlib-ng >=2.3.3,<2.4.0a0 +- libwebp-base >=1.6.0,<2.0a0 +- tk >=8.6.13,<8.7.0a0 +- libfreetype >=2.14.1 +- libfreetype6 >=2.14.1 +- libtiff >=4.7.1,<4.8.0a0 +- lcms2 >=2.18,<3.0a0 +- python_abi 3.14.* *_cp314 +- openjpeg >=2.5.4,<3.0a0 +- libjpeg-turbo >=3.1.2,<4.0a0 +- libxcb >=1.17.0,<2.0a0 +license: HPND +size: 1051828 +timestamp: 1770794010335 +- conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda +sha256: c418d325359fc7a0074cea7f081ef1bce26e114d2da8a0154c5d27ecc87a08e7 +md5: 3e9427ee186846052e81fadde8ebe96a +depends: +- narwhals >=1.15.1 +- packaging +- python >=3.10 +constrains: +- ipywidgets >=7.6 +license: MIT +license_family: MIT +size: 5251872 +timestamp: 1772628857717 +- conda: https://conda.anaconda.org/conda-forge/noarch/polars-1.39.3-pyh58ad624_1.conda +sha256: d332c2d5002fc440ae37ed9679ffc21b552f18d20232390005d1dd3bce0888d3 +md5: d5a4e013a30dd8dfde9ab39f45aaf9c1 +depends: +- polars-runtime-32 ==1.39.3 +- python >=3.10 +- python +constrains: +- numpy >=1.16.0 +- pyarrow >=7.0.0 +- fastexcel >=0.9 +- openpyxl >=3.0.0 +- xlsx2csv >=0.8.0 +- connectorx >=0.3.2 +- deltalake >=1.0.0 +- pyiceberg >=0.7.1 +- altair >=5.4.0 +- great_tables >=0.8.0 +- polars-runtime-32 ==1.39.3 +- polars-runtime-64 ==1.39.3 +- polars-runtime-compat ==1.39.3 +license: MIT +license_family: MIT +size: 533495 +timestamp: 1774207987966 +- conda: https://conda.anaconda.org/conda-forge/noarch/polars-lts-cpu-1.34.0.deprecated-hc364b38_0.conda +sha256: e466fb31f67ba9bde18deafeb34263ca5eb25807f39ead0e9d753a8e82c4c4f4 +md5: ef0340e75068ac8ff96462749b5c98e7 +depends: +- polars >=1.34.0 +- polars-runtime-compat >=1.34.0 +license: MIT +license_family: MIT +size: 3902 +timestamp: 1760206808444 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/polars-runtime-32-1.39.3-py310hff09b76_1.conda +noarch: python +sha256: c070be507c5a90df397a47ae0299660be437d5546d68f1bc0fa4402c9f07d59e +md5: 3c1a7c6b4ba8b9fb773ace9723f8a5db +depends: +- python +- libgcc >=14 +- libstdcxx >=14 +- _python_abi3_support 1.* +- cpython >=3.10 +constrains: +- __glibc >=2.17 +license: MIT +license_family: MIT +size: 34785466 +timestamp: 1774207998285 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/polars-runtime-compat-1.39.3-py310hf00a4a2_1.conda +noarch: python +sha256: 683315f1a49e47ce72bf9462419733b40b588b2b3106552d95fd4cd994e174de +md5: dd3464e2132dc3a783e76e5078870c76 +depends: +- python +- libgcc >=14 +- libstdcxx >=14 +- _python_abi3_support 1.* +- cpython >=3.10 +constrains: +- __glibc >=2.17 +license: MIT +license_family: MIT +size: 34652491 +timestamp: 1774207996879 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/procps-ng-4.0.6-h1779866_0.conda +sha256: e9cbcbc94e151ada3d6dc365380aaaf591f65012c16d9a2abaea4b9b90adc402 +md5: ab7288cc39545556d1bc5e71ab2df9a9 +depends: +- libgcc >=14 +- ncurses >=6.5,<7.0a0 +license: GPL-2.0-or-later AND LGPL-2.0-or-later +license_family: GPL +size: 636733 +timestamp: 1769712412683 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pthread-stubs-0.4-h86ecc28_1002.conda +sha256: 977dfb0cb3935d748521dd80262fe7169ab82920afd38ed14b7fee2ea5ec01ba +md5: bb5a90c93e3bac3d5690acf76b4a6386 +depends: +- libgcc >=13 +license: MIT +license_family: MIT +size: 8342 +timestamp: 1726803319942 +- conda: https://conda.anaconda.org/conda-forge/noarch/pyaml-env-1.2.2-pyhd8ed1ab_0.conda +sha256: 58994e0d2ea8584cb399546e6f6896d771995e6121d1a7b6a2c9948388358932 +md5: e17be1016bcc3516827b836cd3e4d9dc +depends: +- python >=3.9 +- pyyaml >=5.0,<=7.0 +license: MIT +license_family: MIT +size: 14645 +timestamp: 1736766960536 +- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.12.5-pyhcf101f3_1.conda +sha256: 868569d9505b7fe246c880c11e2c44924d7613a8cdcc1f6ef85d5375e892f13d +md5: c3946ed24acdb28db1b5d63321dbca7d +depends: +- typing-inspection >=0.4.2 +- typing_extensions >=4.14.1 +- python >=3.10 +- typing-extensions >=4.6.1 +- annotated-types >=0.6.0 +- pydantic-core ==2.41.5 +- python +license: MIT +license_family: MIT +size: 340482 +timestamp: 1764434463101 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pydantic-core-2.41.5-py314h451b6cc_1.conda +sha256: f8acb2d03ebe80fed0032b9a989fc9acfb6735e3cd3f8c704b72728cb31868f6 +md5: 28f5027a1e04d67aa13fac1c5ba79693 +depends: +- python +- typing-extensions >=4.6.0,!=4.7.0 +- libgcc >=14 +- python 3.14.* *_cp314 +- python_abi 3.14.* *_cp314 +constrains: +- __glibc >=2.17 +license: MIT +license_family: MIT +size: 1828339 +timestamp: 1762989038561 +- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda +sha256: 5577623b9f6685ece2697c6eb7511b4c9ac5fb607c9babc2646c811b428fd46a +md5: 6b6ece66ebcae2d5f326c77ef2c5a066 +depends: +- python >=3.9 +license: BSD-2-Clause +license_family: BSD +size: 889287 +timestamp: 1750615908735 +- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda +sha256: ba3b032fa52709ce0d9fd388f63d330a026754587a2f461117cac9ab73d8d0d8 +md5: 461219d1a5bd61342293efa2c0c90eac +depends: +- __unix +- python >=3.9 +license: BSD-3-Clause +license_family: BSD +size: 21085 +timestamp: 1733217331982 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/python-3.14.3-hb06a95a_101_cp314.conda +build_number: 101 +sha256: 87e9dff5646aba87cecfbc08789634c855871a7325169299d749040b0923a356 +md5: 205011b36899ff0edf41b3db0eda5a44 +depends: +- bzip2 >=1.0.8,<2.0a0 +- ld_impl_linux-aarch64 >=2.36.1 +- libexpat >=2.7.3,<3.0a0 +- libffi >=3.5.2,<3.6.0a0 +- libgcc >=14 +- liblzma >=5.8.2,<6.0a0 +- libmpdec >=4.0.0,<5.0a0 +- libsqlite >=3.51.2,<4.0a0 +- libuuid >=2.41.3,<3.0a0 +- libzlib >=1.3.1,<2.0a0 +- ncurses >=6.5,<7.0a0 +- openssl >=3.5.5,<4.0a0 +- python_abi 3.14.* *_cp314 +- readline >=8.3,<9.0a0 +- tk >=8.6.13,<8.7.0a0 +- tzdata +- zstd >=1.5.7,<1.6.0a0 +license: Python-2.0 +size: 37305578 +timestamp: 1770674395875 +python_site_packages_path: lib/python3.14/site-packages +- conda: https://conda.anaconda.org/conda-forge/noarch/python-dotenv-1.2.2-pyhcf101f3_0.conda +sha256: 74e417a768f59f02a242c25e7db0aa796627b5bc8c818863b57786072aeb85e5 +md5: 130584ad9f3a513cdd71b1fdc1244e9c +depends: +- python >=3.10 +license: BSD-3-Clause +license_family: BSD +size: 27848 +timestamp: 1772388605021 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.3-h4df99d1_101.conda +sha256: 233aebd94c704ac112afefbb29cf4170b7bc606e22958906f2672081bc50638a +md5: 235765e4ea0d0301c75965985163b5a1 +depends: +- cpython 3.14.3.* +- python_abi * *_cp314 +license: Python-2.0 +size: 50062 +timestamp: 1770674497152 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-kaleido-0.2.1-pyhd8ed1ab_0.tar.bz2 +sha256: e17bf63a30aec33432f1ead86e15e9febde9fc40a7f869c0e766be8d2db44170 +md5: 310259a5b03ff02289d7705f39e2b1d2 +depends: +- kaleido-core 0.2.1.* +- python >=3.5 +license: MIT +license_family: MIT +size: 18320 +timestamp: 1615204747600 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda +build_number: 8 +sha256: ad6d2e9ac39751cc0529dd1566a26751a0bf2542adb0c232533d32e176e21db5 +md5: 0539938c55b6b1a59b560e843ad864a4 +constrains: +- python 3.14.* *_cp314 +license: BSD-3-Clause +license_family: BSD +size: 6989 +timestamp: 1752805904792 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pyyaml-6.0.3-py314h807365f_1.conda +sha256: 496b5e65dfdd0aaaaa5de0dcaaf3bceea00fcb4398acf152f89e567c82ec1046 +md5: 9ae2c92975118058bd720e9ba2bb7c58 +depends: +- libgcc >=14 +- python >=3.14,<3.15.0a0 +- python >=3.14,<3.15.0a0 *_cp314 +- python_abi 3.14.* *_cp314 +- yaml >=0.2.5,<0.3.0a0 +license: MIT +license_family: MIT +size: 195678 +timestamp: 1770223441816 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/readline-8.3-hb682ff5_0.conda +sha256: fe695f9d215e9a2e3dd0ca7f56435ab4df24f5504b83865e3d295df36e88d216 +md5: 3d49cad61f829f4f0e0611547a9cda12 +depends: +- libgcc >=14 +- ncurses >=6.5,<7.0a0 +license: GPL-3.0-only +license_family: GPL +size: 357597 +timestamp: 1765815673644 +- conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda +sha256: 0577eedfb347ff94d0f2fa6c052c502989b028216996b45c7f21236f25864414 +md5: 870293df500ca7e18bedefa5838a22ab +depends: +- attrs >=22.2.0 +- python >=3.10 +- rpds-py >=0.7.0 +- typing_extensions >=4.4.0 +- python +license: MIT +license_family: MIT +size: 51788 +timestamp: 1760379115194 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/regex-2026.2.28-py314h51f160d_0.conda +sha256: 2080ecea825e1ef91a2422cc0bc63e85db9e38908ed17657fb8f41de7a6eee71 +md5: 818aa2c9f6b3c808da5e7be22a9a424c +depends: +- libgcc >=14 +- python >=3.14,<3.15.0a0 +- python >=3.14,<3.15.0a0 *_cp314 +- python_abi 3.14.* *_cp314 +license: Apache-2.0 AND CNRI-Python +license_family: PSF +size: 408097 +timestamp: 1772255205521 +- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda +sha256: 7813c38b79ae549504b2c57b3f33394cea4f2ad083f0994d2045c2e24cb538c5 +md5: c65df89a0b2e321045a9e01d1337b182 +depends: +- python >=3.10 +- certifi >=2017.4.17 +- charset-normalizer >=2,<4 +- idna >=2.5,<4 +- urllib3 >=1.21.1,<3 +- python +constrains: +- chardet >=3.0.2,<6 +license: Apache-2.0 +license_family: APACHE +size: 63602 +timestamp: 1766926974520 +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.3.3-pyhcf101f3_0.conda +sha256: b06ce84d6a10c266811a7d3adbfa1c11f13393b91cc6f8a5b468277d90be9590 +md5: 7a6289c50631d620652f5045a63eb573 +depends: +- markdown-it-py >=2.2.0 +- pygments >=2.13.0,<3.0.0 +- python >=3.10 +- typing_extensions >=4.0.0,<5.0.0 +- python +license: MIT +license_family: MIT +size: 208472 +timestamp: 1771572730357 +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.9.7-pyh8f84b5b_0.conda +sha256: aa3fcb167321bae51998de2e94d199109c9024f25a5a063cb1c28d8f1af33436 +md5: 0c20a8ebcddb24a45da89d5e917e6cb9 +depends: +- python >=3.10 +- rich >=12 +- click >=8 +- typing-extensions >=4 +- __unix +- python +license: MIT +license_family: MIT +size: 64356 +timestamp: 1769850479089 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/rpds-py-0.30.0-py314h02b7a91_0.conda +sha256: a587240f16eac7c6a80f9585cef679cd1cb9a287b8dfcdd36dcef1f7e7db15dc +md5: e7f6ed9e60043bb5cbcc527764897f0d +depends: +- python +- libgcc >=14 +- python_abi 3.14.* *_cp314 +constrains: +- __glibc >=2.17 +license: MIT +license_family: MIT +size: 376332 +timestamp: 1764543345455 +- conda: https://conda.anaconda.org/conda-forge/noarch/spectra-0.0.11-pyhd8ed1ab_2.conda +sha256: 7c65782d2511738e62c70462e89d65da4fa54d5a7e47c46667bcd27a59f81876 +md5: 472239e4eb7b5a84bb96b3ed7e3a596a +depends: +- colormath >=3.0.0 +- python >=3.9 +license: MIT +license_family: MIT +size: 22284 +timestamp: 1735770589188 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/sqlite-3.52.0-hf1c7be2_0.conda +sha256: 4f8523f5341f0d9e1547085206c6c1f71f9fc7c277443ca363a8cf98add8fc01 +md5: d9634079df93a65ee045b3c75f35cae1 +depends: +- icu >=78.2,<79.0a0 +- libgcc >=14 +- libsqlite 3.52.0 h10b116e_0 +- libzlib >=1.3.1,<2.0a0 +- ncurses >=6.5,<7.0a0 +- readline >=8.3,<9.0a0 +license: blessing +size: 209416 +timestamp: 1772818891689 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/tiktoken-0.12.0-py314h6a36e60_3.conda +sha256: c1da41c79262b27efa168407cfecc47b20270e5fc071a8307f95a2c85fb94170 +md5: 55bf7b559202236157b14323b40f19e6 +depends: +- libgcc >=14 +- libstdcxx >=14 +- python >=3.14,<3.15.0a0 +- python_abi 3.14.* *_cp314 +- regex >=2022.1.18 +- requests >=2.26.0 +constrains: +- __glibc >=2.17 +license: MIT +license_family: MIT +size: 914402 +timestamp: 1764030357702 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/tk-8.6.13-noxft_h0dc03b3_103.conda +sha256: e25c314b52764219f842b41aea2c98a059f06437392268f09b03561e4f6e5309 +md5: 7fc6affb9b01e567d2ef1d05b84aa6ed +depends: +- libgcc >=14 +- libzlib >=1.3.1,<2.0a0 +constrains: +- xorg-libx11 >=1.8.12,<2.0a0 +license: TCL +license_family: BSD +size: 3368666 +timestamp: 1769464148928 +- conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda +sha256: 9ef8e47cf00e4d6dcc114eb32a1504cc18206300572ef14d76634ba29dfe1eb6 +md5: e5ce43272193b38c2e9037446c1d9206 +depends: +- python >=3.10 +- __unix +- python +license: MPL-2.0 and MIT +size: 94132 +timestamp: 1770153424136 +- conda: https://conda.anaconda.org/conda-forge/noarch/typeguard-4.5.1-pyhd8ed1ab_0.conda +sha256: 39d8ae33c43cdb8f771373e149b0b4fae5a08960ac58dcca95b2f1642bb17448 +md5: 260af1b0a94f719de76b4e14094e9a3b +depends: +- importlib-metadata >=3.6 +- python >=3.10 +- typing-extensions >=4.10.0 +- typing_extensions >=4.14.0 +constrains: +- pytest >=7 +license: MIT +license_family: MIT +size: 36838 +timestamp: 1771532971545 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda +sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c +md5: edd329d7d3a4ab45dcf905899a7a6115 +depends: +- typing_extensions ==4.15.0 pyhcf101f3_0 +license: PSF-2.0 +license_family: PSF +size: 91383 +timestamp: 1756220668932 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_1.conda +sha256: 70db27de58a97aeb7ba7448366c9853f91b21137492e0b4430251a1870aa8ff4 +md5: a0a4a3035667fc34f29bfbd5c190baa6 +depends: +- python >=3.10 +- typing_extensions >=4.12.0 +license: MIT +license_family: MIT +size: 18923 +timestamp: 1764158430324 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda +sha256: 032271135bca55aeb156cee361c81350c6f3fb203f57d024d7e5a1fc9ef18731 +md5: 0caa1af407ecff61170c9437a808404d +depends: +- python >=3.10 +- python +license: PSF-2.0 +license_family: PSF +size: 51692 +timestamp: 1756220668932 +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda +sha256: 1d30098909076af33a35017eed6f2953af1c769e273a0626a04722ac4acaba3c +md5: ad659d0a2b3e47e38d829aa8cad2d610 +license: LicenseRef-Public-Domain +size: 119135 +timestamp: 1767016325805 +- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda +sha256: af641ca7ab0c64525a96fd9ad3081b0f5bcf5d1cbb091afb3f6ed5a9eee6111a +md5: 9272daa869e03efe68833e3dc7a02130 +depends: +- backports.zstd >=1.0.0 +- brotli-python >=1.2.0 +- h2 >=4,<5 +- pysocks >=1.5.6,<2.0,!=1.5.7 +- python >=3.10 +license: MIT +license_family: MIT +size: 103172 +timestamp: 1767817860341 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxau-1.0.12-he30d5cf_1.conda +sha256: e9f6e931feeb2f40e1fdbafe41d3b665f1ab6cb39c5880a1fcf9f79a3f3c84a5 +md5: 1c246e1105000c3660558459e2fd6d43 +depends: +- libgcc >=14 +license: MIT +license_family: MIT +size: 16317 +timestamp: 1762977521691 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxdmcp-1.1.5-he30d5cf_1.conda +sha256: 128d72f36bcc8d2b4cdbec07507542e437c7d67f677b7d77b71ed9eeac7d6df1 +md5: bff06dcde4a707339d66d45d96ceb2e2 +depends: +- libgcc >=14 +license: MIT +license_family: MIT +size: 21039 +timestamp: 1762979038025 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/yaml-0.2.5-h80f16a2_3.conda +sha256: 66265e943f32ce02396ad214e27cb35f5b0490b3bd4f064446390f9d67fa5d88 +md5: 032d8030e4a24fe1f72c74423a46fb88 +depends: +- libgcc >=14 +license: MIT +license_family: MIT +size: 88088 +timestamp: 1753484092643 +- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda +sha256: b4533f7d9efc976511a73ef7d4a2473406d7f4c750884be8e8620b0ce70f4dae +md5: 30cd29cb87d819caead4d55184c1d115 +depends: +- python >=3.10 +- python +license: MIT +license_family: MIT +size: 24194 +timestamp: 1764460141901 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zlib-ng-2.3.3-ha7cb516_1.conda +sha256: 638a3a41a4fbfed52d3c60c8ef5a3693b3f12a5b1a3f58fa29f5698d0a0702e2 +md5: f731af71c723065d91b4c01bb822641b +depends: +- libgcc >=14 +- libstdcxx >=14 +license: Zlib +license_family: Other +size: 121046 +timestamp: 1770167944449 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.7-h85ac4a6_6.conda +sha256: 569990cf12e46f9df540275146da567d9c618c1e9c7a0bc9d9cfefadaed20b75 +md5: c3655f82dcea2aa179b291e7099c1fcc +depends: +- libzlib >=1.3.1,<2.0a0 +license: BSD-3-Clause +license_family: BSD +size: 614429 +timestamp: 1764777145593 diff --git a/modules/nf-core/multiqc/.conda-lock/linux_arm64-bd-d167b8012595a136_1.txt b/modules/nf-core/multiqc/.conda-lock/linux_arm64-bd-d167b8012595a136_1.txt new file mode 100644 index 00000000..f787dbe1 --- /dev/null +++ b/modules/nf-core/multiqc/.conda-lock/linux_arm64-bd-d167b8012595a136_1.txt @@ -0,0 +1,125 @@ + +# This file may be used to create an environment using: +# $ conda create --name --file +# platform: linux-aarch64 +@EXPLICIT +https://conda.anaconda.org/conda-forge/linux-aarch64/libgomp-15.2.0-h8acb6b2_18.conda#4faa39bf919939602e594253bd673958 +https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-20_gnu.conda#468fd3bb9e1f671d36c2cbc677e56f1d +https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-15.2.0-h8acb6b2_18.conda#552567ea2b61e3a3035759b2fdb3f9a6 +https://conda.anaconda.org/conda-forge/linux-aarch64/bzip2-1.0.8-h4777abc_9.conda#840d8fc0d7b3209be93080bc20e07f2d +https://conda.anaconda.org/conda-forge/linux-aarch64/libzlib-1.3.2-hdc9db2a_2.conda#502006882cf5461adced436e410046d1 +https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.7-h85ac4a6_6.conda#c3655f82dcea2aa179b291e7099c1fcc +https://conda.anaconda.org/conda-forge/linux-aarch64/ld_impl_linux-aarch64-2.45.1-default_h1979696_102.conda#a21644fc4a83da26452a718dc9468d5f +https://conda.anaconda.org/conda-forge/linux-aarch64/libexpat-2.7.5-hfae3067_0.conda#05d1e0b30acd816a192c03dc6e164f4d +https://conda.anaconda.org/conda-forge/linux-aarch64/libffi-3.5.2-h376a255_0.conda#2f364feefb6a7c00423e80dcb12db62a +https://conda.anaconda.org/conda-forge/linux-aarch64/liblzma-5.8.3-he30d5cf_0.conda#76298a9e6d71ee6e832a8d0d7373b261 +https://conda.anaconda.org/conda-forge/linux-aarch64/libmpdec-4.0.0-he30d5cf_1.conda#7b9813e885482e3ccb1fa212b86d7fd0 +https://conda.anaconda.org/conda-forge/linux-aarch64/libsqlite-3.53.0-h022381a_0.conda#86db4036fd08bf34e991bf48a8af405d +https://conda.anaconda.org/conda-forge/linux-aarch64/libuuid-2.42-h1022ec0_0.conda#a0b5de740d01c390bdbb46d7503c9fab +https://conda.anaconda.org/conda-forge/linux-aarch64/ncurses-6.5-ha32ae93_3.conda#182afabe009dc78d8b73100255ee6868 +https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.4.22-hbd8a1cb_0.conda#e18ad67cf881dcadee8b8d9e2f8e5f73 +https://conda.anaconda.org/conda-forge/linux-aarch64/openssl-3.6.2-h546c87b_0.conda#3b129669089e4d6a5c6871dbb4669b99 +https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda#0539938c55b6b1a59b560e843ad864a4 +https://conda.anaconda.org/conda-forge/linux-aarch64/readline-8.3-hb682ff5_0.conda#3d49cad61f829f4f0e0611547a9cda12 +https://conda.anaconda.org/conda-forge/linux-aarch64/tk-8.6.13-noxft_h0dc03b3_103.conda#7fc6affb9b01e567d2ef1d05b84aa6ed +https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda#ad659d0a2b3e47e38d829aa8cad2d610 +https://conda.anaconda.org/conda-forge/linux-aarch64/python-3.14.4-hfd9ac0a_100_cp314.conda#3cfbe780f0f51cc8cba41db9f8a28bfe +https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.4-py314hd8ed1ab_100.conda#f111d4cfaf1fe9496f386bc98ae94452 +https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.4-h4df99d1_100.conda#e4e60721757979d01d3964122f674959 +https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda#aaa2a381ccc56eac91d63b6c1240312f +https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda#0caa1af407ecff61170c9437a808404d +https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda#edd329d7d3a4ab45dcf905899a7a6115 +https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda#2934f256a8acfe48f6ebb4fce6cde29c +https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda#c6b0543676ecb1fb2d7643941fe375f2 +https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda#a2ac7763a9ac75055b68f325d3255265 +https://conda.anaconda.org/conda-forge/linux-aarch64/libstdcxx-15.2.0-hef695bb_18.conda#f56573d05e3b735cb03efeb64a15f388 +https://conda.anaconda.org/conda-forge/linux-aarch64/brotli-python-1.2.0-py314h352cb57_1.conda#a1b5c571a0923a205d663d8678df4792 +https://conda.anaconda.org/conda-forge/noarch/certifi-2026.4.22-pyhd8ed1ab_0.conda#929471569c93acefb30282a22060dcd5 +https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda#a9167b9571f3baa9d448faa2139d1089 +https://conda.anaconda.org/conda-forge/noarch/click-8.3.2-pyhc90fa1f_0.conda#4d18bc3af7cfcea97bd817164672a08c +https://conda.anaconda.org/conda-forge/noarch/humanfriendly-10.0-pyh707e725_8.conda#7fe569c10905402ed47024fc481bb371 +https://conda.anaconda.org/conda-forge/noarch/coloredlogs-15.0.1-pyhd8ed1ab_4.conda#b866ff7007b934d564961066c8195983 +https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda#a2c1eeadae7a309daed9d62c96012a2b +https://conda.anaconda.org/conda-forge/linux-aarch64/libgfortran5-15.2.0-h1b7bec0_18.conda#574d88ce3348331e962cfa5ed451b247 +https://conda.anaconda.org/conda-forge/linux-aarch64/libgfortran-15.2.0-he9431aa_18.conda#41f261f5e4e2e8cbd236c2f1f15dae1b +https://conda.anaconda.org/conda-forge/linux-aarch64/libopenblas-0.3.32-pthreads_h9d3fd7e_0.conda#5d2ce5cf40443d055ec6d33840192265 +https://conda.anaconda.org/conda-forge/linux-aarch64/libblas-3.11.0-6_haddc8a3_openblas.conda#652bb20bb4618cacd11e17ae070f47ce +https://conda.anaconda.org/conda-forge/linux-aarch64/libcblas-3.11.0-6_hd72aa62_openblas.conda#939e300b110db241a96a1bed438c315b +https://conda.anaconda.org/conda-forge/linux-aarch64/liblapack-3.11.0-6_h88aeb00_openblas.conda#e23a27b52fb320687239e2c5ae4d7540 +https://conda.anaconda.org/conda-forge/linux-aarch64/numpy-2.4.3-py314haac167e_0.conda#25d896c331481145720a21e5145fad65 +https://conda.anaconda.org/conda-forge/noarch/colormath-3.0.0-pyhd8ed1ab_4.conda#071cf7b0ce333c81718b054066c15102 +https://conda.anaconda.org/conda-forge/linux-aarch64/expat-2.7.5-hfae3067_0.conda#d2bb0c889d94f2fdc5856392c3002976 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb +https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda#49023d73832ef61042f6a237cb2687e7 +https://conda.anaconda.org/conda-forge/linux-aarch64/libpng-1.6.58-h1abf092_0.conda#f51503ac45a4888bce71af9027a2ecc9 +https://conda.anaconda.org/conda-forge/linux-aarch64/libfreetype6-2.14.3-hdae7a39_0.conda#b99ed99e42dafb27889483b3098cace7 +https://conda.anaconda.org/conda-forge/linux-aarch64/libfreetype-2.14.3-h8af1aa0_0.conda#a229e22d4d8814a07702b0919d8e6701 +https://conda.anaconda.org/conda-forge/linux-aarch64/fontconfig-2.17.1-hba86a56_0.conda#0fed1ff55f4938a65907f3ecf62609db +https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda#a7970cd949a077b7cb9696379d338681 +https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda#0a802cb9888dd14eeefc611f05c40b6e +https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda#8e6923fc12f1fe8f8c4e5c9f343256ac +https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda#164fc43f0b53b6e3a7bc7dce5e4f1dc9 +https://conda.anaconda.org/conda-forge/noarch/humanize-4.15.0-pyhd8ed1ab_0.conda#daddf757c3ecd6067b9af1df1f25d89e +https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda#fb7130c190f9b4ec91219840a05ba3ac +https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda#e1c36c6121a7c9c76f2f148f1e83b983 +https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda#080594bf4493e6bae2607e65390c520a +https://conda.anaconda.org/conda-forge/linux-aarch64/markupsafe-3.0.3-py314hb76de3f_1.conda#e5de3c36dd548b35ff2a8aa49208dcb3 +https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda#04558c96691bed63104678757beb4f8d +https://conda.anaconda.org/conda-forge/linux-aarch64/rpds-py-0.30.0-py314h02b7a91_0.conda#e7f6ed9e60043bb5cbcc527764897f0d +https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda#870293df500ca7e18bedefa5838a22ab +https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda#439cd0f567d697b20a8f45cb70a1005a +https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda#ada41c863af263cc4c5fcbaff7c3e4dc +https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-ng-15.2.0-he9431aa_18.conda#4feebd0fbf61075a1a9c2e9b3936c257 +https://conda.anaconda.org/conda-forge/linux-aarch64/mathjax-2.7.7-h8af1aa0_3.tar.bz2#7b08314a6867a9d5648a1c3265e9eb8e +https://conda.anaconda.org/conda-forge/linux-aarch64/nspr-4.38-h3ad9384_0.conda#6dd4f07147774bf720075a210f8026b9 +https://conda.anaconda.org/conda-forge/linux-aarch64/nss-3.118-h544fa81_0.conda#4540f9570d12db2150f42ba036154552 +https://conda.anaconda.org/conda-forge/linux-aarch64/sqlite-3.53.0-he8854b5_0.conda#ad8164bdeece883b825c50639c0c4725 +https://conda.anaconda.org/conda-forge/linux-aarch64/kaleido-core-0.2.1-he5a581e_0.tar.bz2#4f0d284f5d11e04277b552eb1c172c7f +https://conda.anaconda.org/conda-forge/linux-aarch64/libjpeg-turbo-3.1.4.1-he30d5cf_0.conda#a85ba48648f6868016f2741fd9170250 +https://conda.anaconda.org/conda-forge/linux-aarch64/lerc-4.1.0-h52b7260_0.conda#d13423b06447113a90b5b1366d4da171 +https://conda.anaconda.org/conda-forge/linux-aarch64/libdeflate-1.25-h1af38f5_0.conda#a9138815598fe6b91a1d6782ca657b0c +https://conda.anaconda.org/conda-forge/linux-aarch64/libwebp-base-1.6.0-ha2e29f5_0.conda#24e92d0942c799db387f5c9d7b81f1af +https://conda.anaconda.org/conda-forge/linux-aarch64/libtiff-4.7.1-hdb009f0_1.conda#8c6fd84f9c87ac00636007c6131e457d +https://conda.anaconda.org/conda-forge/linux-aarch64/lcms2-2.18-h9d5b58d_0.conda#bb960f01525b5e001608afef9d47b79c +https://conda.anaconda.org/conda-forge/linux-aarch64/pthread-stubs-0.4-h86ecc28_1002.conda#bb5a90c93e3bac3d5690acf76b4a6386 +https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxau-1.0.12-he30d5cf_1.conda#1c246e1105000c3660558459e2fd6d43 +https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxdmcp-1.1.5-he30d5cf_1.conda#bff06dcde4a707339d66d45d96ceb2e2 +https://conda.anaconda.org/conda-forge/linux-aarch64/libxcb-1.17.0-h262b8f6_0.conda#cd14ee5cca2464a425b1dbfc24d90db2 +https://conda.anaconda.org/conda-forge/noarch/markdown-3.10.2-pyhcf101f3_0.conda#ba0a9221ce1063f31692c07370d062f3 +https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda#592132998493b3ff25fd7479396e8351 +https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda#5b5203189eb668f042ac2b0826244964 +https://conda.anaconda.org/conda-forge/noarch/natsort-8.4.0-pyhcf101f3_2.conda#e941e85e273121222580723010bd4fa2 +https://conda.anaconda.org/conda-forge/noarch/packaging-26.1-pyhc364b38_0.conda#b8ae38639d323d808da535fb71e31be8 +https://conda.anaconda.org/conda-forge/linux-aarch64/openjpeg-2.5.4-h5da879a_0.conda#cea962410e327262346d48d01f05936c +https://conda.anaconda.org/conda-forge/linux-aarch64/zlib-ng-2.3.3-ha7cb516_1.conda#f731af71c723065d91b4c01bb822641b +https://conda.anaconda.org/conda-forge/linux-aarch64/pillow-12.2.0-py314hac3e5ec_0.conda#87d58d103b47c4a8567b3d7666647684 +https://conda.anaconda.org/conda-forge/noarch/narwhals-2.20.0-pyhcf101f3_0.conda#6cac1a50359219d786453c6fef819f98 +https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda#3e9427ee186846052e81fadde8ebe96a +https://conda.anaconda.org/conda-forge/linux-aarch64/polars-runtime-32-1.40.0-py310hff09b76_0.conda#d5628a33ce7652511e38fc98643dc910 +https://conda.anaconda.org/conda-forge/noarch/polars-1.40.0-pyh58ad624_0.conda#fd16be490f5403adfbf27dd4901bbe34 +https://conda.anaconda.org/conda-forge/linux-aarch64/polars-runtime-compat-1.40.0-py310hf00a4a2_0.conda#a82af0fcbb72db253dc89a7a45279372 +https://conda.anaconda.org/conda-forge/noarch/polars-lts-cpu-1.34.0.deprecated-hc364b38_0.conda#ef0340e75068ac8ff96462749b5c98e7 +https://conda.anaconda.org/conda-forge/linux-aarch64/yaml-0.2.5-h80f16a2_3.conda#032d8030e4a24fe1f72c74423a46fb88 +https://conda.anaconda.org/conda-forge/linux-aarch64/pyyaml-6.0.3-py314h807365f_1.conda#9ae2c92975118058bd720e9ba2bb7c58 +https://conda.anaconda.org/conda-forge/noarch/pyaml-env-1.2.2-pyhd8ed1ab_0.conda#e17be1016bcc3516827b836cd3e4d9dc +https://conda.anaconda.org/conda-forge/linux-aarch64/pydantic-core-2.46.3-py314h451b6cc_0.conda#1a2cb55be9a153ad6203bff6b787c240 +https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_1.conda#a0a4a3035667fc34f29bfbd5c190baa6 +https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.3-pyhcf101f3_0.conda#f690e6f204efd2e5c06b57518a383d98 +https://conda.anaconda.org/conda-forge/noarch/python-dotenv-1.2.2-pyhcf101f3_0.conda#130584ad9f3a513cdd71b1fdc1244e9c +https://conda.anaconda.org/conda-forge/noarch/python-kaleido-0.2.1-pyhd8ed1ab_0.tar.bz2#310259a5b03ff02289d7705f39e2b1d2 +https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda#461219d1a5bd61342293efa2c0c90eac +https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda#9272daa869e03efe68833e3dc7a02130 +https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda#10afbb4dbf06ff959ad25a92ccee6e59 +https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda#16c18772b340887160c79a6acc022db0 +https://conda.anaconda.org/conda-forge/noarch/rich-15.0.0-pyhcf101f3_0.conda#0242025a3c804966bf71aa04eee82f66 +https://conda.anaconda.org/conda-forge/noarch/rich-click-1.9.7-pyh8f84b5b_0.conda#0c20a8ebcddb24a45da89d5e917e6cb9 +https://conda.anaconda.org/conda-forge/noarch/spectra-0.0.11-pyhd8ed1ab_2.conda#472239e4eb7b5a84bb96b3ed7e3a596a +https://conda.anaconda.org/conda-forge/linux-aarch64/regex-2026.4.4-py314h51f160d_0.conda#88a3dbd279e6b1faf0cddb8397866864 +https://conda.anaconda.org/conda-forge/linux-aarch64/tiktoken-0.12.0-py314h6a36e60_3.conda#55bf7b559202236157b14323b40f19e6 +https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda#e5ce43272193b38c2e9037446c1d9206 +https://conda.anaconda.org/conda-forge/noarch/typeguard-4.5.1-pyhd8ed1ab_0.conda#260af1b0a94f719de76b4e14094e9a3b +https://conda.anaconda.org/bioconda/noarch/multiqc-1.34-pyhdfd78af_0.conda#a7111ab9a6a6146b40cbce16655ac873 +https://conda.anaconda.org/conda-forge/noarch/pip-26.0.1-pyh145f28c_0.conda#09a970fbf75e8ed1aa633827ded6aa4f +https://conda.anaconda.org/conda-forge/linux-aarch64/procps-ng-4.0.6-h1779866_0.conda#ab7288cc39545556d1bc5e71ab2df9a9 diff --git a/modules/nf-core/multiqc/environment.yml b/modules/nf-core/multiqc/environment.yml index d02016a0..37e7612d 100644 --- a/modules/nf-core/multiqc/environment.yml +++ b/modules/nf-core/multiqc/environment.yml @@ -4,4 +4,4 @@ channels: - conda-forge - bioconda dependencies: - - bioconda::multiqc=1.32 + - bioconda::multiqc=1.34 diff --git a/modules/nf-core/multiqc/main.nf b/modules/nf-core/multiqc/main.nf index c1158fb0..e80e8cd8 100644 --- a/modules/nf-core/multiqc/main.nf +++ b/modules/nf-core/multiqc/main.nf @@ -1,24 +1,21 @@ process MULTIQC { + tag "${meta.id}" label 'process_single' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/8c/8c6c120d559d7ee04c7442b61ad7cf5a9e8970be5feefb37d68eeaa60c1034eb/data' : - 'community.wave.seqera.io/library/multiqc:1.32--d58f60e4deb769bf' }" + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/1b/1bef8af6be88c5733461959c46ac8ef73d18f65277f62a1695d0e1633054f9c2/data' + : 'community.wave.seqera.io/library/multiqc:1.34--db7c73dae76bc9e6'}" input: - path multiqc_files, stageAs: "?/*" - path(multiqc_config) - path(extra_multiqc_config) - path(multiqc_logo) - path(replace_names) - path(sample_names) + tuple val(meta), path(multiqc_files, stageAs: "?/*"), path(multiqc_config, stageAs: "?/*"), path(multiqc_logo), path(replace_names), path(sample_names) output: - path "*multiqc_report.html", emit: report - path "*_data" , emit: data - path "*_plots" , optional:true, emit: plots - path "versions.yml" , emit: versions + tuple val(meta), path("*.html"), emit: report + tuple val(meta), path("*_data"), emit: data + tuple val(meta), path("*_plots"), emit: plots, optional: true + // MultiQC should not push its versions to the `versions` topic. Its input depends on the versions topic to be resolved thus outputting to the topic will let the pipeline hang forever + tuple val("${task.process}"), val('multiqc'), eval('multiqc --version | sed "s/.* //g"'), emit: versions when: task.ext.when == null || task.ext.when @@ -26,38 +23,28 @@ process MULTIQC { script: def args = task.ext.args ?: '' def prefix = task.ext.prefix ? "--filename ${task.ext.prefix}.html" : '' - def config = multiqc_config ? "--config $multiqc_config" : '' - def extra_config = extra_multiqc_config ? "--config $extra_multiqc_config" : '' + def config = multiqc_config ? multiqc_config instanceof List ? "--config ${multiqc_config.join(' --config ')}" : "--config ${multiqc_config}" : "" def logo = multiqc_logo ? "--cl-config 'custom_logo: \"${multiqc_logo}\"'" : '' def replace = replace_names ? "--replace-names ${replace_names}" : '' def samples = sample_names ? "--sample-names ${sample_names}" : '' """ multiqc \\ --force \\ - $args \\ - $config \\ - $prefix \\ - $extra_config \\ - $logo \\ - $replace \\ - $samples \\ + ${args} \\ + ${config} \\ + ${prefix} \\ + ${logo} \\ + ${replace} \\ + ${samples} \\ . - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - multiqc: \$( multiqc --version | sed -e "s/multiqc, version //g" ) - END_VERSIONS """ stub: """ mkdir multiqc_data + touch multiqc_data/.stub mkdir multiqc_plots + touch multiqc_plots/.stub touch multiqc_report.html - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - multiqc: \$( multiqc --version | sed -e "s/multiqc, version //g" ) - END_VERSIONS """ } diff --git a/modules/nf-core/multiqc/meta.yml b/modules/nf-core/multiqc/meta.yml index ce30eb73..2facc627 100644 --- a/modules/nf-core/multiqc/meta.yml +++ b/modules/nf-core/multiqc/meta.yml @@ -1,6 +1,6 @@ name: multiqc -description: Aggregate results from bioinformatics analyses across many samples into - a single report +description: Aggregate results from bioinformatics analyses across many samples + into a single report keywords: - QC - bioinformatics tools @@ -12,74 +12,91 @@ tools: It's a general use tool, perfect for summarising the output from numerous bioinformatics tools. homepage: https://multiqc.info/ documentation: https://multiqc.info/docs/ - licence: ["GPL-3.0-or-later"] + licence: + - "GPL-3.0-or-later" identifier: biotools:multiqc input: - - multiqc_files: - type: file - description: | - List of reports / files recognised by MultiQC, for example the html and zip output of FastQC - ontologies: [] - - multiqc_config: - type: file - description: Optional config yml for MultiQC - pattern: "*.{yml,yaml}" - ontologies: - - edam: http://edamontology.org/format_3750 # YAML - - extra_multiqc_config: - type: file - description: Second optional config yml for MultiQC. Will override common sections - in multiqc_config. - pattern: "*.{yml,yaml}" - ontologies: - - edam: http://edamontology.org/format_3750 # YAML - - multiqc_logo: - type: file - description: Optional logo file for MultiQC - pattern: "*.{png}" - ontologies: [] - - replace_names: - type: file - description: | - Optional two-column sample renaming file. First column a set of - patterns, second column a set of corresponding replacements. Passed via - MultiQC's `--replace-names` option. - pattern: "*.{tsv}" - ontologies: - - edam: http://edamontology.org/format_3475 # TSV - - sample_names: - type: file - description: | - Optional TSV file with headers, passed to the MultiQC --sample_names - argument. - pattern: "*.{tsv}" - ontologies: - - edam: http://edamontology.org/format_3475 # TSV -output: - report: - - "*multiqc_report.html": + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample1', single_end:false ] + - multiqc_files: type: file - description: MultiQC report file - pattern: "multiqc_report.html" + description: | + List of reports / files recognised by MultiQC, for example the html and zip output of FastQC ontologies: [] - data: - - "*_data": - type: directory - description: MultiQC data dir - pattern: "multiqc_data" - plots: - - "*_plots": + - multiqc_config: + type: file + description: Optional config yml for MultiQC + pattern: "*.{yml,yaml}" + ontologies: + - edam: http://edamontology.org/format_3750 + - multiqc_logo: type: file - description: Plots created by MultiQC - pattern: "*_data" + description: Optional logo file for MultiQC + pattern: "*.{png}" ontologies: [] - versions: - - versions.yml: + - replace_names: + type: file + description: | + Optional two-column sample renaming file. First column a set of + patterns, second column a set of corresponding replacements. Passed via + MultiQC's `--replace-names` option. + pattern: "*.{tsv}" + ontologies: + - edam: http://edamontology.org/format_3475 + - sample_names: type: file - description: File containing software versions - pattern: "versions.yml" + description: | + Optional TSV file with headers, passed to the MultiQC --sample_names + argument. + pattern: "*.{tsv}" ontologies: - - edam: http://edamontology.org/format_3750 # YAML + - edam: http://edamontology.org/format_3475 +output: + report: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample1', single_end:false ] + - "*.html": + type: file + description: MultiQC report file + pattern: ".html" + ontologies: [] + data: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample1', single_end:false ] + - "*_data": + type: directory + description: MultiQC data dir + pattern: "multiqc_data" + plots: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample1', single_end:false ] + - "*_plots": + type: file + description: Plots created by MultiQC + pattern: "*_plots" + ontologies: [] + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - multiqc: + type: string + description: The tool name + - multiqc --version | sed "s/.* //g": + type: eval + description: The expression to obtain the version of the tool authors: - "@abhi18av" - "@bunop" @@ -90,3 +107,27 @@ maintainers: - "@bunop" - "@drpatelh" - "@jfy133" +containers: + conda: + linux/amd64: + lock_file: modules/nf-core/multiqc/.conda-lock/linux_amd64-bd-db7c73dae76bc9e6_1.txt + linux/arm64: + lock_file: modules/nf-core/multiqc/.conda-lock/linux_arm64-bd-d167b8012595a136_1.txt + docker: + linux/amd64: + name: community.wave.seqera.io/library/multiqc:1.34--db7c73dae76bc9e6 + build_id: bd-db7c73dae76bc9e6_1 + scan_id: sc-66fc7138dbf1cf48_1 + linux/arm64: + name: community.wave.seqera.io/library/multiqc:1.34--d167b8012595a136 + build_id: bd-d167b8012595a136_1 + scan_id: sc-ac701dfa631a2af9_1 + singularity: + linux/amd64: + name: oras://community.wave.seqera.io/library/multiqc:1.34--4fc8657c816047c0 + build_id: bd-4fc8657c816047c0_1 + https: https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/1b/1bef8af6be88c5733461959c46ac8ef73d18f65277f62a1695d0e1633054f9c2/data + linux/arm64: + name: oras://community.wave.seqera.io/library/multiqc:1.34--7fbd82d945c06726 + build_id: bd-7fbd82d945c06726_1 + https: https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/9a/9a1fec9662a152683e6fcae440d0ce20920b3b89dc62d1e3a52e73f92eba0969/data diff --git a/modules/nf-core/multiqc/tests/custom_prefix.config b/modules/nf-core/multiqc/tests/custom_prefix.config new file mode 100644 index 00000000..b30b1358 --- /dev/null +++ b/modules/nf-core/multiqc/tests/custom_prefix.config @@ -0,0 +1,5 @@ +process { + withName: 'MULTIQC' { + ext.prefix = "custom_prefix" + } +} diff --git a/modules/nf-core/multiqc/tests/main.nf.test b/modules/nf-core/multiqc/tests/main.nf.test index 33316a7d..4cbdb95d 100644 --- a/modules/nf-core/multiqc/tests/main.nf.test +++ b/modules/nf-core/multiqc/tests/main.nf.test @@ -15,25 +15,84 @@ nextflow_process { when { process { """ - input[0] = Channel.of(file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true)) - input[1] = [] - input[2] = [] - input[3] = [] - input[4] = [] - input[5] = [] + input[0] = channel.of([ + [ id: 'FASTQC' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true), + [], + [], + [], + [] + ]) """ } } then { - assertAll( - { assert process.success }, - { assert process.out.report[0] ==~ ".*/multiqc_report.html" }, - { assert process.out.data[0] ==~ ".*/multiqc_data" }, - { assert snapshot(process.out.versions).match("multiqc_versions_single") } - ) + assert process.success + assert snapshot( + sanitizeOutput(process.out).collectEntries { key, val -> + if (key == "data") { + return [key, val.collect { [path(it[1]).list().collect { file(it.toString()).name }] }] + } + else if (key == "plots") { + return [key, val.collect { [ + "pdf", + path("${it[1]}/pdf").list().collect { file(it.toString()).name }, + "png", + path("${it[1]}/png").list().collect { file(it.toString()).name }, + "svg", + path("${it[1]}/svg").list().collect { file(it.toString()).name }] }] + } + else if (key == "report") { + return [key, file(val[0][1].toString()).name] + } + return [key, val] + } + ).match() + } + } + + test("sarscov2 single-end [fastqc] - custom prefix") { + config "./custom_prefix.config" + + when { + process { + """ + input[0] = channel.of([ + [ id: 'FASTQC' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true), + [], + [], + [], + [] + ]) + """ + } } + then { + assert process.success + assert snapshot( + sanitizeOutput(process.out).collectEntries { key, val -> + if (key == "data") { + return [key, val.collect { [path(it[1]).list().collect { file(it.toString()).name }] }] + } + else if (key == "plots") { + return [key, val.collect { [ + "pdf", + path("${it[1]}/pdf").list().collect { file(it.toString()).name }, + "png", + path("${it[1]}/png").list().collect { file(it.toString()).name }, + "svg", + path("${it[1]}/svg").list().collect { file(it.toString()).name }] }] + } + else if (key == "report") { + return [key, file(val[0][1].toString()).name] + } + return [key, val] + } + ).match() + } } test("sarscov2 single-end [fastqc] [config]") { @@ -41,23 +100,85 @@ nextflow_process { when { process { """ - input[0] = Channel.of(file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true)) - input[1] = Channel.of(file("https://github.com/nf-core/tools/raw/dev/nf_core/pipeline-template/assets/multiqc_config.yml", checkIfExists: true)) - input[2] = [] - input[3] = [] - input[4] = [] - input[5] = [] + input[0] = channel.of([ + [ id: 'FASTQC' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true), + file("https://raw.githubusercontent.com/nf-core/seqinspector/1.0.0/assets/multiqc_config.yml", checkIfExists: true), + [], + [], + [] + ]) """ } } then { - assertAll( - { assert process.success }, - { assert process.out.report[0] ==~ ".*/multiqc_report.html" }, - { assert process.out.data[0] ==~ ".*/multiqc_data" }, - { assert snapshot(process.out.versions).match("multiqc_versions_config") } - ) + assert process.success + assert snapshot( + sanitizeOutput(process.out).collectEntries { key, val -> + if (key == "data") { + return [key, val.collect { [path(it[1]).list().collect { file(it.toString()).name }] }] + } + else if (key == "plots") { + return [key, val.collect { [ + "pdf", + path("${it[1]}/pdf").list().collect { file(it.toString()).name }, + "png", + path("${it[1]}/png").list().collect { file(it.toString()).name }, + "svg", + path("${it[1]}/svg").list().collect { file(it.toString()).name }] }] + } + else if (key == "report") { + return [key, file(val[0][1].toString()).name] + } + return [key, val] + } + ).match() + } + } + + test("sarscov2 single-end [fastqc] [multiple configs]") { + + when { + process { + """ + input[0] = channel.of([ + [ id: 'FASTQC' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true), + [ + file("https://raw.githubusercontent.com/nf-core/seqinspector/1.0.0/assets/multiqc_config.yml", checkIfExists: true), + file("https://raw.githubusercontent.com/nf-core/seqinspector/1.0.0/assets/multiqc_config.yml", checkIfExists: true) + ], + [], + [], + [] + ]) + """ + } + } + + then { + assert process.success + assert snapshot( + sanitizeOutput(process.out).collectEntries { key, val -> + if (key == "data") { + return [key, val.collect { [path(it[1]).list().collect { file(it.toString()).name }] }] + } + else if (key == "plots") { + return [key, val.collect { [ + "pdf", + path("${it[1]}/pdf").list().collect { file(it.toString()).name }, + "png", + path("${it[1]}/png").list().collect { file(it.toString()).name }, + "svg", + path("${it[1]}/svg").list().collect { file(it.toString()).name }] }] + } + else if (key == "report") { + return [key, file(val[0][1].toString()).name] + } + return [key, val] + } + ).match() } } @@ -68,25 +189,23 @@ nextflow_process { when { process { """ - input[0] = Channel.of(file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true)) - input[1] = [] - input[2] = [] - input[3] = [] - input[4] = [] - input[5] = [] + input[0] = channel.of([ + [ id: 'FASTQC' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true), + [], + [], + [], + [] + ]) """ } } then { + assert process.success assertAll( - { assert process.success }, - { assert snapshot(process.out.report.collect { file(it).getName() } + - process.out.data.collect { file(it).getName() } + - process.out.plots.collect { file(it).getName() } + - process.out.versions ).match("multiqc_stub") } + { assert snapshot(sanitizeOutput(process.out)).match() } ) } - } } diff --git a/modules/nf-core/multiqc/tests/main.nf.test.snap b/modules/nf-core/multiqc/tests/main.nf.test.snap index a88bafd6..7c2f370f 100644 --- a/modules/nf-core/multiqc/tests/main.nf.test.snap +++ b/modules/nf-core/multiqc/tests/main.nf.test.snap @@ -1,41 +1,422 @@ { - "multiqc_versions_single": { + "sarscov2 single-end [fastqc] [multiple configs]": { "content": [ - [ - "versions.yml:md5,737bb2c7cad54ffc2ec020791dc48b8f" - ] + { + "data": [ + [ + [ + "fastqc-status-check-heatmap.txt", + "fastqc_overrepresented_sequences_plot.txt", + "fastqc_per_base_n_content_plot.txt", + "fastqc_per_base_sequence_quality_plot.txt", + "fastqc_per_sequence_gc_content_plot_Counts.txt", + "fastqc_per_sequence_gc_content_plot_Percentages.txt", + "fastqc_per_sequence_quality_scores_plot.txt", + "fastqc_sequence_counts_plot.txt", + "fastqc_sequence_duplication_levels_plot.txt", + "fastqc_sequence_length_distribution_plot.txt", + "fastqc_top_overrepresented_sequences_table.txt", + "llms-full.txt", + "multiqc.log", + "multiqc.parquet", + "multiqc_citations.txt", + "multiqc_data.json", + "multiqc_fastqc.txt", + "multiqc_general_stats.txt", + "multiqc_sources.txt" + ] + ] + ], + "plots": [ + [ + "pdf", + [ + "fastqc-status-check-heatmap.pdf", + "fastqc_overrepresented_sequences_plot.pdf", + "fastqc_per_base_n_content_plot.pdf", + "fastqc_per_base_sequence_quality_plot.pdf", + "fastqc_per_sequence_gc_content_plot_Counts.pdf", + "fastqc_per_sequence_gc_content_plot_Percentages.pdf", + "fastqc_per_sequence_quality_scores_plot.pdf", + "fastqc_sequence_counts_plot-cnt.pdf", + "fastqc_sequence_counts_plot-pct.pdf", + "fastqc_sequence_duplication_levels_plot.pdf", + "fastqc_sequence_length_distribution_plot.pdf", + "fastqc_top_overrepresented_sequences_table.pdf" + ], + "png", + [ + "fastqc-status-check-heatmap.png", + "fastqc_overrepresented_sequences_plot.png", + "fastqc_per_base_n_content_plot.png", + "fastqc_per_base_sequence_quality_plot.png", + "fastqc_per_sequence_gc_content_plot_Counts.png", + "fastqc_per_sequence_gc_content_plot_Percentages.png", + "fastqc_per_sequence_quality_scores_plot.png", + "fastqc_sequence_counts_plot-cnt.png", + "fastqc_sequence_counts_plot-pct.png", + "fastqc_sequence_duplication_levels_plot.png", + "fastqc_sequence_length_distribution_plot.png", + "fastqc_top_overrepresented_sequences_table.png" + ], + "svg", + [ + "fastqc-status-check-heatmap.svg", + "fastqc_overrepresented_sequences_plot.svg", + "fastqc_per_base_n_content_plot.svg", + "fastqc_per_base_sequence_quality_plot.svg", + "fastqc_per_sequence_gc_content_plot_Counts.svg", + "fastqc_per_sequence_gc_content_plot_Percentages.svg", + "fastqc_per_sequence_quality_scores_plot.svg", + "fastqc_sequence_counts_plot-cnt.svg", + "fastqc_sequence_counts_plot-pct.svg", + "fastqc_sequence_duplication_levels_plot.svg", + "fastqc_sequence_length_distribution_plot.svg", + "fastqc_top_overrepresented_sequences_table.svg" + ] + ] + ], + "report": "multiqc_report.html", + "versions": [ + [ + "MULTIQC", + "multiqc", + "1.34" + ] + ] + } ], + "timestamp": "2026-03-17T16:15:42.577775492", "meta": { - "nf-test": "0.9.3", - "nextflow": "24.10.4" - }, - "timestamp": "2025-10-27T13:33:24.356715" + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } }, - "multiqc_stub": { + "sarscov2 single-end [fastqc]": { "content": [ - [ - "multiqc_report.html", - "multiqc_data", - "multiqc_plots", - "versions.yml:md5,737bb2c7cad54ffc2ec020791dc48b8f" - ] + { + "data": [ + [ + [ + "fastqc-status-check-heatmap.txt", + "fastqc_overrepresented_sequences_plot.txt", + "fastqc_per_base_n_content_plot.txt", + "fastqc_per_base_sequence_quality_plot.txt", + "fastqc_per_sequence_gc_content_plot_Counts.txt", + "fastqc_per_sequence_gc_content_plot_Percentages.txt", + "fastqc_per_sequence_quality_scores_plot.txt", + "fastqc_sequence_counts_plot.txt", + "fastqc_sequence_duplication_levels_plot.txt", + "fastqc_sequence_length_distribution_plot.txt", + "fastqc_top_overrepresented_sequences_table.txt", + "llms-full.txt", + "multiqc.log", + "multiqc.parquet", + "multiqc_citations.txt", + "multiqc_data.json", + "multiqc_fastqc.txt", + "multiqc_general_stats.txt", + "multiqc_software_versions.txt", + "multiqc_sources.txt" + ] + ] + ], + "plots": [ + [ + "pdf", + [ + "fastqc-status-check-heatmap.pdf", + "fastqc_overrepresented_sequences_plot.pdf", + "fastqc_per_base_n_content_plot.pdf", + "fastqc_per_base_sequence_quality_plot.pdf", + "fastqc_per_sequence_gc_content_plot_Counts.pdf", + "fastqc_per_sequence_gc_content_plot_Percentages.pdf", + "fastqc_per_sequence_quality_scores_plot.pdf", + "fastqc_sequence_counts_plot-cnt.pdf", + "fastqc_sequence_counts_plot-pct.pdf", + "fastqc_sequence_duplication_levels_plot.pdf", + "fastqc_sequence_length_distribution_plot.pdf", + "fastqc_top_overrepresented_sequences_table.pdf" + ], + "png", + [ + "fastqc-status-check-heatmap.png", + "fastqc_overrepresented_sequences_plot.png", + "fastqc_per_base_n_content_plot.png", + "fastqc_per_base_sequence_quality_plot.png", + "fastqc_per_sequence_gc_content_plot_Counts.png", + "fastqc_per_sequence_gc_content_plot_Percentages.png", + "fastqc_per_sequence_quality_scores_plot.png", + "fastqc_sequence_counts_plot-cnt.png", + "fastqc_sequence_counts_plot-pct.png", + "fastqc_sequence_duplication_levels_plot.png", + "fastqc_sequence_length_distribution_plot.png", + "fastqc_top_overrepresented_sequences_table.png" + ], + "svg", + [ + "fastqc-status-check-heatmap.svg", + "fastqc_overrepresented_sequences_plot.svg", + "fastqc_per_base_n_content_plot.svg", + "fastqc_per_base_sequence_quality_plot.svg", + "fastqc_per_sequence_gc_content_plot_Counts.svg", + "fastqc_per_sequence_gc_content_plot_Percentages.svg", + "fastqc_per_sequence_quality_scores_plot.svg", + "fastqc_sequence_counts_plot-cnt.svg", + "fastqc_sequence_counts_plot-pct.svg", + "fastqc_sequence_duplication_levels_plot.svg", + "fastqc_sequence_length_distribution_plot.svg", + "fastqc_top_overrepresented_sequences_table.svg" + ] + ] + ], + "report": "multiqc_report.html", + "versions": [ + [ + "MULTIQC", + "multiqc", + "1.34" + ] + ] + } ], + "timestamp": "2026-03-17T16:21:17.072841555", "meta": { - "nf-test": "0.9.3", - "nextflow": "24.10.4" - }, - "timestamp": "2025-10-27T13:34:11.103619" + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } }, - "multiqc_versions_config": { + "sarscov2 single-end [fastqc] - stub": { "content": [ - [ - "versions.yml:md5,737bb2c7cad54ffc2ec020791dc48b8f" - ] + { + "data": [ + [ + { + "id": "FASTQC" + }, + [ + ".stub:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "plots": [ + [ + { + "id": "FASTQC" + }, + [ + ".stub:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "report": [ + [ + { + "id": "FASTQC" + }, + "multiqc_report.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + [ + "MULTIQC", + "multiqc", + "1.34" + ] + ] + } ], + "timestamp": "2026-02-26T15:14:39.789193051", "meta": { - "nf-test": "0.9.3", - "nextflow": "24.10.4" - }, - "timestamp": "2025-10-27T13:34:04.615233" + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "sarscov2 single-end [fastqc] [config]": { + "content": [ + { + "data": [ + [ + [ + "fastqc-status-check-heatmap.txt", + "fastqc_overrepresented_sequences_plot.txt", + "fastqc_per_base_n_content_plot.txt", + "fastqc_per_base_sequence_quality_plot.txt", + "fastqc_per_sequence_gc_content_plot_Counts.txt", + "fastqc_per_sequence_gc_content_plot_Percentages.txt", + "fastqc_per_sequence_quality_scores_plot.txt", + "fastqc_sequence_counts_plot.txt", + "fastqc_sequence_duplication_levels_plot.txt", + "fastqc_sequence_length_distribution_plot.txt", + "fastqc_top_overrepresented_sequences_table.txt", + "llms-full.txt", + "multiqc.log", + "multiqc.parquet", + "multiqc_citations.txt", + "multiqc_data.json", + "multiqc_fastqc.txt", + "multiqc_general_stats.txt", + "multiqc_sources.txt" + ] + ] + ], + "plots": [ + [ + "pdf", + [ + "fastqc-status-check-heatmap.pdf", + "fastqc_overrepresented_sequences_plot.pdf", + "fastqc_per_base_n_content_plot.pdf", + "fastqc_per_base_sequence_quality_plot.pdf", + "fastqc_per_sequence_gc_content_plot_Counts.pdf", + "fastqc_per_sequence_gc_content_plot_Percentages.pdf", + "fastqc_per_sequence_quality_scores_plot.pdf", + "fastqc_sequence_counts_plot-cnt.pdf", + "fastqc_sequence_counts_plot-pct.pdf", + "fastqc_sequence_duplication_levels_plot.pdf", + "fastqc_sequence_length_distribution_plot.pdf", + "fastqc_top_overrepresented_sequences_table.pdf" + ], + "png", + [ + "fastqc-status-check-heatmap.png", + "fastqc_overrepresented_sequences_plot.png", + "fastqc_per_base_n_content_plot.png", + "fastqc_per_base_sequence_quality_plot.png", + "fastqc_per_sequence_gc_content_plot_Counts.png", + "fastqc_per_sequence_gc_content_plot_Percentages.png", + "fastqc_per_sequence_quality_scores_plot.png", + "fastqc_sequence_counts_plot-cnt.png", + "fastqc_sequence_counts_plot-pct.png", + "fastqc_sequence_duplication_levels_plot.png", + "fastqc_sequence_length_distribution_plot.png", + "fastqc_top_overrepresented_sequences_table.png" + ], + "svg", + [ + "fastqc-status-check-heatmap.svg", + "fastqc_overrepresented_sequences_plot.svg", + "fastqc_per_base_n_content_plot.svg", + "fastqc_per_base_sequence_quality_plot.svg", + "fastqc_per_sequence_gc_content_plot_Counts.svg", + "fastqc_per_sequence_gc_content_plot_Percentages.svg", + "fastqc_per_sequence_quality_scores_plot.svg", + "fastqc_sequence_counts_plot-cnt.svg", + "fastqc_sequence_counts_plot-pct.svg", + "fastqc_sequence_duplication_levels_plot.svg", + "fastqc_sequence_length_distribution_plot.svg", + "fastqc_top_overrepresented_sequences_table.svg" + ] + ] + ], + "report": "multiqc_report.html", + "versions": [ + [ + "MULTIQC", + "multiqc", + "1.34" + ] + ] + } + ], + "timestamp": "2026-03-17T16:15:30.372239611", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "sarscov2 single-end [fastqc] - custom prefix": { + "content": [ + { + "data": [ + [ + [ + "fastqc-status-check-heatmap.txt", + "fastqc_overrepresented_sequences_plot.txt", + "fastqc_per_base_n_content_plot.txt", + "fastqc_per_base_sequence_quality_plot.txt", + "fastqc_per_sequence_gc_content_plot_Counts.txt", + "fastqc_per_sequence_gc_content_plot_Percentages.txt", + "fastqc_per_sequence_quality_scores_plot.txt", + "fastqc_sequence_counts_plot.txt", + "fastqc_sequence_duplication_levels_plot.txt", + "fastqc_sequence_length_distribution_plot.txt", + "fastqc_top_overrepresented_sequences_table.txt", + "llms-full.txt", + "multiqc.log", + "multiqc.parquet", + "multiqc_citations.txt", + "multiqc_data.json", + "multiqc_fastqc.txt", + "multiqc_general_stats.txt", + "multiqc_software_versions.txt", + "multiqc_sources.txt" + ] + ] + ], + "plots": [ + [ + "pdf", + [ + "fastqc-status-check-heatmap.pdf", + "fastqc_overrepresented_sequences_plot.pdf", + "fastqc_per_base_n_content_plot.pdf", + "fastqc_per_base_sequence_quality_plot.pdf", + "fastqc_per_sequence_gc_content_plot_Counts.pdf", + "fastqc_per_sequence_gc_content_plot_Percentages.pdf", + "fastqc_per_sequence_quality_scores_plot.pdf", + "fastqc_sequence_counts_plot-cnt.pdf", + "fastqc_sequence_counts_plot-pct.pdf", + "fastqc_sequence_duplication_levels_plot.pdf", + "fastqc_sequence_length_distribution_plot.pdf", + "fastqc_top_overrepresented_sequences_table.pdf" + ], + "png", + [ + "fastqc-status-check-heatmap.png", + "fastqc_overrepresented_sequences_plot.png", + "fastqc_per_base_n_content_plot.png", + "fastqc_per_base_sequence_quality_plot.png", + "fastqc_per_sequence_gc_content_plot_Counts.png", + "fastqc_per_sequence_gc_content_plot_Percentages.png", + "fastqc_per_sequence_quality_scores_plot.png", + "fastqc_sequence_counts_plot-cnt.png", + "fastqc_sequence_counts_plot-pct.png", + "fastqc_sequence_duplication_levels_plot.png", + "fastqc_sequence_length_distribution_plot.png", + "fastqc_top_overrepresented_sequences_table.png" + ], + "svg", + [ + "fastqc-status-check-heatmap.svg", + "fastqc_overrepresented_sequences_plot.svg", + "fastqc_per_base_n_content_plot.svg", + "fastqc_per_base_sequence_quality_plot.svg", + "fastqc_per_sequence_gc_content_plot_Counts.svg", + "fastqc_per_sequence_gc_content_plot_Percentages.svg", + "fastqc_per_sequence_quality_scores_plot.svg", + "fastqc_sequence_counts_plot-cnt.svg", + "fastqc_sequence_counts_plot-pct.svg", + "fastqc_sequence_duplication_levels_plot.svg", + "fastqc_sequence_length_distribution_plot.svg", + "fastqc_top_overrepresented_sequences_table.svg" + ] + ] + ], + "report": "custom_prefix.html", + "versions": [ + [ + "MULTIQC", + "multiqc", + "1.34" + ] + ] + } + ], + "timestamp": "2026-03-17T16:15:18.189023981", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } } } \ No newline at end of file diff --git a/modules/nf-core/multiqc/tests/nextflow.config b/modules/nf-core/multiqc/tests/nextflow.config index c537a6a3..374dfef2 100644 --- a/modules/nf-core/multiqc/tests/nextflow.config +++ b/modules/nf-core/multiqc/tests/nextflow.config @@ -1,5 +1,6 @@ process { withName: 'MULTIQC' { ext.prefix = null + ext.args = '-p' } } diff --git a/nextflow.config b/nextflow.config index b9c8f64f..e081c0c2 100644 --- a/nextflow.config +++ b/nextflow.config @@ -32,7 +32,6 @@ params { email_on_fail = null plaintext_email = false monochrome_logs = false - hook_url = System.getenv('HOOK_URL') help = false help_full = false show_hidden = false @@ -53,6 +52,10 @@ params { validate_params = true } +// Backwards compatibility for publishDir syntax +outputDir = params.outdir +workflow.output.mode = params.publish_dir_mode + // Load base.config by default for all pipelines includeConfig 'conf/base.config' @@ -259,8 +262,8 @@ manifest { description = """A pipeline for spatialomics Xenium In Situ data.""" mainScript = 'main.nf' defaultBranch = 'master' - nextflowVersion = '!>=25.04.0' - version = '1.0dev' + nextflowVersion = '!>=25.10.4' + version = '1.0.0' doi = '' } @@ -273,6 +276,5 @@ validation { defaultIgnoreParams = ["genomes"] monochromeLogs = params.monochrome_logs } - // Load modules.config for DSL2 module specific options includeConfig 'conf/modules.config' diff --git a/nextflow_schema.json b/nextflow_schema.json index 024e3569..5d9dff39 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -74,7 +74,6 @@ }, "igenomes_base": { "type": "string", - "format": "directory-path", "description": "The base path to the igenomes reference files", "fa_icon": "fas fa-ban", "hidden": true, @@ -180,13 +179,6 @@ "fa_icon": "fas fa-palette", "hidden": true }, - "hook_url": { - "type": "string", - "description": "Incoming hook URL for messaging service", - "fa_icon": "fas fa-people-group", - "help_text": "Incoming hook URL for messaging service. Currently, MS Teams and Slack are supported.", - "hidden": true - }, "multiqc_config": { "type": "string", "format": "file-path", diff --git a/nf-test.config b/nf-test.config index 3a1fff59..f7aaeb4a 100644 --- a/nf-test.config +++ b/nf-test.config @@ -1,21 +1,35 @@ config { // location for all nf-test tests - testsDir "." + testsDir = "." // nf-test directory including temporary files for each test - workDir System.getenv("NFT_WORKDIR") ?: ".nf-test" + workDir = System.getenv("NFT_WORKDIR") ?: ".nf-test" // location of an optional nextflow.config file specific for executing tests - configFile "tests/nextflow.config" + configFile = "tests/nextflow.config" // ignore tests coming from the nf-core/modules repo - ignore 'modules/nf-core/**/tests/*', 'subworkflows/nf-core/**/tests/*' + ignore = [ + 'modules/nf-core/**/tests/*', + 'subworkflows/nf-core/**/tests/*', + ] // run all test with defined profile(s) from the main nextflow.config - profile "test" + profile = "test" // list of filenames or patterns that should be trigger a full test run - triggers 'nextflow.config', 'nf-test.config', 'conf/test.config', 'tests/nextflow.config', 'tests/.nftignore' + triggers = [ + '.github/actions/nf-test/action.yml', + '.github/workflows/nf-test.yml', + 'assets/schema_input.json', + 'bin/*', + 'conf/test.config', + 'nextflow.config', + 'nextflow_schema.json', + 'nf-test.config', + 'tests/.nftignore', + 'tests/nextflow.config', + ] // load the necessary plugins plugins { diff --git a/ro-crate-metadata.json b/ro-crate-metadata.json index fd557197..2d0ac5c2 100644 --- a/ro-crate-metadata.json +++ b/ro-crate-metadata.json @@ -1,6 +1,6 @@ { "@context": [ - "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/crate/1.2/context", { "GithubService": "https://w3id.org/ro/terms/test#GithubService", "JenkinsService": "https://w3id.org/ro/terms/test#JenkinsService", @@ -21,9 +21,9 @@ { "@id": "./", "@type": "Dataset", - "creativeWorkStatus": "InProgress", - "datePublished": "2025-11-20T09:32:15+00:00", - "description": "

\n \n \n \"nf-core/spatialxe\"\n \n

\n\n[![Open in GitHub Codespaces](https://img.shields.io/badge/Open_In_GitHub_Codespaces-black?labelColor=grey&logo=github)](https://github.com/codespaces/new/nf-core/spatialxe)\n[![GitHub Actions CI Status](https://github.com/nf-core/spatialxe/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/spatialxe/actions/workflows/nf-test.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-core/spatialxe/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/spatialxe/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/spatialxe/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX)\n[![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com)\n\n[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.04.0-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/)\n[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.5.1-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.5.1)\n[![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/)\n[![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/)\n[![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/)\n[![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://cloud.seqera.io/launch?pipeline=https://github.com/nf-core/spatialxe)\n\n[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23spatialxe-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/spatialxe)[![Follow on Bluesky](https://img.shields.io/badge/bluesky-%40nf__core-1185fe?labelColor=000000&logo=bluesky)](https://bsky.app/profile/nf-co.re)[![Follow on Mastodon](https://img.shields.io/badge/mastodon-nf__core-6364ff?labelColor=FFFFFF&logo=mastodon)](https://mstdn.science/@nf_core)[![Watch on YouTube](http://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core)\n\n## Introduction\n\n**nf-core/spatialxe** is a bioinformatics pipeline that ...\n\n\n\n\n1. Read QC ([`FastQC`](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/))2. Present QC for raw reads ([`MultiQC`](http://multiqc.info/))\n\n## Usage\n\n> [!NOTE]\n> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data.\n\n\n\nNow, you can run the pipeline using:\n\n\n\n```bash\nnextflow run nf-core/spatialxe \\\n -profile \\\n --input samplesheet.csv \\\n --outdir \n```\n\n> [!WARNING]\n> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/usage/getting_started/configuration#custom-configuration-files).\n\nFor more details and further functionality, please refer to the [usage documentation](https://nf-co.re/spatialxe/usage) and the [parameter documentation](https://nf-co.re/spatialxe/parameters).\n\n## Pipeline output\n\nTo see the results of an example test run with a full size dataset refer to the [results](https://nf-co.re/spatialxe/results) tab on the nf-core website pipeline page.\nFor more details about the output files and reports, please refer to the\n[output documentation](https://nf-co.re/spatialxe/output).\n\n## Credits\n\nnf-core/spatialxe was originally written by Sameesh Kher, Florian Heyl.\n\nWe thank the following people for their extensive assistance in the development of this pipeline:\n\n\n\n## Contributions and Support\n\nIf you would like to contribute to this pipeline, please see the [contributing guidelines](.github/CONTRIBUTING.md).\n\nFor further information or help, don't hesitate to get in touch on the [Slack `#spatialxe` channel](https://nfcore.slack.com/channels/spatialxe) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## Citations\n\n\n\n\n\n\nAn extensive list of references for the tools used by the pipeline can be found in the [`CITATIONS.md`](CITATIONS.md) file.\n\nYou can cite the `nf-core` publication as follows:\n\n> **The nf-core framework for community-curated bioinformatics pipelines.**\n>\n> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen.\n>\n> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x).\n", + "creativeWorkStatus": "Stable", + "datePublished": "2026-04-30T13:33:23+00:00", + "description": "

\n \n \n \"nf-core/spatialxe\"\n \n

\n\n[![Open in GitHub Codespaces](https://img.shields.io/badge/Open_In_GitHub_Codespaces-black?labelColor=grey&logo=github)](https://github.com/codespaces/new/nf-core/spatialxe)\n[![GitHub Actions CI Status](https://github.com/nf-core/spatialxe/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/spatialxe/actions/workflows/nf-test.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-core/spatialxe/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/spatialxe/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/spatialxe/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX)\n[![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com)\n\n[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.10.4-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/)\n[![nf-core template version](https://img.shields.io/badge/nf--core_template-4.0.2-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/4.0.2)\n[![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/)\n[![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/)\n[![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/)\n[![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://cloud.seqera.io/launch?pipeline=https://github.com/nf-core/spatialxe)\n\n[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23spatialxe-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/spatialxe)[![Follow on Bluesky](https://img.shields.io/badge/bluesky-%40nf__core-1185fe?labelColor=000000&logo=bluesky)](https://bsky.app/profile/nf-co.re)[![Follow on Mastodon](https://img.shields.io/badge/mastodon-nf__core-6364ff?labelColor=FFFFFF&logo=mastodon)](https://mstdn.science/@nf_core)[![Watch on YouTube](http://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core)\n\n## Introduction\n\n**nf-core/spatialxe** is a bioinformatics pipeline that ...\n\n\n\n\n1. Read QC ([`FastQC`](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/))2. Present QC for raw reads ([`MultiQC`](http://multiqc.info/))\n\n## Usage\n\n> [!NOTE]\n> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/get_started/environment_setup/overview) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/get_started/run-your-first-pipeline) with `-profile test` before running the workflow on actual data.\n\n\n\nNow, you can run the pipeline using:\n\n\n\n```bash\nnextflow run nf-core/spatialxe \\\n -profile \\\n --input samplesheet.csv \\\n --outdir \n```\n\n> [!WARNING]\n> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/running/run-pipelines#using-parameter-files).\n\nFor more details and further functionality, please refer to the [usage documentation](https://nf-co.re/spatialxe/usage) and the [parameter documentation](https://nf-co.re/spatialxe/parameters).\n\n## Pipeline output\n\nTo see the results of an example test run with a full size dataset refer to the [results](https://nf-co.re/spatialxe/results) tab on the nf-core website pipeline page.\nFor more details about the output files and reports, please refer to the\n[output documentation](https://nf-co.re/spatialxe/output).\n\n## Credits\n\nnf-core/spatialxe was originally written by Sameesh Kher, Florian Heyl.\n\nWe thank the following people for their extensive assistance in the development of this pipeline:\n\n\n\n## Contributions and Support\n\nIf you would like to contribute to this pipeline, please see the [contributing guidelines](docs/CONTRIBUTING.md).\n\nFor further information or help, don't hesitate to get in touch on the [Slack `#spatialxe` channel](https://nfcore.slack.com/channels/spatialxe) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## Citations\n\n\n\n\n\n\nAn extensive list of references for the tools used by the pipeline can be found in the [`CITATIONS.md`](CITATIONS.md) file.\n\nYou can cite the `nf-core` publication as follows:\n\n> **The nf-core framework for community-curated bioinformatics pipelines.**\n>\n> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen.\n>\n> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x).\n", "hasPart": [ { "@id": "main.nf" @@ -99,7 +99,7 @@ }, "mentions": [ { - "@id": "#ee2c0a4f-0aee-4f4b-9e9d-044fdc833c27" + "@id": "#1c52c7cb-def3-43c9-9e10-865c2cf0ba78" } ], "name": "nf-core/spatialxe" @@ -112,7 +112,7 @@ }, "conformsTo": [ { - "@id": "https://w3id.org/ro/crate/1.1" + "@id": "https://w3id.org/ro/crate/1.2" }, { "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" @@ -122,8 +122,16 @@ { "@id": "main.nf", "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "contributor": [ + { + "@id": "https://orcid.org/0009-0008-2420-6464" + }, + { + "@id": "https://orcid.org/0000-0002-3651-5685" + } + ], "dateCreated": "", - "dateModified": "2025-11-20T09:32:15Z", + "dateModified": "2026-04-30T13:33:23Z", "dct:conformsTo": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE/", "keywords": [ "nf-core", @@ -144,8 +152,8 @@ "sdPublisher": { "@id": "https://nf-co.re/" }, - "url": ["https://github.com/nf-core/spatialxe", "https://nf-co.re/spatialxe/dev/"], - "version": ["1.0dev"] + "url": ["https://github.com/nf-core/spatialxe", "https://nf-co.re/spatialxe/1.0.0/"], + "version": ["1.0.0"] }, { "@id": "https://w3id.org/workflowhub/workflow-ro-crate#nextflow", @@ -157,14 +165,14 @@ "url": { "@id": "https://www.nextflow.io/" }, - "version": "!>=25.04.0" + "version": "!>=25.10.4" }, { - "@id": "#ee2c0a4f-0aee-4f4b-9e9d-044fdc833c27", + "@id": "#1c52c7cb-def3-43c9-9e10-865c2cf0ba78", "@type": "TestSuite", "instance": [ { - "@id": "#ec41083f-c8e9-4bd2-818d-5be534486d2b" + "@id": "#162edd3a-9c52-463a-94f5-78ed109f8513" } ], "mainEntity": { @@ -173,7 +181,7 @@ "name": "Test suite for nf-core/spatialxe" }, { - "@id": "#ec41083f-c8e9-4bd2-818d-5be534486d2b", + "@id": "#162edd3a-9c52-463a-94f5-78ed109f8513", "@type": "TestInstance", "name": "GitHub Actions workflow for testing nf-core/spatialxe", "resource": "repos/nf-core/spatialxe/actions/workflows/nf-test.yml", @@ -300,6 +308,16 @@ "@type": "Organization", "name": "nf-core", "url": "https://nf-co.re/" + }, + { + "@id": "https://orcid.org/0009-0008-2420-6464", + "@type": "Person", + "name": "Sameesh Kher" + }, + { + "@id": "https://orcid.org/0000-0002-3651-5685", + "@type": "Person", + "name": "Florian Heyl" } ] } diff --git a/subworkflows/local/utils_nfcore_spatialxe_pipeline/main.nf b/subworkflows/local/utils_nfcore_spatialxe_pipeline/main.nf index 4e8dde6f..c60249c8 100644 --- a/subworkflows/local/utils_nfcore_spatialxe_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_spatialxe_pipeline/main.nf @@ -14,7 +14,6 @@ include { samplesheetToList } from 'plugin/nf-schema' include { paramsHelp } from 'plugin/nf-schema' include { completionEmail } from '../../nf-core/utils_nfcore_pipeline' include { completionSummary } from '../../nf-core/utils_nfcore_pipeline' -include { imNotification } from '../../nf-core/utils_nfcore_pipeline' include { UTILS_NFCORE_PIPELINE } from '../../nf-core/utils_nfcore_pipeline' include { UTILS_NEXTFLOW_PIPELINE } from '../../nf-core/utils_nextflow_pipeline' @@ -54,6 +53,9 @@ workflow PIPELINE_INITIALISATION { // // Validate parameters and generate parameter summary to stdout // + + def before_text = "" + def after_text = "" before_text = """ -\033[2m----------------------------------------------------\033[0m- \033[0;32m,--.\033[0;30m/\033[0;32m,-.\033[0m @@ -71,6 +73,10 @@ workflow PIPELINE_INITIALISATION { * Software dependencies https://github.com/nf-core/spatialxe/blob/master/CITATIONS.md """ + if (monochrome_logs) { + before_text = before_text.replaceAll(/\033\[[0-9;]*m/, '') + } + command = "nextflow run ${workflow.manifest.name} -profile --input samplesheet.csv --outdir " UTILS_NFSCHEMA_PLUGIN ( @@ -102,7 +108,7 @@ workflow PIPELINE_INITIALISATION { // channel - .fromList(samplesheetToList(params.input, "${projectDir}/assets/schema_input.json")) + .fromList(samplesheetToList(input, "${projectDir}/assets/schema_input.json")) .map { meta, fastq_1, fastq_2 -> if (!fastq_2) { @@ -140,7 +146,6 @@ workflow PIPELINE_COMPLETION { plaintext_email // boolean: Send plain-text email instead of HTML outdir // path: Path to output directory where results will be published monochrome_logs // boolean: Disable ANSI colour codes in log output - hook_url // string: hook URL for notifications multiqc_report // string: Path to MultiQC report main: @@ -164,13 +169,11 @@ workflow PIPELINE_COMPLETION { } completionSummary(monochrome_logs) - if (hook_url) { - imNotification(summary_params, hook_url) - } + } workflow.onError { - log.error "Pipeline failed. Please refer to troubleshooting docs: https://nf-co.re/docs/usage/troubleshooting" + log.error "Pipeline failed. Please refer to troubleshooting docs for common issues: https://nf-co.re/docs/running/troubleshooting" } } diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/main.nf b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf index 2f30e9a4..afca5439 100644 --- a/subworkflows/nf-core/utils_nfcore_pipeline/main.nf +++ b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf @@ -17,7 +17,7 @@ workflow UTILS_NFCORE_PIPELINE { checkProfileProvided(nextflow_cli_args) emit: - valid_config + valid_config = valid_config } /* @@ -353,67 +353,3 @@ def completionSummary(monochrome_logs=true) { log.info("-${colors.purple}[${workflow.manifest.name}]${colors.red} Pipeline completed with errors${colors.reset}-") } } - -// -// Construct and send a notification to a web server as JSON e.g. Microsoft Teams and Slack -// -def imNotification(summary_params, hook_url) { - def summary = [:] - summary_params - .keySet() - .sort() - .each { group -> - summary << summary_params[group] - } - - def misc_fields = [:] - misc_fields['start'] = workflow.start - misc_fields['complete'] = workflow.complete - misc_fields['scriptfile'] = workflow.scriptFile - misc_fields['scriptid'] = workflow.scriptId - if (workflow.repository) { - misc_fields['repository'] = workflow.repository - } - if (workflow.commitId) { - misc_fields['commitid'] = workflow.commitId - } - if (workflow.revision) { - misc_fields['revision'] = workflow.revision - } - misc_fields['nxf_version'] = workflow.nextflow.version - misc_fields['nxf_build'] = workflow.nextflow.build - misc_fields['nxf_timestamp'] = workflow.nextflow.timestamp - - def msg_fields = [:] - msg_fields['version'] = getWorkflowVersion() - msg_fields['runName'] = workflow.runName - msg_fields['success'] = workflow.success - msg_fields['dateComplete'] = workflow.complete - msg_fields['duration'] = workflow.duration - msg_fields['exitStatus'] = workflow.exitStatus - msg_fields['errorMessage'] = (workflow.errorMessage ?: 'None') - msg_fields['errorReport'] = (workflow.errorReport ?: 'None') - msg_fields['commandLine'] = workflow.commandLine.replaceFirst(/ +--hook_url +[^ ]+/, "") - msg_fields['projectDir'] = workflow.projectDir - msg_fields['summary'] = summary << misc_fields - - // Render the JSON template - def engine = new groovy.text.GStringTemplateEngine() - // Different JSON depending on the service provider - // Defaults to "Adaptive Cards" (https://adaptivecards.io), except Slack which has its own format - def json_path = hook_url.contains("hooks.slack.com") ? "slackreport.json" : "adaptivecard.json" - def hf = new File("${workflow.projectDir}/assets/${json_path}") - def json_template = engine.createTemplate(hf).make(msg_fields) - def json_message = json_template.toString() - - // POST - def post = new URL(hook_url).openConnection() - post.setRequestMethod("POST") - post.setDoOutput(true) - post.setRequestProperty("Content-Type", "application/json") - post.getOutputStream().write(json_message.getBytes("UTF-8")) - def postRC = post.getResponseCode() - if (!postRC.equals(200)) { - log.warn(post.getErrorStream().getText()) - } -} diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.nf.test b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.nf.test new file mode 100644 index 00000000..8940d32d --- /dev/null +++ b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.nf.test @@ -0,0 +1,29 @@ +nextflow_workflow { + + name "Test Workflow UTILS_NFCORE_PIPELINE" + script "../main.nf" + config "subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config" + workflow "UTILS_NFCORE_PIPELINE" + tag "subworkflows" + tag "subworkflows_nfcore" + tag "utils_nfcore_pipeline" + tag "subworkflows/utils_nfcore_pipeline" + + test("Should run without failures") { + + when { + workflow { + """ + input[0] = [] + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot(workflow.out).match() } + ) + } + } +} diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.nf.test.snap b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.nf.test.snap new file mode 100644 index 00000000..859d1030 --- /dev/null +++ b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.nf.test.snap @@ -0,0 +1,19 @@ +{ + "Should run without failures": { + "content": [ + { + "0": [ + true + ], + "valid_config": [ + true + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:25.726491" + } +} \ No newline at end of file diff --git a/subworkflows/nf-core/utils_nfschema_plugin/main.nf b/subworkflows/nf-core/utils_nfschema_plugin/main.nf index ee4738c8..1df8b76f 100644 --- a/subworkflows/nf-core/utils_nfschema_plugin/main.nf +++ b/subworkflows/nf-core/utils_nfschema_plugin/main.nf @@ -38,7 +38,7 @@ workflow UTILS_NFSCHEMA_PLUGIN { } log.info paramsHelp( help_options, - params.help instanceof String ? params.help : "", + (params.help instanceof String && params.help != "true") ? params.help : "", ) exit 0 } @@ -71,4 +71,3 @@ workflow UTILS_NFSCHEMA_PLUGIN { emit: dummy_emit = true } - diff --git a/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config b/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config index 8d8c7371..f6537cc3 100644 --- a/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config +++ b/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config @@ -1,5 +1,5 @@ plugins { - id "nf-schema@2.5.1" + id "nf-schema@2.6.1" } validation { diff --git a/tests/default.nf.test b/tests/default.nf.test index ba0b6870..d5000f57 100644 --- a/tests/default.nf.test +++ b/tests/default.nf.test @@ -13,19 +13,19 @@ nextflow_pipeline { } then { - // stable_name: All files + folders in ${params.outdir}/ with a stable name - def stable_name = getAllFilesFromDir(params.outdir, relative: true, includeDir: true, ignore: ['pipeline_info/*.{html,json,txt}']) - // stable_path: All files in ${params.outdir}/ with stable content - def stable_path = getAllFilesFromDir(params.outdir, ignoreFile: 'tests/.nftignore') + // stable_path: All files + folders in ${params.outdir}/ with a stable path (including file name) + def stable_path = getAllFilesFromDir(params.outdir, relative: true, includeDir: true, ignore: ['pipeline_info/*.{html,json,txt}']) + // stable_content: All files in ${params.outdir}/ with stable content + def stable_content = getAllFilesFromDir(params.outdir, ignoreFile: 'tests/.nftignore') + assert workflow.success assertAll( - { assert workflow.success}, { assert snapshot( // pipeline versions.yml file for multiqc from which Nextflow version is removed because we test pipelines on multiple Nextflow versions removeNextflowVersion("$outputDir/pipeline_info/nf_core_spatialxe_software_mqc_versions.yml"), // All stable path name, with a relative path - stable_name, + stable_path, // All files with stable contents - stable_path + stable_content ).match() } ) } diff --git a/tests/nextflow.config b/tests/nextflow.config index 21d5c006..caf25a7b 100644 --- a/tests/nextflow.config +++ b/tests/nextflow.config @@ -8,7 +8,7 @@ // Or any resources requirements params { modules_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/' - pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/refs/heads/spatialxe' + pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/refs/heads/spatialxe/' } aws.client.anonymous = true // fixes S3 access issues on self-hosted runners diff --git a/workflows/spatialxe.nf b/workflows/spatialxe.nf index 957483fd..d9a6a306 100644 --- a/workflows/spatialxe.nf +++ b/workflows/spatialxe.nf @@ -20,23 +20,25 @@ workflow SPATIALXE { take: ch_samplesheet // channel: samplesheet read in from --input + multiqc_config + multiqc_logo + multiqc_methods_description + outdir + main: - ch_versions = channel.empty() - ch_multiqc_files = channel.empty() + def ch_versions = channel.empty() + def ch_multiqc_files = channel.empty() // // MODULE: Run FastQC // - FASTQC ( - ch_samplesheet - ) - ch_multiqc_files = ch_multiqc_files.mix(FASTQC.out.zip.collect{it[1]}) - ch_versions = ch_versions.mix(FASTQC.out.versions.first()) + FASTQC(ch_samplesheet) + ch_multiqc_files = ch_multiqc_files.mix(FASTQC.out.zip.map{ _meta, file -> file }) // // Collate and save software versions // - def topic_versions = Channel.topic("versions") + def topic_versions = channel.topic("versions") .distinct() .branch { entry -> versions_file: entry instanceof Path @@ -53,59 +55,43 @@ workflow SPATIALXE { "${process}:\n${tool_versions.join('\n')}" } - softwareVersionsToYAML(ch_versions.mix(topic_versions.versions_file)) + def ch_collated_versions = softwareVersionsToYAML(ch_versions.mix(topic_versions.versions_file)) .mix(topic_versions_string) .collectFile( - storeDir: "${params.outdir}/pipeline_info", + storeDir: "${outdir}/pipeline_info", name: 'nf_core_' + 'spatialxe_software_' + 'mqc_' + 'versions.yml', sort: true, newLine: true - ).set { ch_collated_versions } - + ) // // MODULE: MultiQC // - ch_multiqc_config = channel.fromPath( - "$projectDir/assets/multiqc_config.yml", checkIfExists: true) - ch_multiqc_custom_config = params.multiqc_config ? - channel.fromPath(params.multiqc_config, checkIfExists: true) : - channel.empty() - ch_multiqc_logo = params.multiqc_logo ? - channel.fromPath(params.multiqc_logo, checkIfExists: true) : - channel.empty() - - summary_params = paramsSummaryMap( - workflow, parameters_schema: "nextflow_schema.json") - ch_workflow_summary = channel.value(paramsSummaryMultiqc(summary_params)) - ch_multiqc_files = ch_multiqc_files.mix( - ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) - ch_multiqc_custom_methods_description = params.multiqc_methods_description ? - file(params.multiqc_methods_description, checkIfExists: true) : - file("$projectDir/assets/methods_description_template.yml", checkIfExists: true) - ch_methods_description = channel.value( - methodsDescriptionText(ch_multiqc_custom_methods_description)) - ch_multiqc_files = ch_multiqc_files.mix(ch_collated_versions) - ch_multiqc_files = ch_multiqc_files.mix( - ch_methods_description.collectFile( - name: 'methods_description_mqc.yaml', - sort: true - ) - ) - - MULTIQC ( - ch_multiqc_files.collect(), - ch_multiqc_config.toList(), - ch_multiqc_custom_config.toList(), - ch_multiqc_logo.toList(), - [], - [] + def ch_summary_params = paramsSummaryMap(workflow, parameters_schema: "nextflow_schema.json") + def ch_workflow_summary = channel.value(paramsSummaryMultiqc(ch_summary_params)) + ch_multiqc_files = ch_multiqc_files.mix(ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) + def ch_multiqc_custom_methods_description = multiqc_methods_description + ? file(multiqc_methods_description, checkIfExists: true) + : file("${projectDir}/assets/methods_description_template.yml", checkIfExists: true) + def ch_methods_description = channel.value(methodsDescriptionText(ch_multiqc_custom_methods_description)) + ch_multiqc_files = ch_multiqc_files.mix(ch_methods_description.collectFile(name: 'methods_description_mqc.yaml', sort: true)) + MULTIQC( + ch_multiqc_files.flatten().collect().map { files -> + [ + [id: 'spatialxe'], + files, + multiqc_config + ? file(multiqc_config, checkIfExists: true) + : file("${projectDir}/assets/multiqc_config.yml", checkIfExists: true), + multiqc_logo ? file(multiqc_logo, checkIfExists: true) : [], + [], + [], + ] + } ) - - emit:multiqc_report = MULTIQC.out.report.toList() // channel: /path/to/multiqc_report.html + emit:multiqc_report = MULTIQC.out.report.map { _meta, report -> [report] }.toList() // channel: /path/to/multiqc_report.html versions = ch_versions // channel: [ path(versions.yml) ] - } /* From 7c264817afcb2b5dd5de2c5916dd164cb08b6db4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 8 May 2026 14:40:34 +0000 Subject: [PATCH 3/4] Revert "Merge dev into nf-core-template-merge-4.0.2" This reverts commit f5def5205ac69e6b0b14301ba81e6e7721b3b86b, reversing changes made to 2ef69a3d3e47d4c1f684ae86f70f61624fb80a05. Co-authored-by: khersameesh24 <58815690+khersameesh24@users.noreply.github.com> --- .github/PULL_REQUEST_TEMPLATE.md | 4 +- .github/workflows/awsfulltest.yml | 26 +- .github/workflows/linting.yml | 34 +- .github/workflows/nf-test.yml | 7 +- .github/workflows/release-announcements.yml | 4 +- .gitignore | 4 +- .nf-core.yml | 7 +- CITATIONS.md | 4 + README.md | 150 +- assets/config/xenium.toml | 15 - assets/email_template.html | 2 +- assets/example_samplesheet.csv | 2 - assets/methods_description_template.yml | 1 + assets/multiqc_config.yml | 34 +- assets/nf-core-spatialxe_logo_light.png | Bin 78352 -> 80727 bytes assets/samplesheet.csv | 5 +- assets/schema_input.json | 18 +- bin/baysor_create_dataset.py | 96 - bin/baysor_preprocess_transcripts.py | 126 - bin/divide_transcripts.py | 1312 ------- bin/ficture_preprocess.py | 101 - bin/segger_create_dataset.py | 253 -- bin/segger_predict.py | 137 - bin/spatialdata_merge.py | 82 - bin/spatialdata_meta.py | 126 - bin/spatialdata_write.py | 156 - bin/stitch_transcripts.py | 848 ----- bin/utility_convert_mask_uint32.py | 46 - bin/utility_downscale_morphology.py | 103 - bin/utility_extract_dapi.py | 60 - bin/utility_extract_data.py | 208 -- bin/utility_get_coordinates.py | 60 - bin/utility_parquet_to_csv.py | 70 - bin/utility_resize_tif.py | 134 - bin/utility_segger2xr.py | 247 -- bin/utility_split_transcripts.py | 109 - bin/utility_upscale_mask.py | 72 - bin/xenium_patch_stitch_postprocess.py | 98 - bin/xenium_patch_stitch_transcripts.py | 808 ---- conf/base.config | 89 +- conf/igenomes.config | 440 +++ conf/igenomes_ignored.config | 9 + conf/modules.config | 336 +- conf/test.config | 19 +- conf/test_coordinate_mode.config | 31 - conf/test_full.config | 11 +- conf/test_image_mode.config | 31 - conf/test_preview_mode.config | 31 - conf/test_segfree_mode.config | 31 - docs/images/nf-core-spatialxe_logo_dark.png | Bin 30158 -> 30543 bytes docs/images/spatialxe-logo.png | Bin 68244 -> 0 bytes docs/images/spatialxe-logo.svg | 234 -- docs/images/spatialxe-metromap.png | Bin 710797 -> 0 bytes docs/images/spatialxe-metromap.svg | 3277 ----------------- docs/output.md | 172 +- docs/usage.md | 157 +- main.nf | 64 +- modules.json | 67 +- modules/local/baysor/create_dataset/main.nf | 45 - modules/local/baysor/create_dataset/meta.yml | 82 - .../resources/usr/bin/create_dataset.py | 96 - .../baysor/create_dataset/tests/main.nf.test | 60 - .../create_dataset/tests/main.nf.test.snap | 34 - .../create_dataset/tests/nextflow.config | 9 - modules/local/baysor/preprocess/main.nf | 55 - modules/local/baysor/preprocess/meta.yml | 70 - .../usr/bin/preprocess_transcripts.py | 126 - .../baysor/preprocess/tests/main.nf.test | 68 - .../baysor/preprocess/tests/main.nf.test.snap | 34 - .../baysor/preprocess/tests/nextflow.config | 9 - modules/local/baysor/preview/main.nf | 50 - modules/local/baysor/preview/meta.yml | 56 - .../local/baysor/preview/tests/main.nf.test | 60 - .../baysor/preview/tests/main.nf.test.snap | 34 - .../baysor/preview/tests/nextflow.config | 9 - modules/local/baysor/run/main.nf | 67 - modules/local/baysor/run/meta.yml | 88 - modules/local/baysor/run/tests/main.nf.test | 67 - .../local/baysor/run/tests/main.nf.test.snap | 34 - modules/local/baysor/segfree/main.nf | 50 - modules/local/baysor/segfree/meta.yml | 55 - .../local/baysor/segfree/tests/main.nf.test | 60 - .../baysor/segfree/tests/main.nf.test.snap | 34 - modules/local/ficture/model/main.nf | 43 - modules/local/ficture/model/meta.yml | 47 - modules/local/ficture/preprocess/main.nf | 39 - modules/local/ficture/preprocess/meta.yml | 53 - .../resources/usr/bin/ficture_preprocess.py | 101 - modules/local/parquet_to_csv/main.nf | 44 - modules/local/parquet_to_csv/meta.yml | 70 - modules/local/proseg/preset/main.nf | 71 - modules/local/proseg/preset/meta.yml | 76 - .../local/proseg/preset/tests/main.nf.test | 61 - .../proseg/preset/tests/main.nf.test.snap | 34 - .../local/proseg/preset/tests/nextflow.config | 9 - modules/local/proseg/proseg2baysor/main.nf | 47 - modules/local/proseg/proseg2baysor/meta.yml | 56 - .../proseg/proseg2baysor/tests/main.nf.test | 72 - .../proseg2baysor/tests/main.nf.test.snap | 60 - .../proseg2baysor/tests/nextflow.config | 9 - modules/local/resolift/main.nf | 46 - modules/local/resolift/meta.yml | 46 - modules/local/resolift/tests/main.nf.test | 60 - .../local/resolift/tests/main.nf.test.snap | 84 - modules/local/resolift/tests/nextflow.config | 9 - modules/local/segger/Dockerfile | 52 - modules/local/segger/create_dataset/main.nf | 58 - modules/local/segger/create_dataset/meta.yml | 45 - .../resources/usr/bin/run_create_dataset.py | 253 -- modules/local/segger/predict/main.nf | 54 - modules/local/segger/predict/meta.yml | 58 - .../predict/resources/usr/bin/run_predict.py | 137 - modules/local/segger/train/main.nf | 71 - modules/local/segger/train/meta.yml | 47 - modules/local/spatialdata/merge/main.nf | 48 - modules/local/spatialdata/merge/meta.yml | 44 - .../resources/usr/bin/spatialdata_merge.py | 82 - modules/local/spatialdata/meta/main.nf | 49 - modules/local/spatialdata/meta/meta.yml | 44 - .../resources/usr/bin/spatialdata_meta.py | 126 - modules/local/spatialdata/write/main.nf | 52 - modules/local/spatialdata/write/meta.yml | 40 - .../resources/usr/bin/spatialdata_write.py | 156 - .../local/utility/convert_mask_uint32/main.nf | 48 - .../utility/convert_mask_uint32/meta.yml | 128 - .../utility/downscale_morphology/main.nf | 58 - .../utility/downscale_morphology/meta.yml | 138 - modules/local/utility/extract_dapi/main.nf | 50 - modules/local/utility/extract_dapi/meta.yml | 126 - .../utility/extract_preview_data/main.nf | 49 - .../utility/extract_preview_data/meta.yml | 99 - .../resources/usr/bin/extract_data.py | 208 -- modules/local/utility/get_coordinates/main.nf | 42 - .../local/utility/get_coordinates/meta.yml | 81 - .../resources/usr/bin/get_coordinates.py | 60 - modules/local/utility/parquet_to_csv/main.nf | 46 - modules/local/utility/parquet_to_csv/meta.yml | 42 - .../resources/usr/bin/parquet_to_csv.py | 70 - .../local/utility/reconstruct_patches/main.nf | 68 - .../utility/reconstruct_patches/meta.yml | 87 - modules/local/utility/resize_tif/main.nf | 48 - modules/local/utility/resize_tif/meta.yml | 126 - .../resources/usr/bin/resize_tif.py | 134 - modules/local/utility/segger2xr/main.nf | 50 - modules/local/utility/segger2xr/meta.yml | 101 - .../segger2xr/resources/usr/bin/segger2xr.py | 247 -- .../local/utility/split_transcripts/main.nf | 46 - .../local/utility/split_transcripts/meta.yml | 54 - .../resources/usr/bin/split_transcripts.py | 109 - modules/local/utility/upscale_mask/main.nf | 50 - modules/local/utility/upscale_mask/meta.yml | 103 - modules/local/xenium_patch/divide/main.nf | 57 - modules/local/xenium_patch/divide/meta.yml | 115 - modules/local/xenium_patch/stitch/main.nf | 56 - modules/local/xenium_patch/stitch/meta.yml | 104 - .../resources/usr/bin/stitch_transcripts.py | 808 ---- modules/nf-core/cellpose/Dockerfile | 25 - modules/nf-core/cellpose/cellpose.diff | 32 - modules/nf-core/cellpose/environment.yml | 9 - modules/nf-core/cellpose/main.nf | 68 - modules/nf-core/cellpose/meta.yml | 158 - modules/nf-core/cellpose/tests/main.nf.test | 63 - .../nf-core/cellpose/tests/main.nf.test.snap | 82 - .../cellpose/tests/nextflow_wflows.config | 5 - .../nf-core/{unzip => fastqc}/environment.yml | 2 +- modules/nf-core/fastqc/main.nf | 57 + modules/nf-core/fastqc/meta.yml | 111 + modules/nf-core/fastqc/tests/main.nf.test | 309 ++ .../nf-core/fastqc/tests/main.nf.test.snap | 476 +++ modules/nf-core/multiqc/environment.yml | 2 +- modules/nf-core/multiqc/main.nf | 4 +- modules/nf-core/multiqc/meta.yml | 28 +- modules/nf-core/multiqc/multiqc.diff | 23 - .../nf-core/multiqc/tests/main.nf.test.snap | 10 +- modules/nf-core/opt/flip/main.nf | 60 - modules/nf-core/opt/flip/meta.yml | 68 - modules/nf-core/opt/flip/opt-flip.diff | 48 - modules/nf-core/opt/flip/tests/main.nf.test | 61 - .../nf-core/opt/flip/tests/main.nf.test.snap | 68 - modules/nf-core/opt/stat/main.nf | 60 - modules/nf-core/opt/stat/meta.yml | 72 - modules/nf-core/opt/stat/opt-stat.diff | 44 - modules/nf-core/opt/stat/tests/main.nf.test | 63 - .../nf-core/opt/stat/tests/main.nf.test.snap | 68 - modules/nf-core/opt/track/main.nf | 59 - modules/nf-core/opt/track/meta.yml | 69 - modules/nf-core/opt/track/opt-track.diff | 44 - modules/nf-core/opt/track/tests/main.nf.test | 60 - .../nf-core/opt/track/tests/main.nf.test.snap | 68 - modules/nf-core/stardist/environment.yml | 26 - modules/nf-core/stardist/main.nf | 41 - modules/nf-core/stardist/meta.yml | 157 - modules/nf-core/stardist/stardist.diff | 13 - modules/nf-core/stardist/tests/main.nf.test | 59 - .../nf-core/stardist/tests/main.nf.test.snap | 168 - .../nf-core/stardist/tests/nextflow.config | 3 - modules/nf-core/untar/environment.yml | 12 - modules/nf-core/untar/main.nf | 80 - modules/nf-core/untar/meta.yml | 73 - modules/nf-core/untar/tests/main.nf.test | 85 - modules/nf-core/untar/tests/main.nf.test.snap | 158 - modules/nf-core/untar/untar.diff | 20 - modules/nf-core/unzip/main.nf | 50 - modules/nf-core/unzip/meta.yml | 50 - modules/nf-core/unzip/tests/main.nf.test | 54 - modules/nf-core/unzip/tests/main.nf.test.snap | 76 - modules/nf-core/unzip/unzip.diff | 18 - .../xeniumranger/import-segmentation/main.nf | 69 - .../xeniumranger/import-segmentation/meta.yml | 141 - .../import-segmentation/tests/main.nf.test | 314 -- .../tests/main.nf.test.snap | 127 - .../import-segmentation/tests/nextflow.config | 0 .../import-segmentation/tests/tags.yml | 2 - .../xeniumranger-import-segmentation.diff | 14 - modules/nf-core/xeniumranger/relabel/main.nf | 47 - modules/nf-core/xeniumranger/relabel/meta.yml | 72 - .../xeniumranger/relabel/tests/main.nf.test | 93 - .../relabel/tests/main.nf.test.snap | 66 - .../relabel/tests/nextflow.config | 0 .../xeniumranger/relabel/tests/tags.yml | 2 - .../nf-core/xeniumranger/resegment/main.nf | 54 - .../nf-core/xeniumranger/resegment/meta.yml | 69 - .../xeniumranger/resegment/tests/main.nf.test | 77 - .../resegment/tests/main.nf.test.snap | 35 - .../resegment/tests/nextflow.config | 0 .../xeniumranger/resegment/tests/tags.yml | 2 - .../resegment/xeniumranger-resegment.diff | 30 - nextflow.config | 242 +- nextflow_schema.json | 437 +-- ro-crate-metadata.json | 77 +- .../local/baysor_generate_preview/main.nf | 49 - .../local/baysor_generate_preview/meta.yml | 38 - .../local/baysor_generate_segfree/main.nf | 52 - .../local/baysor_generate_segfree/meta.yml | 32 - .../main.nf | 83 - .../meta.yml | 62 - .../baysor_run_transcripts_parquet/main.nf | 166 - .../baysor_run_transcripts_parquet/meta.yml | 58 - .../main.nf | 104 - .../main.nf | 191 - .../meta.yml | 90 - .../main.nf | 141 - .../meta.yml | 63 - .../local/ficture_preprocess_model/main.nf | 39 - .../local/ficture_preprocess_model/meta.yml | 38 - .../local/opt_flip_track_stat/main.nf | 34 - .../local/opt_flip_track_stat/meta.yml | 51 - .../local/proseg_preset_proseg2baysor/main.nf | 50 - .../proseg_preset_proseg2baysor/meta.yml | 52 - .../proseg_preset_proseg2baysor_tiled/main.nf | 86 - .../local/segger_create_train_predict/main.nf | 77 - .../segger_create_train_predict/meta.yml | 59 - .../spatialdata_write_meta_merge/main.nf | 81 - .../spatialdata_write_meta_merge/meta.yml | 54 - .../main.nf | 70 - .../utils_nfcore_spatialxe_pipeline/main.nf | 342 +- .../main.nf | 133 - .../meta.yml | 36 - .../xeniumranger_relabel_resegment/main.nf | 29 - .../xeniumranger_relabel_resegment/meta.yml | 37 - .../main.nf | 61 - .../meta.yml | 37 - .../tests/main.function.nf.test | 4 +- .../tests/main.function.nf.test.snap | 2 +- tests/.nftignore | 2 +- tests/conftest.py | 9 - tests/coordinate_mode.nf.test | 36 - tests/coordinate_mode.nf.test.snap | 115 - tests/default.nf.test | 7 +- tests/default.nf.test.snap | 115 - tests/image_mode.nf.test | 36 - tests/image_mode.nf.test.snap | 130 - tests/nextflow.config | 1 + tests/preview_mode.nf.test | 36 - tests/preview_mode.nf.test.snap | 83 - tests/segfree_mode.nf.test | 36 - tests/segfree_mode.nf.test.snap | 60 - tests/test_xenium_patch/__init__.py | 0 .../test_divide_transcripts.py | 720 ---- .../test_stitch_transcripts.py | 865 ----- workflows/spatialxe.nf | 792 +--- 281 files changed, 1939 insertions(+), 27175 deletions(-) delete mode 100644 assets/config/xenium.toml delete mode 100644 assets/example_samplesheet.csv delete mode 100755 bin/baysor_create_dataset.py delete mode 100755 bin/baysor_preprocess_transcripts.py delete mode 100755 bin/divide_transcripts.py delete mode 100755 bin/ficture_preprocess.py delete mode 100755 bin/segger_create_dataset.py delete mode 100755 bin/segger_predict.py delete mode 100755 bin/spatialdata_merge.py delete mode 100755 bin/spatialdata_meta.py delete mode 100755 bin/spatialdata_write.py delete mode 100755 bin/stitch_transcripts.py delete mode 100755 bin/utility_convert_mask_uint32.py delete mode 100755 bin/utility_downscale_morphology.py delete mode 100755 bin/utility_extract_dapi.py delete mode 100755 bin/utility_extract_data.py delete mode 100755 bin/utility_get_coordinates.py delete mode 100755 bin/utility_parquet_to_csv.py delete mode 100755 bin/utility_resize_tif.py delete mode 100755 bin/utility_segger2xr.py delete mode 100755 bin/utility_split_transcripts.py delete mode 100755 bin/utility_upscale_mask.py delete mode 100755 bin/xenium_patch_stitch_postprocess.py delete mode 100755 bin/xenium_patch_stitch_transcripts.py create mode 100644 conf/igenomes.config create mode 100644 conf/igenomes_ignored.config delete mode 100644 conf/test_coordinate_mode.config delete mode 100644 conf/test_image_mode.config delete mode 100644 conf/test_preview_mode.config delete mode 100644 conf/test_segfree_mode.config delete mode 100644 docs/images/spatialxe-logo.png delete mode 100644 docs/images/spatialxe-logo.svg delete mode 100644 docs/images/spatialxe-metromap.png delete mode 100644 docs/images/spatialxe-metromap.svg delete mode 100644 modules/local/baysor/create_dataset/main.nf delete mode 100644 modules/local/baysor/create_dataset/meta.yml delete mode 100755 modules/local/baysor/create_dataset/resources/usr/bin/create_dataset.py delete mode 100644 modules/local/baysor/create_dataset/tests/main.nf.test delete mode 100644 modules/local/baysor/create_dataset/tests/main.nf.test.snap delete mode 100644 modules/local/baysor/create_dataset/tests/nextflow.config delete mode 100644 modules/local/baysor/preprocess/main.nf delete mode 100644 modules/local/baysor/preprocess/meta.yml delete mode 100755 modules/local/baysor/preprocess/resources/usr/bin/preprocess_transcripts.py delete mode 100644 modules/local/baysor/preprocess/tests/main.nf.test delete mode 100644 modules/local/baysor/preprocess/tests/main.nf.test.snap delete mode 100644 modules/local/baysor/preprocess/tests/nextflow.config delete mode 100644 modules/local/baysor/preview/main.nf delete mode 100644 modules/local/baysor/preview/meta.yml delete mode 100644 modules/local/baysor/preview/tests/main.nf.test delete mode 100644 modules/local/baysor/preview/tests/main.nf.test.snap delete mode 100644 modules/local/baysor/preview/tests/nextflow.config delete mode 100644 modules/local/baysor/run/main.nf delete mode 100644 modules/local/baysor/run/meta.yml delete mode 100644 modules/local/baysor/run/tests/main.nf.test delete mode 100644 modules/local/baysor/run/tests/main.nf.test.snap delete mode 100644 modules/local/baysor/segfree/main.nf delete mode 100644 modules/local/baysor/segfree/meta.yml delete mode 100644 modules/local/baysor/segfree/tests/main.nf.test delete mode 100644 modules/local/baysor/segfree/tests/main.nf.test.snap delete mode 100644 modules/local/ficture/model/main.nf delete mode 100644 modules/local/ficture/model/meta.yml delete mode 100644 modules/local/ficture/preprocess/main.nf delete mode 100644 modules/local/ficture/preprocess/meta.yml delete mode 100755 modules/local/ficture/preprocess/resources/usr/bin/ficture_preprocess.py delete mode 100644 modules/local/parquet_to_csv/main.nf delete mode 100644 modules/local/parquet_to_csv/meta.yml delete mode 100644 modules/local/proseg/preset/main.nf delete mode 100644 modules/local/proseg/preset/meta.yml delete mode 100644 modules/local/proseg/preset/tests/main.nf.test delete mode 100644 modules/local/proseg/preset/tests/main.nf.test.snap delete mode 100644 modules/local/proseg/preset/tests/nextflow.config delete mode 100644 modules/local/proseg/proseg2baysor/main.nf delete mode 100644 modules/local/proseg/proseg2baysor/meta.yml delete mode 100644 modules/local/proseg/proseg2baysor/tests/main.nf.test delete mode 100644 modules/local/proseg/proseg2baysor/tests/main.nf.test.snap delete mode 100644 modules/local/proseg/proseg2baysor/tests/nextflow.config delete mode 100644 modules/local/resolift/main.nf delete mode 100644 modules/local/resolift/meta.yml delete mode 100644 modules/local/resolift/tests/main.nf.test delete mode 100644 modules/local/resolift/tests/main.nf.test.snap delete mode 100644 modules/local/resolift/tests/nextflow.config delete mode 100644 modules/local/segger/Dockerfile delete mode 100644 modules/local/segger/create_dataset/main.nf delete mode 100644 modules/local/segger/create_dataset/meta.yml delete mode 100755 modules/local/segger/create_dataset/resources/usr/bin/run_create_dataset.py delete mode 100644 modules/local/segger/predict/main.nf delete mode 100644 modules/local/segger/predict/meta.yml delete mode 100755 modules/local/segger/predict/resources/usr/bin/run_predict.py delete mode 100644 modules/local/segger/train/main.nf delete mode 100644 modules/local/segger/train/meta.yml delete mode 100644 modules/local/spatialdata/merge/main.nf delete mode 100644 modules/local/spatialdata/merge/meta.yml delete mode 100755 modules/local/spatialdata/merge/resources/usr/bin/spatialdata_merge.py delete mode 100644 modules/local/spatialdata/meta/main.nf delete mode 100644 modules/local/spatialdata/meta/meta.yml delete mode 100755 modules/local/spatialdata/meta/resources/usr/bin/spatialdata_meta.py delete mode 100644 modules/local/spatialdata/write/main.nf delete mode 100644 modules/local/spatialdata/write/meta.yml delete mode 100755 modules/local/spatialdata/write/resources/usr/bin/spatialdata_write.py delete mode 100644 modules/local/utility/convert_mask_uint32/main.nf delete mode 100644 modules/local/utility/convert_mask_uint32/meta.yml delete mode 100644 modules/local/utility/downscale_morphology/main.nf delete mode 100644 modules/local/utility/downscale_morphology/meta.yml delete mode 100644 modules/local/utility/extract_dapi/main.nf delete mode 100644 modules/local/utility/extract_dapi/meta.yml delete mode 100644 modules/local/utility/extract_preview_data/main.nf delete mode 100644 modules/local/utility/extract_preview_data/meta.yml delete mode 100755 modules/local/utility/extract_preview_data/resources/usr/bin/extract_data.py delete mode 100644 modules/local/utility/get_coordinates/main.nf delete mode 100644 modules/local/utility/get_coordinates/meta.yml delete mode 100755 modules/local/utility/get_coordinates/resources/usr/bin/get_coordinates.py delete mode 100644 modules/local/utility/parquet_to_csv/main.nf delete mode 100644 modules/local/utility/parquet_to_csv/meta.yml delete mode 100755 modules/local/utility/parquet_to_csv/resources/usr/bin/parquet_to_csv.py delete mode 100644 modules/local/utility/reconstruct_patches/main.nf delete mode 100644 modules/local/utility/reconstruct_patches/meta.yml delete mode 100644 modules/local/utility/resize_tif/main.nf delete mode 100644 modules/local/utility/resize_tif/meta.yml delete mode 100755 modules/local/utility/resize_tif/resources/usr/bin/resize_tif.py delete mode 100644 modules/local/utility/segger2xr/main.nf delete mode 100644 modules/local/utility/segger2xr/meta.yml delete mode 100755 modules/local/utility/segger2xr/resources/usr/bin/segger2xr.py delete mode 100644 modules/local/utility/split_transcripts/main.nf delete mode 100644 modules/local/utility/split_transcripts/meta.yml delete mode 100755 modules/local/utility/split_transcripts/resources/usr/bin/split_transcripts.py delete mode 100644 modules/local/utility/upscale_mask/main.nf delete mode 100644 modules/local/utility/upscale_mask/meta.yml delete mode 100644 modules/local/xenium_patch/divide/main.nf delete mode 100644 modules/local/xenium_patch/divide/meta.yml delete mode 100644 modules/local/xenium_patch/stitch/main.nf delete mode 100644 modules/local/xenium_patch/stitch/meta.yml delete mode 100755 modules/local/xenium_patch/stitch/resources/usr/bin/stitch_transcripts.py delete mode 100644 modules/nf-core/cellpose/Dockerfile delete mode 100644 modules/nf-core/cellpose/cellpose.diff delete mode 100644 modules/nf-core/cellpose/environment.yml delete mode 100644 modules/nf-core/cellpose/main.nf delete mode 100644 modules/nf-core/cellpose/meta.yml delete mode 100644 modules/nf-core/cellpose/tests/main.nf.test delete mode 100644 modules/nf-core/cellpose/tests/main.nf.test.snap delete mode 100644 modules/nf-core/cellpose/tests/nextflow_wflows.config rename modules/nf-core/{unzip => fastqc}/environment.yml (85%) create mode 100644 modules/nf-core/fastqc/main.nf create mode 100644 modules/nf-core/fastqc/meta.yml create mode 100644 modules/nf-core/fastqc/tests/main.nf.test create mode 100644 modules/nf-core/fastqc/tests/main.nf.test.snap delete mode 100644 modules/nf-core/multiqc/multiqc.diff delete mode 100644 modules/nf-core/opt/flip/main.nf delete mode 100644 modules/nf-core/opt/flip/meta.yml delete mode 100644 modules/nf-core/opt/flip/opt-flip.diff delete mode 100644 modules/nf-core/opt/flip/tests/main.nf.test delete mode 100644 modules/nf-core/opt/flip/tests/main.nf.test.snap delete mode 100644 modules/nf-core/opt/stat/main.nf delete mode 100644 modules/nf-core/opt/stat/meta.yml delete mode 100644 modules/nf-core/opt/stat/opt-stat.diff delete mode 100644 modules/nf-core/opt/stat/tests/main.nf.test delete mode 100644 modules/nf-core/opt/stat/tests/main.nf.test.snap delete mode 100644 modules/nf-core/opt/track/main.nf delete mode 100644 modules/nf-core/opt/track/meta.yml delete mode 100644 modules/nf-core/opt/track/opt-track.diff delete mode 100644 modules/nf-core/opt/track/tests/main.nf.test delete mode 100644 modules/nf-core/opt/track/tests/main.nf.test.snap delete mode 100644 modules/nf-core/stardist/environment.yml delete mode 100644 modules/nf-core/stardist/main.nf delete mode 100644 modules/nf-core/stardist/meta.yml delete mode 100644 modules/nf-core/stardist/stardist.diff delete mode 100644 modules/nf-core/stardist/tests/main.nf.test delete mode 100644 modules/nf-core/stardist/tests/main.nf.test.snap delete mode 100644 modules/nf-core/stardist/tests/nextflow.config delete mode 100644 modules/nf-core/untar/environment.yml delete mode 100644 modules/nf-core/untar/main.nf delete mode 100644 modules/nf-core/untar/meta.yml delete mode 100644 modules/nf-core/untar/tests/main.nf.test delete mode 100644 modules/nf-core/untar/tests/main.nf.test.snap delete mode 100644 modules/nf-core/untar/untar.diff delete mode 100644 modules/nf-core/unzip/main.nf delete mode 100644 modules/nf-core/unzip/meta.yml delete mode 100644 modules/nf-core/unzip/tests/main.nf.test delete mode 100644 modules/nf-core/unzip/tests/main.nf.test.snap delete mode 100644 modules/nf-core/unzip/unzip.diff delete mode 100644 modules/nf-core/xeniumranger/import-segmentation/main.nf delete mode 100644 modules/nf-core/xeniumranger/import-segmentation/meta.yml delete mode 100644 modules/nf-core/xeniumranger/import-segmentation/tests/main.nf.test delete mode 100644 modules/nf-core/xeniumranger/import-segmentation/tests/main.nf.test.snap delete mode 100644 modules/nf-core/xeniumranger/import-segmentation/tests/nextflow.config delete mode 100644 modules/nf-core/xeniumranger/import-segmentation/tests/tags.yml delete mode 100644 modules/nf-core/xeniumranger/import-segmentation/xeniumranger-import-segmentation.diff delete mode 100644 modules/nf-core/xeniumranger/relabel/main.nf delete mode 100644 modules/nf-core/xeniumranger/relabel/meta.yml delete mode 100644 modules/nf-core/xeniumranger/relabel/tests/main.nf.test delete mode 100644 modules/nf-core/xeniumranger/relabel/tests/main.nf.test.snap delete mode 100644 modules/nf-core/xeniumranger/relabel/tests/nextflow.config delete mode 100644 modules/nf-core/xeniumranger/relabel/tests/tags.yml delete mode 100644 modules/nf-core/xeniumranger/resegment/main.nf delete mode 100644 modules/nf-core/xeniumranger/resegment/meta.yml delete mode 100644 modules/nf-core/xeniumranger/resegment/tests/main.nf.test delete mode 100644 modules/nf-core/xeniumranger/resegment/tests/main.nf.test.snap delete mode 100644 modules/nf-core/xeniumranger/resegment/tests/nextflow.config delete mode 100644 modules/nf-core/xeniumranger/resegment/tests/tags.yml delete mode 100644 modules/nf-core/xeniumranger/resegment/xeniumranger-resegment.diff delete mode 100644 subworkflows/local/baysor_generate_preview/main.nf delete mode 100644 subworkflows/local/baysor_generate_preview/meta.yml delete mode 100644 subworkflows/local/baysor_generate_segfree/main.nf delete mode 100644 subworkflows/local/baysor_generate_segfree/meta.yml delete mode 100644 subworkflows/local/baysor_run_prior_segmentation_mask/main.nf delete mode 100644 subworkflows/local/baysor_run_prior_segmentation_mask/meta.yml delete mode 100644 subworkflows/local/baysor_run_transcripts_parquet/main.nf delete mode 100644 subworkflows/local/baysor_run_transcripts_parquet/meta.yml delete mode 100644 subworkflows/local/baysor_run_transcripts_parquet_tiled/main.nf delete mode 100644 subworkflows/local/cellpose_baysor_import_segmentation/main.nf delete mode 100644 subworkflows/local/cellpose_baysor_import_segmentation/meta.yml delete mode 100644 subworkflows/local/cellpose_resolift_morphology_ome_tif/main.nf delete mode 100644 subworkflows/local/cellpose_resolift_morphology_ome_tif/meta.yml delete mode 100644 subworkflows/local/ficture_preprocess_model/main.nf delete mode 100644 subworkflows/local/ficture_preprocess_model/meta.yml delete mode 100644 subworkflows/local/opt_flip_track_stat/main.nf delete mode 100644 subworkflows/local/opt_flip_track_stat/meta.yml delete mode 100644 subworkflows/local/proseg_preset_proseg2baysor/main.nf delete mode 100644 subworkflows/local/proseg_preset_proseg2baysor/meta.yml delete mode 100644 subworkflows/local/proseg_preset_proseg2baysor_tiled/main.nf delete mode 100644 subworkflows/local/segger_create_train_predict/main.nf delete mode 100644 subworkflows/local/segger_create_train_predict/meta.yml delete mode 100644 subworkflows/local/spatialdata_write_meta_merge/main.nf delete mode 100644 subworkflows/local/spatialdata_write_meta_merge/meta.yml delete mode 100644 subworkflows/local/stardist_resolift_morphology_ome_tif/main.nf delete mode 100644 subworkflows/local/xeniumranger_import_segmentation_redefine_bundle/main.nf delete mode 100644 subworkflows/local/xeniumranger_import_segmentation_redefine_bundle/meta.yml delete mode 100644 subworkflows/local/xeniumranger_relabel_resegment/main.nf delete mode 100644 subworkflows/local/xeniumranger_relabel_resegment/meta.yml delete mode 100644 subworkflows/local/xeniumranger_resegment_morphology_ome_tif/main.nf delete mode 100644 subworkflows/local/xeniumranger_resegment_morphology_ome_tif/meta.yml delete mode 100644 tests/conftest.py delete mode 100644 tests/coordinate_mode.nf.test delete mode 100644 tests/coordinate_mode.nf.test.snap delete mode 100644 tests/default.nf.test.snap delete mode 100644 tests/image_mode.nf.test delete mode 100644 tests/image_mode.nf.test.snap delete mode 100644 tests/preview_mode.nf.test delete mode 100644 tests/preview_mode.nf.test.snap delete mode 100644 tests/segfree_mode.nf.test delete mode 100644 tests/segfree_mode.nf.test.snap delete mode 100644 tests/test_xenium_patch/__init__.py delete mode 100644 tests/test_xenium_patch/test_divide_transcripts.py delete mode 100644 tests/test_xenium_patch/test_stitch_transcripts.py diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ba3d4540..6a378da4 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -8,14 +8,14 @@ These are the most common things requested on pull requests (PRs). Remember that PRs should be made against the dev branch, unless you're preparing a pipeline release. -Learn more about contributing: [CONTRIBUTING.md](https://github.com/nf-core/spatialxe/tree/main/.github/CONTRIBUTING.md) +Learn more about contributing: [CONTRIBUTING.md](https://github.com/nf-core/spatialxe/tree/master/docs/CONTRIBUTING.md) --> ## PR checklist - [ ] This comment contains a description of changes (with reason). - [ ] If you've fixed a bug or added code that should be tested, add tests! -- [ ] If you've added a new tool - have you followed the pipeline conventions in the [contribution docs](https://github.com/nf-core/spatialxe/tree/main/.github/CONTRIBUTING.md) +- [ ] If you've added a new tool - have you followed the pipeline conventions in the [contribution docs](https://github.com/nf-core/spatialxe/tree/master/docs/CONTRIBUTING.md) - [ ] If necessary, also make a PR on the nf-core/spatialxe _branch_ on the [nf-core/test-datasets](https://github.com/nf-core/test-datasets) repository. - [ ] Make sure your code lints (`nf-core pipelines lint`). - [ ] Ensure the test suite passes (`nextflow run . -profile test,docker --outdir `). diff --git a/.github/workflows/awsfulltest.yml b/.github/workflows/awsfulltest.yml index 2f6822e4..5eb53aee 100644 --- a/.github/workflows/awsfulltest.yml +++ b/.github/workflows/awsfulltest.yml @@ -23,7 +23,8 @@ jobs: echo "revision=${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'release') && github.sha || 'dev' }}" >> "$GITHUB_OUTPUT" - name: Launch workflow via Seqera Platform - uses: seqeralabs/action-tower-launch@v2 + uses: seqeralabs/action-tower-launch@51565b514bff1827cf34620de25d0055759f1fc9 # v2 + # TODO nf-core: You can customise AWS full pipeline tests as required # Add full size test data (but still relatively small datasets for few samples) # on the `test_full.config` test runs with only one set of parameters with: @@ -32,14 +33,33 @@ jobs: compute_env: ${{ vars.TOWER_COMPUTE_ENV }} revision: ${{ steps.revision.outputs.revision }} workdir: s3://${{ vars.AWS_S3_BUCKET }}/work/spatialxe/work-${{ steps.revision.outputs.revision }} + nextflow_config: | + plugins { + id 'nf-slack@0.5.0' + } + slack { + enabled = true + bot { + token = '${{ secrets.NFSLACK_BOT_TOKEN }}' + channel = 'spatialxe' + } + onStart { + enabled = false + } + onComplete { + message = ':white_check_mark: *spatialxe/test_full* completed successfully! :tada:' + } + onError { + message = ':x: *spatialxe/test_full* failed :crying_cat_face:' + } + } parameters: | { - "hook_url": "${{ secrets.MEGATESTS_ALERTS_SLACK_HOOK_URL }}", "outdir": "s3://${{ vars.AWS_S3_BUCKET }}/spatialxe/results-${{ steps.revision.outputs.revision }}" } profiles: test_full - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: Seqera Platform debug log file path: | diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 7a527a34..8738ffc9 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -11,33 +11,31 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Set up Python 3.14 - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 - with: - python-version: "3.14" - - - name: Install pre-commit - run: pip install pre-commit + - name: Install Nextflow + uses: nf-core/setup-nextflow@b4ec1bc7c16a94435159de94a05253542fddf6ef # v3 - - name: Run pre-commit - run: pre-commit run --all-files + - name: Run prek + uses: j178/prek-action@6ad80277337ad479fe43bd70701c3f7f8aa74db3 # v2 nf-core: runs-on: ubuntu-latest steps: - name: Check out pipeline code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install Nextflow - uses: nf-core/setup-nextflow@v2 + uses: nf-core/setup-nextflow@b4ec1bc7c16a94435159de94a05253542fddf6ef # v3 - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: "3.14" architecture: "x64" + - name: Setup uv + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + - name: read .nf-core.yml uses: pietrobolcato/action-read-yaml@9f13718d61111b69f30ab4ac683e67a56d254e1d # 1.1.0 id: read_yml @@ -45,12 +43,10 @@ jobs: config: ${{ github.workspace }}/.nf-core.yml - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install nf-core==${{ steps.read_yml.outputs['nf_core_version'] }} + run: uv tool install nf-core==${{ steps.read_yml.outputs['nf_core_version'] }} - name: Run nf-core pipelines lint - if: ${{ github.base_ref != 'master' }} + if: ${{ github.base_ref != 'master' || github.base_ref != 'main' }} env: GITHUB_COMMENTS_URL: ${{ github.event.pull_request.comments_url }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -58,7 +54,7 @@ jobs: run: nf-core -l lint_log.txt pipelines lint --dir ${GITHUB_WORKSPACE} --markdown lint_results.md - name: Run nf-core pipelines lint --release - if: ${{ github.base_ref == 'master' }} + if: ${{ github.base_ref == 'master' || github.base_ref == 'main' }} env: GITHUB_COMMENTS_URL: ${{ github.event.pull_request.comments_url }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -71,7 +67,7 @@ jobs: - name: Upload linting log file artifact if: ${{ always() }} - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: linting-logs path: | diff --git a/.github/workflows/nf-test.yml b/.github/workflows/nf-test.yml index c38f3d01..efd72d65 100644 --- a/.github/workflows/nf-test.yml +++ b/.github/workflows/nf-test.yml @@ -50,7 +50,7 @@ jobs: env: NFT_VER: ${{ env.NFT_VER }} with: - max_shards: 12 + max_shards: 7 - name: debug run: | @@ -64,7 +64,6 @@ jobs: runs-on: # use self-hosted runners - runs-on=${{ github.run_id }}-nf-test - runner=4cpu-linux-x64 - - disk=large strategy: fail-fast: false matrix: @@ -72,14 +71,12 @@ jobs: profile: [conda, docker, singularity] isMain: - ${{ github.base_ref == 'master' || github.base_ref == 'main' }} - # Exclude conda and singularity on dev; conda disabled on all branches + # Exclude conda and singularity on dev exclude: - isMain: false profile: "conda" - isMain: false profile: "singularity" - - isMain: true - profile: "conda" NXF_VER: - "25.10.4" - "latest-everything" diff --git a/.github/workflows/release-announcements.yml b/.github/workflows/release-announcements.yml index 431d3d44..78d5dbe0 100644 --- a/.github/workflows/release-announcements.yml +++ b/.github/workflows/release-announcements.yml @@ -18,7 +18,7 @@ jobs: id: get_description run: | echo "description=$(curl -s https://nf-co.re/pipelines.json | jq -r '.remote_workflows[] | select(.full_name == "${{ github.repository }}") | .description')" >> $GITHUB_OUTPUT - - uses: rzr/fediverse-action@master + - uses: rzr/fediverse-action@563159eb8d45f70ab6aaba36ed55cd037e51f441 # master with: access-token: ${{ secrets.MASTODON_ACCESS_TOKEN }} host: "mstdn.science" # custom host if not "mastodon.social" (default) @@ -34,7 +34,7 @@ jobs: bsky-post: runs-on: ubuntu-latest steps: - - uses: zentered/bluesky-post-action@6461056ea355ea43b977e149f7bf76aaa572e5e8 # v0.3.0 + - uses: zentered/bluesky-post-action@5a91cc2ad10a304a4e96c16182dbe4918710bcf6 # v0.4.0 with: post: | Pipeline release! ${{ github.repository }} v${{ github.event.release.tag_name }} - ${{ github.event.release.name }}! diff --git a/.gitignore b/.gitignore index 2ef7dde1..cc2b1a77 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,4 @@ testing/ testing* *.pyc null/ -.nf-test/ -.nf-test.log -.nf-test-* +.lineage/ diff --git a/.nf-core.yml b/.nf-core.yml index 08a86cf0..ae563bd5 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -1,5 +1,4 @@ lint: - actions_ci: false files_exist: - .github/workflows/awsfulltest.yml - .github/workflows/awstest.yml @@ -9,16 +8,14 @@ lint: - docs/images/nf-core-spatialxe_logo_dark.png - docs/images/nf-core-spatialxe_logo_light.png - .github/PULL_REQUEST_TEMPLATE.md -nf_core_version: 3.5.2 +nf_core_version: 4.0.2 repository_type: pipeline template: - author: Sameesh Kher, Dongze He, Florian Heyl + author: Sameesh Kher, Florian Heyl description: A pipeline for spatialomics Xenium In Situ data. force: false is_nfcore: true name: spatialxe org: nf-core outdir: . - skip_features: - - igenomes version: 1.0.0 diff --git a/CITATIONS.md b/CITATIONS.md index 542e6e38..e8c14658 100644 --- a/CITATIONS.md +++ b/CITATIONS.md @@ -10,6 +10,10 @@ ## Pipeline tools +- [FastQC](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/) + +> Andrews, S. (2010). FastQC: A Quality Control Tool for High Throughput Sequence Data [Online]. + - [MultiQC](https://pubmed.ncbi.nlm.nih.gov/27312411/) > Ewels P, Magnusson M, Lundin S, Käller M. MultiQC: summarize analysis results for multiple tools and samples in a single report. Bioinformatics. 2016 Oct 1;32(19):3047-8. doi: 10.1093/bioinformatics/btw354. Epub 2016 Jun 16. PubMed PMID: 27312411; PubMed Central PMCID: PMC5039924. diff --git a/README.md b/README.md index 6eafaeef..f3f2cbde 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,14 @@ -[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new/nf-core/spatialxe) +[![Open in GitHub Codespaces](https://img.shields.io/badge/Open_In_GitHub_Codespaces-black?labelColor=grey&logo=github)](https://github.com/codespaces/new/nf-core/spatialxe) [![GitHub Actions CI Status](https://github.com/nf-core/spatialxe/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/spatialxe/actions/workflows/nf-test.yml) [![GitHub Actions Linting Status](https://github.com/nf-core/spatialxe/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/spatialxe/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/spatialxe/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX) [![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com) -[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.04.0-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/) -[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.4.1-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.4.1) +[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.10.4-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/) +[![nf-core template version](https://img.shields.io/badge/nf--core_template-4.0.2-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/4.0.2) +[![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/) [![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/) [![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/) [![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://cloud.seqera.io/launch?pipeline=https://github.com/nf-core/spatialxe) @@ -20,115 +21,52 @@ ## Introduction -**nf-core/spatialxe** is a bioinformatics best-practice processing and quality control pipeline for Xenium data. The current plan for the pipeline implementation is shown in the metromap below. **The pipeline is under active developement and changes might occure frequently**. +**nf-core/spatialxe** is a bioinformatics pipeline that ... -![nf-core/spatialxe-metromap](docs/images/spatialxe-metromap.png) + -> [!NOTE] -> We are currently testing the pipeline for the [10x Atera system](https://www.10xgenomics.com/platforms/atera). - -## Tools supported - -The pipeline supports the following tools: - -- Segmenation methods: - - [Baysor](https://doi.org/10.1038/s41587-021-01044-w) - - [Cellpose](https://doi.org/10.1038/s41592-020-01018-x) - - [Xenium ranger (XR)](https://www.10xgenomics.com/support/software/xenium-ranger/latest) - - [StarDist](https://doi.org/10.48550/arXiv.2203.02284) -- Segmentation free methods: - - [Ficture](https://doi.org/10.1038/s41592-024-02415-2) - - [Baysor](https://doi.org/10.1038/s41587-021-01044-w) -- Transcript assignment methods: - - [Segger](https://doi.org/10.1101/2025.03.14.643160) - - [Proseg](https://doi.org/10.1038/s41592-025-02697-0) -- Utility methods: - - [SpatialData](https://doi.org/10.1038/s41592-024-02212-x) - - [Baysor](https://doi.org/10.1038/s41587-021-01044-w) -- QC methods: - - [MultiQC Xenium Extra Plugin](https://github.com/MultiQC/xenium-extra) - - [OPT](https://github.com/JEFworks-Lab/off-target-probe-tracker) + +1. Read QC ([`FastQC`](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/))2. Present QC for raw reads ([`MultiQC`](http://multiqc.info/)) ## Usage -On release, automated continuous integration tests run the pipeline on a full-sized dataset on the AWS cloud infrastructure. This ensures that the pipeline runs on AWS, has sensible resource allocation defaults set to run on real-world datasets, and permits the persistent storage of results to benchmark between pipeline releases and other analysis sources. The results obtained from the full-sized test can be viewed on the [nf-core website](https://nf-co.re/spatialxe/results). - > [!NOTE] -> The pipeline does not support conda currently. We are working on it. +> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/get_started/environment_setup/overview) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/get_started/run-your-first-pipeline) with `-profile test` before running the workflow on actual data. + + -```bash -nextflow run nf-core/spatialxe \ - -profile \ - --input samplesheet.csv \ - --outdir \ - --mode preview -``` +Now, you can run the pipeline using: -### Run just the quality control
+ ```bash nextflow run nf-core/spatialxe \ -profile \ --input samplesheet.csv \ - --outdir \ - --mode qc + --outdir ``` -### Additional information - > [!WARNING] -> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/usage/getting_started/configuration#custom-configuration-files). +> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/running/run-pipelines#using-parameter-files). For more details and further functionality, please refer to the [usage documentation](https://nf-co.re/spatialxe/usage) and the [parameter documentation](https://nf-co.re/spatialxe/parameters). @@ -138,51 +76,27 @@ To see the results of an example test run with a full size dataset refer to the For more details about the output files and reports, please refer to the [output documentation](https://nf-co.re/spatialxe/output). -## Runtime and resource estimations - -| Tool | Compute | Runtime (min / med / max) | Peak RSS (min / med / max) | -| ------------------------- | ------- | ------------------------- | -------------------------- | -| Cellpose | GPU | 1m / 4m / 1.4h | 10 GB / 26 GB / 554 GB | -| Cellpose | CPU | 1.3h / 2.3h / 6.5h | 161 GB / 426 GB / 1115 GB | -| StarDist | GPU | 1m / 4m / 7m | 5 GB / 12 GB / 18 GB | -| StarDist | CPU | 5m / 6m / 7m | 18 GB / 18 GB / 18 GB | -| Segger (create_dataset) | GPU | 2m / 9m / 31m | 1.7 GB / 14 GB / 50 GB | -| Segger (create_dataset) | CPU | 13m / 21m / 46m | 13 GB / 19 GB / 49 GB | -| Segger (train) | GPU | 10m / 43m / 2.9h | 30 GB / 33 GB / 60 GB | -| Segger (predict) | GPU | 2m / 16m / 59m | 10 GB / 25 GB / 87 GB | -| Baysor (whole-image) | CPU | 2m / 30m / 17h | 6 GB / 10 GB / 650 GB | -| Baysor (tiled) | CPU | 1m / 18m / 13h | 0.2 GB / 34 GB / 530 GB | -| Proseg | CPU | 1m / 18m / 6.8h | 279 MB / 3.8 GB / 136 GB | -| XeniumRanger (resegment) | CPU | 18m / 39m / 3.7h | 28 GB / 54 GB / 60 GB | -| XeniumRanger (import_seg) | CPU | 2m / 7m / 2.7h | 2.6 GB / 11 GB / 51 GB | -| Ficture (preprocess) | CPU | 3m / 4m / 13m | 331 MB / 357 MB / 21 GB | - -- Cellpose GPU vs CPU: 35x faster on GPU (4m median vs 2.3h), 16x less memory (26 GB vs 426 GB) -- Segger: Only tool that truly requires GPU for all 3 steps (create_dataset, train, predict) -- StarDist: Very fast on CPU, GPU is not necessary to run its default model - ## Credits -nf-core/spatialxe is mainly developed by [Sameesh Kher](https://github.com/khersameesh24), [Dongze He](https://github.com/dongzehe), and [Florian Heyl](https://github.com/heylf). +nf-core/spatialxe was originally written by Sameesh Kher, Florian Heyl. We thank the following people for their extensive assistance in the development of this pipeline: -- Tobias Krause -- Krešimir Beštak (kbestak) -- Matthias Hörtenhuber (mashehu) -- Maxime Garcia (maxulysse) -- Kübra Narcı (kubranarci) + ## Contributions and Support -If you would like to contribute to this pipeline, please see the [contributing guidelines](.github/CONTRIBUTING.md). +If you would like to contribute to this pipeline, please see the [contributing guidelines](docs/CONTRIBUTING.md). For further information or help, don't hesitate to get in touch on the [Slack `#spatialxe` channel](https://nfcore.slack.com/channels/spatialxe) (you can join with [this invite](https://nf-co.re/join/slack)). ## Citations + + + An extensive list of references for the tools used by the pipeline can be found in the [`CITATIONS.md`](CITATIONS.md) file. You can cite the `nf-core` publication as follows: diff --git a/assets/config/xenium.toml b/assets/config/xenium.toml deleted file mode 100644 index c7740146..00000000 --- a/assets/config/xenium.toml +++ /dev/null @@ -1,15 +0,0 @@ -[data] -x = "x_location" -y = "y_location" -z = "z_location" -gene = "feature_name" -min_molecules_per_gene = 10 -exclude_genes = "NegControl*,BLANK_*,antisense_*" -min_molecules_per_cell = 50 - -[segmentation] -unassigned_prior_label = "UNASSIGNED" -prior_segmentation_confidence = 0.5 - -[plotting] -min_pixels_per_cell = 10 diff --git a/assets/email_template.html b/assets/email_template.html index 35fd954c..819a2f90 100644 --- a/assets/email_template.html +++ b/assets/email_template.html @@ -4,7 +4,7 @@ - + nf-core/spatialxe Pipeline Report diff --git a/assets/example_samplesheet.csv b/assets/example_samplesheet.csv deleted file mode 100644 index 9cc36cf4..00000000 --- a/assets/example_samplesheet.csv +++ /dev/null @@ -1,2 +0,0 @@ -sample,bundle,image -xenium_prime_mouse_ileum,/home/user/raw_data/xenium/Xenium_Prime_Mouse_Ileum_tiny_outs,/home/user/raw_data/xenium/Xenium_Prime_Mouse_Ileum_tiny_outs/morphology.ome.tif diff --git a/assets/methods_description_template.yml b/assets/methods_description_template.yml index c1fbf537..1ac4277b 100644 --- a/assets/methods_description_template.yml +++ b/assets/methods_description_template.yml @@ -3,6 +3,7 @@ description: "Suggested text and references to use when describing pipeline usag section_name: "nf-core/spatialxe Methods Description" section_href: "https://github.com/nf-core/spatialxe" plot_type: "html" +## TODO nf-core: Update the HTML below to your preferred methods description, e.g. add publication citation for this pipeline ## You inject any metadata in the Nextflow '${workflow}' object data: |

Methods

diff --git a/assets/multiqc_config.yml b/assets/multiqc_config.yml index f0862f78..a11d3d7f 100644 --- a/assets/multiqc_config.yml +++ b/assets/multiqc_config.yml @@ -1,5 +1,7 @@ report_comment: > - This report has been generated by the nf-core/spatialxe analysis pipeline. For information about how to interpret these results, please see the documentation. + This report has been generated by the nf-core/spatialxe + analysis pipeline. For information about how to interpret these results, please see the + documentation. report_section_order: "nf-core-spatialxe-methods-description": order: -1000 @@ -11,33 +13,3 @@ report_section_order: export_plots: true disable_version_detection: true - -run_module: - - xenium - -module_order: - - xenium - -sp: - cell_feature_matrix: - fn: cell_feature_matrix.h5 - cells: - fn: cells.parquet - experiment: - fn: experiment.xenium - num_lines: 50 - metrics: - contents: num_cells_detected - fn: metrics_summary.csv - num_lines: 5 - transcripts: - fn: transcripts.parquet - -custom_data: - focus_density_plot: - pconfig: - title: "Focus Score: Per Sequence Density" - xlab: "CCFS Focus Score" - ylab: "Density" - ymin: 0 - logswitch: false diff --git a/assets/nf-core-spatialxe_logo_light.png b/assets/nf-core-spatialxe_logo_light.png index f74a46fb5a8871e01569ed23cbd26db1e9a21425..8a8ca75bbb8c67fe1521e272cbd6ab71fce03182 100644 GIT binary patch literal 80727 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 Zr0LVfWv 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
gijG40X;L3Fo^?@ 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 literal 78352 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

with id {h1_id} found") - continue - - # Look for the first after the h1 in the DOM - img_tag = h1_tag.find_next("img") - if not img_tag or not img_tag.get("src"): - print(f"[WARN] No found after h1#{h1_id}") - continue - - img_src = img_tag["src"] - if img_src.startswith("data:image/png;base64,"): - base64_data = img_src.split(",", 1)[1] - data = base64.b64decode(base64_data) - else: - print(f"[WARN] img src is not base64 PNG for h1#{h1_id}") - continue - - # save png files with _mqc suffix for MultiQC integration - img_name = f"{h1_id}_mqc.png".lower() - out_path = outdir / img_name - with open(out_path, "wb") as f: - f.write(data) - - print(f"[INFO] Saved {img_name}") - - return None - - -def extract_js_object(text: str, start_idx: int) -> Tuple[Optional[str], int]: - """Extract json-like object starting at start_idx.""" - if start_idx >= len(text) or text[start_idx] != "{": - return None, start_idx - - stack, in_str, escape, quote = [], False, False, None - for i in range(start_idx, len(text)): - ch = text[i] - if in_str: - if escape: - escape = False - elif ch == "\\": - escape = True - elif ch == quote: - in_str = False - else: - if ch in ('"', "'"): - in_str, quote = True, ch - elif ch == "{": - stack.append("{") - elif ch == "}": - stack.pop() - if not stack: - return text[start_idx : i + 1], i + 1 - elif ch == "/" and i + 1 < len(text): - # skip js comments - nxt = text[i + 1] - if nxt == "/": - end = text.find("\n", i + 2) - i = len(text) - 1 if end == -1 else end - elif nxt == "*": - end = text.find("*/", i + 2) - if end == -1: - break - i = end + 1 - - return None, start_idx - - -def js_to_json(js: str) -> str: - """Convert a JS object string to valid JSON.""" - # Remove comments - js = re.sub(r"/\*.*?\*/", "", js, flags=re.S) - js = re.sub(r"//[^\n]*", "", js) - - # Convert single-quoted strings to double-quoted strings - js = re.sub( - r"'((?:\\.|[^'\\])*)'", - lambda m: '"' + m.group(1).replace('"', '\\"') + '"', - js, - ) - - # Remove trailing commas - js = re.sub(r",\s*(?=[}\]])", "", js) - js = re.sub(r",\s*,+", ",", js) - - return js.strip() - - -def find_variables(script_text: str) -> Dict[str, str]: - """Find all 'var|let|const specN =' declarations and extract their objects.""" - specs: Dict[str, str] = {} - script_text = html.unescape(script_text) - pattern = re.compile(r"(?:var|let|const)\s+(spec\d+)\s*=\s*{", re.I) - - for match in pattern.finditer(script_text): - var = match.group(1) - obj, _ = extract_js_object(script_text, match.end() - 1) - if obj: - specs[var] = obj - else: - print(f"[WARN] Could not extract object for {var}") - return specs - - -def write_tsvs(specs: Dict[str, str], outdir: Path) -> List[Path]: - """Convert extracted json to tsv.""" - outdir.mkdir(parents=True, exist_ok=True) - written: List[Path] = [] - - for var, js_obj in specs.items(): - try: - data = json.loads(js_to_json(js_obj)) - values = data.get("data", {}).get("values", []) - if not values: - print(f"[WARN] No data.values found in {var}") - continue - - df = pd.DataFrame(values) - outpath = outdir / f"{var}_mqc.tsv" - - with open(outpath, "w") as f: - f.write("# plot_type: linegraph\n") - f.write(f"# section_name: {var}\n") - f.write("# description: Extracted preview data\n") - df.to_csv(f, sep="\t", index=False) - - written.append(outpath) - print(f"[INFO] Wrote {outpath} ({len(df)} rows × {len(df.columns)} cols)") - except Exception as e: - print(f"[ERROR] Failed to process {var}: {e}") - - return written - - -def parse_args() -> argparse.Namespace: - """Parse command-line arguments.""" - parser = argparse.ArgumentParser( - description="Extract preview data from Baysor preview HTML reports." - ) - parser.add_argument( - "--preview-html", - required=True, - help="Path to Baysor preview HTML file", - ) - parser.add_argument( - "--prefix", - required=True, - help="Output directory prefix (sample ID)", - ) - return parser.parse_args() - - -if __name__ == "__main__": - args = parse_args() - - input_path: Path = Path(args.preview_html) - outdir: Path = Path(args.prefix) - - text = input_path.read_text(encoding="utf-8", errors="ignore") - soup = BeautifulSoup(text, "html.parser") - - # get the script section - if " argparse.Namespace: - """Parse command-line arguments.""" - parser = argparse.ArgumentParser( - description="Get transcript coordinate bounds from a Parquet file." - ) - parser.add_argument( - "--transcripts", - required=True, - help="Path to transcripts parquet file", - ) - return parser.parse_args() - - -if __name__ == "__main__": - args = parse_args() - result = get_coordinates(args.transcripts) - print(",".join(str(v) for v in result)) diff --git a/bin/utility_parquet_to_csv.py b/bin/utility_parquet_to_csv.py deleted file mode 100755 index bfa19c40..00000000 --- a/bin/utility_parquet_to_csv.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python3 -""" -Convert a Parquet file to CSV format. - -Reads a Parquet file and writes it as CSV, optionally gzip-compressed. -""" - -import argparse -from pathlib import Path - -import pandas as pd - - -def convert_parquet( - transcripts: str, - extension: str = ".csv", - prefix: str = "", -) -> None: - """ - Convert a Parquet file to CSV or CSV.GZ format. - - Args: - transcripts: Filename of the input parquet file - extension: Output extension ('.csv' or '.gz' for gzip) - prefix: Output directory prefix - """ - df = pd.read_parquet(transcripts, engine="pyarrow") - - Path(prefix).mkdir(parents=True, exist_ok=True) - - if extension == ".gz": - output = transcripts.replace(".parquet", ".csv.gz") - df.to_csv(f"{prefix}/{output}", compression="gzip", index=False) - else: - output = transcripts.replace(".parquet", ".csv") - df.to_csv(f"{prefix}/{output}", index=False) - - return None - - -def parse_args() -> argparse.Namespace: - """Parse command-line arguments.""" - parser = argparse.ArgumentParser( - description="Convert a Parquet file to CSV format." - ) - parser.add_argument( - "--transcripts", - required=True, - help="Input parquet filename", - ) - parser.add_argument( - "--extension", - default=".csv", - help="Output extension: '.csv' or '.gz' (default: .csv)", - ) - parser.add_argument( - "--prefix", - required=True, - help="Output directory prefix (sample ID)", - ) - return parser.parse_args() - - -if __name__ == "__main__": - args = parse_args() - convert_parquet( - transcripts=args.transcripts, - extension=args.extension, - prefix=args.prefix, - ) diff --git a/bin/utility_resize_tif.py b/bin/utility_resize_tif.py deleted file mode 100755 index 6cca640d..00000000 --- a/bin/utility_resize_tif.py +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env python3 -""" -Resize a segmentation TIFF mask to match transcript coordinates. - -This script rescales a segmentation mask image to match the coordinate -space of Xenium transcript data using microns-per-pixel metadata. -""" - -import argparse -import json -import os -from typing import Tuple - -import numpy as np -import pandas as pd -import tifffile -from skimage.transform import resize - - -def read_mask(mask_path: str) -> np.ndarray: - """Read the segmentation mask from a TIFF file.""" - print(f"Reading mask: {mask_path}") - mask = tifffile.imread(mask_path) - print(f"Mask shape: {mask.shape}, dtype: {mask.dtype}") - return mask - - -def read_transcript_bounds(transcript_path: str) -> Tuple[float, float, float, float]: - """Read transcript coordinates and return their bounding box.""" - print(f"Reading transcripts: {transcript_path}") - if transcript_path.endswith(".parquet"): - transcripts = pd.read_parquet(transcript_path, columns=["x_location", "y_location"]) - else: - transcripts = pd.read_csv(transcript_path) - - if "x_location" not in transcripts.columns or "y_location" not in transcripts.columns: - raise ValueError("Transcript file must contain 'x_location' and 'y_location' columns.") - - x_min, x_max = transcripts["x_location"].min(), transcripts["x_location"].max() - y_min, y_max = transcripts["y_location"].min(), transcripts["y_location"].max() - - print(f"Transcript bounds: X=({x_min:.2f}, {x_max:.2f}), Y=({y_min:.2f}, {y_max:.2f})") - return x_min, x_max, y_min, y_max - - -def read_microns_per_pixel(metadata_path: str) -> float: - """Extract microns_per_pixel or pixel_size from metadata JSON.""" - print(f"Reading metadata: {metadata_path}") - with open(metadata_path, "r") as f: - metadata = json.load(f) - - mpp = metadata.get("microns_per_pixel") or metadata.get("pixel_size") - if mpp is None: - raise KeyError("Metadata JSON must contain 'microns_per_pixel' or 'pixel_size'.") - - print(f"Microns per pixel: {mpp}") - return float(mpp) - - -def compute_target_size( - x_min: float, x_max: float, y_min: float, y_max: float, microns_per_pixel: float -) -> Tuple[int, int]: - """Compute new image size (in pixels) to cover given coordinates.""" - new_width = int(round((x_max - x_min) / microns_per_pixel)) - new_height = int(round((y_max - y_min) / microns_per_pixel)) - print(f"Target image size: {new_width} x {new_height} pixels") - return new_height, new_width - - -def resize_mask(mask: np.ndarray, new_shape: Tuple[int, int]) -> np.ndarray: - """Resize mask using nearest-neighbor interpolation (preserve labels).""" - print("Resizing mask...") - resized = resize( - mask, - new_shape, - order=0, # nearest neighbor to preserve segmentation labels - preserve_range=True, - anti_aliasing=False, - ).astype(mask.dtype) - print(f"Resized shape: {resized.shape}") - return resized - - -def main(mask_path: str, transcripts_path: str, metadata_path: str, output_path: str) -> None: - """Resize segmentation mask to match Xenium coordinate space.""" - # Validate input files - for path in [mask_path, transcripts_path, metadata_path]: - if not os.path.exists(path): - raise FileNotFoundError(f"File not found: {path}") - - # Load data - mask = read_mask(mask_path) - x_min, x_max, y_min, y_max = read_transcript_bounds(transcripts_path) - microns_per_pixel = read_microns_per_pixel(metadata_path) - - # Compute physical mask size - height, width = mask.shape - print(f"Original mask size: {width * microns_per_pixel:.2f} x {height * microns_per_pixel:.2f} um") - - # Compute target size - new_height, new_width = compute_target_size(x_min, x_max, y_min, y_max, microns_per_pixel) - - # Resize and save - resized_mask = resize_mask(mask, (new_height, new_width)) - tifffile.imwrite(output_path, resized_mask) - - print(f"Saved resized mask -> {output_path}") - - -def parse_args() -> argparse.Namespace: - """Parse command-line arguments.""" - parser = argparse.ArgumentParser( - description="Resize a segmentation TIFF mask to match transcript coordinates." - ) - parser.add_argument("--mask", required=True, help="Path to segmentation mask TIFF") - parser.add_argument("--transcripts", required=True, help="Path to transcripts file") - parser.add_argument("--metadata", required=True, help="Path to metadata JSON") - parser.add_argument("--prefix", required=True, help="Output directory prefix") - parser.add_argument("--mask-filename", required=True, help="Original mask filename for output naming") - return parser.parse_args() - - -if __name__ == "__main__": - args = parse_args() - - os.makedirs(args.prefix, exist_ok=True) - output_mask: str = os.path.join(args.prefix, f"resized_{args.mask_filename}.tif") - - main( - mask_path=args.mask, - transcripts_path=args.transcripts, - metadata_path=args.metadata, - output_path=output_mask, - ) diff --git a/bin/utility_segger2xr.py b/bin/utility_segger2xr.py deleted file mode 100755 index 22889e82..00000000 --- a/bin/utility_segger2xr.py +++ /dev/null @@ -1,247 +0,0 @@ -#!/usr/bin/env python3 -""" -Convert Segger prediction output to XeniumRanger-compatible format. - -Reads Segger PREDICT output (transcripts.parquet with segger_cell_id), -produces Baysor-format segmentation CSV, refined transcripts parquet, -and GeoJSON cell boundary polygons for xeniumranger import-segmentation. -""" - -import argparse -import json -from pathlib import Path -from typing import List - -import pandas as pd -from scipy.spatial import ConvexHull - -# Expected columns in transcripts.parquet -REQUIRED_COLUMNS: List[str] = [ - "transcript_id", - "cell_id", - "overlaps_nucleus", - "feature_name", - "x_location", - "y_location", - "z_location", - "qv", -] - -# Column name for segger cell assignment (varies by segger version) -SEGGER_ID_CANDIDATES: List[str] = ["segger_cell_id", "segger_id"] - - -def refine_transcripts(parquet_path: str) -> pd.DataFrame: - """ - Read segger PREDICT output and extract cell assignments. - Supports both 'segger_cell_id' (newer) and 'segger_id' (older) column names. - """ - parquet_file = Path(parquet_path) - if not parquet_file.exists(): - raise FileNotFoundError(f"File not found: {parquet_path}") - - df = pd.read_parquet(parquet_file, engine="pyarrow") - - missing_cols = [col for col in REQUIRED_COLUMNS if col not in df.columns] - if missing_cols: - raise ValueError(f"Missing required columns: {missing_cols}") - - # Find segger cell assignment column - segger_col = None - for candidate in SEGGER_ID_CANDIDATES: - if candidate in df.columns: - segger_col = candidate - break - if segger_col is None: - raise ValueError( - f"No segger cell assignment column found. " - f"Expected one of {SEGGER_ID_CANDIDATES}, got columns: {list(df.columns)}" - ) - - # Replace cell_id with segger assignment - cell_id_index = df.columns.get_loc("cell_id") - df = df.drop(columns=["cell_id"]) - segger_series = df.pop(segger_col) - df.insert(cell_id_index, "cell_id", segger_series) - - return df - - -def build_cell_map(df: pd.DataFrame, min_transcripts: int = 3) -> dict: - """ - Build a mapping from raw segger cell IDs to non-numeric string IDs. - - Only includes cells that have: - - >= min_transcripts assigned transcripts - - At least one transcript with valid (non-NaN) x/y coordinates - - Cell IDs use "cell-N" format (hyphen + integer) as required by - xeniumranger's cell ID parser. Non-numeric to avoid polars Int64 inference. - """ - cell_ids = df["cell_id"].fillna("UNASSIGNED").astype(str) - is_unassigned = (cell_ids == "UNASSIGNED") | (cell_ids == "") | (cell_ids == "0") - assigned = cell_ids[~is_unassigned] - counts = assigned.value_counts() - enough_tx = set(counts[counts >= min_transcripts].index) - - # Exclude cells with all-NaN coordinates (no spatial info = useless) - has_coords = df.dropna(subset=["x_location", "y_location"]) - has_coords_ids = set(has_coords["cell_id"].fillna("UNASSIGNED").astype(str)) - valid_cells = sorted(enough_tx & has_coords_ids) - - return {cell: f"cell-{i + 1}" for i, cell in enumerate(valid_cells)} - - -def to_baysor_csv(df: pd.DataFrame, output_path: str, cell_map: dict) -> None: - """ - Convert transcript DataFrame to Baysor-compatible CSV format. - - xeniumranger 4.0 import-segmentation --transcript-assignment expects a - Baysor segmentation CSV with at minimum: transcript_id, cell, is_noise, - x, y columns. This function maps Xenium/Segger columns to Baysor format. - """ - baysor_df = pd.DataFrame() - baysor_df["transcript_id"] = df["transcript_id"] - baysor_df["x"] = df["x_location"] - baysor_df["y"] = df["y_location"] - baysor_df["z"] = df["z_location"] - baysor_df["gene"] = df["feature_name"] - - cell_ids = df["cell_id"].fillna("UNASSIGNED").astype(str) - is_unassigned = (cell_ids == "UNASSIGNED") | (cell_ids == "") | (cell_ids == "0") - baysor_df["cell"] = cell_ids.map(cell_map).fillna("") - baysor_df["is_noise"] = is_unassigned.astype(int) - - baysor_df.to_csv(output_path, index=False) - - n_assigned = (~is_unassigned).sum() - n_noise = is_unassigned.sum() - n_cells = len(cell_map) - print( - f"Baysor CSV: {n_assigned} assigned, {n_noise} noise, {n_cells} cells -> {output_path}" - ) - - -def _make_buffer_polygon(cx: float, cy: float, radius: float = 0.5) -> list: - """Create a small square polygon around a centroid as fallback.""" - return [ - [cx - radius, cy - radius], - [cx + radius, cy - radius], - [cx + radius, cy + radius], - [cx - radius, cy + radius], - [cx - radius, cy - radius], # close ring - ] - - -def generate_viz_polygons(df: pd.DataFrame, output_path: str, cell_map: dict) -> None: - """ - Generate a GeoJSON file with cell boundary polygons. - - Uses ConvexHull when possible; falls back to a small buffer polygon around - the centroid for cells with < 3 unique points or collinear points. - - Required by xeniumranger import-segmentation when using --transcript-assignment. - Each feature MUST have a top-level "id" field (xeniumranger reads item["id"]). - Cell IDs must match those in the Baysor CSV. - """ - assigned = df[ - df["cell_id"].notna() - & (df["cell_id"].astype(str) != "UNASSIGNED") - & (df["cell_id"].astype(str) != "") - ].copy() - - features = [] - grouped = assigned.groupby("cell_id") - - for cell_id, group in grouped: - mapped_id = cell_map.get(str(cell_id)) - if mapped_id is None: - continue - - coords = group[["x_location", "y_location"]].dropna().values - - polygon_coords = None - if len(coords) >= 3: - try: - hull = ConvexHull(coords) - hull_points = coords[hull.vertices].tolist() - hull_points.append(hull_points[0]) # close polygon ring - polygon_coords = hull_points - except Exception: - pass - - # Fallback: buffer polygon around centroid - if polygon_coords is None: - cx, cy = coords.mean(axis=0).astype(float) - polygon_coords = _make_buffer_polygon(cx, cy) - - features.append( - { - "type": "Feature", - "id": mapped_id, - "geometry": { - "type": "Polygon", - "coordinates": [polygon_coords], - }, - "properties": {"cell_id": mapped_id}, - } - ) - - geojson = {"type": "FeatureCollection", "features": features} - - with open(output_path, "w") as f: - json.dump(geojson, f) - - print(f"Generated {len(features)} cell polygons in {output_path}") - - -def main(input_file: str, prefix: str, min_transcripts: int = 3) -> None: - """Run the full segger-to-xeniumranger conversion pipeline.""" - Path(prefix).mkdir(parents=True, exist_ok=True) - transcripts = refine_transcripts(input_file) - - # Build cell ID mapping, filtering cells with < min_transcripts - cell_map = build_cell_map(transcripts, min_transcripts=min_transcripts) - - # xeniumranger 4.0 expects Baysor-format CSV (not parquet) with is_noise column - to_baysor_csv(transcripts, f"{prefix}/segmentation.csv", cell_map) - - # Also save the refined parquet for downstream use - transcripts.to_parquet(f"{prefix}/transcripts.parquet", engine="pyarrow") - - # Generate cell boundary polygons (required companion to --transcript-assignment) - # Uses ConvexHull when possible; falls back to buffer polygon for edge cases - generate_viz_polygons(transcripts, f"{prefix}/segmentation_polygons.json", cell_map) - - -def parse_args() -> argparse.Namespace: - """Parse command-line arguments.""" - parser = argparse.ArgumentParser( - description="Convert Segger prediction output to XeniumRanger-compatible format." - ) - parser.add_argument( - "--transcripts", - required=True, - help="Path to Segger output transcripts parquet file", - ) - parser.add_argument( - "--prefix", - required=True, - help="Output directory prefix (sample ID)", - ) - parser.add_argument( - "--min-transcripts", - type=int, - default=3, - help="Minimum transcripts per cell (default: 3)", - ) - return parser.parse_args() - - -if __name__ == "__main__": - args = parse_args() - main( - input_file=args.transcripts, - prefix=args.prefix, - min_transcripts=args.min_transcripts, - ) diff --git a/bin/utility_split_transcripts.py b/bin/utility_split_transcripts.py deleted file mode 100755 index 275fbab1..00000000 --- a/bin/utility_split_transcripts.py +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env python3 -""" -Split transcript coordinates into spatial tiles. - -Reads a Xenium transcripts.parquet file and computes quantile-based spatial -tiles, writing a splits.csv with tile boundaries. -""" - -import argparse -import os -from typing import List - -import pandas as pd - - -def compute_quantile_ranges(df: pd.DataFrame, col: str, n_bins: int) -> List: - """ - Compute the bin edges for `df[col]` such that each of the n_bins - has ~equal count of points. Returns a list of (min, max) tuples. - """ - _, bins = pd.qcut(df[col], q=n_bins, retbins=True, duplicates="drop") - - ranges = [(bins[i], bins[i + 1]) for i in range(len(bins) - 1)] - - return ranges - - -def make_tiles(df: pd.DataFrame, x_bins: int, y_bins: int) -> pd.DataFrame: - """ - Produce a DataFrame with one row per tile: - tile_id, x_min, x_max, y_min, y_max - """ - x_ranges = compute_quantile_ranges(df, "x_location", x_bins) - y_ranges = compute_quantile_ranges(df, "y_location", y_bins) - - tiles = [] - for ix, (x_min, x_max) in enumerate(x_ranges, start=1): - for iy, (y_min, y_max) in enumerate(y_ranges, start=1): - tiles.append( - { - "tile_id": f"{ix}_{iy}", - "x_min": x_min, - "x_max": x_max, - "y_min": y_min, - "y_max": y_max, - } - ) - - return pd.DataFrame(tiles) - - -def main( - transcripts: str, - x_bins: int = 10, - y_bins: int = 10, - prefix: str = "", -) -> None: - """Generate spatial tile splits from transcript coordinates.""" - # read parquet file - df = pd.read_parquet(transcripts, engine="fastparquet") - - # compute tiles - tiles_df = make_tiles(df, x_bins, y_bins) - - # save csv file - os.makedirs(prefix, exist_ok=True) - tiles_df.to_csv(f"{prefix}/splits.csv", index=False) - - return None - - -def parse_args() -> argparse.Namespace: - """Parse command-line arguments.""" - parser = argparse.ArgumentParser( - description="Split transcript coordinates into spatial tiles." - ) - parser.add_argument( - "--transcripts", - required=True, - help="Path to transcripts parquet file", - ) - parser.add_argument( - "--x-bins", - type=int, - required=True, - help="Number of bins along X axis", - ) - parser.add_argument( - "--y-bins", - type=int, - required=True, - help="Number of bins along Y axis", - ) - parser.add_argument( - "--prefix", - required=True, - help="Output directory prefix", - ) - return parser.parse_args() - - -if __name__ == "__main__": - args = parse_args() - main( - transcripts=args.transcripts, - x_bins=args.x_bins, - y_bins=args.y_bins, - prefix=args.prefix, - ) diff --git a/bin/utility_upscale_mask.py b/bin/utility_upscale_mask.py deleted file mode 100755 index 6cc1694e..00000000 --- a/bin/utility_upscale_mask.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python3 -""" -Restore Cellpose masks to original image resolution. - -Uses nearest-neighbor interpolation to upscale segmentation masks back -to the original image dimensions recorded in scale_info.json (produced -by downscale_morphology.py). - -Output: {prefix}/upscaled_{mask_basename}.tif -""" - -import argparse -import json -from pathlib import Path - -import numpy as np -import tifffile -from PIL import Image - - -def upscale_mask(mask_path: str, scale_info_path: str, prefix: str) -> None: - """ - Read a downscaled mask and upscale it to original dimensions. - - Args: - mask_path: Path to downscaled segmentation mask TIFF. - scale_info_path: Path to scale_info.json from downscale_morphology. - prefix: Output directory. - """ - with open(scale_info_path) as f: - info = json.load(f) - orig_h, orig_w = info["orig_h"], info["orig_w"] - - mask = tifffile.imread(mask_path) - print( - f"Mask: {mask.shape}, dtype={mask.dtype}, " - f"unique cells: {len(np.unique(mask)) - 1}" - ) - print(f"Upscaling to ({orig_h}, {orig_w})") - - pil_mask = Image.fromarray(mask) - pil_mask = pil_mask.resize((orig_w, orig_h), Image.NEAREST) - mask_up = np.array(pil_mask, dtype=mask.dtype) - - out_dir = Path(prefix) - out_dir.mkdir(parents=True, exist_ok=True) - base = Path(mask_path).stem - out_name = out_dir / f"upscaled_{base}.tif" - tifffile.imwrite(str(out_name), mask_up, compression="zlib") - print( - f"Done: {out_name}, unique cells: {len(np.unique(mask_up)) - 1}" - ) - - -def parse_args() -> argparse.Namespace: - """Parse command-line arguments.""" - parser = argparse.ArgumentParser( - description="Upscale a Cellpose mask back to original resolution." - ) - parser.add_argument("--mask", required=True, help="Downscaled mask TIFF") - parser.add_argument("--scale-info", required=True, help="scale_info.json from downscale step") - parser.add_argument("--prefix", required=True, help="Output directory") - return parser.parse_args() - - -if __name__ == "__main__": - args = parse_args() - upscale_mask( - mask_path=args.mask, - scale_info_path=args.scale_info, - prefix=args.prefix, - ) diff --git a/bin/xenium_patch_stitch_postprocess.py b/bin/xenium_patch_stitch_postprocess.py deleted file mode 100755 index 7144b1ac..00000000 --- a/bin/xenium_patch_stitch_postprocess.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python3 -""" -Post-process stitched per-patch segmentation outputs. - -Ensures every GeoJSON feature is a single Polygon: make_valid() and -sopa.solve_conflicts() can produce MultiPolygon, MultiLineString, or -GeometryCollection geometries that XeniumRanger rejects. Cells dropped -during cleanup are also reassigned to UNASSIGNED in the transcript CSV -so the two outputs stay consistent. -""" - -import argparse -import csv -import json - -import shapely -from shapely.geometry import mapping, shape - - -def clean_geojson(geojson_path: str) -> set: - """ - Force every feature to a single valid Polygon. - - Returns the set of cell ids whose features were dropped. - """ - with open(geojson_path) as f: - data = json.load(f) - - clean = [] - dropped_cells = set() - for feat in data["features"]: - geom = shape(feat["geometry"]) - if not geom.is_valid: - geom = shapely.make_valid(geom) - poly = None - if geom.geom_type == "Polygon": - poly = geom - elif geom.geom_type == "MultiPolygon": - poly = max(geom.geoms, key=lambda g: g.area) - elif geom.geom_type == "GeometryCollection": - polys = [g for g in geom.geoms if g.geom_type == "Polygon"] - if polys: - poly = max(polys, key=lambda g: g.area) - if poly is not None and not poly.is_empty: - feat["geometry"] = mapping(poly) - clean.append(feat) - else: - cell_id = feat.get("id") or feat.get("properties", {}).get("cell_id", "") - dropped_cells.add(str(cell_id)) - - print(f"GeoJSON: {len(clean)} kept, {len(dropped_cells)} dropped: {dropped_cells}") - data["features"] = clean - with open(geojson_path, "w") as f: - json.dump(data, f) - - return dropped_cells - - -def reassign_dropped(csv_path: str, dropped_cells: set) -> None: - """ - Reassign transcripts of dropped cells to UNASSIGNED in the CSV. - """ - if not dropped_cells: - return - - with open(csv_path) as f: - reader = csv.DictReader(f) - fieldnames = reader.fieldnames - rows = list(reader) - - reassigned = 0 - for row in rows: - if row["cell"] in dropped_cells: - row["cell"] = "" - row["is_noise"] = "1" - reassigned += 1 - - with open(csv_path, "w", newline="") as f: - writer = csv.DictWriter(f, fieldnames=fieldnames) - writer.writeheader() - writer.writerows(rows) - print(f"CSV: {reassigned} transcripts reassigned to UNASSIGNED") - - -def parse_args() -> argparse.Namespace: - """Parse command-line arguments.""" - parser = argparse.ArgumentParser( - description="Clean stitched GeoJSON polygons and reconcile transcript CSV." - ) - parser.add_argument("--geojson", required=True, help="Path to xr-cell-polygons.geojson") - parser.add_argument("--csv", required=True, help="Path to xr-transcript-metadata.csv") - return parser.parse_args() - - -if __name__ == "__main__": - args = parse_args() - dropped = clean_geojson(args.geojson) - reassign_dropped(args.csv, dropped) diff --git a/bin/xenium_patch_stitch_transcripts.py b/bin/xenium_patch_stitch_transcripts.py deleted file mode 100755 index d9fb8d41..00000000 --- a/bin/xenium_patch_stitch_transcripts.py +++ /dev/null @@ -1,808 +0,0 @@ -#!/usr/bin/env python3 -"""Stitch per-patch Baysor segmentation results into unified output. - -Standalone script that replaces the xenium_patch CLI package's stitch -functionality. Uses sopa's solve_conflicts() for overlap resolution. -""" - -from __future__ import annotations - -import argparse -import json -import os -from concurrent.futures import ThreadPoolExecutor -from dataclasses import dataclass -from pathlib import Path - -import geopandas as gpd -import numpy as np -import pyarrow as pa -import pyarrow.compute as pc -import pyarrow.csv as pa_csv -import shapely -from shapely.affinity import translate -from shapely.geometry import mapping, shape -from sopa.segmentation.resolve import solve_conflicts - -# --------------------------------------------------------------------------- -# Geometry helpers -# --------------------------------------------------------------------------- - - -def _ensure_polygon(geom) -> "shapely.Polygon | None": - """Extract a single Polygon from any geometry, or return None. - - XeniumRanger only accepts Polygon. make_valid() and solve_conflicts - can produce MultiPolygon, GeometryCollection, MultiLineString, etc. - """ - if geom is None or geom.is_empty: - return None - if geom.geom_type == "Polygon": - return geom - if geom.geom_type == "MultiPolygon": - return max(geom.geoms, key=lambda g: g.area) - if geom.geom_type == "GeometryCollection": - polys = [g for g in geom.geoms if g.geom_type == "Polygon"] - return max(polys, key=lambda g: g.area) if polys else None - # LineString, MultiLineString, Point, etc. — not a polygon - return None - - -# --------------------------------------------------------------------------- -# Inline types (from _types.py) -# --------------------------------------------------------------------------- - - -@dataclass(frozen=True) -class Bounds: - """Axis-aligned bounding box in either pixel or micron coordinates.""" - - x_min: float - x_max: float - y_min: float - y_max: float - - -@dataclass(frozen=True) -class PatchInfo: - """Metadata for a single patch in the grid.""" - - patch_id: str - row: int - col: int - global_bounds_px: Bounds - global_bounds_um: Bounds - core_bounds_px: Bounds - core_bounds_um: Bounds - - -@dataclass -class PatchGridMetadata: - """Full grid metadata, serializable to JSON.""" - - version: str - bundle_path: str - image_height_px: int - image_width_px: int - pixel_size_um: float - transcript_extent_um: Bounds - grid_rows: int - grid_cols: int - overlap_um: float - overlap_px: int - patches: list[PatchInfo] - grid_type: str = "uniform" - - -# --------------------------------------------------------------------------- -# Internal result containers -# --------------------------------------------------------------------------- - - -@dataclass -class _PatchGeoResult: - """Result of parallel GeoJSON processing for a single patch.""" - - features: list[dict] - cell_ids: list[str] - - -@dataclass -class _PatchCsvResult: - """Result of parallel CSV reading for a single patch.""" - - table: pa.Table - has_cell_col: bool - has_x_col: bool - has_y_col: bool - has_gene_col: bool = False - has_feature_name_col: bool = False - - -# --------------------------------------------------------------------------- -# Grid metadata I/O (from grid.py) -# --------------------------------------------------------------------------- - - -def _dict_to_bounds(d: dict) -> Bounds: - return Bounds(d["x_min"], d["x_max"], d["y_min"], d["y_max"]) - - -def load_grid_metadata(input_path: Path) -> PatchGridMetadata: - """Deserialize PatchGridMetadata from JSON. - - Args: - input_path: Path to JSON file to read. - - Returns: - Reconstructed PatchGridMetadata. - """ - with open(input_path) as f: - data = json.load(f) - - patches = [ - PatchInfo( - patch_id=p["patch_id"], - row=p["row"], - col=p["col"], - global_bounds_px=_dict_to_bounds(p["global_bounds_px"]), - global_bounds_um=_dict_to_bounds(p["global_bounds_um"]), - core_bounds_px=_dict_to_bounds(p["core_bounds_px"]), - core_bounds_um=_dict_to_bounds(p["core_bounds_um"]), - ) - for p in data["patches"] - ] - - return PatchGridMetadata( - version=data["version"], - bundle_path=data["bundle_path"], - image_height_px=data["image_height_px"], - image_width_px=data["image_width_px"], - pixel_size_um=data["pixel_size_um"], - transcript_extent_um=_dict_to_bounds(data["transcript_extent_um"]), - grid_rows=data["grid_rows"], - grid_cols=data["grid_cols"], - overlap_um=data["overlap_um"], - overlap_px=data["overlap_px"], - grid_type=data.get("grid_type", "uniform"), - patches=patches, - ) - - -# --------------------------------------------------------------------------- -# GeoJSON I/O (from polygon_io.py) -# --------------------------------------------------------------------------- - - -def _normalize_geometry_collection(geojson: dict) -> dict: - """Convert a GeometryCollection to a FeatureCollection. - - proseg-to-baysor produces a non-standard GeoJSON GeometryCollection where - each geometry object has a custom ``cell`` key (bare integer) instead of - using Feature wrappers. This normalises it to a standard FeatureCollection - with ``id`` and ``properties.cell_id`` on each feature, using the - ``"cell-{N}"`` format that matches the companion CSV. - - Args: - geojson: Parsed GeoJSON dict with type GeometryCollection. - - Returns: - Standard FeatureCollection dict. - """ - features = [] - for geom in geojson.get("geometries", []): - cell_raw = geom.get("cell", "") - cell_id = str(cell_raw) - clean_geom = {k: v for k, v in geom.items() if k != "cell"} - feature = { - "type": "Feature", - "id": cell_id, - "geometry": clean_geom, - "properties": {"cell_id": cell_id}, - } - features.append(feature) - return {"type": "FeatureCollection", "features": features} - - -def read_geojson(geojson_path: Path) -> dict: - """Read a GeoJSON file and normalise to FeatureCollection. - - Handles both standard FeatureCollections and the GeometryCollection - format produced by proseg-to-baysor. - - Args: - geojson_path: Path to the GeoJSON file. - - Returns: - Parsed GeoJSON dict (always a FeatureCollection). - """ - with open(geojson_path) as f: - data = json.load(f) - if data.get("type") == "GeometryCollection": - return _normalize_geometry_collection(data) - return data - - -def transform_polygons(geojson: dict, offset_x: float, offset_y: float) -> dict: - """Shift all polygon coordinates by (offset_x, offset_y). - - Args: - geojson: Input FeatureCollection. - offset_x: Translation in x. - offset_y: Translation in y. - - Returns: - New FeatureCollection with shifted geometries. - """ - features = [] - for feat in geojson.get("features", []): - geom = shape(feat["geometry"]) - shifted = translate(geom, xoff=offset_x, yoff=offset_y) - new_feat = {**feat, "geometry": mapping(shifted)} - features.append(new_feat) - return {"type": "FeatureCollection", "features": features} - - -def write_geojson(geojson: dict, output_path: Path) -> None: - """Write a GeoJSON FeatureCollection. - - Args: - geojson: GeoJSON dict to write. - output_path: Destination path (parent dirs created automatically). - """ - output_path.parent.mkdir(parents=True, exist_ok=True) - with open(output_path, "w") as f: - json.dump(geojson, f) - - -# --------------------------------------------------------------------------- -# Arrow utilities (from _arrow_utils.py) -# --------------------------------------------------------------------------- - - -def float_str_array(f64_array: pa.Array) -> pa.Array: - """Convert a float64 pyarrow array to string using Python's str(float) format. - - pyarrow's built-in cast omits trailing '.0' for whole numbers. This - function ensures output matches str(float(...)) for CSV compatibility. - - Args: - f64_array: Float64 pyarrow array to convert. - - Returns: - String pyarrow array with Python-formatted float values. - """ - return pa.array( - [str(v) if v is not None else None for v in f64_array.to_pylist()], - type=pa.string(), - ) - - -# --------------------------------------------------------------------------- -# Parallel I/O -# --------------------------------------------------------------------------- - - -def _read_and_transform_geojson( - patch: PatchInfo, - patches_dir: Path, - geojson_filename: str, -) -> _PatchGeoResult | None: - """Read, transform GeoJSON for a single patch (no core clipping). - - Args: - patch: Patch metadata. - patches_dir: Root patches directory. - geojson_filename: GeoJSON filename within each patch directory. - - Returns: - _PatchGeoResult with features and cell IDs, or None if no GeoJSON. - """ - geojson_path = patches_dir / patch.patch_id / geojson_filename - if not geojson_path.exists(): - return None - - geojson = read_geojson(geojson_path) - - offset_x = patch.global_bounds_um.x_min - offset_y = patch.global_bounds_um.y_min - geojson = transform_polygons(geojson, offset_x, offset_y) - - features = geojson.get("features", []) - seen: set[str] = set() - cell_ids: list[str] = [] - for feat in features: - old_id = str(feat.get("id", feat.get("properties", {}).get("cell_id", ""))) - if old_id not in seen: - seen.add(old_id) - cell_ids.append(old_id) - - return _PatchGeoResult(features=features, cell_ids=cell_ids) - - -def _read_patch_csv( - patch: PatchInfo, - patches_dir: Path, - csv_filename: str, -) -> _PatchCsvResult | None: - """Read a patch CSV into a pyarrow Table. - - All columns are read as strings to preserve exact formatting. - - Args: - patch: Patch metadata. - patches_dir: Root patches directory. - csv_filename: CSV filename within each patch directory. - - Returns: - _PatchCsvResult with the table and column presence flags, or None. - """ - csv_path = patches_dir / patch.patch_id / csv_filename - if not csv_path.exists(): - return None - - with open(csv_path) as fh: - header_line = fh.readline().strip() - col_names = header_line.split(",") - all_string_types = {name: pa.string() for name in col_names} - - table = pa_csv.read_csv( - csv_path, - convert_options=pa_csv.ConvertOptions( - column_types=all_string_types, - strings_can_be_null=False, - ), - read_options=pa_csv.ReadOptions(use_threads=True), - ) - - return _PatchCsvResult( - table=table, - has_cell_col="cell" in table.column_names, - has_x_col="x" in table.column_names, - has_y_col="y" in table.column_names, - has_gene_col="gene" in table.column_names, - has_feature_name_col="feature_name" in table.column_names, - ) - - -# --------------------------------------------------------------------------- -# CSV processing -# --------------------------------------------------------------------------- - - -def _transform_patch_coords( - csv_result: _PatchCsvResult, - offset_x: float, - offset_y: float, -) -> pa.Table: - """Shift transcript coordinates from local patch space to global space. - - Args: - csv_result: The raw CSV table and column flags. - offset_x: X offset for coordinate transform (microns). - offset_y: Y offset for coordinate transform (microns). - - Returns: - Table with x, y columns shifted to global coordinates. - """ - table = csv_result.table - - if table.num_rows == 0: - return table - - if csv_result.has_x_col: - x_f64 = pc.add( - table.column("x").cast(pa.float64()), - pa.scalar(offset_x, type=pa.float64()), - ) - table = table.set_column( - table.schema.get_field_index("x"), - "x", - float_str_array(x_f64), - ) - if csv_result.has_y_col: - y_f64 = pc.add( - table.column("y").cast(pa.float64()), - pa.scalar(offset_y, type=pa.float64()), - ) - table = table.set_column( - table.schema.get_field_index("y"), - "y", - float_str_array(y_f64), - ) - - return table - - -# --------------------------------------------------------------------------- -# Sopa conflict resolution -# --------------------------------------------------------------------------- - - -def _stitch_sopa_resolve( - metadata: PatchGridMetadata, - geo_results: list[_PatchGeoResult | None], - csv_results: list[_PatchCsvResult | None], - all_geojson_features: list[dict], - all_tables: list[pa.Table], - threshold: float = 0.5, -) -> set[str]: - """Stitch per-patch segmentation using spatial containment assignment. - - 1. Collect ALL non-empty polygons from all patches (no transcript filtering). - 2. Resolve overlapping polygons via sopa's solve_conflicts(). - 3. Assign sequential global cell IDs (cell-1, cell-2, ...). - 4. Spatially assign transcripts to resolved polygons using STRtree. - 5. Noise transcripts (outside all polygons) kept only from their core patch. - - This approach works regardless of whether Baysor's CSV ``cell`` column - matches GeoJSON cell IDs -- all assignment is done by spatial containment. - - Args: - metadata: Grid metadata with patch list. - geo_results: Per-patch GeoJSON results (already in global coords). - csv_results: Per-patch CSV results. - all_geojson_features: Output list to append resolved GeoJSON features. - all_tables: Output list to append processed CSV tables. - threshold: Overlap threshold for sopa's solve_conflicts (0-1). - - Returns: - Set of global cell IDs created by merging overlapping cells. - """ - # --- Phase 1: Collect all polygons from all patches --- - all_polygons: list = [] - patch_indices_list: list[int] = [] - - for i, patch in enumerate(metadata.patches): - geo_result = geo_results[i] - if geo_result is None: - continue - - for feat in geo_result.features: - polygon = shape(feat["geometry"]) - if polygon.is_empty: - continue - if not polygon.is_valid: - polygon = shapely.make_valid(polygon) - # Ensure we have a single Polygon (xeniumranger rejects all else) - polygon = _ensure_polygon(polygon) - if polygon is None: - continue - - all_polygons.append(polygon) - patch_indices_list.append(i) - - if not all_polygons: - print("[stitch] No polygons found in any patch") - # Still transform and collect CSVs as noise-only - for i, patch in enumerate(metadata.patches): - csv_result = csv_results[i] - if csv_result is None: - continue - offset_x = patch.global_bounds_um.x_min - offset_y = patch.global_bounds_um.y_min - transformed = _transform_patch_coords(csv_result, offset_x, offset_y) - if transformed.num_rows > 0: - all_tables.append(transformed) - return set() - - # --- Phase 2: Resolve overlapping polygons via sopa --- - patch_idx_array = np.array(patch_indices_list, dtype=np.int64) - input_gdf = gpd.GeoDataFrame(geometry=all_polygons) - resolved_gdf, kept_indices = solve_conflicts( - input_gdf, - threshold=threshold, - patch_indices=patch_idx_array, - return_indices=True, - ) - - # --- Phase 3: Assign global cell IDs to resolved polygons --- - merged_cell_ids: set[str] = set() - kept_arr = np.asarray(kept_indices) - resolved_polys: list = [] - resolved_ids: list[str] = [] - - for rank, orig_idx in enumerate(kept_arr, start=1): - global_id = f"cell-{rank}" - geom = resolved_gdf.geometry.iloc[rank - 1] - - # Ensure single Polygon after solve_conflicts union - geom = _ensure_polygon(geom) - if geom is None: - continue - - if orig_idx < 0: - merged_cell_ids.add(global_id) - - resolved_polys.append(geom) - resolved_ids.append(global_id) - - all_geojson_features.append( - { - "type": "Feature", - "id": global_id, - "geometry": mapping(geom), - "properties": {"cell_id": global_id}, - } - ) - - print( - f"[stitch] Resolved {len(all_polygons)} input polygons to " - f"{len(resolved_polys)} cells ({len(merged_cell_ids)} merged)" - ) - - # --- Phase 4: Spatial transcript assignment via STRtree --- - poly_tree = shapely.STRtree(resolved_polys) - - for i, patch in enumerate(metadata.patches): - csv_result = csv_results[i] - if csv_result is None: - continue - - offset_x = patch.global_bounds_um.x_min - offset_y = patch.global_bounds_um.y_min - core = patch.core_bounds_um - - transformed = _transform_patch_coords(csv_result, offset_x, offset_y) - if transformed.num_rows == 0: - continue - - if not csv_result.has_x_col or not csv_result.has_y_col: - all_tables.append(transformed) - continue - - # Get global coordinates for spatial query - gx = transformed.column("x").cast(pa.float64()).to_numpy(zero_copy_only=False) - gy = transformed.column("y").cast(pa.float64()).to_numpy(zero_copy_only=False) - points = shapely.points(gx, gy) - - # Query STRtree: returns (input_indices, tree_indices) - point_hits, poly_hits = poly_tree.query(points, predicate="intersects") - - # Build point -> cell_id mapping (first hit wins) - point_to_cell: dict[int, str] = {} - for pt_idx, poly_idx in zip(point_hits, poly_hits): - if pt_idx not in point_to_cell: - point_to_cell[pt_idx] = resolved_ids[poly_idx] - - # Build cell and is_noise columns - n_rows = transformed.num_rows - cell_arr = [""] * n_rows - is_noise_arr = ["true"] * n_rows - for pt_idx, cell_id in point_to_cell.items(): - cell_arr[pt_idx] = cell_id - is_noise_arr[pt_idx] = "false" - - # Filter noise transcripts to core bounds only - # Assigned transcripts are kept from all patches (dedup later by transcript_id) - in_core = ( - (gx >= core.x_min) - & (gx < core.x_max) - & (gy >= core.y_min) - & (gy < core.y_max) - ) - is_assigned = np.array([c != "" for c in cell_arr]) - keep_mask = pa.array(is_assigned | in_core, type=pa.bool_()) - - filtered = transformed.filter(keep_mask) - cell_arr_filtered = [c for c, k in zip(cell_arr, (is_assigned | in_core)) if k] - is_noise_filtered = [ - n for n, k in zip(is_noise_arr, (is_assigned | in_core)) if k - ] - - if filtered.num_rows == 0: - continue - - # Set cell and is_noise columns - cell_idx = ( - filtered.schema.get_field_index("cell") - if "cell" in filtered.column_names - else None - ) - if cell_idx is not None: - filtered = filtered.set_column( - cell_idx, "cell", pa.array(cell_arr_filtered, type=pa.string()) - ) - else: - filtered = filtered.append_column( - "cell", pa.array(cell_arr_filtered, type=pa.string()) - ) - - noise_idx = ( - filtered.schema.get_field_index("is_noise") - if "is_noise" in filtered.column_names - else None - ) - if noise_idx is not None: - filtered = filtered.set_column( - noise_idx, - "is_noise", - pa.array(is_noise_filtered, type=pa.string()), - ) - else: - filtered = filtered.append_column( - "is_noise", pa.array(is_noise_filtered, type=pa.string()) - ) - - all_tables.append(filtered) - - return merged_cell_ids - - -# --------------------------------------------------------------------------- -# Main orchestrator -# --------------------------------------------------------------------------- - - -def stitch_transcript_assignments( - patches_dir: Path, - output_dir: Path, - csv_filename: str = "segmentation.csv", - geojson_filename: str = "segmentation_polygons.json", - max_workers: int | None = None, -) -> None: - """Stitch per-patch transcript assignments and polygons into unified output. - - For each patch, reads the transcript assignment CSV and polygon GeoJSON. - Cells are deduplicated using sopa's solve_conflicts() which resolves - overlapping cells at patch boundaries based on area overlap ratio. - - Processing is split into a parallel I/O phase (reading GeoJSON and CSV - files via thread pool) and a sequential phase (dedup, global cell ID - assignment, remapping, and concatenation). - - Args: - patches_dir: Directory containing patch subdirectories and patch_grid.json. - output_dir: Output directory for stitched CSV and GeoJSON. - csv_filename: CSV filename within each patch directory. - geojson_filename: GeoJSON filename within each patch directory. - max_workers: Maximum number of threads for parallel I/O. - """ - patches_dir = Path(patches_dir) - output_dir = Path(output_dir) - output_dir.mkdir(parents=True, exist_ok=True) - - metadata = load_grid_metadata(patches_dir / "patch_grid.json") - - n_patches = len(metadata.patches) - if max_workers is None: - max_workers = min(n_patches, os.cpu_count() or 1) - - # ---- Parallel phase: read GeoJSON and CSV files concurrently ---- - with ThreadPoolExecutor(max_workers=max_workers) as executor: - geo_futures = [ - executor.submit( - _read_and_transform_geojson, p, patches_dir, geojson_filename - ) - for p in metadata.patches - ] - csv_futures = [ - executor.submit(_read_patch_csv, p, patches_dir, csv_filename) - for p in metadata.patches - ] - geo_results = [f.result() for f in geo_futures] - csv_results = [f.result() for f in csv_futures] - - # ---- Sequential phase: assign global cell IDs, remap, concatenate ---- - all_tables: list[pa.Table] = [] - all_geojson_features: list[dict] = [] - - _stitch_sopa_resolve( - metadata, - geo_results, - csv_results, - all_geojson_features, - all_tables, - threshold=0.5, - ) - - # Concatenate all patch tables - if all_tables: - merged = pa.concat_tables(all_tables) - - # Deduplicate by transcript_id: prefer assigned over noise - if "transcript_id" in merged.column_names: - if "cell" in merged.column_names: - is_noise = pc.equal(merged.column("cell"), "").cast(pa.int8()) - row_order = pa.array(np.arange(merged.num_rows), type=pa.int64()) - sort_table = pa.table({"_noise": is_noise, "_row": row_order}) - sort_indices = pc.sort_indices( - sort_table, - sort_keys=[("_noise", "ascending"), ("_row", "ascending")], - ) - merged = merged.take(sort_indices) - - tid_np = merged.column("transcript_id").to_numpy(zero_copy_only=False) - _, first_indices = np.unique(tid_np, return_index=True) - first_indices.sort() - merged = merged.take(first_indices) - - # Log assignment stats - if "cell" in merged.column_names: - cell_vals = merged.column("cell").to_pylist() - n_assigned = sum(1 for c in cell_vals if c) - n_noise = sum(1 for c in cell_vals if not c) - print( - f"[stitch] Final: {merged.num_rows} transcripts, " - f"{n_assigned} assigned, {n_noise} noise" - ) - - # Cast is_noise to integer for xeniumranger compatibility - if "is_noise" in merged.column_names: - noise_col = merged.column("is_noise") - if noise_col.type == pa.string(): - lower = pc.utf8_lower(noise_col) - is_true = pc.or_(pc.equal(lower, "true"), pc.equal(lower, "1")) - idx = merged.column_names.index("is_noise") - merged = merged.set_column(idx, "is_noise", is_true.cast(pa.int8())) - - # Write CSV - if merged.num_rows > 0: - csv_out = output_dir / "xr-transcript-metadata.csv" - pa_csv.write_csv( - merged, - csv_out, - write_options=pa_csv.WriteOptions(quoting_style="needed"), - ) - - # Safety net: remove orphan polygons with zero transcripts - if all_geojson_features and all_tables: - csv_cell_ids: set[str] = set() - if "cell" in merged.column_names: - csv_cell_ids = set(c for c in merged.column("cell").to_pylist() if c) - all_geojson_features = [ - f - for f in all_geojson_features - if str(f.get("id", f.get("properties", {}).get("cell_id", ""))) - in csv_cell_ids - ] - - # Write merged GeoJSON - if all_geojson_features: - merged_geo = {"type": "FeatureCollection", "features": all_geojson_features} - write_geojson(merged_geo, output_dir / "xr-cell-polygons.geojson") - - -# --------------------------------------------------------------------------- -# CLI -# --------------------------------------------------------------------------- - - -def main() -> None: - parser = argparse.ArgumentParser( - description="Stitch per-patch Baysor segmentation results into unified output." - ) - parser.add_argument( - "--patches", - type=Path, - required=True, - help="Directory containing patch subdirectories and patch_grid.json", - ) - parser.add_argument( - "--output", - type=Path, - required=True, - help="Output directory for stitched CSV and GeoJSON", - ) - parser.add_argument( - "--csv-filename", - default="segmentation.csv", - help="CSV filename within each patch (default: segmentation.csv)", - ) - parser.add_argument( - "--geojson-filename", - default="segmentation_polygons.json", - help="GeoJSON filename within each patch (default: segmentation_polygons.json)", - ) - args = parser.parse_args() - - stitch_transcript_assignments( - patches_dir=args.patches, - output_dir=args.output, - csv_filename=args.csv_filename, - geojson_filename=args.geojson_filename, - ) - - -if __name__ == "__main__": - main() diff --git a/conf/base.config b/conf/base.config index 45b62604..187a154e 100644 --- a/conf/base.config +++ b/conf/base.config @@ -10,94 +10,57 @@ process { - cpus = { 1 * task.attempt } - memory = { 6.GB * task.attempt } - time = { 4.h * task.attempt } + // TODO nf-core: Check the defaults for all processes + cpus = { 1 * task.attempt } + memory = { 6.GB * task.attempt } + time = { 4.h * task.attempt } - // resourceLimits = [ cpus: 192, memory: 750.GB, time: 72.h ] - - // Retry signal-induced exits and "killed without exit code" cases: - // 130..145 = signal exits (SIGINT=130, SIGKILL=137, SIGTERM=143, etc.) - // 104 = ECONNRESET (transient network failures during stage-in/out) - // 2147483647 = Integer.MAX_VALUE, Nextflow's sentinel for tasks that died - // before writing .exitcode (Nextflow surfaces this as - // "terminated for an unknown reason -- Likely it has been - // terminated by the external system"). Common on AWS Batch - // spot capacity, kubernetes preemption, and grid-scheduler - // cancellations. See nextflow docs/aws.md for the AWS case. - errorStrategy = { task.exitStatus in ((130..145) + 104 + 2147483647) ? 'retry' : 'finish' } - maxRetries = 3 + errorStrategy = { task.exitStatus in ((130..145) + 104 + (175..177)) ? 'retry' : 'finish' } + maxRetries = 1 maxErrors = '-1' - // ========================================================================= - // Standard nf-core CPU labels - // ========================================================================= - + // Process-specific resource requirements + // NOTE - Please try and reuse the labels below as much as possible. + // These labels are used and recognised by default in DSL2 files hosted on nf-core/modules. + // If possible, it would be nice to keep the same label naming convention when + // adding in your local modules too. + // TODO nf-core: Customise requirements for specific processes. + // See https://www.nextflow.io/docs/latest/config.html#config-process-selectors withLabel:process_single { - cpus = { 1 } + cpus = { 1 } memory = { 6.GB * task.attempt } - time = { 4.h * task.attempt } + time = { 4.h * task.attempt } } - withLabel:process_low { - cpus = { 2 * task.attempt } + cpus = { 2 * task.attempt } memory = { 12.GB * task.attempt } - time = { 4.h * task.attempt } + time = { 4.h * task.attempt } } - withLabel:process_medium { - cpus = { 6 * task.attempt } - memory = { 42.GB * task.attempt } - time = { 8.h * task.attempt } + cpus = { 6 * task.attempt } + memory = { 36.GB * task.attempt } + time = { 8.h * task.attempt } } - withLabel:process_high { - cpus = { 12 * task.attempt } + cpus = { 12 * task.attempt } memory = { 72.GB * task.attempt } - time = { 16.h * task.attempt } + time = { 16.h * task.attempt } } - - withLabel:process_xl { - cpus = { 30 * task.attempt } - memory = { 240.GB * task.attempt } - time = { 24.h * task.attempt } - } - withLabel:process_long { - time = { 20.h * task.attempt } + time = { 20.h * task.attempt } } - withLabel:process_high_memory { memory = { 200.GB * task.attempt } } - withLabel:error_ignore { errorStrategy = 'ignore' } - withLabel:error_retry { errorStrategy = 'retry' maxRetries = 2 } - - // ========================================================================= - // GPU labels - // ========================================================================= - - // Multi-GPU processes (e.g., Segger train/predict) - withLabel:process_gpu { - ext.use_gpu = { params.use_gpu } - accelerator = { params.use_gpu ? 1 : null } - // containerOptions = { "--shm-size ${task.memory.toGiga().intValue()}g" } - } - - // Single-GPU processes (e.g., Cellpose, StarDist) - withLabel:process_gpu_single { - ext.use_gpu = { params.use_gpu } - accelerator = { params.use_gpu ? 1 : null } - cpus = { 12 * task.attempt } - memory = { 72.GB * task.attempt } - time = { 16.h * task.attempt } + withLabel: process_gpu { + ext.use_gpu = { workflow.profile.contains('gpu') } + accelerator = { workflow.profile.contains('gpu') ? 1 : null } } - } diff --git a/conf/igenomes.config b/conf/igenomes.config new file mode 100644 index 00000000..3f114377 --- /dev/null +++ b/conf/igenomes.config @@ -0,0 +1,440 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Nextflow config file for iGenomes paths +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Defines reference genomes using iGenome paths. + Can be used by any config that customises the base path using: + $params.igenomes_base / --igenomes_base +---------------------------------------------------------------------------------------- +*/ + +params { + // illumina iGenomes reference file paths + genomes { + 'GRCh37' { + fasta = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Annotation/README.txt" + mito_name = "MT" + macs_gsize = "2.7e9" + blacklist = "${projectDir}/assets/blacklists/GRCh37-blacklist.bed" + } + 'GRCh38' { + fasta = "${params.igenomes_base}/Homo_sapiens/NCBI/GRCh38/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Homo_sapiens/NCBI/GRCh38/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Homo_sapiens/NCBI/GRCh38/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Homo_sapiens/NCBI/GRCh38/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Homo_sapiens/NCBI/GRCh38/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Homo_sapiens/NCBI/GRCh38/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Homo_sapiens/NCBI/GRCh38/Annotation/Genes/genes.bed" + mito_name = "chrM" + macs_gsize = "2.7e9" + blacklist = "${projectDir}/assets/blacklists/hg38-blacklist.bed" + } + 'CHM13' { + fasta = "${params.igenomes_base}/Homo_sapiens/UCSC/CHM13/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Homo_sapiens/UCSC/CHM13/Sequence/BWAIndex/" + bwamem2 = "${params.igenomes_base}/Homo_sapiens/UCSC/CHM13/Sequence/BWAmem2Index/" + gtf = "${params.igenomes_base}/Homo_sapiens/NCBI/CHM13/Annotation/Genes/genes.gtf" + gff = "ftp://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/009/914/755/GCF_009914755.1_T2T-CHM13v2.0/GCF_009914755.1_T2T-CHM13v2.0_genomic.gff.gz" + mito_name = "chrM" + } + 'GRCm38' { + fasta = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Annotation/README.txt" + mito_name = "MT" + macs_gsize = "1.87e9" + blacklist = "${projectDir}/assets/blacklists/GRCm38-blacklist.bed" + } + 'TAIR10' { + fasta = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Annotation/README.txt" + mito_name = "Mt" + } + 'EB2' { + fasta = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Annotation/README.txt" + } + 'UMD3.1' { + fasta = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Annotation/README.txt" + mito_name = "MT" + } + 'WBcel235' { + fasta = "${params.igenomes_base}/Caenorhabditis_elegans/Ensembl/WBcel235/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Caenorhabditis_elegans/Ensembl/WBcel235/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Caenorhabditis_elegans/Ensembl/WBcel235/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Caenorhabditis_elegans/Ensembl/WBcel235/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Caenorhabditis_elegans/Ensembl/WBcel235/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Caenorhabditis_elegans/Ensembl/WBcel235/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Caenorhabditis_elegans/Ensembl/WBcel235/Annotation/Genes/genes.bed" + mito_name = "MtDNA" + macs_gsize = "9e7" + } + 'CanFam3.1' { + fasta = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Annotation/README.txt" + mito_name = "MT" + } + 'GRCz10' { + fasta = "${params.igenomes_base}/Danio_rerio/Ensembl/GRCz10/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Danio_rerio/Ensembl/GRCz10/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Danio_rerio/Ensembl/GRCz10/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Danio_rerio/Ensembl/GRCz10/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Danio_rerio/Ensembl/GRCz10/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Danio_rerio/Ensembl/GRCz10/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Danio_rerio/Ensembl/GRCz10/Annotation/Genes/genes.bed" + mito_name = "MT" + } + 'BDGP6' { + fasta = "${params.igenomes_base}/Drosophila_melanogaster/Ensembl/BDGP6/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Drosophila_melanogaster/Ensembl/BDGP6/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Drosophila_melanogaster/Ensembl/BDGP6/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Drosophila_melanogaster/Ensembl/BDGP6/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Drosophila_melanogaster/Ensembl/BDGP6/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Drosophila_melanogaster/Ensembl/BDGP6/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Drosophila_melanogaster/Ensembl/BDGP6/Annotation/Genes/genes.bed" + mito_name = "M" + macs_gsize = "1.2e8" + } + 'EquCab2' { + fasta = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Annotation/README.txt" + mito_name = "MT" + } + 'EB1' { + fasta = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Annotation/README.txt" + } + 'Galgal4' { + fasta = "${params.igenomes_base}/Gallus_gallus/Ensembl/Galgal4/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Gallus_gallus/Ensembl/Galgal4/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Gallus_gallus/Ensembl/Galgal4/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Gallus_gallus/Ensembl/Galgal4/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Gallus_gallus/Ensembl/Galgal4/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Gallus_gallus/Ensembl/Galgal4/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Gallus_gallus/Ensembl/Galgal4/Annotation/Genes/genes.bed" + mito_name = "MT" + } + 'Gm01' { + fasta = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Annotation/README.txt" + } + 'Mmul_1' { + fasta = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Annotation/README.txt" + mito_name = "MT" + } + 'IRGSP-1.0' { + fasta = "${params.igenomes_base}/Oryza_sativa_japonica/Ensembl/IRGSP-1.0/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Oryza_sativa_japonica/Ensembl/IRGSP-1.0/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Oryza_sativa_japonica/Ensembl/IRGSP-1.0/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Oryza_sativa_japonica/Ensembl/IRGSP-1.0/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Oryza_sativa_japonica/Ensembl/IRGSP-1.0/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Oryza_sativa_japonica/Ensembl/IRGSP-1.0/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Oryza_sativa_japonica/Ensembl/IRGSP-1.0/Annotation/Genes/genes.bed" + mito_name = "Mt" + } + 'CHIMP2.1.4' { + fasta = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Annotation/README.txt" + mito_name = "MT" + } + 'Rnor_5.0' { + fasta = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_5.0/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_5.0/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_5.0/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_5.0/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_5.0/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_5.0/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_5.0/Annotation/Genes/genes.bed" + mito_name = "MT" + } + 'Rnor_6.0' { + fasta = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_6.0/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_6.0/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_6.0/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_6.0/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_6.0/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_6.0/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_6.0/Annotation/Genes/genes.bed" + mito_name = "MT" + } + 'R64-1-1' { + fasta = "${params.igenomes_base}/Saccharomyces_cerevisiae/Ensembl/R64-1-1/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Saccharomyces_cerevisiae/Ensembl/R64-1-1/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Saccharomyces_cerevisiae/Ensembl/R64-1-1/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Saccharomyces_cerevisiae/Ensembl/R64-1-1/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Saccharomyces_cerevisiae/Ensembl/R64-1-1/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Saccharomyces_cerevisiae/Ensembl/R64-1-1/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Saccharomyces_cerevisiae/Ensembl/R64-1-1/Annotation/Genes/genes.bed" + mito_name = "MT" + macs_gsize = "1.2e7" + } + 'EF2' { + fasta = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Annotation/README.txt" + mito_name = "MT" + macs_gsize = "1.21e7" + } + 'Sbi1' { + fasta = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Annotation/README.txt" + } + 'Sscrofa10.2' { + fasta = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Annotation/README.txt" + mito_name = "MT" + } + 'AGPv3' { + fasta = "${params.igenomes_base}/Zea_mays/Ensembl/AGPv3/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Zea_mays/Ensembl/AGPv3/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Zea_mays/Ensembl/AGPv3/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Zea_mays/Ensembl/AGPv3/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Zea_mays/Ensembl/AGPv3/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Zea_mays/Ensembl/AGPv3/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Zea_mays/Ensembl/AGPv3/Annotation/Genes/genes.bed" + mito_name = "Mt" + } + 'hg38' { + fasta = "${params.igenomes_base}/Homo_sapiens/UCSC/hg38/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Homo_sapiens/UCSC/hg38/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Homo_sapiens/UCSC/hg38/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Homo_sapiens/UCSC/hg38/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Homo_sapiens/UCSC/hg38/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Homo_sapiens/UCSC/hg38/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Homo_sapiens/UCSC/hg38/Annotation/Genes/genes.bed" + mito_name = "chrM" + macs_gsize = "2.7e9" + blacklist = "${projectDir}/assets/blacklists/hg38-blacklist.bed" + } + 'hg19' { + fasta = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Annotation/README.txt" + mito_name = "chrM" + macs_gsize = "2.7e9" + blacklist = "${projectDir}/assets/blacklists/hg19-blacklist.bed" + } + 'mm10' { + fasta = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Annotation/README.txt" + mito_name = "chrM" + macs_gsize = "1.87e9" + blacklist = "${projectDir}/assets/blacklists/mm10-blacklist.bed" + } + 'bosTau8' { + fasta = "${params.igenomes_base}/Bos_taurus/UCSC/bosTau8/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Bos_taurus/UCSC/bosTau8/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Bos_taurus/UCSC/bosTau8/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Bos_taurus/UCSC/bosTau8/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Bos_taurus/UCSC/bosTau8/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Bos_taurus/UCSC/bosTau8/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Bos_taurus/UCSC/bosTau8/Annotation/Genes/genes.bed" + mito_name = "chrM" + } + 'ce10' { + fasta = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Annotation/README.txt" + mito_name = "chrM" + macs_gsize = "9e7" + } + 'canFam3' { + fasta = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Annotation/README.txt" + mito_name = "chrM" + } + 'danRer10' { + fasta = "${params.igenomes_base}/Danio_rerio/UCSC/danRer10/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Danio_rerio/UCSC/danRer10/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Danio_rerio/UCSC/danRer10/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Danio_rerio/UCSC/danRer10/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Danio_rerio/UCSC/danRer10/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Danio_rerio/UCSC/danRer10/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Danio_rerio/UCSC/danRer10/Annotation/Genes/genes.bed" + mito_name = "chrM" + macs_gsize = "1.37e9" + } + 'dm6' { + fasta = "${params.igenomes_base}/Drosophila_melanogaster/UCSC/dm6/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Drosophila_melanogaster/UCSC/dm6/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Drosophila_melanogaster/UCSC/dm6/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Drosophila_melanogaster/UCSC/dm6/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Drosophila_melanogaster/UCSC/dm6/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Drosophila_melanogaster/UCSC/dm6/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Drosophila_melanogaster/UCSC/dm6/Annotation/Genes/genes.bed" + mito_name = "chrM" + macs_gsize = "1.2e8" + } + 'equCab2' { + fasta = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Annotation/README.txt" + mito_name = "chrM" + } + 'galGal4' { + fasta = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Annotation/README.txt" + mito_name = "chrM" + } + 'panTro4' { + fasta = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Annotation/README.txt" + mito_name = "chrM" + } + 'rn6' { + fasta = "${params.igenomes_base}/Rattus_norvegicus/UCSC/rn6/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Rattus_norvegicus/UCSC/rn6/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Rattus_norvegicus/UCSC/rn6/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Rattus_norvegicus/UCSC/rn6/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Rattus_norvegicus/UCSC/rn6/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Rattus_norvegicus/UCSC/rn6/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Rattus_norvegicus/UCSC/rn6/Annotation/Genes/genes.bed" + mito_name = "chrM" + } + 'sacCer3' { + fasta = "${params.igenomes_base}/Saccharomyces_cerevisiae/UCSC/sacCer3/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Saccharomyces_cerevisiae/UCSC/sacCer3/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Saccharomyces_cerevisiae/UCSC/sacCer3/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Saccharomyces_cerevisiae/UCSC/sacCer3/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Saccharomyces_cerevisiae/UCSC/sacCer3/Sequence/BismarkIndex/" + readme = "${params.igenomes_base}/Saccharomyces_cerevisiae/UCSC/sacCer3/Annotation/README.txt" + mito_name = "chrM" + macs_gsize = "1.2e7" + } + 'susScr3' { + fasta = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Annotation/README.txt" + mito_name = "chrM" + } + } +} diff --git a/conf/igenomes_ignored.config b/conf/igenomes_ignored.config new file mode 100644 index 00000000..b4034d82 --- /dev/null +++ b/conf/igenomes_ignored.config @@ -0,0 +1,9 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Nextflow config file for iGenomes paths +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Empty genomes dictionary to use when igenomes is ignored. +---------------------------------------------------------------------------------------- +*/ + +params.genomes = [:] diff --git a/conf/modules.config b/conf/modules.config index bb5a8786..d203d2b6 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -15,344 +15,20 @@ process { publishDir = [ path: { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" }, mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] - // ---------------------------- multiqc --------------------------------------------------- - - withName: 'MULTIQC|MULTIQC_PRE_XR_RUN|MULTIQC_POST_XR_RUN' { - errorStrategy = 'ignore' - } - - withName: MULTIQC { - ext.args = { params.multiqc_title ? "--title \"${params.multiqc_title}\"" : '' } - publishDir = [ - path: { "${params.outdir}/${params.mode}/multiqc" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, - ] - } - - withName: MULTIQC_PRE_XR_RUN { - ext.args = { "--title \"${params.multiqc_title ?: 'MultiQC Pre Xeniumranger import-segmentation Run'}\"" } - publishDir = [ - path: { "${params.outdir}/${params.mode}/multiqc/raw_bundle" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, - ] - } - - withName: MULTIQC_POST_XR_RUN { - ext.args = { "--title \"${params.multiqc_title ?: 'MultiQC Post Xeniumranger import-segmentation Run'}\"" } - publishDir = [ - path: { "${params.outdir}/${params.mode}/multiqc/redefined_bundle" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, - ] - } - - - // ---------------------------- xeniumranger --------------------------------------------------- - - // XeniumRanger: must use local scratch for large output bundles - withName:".*XENIUMRANGER.*" { - scratch = true - } - - // scratch=true is set in base.config via withName:".*XENIUMRANGER.*" - withName: XENIUMRANGER_RELABEL { - publishDir = [ - path: "${params.outdir}/${params.mode}/xeniumranger/relabel", - mode: params.publish_dir_mode, - ] - } - - withName: XENIUMRANGER_RESEGMENT { - publishDir = [ - path: "${params.outdir}/${params.mode}/xeniumranger/resegment", - mode: params.publish_dir_mode, - ] - } - - withName: XENIUMRANGER_IMPORT_SEGMENTATION { - publishDir = [ - path: "${params.outdir}/${params.mode}/xeniumranger/import_segementation", - mode: params.publish_dir_mode, - ] - } - - // ---------------------------- proseg --------------------------------------------------- - - withName: PROSEG { - publishDir = [ - path: "${params.outdir}/${params.mode}/proseg/preset", - mode: params.publish_dir_mode, - ] - } - - withName: PROSEG2BAYSOR { - publishDir = [ - path: "${params.outdir}/${params.mode}/proseg/proseg2baysor", - mode: params.publish_dir_mode, - ] - } - - // ---------------------------- baysor --------------------------------------------------- - - withName: BAYSOR_RUN { - memory = { params.baysor_tiling ? 240.GB * task.attempt : 720.GB } - ext.args = "--min-molecules-per-cell ${params.baysor_tiling ? params.baysor_tiling_min_mols_per_cell : 30} --x-column x_location --y-column y_location --z-column z_location --gene-column feature_name" - ext.prior_column = params.baysor_prior == 'cells' ? 'cell_id' : null - ext.prior_confidence = params.baysor_prior != null ? params.baysor_prior_confidence : null - publishDir = [ - path: { "${params.outdir}/${params.mode}/baysor/run" }, - mode: params.publish_dir_mode, - ] - } - - withName: BAYSOR_SEGFREE { - memory = { 720.GB } - publishDir = [ - path: { "${params.outdir}/${params.mode}/baysor/segfree" }, - mode: params.publish_dir_mode, - ] - } - - withName: BAYSOR_CREATE_DATASET { - publishDir = [ - path: { "${params.outdir}/${params.mode}/baysor/create_dataset" }, - mode: params.publish_dir_mode, - ] - } - - withName: BAYSOR_PREPROCESS_TRANSCRIPTS { - publishDir = [ - path: { "${params.outdir}/${params.mode}/baysor/preprocess" }, - mode: params.publish_dir_mode, - ] + withName: FASTQC { + ext.args = '--quiet' } - withName: BAYSOR_PREVIEW { - memory = { 240.GB * task.attempt } + withName: 'MULTIQC' { + ext.args = { params.multiqc_title ? "--title \"$params.multiqc_title\"" : '' } publishDir = [ - path: { "${params.outdir}/${params.mode}/baysor/preview" }, - mode: params.publish_dir_mode, - ] - } - - // ---------------------------- xenium_patch (tiling) ------------------------------------ - - withName: 'XENIUM_PATCH_DIVIDE' { - ext.tile_width = params.baysor_tiling_micron - ext.overlap = params.baysor_tiling_overlap - ext.balanced = params.baysor_tiling_balanced - publishDir = [ - path: { "${params.outdir}/${meta.id}/xenium_patch" }, + path: { "${params.outdir}/multiqc" }, mode: params.publish_dir_mode, saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] } - withName: 'XENIUM_PATCH_STITCH' { - ext.filter_method = params.patch_filter_method ?: null - ext.iqr_multiplier = params.patch_filter_iqr_multiplier - ext.z_threshold = params.patch_filter_z_threshold - ext.args = { "--min-transcripts-per-cell ${params.baysor_tiling_min_transcripts_per_cell}" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/xenium_patch" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - - // ---------------------------- segger --------------------------------------------------- - - withName: SEGGER_CREATE_DATASET { - publishDir = [ - path: { "${params.outdir}/${params.mode}/segger/create_dataset" }, - mode: params.publish_dir_mode, - ] - } - - withName: SEGGER_TRAIN { - publishDir = [ - path: { "${params.outdir}/${params.mode}/segger/train" }, - mode: params.publish_dir_mode, - ] - ext.args = { "--init_emb 8 --hidden_channels 32 --num_tx_tokens 10000 --out_channels 8 --heads 2 --num_mid_layers 2 --strategy auto --precision bf16-mixed" } - } - - withName: SEGGER_PREDICT { - publishDir = [ - path: { "${params.outdir}/${params.mode}/segger/predict" }, - mode: params.publish_dir_mode, - // Skip partitioned parquet dirs (Hive-style) that S3 copy can't handle - saveAs: { filename -> filename.contains('transcripts_df.parquet') ? null : filename }, - ] - } - - // ---------------------------- ficture ------------------------------------------ - - withName: FICTURE_PREPROCESS { - publishDir = [ - path: "${params.outdir}/${params.mode}/ficture/preprocess", - mode: params.publish_dir_mode, - ] - } - - // ---------------------------- utility modules ----------------------------------- - - - withName: UNTAR { - publishDir = [ - path: { "${params.outdir}/${params.mode}/untar/" }, - mode: params.publish_dir_mode, - ] - } - - withName: RESOLIFT { - publishDir = [ - path: { "${params.outdir}/${params.mode}/resolift/" }, - mode: params.publish_dir_mode, - ] - } - - withName: PARQUET_TO_CSV { - publishDir = [ - path: { "${params.outdir}/${params.mode}/utility/parquet_to_csv" }, - mode: params.publish_dir_mode, - ] - } - - withName: EXTRACT_PREVIEW_DATA { - publishDir = [ - path: { "${params.outdir}/${params.mode}/utility/preview_data/" }, - mode: params.publish_dir_mode, - ] - } - - withName: GET_TRANSCRIPTS_COORDINATES { - publishDir = [ - path: { "${params.outdir}/${params.mode}/utility/get_coordinates/" }, - mode: params.publish_dir_mode, - ] - } - - withName: RESIZE_TIF { - publishDir = [ - path: { "${params.outdir}/${params.mode}/utility/resize_tif/" }, - mode: params.publish_dir_mode, - ] - } - - withName: SEGGER2XR { - publishDir = [ - path: { "${params.outdir}/${params.mode}/utility/segger2xr/" }, - mode: params.publish_dir_mode, - ] - } - - withName: SPLIT_TRANSCRIPTS { - publishDir = [ - path: { "${params.outdir}/${params.mode}/utility/split_transcripts/" }, - mode: params.publish_dir_mode, - ] - } - - // ---------------------------- spatialdata -------------------------------------- - - withName: SPATIALDATA_WRITE { - publishDir = [ - path: { "${params.outdir}/${params.mode}/spatialdata/write" }, - mode: params.publish_dir_mode, - ] - } - - withName: SPATIALDATA_MERGE { - publishDir = [ - path: { "${params.outdir}/${params.mode}/spatialdata/merge" }, - mode: params.publish_dir_mode, - ] - } - - withName: SPATIALDATA_META { - publishDir = [ - path: { "${params.outdir}/${params.mode}/spatialdata/meta" }, - mode: params.publish_dir_mode, - ] - } - - // ---------------------------- cellpose ----------------------------------------- - - // GPU is auto-detected via task.accelerator in the official nf-core cellpose module - withName: CELLPOSE { - publishDir = [ - path: { "${params.outdir}/${params.mode}/cellpose" }, - mode: params.publish_dir_mode, - ] - ext.args = "--flow_threshold 0 --batch_size 1" - } - - withName: CELLPOSE_CELLS { - publishDir = [ - path: { "${params.outdir}/${params.mode}/cellpose_cells" }, - mode: params.publish_dir_mode, - ] - ext.args = "--flow_threshold 0 --batch_size 1" - } - - // ---------------------------- stardist ----------------------------------------- - - withName: '.*STARDIST.*' { - ext.args = {[ - params.stardist_prob_thresh != null ? "--prob_thresh ${params.stardist_prob_thresh}" : "", - params.stardist_nms_thresh != null ? "--nms_thresh ${params.stardist_nms_thresh}" : "", - params.stardist_n_tiles != null ? "--n_tiles ${params.stardist_n_tiles}" : "", - ].join(' ').trim()} - } - - withName: 'STARDIST_NUCLEI' { - publishDir = [ - path: { "${params.outdir}/${params.mode}/stardist_nuclei" }, - mode: params.publish_dir_mode, - ] - } - - // StarDist preprocessing/postprocessing utilities - withName: '.*EXTRACT_DAPI.*' { - publishDir = [ - path: { "${params.outdir}/${params.mode}/extract_dapi" }, - mode: params.publish_dir_mode, - ] - } - - withName: '.*CONVERT_MASK_UINT32.*|.*CONVERT_CELLS_MASK.*|.*CONVERT_NUCLEI_MASK.*' { - publishDir = [ - path: { "${params.outdir}/${params.mode}/convert_mask" }, - mode: params.publish_dir_mode, - ] - } - - // ---------------------------- opt ----------------------------------------- - - withName: OPT_FLIP { - publishDir = [ - path: { "${params.outdir}/${params.mode}/opt/flip" }, - mode: params.publish_dir_mode, - ] - } - - withName: OPT_TRACK { - publishDir = [ - path: { "${params.outdir}/${params.mode}/opt/track" }, - mode: params.publish_dir_mode, - ] - } - - withName: OPT_STAT { - publishDir = [ - path: { "${params.outdir}/${params.mode}/opt/stat" }, - mode: params.publish_dir_mode, - ] - } } diff --git a/conf/test.config b/conf/test.config index bae5a73f..bfcc3864 100644 --- a/conf/test.config +++ b/conf/test.config @@ -5,23 +5,17 @@ Defines input files and everything required to run a fast and simple pipeline test. Use as follows: - nextflow run nf-core/spatialxe -profile test, --mode --outdir + nextflow run nf-core/spatialxe -profile test, --outdir ---------------------------------------------------------------------------------------- */ process { - resourceLimits = [ cpus: 4, - memory: '8.GB', - time: '2.h', + memory: '15.GB', + time: '1.h' ] - - withName: UNTAR { - ext.prefix = "test_run" - } - } params { @@ -29,7 +23,8 @@ params { config_profile_description = 'Minimal test dataset to check pipeline function' // Input data - input = "${projectDir}/assets/samplesheet.csv" - outdir = 'results' - mode = 'coordinate' + // TODO nf-core: Specify the paths to your test data on nf-core/test-datasets + // TODO nf-core: Give any required params for the test so that command line flags are not needed + input = params.pipelines_testdata_base_path + 'viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv'// Genome references + genome = 'R64-1-1' } diff --git a/conf/test_coordinate_mode.config b/conf/test_coordinate_mode.config deleted file mode 100644 index e8338a5c..00000000 --- a/conf/test_coordinate_mode.config +++ /dev/null @@ -1,31 +0,0 @@ -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Nextflow config file for running minimal tests -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Defines input files and everything required to run a fast and simple pipeline test. - - Use as follows: - nextflow run nf-core/spatialxe -profile test, --mode --outdir - ----------------------------------------------------------------------------------------- -*/ - -process { - - resourceLimits = [ - cpus: 4, - memory: '8.GB', - time: '2.h', - ] - -} - -params { - config_profile_name = 'Test profile coordinate mode' - config_profile_description = 'Minimal test dataset to check pipeline function in the coordinate mode' - - // Input data - input = "${projectDir}/assets/samplesheet.csv" - outdir = 'results' - mode = 'coordinate' -} diff --git a/conf/test_full.config b/conf/test_full.config index 9e0980c4..4ddba9a2 100644 --- a/conf/test_full.config +++ b/conf/test_full.config @@ -14,8 +14,11 @@ params { config_profile_name = 'Full test profile' config_profile_description = 'Full test dataset to check pipeline function' - // Input data - input = "${projectDir}/assets/samplesheet.csv" - outdir = 'results' - mode = 'coordinate' + // Input data for full size test + // TODO nf-core: Specify the paths to your full test data ( on nf-core/test-datasets or directly in repositories, e.g. SRA) + // TODO nf-core: Give any required params for the test so that command line flags are not needed + input = params.pipelines_testdata_base_path + 'viralrecon/samplesheet/samplesheet_full_illumina_amplicon.csv' + + // Genome references + genome = 'R64-1-1' } diff --git a/conf/test_image_mode.config b/conf/test_image_mode.config deleted file mode 100644 index 17e8124a..00000000 --- a/conf/test_image_mode.config +++ /dev/null @@ -1,31 +0,0 @@ -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Nextflow config file for running minimal tests -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Defines input files and everything required to run a fast and simple pipeline test. - - Use as follows: - nextflow run nf-core/spatialxe -profile test, --mode --outdir - ----------------------------------------------------------------------------------------- -*/ - -process { - - resourceLimits = [ - cpus: 4, - memory: '8.GB', - time: '2.h', - ] - -} - -params { - config_profile_name = 'Test profile image mode' - config_profile_description = 'Minimal test dataset to check pipeline function in the image mode' - - // Input data - input = "${projectDir}/assets/samplesheet.csv" - outdir = 'results' - mode = 'image' -} diff --git a/conf/test_preview_mode.config b/conf/test_preview_mode.config deleted file mode 100644 index 144a2349..00000000 --- a/conf/test_preview_mode.config +++ /dev/null @@ -1,31 +0,0 @@ -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Nextflow config file for running minimal tests -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Defines input files and everything required to run a fast and simple pipeline test. - - Use as follows: - nextflow run nf-core/spatialxe -profile test, --mode --outdir - ----------------------------------------------------------------------------------------- -*/ - -process { - - resourceLimits = [ - cpus: 4, - memory: '8.GB', - time: '2.h', - ] - -} - -params { - config_profile_name = 'Test profile preview mode' - config_profile_description = 'Minimal test dataset to check pipeline function in the preview mode' - - // Input data - input = "${projectDir}/assets/samplesheet.csv" - outdir = 'results' - mode = 'preview' -} diff --git a/conf/test_segfree_mode.config b/conf/test_segfree_mode.config deleted file mode 100644 index 5f4467b5..00000000 --- a/conf/test_segfree_mode.config +++ /dev/null @@ -1,31 +0,0 @@ -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Nextflow config file for running minimal tests -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Defines input files and everything required to run a fast and simple pipeline test. - - Use as follows: - nextflow run nf-core/spatialxe -profile test, --mode --outdir - ----------------------------------------------------------------------------------------- -*/ - -process { - - resourceLimits = [ - cpus: 4, - memory: '8.GB', - time: '2.h', - ] - -} - -params { - config_profile_name = 'Test profile segfree mode' - config_profile_description = 'Minimal test dataset to check pipeline function in the segfree mode' - - // Input data - input = "${projectDir}/assets/samplesheet.csv" - outdir = 'results' - mode = 'segfree' -} diff --git a/docs/images/nf-core-spatialxe_logo_dark.png b/docs/images/nf-core-spatialxe_logo_dark.png index 927c3bfbf7b3c96de8ac0b75b1991aef896da6ad..6a6a051e61ced495a082297fde51a19c3da6a749 100644 GIT binary patch literal 30543 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%0A>XT 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

(t<7(*HiX?A?;q*C6E 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 diff --git a/assets/samplesheet.csv b/assets/samplesheet.csv index f5710dab..5f653ab7 100644 --- a/assets/samplesheet.csv +++ b/assets/samplesheet.csv @@ -1,2 +1,3 @@ -sample,bundle,image -test_run,https://raw.githubusercontent.com/nf-core/test-datasets/spatialxe/xenium_bundle.tar.gz +sample,fastq_1,fastq_2 +SAMPLE_PAIRED_END,/path/to/fastq/files/AEG588A1_S1_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A1_S1_L002_R2_001.fastq.gz +SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz, diff --git a/assets/schema_input.json b/assets/schema_input.json index 2dfb357a..7435ad64 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -13,17 +13,21 @@ "errorMessage": "Sample name must be provided and cannot contain spaces", "meta": ["id"] }, - "bundle": { + "fastq_1": { "type": "string", - "pattern": "^\\S+$", - "errorMessage": "Please provide a bundle as input data" + "format": "file-path", + "exists": true, + "pattern": "^([\\S\\s]*\\/)?[^\\s\\/]+\\.f(ast)?q\\.gz$", + "errorMessage": "FastQ file for reads 1 must be provided, cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" }, - "image": { + "fastq_2": { "type": "string", - "pattern": "^\\S+$", - "errorMessage": "You can provide an image. If you do not then please leave the field empty." + "format": "file-path", + "exists": true, + "pattern": "^([\\S\\s]*\\/)?[^\\s\\/]+\\.f(ast)?q\\.gz$", + "errorMessage": "FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" } }, - "required": ["sample", "bundle"] + "required": ["sample", "fastq_1"] } } diff --git a/bin/baysor_create_dataset.py b/bin/baysor_create_dataset.py deleted file mode 100755 index 4e5a263a..00000000 --- a/bin/baysor_create_dataset.py +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env python3 -""" -Create a sampled dataset for Baysor preview mode. - -Reads a CSV transcript file and randomly samples a fraction of rows, -writing the result to a new CSV file. -""" - -import argparse -import csv -import os -import random -from pathlib import Path - - -class BaysorPreview(): - """ - Utility class to generate baysor preview dataset - """ - @staticmethod - def generate_dataset( - transcripts: Path, - sampled_transcripts: Path, - sample_fraction: float = 0.3, - random_state: int = 42, - prefix: str = "" - ) -> None: - """ - Reads a csv file & randomly samples a fraction of rows, - and writes the result to a .csv file. - - Args: - transcripts: unziped transcripts.csv from xenium bundle - sampled_transcripts: randomly subsampled transcripts.csv file - sample_fraction: Fraction of rows to sample - random_state: Seed for reproducibility - prefix: Output directory prefix - """ - - random.seed(random_state) - output_path = f"{prefix}/{sampled_transcripts}" - os.makedirs(os.path.dirname(output_path), exist_ok=True) - with open(transcripts, mode='rt', newline='') as infile, \ - open(output_path, mode='wt', newline='') as outfile: - - reader = csv.reader(infile) - writer = csv.writer(outfile) - - # get the header line - header = next(reader) - writer.writerow(header) - - # randomize csv rows to write - for row in reader: - if random.random() < float(sample_fraction): - writer.writerow(row) - - return None - - -def main() -> None: - """ - Run create dataset as nf module - """ - parser = argparse.ArgumentParser( - description="Create sampled dataset for Baysor preview" - ) - parser.add_argument( - "--transcripts", required=True, - help="Path to transcripts CSV file" - ) - parser.add_argument( - "--sample-fraction", required=True, type=float, - help="Fraction of rows to sample" - ) - parser.add_argument( - "--prefix", required=True, - help="Output directory prefix" - ) - args = parser.parse_args() - - sampled_transcripts = "sampled_transcripts.csv" - - # generate dataset - BaysorPreview.generate_dataset( - transcripts=args.transcripts, - sampled_transcripts=sampled_transcripts, - sample_fraction=args.sample_fraction, - prefix=args.prefix - ) - - return None - - -if __name__ == "__main__": - main() diff --git a/bin/baysor_preprocess_transcripts.py b/bin/baysor_preprocess_transcripts.py deleted file mode 100755 index 2662f83c..00000000 --- a/bin/baysor_preprocess_transcripts.py +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env python3 -""" -Preprocess Xenium transcripts for Baysor segmentation. - -Filters transcripts based on quality score and spatial coordinate thresholds, -removes negative control probes, and outputs filtered CSV for Baysor compatibility. -""" - -import argparse -import os - -import pandas as pd - - -def filter_transcripts( - transcripts: str, - min_qv: float = 20.0, - min_x: float = 0.0, - max_x: float = 24000.0, - min_y: float = 0.0, - max_y: float = 24000.0, - prefix: str = "", -) -> None: - """ - Filter transcripts based on the specified thresholds. - - Args: - transcripts: Path to transcripts parquet file - min_qv: Minimum Q-Score to pass filtering - min_x: Minimum x-coordinate threshold - max_x: Maximum x-coordinate threshold - min_y: Minimum y-coordinate threshold - max_y: Maximum y-coordinate threshold - prefix: Output directory prefix - """ - df = pd.read_parquet(transcripts, engine="pyarrow") - - # filter transcripts df with thresholds, ignore negative controls - filtered_df = df[ - (df["qv"] >= min_qv) - & (df["x_location"] >= min_x) - & (df["x_location"] <= max_x) - & (df["y_location"] >= min_y) - & (df["y_location"] <= max_y) - & (~df["feature_name"].str.startswith("NegControlProbe_")) - & (~df["feature_name"].str.startswith("antisense_")) - & (~df["feature_name"].str.startswith("NegControlCodeword_")) - & (~df["feature_name"].str.startswith("BLANK_")) - ] - - # change cell_id of cell-free transcripts to "0" (Baysor's no-cell sentinel). - # Modern Xenium stores cell_id as a string ("UNASSIGNED" for cell-free transcripts); - # legacy Xenium used integer -1. Normalize to string and handle both cases — pandas 3 - # rejects mixing int values into a string-dtype column. - filtered_df["cell_id"] = filtered_df["cell_id"].astype(str) - neg_cell_row = filtered_df["cell_id"].isin(["-1", "UNASSIGNED"]) - filtered_df.loc[neg_cell_row, "cell_id"] = "0" - - # Output filtered transcripts as CSV for Baysor 0.7.1 compatibility. - # Baysor's Julia Parquet.jl cannot read modern pyarrow Parquet files - # (pyarrow 15+ writes size_statistics Thrift field 16 unconditionally, - # which Baysor's old Thrift deserializer doesn't recognize). - os.makedirs(prefix, exist_ok=True) - filtered_df.to_csv(f"{prefix}/filtered_transcripts.csv", index=False) - - return None - - -def main() -> None: - """ - Run preprocess transcripts as nf module. - """ - parser = argparse.ArgumentParser( - description="Preprocess Xenium transcripts for Baysor" - ) - parser.add_argument( - "--transcripts", required=True, help="Path to transcripts parquet file" - ) - parser.add_argument("--prefix", required=True, help="Output directory prefix") - parser.add_argument( - "--min-qv", - type=float, - default=20.0, - help="Minimum Q-Score threshold (default: 20.0)", - ) - parser.add_argument( - "--min-x", - type=float, - default=0.0, - help="Minimum x-coordinate threshold (default: 0.0)", - ) - parser.add_argument( - "--max-x", - type=float, - default=24000.0, - help="Maximum x-coordinate threshold (default: 24000.0)", - ) - parser.add_argument( - "--min-y", - type=float, - default=0.0, - help="Minimum y-coordinate threshold (default: 0.0)", - ) - parser.add_argument( - "--max-y", - type=float, - default=24000.0, - help="Maximum y-coordinate threshold (default: 24000.0)", - ) - args = parser.parse_args() - - filter_transcripts( - transcripts=args.transcripts, - min_qv=args.min_qv, - min_x=args.min_x, - max_x=args.max_x, - min_y=args.min_y, - max_y=args.max_y, - prefix=args.prefix, - ) - - return None - - -if __name__ == "__main__": - main() diff --git a/bin/divide_transcripts.py b/bin/divide_transcripts.py deleted file mode 100755 index 133fcede..00000000 --- a/bin/divide_transcripts.py +++ /dev/null @@ -1,1312 +0,0 @@ -#!/usr/bin/env python3 -"""Divide a Xenium transcripts.parquet file into spatial patches for tiled segmentation. - -Standalone script — no imports from xenium_patch or any local package. -Only uses stdlib + pyarrow + numpy. - -Two grid modes: - - Uniform (default): equal-sized tiles based on --tile-width - - Quadtree (--balanced): starts uniform, recursively subdivides dense tiles -""" - -from __future__ import annotations - -import argparse -import json -import math -import os -from concurrent.futures import ThreadPoolExecutor -from dataclasses import dataclass -from pathlib import Path - -import numpy as np -import pyarrow as pa -import pyarrow.compute as pc -import pyarrow.parquet as pq - -# --------------------------------------------------------------------------- -# Constants -# --------------------------------------------------------------------------- - -XENIUM_PIXEL_SIZE_UM: float = 0.2125 - -TRANSCRIPT_COLS = [ - "transcript_id", - "cell_id", - "overlaps_nucleus", - "feature_name", - "x_location", - "y_location", - "z_location", - "qv", -] - -# Quadtree defaults -QUADTREE_MIN_TILE_WIDTH_UM: float = 200.0 -QUADTREE_MAX_DEPTH: int = 4 -QUADTREE_HISTOGRAM_BINS: int = 500 - -# --------------------------------------------------------------------------- -# Data types -# --------------------------------------------------------------------------- - - -@dataclass(frozen=True) -class Bounds: - """Axis-aligned bounding box in either pixel or micron coordinates.""" - - x_min: float - x_max: float - y_min: float - y_max: float - - @property - def width(self) -> float: - return self.x_max - self.x_min - - @property - def height(self) -> float: - return self.y_max - self.y_min - - -@dataclass(frozen=True) -class PatchInfo: - """Metadata for a single patch in the grid.""" - - patch_id: str - row: int - col: int - global_bounds_px: Bounds - global_bounds_um: Bounds - core_bounds_px: Bounds - core_bounds_um: Bounds - - -# --------------------------------------------------------------------------- -# Grid computation — uniform -# --------------------------------------------------------------------------- - - -def _compute_uniform_grid( - image_height_px: int, - image_width_px: int, - grid_rows: int, - grid_cols: int, - overlap_px: int, - pixel_size_um: float, -) -> list[PatchInfo]: - """ - Compute a regular NxM grid of overlapping patches. - - Grid is computed in pixel space. Each patch overlaps its neighbors by - overlap_px pixels. Core regions are computed such that every pixel - belongs to exactly one core. - - Args: - image_height_px: Image height in pixels. - image_width_px: Image width in pixels. - grid_rows: Number of rows in the patch grid. - grid_cols: Number of columns in the patch grid. - overlap_px: Overlap between adjacent patches in pixels. - pixel_size_um: Microns per pixel. - - Returns: - List of PatchInfo for every patch. - """ - step_x = (image_width_px - overlap_px) / grid_cols - step_y = (image_height_px - overlap_px) / grid_rows - - patches: list[PatchInfo] = [] - for row in range(grid_rows): - for col in range(grid_cols): - x_min_px = int(round(col * step_x)) - y_min_px = int(round(row * step_y)) - x_max_px = min( - int(round(col * step_x + step_x + overlap_px)), image_width_px - ) - y_max_px = min( - int(round(row * step_y + step_y + overlap_px)), image_height_px - ) - - global_bounds_px = Bounds(x_min_px, x_max_px, y_min_px, y_max_px) - - # Core bounds: trim half-overlap from sides that have neighbors - half_overlap = overlap_px // 2 - remainder = overlap_px % 2 - core_x_min = x_min_px + (half_overlap + remainder if col > 0 else 0) - core_x_max = x_max_px - (half_overlap if col < grid_cols - 1 else 0) - core_y_min = y_min_px + (half_overlap + remainder if row > 0 else 0) - core_y_max = y_max_px - (half_overlap if row < grid_rows - 1 else 0) - - core_bounds_px = Bounds(core_x_min, core_x_max, core_y_min, core_y_max) - - global_bounds_um = Bounds( - x_min_px * pixel_size_um, - x_max_px * pixel_size_um, - y_min_px * pixel_size_um, - y_max_px * pixel_size_um, - ) - core_bounds_um = Bounds( - core_x_min * pixel_size_um, - core_x_max * pixel_size_um, - core_y_min * pixel_size_um, - core_y_max * pixel_size_um, - ) - - patches.append( - PatchInfo( - patch_id=f"patch_{row}_{col}", - row=row, - col=col, - global_bounds_px=global_bounds_px, - global_bounds_um=global_bounds_um, - core_bounds_px=core_bounds_px, - core_bounds_um=core_bounds_um, - ) - ) - - return patches - - -def compute_tilewidth_uniform_grid( - image_height_px: int, - image_width_px: int, - tile_width_um: float, - overlap_um: float, - pixel_size_um: float, - transcript_extent_um: Bounds, -) -> tuple[list[PatchInfo], int, int, int]: - """ - Compute a uniform grid from a tile width in microns. - - Args: - image_height_px: Image height in pixels. - image_width_px: Image width in pixels. - tile_width_um: Desired tile width in microns. - overlap_um: Overlap between adjacent patches in microns. - pixel_size_um: Size of one pixel in microns. - transcript_extent_um: Bounding box of transcript coordinates. - - Returns: - Tuple of (patches, grid_rows, grid_cols, overlap_px). - """ - image_width_um = image_width_px * pixel_size_um - image_height_um = image_height_px * pixel_size_um - cols = max(1, math.ceil(image_width_um / tile_width_um)) - rows = max(1, math.ceil(image_height_um / tile_width_um)) - overlap_px = int(math.ceil(overlap_um / pixel_size_um)) - - patches = _compute_uniform_grid( - image_height_px, image_width_px, rows, cols, overlap_px, pixel_size_um - ) - return patches, rows, cols, overlap_px - - -# --------------------------------------------------------------------------- -# Grid computation — density quadtree -# --------------------------------------------------------------------------- - - -def _build_prefix_sum( - x_coords_um: np.ndarray, - y_coords_um: np.ndarray, - n_bins: int = QUADTREE_HISTOGRAM_BINS, -) -> tuple[np.ndarray, np.ndarray, np.ndarray]: - """ - Build a 2D histogram and its prefix sum for fast rectangle count queries. - - Args: - x_coords_um: Transcript X coordinates in microns. - y_coords_um: Transcript Y coordinates in microns. - n_bins: Number of bins along each axis. - - Returns: - Tuple of (prefix_sum, x_edges, y_edges). - """ - x_min, x_max = float(np.min(x_coords_um)), float(np.max(x_coords_um)) - y_min, y_max = float(np.min(y_coords_um)), float(np.max(y_coords_um)) - - eps = 1e-6 - x_edges = np.linspace(x_min, x_max + eps, n_bins + 1) - y_edges = np.linspace(y_min, y_max + eps, n_bins + 1) - - hist, _, _ = np.histogram2d(x_coords_um, y_coords_um, bins=[x_edges, y_edges]) - # hist shape is (n_bins_x, n_bins_y), transpose to (y, x) for row-major access - hist = hist.T - - prefix_sum = np.cumsum(np.cumsum(hist, axis=0), axis=1) - return prefix_sum, x_edges, y_edges - - -def _count_transcripts_in_rect( - prefix_sum: np.ndarray, - x_edges: np.ndarray, - y_edges: np.ndarray, - x_min_um: float, - x_max_um: float, - y_min_um: float, - y_max_um: float, -) -> int: - """ - Count transcripts in a rectangle using a 2D prefix sum array. - - Args: - prefix_sum: 2D cumulative sum array (n_bins_y x n_bins_x). - x_edges: Histogram bin edges along X. - y_edges: Histogram bin edges along Y. - x_min_um: Left bound in microns. - x_max_um: Right bound in microns. - y_min_um: Top bound in microns. - y_max_um: Bottom bound in microns. - - Returns: - Approximate transcript count in the rectangle. - """ - col_lo = max(0, int(np.searchsorted(x_edges, x_min_um, side="right")) - 1) - col_hi = min( - len(x_edges) - 1, int(np.searchsorted(x_edges, x_max_um, side="right")) - 1 - ) - row_lo = max(0, int(np.searchsorted(y_edges, y_min_um, side="right")) - 1) - row_hi = min( - len(y_edges) - 1, int(np.searchsorted(y_edges, y_max_um, side="right")) - 1 - ) - - col_hi = min(col_hi, prefix_sum.shape[1] - 1) - row_hi = min(row_hi, prefix_sum.shape[0] - 1) - - if col_lo > col_hi or row_lo > row_hi: - return 0 - - total = int( - prefix_sum[row_hi, col_hi] - - (prefix_sum[row_lo - 1, col_hi] if row_lo > 0 else 0) - - (prefix_sum[row_hi, col_lo - 1] if col_lo > 0 else 0) - + (prefix_sum[row_lo - 1, col_lo - 1] if row_lo > 0 and col_lo > 0 else 0) - ) - return max(0, total) - - -def _subdivide_regions( - regions: list[tuple[float, float, float, float]], - prefix_sum: np.ndarray, - x_edges: np.ndarray, - y_edges: np.ndarray, - max_transcripts: int, - min_tile_width_um: float, - max_depth: int, -) -> list[tuple[float, float, float, float]]: - """ - Recursively subdivide regions exceeding the transcript threshold. - - Uses a stack instead of recursion for large grids. - - Args: - regions: List of (x_min, x_max, y_min, y_max) tuples in microns. - prefix_sum: 2D prefix sum for fast counting. - x_edges: Histogram X bin edges. - y_edges: Histogram Y bin edges. - max_transcripts: Maximum transcripts allowed per region. - min_tile_width_um: Minimum tile dimension before stopping. - max_depth: Maximum recursion depth. - - Returns: - List of final (x_min, x_max, y_min, y_max) regions. - """ - result: list[tuple[float, float, float, float]] = [] - stack: list[tuple[tuple[float, float, float, float], int]] = [ - (r, 0) for r in regions - ] - - while stack: - region, depth = stack.pop() - x_min, x_max, y_min, y_max = region - width = x_max - x_min - height = y_max - y_min - - count = _count_transcripts_in_rect( - prefix_sum, x_edges, y_edges, x_min, x_max, y_min, y_max - ) - - if count <= max_transcripts or depth >= max_depth: - result.append(region) - continue - - if min(width, height) / 2 < min_tile_width_um: - result.append(region) - continue - - # Split into 4 quadrants - mid_x = (x_min + x_max) / 2 - mid_y = (y_min + y_max) / 2 - children = [ - (x_min, mid_x, y_min, mid_y), - (mid_x, x_max, y_min, mid_y), - (x_min, mid_x, mid_y, y_max), - (mid_x, x_max, mid_y, y_max), - ] - for child in children: - stack.append((child, depth + 1)) - - return result - - -def _regions_to_patches( - regions: list[tuple[float, float, float, float]], - overlap_um: float, - overlap_px: int, - pixel_size_um: float, - image_width_px: int, - image_height_px: int, -) -> list[PatchInfo]: - """ - Convert quadtree regions to PatchInfo objects with overlap. - - Args: - regions: Sorted list of (x_min, x_max, y_min, y_max) in microns. - overlap_um: Overlap in microns. - overlap_px: Overlap in pixels. - pixel_size_um: Microns per pixel. - image_width_px: Image width in pixels. - image_height_px: Image height in pixels. - - Returns: - List of PatchInfo objects. - """ - patches: list[PatchInfo] = [] - for i, (x_min_um, x_max_um, y_min_um, y_max_um) in enumerate(regions): - # Core bounds in pixels - core_x_min_px = max( - 0, min(int(round(x_min_um / pixel_size_um)), image_width_px) - ) - core_x_max_px = max( - 0, min(int(round(x_max_um / pixel_size_um)), image_width_px) - ) - core_y_min_px = max( - 0, min(int(round(y_min_um / pixel_size_um)), image_height_px) - ) - core_y_max_px = max( - 0, min(int(round(y_max_um / pixel_size_um)), image_height_px) - ) - - core_bounds_px = Bounds( - core_x_min_px, core_x_max_px, core_y_min_px, core_y_max_px - ) - - # Global bounds: core extended by overlap, clamped to image - global_x_min_px = max(0, core_x_min_px - overlap_px) - global_x_max_px = min(image_width_px, core_x_max_px + overlap_px) - global_y_min_px = max(0, core_y_min_px - overlap_px) - global_y_max_px = min(image_height_px, core_y_max_px + overlap_px) - - global_bounds_px = Bounds( - global_x_min_px, global_x_max_px, global_y_min_px, global_y_max_px - ) - - core_bounds_um = Bounds( - core_x_min_px * pixel_size_um, - core_x_max_px * pixel_size_um, - core_y_min_px * pixel_size_um, - core_y_max_px * pixel_size_um, - ) - global_bounds_um = Bounds( - global_x_min_px * pixel_size_um, - global_x_max_px * pixel_size_um, - global_y_min_px * pixel_size_um, - global_y_max_px * pixel_size_um, - ) - - patches.append( - PatchInfo( - patch_id=f"patch_{i}", - row=i, - col=0, - global_bounds_px=global_bounds_px, - global_bounds_um=global_bounds_um, - core_bounds_px=core_bounds_px, - core_bounds_um=core_bounds_um, - ) - ) - - return patches - - -def compute_density_quadtree_grid( - image_height_px: int, - image_width_px: int, - tile_width_um: float, - overlap_um: float, - pixel_size_um: float, - x_coords_um: np.ndarray, - y_coords_um: np.ndarray, - max_transcripts_per_patch: int | None = None, - min_tile_width_um: float = QUADTREE_MIN_TILE_WIDTH_UM, - max_depth: int = QUADTREE_MAX_DEPTH, -) -> tuple[list[PatchInfo], int, int, int]: - """ - Compute an adaptive quadtree grid that subdivides dense regions. - - Starts with a uniform grid derived from tile_width_um, then recursively - subdivides patches exceeding max_transcripts_per_patch. - - Args: - image_height_px: Image height in pixels. - image_width_px: Image width in pixels. - tile_width_um: Base tile width in microns. - overlap_um: Overlap between adjacent patches in microns. - pixel_size_um: Microns per pixel. - x_coords_um: Transcript X coordinates in microns. - y_coords_um: Transcript Y coordinates in microns. - max_transcripts_per_patch: Target max transcripts per patch. - If None, auto-computed as 2x the average per initial patch. - min_tile_width_um: Minimum tile dimension before stopping. - max_depth: Maximum recursion depth. - - Returns: - Tuple of (patches, initial_rows, initial_cols, overlap_px). - """ - image_width_um = image_width_px * pixel_size_um - image_height_um = image_height_px * pixel_size_um - overlap_px = int(math.ceil(overlap_um / pixel_size_um)) - - initial_cols = max(1, math.ceil(image_width_um / tile_width_um)) - initial_rows = max(1, math.ceil(image_height_um / tile_width_um)) - - # Build prefix sum for fast counting - prefix_sum, x_edges, y_edges = _build_prefix_sum(x_coords_um, y_coords_um) - - # Define initial regions in microns - cell_width_um = image_width_um / initial_cols - cell_height_um = image_height_um / initial_rows - - initial_regions: list[tuple[float, float, float, float]] = [] - for row in range(initial_rows): - for col in range(initial_cols): - x_min = col * cell_width_um - x_max = min((col + 1) * cell_width_um, image_width_um) - y_min = row * cell_height_um - y_max = min((row + 1) * cell_height_um, image_height_um) - initial_regions.append((x_min, x_max, y_min, y_max)) - - # Auto-compute threshold - n_initial = len(initial_regions) - total_transcripts = len(x_coords_um) - if max_transcripts_per_patch is None: - max_transcripts_per_patch = max(1, int(total_transcripts / n_initial * 2)) - - # Recursive subdivision - final_regions = _subdivide_regions( - initial_regions, - prefix_sum, - x_edges, - y_edges, - max_transcripts_per_patch, - min_tile_width_um, - max_depth, - ) - - # Sort by (y_min, x_min) for deterministic ordering - final_regions.sort(key=lambda r: (r[2], r[0])) - - # Convert to PatchInfo - patches = _regions_to_patches( - final_regions, - overlap_um, - overlap_px, - pixel_size_um, - image_width_px, - image_height_px, - ) - - return patches, initial_rows, initial_cols, overlap_px - - -# --------------------------------------------------------------------------- -# Sparse tile merging -# --------------------------------------------------------------------------- - - -def _count_transcripts_per_tile( - patches: list[PatchInfo], - x_coords_um: np.ndarray, - y_coords_um: np.ndarray, -) -> dict[str, int]: - """ - Count transcripts falling within each patch's core bounds. - - Uses core bounds (not global) to avoid double-counting transcripts - in overlap regions. - - Args: - patches: List of PatchInfo objects. - x_coords_um: Transcript X coordinates in microns. - y_coords_um: Transcript Y coordinates in microns. - - Returns: - Dict mapping patch_id to transcript count. - """ - counts: dict[str, int] = {} - for p in patches: - cb = p.core_bounds_um - mask = ( - (x_coords_um >= cb.x_min) - & (x_coords_um < cb.x_max) - & (y_coords_um >= cb.y_min) - & (y_coords_um < cb.y_max) - ) - counts[p.patch_id] = int(np.sum(mask)) - return counts - - -def _find_adjacent_patches( - patches: list[PatchInfo], -) -> dict[str, list[str]]: - """ - Build an adjacency map: patches sharing a core bounds edge are neighbors. - - Two patches are adjacent if their core bounds share an edge (touch or - overlap along one axis while overlapping along the other axis). - - Args: - patches: List of PatchInfo objects. - - Returns: - Dict mapping patch_id to list of adjacent patch_ids. - """ - adjacency: dict[str, list[str]] = {p.patch_id: [] for p in patches} - eps = 1.0 # tolerance in microns for edge sharing - - for i, a in enumerate(patches): - for j in range(i + 1, len(patches)): - b = patches[j] - ac = a.core_bounds_um - bc = b.core_bounds_um - - # Check X-axis overlap (cores overlap in X) - x_overlap = ac.x_min < bc.x_max and bc.x_min < ac.x_max - # Check Y-axis overlap (cores overlap in Y) - y_overlap = ac.y_min < bc.y_max and bc.y_min < ac.y_max - - # Adjacent along X: share a vertical edge, overlap in Y - x_touching = ( - abs(ac.x_max - bc.x_min) < eps or abs(bc.x_max - ac.x_min) < eps - ) - # Adjacent along Y: share a horizontal edge, overlap in X - y_touching = ( - abs(ac.y_max - bc.y_min) < eps or abs(bc.y_max - ac.y_min) < eps - ) - - if (x_touching and y_overlap) or (y_touching and x_overlap): - adjacency[a.patch_id].append(b.patch_id) - adjacency[b.patch_id].append(a.patch_id) - - return adjacency - - -def _recalculate_core_bounds( - patches: list[PatchInfo], - overlap_px: int, - pixel_size_um: float, - image_width_px: int, - image_height_px: int, -) -> list[PatchInfo]: - """ - Recalculate core bounds for all patches after merging. - - Core bounds are derived from the regions: the core is the - non-overlapping portion of each tile. After merging, we extract - core regions from global bounds by trimming the overlap, then - rebuild PatchInfo objects. - - For merged grids where tiles may be irregular, core bounds equal - the global bounds shrunk by half the overlap on each side that has - a neighbor, clamped to the image extent. - - Args: - patches: Current list of PatchInfo (with updated global bounds). - overlap_px: Overlap in pixels. - pixel_size_um: Microns per pixel. - image_width_px: Image width in pixels. - image_height_px: Image height in pixels. - - Returns: - New list of PatchInfo with recalculated core and global bounds. - """ - if not patches: - return [] - - # Extract core regions in microns from global bounds minus overlap - half_overlap_um = (overlap_px * pixel_size_um) / 2.0 - image_width_um = image_width_px * pixel_size_um - image_height_um = image_height_px * pixel_size_um - - # Collect all core regions (global shrunk by half overlap) - core_regions_um: list[tuple[float, float, float, float]] = [] - for p in patches: - gb = p.global_bounds_um - # Shrink by half overlap on each side, but not past image edge - cx_min = gb.x_min + (half_overlap_um if gb.x_min > 0 else 0) - cx_max = gb.x_max - (half_overlap_um if gb.x_max < image_width_um else 0) - cy_min = gb.y_min + (half_overlap_um if gb.y_min > 0 else 0) - cy_max = gb.y_max - (half_overlap_um if gb.y_max < image_height_um else 0) - core_regions_um.append((cx_min, cx_max, cy_min, cy_max)) - - # Rebuild patches using core regions -> global bounds (core + overlap) - result: list[PatchInfo] = [] - for i, p in enumerate(patches): - cx_min, cx_max, cy_min, cy_max = core_regions_um[i] - - # Core bounds in pixels - core_x_min_px = max(0, min(int(round(cx_min / pixel_size_um)), image_width_px)) - core_x_max_px = max(0, min(int(round(cx_max / pixel_size_um)), image_width_px)) - core_y_min_px = max(0, min(int(round(cy_min / pixel_size_um)), image_height_px)) - core_y_max_px = max(0, min(int(round(cy_max / pixel_size_um)), image_height_px)) - - core_bounds_px = Bounds( - core_x_min_px, core_x_max_px, core_y_min_px, core_y_max_px - ) - - # Global bounds: core extended by overlap, clamped to image - global_x_min_px = max(0, core_x_min_px - overlap_px) - global_x_max_px = min(image_width_px, core_x_max_px + overlap_px) - global_y_min_px = max(0, core_y_min_px - overlap_px) - global_y_max_px = min(image_height_px, core_y_max_px + overlap_px) - - global_bounds_px = Bounds( - global_x_min_px, global_x_max_px, global_y_min_px, global_y_max_px - ) - - core_bounds_um = Bounds( - core_x_min_px * pixel_size_um, - core_x_max_px * pixel_size_um, - core_y_min_px * pixel_size_um, - core_y_max_px * pixel_size_um, - ) - global_bounds_um = Bounds( - global_x_min_px * pixel_size_um, - global_x_max_px * pixel_size_um, - global_y_min_px * pixel_size_um, - global_y_max_px * pixel_size_um, - ) - - result.append( - PatchInfo( - patch_id=p.patch_id, - row=p.row, - col=p.col, - global_bounds_px=global_bounds_px, - global_bounds_um=global_bounds_um, - core_bounds_px=core_bounds_px, - core_bounds_um=core_bounds_um, - ) - ) - - return result - - -def merge_sparse_tiles( - patches: list[PatchInfo], - x_coords_um: np.ndarray, - y_coords_um: np.ndarray, - overlap_px: int, - pixel_size_um: float, - image_width_px: int, - image_height_px: int, - min_transcripts: int = 1000, -) -> tuple[list[PatchInfo], int]: - """ - Merge tiles below min_transcripts into their least populated adjacent neighbor. - - Iteratively finds the sparsest tile below the threshold and merges it - into its smallest neighbor for balanced tile sizes. Repeats until no - tiles remain below the threshold (or a tile has no neighbors to merge into). - - Args: - patches: List of PatchInfo objects from grid computation. - x_coords_um: Transcript X coordinates in microns. - y_coords_um: Transcript Y coordinates in microns. - overlap_px: Overlap in pixels. - pixel_size_um: Microns per pixel. - image_width_px: Image width in pixels. - image_height_px: Image height in pixels. - min_transcripts: Minimum transcript count per tile. - - Returns: - Tuple of (merged patches, number of merges performed). - """ - if len(patches) <= 1: - return patches, 0 - - # Work with mutable list - active = list(patches) - merge_count = 0 - - while True: - counts = _count_transcripts_per_tile(active, x_coords_um, y_coords_um) - adjacency = _find_adjacent_patches(active) - - # Find sparsest tile below threshold - sparse_candidates = [ - (pid, cnt) for pid, cnt in counts.items() if cnt < min_transcripts - ] - if not sparse_candidates: - break - - # Sort by count ascending to merge sparsest first - sparse_candidates.sort(key=lambda t: t[1]) - sparse_id, sparse_count = sparse_candidates[0] - - # Find neighbors and pick the least populated one for balanced merging - neighbors = adjacency.get(sparse_id, []) - if not neighbors: - # No neighbors for this tile — skip it and try next sparsest - sparse_candidates = [(pid, cnt) for pid, cnt in sparse_candidates[1:]] - found = False - for pid, cnt in sparse_candidates: - nbrs = adjacency.get(pid, []) - if nbrs: - sparse_id, sparse_count = pid, cnt - neighbors = nbrs - found = True - break - if not found: - break - - best_neighbor_id = min(neighbors, key=lambda nid: counts.get(nid, 0)) - - # Find the actual PatchInfo objects - sparse_patch = next(p for p in active if p.patch_id == sparse_id) - neighbor_patch = next(p for p in active if p.patch_id == best_neighbor_id) - - # Expand neighbor's global bounds to cover both tiles - sg = sparse_patch.global_bounds_um - ng = neighbor_patch.global_bounds_um - merged_global_um = Bounds( - x_min=min(sg.x_min, ng.x_min), - x_max=max(sg.x_max, ng.x_max), - y_min=min(sg.y_min, ng.y_min), - y_max=max(sg.y_max, ng.y_max), - ) - - # Also merge core bounds (union) - sc = sparse_patch.core_bounds_um - nc = neighbor_patch.core_bounds_um - merged_core_um = Bounds( - x_min=min(sc.x_min, nc.x_min), - x_max=max(sc.x_max, nc.x_max), - y_min=min(sc.y_min, nc.y_min), - y_max=max(sc.y_max, nc.y_max), - ) - - # Convert merged bounds to pixels - merged_global_px = Bounds( - x_min=max(0, int(round(merged_global_um.x_min / pixel_size_um))), - x_max=min( - image_width_px, int(round(merged_global_um.x_max / pixel_size_um)) - ), - y_min=max(0, int(round(merged_global_um.y_min / pixel_size_um))), - y_max=min( - image_height_px, int(round(merged_global_um.y_max / pixel_size_um)) - ), - ) - merged_core_px = Bounds( - x_min=max(0, int(round(merged_core_um.x_min / pixel_size_um))), - x_max=min(image_width_px, int(round(merged_core_um.x_max / pixel_size_um))), - y_min=max(0, int(round(merged_core_um.y_min / pixel_size_um))), - y_max=min( - image_height_px, int(round(merged_core_um.y_max / pixel_size_um)) - ), - ) - - # Create merged patch (keeps absorbing tile's ID and position) - merged_patch = PatchInfo( - patch_id=neighbor_patch.patch_id, - row=neighbor_patch.row, - col=neighbor_patch.col, - global_bounds_px=merged_global_px, - global_bounds_um=merged_global_um, - core_bounds_px=merged_core_px, - core_bounds_um=merged_core_um, - ) - - # Replace neighbor with merged patch and remove sparse tile - active = [ - merged_patch if p.patch_id == best_neighbor_id else p - for p in active - if p.patch_id != sparse_id - ] - merge_count += 1 - - print( - f" Merged {sparse_id} ({sparse_count:,} transcripts) " - f"into {best_neighbor_id} ({counts[best_neighbor_id]:,} transcripts)" - ) - - if merge_count > 0: - # Recalculate core bounds for consistency - active = _recalculate_core_bounds( - active, overlap_px, pixel_size_um, image_width_px, image_height_px - ) - - return active, merge_count - - -# --------------------------------------------------------------------------- -# Transcript division -# --------------------------------------------------------------------------- - - -def _filter_and_write_patch_transcripts( - full_table: pa.Table, - output_path: Path, - bounds_um: Bounds, - origin_x: float, - origin_y: float, -) -> int: - """ - Filter transcripts to a spatial region and write to parquet. - - Transcripts are filtered to global_bounds (including overlap), then - coordinates are offset by subtracting the global_bounds origin. - - Args: - full_table: Full transcript table as a pyarrow Table. - output_path: Path for the filtered output parquet. - bounds_um: Spatial bounding box for filtering (microns). - origin_x: X offset to subtract for local coordinates. - origin_y: Y offset to subtract for local coordinates. - - Returns: - Number of transcripts written. - """ - x_col = full_table.column("x_location") - y_col = full_table.column("y_location") - - mask = pc.and_( - pc.and_( - pc.greater_equal(x_col, pa.scalar(bounds_um.x_min, type=x_col.type)), - pc.less(x_col, pa.scalar(bounds_um.x_max, type=x_col.type)), - ), - pc.and_( - pc.greater_equal(y_col, pa.scalar(bounds_um.y_min, type=y_col.type)), - pc.less(y_col, pa.scalar(bounds_um.y_max, type=y_col.type)), - ), - ) - filtered = full_table.filter(mask) - - if origin_x != 0.0 or origin_y != 0.0: - fx = filtered.column("x_location") - fy = filtered.column("y_location") - x_local = pc.subtract(fx, pa.scalar(origin_x, type=fx.type)) - y_local = pc.subtract(fy, pa.scalar(origin_y, type=fy.type)) - idx_x = filtered.schema.get_field_index("x_location") - idx_y = filtered.schema.get_field_index("y_location") - filtered = filtered.set_column(idx_x, "x_location", x_local) - filtered = filtered.set_column(idx_y, "y_location", y_local) - - output_path.parent.mkdir(parents=True, exist_ok=True) - pq.write_table(filtered, str(output_path)) - return len(filtered) - - -def _process_patch( - patch: PatchInfo, - output_dir: Path, - full_table: pa.Table, -) -> int: - """ - Write transcript subset for a single patch. - - Args: - patch: Patch metadata. - output_dir: Root output directory. - full_table: Full transcript table. - - Returns: - Number of transcripts written. - """ - patch_dir = output_dir / patch.patch_id - bounds_um = patch.global_bounds_um - return _filter_and_write_patch_transcripts( - full_table, - patch_dir / "transcripts.parquet", - bounds_um, - origin_x=bounds_um.x_min, - origin_y=bounds_um.y_min, - ) - - -# --------------------------------------------------------------------------- -# JSON serialization -# --------------------------------------------------------------------------- - - -def _bounds_to_dict(b: Bounds) -> dict[str, float]: - """Serialize a Bounds to a JSON-compatible dict.""" - return {"x_min": b.x_min, "x_max": b.x_max, "y_min": b.y_min, "y_max": b.y_max} - - -def save_grid_metadata( - patches: list[PatchInfo], - image_height_px: int, - image_width_px: int, - pixel_size_um: float, - transcript_extent_um: Bounds, - grid_rows: int, - grid_cols: int, - overlap_um: float, - overlap_px: int, - grid_type: str, - output_path: Path, -) -> None: - """ - Serialize grid metadata to JSON. - - Args: - patches: List of PatchInfo objects. - image_height_px: Image height in pixels. - image_width_px: Image width in pixels. - pixel_size_um: Microns per pixel. - transcript_extent_um: Bounding box of transcript coordinates. - grid_rows: Number of rows in the initial grid. - grid_cols: Number of columns in the initial grid. - overlap_um: Overlap in microns. - overlap_px: Overlap in pixels. - grid_type: Grid type string ("uniform" or "density_quadtree"). - output_path: Path to write JSON file. - """ - data = { - "version": "1.0", - "bundle_path": "", - "image_height_px": image_height_px, - "image_width_px": image_width_px, - "pixel_size_um": pixel_size_um, - "transcript_extent_um": _bounds_to_dict(transcript_extent_um), - "grid_rows": grid_rows, - "grid_cols": grid_cols, - "overlap_um": overlap_um, - "overlap_px": overlap_px, - "grid_type": grid_type, - "patches": [ - { - "patch_id": p.patch_id, - "row": p.row, - "col": p.col, - "global_bounds_px": _bounds_to_dict(p.global_bounds_px), - "global_bounds_um": _bounds_to_dict(p.global_bounds_um), - "core_bounds_px": _bounds_to_dict(p.core_bounds_px), - "core_bounds_um": _bounds_to_dict(p.core_bounds_um), - } - for p in patches - ], - } - output_path.parent.mkdir(parents=True, exist_ok=True) - with open(output_path, "w") as f: - json.dump(data, f, indent=2) - - -# --------------------------------------------------------------------------- -# Coordinate shift helper -# --------------------------------------------------------------------------- - - -def _shift_patches_to_real_coords( - patches: list[PatchInfo], - ox: float, - oy: float, -) -> list[PatchInfo]: - """ - Shift patch micron bounds by (ox, oy) to align with real transcript coords. - - Pixel bounds remain zero-origin (there is no real image to index into). - - Args: - patches: Patches in zero-origin micron space. - ox: X offset (transcript extent x_min). - oy: Y offset (transcript extent y_min). - - Returns: - New list of PatchInfo with shifted micron bounds. - """ - shifted: list[PatchInfo] = [] - for p in patches: - gu = p.global_bounds_um - cu = p.core_bounds_um - shifted.append( - PatchInfo( - patch_id=p.patch_id, - row=p.row, - col=p.col, - global_bounds_px=p.global_bounds_px, - global_bounds_um=Bounds( - gu.x_min + ox, gu.x_max + ox, gu.y_min + oy, gu.y_max + oy - ), - core_bounds_px=p.core_bounds_px, - core_bounds_um=Bounds( - cu.x_min + ox, cu.x_max + ox, cu.y_min + oy, cu.y_max + oy - ), - ) - ) - return shifted - - -# --------------------------------------------------------------------------- -# Main divide logic -# --------------------------------------------------------------------------- - - -def divide_transcripts( - transcripts_path: Path, - output_dir: Path, - image_width_px: int, - image_height_px: int, - tile_width_um: float, - overlap_um: float, - balanced: bool, - pixel_size_um: float = XENIUM_PIXEL_SIZE_UM, - max_workers: int | None = None, - min_transcripts: int = 1000, -) -> None: - """ - Divide transcripts into overlapping spatial patches. - - Reads the transcript table once, computes a grid, merges sparse tiles - into neighbors, and writes per-patch parquet files with coordinates - offset to patch-local space. - - Args: - transcripts_path: Path to transcripts.parquet. - output_dir: Output directory for patches. - image_width_px: Image width in pixels. - image_height_px: Image height in pixels. - tile_width_um: Tile width in microns. - overlap_um: Overlap between adjacent patches in microns. - balanced: If True, use density quadtree mode. - pixel_size_um: Microns per pixel. - max_workers: Maximum threads for parallel patch writes. - min_transcripts: Minimum transcripts per tile; sparse tiles merged - into neighbors. Set to 0 to disable merging. - """ - output_dir.mkdir(parents=True, exist_ok=True) - - # Read full transcript table - full_table = pq.read_table(str(transcripts_path)) - n_total = len(full_table) - print(f"Read {n_total:,} transcripts from {transcripts_path}") - - # Compute transcript extent - x_col = full_table.column("x_location") - y_col = full_table.column("y_location") - extent_um = Bounds( - x_min=pc.min(x_col).as_py(), - x_max=pc.max(x_col).as_py(), - y_min=pc.min(y_col).as_py(), - y_max=pc.max(y_col).as_py(), - ) - print( - f"Transcript extent: " - f"x=[{extent_um.x_min:.1f}, {extent_um.x_max:.1f}] " - f"y=[{extent_um.y_min:.1f}, {extent_um.y_max:.1f}] um" - ) - - # Build grid in zero-origin space when transcripts have a positive offset. - # The grid functions work in pixel space starting at (0, 0). We shift - # micron bounds back to real coordinates afterward. - ox = extent_um.x_min - oy = extent_um.y_min - - if balanced: - # Shift coordinates to zero-origin for density computation - x_coords = x_col.to_numpy() - ox - y_coords = y_col.to_numpy() - oy - - patches, grid_rows, grid_cols, overlap_px = compute_density_quadtree_grid( - image_height_px=image_height_px, - image_width_px=image_width_px, - tile_width_um=tile_width_um, - overlap_um=overlap_um, - pixel_size_um=pixel_size_um, - x_coords_um=x_coords, - y_coords_um=y_coords, - ) - grid_type = "density_quadtree" - else: - patches, grid_rows, grid_cols, overlap_px = compute_tilewidth_uniform_grid( - image_height_px=image_height_px, - image_width_px=image_width_px, - tile_width_um=tile_width_um, - overlap_um=overlap_um, - pixel_size_um=pixel_size_um, - transcript_extent_um=extent_um, - ) - grid_type = "uniform" - - # Merge sparse tiles into neighbors - n_before_merge = len(patches) - if min_transcripts > 0 and len(patches) > 1: - # Coordinates for counting: use zero-origin if not already - if balanced: - merge_x = x_coords - merge_y = y_coords - else: - merge_x = x_col.to_numpy() - ox - merge_y = y_col.to_numpy() - oy - - patches, n_merged = merge_sparse_tiles( - patches=patches, - x_coords_um=merge_x, - y_coords_um=merge_y, - overlap_px=overlap_px, - pixel_size_um=pixel_size_um, - image_width_px=image_width_px, - image_height_px=image_height_px, - min_transcripts=min_transcripts, - ) - if n_merged > 0: - grid_type = f"{grid_type}+merged" - print( - f"Merged {n_merged} sparse tiles: " - f"{n_before_merge} -> {len(patches)} patches" - ) - - # Shift micron bounds to real transcript coordinates - if ox != 0.0 or oy != 0.0: - patches = _shift_patches_to_real_coords(patches, ox, oy) - - print( - f"Grid: {grid_type}, {grid_rows}x{grid_cols} initial, " - f"{len(patches)} patches, overlap={overlap_um} um" - ) - - # Write patches in parallel - n_patches = len(patches) - workers = ( - max_workers if max_workers is not None else min(n_patches, os.cpu_count() or 1) - ) - - with ThreadPoolExecutor(max_workers=workers) as pool: - futures = [ - pool.submit(_process_patch, patch, output_dir, full_table) - for patch in patches - ] - for i, future in enumerate(futures): - count = future.result() - print(f" {patches[i].patch_id}: {count:,} transcripts") - - # Save grid metadata - save_grid_metadata( - patches=patches, - image_height_px=image_height_px, - image_width_px=image_width_px, - pixel_size_um=pixel_size_um, - transcript_extent_um=extent_um, - grid_rows=grid_rows, - grid_cols=grid_cols, - overlap_um=overlap_um, - overlap_px=overlap_px, - grid_type=grid_type, - output_path=output_dir / "patch_grid.json", - ) - print(f"Grid metadata saved to {output_dir / 'patch_grid.json'}") - - -# --------------------------------------------------------------------------- -# CLI -# --------------------------------------------------------------------------- - - -def parse_args(argv: list[str] | None = None) -> argparse.Namespace: - """ - Parse command-line arguments. - - Args: - argv: Argument list (defaults to sys.argv[1:]). - - Returns: - Parsed arguments namespace. - """ - parser = argparse.ArgumentParser( - description="Divide Xenium transcripts.parquet into spatial patches.", - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - ) - parser.add_argument( - "--transcripts", - type=Path, - required=True, - help="Path to transcripts.parquet", - ) - parser.add_argument( - "--output", - type=Path, - required=True, - help="Output directory for patches", - ) - parser.add_argument( - "--tile-width", - type=float, - default=2000.0, - help="Tile width in microns", - ) - parser.add_argument( - "--overlap", - type=float, - default=50.0, - help="Overlap between patches in microns", - ) - parser.add_argument( - "--balanced", - action="store_true", - help="Enable density quadtree mode (subdivides dense tiles)", - ) - parser.add_argument( - "--image-width", - type=int, - required=True, - help="Image width in pixels", - ) - parser.add_argument( - "--image-height", - type=int, - required=True, - help="Image height in pixels", - ) - parser.add_argument( - "--pixel-size", - type=float, - default=XENIUM_PIXEL_SIZE_UM, - help="Pixel size in microns", - ) - parser.add_argument( - "--min-transcripts", - type=int, - default=1000, - help="Minimum transcripts per tile; sparse tiles are merged into neighbors", - ) - parser.add_argument( - "--max-workers", - type=int, - default=None, - help="Maximum threads for parallel writes", - ) - return parser.parse_args(argv) - - -def main(argv: list[str] | None = None) -> None: - """Entry point.""" - args = parse_args(argv) - - divide_transcripts( - transcripts_path=args.transcripts, - output_dir=args.output, - image_width_px=args.image_width, - image_height_px=args.image_height, - tile_width_um=args.tile_width, - overlap_um=args.overlap, - balanced=args.balanced, - pixel_size_um=args.pixel_size, - max_workers=args.max_workers, - min_transcripts=args.min_transcripts, - ) - - -if __name__ == "__main__": - main() diff --git a/bin/ficture_preprocess.py b/bin/ficture_preprocess.py deleted file mode 100755 index 2e0c687c..00000000 --- a/bin/ficture_preprocess.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env python3 -"""Preprocess Xenium transcripts for FICTURE analysis.""" - -import argparse -import gzip -import logging -import os -import re -import sys - -import pandas as pd - - -def parse_args(): - """Parse command-line arguments.""" - parser = argparse.ArgumentParser( - description="Preprocess Xenium transcripts for FICTURE" - ) - parser.add_argument( - "--transcripts", required=True, help="Path to transcripts file (CSV)" - ) - parser.add_argument( - "--features", default="", help="Path to features file (optional)" - ) - parser.add_argument( - "--negative-control-regex", default="", help="Regex for negative control probes" - ) - return parser.parse_args() - - -def main(): - """Run FICTURE preprocessing.""" - args = parse_args() - print("[START]") - - negctrl_regex = "BLANK|NegCon" - if args.negative_control_regex: - negctrl_regex = args.negative_control_regex - - unit_info = ["X", "Y", "gene", "cell_id", "overlaps_nucleus"] - oheader = unit_info + ["Count"] - - feature = pd.DataFrame() - xmin = sys.maxsize - xmax = 0 - ymin = sys.maxsize - ymax = 0 - - output = "processed_transcripts.tsv.gz" - feature_file = "feature.clean.tsv.gz" - min_phred_score = 15 - - with gzip.open(output, "wt") as wf: - wf.write("\t".join(oheader) + "\n") - - for chunk in pd.read_csv(args.transcripts, header=0, chunksize=500000): - chunk = chunk.loc[(chunk.qv > min_phred_score)] - chunk.rename(columns={"feature_name": "gene"}, inplace=True) - if negctrl_regex != "": - chunk = chunk[ - ~chunk.gene.str.contains(negctrl_regex, flags=re.IGNORECASE, regex=True) - ] - chunk.rename(columns={"x_location": "X", "y_location": "Y"}, inplace=True) - chunk["Count"] = 1 - chunk[oheader].to_csv( - output, sep="\t", mode="a", index=False, header=False, float_format="%.2f" - ) - logging.info(f"{chunk.shape[0]}") - feature = pd.concat( - [feature, chunk.groupby(by="gene").agg({"Count": "sum"}).reset_index()] - ) - x0 = chunk.X.min() - x1 = chunk.X.max() - y0 = chunk.Y.min() - y1 = chunk.Y.max() - xmin = min(int(xmin), int(x0)) - xmax = max(int(xmax), int(x1)) - ymin = min(int(ymin), int(y0)) - ymax = max(int(ymax), int(y1)) - - if os.path.exists(args.features): - feature_list = [] - with open(args.features, "r") as ff: - for line in ff: - feature_list.append(line.strip("\n")) - feature = feature.groupby(by="gene").agg({"Count": "sum"}).reset_index() - feature = feature[[x in feature_list for x in feature["gene"]]] - feature.to_csv(feature_file, sep="\t", index=False) - - f = os.path.join(os.path.dirname(output), "coordinate_minmax.tsv") - with open(f, "w") as wf: - wf.write(f"xmin\t{xmin}\n") - wf.write(f"xmax\t{xmax}\n") - wf.write(f"ymin\t{ymin}\n") - wf.write(f"ymax\t{ymax}\n") - - print("[FINISH]") - - -if __name__ == "__main__": - main() diff --git a/bin/segger_create_dataset.py b/bin/segger_create_dataset.py deleted file mode 100755 index c73ab006..00000000 --- a/bin/segger_create_dataset.py +++ /dev/null @@ -1,253 +0,0 @@ -#!/usr/bin/env python3 -""" -Run segger create_dataset with spatialxe-specific preprocessing and workarounds. - -Wraps segger's create_dataset_fast.py with: - - bundle_local symlink prep (handles read-only S3/Fusion mounts) - - parquet column statistics (segger needs these) - - WORKAROUND: filter trainable tiles from test_tiles when segger commit 0787167 mis-splits - - WORKAROUND: replace NaN bd.x with zeros after get_polygon_props produces NaN - -Each WORKAROUND should be removable when the upstream segger bug is fixed. -""" - -import argparse -import os -import shutil -import subprocess -import sys -from pathlib import Path - -# imports for actual work (used in functions below) -import pyarrow.parquet as pq -import pyarrow.compute as pc -import torch - - -SEGGER_CLI = "/workspace/segger_dev/src/segger/cli/create_dataset_fast.py" - - -def parse_args(): - p = argparse.ArgumentParser() - p.add_argument("--bundle-dir", required=True) - p.add_argument("--output-dir", required=True) - p.add_argument("--sample-type", required=True, choices=["xenium"]) - p.add_argument("--tile-width", type=int, required=True) - p.add_argument("--tile-height", type=int, required=True) - p.add_argument("--n-workers", type=int, required=True) - # remaining args forwarded to segger CLI - args, extra = p.parse_known_args() - return args, extra - - -def prepare_bundle(bundle_dir): - """Create local bundle dir with absolute symlinks (S3/Fusion read-only-safe).""" - Path("bundle_local").mkdir(exist_ok=True) - for item in Path(bundle_dir).iterdir(): - try: - abs_path = item.resolve() - except Exception: - abs_path = item - target = Path("bundle_local") / item.name - if target.exists() or target.is_symlink(): - target.unlink() - target.symlink_to(abs_path) - - # Segger expects nucleus_boundaries.parquet but Xenium bundles have cell_boundaries.parquet - nb = Path("bundle_local/nucleus_boundaries.parquet") - cb = Path("bundle_local/cell_boundaries.parquet") - if not nb.exists() and cb.exists(): - print( - "Creating nucleus_boundaries.parquet symlink from cell_boundaries.parquet" - ) - nb.symlink_to(cb.resolve()) - - print("Bundle contents:") - for item in sorted(Path("bundle_local").iterdir()): - print(f" {item.name}") - - -def add_parquet_stats(): - """Rewrite key parquet files with column statistics (segger requires them).""" - Path("bundle_stats").mkdir(exist_ok=True) - for fname in ["transcripts.parquet", "nucleus_boundaries.parquet"]: - src = Path("bundle_local") / fname - dst = Path("bundle_stats") / fname - if not src.exists(): - print(f" Skip {src}") - continue - t = pq.read_table(str(src)) - pq.write_table(t, str(dst), write_statistics=True, compression="snappy") - print(f" Done {fname} ({len(t)} rows)") - - # Symlink everything else from bundle_local into bundle_stats - for item in Path("bundle_local").iterdir(): - dst = Path("bundle_stats") / item.name - if not dst.exists(): - dst.symlink_to(item.resolve()) - - # Debug: check overlaps_nucleus column in transcripts - print("\n=== Debugging overlaps_nucleus data ===") - tx = pq.read_table("bundle_stats/transcripts.parquet") - bd = pq.read_table("bundle_stats/nucleus_boundaries.parquet") - if "overlaps_nucleus" in tx.column_names: - col = tx.column("overlaps_nucleus") - print(f"overlaps_nucleus dtype: {col.type}") - unique_vals = pc.unique(col) - print(f"overlaps_nucleus unique values: {unique_vals.to_pylist()[:10]}") - val_counts = pc.value_counts(col) - print(f"overlaps_nucleus value_counts: {val_counts.to_pylist()}") - else: - print("WARNING: overlaps_nucleus column NOT FOUND in transcripts.parquet") - - if "cell_id" in tx.column_names and "cell_id" in bd.column_names: - tx_cells = set(pc.unique(tx.column("cell_id")).to_pylist()) - bd_cells = set(pc.unique(bd.column("cell_id")).to_pylist()) - overlap = tx_cells & bd_cells - print(f"Transcripts unique cell_ids: {len(tx_cells)}") - print(f"Boundaries unique cell_ids: {len(bd_cells)}") - print(f"Overlapping cell_ids: {len(overlap)}") - print("=== End Debug ===\n") - - -def run_segger_cli(args, extra): - cmd = [ - "python3", - SEGGER_CLI, - "--base_dir", - "bundle_stats", - "--data_dir", - args.output_dir, - "--sample_type", - args.sample_type, - "--tile_width", - str(args.tile_width), - "--tile_height", - str(args.tile_height), - "--n_workers", - str(args.n_workers), - *extra, - ] - print(f"Running: {' '.join(cmd)}") - result = subprocess.run(cmd) - if result.returncode != 0: - sys.exit(result.returncode) - - -def filter_trainable_tiles_if_needed(prefix): - """ - WORKAROUND: segger commit 0787167 has a bug where all tiles end up in test_tiles - regardless of test_prob/val_prob settings. Move ONLY trainable tiles (those with - edge_label_index) from test_tiles to train_tiles. - - Remove this function once segger >= 0.1.x is bumped with the upstream fix. - """ - train_dir = Path(prefix) / "train_tiles" / "processed" - test_dir = Path(prefix) / "test_tiles" / "processed" - val_dir = Path(prefix) / "val_tiles" / "processed" - - train_count = len(list(train_dir.iterdir())) if train_dir.exists() else 0 - test_count = len(list(test_dir.iterdir())) if test_dir.exists() else 0 - val_count = len(list(val_dir.iterdir())) if val_dir.exists() else 0 - print( - f"Dataset split (before fix): train={train_count} val={val_count} test={test_count}" - ) - - if train_count == 0 and test_count > 0: - print( - "Applying workaround: filtering trainable tiles from test_tiles (segger split bug)" - ) - moved = 0 - skipped = 0 - for tile_path in list(test_dir.iterdir()): - if not tile_path.name.endswith(".pt"): - continue - try: - tile = torch.load(str(tile_path), weights_only=False) - edge_store = tile["tx", "belongs", "bd"] - if ( - hasattr(edge_store, "edge_label_index") - and edge_store.edge_label_index.numel() > 0 - ): - shutil.move(str(tile_path), str(train_dir / tile_path.name)) - moved += 1 - else: - skipped += 1 - except Exception as e: - print(f"Warning: Could not process {tile_path.name}: {e}") - skipped += 1 - print(f"Moved {moved} trainable tiles to train_tiles") - print(f"Skipped {skipped} test-only tiles (no edge_label_index)") - - train_count = len(list(train_dir.iterdir())) if train_dir.exists() else 0 - test_count = len(list(test_dir.iterdir())) if test_dir.exists() else 0 - val_count = len(list(val_dir.iterdir())) if val_dir.exists() else 0 - print( - f"Dataset split (after fix): train={train_count} val={val_count} test={test_count}" - ) - - if train_count == 0: - print(f"ERROR: No trainable tiles were created in {train_dir}", file=sys.stderr) - print( - "This usually means no transcripts overlap with nucleus boundaries in the dataset.", - file=sys.stderr, - ) - print( - "Check if the Xenium bundle contains valid overlaps_nucleus data in transcripts.parquet.", - file=sys.stderr, - ) - sys.exit(1) - print(f"Successfully created {train_count} trainable tiles") - - -def fix_bd_x_nan(prefix): - """ - WORKAROUND: segger's get_polygon_props() produces NaN boundary features (bd.x) - when polygon geometries have zero area or index misalignment during GeoDataFrame - construction. Replace NaN bd.x with zeros so BCEWithLogitsLoss doesn't propagate NaN. - - Remove this function once segger >= 0.1.x is bumped with the upstream fix. - """ - fixed = 0 - total = 0 - for split in ["train_tiles", "test_tiles", "val_tiles"]: - tile_dir = Path(prefix) / split / "processed" - if not tile_dir.is_dir(): - continue - for tile_path in tile_dir.iterdir(): - if not tile_path.name.endswith(".pt"): - continue - total += 1 - tile = torch.load(str(tile_path), weights_only=False) - bd_x = tile["bd"].x - if bd_x.isnan().any(): - tile["bd"].x = torch.nan_to_num(bd_x, nan=0.0) - torch.save(tile, str(tile_path)) - fixed += 1 - print(f"Fixed NaN bd.x in {fixed}/{total} tiles") - - -def main(): - args, extra = parse_args() - - # Ensure numba cache dir is writable (env var should be set by caller, but belt-and-suspenders) - os.environ.setdefault("NUMBA_CACHE_DIR", os.path.join(os.getcwd(), ".numba_cache")) - os.makedirs(os.environ["NUMBA_CACHE_DIR"], exist_ok=True) - - prepare_bundle(args.bundle_dir) - print("Adding statistics to parquet files...") - add_parquet_stats() - - # Sanity-check bundle_stats - print("bundle_stats contents:") - for item in sorted(Path("bundle_stats").iterdir()): - print(f" {item.name}") - - run_segger_cli(args, extra) - - filter_trainable_tiles_if_needed(args.output_dir) - fix_bd_x_nan(args.output_dir) - - -if __name__ == "__main__": - main() diff --git a/bin/segger_predict.py b/bin/segger_predict.py deleted file mode 100755 index 56a77ffc..00000000 --- a/bin/segger_predict.py +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env python3 -""" -Run segger predict with spatialxe-specific preprocessing. - -Wraps segger's predict_fast.py with: - - GPU enumeration (replaces inline python3 -c torch check) - - WORKAROUND: patch predict_parquet.py at runtime to add torch.no_grad() for ~30-50% VRAM savings - - WORKAROUND: seed random.choice for deterministic GPU assignment (avoids stochastic OOM) - -Both WORKAROUNDs should be removable once the patches are upstreamed to segger. -""" - -import argparse -import os -import subprocess -import sys - - -SEGGER_CLI = "/workspace/segger_dev/src/segger/cli/predict_fast.py" - - -def parse_args(): - p = argparse.ArgumentParser() - p.add_argument("--models-dir", required=True) - p.add_argument("--segger-data-dir", required=True) - p.add_argument("--transcripts-file", required=True) - p.add_argument("--benchmarks-dir", required=True) - p.add_argument("--batch-size", type=int, required=True) - p.add_argument("--use-cc", required=True) - p.add_argument("--knn-method", required=True) - p.add_argument("--num-workers", type=int, required=True) - args, extra = p.parse_known_args() - return args, extra - - -def detect_gpus(): - """Return comma-separated list of available CUDA device ids (or "0" if none).""" - import torch - - print("=== GPU Detection (SEGGER_PREDICT) ===") - print(f"PyTorch CUDA available: {torch.cuda.is_available()}") - n = torch.cuda.device_count() - print(f"CUDA device count: {n}") - print("======================================") - if n > 0: - return ",".join(str(i) for i in range(n)) - return "0" - - -def patch_predict_parquet(): - """ - WORKAROUND: patch segger.prediction.predict_parquet at runtime. - - Avoids rebuilding the segger Docker image. Two patches: - 1. Add torch.no_grad() to disable gradient graphs during inference (~30-50% VRAM savings). - 2. Seed random for deterministic GPU assignment (avoids stochastic OOM). - - Remove this function once the patches are upstreamed to segger. - """ - import segger.prediction.predict_parquet as m - - pred_py = m.__file__ - print(f"Patching {pred_py}: torch.no_grad() + round-robin GPU assignment") - # Use sed via subprocess for in-place edit (matches the original behavior exactly) - subprocess.run( - [ - "sed", - "-i", - "s/with cp.cuda.Device(gpu_id):/with cp.cuda.Device(gpu_id), torch.no_grad():/", - pred_py, - ], - check=True, - ) - subprocess.run( - [ - "sed", - "-i", - "s/gpu_id = random.choice(gpu_ids)/random.seed(0); gpu_id = random.choice(gpu_ids)/", - pred_py, - ], - check=True, - ) - - -def run_segger_cli(args, extra, gpu_ids): - cmd = [ - "python3", - SEGGER_CLI, - "--models_dir", - args.models_dir, - "--segger_data_dir", - args.segger_data_dir, - "--transcripts_file", - args.transcripts_file, - "--benchmarks_dir", - args.benchmarks_dir, - "--batch_size", - str(args.batch_size), - "--use_cc", - str(args.use_cc), - "--knn_method", - args.knn_method, - "--num_workers", - str(args.num_workers), - "--gpu_ids", - gpu_ids, - *extra, - ] - print(f"Running: {' '.join(cmd)}") - result = subprocess.run(cmd) - if result.returncode != 0: - sys.exit(result.returncode) - - -def main(): - args, extra = parse_args() - - # Limit cupy GPU memory to 80% so PyTorch has headroom for graph attention ops - os.environ.setdefault("CUPY_GPU_MEMORY_LIMIT", "80%") - # Belt-and-suspenders: ensure PyTorch uses expandable segments - os.environ.setdefault( - "PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True,max_split_size_mb:512" - ) - # Numba cache directory - os.environ.setdefault("NUMBA_CACHE_DIR", os.path.join(os.getcwd(), ".numba_cache")) - os.makedirs(os.environ["NUMBA_CACHE_DIR"], exist_ok=True) - - gpu_ids = detect_gpus() - print(f"Using GPUs: {gpu_ids}") - - patch_predict_parquet() - - run_segger_cli(args, extra, gpu_ids) - - -if __name__ == "__main__": - main() diff --git a/bin/spatialdata_merge.py b/bin/spatialdata_merge.py deleted file mode 100755 index 409d8c00..00000000 --- a/bin/spatialdata_merge.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env python3 -"""Merge two spatialdata bundles to create a layered spatialdata object.""" - -import argparse -import json -import os -import shutil - -import spatialdata - - -def parse_args(): - """Parse command-line arguments.""" - parser = argparse.ArgumentParser(description="Merge two spatialdata bundles") - parser.add_argument("--raw-bundle", required=True, help="Path to raw spatialdata bundle") - parser.add_argument("--redefined-bundle", required=True, help="Path to redefined spatialdata bundle") - parser.add_argument("--prefix", required=True, help="Output prefix (sample ID)") - parser.add_argument("--output-folder", required=True, help="Output folder name") - return parser.parse_args() - - -def main(): - """Run spatialdata merge.""" - args = parse_args() - print("[START]") - - output_dir = f"spatialdata/{args.prefix}/{args.output_folder}" - - # Ensure the output folder exists - if os.path.exists(output_dir): - shutil.rmtree(output_dir) - os.makedirs(output_dir) - - # Copy the entire reference bundle as is - for root, _, files in os.walk(args.raw_bundle): - rel_path = os.path.relpath(root, args.raw_bundle) - target_path = os.path.join(output_dir, rel_path) - os.makedirs(target_path, exist_ok=True) - for file in files: - shutil.copy(os.path.join(root, file), os.path.join(target_path, file)) - - # Rename folders in Points, Shapes, and Tables to raw_* - for category in ["points", "shapes", "tables"]: - category_path = os.path.join(output_dir, category) - if os.path.exists(category_path): - for folder in next(os.walk(category_path))[1]: - old_path = os.path.join(category_path, folder) - print(folder) - new_path = os.path.join(category_path, f"raw_{folder}") - os.rename(old_path, new_path) - - # Copy folders from redefined_bundle and rename them as redefined_* - for category in ["points", "shapes", "tables"]: - add_category_path = os.path.join(args.redefined_bundle, category) - output_category_path = os.path.join(output_dir, category) - os.makedirs(output_category_path, exist_ok=True) - - if os.path.exists(add_category_path): - for folder in next(os.walk(add_category_path))[1]: - src_folder = os.path.join(add_category_path, folder) - dest_folder = os.path.join(output_category_path, f"redefined_{folder}") - shutil.copytree(src_folder, dest_folder) - - # Invalidate consolidated metadata in zarr.json -- the directory renames above - # made the element paths in the metadata stale (e.g., 'points/transcripts' -> - # 'points/raw_transcripts'). Without consolidated metadata, sd.read_zarr() - # discovers elements by scanning the filesystem directly. - zarr_json = os.path.join(output_dir, "zarr.json") - if os.path.exists(zarr_json): - with open(zarr_json) as f: - meta = json.load(f) - if "consolidated_metadata" in meta: - del meta["consolidated_metadata"] - with open(zarr_json, "w") as f: - json.dump(meta, f) - print("[NOTE] Removed stale consolidated metadata from zarr.json") - - print("[FINISH]") - - -if __name__ == "__main__": - main() diff --git a/bin/spatialdata_meta.py b/bin/spatialdata_meta.py deleted file mode 100755 index 935f39b2..00000000 --- a/bin/spatialdata_meta.py +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env python3 -"""Add metadata to SpatialData bundle.""" - -import argparse -import json -import sys - -import pandas as pd -import spatialdata as sd -import zarr - -# Fix zarr v3 + anndata + numcodecs incompatibility: -# anndata's string writer passes numcodecs.VLenUTF8 to zarr.Group.create_array, -# but zarr v3 only accepts ArrayArrayCodec types. OME-Zarr 0.5 requires zarr v3 -# for images, so we can't downgrade the store format. Instead, we intercept -# create_array to strip numcodecs codecs and let zarr v3 handle strings natively. -import numcodecs -import zarr.core.group as _zarr_group - -_orig_create_array = _zarr_group.Group.create_array - - -def _v3_compat_create_array(self, *args, **kwargs): - """Strip numcodecs VLenUTF8 from codec params for zarr v3 compatibility.""" - for param in ("filters", "compressor", "object_codec"): - val = kwargs.get(param) - if val is None: - continue - if isinstance(val, numcodecs.vlen.VLenUTF8): - del kwargs[param] - elif isinstance(val, (list, tuple)): - cleaned = [v for v in val if not isinstance(v, numcodecs.vlen.VLenUTF8)] - if len(cleaned) != len(val): - if cleaned: - kwargs[param] = cleaned - else: - del kwargs[param] - return _orig_create_array(self, *args, **kwargs) - - -_zarr_group.Group.create_array = _v3_compat_create_array - - -def _is_arrow_backed(dtype): - """Check if a pandas dtype is backed by PyArrow.""" - return isinstance(dtype, pd.ArrowDtype) or ( - hasattr(dtype, "storage") and getattr(dtype, "storage", None) == "pyarrow" - ) or "pyarrow" in str(dtype) - - -def _convert_df_arrow_to_numpy(df): - """Convert Arrow-backed dtypes in a DataFrame to numpy object dtype.""" - for col in df.columns: - dtype = df[col].dtype - if _is_arrow_backed(dtype): - df[col] = df[col].astype("object") - elif isinstance(dtype, pd.CategoricalDtype): - cats = dtype.categories - if cats is not None and _is_arrow_backed(cats.dtype): - df[col] = df[col].cat.rename_categories(cats.astype("object")) - if _is_arrow_backed(df.index.dtype): - df.index = pd.Index(df.index.astype("object")) - - -def convert_arrow_to_numpy(sdata): - """Convert Arrow-backed dtypes to numpy for anndata zarr write compatibility.""" - for table_key in list(sdata.tables.keys()): - adata = sdata.tables[table_key] - _convert_df_arrow_to_numpy(adata.obs) - _convert_df_arrow_to_numpy(adata.var) - - -def parse_args(): - """Parse command-line arguments.""" - parser = argparse.ArgumentParser(description="Add metadata to SpatialData bundle") - parser.add_argument("--spatialdata-bundle", required=True, help="Path to spatialdata bundle") - parser.add_argument("--xenium-bundle", required=True, help="Path to xenium bundle") - parser.add_argument("--prefix", required=True, help="Output prefix (sample ID)") - parser.add_argument("--metadata", required=True, help="Metadata string from Nextflow meta map") - parser.add_argument("--output-folder", required=True, help="Output folder name") - return parser.parse_args() - - -def main(): - """Run spatialdata metadata addition.""" - args = parse_args() - print("[START]") - - sdata = sd.read_zarr(args.spatialdata_bundle) - - # Convert metadata into dict - print("[NOTE] Read in provenance ...") - metadata = args.metadata.strip("[]") # Remove square brackets - pairs = metadata.split(", ") # Split by comma and space - metadata = {k: v for k, v in (pair.split(":") for pair in pairs)} # Create dictionary - - for key in metadata: - if key not in sdata['raw_table'].uns['spatialdata_attrs']: - sdata['raw_table'].uns['spatialdata_attrs'][key] = metadata[key] - else: - print(f'[ERROR] {key} already exist in sdata[raw_table].uns[spatialdata_attrs].', file=sys.stderr) - - # Add experimental metadata - print("[NOTE] Read in experiment metadata ...") - sdata['raw_table'].uns['experiment_xenium'] = '' - metadata_experiment = f'{args.xenium_bundle}/experiment.xenium' - with open(metadata_experiment, "r") as f: - metadata_experiment = json.load(f) - sdata['raw_table'].uns['experiment_xenium'] = json.dumps(metadata_experiment) - - # Add gene panel metadata - print("[NOTE] Read in gene panel metadata ...") - sdata['raw_table'].uns['gene_panel'] = '' - metadata_gene_panel = f'{args.xenium_bundle}/gene_panel.json' - with open(metadata_gene_panel, "r") as f: - metadata_gene_panel = json.load(f) - sdata['raw_table'].uns['gene_panel'] = json.dumps(metadata_gene_panel) - - convert_arrow_to_numpy(sdata) - sdata.write(f"spatialdata/{args.prefix}/{args.output_folder}", overwrite=True, consolidate_metadata=True, sdata_formats=None) - - print("[FINISH]") - - -if __name__ == "__main__": - main() diff --git a/bin/spatialdata_write.py b/bin/spatialdata_write.py deleted file mode 100755 index 421e830f..00000000 --- a/bin/spatialdata_write.py +++ /dev/null @@ -1,156 +0,0 @@ -#!/usr/bin/env python3 -"""Write spatialdata object from segmentation format.""" - -import argparse -import sys - -import pandas as pd -import spatialdata -from spatialdata_io import xenium - -# Fix zarr v3 + anndata + numcodecs incompatibility: -# anndata's string writer passes numcodecs.VLenUTF8 to zarr.Group.create_array, -# but zarr v3 only accepts ArrayArrayCodec types. OME-Zarr 0.5 requires zarr v3 -# for images, so we can't downgrade the store format. Instead, we intercept -# create_array to strip numcodecs codecs and let zarr v3 handle strings natively. -import numcodecs -import zarr.core.group as _zarr_group - -_orig_create_array = _zarr_group.Group.create_array - - -def _v3_compat_create_array(self, *args, **kwargs): - """Strip numcodecs VLenUTF8 from codec params for zarr v3 compatibility.""" - for param in ("filters", "compressor", "object_codec"): - val = kwargs.get(param) - if val is None: - continue - if isinstance(val, numcodecs.vlen.VLenUTF8): - del kwargs[param] - elif isinstance(val, (list, tuple)): - cleaned = [v for v in val if not isinstance(v, numcodecs.vlen.VLenUTF8)] - if len(cleaned) != len(val): - if cleaned: - kwargs[param] = cleaned - else: - del kwargs[param] - return _orig_create_array(self, *args, **kwargs) - - -_zarr_group.Group.create_array = _v3_compat_create_array - - -def _is_arrow_backed(dtype): - """Check if a pandas dtype is backed by PyArrow.""" - return ( - isinstance(dtype, pd.ArrowDtype) - or (hasattr(dtype, "storage") and getattr(dtype, "storage", None) == "pyarrow") - or "pyarrow" in str(dtype) - ) - - -def _convert_df_arrow_to_numpy(df): - """Convert Arrow-backed dtypes in a DataFrame to numpy object dtype. - - Handles three cases: - 1. Regular columns with Arrow-backed dtypes - 2. Categorical columns whose categories are Arrow-backed - 3. Index with Arrow-backed dtype - """ - for col in df.columns: - dtype = df[col].dtype - if _is_arrow_backed(dtype): - df[col] = df[col].astype("object") - elif isinstance(dtype, pd.CategoricalDtype): - cats = dtype.categories - if cats is not None and _is_arrow_backed(cats.dtype): - df[col] = df[col].cat.rename_categories(cats.astype("object")) - if _is_arrow_backed(df.index.dtype): - df.index = pd.Index(df.index.astype("object")) - - -def convert_arrow_to_numpy(sdata): - """Convert Arrow-backed dtypes to numpy for anndata zarr write compatibility.""" - for table_key in list(sdata.tables.keys()): - adata = sdata.tables[table_key] - _convert_df_arrow_to_numpy(adata.obs) - _convert_df_arrow_to_numpy(adata.var) - - -def parse_args(): - """Parse command-line arguments.""" - parser = argparse.ArgumentParser(description="Write spatialdata object from segmentation format") - parser.add_argument("--bundle", required=True, help="Path to input bundle") - parser.add_argument("--prefix", required=True, help="Output prefix (sample ID)") - parser.add_argument("--output-folder", required=True, help="Output folder name") - parser.add_argument("--segmented-object", required=True, help="Segmented object type (cells, nuclei, cells_and_nuclei)") - parser.add_argument("--coordinate-space", required=True, help="Coordinate space (pixels, microns)") - parser.add_argument("--format", required=True, help="Input format (xenium)") - return parser.parse_args() - - -def main(): - """Run spatialdata write.""" - args = parse_args() - print("[START]") - - cells_as_circles = False - cells_boundaries = False - nucleus_boundaries = False - cells_labels = False - nucleus_labels = False - - if args.segmented_object == "cells": - cells_boundaries = True - cells_labels = True - elif args.segmented_object == "nuclei": - nucleus_boundaries = True - nucleus_labels = True - elif args.segmented_object == "cells_and_nuclei": - cells_boundaries = True - nucleus_boundaries = True - cells_labels = True - nucleus_labels = True - else: - cells_as_circles = False - - # set sd variables based on the coordinate space - if args.coordinate_space == "pixels": - cells_labels = True - nucleus_labels = True - # Labels are sufficient in pixel space; boundaries can contain - # degenerate polygons (< 4 vertices) from XeniumRanger that - # crash spatialdata_io's shapely LinearRing parser. - cells_boundaries = False - nucleus_boundaries = False - - if args.coordinate_space == "microns": - cells_labels = False - cells_boundaries = True - nucleus_boundaries = False - nucleus_labels = False - cells_as_circles = False - - if args.format == "xenium": - sd_xenium_obj = xenium( - args.bundle, - cells_as_circles=cells_as_circles, - cells_boundaries=cells_boundaries, - nucleus_boundaries=nucleus_boundaries, - cells_labels=cells_labels, - nucleus_labels=nucleus_labels, - transcripts=True, - morphology_mip=True, - morphology_focus=True, - ) - print(sd_xenium_obj) - convert_arrow_to_numpy(sd_xenium_obj) - sd_xenium_obj.write(f"spatialdata/{args.prefix}/{args.output_folder}") - else: - sys.exit("[ERROR] Format not found") - - print("[FINISH]") - - -if __name__ == "__main__": - main() diff --git a/bin/stitch_transcripts.py b/bin/stitch_transcripts.py deleted file mode 100755 index 45f057e3..00000000 --- a/bin/stitch_transcripts.py +++ /dev/null @@ -1,848 +0,0 @@ -#!/usr/bin/env python3 -"""Stitch per-patch Baysor segmentation results into unified output. - -Standalone script that replaces the xenium_patch CLI package's stitch -functionality. Uses sopa's solve_conflicts() for overlap resolution. -""" - -from __future__ import annotations - -import argparse -import json -import os -from concurrent.futures import ThreadPoolExecutor -from dataclasses import dataclass -from pathlib import Path - -import geopandas as gpd -import numpy as np -import pyarrow as pa -import pyarrow.compute as pc -import pyarrow.csv as pa_csv -import shapely -from shapely.affinity import translate -from shapely.geometry import mapping, shape -from sopa.segmentation.resolve import solve_conflicts - -# --------------------------------------------------------------------------- -# Inline types (from _types.py) -# --------------------------------------------------------------------------- - - -@dataclass(frozen=True) -class Bounds: - """Axis-aligned bounding box in either pixel or micron coordinates.""" - - x_min: float - x_max: float - y_min: float - y_max: float - - -@dataclass(frozen=True) -class PatchInfo: - """Metadata for a single patch in the grid.""" - - patch_id: str - row: int - col: int - global_bounds_px: Bounds - global_bounds_um: Bounds - core_bounds_px: Bounds - core_bounds_um: Bounds - - -@dataclass -class PatchGridMetadata: - """Full grid metadata, serializable to JSON.""" - - version: str - bundle_path: str - image_height_px: int - image_width_px: int - pixel_size_um: float - transcript_extent_um: Bounds - grid_rows: int - grid_cols: int - overlap_um: float - overlap_px: int - patches: list[PatchInfo] - grid_type: str = "uniform" - - -# --------------------------------------------------------------------------- -# Internal result containers -# --------------------------------------------------------------------------- - - -@dataclass -class _PatchGeoResult: - """Result of parallel GeoJSON processing for a single patch.""" - - features: list[dict] - cell_ids: list[str] - - -@dataclass -class _PatchCsvResult: - """Result of parallel CSV reading for a single patch.""" - - table: pa.Table - has_cell_col: bool - has_x_col: bool - has_y_col: bool - has_gene_col: bool = False - has_feature_name_col: bool = False - - -# --------------------------------------------------------------------------- -# Grid metadata I/O (from grid.py) -# --------------------------------------------------------------------------- - - -def _dict_to_bounds(d: dict) -> Bounds: - return Bounds(d["x_min"], d["x_max"], d["y_min"], d["y_max"]) - - -def load_grid_metadata(input_path: Path) -> PatchGridMetadata: - """Deserialize PatchGridMetadata from JSON. - - Args: - input_path: Path to JSON file to read. - - Returns: - Reconstructed PatchGridMetadata. - """ - with open(input_path) as f: - data = json.load(f) - - patches = [ - PatchInfo( - patch_id=p["patch_id"], - row=p["row"], - col=p["col"], - global_bounds_px=_dict_to_bounds(p["global_bounds_px"]), - global_bounds_um=_dict_to_bounds(p["global_bounds_um"]), - core_bounds_px=_dict_to_bounds(p["core_bounds_px"]), - core_bounds_um=_dict_to_bounds(p["core_bounds_um"]), - ) - for p in data["patches"] - ] - - return PatchGridMetadata( - version=data["version"], - bundle_path=data["bundle_path"], - image_height_px=data["image_height_px"], - image_width_px=data["image_width_px"], - pixel_size_um=data["pixel_size_um"], - transcript_extent_um=_dict_to_bounds(data["transcript_extent_um"]), - grid_rows=data["grid_rows"], - grid_cols=data["grid_cols"], - overlap_um=data["overlap_um"], - overlap_px=data["overlap_px"], - grid_type=data.get("grid_type", "uniform"), - patches=patches, - ) - - -# --------------------------------------------------------------------------- -# GeoJSON I/O (from polygon_io.py) -# --------------------------------------------------------------------------- - - -def _normalize_geometry_collection(geojson: dict) -> dict: - """Convert a GeometryCollection to a FeatureCollection. - - proseg-to-baysor produces a non-standard GeoJSON GeometryCollection where - each geometry object has a custom ``cell`` key (bare integer) instead of - using Feature wrappers. This normalises it to a standard FeatureCollection - with ``id`` and ``properties.cell_id`` on each feature, using the - ``"cell-{N}"`` format that matches the companion CSV. - - Args: - geojson: Parsed GeoJSON dict with type GeometryCollection. - - Returns: - Standard FeatureCollection dict. - """ - features = [] - for geom in geojson.get("geometries", []): - cell_raw = geom.get("cell", "") - cell_id = str(cell_raw) - clean_geom = {k: v for k, v in geom.items() if k != "cell"} - feature = { - "type": "Feature", - "id": cell_id, - "geometry": clean_geom, - "properties": {"cell_id": cell_id}, - } - features.append(feature) - return {"type": "FeatureCollection", "features": features} - - -def read_geojson(geojson_path: Path) -> dict: - """Read a GeoJSON file and normalise to FeatureCollection. - - Handles both standard FeatureCollections and the GeometryCollection - format produced by proseg-to-baysor. - - Args: - geojson_path: Path to the GeoJSON file. - - Returns: - Parsed GeoJSON dict (always a FeatureCollection). - """ - with open(geojson_path) as f: - data = json.load(f) - if data.get("type") == "GeometryCollection": - return _normalize_geometry_collection(data) - return data - - -def transform_polygons(geojson: dict, offset_x: float, offset_y: float) -> dict: - """Shift all polygon coordinates by (offset_x, offset_y). - - Args: - geojson: Input FeatureCollection. - offset_x: Translation in x. - offset_y: Translation in y. - - Returns: - New FeatureCollection with shifted geometries. - """ - features = [] - for feat in geojson.get("features", []): - geom = shape(feat["geometry"]) - shifted = translate(geom, xoff=offset_x, yoff=offset_y) - new_feat = {**feat, "geometry": mapping(shifted)} - features.append(new_feat) - return {"type": "FeatureCollection", "features": features} - - -def write_geojson(geojson: dict, output_path: Path) -> None: - """Write a GeoJSON FeatureCollection. - - Args: - geojson: GeoJSON dict to write. - output_path: Destination path (parent dirs created automatically). - """ - output_path.parent.mkdir(parents=True, exist_ok=True) - with open(output_path, "w") as f: - json.dump(geojson, f) - - -# --------------------------------------------------------------------------- -# Arrow utilities (from _arrow_utils.py) -# --------------------------------------------------------------------------- - - -def float_str_array(f64_array: pa.Array) -> pa.Array: - """Convert a float64 pyarrow array to string using Python's str(float) format. - - pyarrow's built-in cast omits trailing '.0' for whole numbers. This - function ensures output matches str(float(...)) for CSV compatibility. - - Args: - f64_array: Float64 pyarrow array to convert. - - Returns: - String pyarrow array with Python-formatted float values. - """ - return pa.array( - [str(v) if v is not None else None for v in f64_array.to_pylist()], - type=pa.string(), - ) - - -# --------------------------------------------------------------------------- -# Parallel I/O -# --------------------------------------------------------------------------- - - -def _read_and_transform_geojson( - patch: PatchInfo, - patches_dir: Path, - geojson_filename: str, -) -> _PatchGeoResult | None: - """Read, transform GeoJSON for a single patch (no core clipping). - - Args: - patch: Patch metadata. - patches_dir: Root patches directory. - geojson_filename: GeoJSON filename within each patch directory. - - Returns: - _PatchGeoResult with features and cell IDs, or None if no GeoJSON. - """ - geojson_path = patches_dir / patch.patch_id / geojson_filename - if not geojson_path.exists(): - return None - - geojson = read_geojson(geojson_path) - - offset_x = patch.global_bounds_um.x_min - offset_y = patch.global_bounds_um.y_min - geojson = transform_polygons(geojson, offset_x, offset_y) - - features = geojson.get("features", []) - seen: set[str] = set() - cell_ids: list[str] = [] - for feat in features: - old_id = str(feat.get("id", feat.get("properties", {}).get("cell_id", ""))) - if old_id not in seen: - seen.add(old_id) - cell_ids.append(old_id) - - return _PatchGeoResult(features=features, cell_ids=cell_ids) - - -def _read_patch_csv( - patch: PatchInfo, - patches_dir: Path, - csv_filename: str, -) -> _PatchCsvResult | None: - """Read a patch CSV into a pyarrow Table. - - All columns are read as strings to preserve exact formatting. - - Args: - patch: Patch metadata. - patches_dir: Root patches directory. - csv_filename: CSV filename within each patch directory. - - Returns: - _PatchCsvResult with the table and column presence flags, or None. - """ - csv_path = patches_dir / patch.patch_id / csv_filename - if not csv_path.exists(): - return None - - with open(csv_path) as fh: - header_line = fh.readline().strip() - col_names = header_line.split(",") - all_string_types = {name: pa.string() for name in col_names} - - table = pa_csv.read_csv( - csv_path, - convert_options=pa_csv.ConvertOptions( - column_types=all_string_types, - strings_can_be_null=False, - ), - read_options=pa_csv.ReadOptions(use_threads=True), - ) - - return _PatchCsvResult( - table=table, - has_cell_col="cell" in table.column_names, - has_x_col="x" in table.column_names, - has_y_col="y" in table.column_names, - has_gene_col="gene" in table.column_names, - has_feature_name_col="feature_name" in table.column_names, - ) - - -# --------------------------------------------------------------------------- -# CSV processing -# --------------------------------------------------------------------------- - - -def _transform_patch_coords( - csv_result: _PatchCsvResult, - offset_x: float, - offset_y: float, -) -> pa.Table: - """Shift transcript coordinates from local patch space to global space. - - Args: - csv_result: The raw CSV table and column flags. - offset_x: X offset for coordinate transform (microns). - offset_y: Y offset for coordinate transform (microns). - - Returns: - Table with x, y columns shifted to global coordinates. - """ - table = csv_result.table - - if table.num_rows == 0: - return table - - if csv_result.has_x_col: - x_f64 = pc.add( - table.column("x").cast(pa.float64()), - pa.scalar(offset_x, type=pa.float64()), - ) - table = table.set_column( - table.schema.get_field_index("x"), - "x", - float_str_array(x_f64), - ) - if csv_result.has_y_col: - y_f64 = pc.add( - table.column("y").cast(pa.float64()), - pa.scalar(offset_y, type=pa.float64()), - ) - table = table.set_column( - table.schema.get_field_index("y"), - "y", - float_str_array(y_f64), - ) - - return table - - -# --------------------------------------------------------------------------- -# Sopa conflict resolution -# --------------------------------------------------------------------------- - - -def _stitch_sopa_resolve( - metadata: PatchGridMetadata, - geo_results: list[_PatchGeoResult | None], - csv_results: list[_PatchCsvResult | None], - all_geojson_features: list[dict], - all_tables: list[pa.Table], - threshold: float = 0.5, -) -> set[str]: - """Stitch per-patch segmentation using spatial containment assignment. - - 1. Collect ALL non-empty polygons from all patches (no transcript filtering). - 2. Resolve overlapping polygons via sopa's solve_conflicts(). - 3. Assign sequential global cell IDs (cell-1, cell-2, ...). - 4. Spatially assign transcripts to resolved polygons using STRtree. - 5. Noise transcripts (outside all polygons) kept only from their core patch. - - This approach works regardless of whether Baysor's CSV ``cell`` column - matches GeoJSON cell IDs -- all assignment is done by spatial containment. - - Args: - metadata: Grid metadata with patch list. - geo_results: Per-patch GeoJSON results (already in global coords). - csv_results: Per-patch CSV results. - all_geojson_features: Output list to append resolved GeoJSON features. - all_tables: Output list to append processed CSV tables. - threshold: Overlap threshold for sopa's solve_conflicts (0-1). - - Returns: - Set of global cell IDs created by merging overlapping cells. - """ - # --- Phase 1: Collect all polygons from all patches --- - all_polygons: list = [] - patch_indices_list: list[int] = [] - - for i, patch in enumerate(metadata.patches): - geo_result = geo_results[i] - if geo_result is None: - continue - - for feat in geo_result.features: - polygon = shape(feat["geometry"]) - if polygon.is_empty: - continue - if not polygon.is_valid: - polygon = shapely.make_valid(polygon) - if polygon.is_empty: - continue - # make_valid can produce MultiPolygon/GeometryCollection; - # xeniumranger only accepts Polygon, so keep largest component - if polygon.geom_type == "MultiPolygon": - polygon = max(polygon.geoms, key=lambda g: g.area) - elif polygon.geom_type == "GeometryCollection": - polys = [g for g in polygon.geoms if g.geom_type == "Polygon"] - if not polys: - continue - polygon = max(polys, key=lambda g: g.area) - - all_polygons.append(polygon) - patch_indices_list.append(i) - - if not all_polygons: - print("[stitch] No polygons found in any patch") - # Still transform and collect CSVs as noise-only - for i, patch in enumerate(metadata.patches): - csv_result = csv_results[i] - if csv_result is None: - continue - offset_x = patch.global_bounds_um.x_min - offset_y = patch.global_bounds_um.y_min - transformed = _transform_patch_coords(csv_result, offset_x, offset_y) - if transformed.num_rows > 0: - all_tables.append(transformed) - return set() - - # --- Phase 2: Resolve overlapping polygons via sopa --- - patch_idx_array = np.array(patch_indices_list, dtype=np.int64) - input_gdf = gpd.GeoDataFrame(geometry=all_polygons) - resolved_gdf, kept_indices = solve_conflicts( - input_gdf, - threshold=threshold, - patch_indices=patch_idx_array, - return_indices=True, - ) - - # --- Phase 3: Assign global cell IDs to resolved polygons --- - merged_cell_ids: set[str] = set() - kept_arr = np.asarray(kept_indices) - resolved_polys: list = [] - resolved_ids: list[str] = [] - - for rank, orig_idx in enumerate(kept_arr, start=1): - global_id = f"cell-{rank}" - geom = resolved_gdf.geometry.iloc[rank - 1] - - # solve_conflicts union can produce MultiPolygon; keep largest - if geom.geom_type == "MultiPolygon": - geom = max(geom.geoms, key=lambda g: g.area) - elif geom.geom_type == "GeometryCollection": - polys = [g for g in geom.geoms if g.geom_type == "Polygon"] - if not polys: - continue - geom = max(polys, key=lambda g: g.area) - - if orig_idx < 0: - merged_cell_ids.add(global_id) - - resolved_polys.append(geom) - resolved_ids.append(global_id) - - all_geojson_features.append( - { - "type": "Feature", - "id": global_id, - "geometry": mapping(geom), - "properties": {"cell_id": global_id}, - } - ) - - print( - f"[stitch] Resolved {len(all_polygons)} input polygons to " - f"{len(resolved_polys)} cells ({len(merged_cell_ids)} merged)" - ) - - # --- Phase 4: Spatial transcript assignment via STRtree --- - poly_tree = shapely.STRtree(resolved_polys) - - for i, patch in enumerate(metadata.patches): - csv_result = csv_results[i] - if csv_result is None: - continue - - offset_x = patch.global_bounds_um.x_min - offset_y = patch.global_bounds_um.y_min - core = patch.core_bounds_um - - transformed = _transform_patch_coords(csv_result, offset_x, offset_y) - if transformed.num_rows == 0: - continue - - if not csv_result.has_x_col or not csv_result.has_y_col: - all_tables.append(transformed) - continue - - # Get global coordinates for spatial query - gx = transformed.column("x").cast(pa.float64()).to_numpy(zero_copy_only=False) - gy = transformed.column("y").cast(pa.float64()).to_numpy(zero_copy_only=False) - points = shapely.points(gx, gy) - - # Query STRtree: returns (input_indices, tree_indices) - point_hits, poly_hits = poly_tree.query(points, predicate="intersects") - - # Build point -> cell_id mapping (first hit wins) - point_to_cell: dict[int, str] = {} - for pt_idx, poly_idx in zip(point_hits, poly_hits): - if pt_idx not in point_to_cell: - point_to_cell[pt_idx] = resolved_ids[poly_idx] - - # Build cell and is_noise columns - n_rows = transformed.num_rows - cell_arr = [""] * n_rows - is_noise_arr = ["true"] * n_rows - for pt_idx, cell_id in point_to_cell.items(): - cell_arr[pt_idx] = cell_id - is_noise_arr[pt_idx] = "false" - - # Filter noise transcripts to core bounds only - # Assigned transcripts are kept from all patches (dedup later by transcript_id) - in_core = ( - (gx >= core.x_min) - & (gx < core.x_max) - & (gy >= core.y_min) - & (gy < core.y_max) - ) - is_assigned = np.array([c != "" for c in cell_arr]) - keep_mask = pa.array(is_assigned | in_core, type=pa.bool_()) - - filtered = transformed.filter(keep_mask) - cell_arr_filtered = [c for c, k in zip(cell_arr, (is_assigned | in_core)) if k] - is_noise_filtered = [ - n for n, k in zip(is_noise_arr, (is_assigned | in_core)) if k - ] - - if filtered.num_rows == 0: - continue - - # Set cell and is_noise columns - cell_idx = ( - filtered.schema.get_field_index("cell") - if "cell" in filtered.column_names - else None - ) - if cell_idx is not None: - filtered = filtered.set_column( - cell_idx, "cell", pa.array(cell_arr_filtered, type=pa.string()) - ) - else: - filtered = filtered.append_column( - "cell", pa.array(cell_arr_filtered, type=pa.string()) - ) - - noise_idx = ( - filtered.schema.get_field_index("is_noise") - if "is_noise" in filtered.column_names - else None - ) - if noise_idx is not None: - filtered = filtered.set_column( - noise_idx, - "is_noise", - pa.array(is_noise_filtered, type=pa.string()), - ) - else: - filtered = filtered.append_column( - "is_noise", pa.array(is_noise_filtered, type=pa.string()) - ) - - all_tables.append(filtered) - - return merged_cell_ids - - -# --------------------------------------------------------------------------- -# Main orchestrator -# --------------------------------------------------------------------------- - - -def stitch_transcript_assignments( - patches_dir: Path, - output_dir: Path, - csv_filename: str = "segmentation.csv", - geojson_filename: str = "segmentation_polygons.json", - max_workers: int | None = None, - min_transcripts_per_cell: int = 0, -) -> None: - """Stitch per-patch transcript assignments and polygons into unified output. - - For each patch, reads the transcript assignment CSV and polygon GeoJSON. - Cells are deduplicated using sopa's solve_conflicts() which resolves - overlapping cells at patch boundaries based on area overlap ratio. - - Processing is split into a parallel I/O phase (reading GeoJSON and CSV - files via thread pool) and a sequential phase (dedup, global cell ID - assignment, remapping, and concatenation). - - Args: - patches_dir: Directory containing patch subdirectories and patch_grid.json. - output_dir: Output directory for stitched CSV and GeoJSON. - csv_filename: CSV filename within each patch directory. - geojson_filename: GeoJSON filename within each patch directory. - max_workers: Maximum number of threads for parallel I/O. - """ - patches_dir = Path(patches_dir) - output_dir = Path(output_dir) - output_dir.mkdir(parents=True, exist_ok=True) - - metadata = load_grid_metadata(patches_dir / "patch_grid.json") - - n_patches = len(metadata.patches) - if max_workers is None: - max_workers = min(n_patches, os.cpu_count() or 1) - - # ---- Parallel phase: read GeoJSON and CSV files concurrently ---- - with ThreadPoolExecutor(max_workers=max_workers) as executor: - geo_futures = [ - executor.submit( - _read_and_transform_geojson, p, patches_dir, geojson_filename - ) - for p in metadata.patches - ] - csv_futures = [ - executor.submit(_read_patch_csv, p, patches_dir, csv_filename) - for p in metadata.patches - ] - geo_results = [f.result() for f in geo_futures] - csv_results = [f.result() for f in csv_futures] - - # ---- Sequential phase: assign global cell IDs, remap, concatenate ---- - all_tables: list[pa.Table] = [] - all_geojson_features: list[dict] = [] - - _stitch_sopa_resolve( - metadata, - geo_results, - csv_results, - all_geojson_features, - all_tables, - threshold=0.5, - ) - - # Concatenate all patch tables - if all_tables: - merged = pa.concat_tables(all_tables) - - # Deduplicate by transcript_id: prefer assigned over noise - if "transcript_id" in merged.column_names: - if "cell" in merged.column_names: - is_noise = pc.equal(merged.column("cell"), "").cast(pa.int8()) - row_order = pa.array(np.arange(merged.num_rows), type=pa.int64()) - sort_table = pa.table({"_noise": is_noise, "_row": row_order}) - sort_indices = pc.sort_indices( - sort_table, - sort_keys=[("_noise", "ascending"), ("_row", "ascending")], - ) - merged = merged.take(sort_indices) - - tid_np = merged.column("transcript_id").to_numpy(zero_copy_only=False) - _, first_indices = np.unique(tid_np, return_index=True) - first_indices.sort() - merged = merged.take(first_indices) - - # Post-stitch cell filter: drop cells below min_transcripts_per_cell - if min_transcripts_per_cell > 0 and "cell" in merged.column_names: - cell_col = merged.column("cell") - cell_counts: dict[str, int] = {} - for c in cell_col.to_pylist(): - if c: - cell_counts[c] = cell_counts.get(c, 0) + 1 - small_cells = { - cid - for cid, cnt in cell_counts.items() - if cnt < min_transcripts_per_cell - } - if small_cells: - # Reassign transcripts from small cells to noise - new_cell = ["" if c in small_cells else c for c in cell_col.to_pylist()] - new_noise = [ - "true" if c in small_cells else n - for c, n in zip( - cell_col.to_pylist(), - merged.column("is_noise").to_pylist() - if "is_noise" in merged.column_names - else ["false"] * merged.num_rows, - ) - ] - cidx = merged.column_names.index("cell") - merged = merged.set_column( - cidx, "cell", pa.array(new_cell, type=pa.string()) - ) - if "is_noise" in merged.column_names: - nidx = merged.column_names.index("is_noise") - merged = merged.set_column( - nidx, "is_noise", pa.array(new_noise, type=pa.string()) - ) - # Remove filtered cells from GeoJSON - all_geojson_features[:] = [ - f - for f in all_geojson_features - if str(f.get("id", f.get("properties", {}).get("cell_id", ""))) - not in small_cells - ] - print( - f"[stitch] Filtered {len(small_cells)} cells with " - f"<{min_transcripts_per_cell} transcripts" - ) - - # Log assignment stats - if "cell" in merged.column_names: - cell_vals = merged.column("cell").to_pylist() - n_assigned = sum(1 for c in cell_vals if c) - n_noise = sum(1 for c in cell_vals if not c) - print( - f"[stitch] Final: {merged.num_rows} transcripts, " - f"{n_assigned} assigned, {n_noise} noise" - ) - - # Cast is_noise to integer for xeniumranger compatibility - if "is_noise" in merged.column_names: - noise_col = merged.column("is_noise") - if noise_col.type == pa.string(): - lower = pc.utf8_lower(noise_col) - is_true = pc.or_(pc.equal(lower, "true"), pc.equal(lower, "1")) - idx = merged.column_names.index("is_noise") - merged = merged.set_column(idx, "is_noise", is_true.cast(pa.int8())) - - # Write CSV - if merged.num_rows > 0: - csv_out = output_dir / "xr-transcript-metadata.csv" - pa_csv.write_csv( - merged, - csv_out, - write_options=pa_csv.WriteOptions(quoting_style="needed"), - ) - - # Safety net: remove orphan polygons with zero transcripts - if all_geojson_features and all_tables: - csv_cell_ids: set[str] = set() - if "cell" in merged.column_names: - csv_cell_ids = set(c for c in merged.column("cell").to_pylist() if c) - all_geojson_features = [ - f - for f in all_geojson_features - if str(f.get("id", f.get("properties", {}).get("cell_id", ""))) - in csv_cell_ids - ] - - # Write merged GeoJSON - if all_geojson_features: - merged_geo = {"type": "FeatureCollection", "features": all_geojson_features} - write_geojson(merged_geo, output_dir / "xr-cell-polygons.geojson") - - -# --------------------------------------------------------------------------- -# CLI -# --------------------------------------------------------------------------- - - -def main() -> None: - parser = argparse.ArgumentParser( - description="Stitch per-patch Baysor segmentation results into unified output." - ) - parser.add_argument( - "--patches", - type=Path, - required=True, - help="Directory containing patch subdirectories and patch_grid.json", - ) - parser.add_argument( - "--output", - type=Path, - required=True, - help="Output directory for stitched CSV and GeoJSON", - ) - parser.add_argument( - "--csv-filename", - default="segmentation.csv", - help="CSV filename within each patch (default: segmentation.csv)", - ) - parser.add_argument( - "--geojson-filename", - default="segmentation_polygons.json", - help="GeoJSON filename within each patch (default: segmentation_polygons.json)", - ) - parser.add_argument( - "--min-transcripts-per-cell", - type=int, - default=0, - help="Drop cells with fewer transcripts (0 = no filter, default: 0)", - ) - args = parser.parse_args() - - stitch_transcript_assignments( - patches_dir=args.patches, - output_dir=args.output, - csv_filename=args.csv_filename, - geojson_filename=args.geojson_filename, - min_transcripts_per_cell=args.min_transcripts_per_cell, - ) - - -if __name__ == "__main__": - main() diff --git a/bin/utility_convert_mask_uint32.py b/bin/utility_convert_mask_uint32.py deleted file mode 100755 index 955ad4b7..00000000 --- a/bin/utility_convert_mask_uint32.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python3 -""" -Convert a segmentation mask TIFF to uint32 dtype. - -XeniumRanger import-segmentation requires uint32 masks, but upstream -segmenters (e.g. StarDist) often emit int32 labels. This script reads -the input mask, casts it to uint32, and writes the result. -""" - -import argparse - -import numpy as np -import tifffile - - -def convert_mask_to_uint32(input_path: str, output_path: str) -> None: - """ - Read a mask TIFF, cast to uint32, and write to output_path. - - Args: - input_path: Path to input mask TIFF (any integer dtype). - output_path: Path where the uint32 mask will be written. - """ - mask = tifffile.imread(input_path) - print(f"Input dtype: {mask.dtype}, shape: {mask.shape}, labels: {mask.max()}") - tifffile.imwrite(output_path, mask.astype(np.uint32)) - print("Output dtype: uint32") - - -def parse_args() -> argparse.Namespace: - """Parse command-line arguments.""" - parser = argparse.ArgumentParser( - description="Convert a segmentation mask TIFF to uint32 dtype." - ) - parser.add_argument( - "--input", required=True, help="Path to input mask TIFF" - ) - parser.add_argument( - "--output", required=True, help="Path where uint32 mask will be written" - ) - return parser.parse_args() - - -if __name__ == "__main__": - args = parse_args() - convert_mask_to_uint32(input_path=args.input, output_path=args.output) diff --git a/bin/utility_downscale_morphology.py b/bin/utility_downscale_morphology.py deleted file mode 100755 index 8544ecf3..00000000 --- a/bin/utility_downscale_morphology.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env python3 -""" -Pre-downscale a morphology image for Cellpose. - -Reduces image dimensions by a scale factor so that Cellpose's internal -rescaling (diam_mean / diameter) does not exceed GPU/CPU memory. The -scale factor defaults to diameter / diam_mean (e.g., 9 / 30 = 0.3). -After downscaling, Cellpose should run with --diameter equal to -diam_mean (no further internal rescaling). - -Outputs: - {prefix}/downscaled.tif - Downscaled image at the same dtype as input. - {prefix}/scale_info.json - Scale factor and original/new dimensions. -""" - -import argparse -import json -from pathlib import Path - -import tifffile -from skimage.transform import resize - -# Cellpose network requires a minimum spatial size of 256 px. -MIN_DIM = 256 - - -def downscale_image( - image_path: str, diameter: float, diam_mean: float, prefix: str -) -> None: - """ - Downscale image so Cellpose can run with diameter == diam_mean. - - Args: - image_path: Path to morphology TIFF (2D, 3D, or 4D). - diameter: Target object diameter (used to compute scale). - diam_mean: Cellpose model's mean diameter assumption. - prefix: Output directory. - """ - scale = min(diameter / diam_mean, 1.0) # clamp to prevent upscaling - - img = tifffile.imread(image_path) - print(f"Original: {img.shape}, dtype={img.dtype}, ndim={img.ndim}") - - # Handle multichannel OME-TIFFs: shape can be (H, W), (C, H, W), or (Z, C, H, W) - if img.ndim == 2: - orig_h, orig_w = img.shape - new_h = max(int(orig_h * scale), MIN_DIM) - new_w = max(int(orig_w * scale), MIN_DIM) - output_shape = (new_h, new_w) - elif img.ndim == 3: - orig_h, orig_w = img.shape[1], img.shape[2] - new_h = max(int(orig_h * scale), MIN_DIM) - new_w = max(int(orig_w * scale), MIN_DIM) - output_shape = (img.shape[0], new_h, new_w) - else: - orig_h, orig_w = img.shape[-2], img.shape[-1] - new_h = max(int(orig_h * scale), MIN_DIM) - new_w = max(int(orig_w * scale), MIN_DIM) - output_shape = img.shape[:-2] + (new_h, new_w) - - print(f"Downscaling by {scale:.3f}: ({orig_h}, {orig_w}) -> ({new_h}, {new_w})") - - img_ds = resize(img, output_shape, order=3, preserve_range=True, anti_aliasing=True) - img_ds = img_ds.astype(img.dtype) - - out_dir = Path(prefix) - out_dir.mkdir(parents=True, exist_ok=True) - tifffile.imwrite(str(out_dir / "downscaled.tif"), img_ds, compression="zlib") - - info = { - "scale": scale, - "orig_h": orig_h, - "orig_w": orig_w, - "new_h": new_h, - "new_w": new_w, - "diameter": diameter, - "diam_mean": diam_mean, - } - with open(out_dir / "scale_info.json", "w") as f: - json.dump(info, f) - print(f"Done: downscaled.tif written, shape={img_ds.shape}") - - -def parse_args() -> argparse.Namespace: - """Parse command-line arguments.""" - parser = argparse.ArgumentParser( - description="Pre-downscale a morphology image for Cellpose." - ) - parser.add_argument("--image", required=True, help="Morphology TIFF input") - parser.add_argument("--diameter", type=float, required=True, help="Target object diameter") - parser.add_argument("--diam-mean", type=float, required=True, help="Cellpose model diam_mean") - parser.add_argument("--prefix", required=True, help="Output directory") - return parser.parse_args() - - -if __name__ == "__main__": - args = parse_args() - downscale_image( - image_path=args.image, - diameter=args.diameter, - diam_mean=args.diam_mean, - prefix=args.prefix, - ) diff --git a/bin/utility_extract_dapi.py b/bin/utility_extract_dapi.py deleted file mode 100755 index 3d60f563..00000000 --- a/bin/utility_extract_dapi.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python3 -""" -Extract a single channel (e.g., DAPI) from a multi-channel OME-TIFF. - -Xenium morphology_focus.ome.tif has multiple channels (DAPI, boundary, -interior). Single-channel segmenters such as StarDist 2D_versatile_fluo -expect one channel as input. This script reads the input image, slices -the requested channel, and writes the result. -""" - -import argparse - -import tifffile - - -def extract_channel(input_path: str, output_path: str, channel_index: int) -> None: - """ - Read an OME-TIFF, extract a single channel, and write the result. - - Args: - input_path: Path to multi-channel OME-TIFF morphology image. - output_path: Path where the single-channel TIFF will be written. - channel_index: Index of the channel to extract. - """ - img = tifffile.imread(input_path) - orig_shape = img.shape - - if img.ndim == 3: - img = img[channel_index] - elif img.ndim == 4: - img = img[0, channel_index] - - tifffile.imwrite(output_path, img) - print(f"Input shape: {orig_shape} -> extracted channel {channel_index}: {img.shape}") - - -def parse_args() -> argparse.Namespace: - """Parse command-line arguments.""" - parser = argparse.ArgumentParser( - description="Extract a single channel from a multi-channel OME-TIFF." - ) - parser.add_argument( - "--input", required=True, help="Path to multi-channel OME-TIFF morphology image" - ) - parser.add_argument( - "--output", required=True, help="Path where the single-channel TIFF will be written" - ) - parser.add_argument( - "--channel-index", type=int, default=0, help="Channel index to extract (default: 0)" - ) - return parser.parse_args() - - -if __name__ == "__main__": - args = parse_args() - extract_channel( - input_path=args.input, - output_path=args.output, - channel_index=args.channel_index, - ) diff --git a/bin/utility_extract_data.py b/bin/utility_extract_data.py deleted file mode 100755 index 0ea737c2..00000000 --- a/bin/utility_extract_data.py +++ /dev/null @@ -1,208 +0,0 @@ -#!/usr/bin/env python3 -""" -Extract preview data from Baysor preview HTML reports. - -Parses embedded Vega-Lite spec variables and base64 PNG images from the -Baysor preview.html file, writing MultiQC-compatible TSV and PNG files. -""" - -import argparse -import base64 -import html -import json -import re -import sys -from pathlib import Path -from typing import Dict, List, Optional, Tuple - -import pandas as pd -from bs4 import BeautifulSoup - - -def get_png_files(soup: BeautifulSoup, outdir: Path) -> None: - """Get png base64 images following specific h1 tags in preview.html""" - target_ids = ["Transcript_Plots", "Noise_Level"] - outdir.mkdir(parents=True, exist_ok=True) - - for h1_id in target_ids: - h1_tag = soup.find("h1", id=h1_id) - if not h1_tag: - print(f"[WARN] No