From 8cc370f57e02bc788fc869fc356c0df1d2f1a5ca Mon Sep 17 00:00:00 2001 From: Sameesh Kher Date: Thu, 25 Jun 2026 23:57:28 +0200 Subject: [PATCH] Revert "Important! Template update for nf-core/tools v4.0.2" --- .devcontainer/devcontainer.json | 1 - .devcontainer/setup.sh | 2 +- .github/CONTRIBUTING.md | 125 + .github/ISSUE_TEMPLATE/bug_report.yml | 4 +- .github/ISSUE_TEMPLATE/config.yml | 6 +- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- .github/PULL_REQUEST_TEMPLATE.md | 10 +- .github/actions/get-shards/action.yml | 2 +- .github/actions/nf-test/action.yml | 10 +- .github/workflows/awsfulltest.yml | 32 +- .github/workflows/awstest.yml | 10 +- .github/workflows/branch.yml | 6 +- .github/workflows/clean-up.yml | 2 +- .github/workflows/download_pipeline.yml | 16 +- .github/workflows/fix_linting.yml | 26 +- .github/workflows/linting.yml | 34 +- .github/workflows/linting_comment.yml | 4 +- .github/workflows/nf-test.yml | 15 +- .github/workflows/release-announcements.yml | 4 +- .../workflows/template-version-comment.yml | 6 +- .gitignore | 4 +- .nf-core.yml | 17 +- .pre-commit-config.yaml | 16 +- .prettierignore | 4 +- CHANGELOG.md | 18 +- CITATIONS.md | 6 +- LICENSE | 2 +- README.md | 174 +- assets/adaptivecard.json | 67 + assets/config/xenium.toml | 15 + assets/email_template.html | 14 +- assets/email_template.txt | 10 +- assets/example_samplesheet.csv | 2 + assets/methods_description_template.yml | 9 +- assets/multiqc_config.yml | 38 +- assets/nf-core-spatialaxe_logo_light.png | Bin 0 -> 79633 bytes assets/nf-core-spatialxe_logo_light.png | Bin 80727 -> 0 bytes assets/samplesheet.csv | 5 +- assets/schema_input.json | 22 +- assets/sendmail_template.txt | 6 +- assets/slackreport.json | 34 + 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 | 80 + bin/spatialdata_meta.py | 125 + bin/spatialdata_write.py | 155 + 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_preview_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 | 91 +- 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 - conf/igenomes.config | 440 --- conf/igenomes_ignored.config | 9 - conf/modules.config | 362 +- conf/test.config | 19 +- conf/test_coordinate_mode.config | 31 + conf/test_full.config | 13 +- conf/test_image_mode.config | 31 + conf/test_preview_mode.config | 31 + conf/test_segfree_mode.config | 31 + docs/CONTRIBUTING.md | 185 - docs/README.md | 4 +- docs/images/nf-core-spatialaxe_logo_dark.png | Bin 0 -> 31726 bytes docs/images/nf-core-spatialaxe_logo_light.png | Bin 0 -> 26722 bytes docs/images/nf-core-spatialxe_logo_dark.png | Bin 30543 -> 0 bytes docs/images/nf-core-spatialxe_logo_light.png | Bin 25890 -> 0 bytes docs/images/spatialaxe-logo.png | Bin 0 -> 69003 bytes docs/images/spatialaxe-logo.svg | 224 ++ docs/images/spatialaxe-metromap.png | Bin 0 -> 709694 bytes docs/images/spatialaxe-metromap.svg | 3270 +++++++++++++++++ docs/output.md | 174 +- docs/usage.md | 173 +- main.nf | 87 +- modules.json | 69 +- modules/local/baysor/create_dataset/main.nf | 45 + modules/local/baysor/create_dataset/meta.yml | 82 + .../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 | 88 + .../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 | 72 + .../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 | 106 + 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 | 75 + .../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 | 72 + modules/local/ficture/preprocess/main.nf | 38 + modules/local/ficture/preprocess/meta.yml | 80 + modules/local/parquet_to_csv/main.nf | 33 + modules/local/parquet_to_csv/meta.yml | 70 + modules/local/proseg/preset/main.nf | 65 + 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 | 80 + .../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 | 64 + 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 | 49 + modules/local/segger/create_dataset/meta.yml | 63 + modules/local/segger/predict/main.nf | 51 + modules/local/segger/predict/meta.yml | 86 + modules/local/segger/train/main.nf | 66 + modules/local/segger/train/meta.yml | 65 + modules/local/spatialdata/merge/main.nf | 48 + modules/local/spatialdata/merge/meta.yml | 44 + modules/local/spatialdata/meta/main.nf | 49 + modules/local/spatialdata/meta/meta.yml | 44 + modules/local/spatialdata/write/main.nf | 53 + modules/local/spatialdata/write/meta.yml | 40 + .../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 + modules/local/utility/get_coordinates/main.nf | 42 + .../local/utility/get_coordinates/meta.yml | 81 + modules/local/utility/parquet_to_csv/main.nf | 46 + modules/local/utility/parquet_to_csv/meta.yml | 42 + .../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 + modules/local/utility/segger2xr/main.nf | 50 + modules/local/utility/segger2xr/meta.yml | 101 + .../local/utility/split_transcripts/main.nf | 46 + .../local/utility/split_transcripts/meta.yml | 54 + 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 + 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 + .../linux_amd64-bd-5cb1a2fa2f18c7c2_1.txt | 822 ----- .../linux_arm64-bd-e455e32f745abe68_1.txt | 769 ---- 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 --- .../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 | 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 | 93 + 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 | 54 + .../nf-core/{fastqc => unzip}/environment.yml | 2 +- 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 | 68 + .../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 + 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 | 45 + .../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 + nextflow.config | 251 +- nextflow_schema.json | 437 ++- nf-test.config | 26 +- ro-crate-metadata.json | 86 +- .../local/baysor_generate_preview/main.nf | 49 + .../local/baysor_generate_preview/meta.yml | 34 + .../local/baysor_generate_segfree/main.nf | 52 + .../local/baysor_generate_segfree/meta.yml | 28 + .../main.nf | 83 + .../meta.yml | 58 + .../baysor_run_transcripts_parquet/main.nf | 166 + .../baysor_run_transcripts_parquet/meta.yml | 54 + .../main.nf | 104 + .../main.nf | 191 + .../meta.yml | 86 + .../main.nf | 141 + .../meta.yml | 59 + .../local/ficture_preprocess_model/main.nf | 39 + .../local/ficture_preprocess_model/meta.yml | 34 + .../local/opt_flip_track_stat/main.nf | 34 + .../local/opt_flip_track_stat/meta.yml | 45 + .../local/proseg_preset_proseg2baysor/main.nf | 50 + .../proseg_preset_proseg2baysor/meta.yml | 48 + .../proseg_preset_proseg2baysor_tiled/main.nf | 86 + .../local/segger_create_train_predict/main.nf | 77 + .../segger_create_train_predict/meta.yml | 55 + .../spatialdata_write_meta_merge/main.nf | 81 + .../spatialdata_write_meta_merge/meta.yml | 50 + .../main.nf | 70 + .../utils_nfcore_spatialaxe_pipeline/main.nf | 351 ++ .../utils_nfcore_spatialxe_pipeline/main.nf | 295 -- .../main.nf | 133 + .../meta.yml | 32 + .../xeniumranger_relabel_resegment/main.nf | 29 + .../xeniumranger_relabel_resegment/meta.yml | 33 + .../main.nf | 61 + .../meta.yml | 33 + .../tests/main.function.nf.test | 4 +- .../tests/main.function.nf.test.snap | 2 +- .../nf-core/utils_nfcore_pipeline/main.nf | 68 +- .../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/.nftignore | 2 +- tests/conftest.py | 9 + tests/coordinate_mode.nf.test | 36 + tests/coordinate_mode.nf.test.snap | 152 + tests/default.nf.test | 23 +- tests/default.nf.test.snap | 152 + tests/image_mode.nf.test | 36 + tests/image_mode.nf.test.snap | 167 + tests/nextflow.config | 3 +- tests/preview_mode.nf.test | 36 + tests/preview_mode.nf.test.snap | 120 + tests/segfree_mode.nf.test | 36 + tests/segfree_mode.nf.test.snap | 97 + tests/test_xenium_patch/__init__.py | 0 .../test_divide_transcripts.py | 720 ++++ .../test_stitch_transcripts.py | 865 +++++ workflows/spatialaxe.nf | 794 ++++ workflows/spatialxe.nf | 101 - 315 files changed, 25427 insertions(+), 7496 deletions(-) create mode 100644 .github/CONTRIBUTING.md create mode 100644 assets/adaptivecard.json create mode 100644 assets/config/xenium.toml create mode 100644 assets/example_samplesheet.csv create mode 100644 assets/nf-core-spatialaxe_logo_light.png delete mode 100644 assets/nf-core-spatialxe_logo_light.png create mode 100644 assets/slackreport.json create mode 100755 bin/baysor_create_dataset.py create mode 100755 bin/baysor_preprocess_transcripts.py create mode 100755 bin/divide_transcripts.py create mode 100755 bin/ficture_preprocess.py create mode 100755 bin/segger_create_dataset.py create mode 100755 bin/segger_predict.py create mode 100755 bin/spatialdata_merge.py create mode 100755 bin/spatialdata_meta.py create mode 100755 bin/spatialdata_write.py create mode 100755 bin/stitch_transcripts.py create mode 100755 bin/utility_convert_mask_uint32.py create mode 100755 bin/utility_downscale_morphology.py create mode 100755 bin/utility_extract_dapi.py create mode 100755 bin/utility_extract_preview_data.py create mode 100755 bin/utility_get_coordinates.py create mode 100755 bin/utility_parquet_to_csv.py create mode 100755 bin/utility_resize_tif.py create mode 100755 bin/utility_segger2xr.py create mode 100755 bin/utility_split_transcripts.py create mode 100755 bin/utility_upscale_mask.py create mode 100755 bin/xenium_patch_stitch_postprocess.py create mode 100755 bin/xenium_patch_stitch_transcripts.py delete mode 100644 conf/containers_conda_lock_files_amd64.config delete mode 100644 conf/containers_conda_lock_files_arm64.config delete mode 100644 conf/containers_docker_amd64.config delete mode 100644 conf/containers_docker_arm64.config delete mode 100644 conf/containers_singularity_https_amd64.config delete mode 100644 conf/containers_singularity_https_arm64.config delete mode 100644 conf/containers_singularity_oras_amd64.config delete mode 100644 conf/containers_singularity_oras_arm64.config delete mode 100644 conf/igenomes.config delete mode 100644 conf/igenomes_ignored.config create mode 100644 conf/test_coordinate_mode.config create mode 100644 conf/test_image_mode.config create mode 100644 conf/test_preview_mode.config create mode 100644 conf/test_segfree_mode.config delete mode 100644 docs/CONTRIBUTING.md create mode 100644 docs/images/nf-core-spatialaxe_logo_dark.png create mode 100644 docs/images/nf-core-spatialaxe_logo_light.png delete mode 100644 docs/images/nf-core-spatialxe_logo_dark.png delete mode 100644 docs/images/nf-core-spatialxe_logo_light.png create mode 100644 docs/images/spatialaxe-logo.png create mode 100644 docs/images/spatialaxe-logo.svg create mode 100644 docs/images/spatialaxe-metromap.png create mode 100644 docs/images/spatialaxe-metromap.svg create mode 100644 modules/local/baysor/create_dataset/main.nf create mode 100644 modules/local/baysor/create_dataset/meta.yml create mode 100644 modules/local/baysor/create_dataset/tests/main.nf.test create mode 100644 modules/local/baysor/create_dataset/tests/main.nf.test.snap create mode 100644 modules/local/baysor/create_dataset/tests/nextflow.config create mode 100644 modules/local/baysor/preprocess/main.nf create mode 100644 modules/local/baysor/preprocess/meta.yml create mode 100644 modules/local/baysor/preprocess/tests/main.nf.test create mode 100644 modules/local/baysor/preprocess/tests/main.nf.test.snap create mode 100644 modules/local/baysor/preprocess/tests/nextflow.config create mode 100644 modules/local/baysor/preview/main.nf create mode 100644 modules/local/baysor/preview/meta.yml create mode 100644 modules/local/baysor/preview/tests/main.nf.test create mode 100644 modules/local/baysor/preview/tests/main.nf.test.snap create mode 100644 modules/local/baysor/preview/tests/nextflow.config create mode 100644 modules/local/baysor/run/main.nf create mode 100644 modules/local/baysor/run/meta.yml create mode 100644 modules/local/baysor/run/tests/main.nf.test create mode 100644 modules/local/baysor/run/tests/main.nf.test.snap create mode 100644 modules/local/baysor/segfree/main.nf create mode 100644 modules/local/baysor/segfree/meta.yml create mode 100644 modules/local/baysor/segfree/tests/main.nf.test create mode 100644 modules/local/baysor/segfree/tests/main.nf.test.snap create mode 100644 modules/local/ficture/model/main.nf create mode 100644 modules/local/ficture/model/meta.yml create mode 100644 modules/local/ficture/preprocess/main.nf create mode 100644 modules/local/ficture/preprocess/meta.yml create mode 100644 modules/local/parquet_to_csv/main.nf create mode 100644 modules/local/parquet_to_csv/meta.yml create mode 100644 modules/local/proseg/preset/main.nf create mode 100644 modules/local/proseg/preset/meta.yml create mode 100644 modules/local/proseg/preset/tests/main.nf.test create mode 100644 modules/local/proseg/preset/tests/main.nf.test.snap create mode 100644 modules/local/proseg/preset/tests/nextflow.config create mode 100644 modules/local/proseg/proseg2baysor/main.nf create mode 100644 modules/local/proseg/proseg2baysor/meta.yml create mode 100644 modules/local/proseg/proseg2baysor/tests/main.nf.test create mode 100644 modules/local/proseg/proseg2baysor/tests/main.nf.test.snap create mode 100644 modules/local/proseg/proseg2baysor/tests/nextflow.config create mode 100644 modules/local/resolift/main.nf create mode 100644 modules/local/resolift/meta.yml create mode 100644 modules/local/resolift/tests/main.nf.test create mode 100644 modules/local/resolift/tests/main.nf.test.snap create mode 100644 modules/local/resolift/tests/nextflow.config create mode 100644 modules/local/segger/Dockerfile create mode 100644 modules/local/segger/create_dataset/main.nf create mode 100644 modules/local/segger/create_dataset/meta.yml create mode 100644 modules/local/segger/predict/main.nf create mode 100644 modules/local/segger/predict/meta.yml create mode 100644 modules/local/segger/train/main.nf create mode 100644 modules/local/segger/train/meta.yml create mode 100644 modules/local/spatialdata/merge/main.nf create mode 100644 modules/local/spatialdata/merge/meta.yml create mode 100644 modules/local/spatialdata/meta/main.nf create mode 100644 modules/local/spatialdata/meta/meta.yml create mode 100644 modules/local/spatialdata/write/main.nf create mode 100644 modules/local/spatialdata/write/meta.yml create mode 100644 modules/local/utility/convert_mask_uint32/main.nf create mode 100644 modules/local/utility/convert_mask_uint32/meta.yml create mode 100644 modules/local/utility/downscale_morphology/main.nf create mode 100644 modules/local/utility/downscale_morphology/meta.yml create mode 100644 modules/local/utility/extract_dapi/main.nf create mode 100644 modules/local/utility/extract_dapi/meta.yml create mode 100644 modules/local/utility/extract_preview_data/main.nf create mode 100644 modules/local/utility/extract_preview_data/meta.yml create mode 100644 modules/local/utility/get_coordinates/main.nf create mode 100644 modules/local/utility/get_coordinates/meta.yml create mode 100644 modules/local/utility/parquet_to_csv/main.nf create mode 100644 modules/local/utility/parquet_to_csv/meta.yml create mode 100644 modules/local/utility/reconstruct_patches/main.nf create mode 100644 modules/local/utility/reconstruct_patches/meta.yml create mode 100644 modules/local/utility/resize_tif/main.nf create mode 100644 modules/local/utility/resize_tif/meta.yml create mode 100644 modules/local/utility/segger2xr/main.nf create mode 100644 modules/local/utility/segger2xr/meta.yml create mode 100644 modules/local/utility/split_transcripts/main.nf create mode 100644 modules/local/utility/split_transcripts/meta.yml create mode 100644 modules/local/utility/upscale_mask/main.nf create mode 100644 modules/local/utility/upscale_mask/meta.yml create mode 100644 modules/local/xenium_patch/divide/main.nf create mode 100644 modules/local/xenium_patch/divide/meta.yml create mode 100644 modules/local/xenium_patch/stitch/main.nf create mode 100644 modules/local/xenium_patch/stitch/meta.yml create mode 100644 modules/nf-core/cellpose/Dockerfile create mode 100644 modules/nf-core/cellpose/cellpose.diff create mode 100644 modules/nf-core/cellpose/environment.yml create mode 100644 modules/nf-core/cellpose/main.nf create mode 100644 modules/nf-core/cellpose/meta.yml create mode 100644 modules/nf-core/cellpose/tests/main.nf.test create mode 100644 modules/nf-core/cellpose/tests/main.nf.test.snap create mode 100644 modules/nf-core/cellpose/tests/nextflow_wflows.config delete mode 100644 modules/nf-core/fastqc/.conda-lock/linux_amd64-bd-5cb1a2fa2f18c7c2_1.txt delete mode 100644 modules/nf-core/fastqc/.conda-lock/linux_arm64-bd-e455e32f745abe68_1.txt delete mode 100644 modules/nf-core/fastqc/main.nf delete mode 100644 modules/nf-core/fastqc/meta.yml delete mode 100644 modules/nf-core/fastqc/tests/main.nf.test delete mode 100644 modules/nf-core/fastqc/tests/main.nf.test.snap delete mode 100644 modules/nf-core/multiqc/.conda-lock/linux_amd64-bd-c1f4a7982b743963_1.txt delete mode 100644 modules/nf-core/multiqc/.conda-lock/linux_amd64-bd-db7c73dae76bc9e6_1.txt delete mode 100644 modules/nf-core/multiqc/.conda-lock/linux_arm64-bd-40bf3b435e89dc22_1.txt delete mode 100644 modules/nf-core/multiqc/.conda-lock/linux_arm64-bd-d167b8012595a136_1.txt create mode 100644 modules/nf-core/multiqc/multiqc.diff create mode 100644 modules/nf-core/opt/flip/main.nf create mode 100644 modules/nf-core/opt/flip/meta.yml create mode 100644 modules/nf-core/opt/flip/opt-flip.diff create mode 100644 modules/nf-core/opt/flip/tests/main.nf.test create mode 100644 modules/nf-core/opt/flip/tests/main.nf.test.snap create mode 100644 modules/nf-core/opt/stat/main.nf create mode 100644 modules/nf-core/opt/stat/meta.yml create mode 100644 modules/nf-core/opt/stat/opt-stat.diff create mode 100644 modules/nf-core/opt/stat/tests/main.nf.test create mode 100644 modules/nf-core/opt/stat/tests/main.nf.test.snap create mode 100644 modules/nf-core/opt/track/main.nf create mode 100644 modules/nf-core/opt/track/meta.yml create mode 100644 modules/nf-core/opt/track/opt-track.diff create mode 100644 modules/nf-core/opt/track/tests/main.nf.test create mode 100644 modules/nf-core/opt/track/tests/main.nf.test.snap create mode 100644 modules/nf-core/stardist/environment.yml create mode 100644 modules/nf-core/stardist/main.nf create mode 100644 modules/nf-core/stardist/meta.yml create mode 100644 modules/nf-core/stardist/stardist.diff create mode 100644 modules/nf-core/stardist/tests/main.nf.test create mode 100644 modules/nf-core/stardist/tests/main.nf.test.snap create mode 100644 modules/nf-core/stardist/tests/nextflow.config create mode 100644 modules/nf-core/untar/environment.yml create mode 100644 modules/nf-core/untar/main.nf create mode 100644 modules/nf-core/untar/meta.yml create mode 100644 modules/nf-core/untar/tests/main.nf.test create mode 100644 modules/nf-core/untar/tests/main.nf.test.snap create mode 100644 modules/nf-core/untar/untar.diff rename modules/nf-core/{fastqc => unzip}/environment.yml (85%) create mode 100644 modules/nf-core/unzip/main.nf create mode 100644 modules/nf-core/unzip/meta.yml create mode 100644 modules/nf-core/unzip/tests/main.nf.test create mode 100644 modules/nf-core/unzip/tests/main.nf.test.snap create mode 100644 modules/nf-core/unzip/unzip.diff create mode 100644 modules/nf-core/xeniumranger/import-segmentation/main.nf create mode 100644 modules/nf-core/xeniumranger/import-segmentation/meta.yml create mode 100644 modules/nf-core/xeniumranger/import-segmentation/tests/main.nf.test create mode 100644 modules/nf-core/xeniumranger/import-segmentation/tests/main.nf.test.snap create mode 100644 modules/nf-core/xeniumranger/import-segmentation/tests/nextflow.config create mode 100644 modules/nf-core/xeniumranger/import-segmentation/tests/tags.yml create mode 100644 modules/nf-core/xeniumranger/relabel/main.nf create mode 100644 modules/nf-core/xeniumranger/relabel/meta.yml create mode 100644 modules/nf-core/xeniumranger/relabel/tests/main.nf.test create mode 100644 modules/nf-core/xeniumranger/relabel/tests/main.nf.test.snap create mode 100644 modules/nf-core/xeniumranger/relabel/tests/nextflow.config create mode 100644 modules/nf-core/xeniumranger/relabel/tests/tags.yml create mode 100644 modules/nf-core/xeniumranger/resegment/main.nf create mode 100644 modules/nf-core/xeniumranger/resegment/meta.yml create mode 100644 modules/nf-core/xeniumranger/resegment/tests/main.nf.test create mode 100644 modules/nf-core/xeniumranger/resegment/tests/main.nf.test.snap create mode 100644 modules/nf-core/xeniumranger/resegment/tests/nextflow.config create mode 100644 modules/nf-core/xeniumranger/resegment/tests/tags.yml create mode 100644 subworkflows/local/baysor_generate_preview/main.nf create mode 100644 subworkflows/local/baysor_generate_preview/meta.yml create mode 100644 subworkflows/local/baysor_generate_segfree/main.nf create mode 100644 subworkflows/local/baysor_generate_segfree/meta.yml create mode 100644 subworkflows/local/baysor_run_prior_segmentation_mask/main.nf create mode 100644 subworkflows/local/baysor_run_prior_segmentation_mask/meta.yml create mode 100644 subworkflows/local/baysor_run_transcripts_parquet/main.nf create mode 100644 subworkflows/local/baysor_run_transcripts_parquet/meta.yml create mode 100644 subworkflows/local/baysor_run_transcripts_parquet_tiled/main.nf create mode 100644 subworkflows/local/cellpose_baysor_import_segmentation/main.nf create mode 100644 subworkflows/local/cellpose_baysor_import_segmentation/meta.yml create mode 100644 subworkflows/local/cellpose_resolift_morphology_ome_tif/main.nf create mode 100644 subworkflows/local/cellpose_resolift_morphology_ome_tif/meta.yml create mode 100644 subworkflows/local/ficture_preprocess_model/main.nf create mode 100644 subworkflows/local/ficture_preprocess_model/meta.yml create mode 100644 subworkflows/local/opt_flip_track_stat/main.nf create mode 100644 subworkflows/local/opt_flip_track_stat/meta.yml create mode 100644 subworkflows/local/proseg_preset_proseg2baysor/main.nf create mode 100644 subworkflows/local/proseg_preset_proseg2baysor/meta.yml create mode 100644 subworkflows/local/proseg_preset_proseg2baysor_tiled/main.nf create mode 100644 subworkflows/local/segger_create_train_predict/main.nf create mode 100644 subworkflows/local/segger_create_train_predict/meta.yml create mode 100644 subworkflows/local/spatialdata_write_meta_merge/main.nf create mode 100644 subworkflows/local/spatialdata_write_meta_merge/meta.yml create mode 100644 subworkflows/local/stardist_resolift_morphology_ome_tif/main.nf create mode 100644 subworkflows/local/utils_nfcore_spatialaxe_pipeline/main.nf delete mode 100644 subworkflows/local/utils_nfcore_spatialxe_pipeline/main.nf create mode 100644 subworkflows/local/xeniumranger_import_segmentation_redefine_bundle/main.nf create mode 100644 subworkflows/local/xeniumranger_import_segmentation_redefine_bundle/meta.yml create mode 100644 subworkflows/local/xeniumranger_relabel_resegment/main.nf create mode 100644 subworkflows/local/xeniumranger_relabel_resegment/meta.yml create mode 100644 subworkflows/local/xeniumranger_resegment_morphology_ome_tif/main.nf create mode 100644 subworkflows/local/xeniumranger_resegment_morphology_ome_tif/meta.yml delete mode 100644 subworkflows/nf-core/utils_nfcore_pipeline/tests/main.nf.test delete mode 100644 subworkflows/nf-core/utils_nfcore_pipeline/tests/main.nf.test.snap create mode 100644 tests/conftest.py create mode 100644 tests/coordinate_mode.nf.test create mode 100644 tests/coordinate_mode.nf.test.snap create mode 100644 tests/default.nf.test.snap create mode 100644 tests/image_mode.nf.test create mode 100644 tests/image_mode.nf.test.snap create mode 100644 tests/preview_mode.nf.test create mode 100644 tests/preview_mode.nf.test.snap create mode 100644 tests/segfree_mode.nf.test create mode 100644 tests/segfree_mode.nf.test.snap create mode 100644 tests/test_xenium_patch/__init__.py create mode 100644 tests/test_xenium_patch/test_divide_transcripts.py create mode 100644 tests/test_xenium_patch/test_stitch_transcripts.py create mode 100644 workflows/spatialaxe.nf delete mode 100644 workflows/spatialxe.nf diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 237c9ed0..97c8c97f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,5 +1,4 @@ { - "$schema": "https://raw.githubusercontent.com/devcontainers/spec/main/schemas/devContainer.schema.json", "name": "nfcore", "image": "nfcore/devcontainer:latest", diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh index b16c2e73..033278f7 100755 --- a/.devcontainer/setup.sh +++ b/.devcontainer/setup.sh @@ -10,4 +10,4 @@ export PS1='\[\e[3;36m\]\w ->\[\e[0m\\] ' nextflow self-update # Update welcome message -echo "Welcome to the nf-core/spatialxe devcontainer!" > /usr/local/etc/vscode-dev-containers/first-run-notice.txt +echo "Welcome to the nf-core/spatialaxe devcontainer!" > /usr/local/etc/vscode-dev-containers/first-run-notice.txt diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000..21358536 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,125 @@ +# `nf-core/spatialaxe`: Contributing Guidelines + +Hi there! +Many thanks for taking an interest in improving nf-core/spatialaxe. + +We try to manage the required tasks for nf-core/spatialaxe 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/spatialaxe then the best place to ask is on the nf-core Slack [#spatialaxe](https://nfcore.slack.com/channels/spatialaxe) channel ([join our Slack here](https://nf-co.re/join/slack)). + +## Contribution workflow + +If you'd like to write some code for nf-core/spatialaxe, the standard workflow is as follows: + +1. Check that there isn't already an issue about your idea in the [nf-core/spatialaxe issues](https://github.com/nf-core/spatialaxe/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/spatialaxe repository](https://github.com/nf-core/spatialaxe) 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/spatialaxe documentation](https://nf-co.re/spatialaxe/usage) and don't hesitate to get in touch on the nf-core Slack [#spatialaxe](https://nfcore.slack.com/channels/spatialaxe) channel ([join our Slack here](https://nf-co.re/join/slack)). + +## Pipeline contribution conventions + +To make the `nf-core/spatialaxe` 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/spatialaxe/codespaces) +- Tools installed + - nf-core + - Nextflow + +Devcontainer specs: + +- [DevContainer config](.devcontainer/devcontainer.json) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 08eca313..b08b451d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -8,7 +8,7 @@ body: Before you post this issue, please check the documentation: - [nf-core website: troubleshooting](https://nf-co.re/usage/troubleshooting) - - [nf-core/spatialxe pipeline documentation](https://nf-co.re/spatialxe/usage) + - [nf-core/spatialaxe pipeline documentation](https://nf-co.re/spatialaxe/usage) - type: textarea id: description attributes: @@ -46,4 +46,4 @@ body: * Executor _(eg. slurm, local, awsbatch)_ * Container engine: _(e.g. Docker, Singularity, Conda, Podman, Shifter, Charliecloud, or Apptainer)_ * OS _(eg. CentOS Linux, macOS, Linux Mint)_ - * Version of nf-core/spatialxe _(eg. 1.1, 1.5, 1.8.2)_ + * Version of nf-core/spatialaxe _(eg. 1.1, 1.5, 1.8.2)_ diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 6192b81d..881458ad 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,6 +2,6 @@ contact_links: - name: Join nf-core url: https://nf-co.re/join about: Please join the nf-core community here - - name: "Slack #spatialxe channel" - url: https://nfcore.slack.com/channels/spatialxe - about: Discussion about the nf-core/spatialxe pipeline + - name: "Slack #spatialaxe channel" + url: https://nfcore.slack.com/channels/spatialaxe + about: Discussion about the nf-core/spatialaxe pipeline diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 9276baef..23ffd0c9 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,5 +1,5 @@ name: Feature request -description: Suggest an idea for the nf-core/spatialxe pipeline +description: Suggest an idea for the nf-core/spatialaxe pipeline labels: enhancement body: - type: textarea diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 6a378da4..7d1f9dfd 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,22 +1,22 @@ ## 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/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. +- [ ] If you've added a new tool - have you followed the pipeline conventions in the [contribution docs](https://github.com/nf-core/spatialaxe/tree/main/.github/CONTRIBUTING.md) +- [ ] If necessary, also make a PR on the nf-core/spatialaxe _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 `). - [ ] Check for unexpected warnings in debug mode (`nextflow run . -profile debug,test,docker --outdir `). diff --git a/.github/actions/get-shards/action.yml b/.github/actions/get-shards/action.yml index e2833ee9..34085279 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@4069fbbaabe94c08faba4ad261bfa88225ba133f # v2 + uses: nf-core/setup-nf-test@v1 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 ad686e8e..3b9724c7 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@b4ec1bc7c16a94435159de94a05253542fddf6ef # v3 + uses: nf-core/setup-nextflow@v2 with: version: "${{ env.NXF_VERSION }}" - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 + uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 with: python-version: "3.14" - name: Install nf-test - uses: nf-core/setup-nf-test@4069fbbaabe94c08faba4ad261bfa88225ba133f # v2 + uses: nf-core/setup-nf-test@v1 with: version: "${{ env.NFT_VER }}" install-pdiff: true - name: Setup apptainer if: contains(inputs.profile, 'singularity') - uses: eWaterCycle/setup-apptainer@3f706d898c9db585b1d741b4692e66755f3a1b40 # v2 + uses: eWaterCycle/setup-apptainer@main - 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@8ee1f361103df19b6f8c8655fd3967a8ecb162d5 # v4 + uses: conda-incubator/setup-miniconda@505e6394dae86d6a5c7fbb6e3fb8938e3e863830 # v3 with: auto-update-conda: true conda-solver: libmamba diff --git a/.github/workflows/awsfulltest.yml b/.github/workflows/awsfulltest.yml index 5eb53aee..874c73e8 100644 --- a/.github/workflows/awsfulltest.yml +++ b/.github/workflows/awsfulltest.yml @@ -14,7 +14,7 @@ jobs: run-platform: name: Run AWS full tests # run only if the PR is approved by at least 2 reviewers and against the master/main branch or manually triggered - if: github.repository == 'nf-core/spatialxe' && github.event.review.state == 'approved' && (github.event.pull_request.base.ref == 'master' || github.event.pull_request.base.ref == 'main') || github.event_name == 'workflow_dispatch' || github.event_name == 'release' + if: github.repository == 'nf-core/spatialaxe' && github.event.review.state == 'approved' && (github.event.pull_request.base.ref == 'master' || github.event.pull_request.base.ref == 'main') || github.event_name == 'workflow_dispatch' || github.event_name == 'release' runs-on: ubuntu-latest steps: - name: Set revision variable @@ -23,8 +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@51565b514bff1827cf34620de25d0055759f1fc9 # v2 - # TODO nf-core: You can customise AWS full pipeline tests as required + uses: seqeralabs/action-tower-launch@v2 # 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,34 +31,15 @@ jobs: access_token: ${{ secrets.TOWER_ACCESS_TOKEN }} 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:' - } - } + workdir: s3://${{ vars.AWS_S3_BUCKET }}/work/spatialaxe/work-${{ steps.revision.outputs.revision }} parameters: | { - "outdir": "s3://${{ vars.AWS_S3_BUCKET }}/spatialxe/results-${{ steps.revision.outputs.revision }}" + "hook_url": "${{ secrets.MEGATESTS_ALERTS_SLACK_HOOK_URL }}", + "outdir": "s3://${{ vars.AWS_S3_BUCKET }}/spatialaxe/results-${{ steps.revision.outputs.revision }}" } profiles: test_full - - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + - 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 632a6a5e..8a8d7f8e 100644 --- a/.github/workflows/awstest.yml +++ b/.github/workflows/awstest.yml @@ -7,25 +7,25 @@ on: jobs: run-platform: name: Run AWS tests - if: github.repository == 'nf-core/spatialxe' + if: github.repository == 'nf-core/spatialaxe' runs-on: ubuntu-latest steps: # Launch workflow using Seqera Platform CLI tool action - name: Launch workflow via Seqera Platform - uses: seqeralabs/action-tower-launch@51565b514bff1827cf34620de25d0055759f1fc9 # v2 + uses: seqeralabs/action-tower-launch@v2 with: workspace_id: ${{ vars.TOWER_WORKSPACE_ID }} access_token: ${{ secrets.TOWER_ACCESS_TOKEN }} compute_env: ${{ vars.TOWER_COMPUTE_ENV }} revision: ${{ github.sha }} - workdir: s3://${{ vars.AWS_S3_BUCKET }}/work/spatialxe/work-${{ github.sha }} + workdir: s3://${{ vars.AWS_S3_BUCKET }}/work/spatialaxe/work-${{ github.sha }} parameters: | { - "outdir": "s3://${{ vars.AWS_S3_BUCKET }}/spatialxe/results-test-${{ github.sha }}" + "outdir": "s3://${{ vars.AWS_S3_BUCKET }}/spatialaxe/results-test-${{ github.sha }}" } profiles: test - - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: Seqera Platform debug log file path: | diff --git a/.github/workflows/branch.yml b/.github/workflows/branch.yml index 873c6700..1d9a4442 100644 --- a/.github/workflows/branch.yml +++ b/.github/workflows/branch.yml @@ -13,15 +13,15 @@ jobs: steps: # PRs to the nf-core repo main/master branch are only ok if coming from the nf-core repo `dev` or any `patch` branches - name: Check PRs - if: github.repository == 'nf-core/spatialxe' + if: github.repository == 'nf-core/spatialaxe' run: | - { [[ ${{github.event.pull_request.head.repo.full_name }} == nf-core/spatialxe ]] && [[ $GITHUB_HEAD_REF == "dev" ]]; } || [[ $GITHUB_HEAD_REF == "patch" ]] + { [[ ${{github.event.pull_request.head.repo.full_name }} == nf-core/spatialaxe ]] && [[ $GITHUB_HEAD_REF == "dev" ]]; } || [[ $GITHUB_HEAD_REF == "patch" ]] # If the above check failed, post a comment on the PR explaining the failure # 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@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3 + uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2 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 172de6f3..6adb0fff 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@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10 + - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # 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 a7bf4fc2..45884ff9 100644 --- a/.github/workflows/download_pipeline.yml +++ b/.github/workflows/download_pipeline.yml @@ -38,16 +38,13 @@ 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@b4ec1bc7c16a94435159de94a05253542fddf6ef # v3 + uses: nf-core/setup-nextflow@v2 - name: Disk space cleanup uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 + - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 with: python-version: "3.14" architecture: "x64" @@ -57,15 +54,10 @@ 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 nf-core==${{ steps.read_yml.outputs['nf_core_version'] }} + pip install git+https://github.com/nf-core/tools.git - name: Make a cache directory for the container images run: | @@ -135,7 +127,7 @@ jobs: fi - name: Upload Nextflow logfile for debugging purposes - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + 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 e9c90ca8..5f731f04 100644 --- a/.github/workflows/fix_linting.yml +++ b/.github/workflows/fix_linting.yml @@ -9,11 +9,11 @@ jobs: if: > contains(github.event.comment.html_url, '/pull/') && contains(github.event.comment.body, '@nf-core-bot fix linting') && - github.repository == 'nf-core/spatialxe' + github.repository == 'nf-core/spatialaxe' runs-on: ubuntu-latest steps: # Use the @nf-core-bot token to check out so we can push later - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 with: token: ${{ secrets.nf_core_bot_auth_token }} @@ -31,18 +31,22 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.nf_core_bot_auth_token }} - - name: Install Nextflow - uses: nf-core/setup-nextflow@b4ec1bc7c16a94435159de94a05253542fddf6ef # v3 + # 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 - # Install and run prek - - name: Run prek - id: prek - uses: j178/prek-action@6ad80277337ad479fe43bd70701c3f7f8aa74db3 # v2 + - name: Run pre-commit + id: pre-commit + run: pre-commit run --all-files continue-on-error: true # indication that the linting has finished - name: react if linting finished succesfully - if: steps.prek.outcome == 'success' + if: steps.pre-commit.outcome == 'success' uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5 with: comment-id: ${{ github.event.comment.id }} @@ -50,7 +54,7 @@ jobs: - name: Commit & push changes id: commit-and-push - if: steps.prek.outcome == 'failure' + if: steps.pre-commit.outcome == 'failure' run: | git config user.email "core@nf-co.re" git config user.name "nf-core-bot" @@ -82,4 +86,4 @@ jobs: issue-number: ${{ github.event.issue.number }} body: | @${{ github.actor }} I tried to fix the linting errors, but it didn't work. Please fix them manually. - See [CI log](https://github.com/nf-core/spatialxe/actions/runs/${{ github.run_id }}) for more details. + See [CI log](https://github.com/nf-core/spatialaxe/actions/runs/${{ github.run_id }}) for more details. diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 8738ffc9..7a527a34 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -11,31 +11,33 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 - - name: Install Nextflow - uses: nf-core/setup-nextflow@b4ec1bc7c16a94435159de94a05253542fddf6ef # v3 + - name: Set up Python 3.14 + uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 + with: + python-version: "3.14" - - name: Run prek - uses: j178/prek-action@6ad80277337ad479fe43bd70701c3f7f8aa74db3 # v2 + - name: Install pre-commit + run: pip install pre-commit + + - name: Run pre-commit + run: pre-commit run --all-files nf-core: runs-on: ubuntu-latest steps: - name: Check out pipeline code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 - name: Install Nextflow - uses: nf-core/setup-nextflow@b4ec1bc7c16a94435159de94a05253542fddf6ef # v3 + uses: nf-core/setup-nextflow@v2 - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 + - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # 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 @@ -43,10 +45,12 @@ jobs: config: ${{ github.workspace }}/.nf-core.yml - name: Install dependencies - run: uv tool install nf-core==${{ steps.read_yml.outputs['nf_core_version'] }} + run: | + python -m pip install --upgrade pip + pip install nf-core==${{ steps.read_yml.outputs['nf_core_version'] }} - name: Run nf-core pipelines lint - if: ${{ github.base_ref != 'master' || github.base_ref != 'main' }} + if: ${{ github.base_ref != 'master' }} env: GITHUB_COMMENTS_URL: ${{ github.event.pull_request.comments_url }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -54,7 +58,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' || github.base_ref == 'main' }} + if: ${{ github.base_ref == 'master' }} env: GITHUB_COMMENTS_URL: ${{ github.event.pull_request.comments_url }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -67,7 +71,7 @@ jobs: - name: Upload linting log file artifact if: ${{ always() }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: linting-logs path: | diff --git a/.github/workflows/linting_comment.yml b/.github/workflows/linting_comment.yml index 5b0c24f7..e6e9bc26 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@b6e2e70617bc3265edd6dab6c906732b2f1ae151 # v21 + uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 # v11 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@70d2764d1a7d5d9560b100cbea0077fc8f633987 # v3 + uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2 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 efd72d65..410a24c3 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.4" + NFT_VER: "0.9.5" 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 with: fetch-depth: 0 @@ -50,7 +50,7 @@ jobs: env: NFT_VER: ${{ env.NFT_VER }} with: - max_shards: 7 + max_shards: 12 - name: debug run: | @@ -64,6 +64,7 @@ jobs: runs-on: # use self-hosted runners - runs-on=${{ github.run_id }}-nf-test - runner=4cpu-linux-x64 + - volume=80gb strategy: fail-fast: false matrix: @@ -71,21 +72,23 @@ jobs: profile: [conda, docker, singularity] isMain: - ${{ github.base_ref == 'master' || github.base_ref == 'main' }} - # Exclude conda and singularity on dev + # Exclude conda and singularity on dev; conda disabled on all branches exclude: - isMain: false profile: "conda" - isMain: false profile: "singularity" + - isMain: true + profile: "conda" NXF_VER: - - "25.10.4" + - "25.04.0" - "latest-everything" env: NXF_ANSI_LOG: false TOTAL_SHARDS: ${{ needs.nf-test-changes.outputs.total_shards }} steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 with: fetch-depth: 0 diff --git a/.github/workflows/release-announcements.yml b/.github/workflows/release-announcements.yml index 78d5dbe0..431d3d44 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@563159eb8d45f70ab6aaba36ed55cd037e51f441 # master + - uses: rzr/fediverse-action@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@5a91cc2ad10a304a4e96c16182dbe4918710bcf6 # v0.4.0 + - uses: zentered/bluesky-post-action@6461056ea355ea43b977e149f7bf76aaa572e5e8 # v0.3.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 ea30827e..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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 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@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3 + uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2 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 [Synchronisation documentation](https://nf-co.re/docs/developing/template-syncs/overview). + > 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). # diff --git a/.gitignore b/.gitignore index cc2b1a77..2ef7dde1 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ testing/ testing* *.pyc null/ -.lineage/ +.nf-test/ +.nf-test.log +.nf-test-* diff --git a/.nf-core.yml b/.nf-core.yml index ae563bd5..1e213b56 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -1,21 +1,24 @@ lint: + actions_ci: false files_exist: - .github/workflows/awsfulltest.yml - .github/workflows/awstest.yml files_unchanged: - .gitignore - - assets/nf-core-spatialxe_logo_light.png - - docs/images/nf-core-spatialxe_logo_dark.png - - docs/images/nf-core-spatialxe_logo_light.png + - assets/nf-core-spatialaxe_logo_light.png + - docs/images/nf-core-spatialaxe_logo_dark.png + - docs/images/nf-core-spatialaxe_logo_light.png - .github/PULL_REQUEST_TEMPLATE.md -nf_core_version: 4.0.2 +nf_core_version: 3.5.2 repository_type: pipeline template: - author: Sameesh Kher, Florian Heyl + author: Sameesh Kher, Dongze He, Florian Heyl description: A pipeline for spatialomics Xenium In Situ data. force: false is_nfcore: true - name: spatialxe + name: spatialaxe org: nf-core outdir: . - version: 1.0.0 + skip_features: + - igenomes + version: 1.1.0dev diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f51e1a28..d06777a8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: hooks: - id: prettier additional_dependencies: - - prettier@3.8.3 + - prettier@3.6.2 - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: @@ -13,21 +13,15 @@ repos: exclude: | (?x)^( .*ro-crate-metadata.json$| - modules/(?!local/).*| - subworkflows/(?!local/).*| + modules/nf-core/.*| + subworkflows/nf-core/.*| .*\.snap$ )$ - id: end-of-file-fixer exclude: | (?x)^( .*ro-crate-metadata.json$| - modules/(?!local/).*| - subworkflows/(?!local/).*| + modules/nf-core/.*| + subworkflows/nf-core/.*| .*\.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 63cde500..2255e3e3 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,6 @@ email_template.html +adaptivecard.json +slackreport.json .nextflow* work/ data/ @@ -10,5 +12,3 @@ testing* bin/ .nf-test/ ro-crate-metadata.json -modules/nf-core/ -subworkflows/nf-core/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f0d1258..afee0d0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,23 @@ -# nf-core/spatialxe: Changelog +# nf-core/spatialaxe: Changelog 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.0.0 - [date] +## 1.0.0dev - [date] -Initial release of nf-core/spatialxe, created with the [nf-core](https://nf-co.re/) template. +Initial release of nf-core/spatialaxe, created with the [nf-core](https://nf-co.re/) template. + +### `Added` + +### `Fixed` + +### `Dependencies` + +### `Deprecated` + +## 1.0.0 - [18.06.2026] + +Initial release of nf-core/spatialaxe, created with the [nf-core](https://nf-co.re/) template. ### `Added` diff --git a/CITATIONS.md b/CITATIONS.md index e8c14658..1457e968 100644 --- a/CITATIONS.md +++ b/CITATIONS.md @@ -1,4 +1,4 @@ -# nf-core/spatialxe: Citations +# nf-core/spatialaxe: Citations ## [nf-core](https://pubmed.ncbi.nlm.nih.gov/32055031/) @@ -10,10 +10,6 @@ ## 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/LICENSE b/LICENSE index df908d32..cf21b558 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) The nf-core/spatialxe team +Copyright (c) The nf-core/spatialaxe team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index f3f2cbde..3ea92e69 100644 --- a/README.md +++ b/README.md @@ -1,101 +1,187 @@

- - nf-core/spatialxe + + nf-core/spatialaxe

-[![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) +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new/nf-core/spatialaxe) +[![GitHub Actions CI Status](https://github.com/nf-core/spatialaxe/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/spatialaxe/actions/workflows/nf-test.yml) +[![GitHub Actions Linting Status](https://github.com/nf-core/spatialaxe/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/spatialaxe/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/spatialaxe/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.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/) +[![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) [![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) +[![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/spatialaxe) -[![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) +[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23spatialaxe-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/spatialaxe)[![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) ## Introduction -**nf-core/spatialxe** is a bioinformatics pipeline that ... +**nf-core/spatialaxe** is a bioinformatics best-practice processing and quality control pipeline for Xenium (and soon Atera) 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/spatialaxe-metromap](docs/images/spatialaxe-metromap.png) - -1. Read QC ([`FastQC`](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/))2. Present QC for raw reads ([`MultiQC`](http://multiqc.info/)) +> [!NOTE] +> We are currently extending 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) ## Usage -> [!NOTE] -> 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. +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/spatialaxe/results). - +### Run image-based segmentation mode
-Now, you can run the pipeline using: +`CELLPOSE -> BAYSOR -> XR-IMPORT_SEGMENTATION -> SPATIALDATA -> QC` - +```bash +nextflow run nf-core/spatialaxe \ + -profile \ + --input samplesheet.csv \ + --outdir \ + --mode +``` + +### Run coordinate-based segmentation mode
+ +`PROSEG -> PROSEG2BAYSOR -> XR-IMPORT_SEGMENTATION -> SPATIALDATA -> QC` ```bash -nextflow run nf-core/spatialxe \ +nextflow run nf-core/spatialaxe \ -profile \ --input samplesheet.csv \ - --outdir + --outdir \ + --mode coordinate ``` +### Run segfree mode
+ +`BAYSOR_SEGFREE` + +```bash +nextflow run nf-core/spatialaxe \ + -profile \ + --input samplesheet.csv \ + --outdir \ + --mode segfree +``` + +### Run preview mode
+ +`BAYSOR_PREVIEW` + +```bash +nextflow run nf-core/spatialaxe \ + -profile \ + --input samplesheet.csv \ + --outdir \ + --mode preview +``` + +### Run just the quality control
+ +```bash +nextflow run nf-core/spatialaxe \ + -profile \ + --input samplesheet.csv \ + --outdir \ + --mode qc +``` + +### 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/running/run-pipelines#using-parameter-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/usage/getting_started/configuration#custom-configuration-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). +For more details and further functionality, please refer to the [usage documentation](https://nf-co.re/spatialaxe/usage) and the [parameter documentation](https://nf-co.re/spatialaxe/parameters). ## Pipeline output -To 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. +To see the results of an example test run with a full size dataset refer to the [results](https://nf-co.re/spatialaxe/results) tab on the nf-core website pipeline page. For more details about the output files and reports, please refer to the -[output documentation](https://nf-co.re/spatialxe/output). +[output documentation](https://nf-co.re/spatialaxe/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 was originally written by Sameesh Kher, Florian Heyl. +nf-core/spatialaxe is mainly developed by [Sameesh Kher](https://github.com/khersameesh24), [Dongze He](https://github.com/dongzehe), and [Florian Heyl](https://github.com/heylf). 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](docs/CONTRIBUTING.md). +If you would like to contribute to this pipeline, please see the [contributing guidelines](.github/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)). +For further information or help, don't hesitate to get in touch on the [Slack `#spatialaxe` channel](https://nfcore.slack.com/channels/spatialaxe) (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. diff --git a/assets/adaptivecard.json b/assets/adaptivecard.json new file mode 100644 index 00000000..1fbf3f15 --- /dev/null +++ b/assets/adaptivecard.json @@ -0,0 +1,67 @@ +{ + "type": "message", + "attachments": [ + { + "contentType": "application/vnd.microsoft.card.adaptive", + "contentUrl": null, + "content": { + "\$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "msteams": { + "width": "Full" + }, + "type": "AdaptiveCard", + "version": "1.2", + "body": [ + { + "type": "TextBlock", + "size": "Large", + "weight": "Bolder", + "color": "<% if (success) { %>Good<% } else { %>Attention<%} %>", + "text": "nf-core/spatialaxe v${version} - ${runName}", + "wrap": true + }, + { + "type": "TextBlock", + "spacing": "None", + "text": "Completed at ${dateComplete} (duration: ${duration})", + "isSubtle": true, + "wrap": true + }, + { + "type": "TextBlock", + "text": "<% if (success) { %>Pipeline completed successfully!<% } else { %>Pipeline completed with errors. The full error message was: ${errorReport}.<% } %>", + "wrap": true + }, + { + "type": "TextBlock", + "text": "The command used to launch the workflow was as follows:", + "wrap": true + }, + { + "type": "TextBlock", + "text": "${commandLine}", + "isSubtle": true, + "wrap": true + } + ], + "actions": [ + { + "type": "Action.ShowCard", + "title": "Pipeline Configuration", + "card": { + "type": "AdaptiveCard", + "\$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "body": [ + { + "type": "FactSet", + "facts": [<% out << summary.collect{ k,v -> "{\"title\": \"$k\", \"value\" : \"$v\"}"}.join(",\n") %> + ] + } + ] + } + } + ] + } + } + ] +} diff --git a/assets/config/xenium.toml b/assets/config/xenium.toml new file mode 100644 index 00000000..c7740146 --- /dev/null +++ b/assets/config/xenium.toml @@ -0,0 +1,15 @@ +[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 819a2f90..636a526a 100644 --- a/assets/email_template.html +++ b/assets/email_template.html @@ -4,21 +4,21 @@ - - nf-core/spatialxe Pipeline Report + + nf-core/spatialaxe Pipeline Report
-

nf-core/spatialxe ${version}

+

nf-core/spatialaxe ${version}

Run Name: $runName

<% if (!success){ out << """
-

nf-core/spatialxe execution completed unsuccessfully!

+

nf-core/spatialaxe execution completed unsuccessfully!

The exit status of the task that caused the workflow execution to fail was: $exitStatus.

The full error message was:

${errorReport}
@@ -27,7 +27,7 @@

nf-core/spatialxe execution completed } else { out << """
- nf-core/spatialxe execution completed successfully! + nf-core/spatialaxe execution completed successfully!
""" } @@ -44,8 +44,8 @@

Pipeline Configuration:

-

nf-core/spatialxe

-

https://github.com/nf-core/spatialxe

+

nf-core/spatialaxe

+

https://github.com/nf-core/spatialaxe

diff --git a/assets/email_template.txt b/assets/email_template.txt index f92d5849..9447a493 100644 --- a/assets/email_template.txt +++ b/assets/email_template.txt @@ -4,15 +4,15 @@ |\\ | |__ __ / ` / \\ |__) |__ } { | \\| | \\__, \\__/ | \\ |___ \\`-._,-`-, `._,._,' - nf-core/spatialxe ${version} + nf-core/spatialaxe ${version} ---------------------------------------------------- Run Name: $runName <% if (success){ - out << "## nf-core/spatialxe execution completed successfully! ##" + out << "## nf-core/spatialaxe execution completed successfully! ##" } else { out << """#################################################### -## nf-core/spatialxe execution completed unsuccessfully! ## +## nf-core/spatialaxe execution completed unsuccessfully! ## #################################################### The exit status of the task that caused the workflow execution to fail was: $exitStatus. The full error message was: @@ -35,5 +35,5 @@ Pipeline Configuration: <% out << summary.collect{ k,v -> " - $k: $v" }.join("\n") %> -- -nf-core/spatialxe -https://github.com/nf-core/spatialxe +nf-core/spatialaxe +https://github.com/nf-core/spatialaxe diff --git a/assets/example_samplesheet.csv b/assets/example_samplesheet.csv new file mode 100644 index 00000000..9cc36cf4 --- /dev/null +++ b/assets/example_samplesheet.csv @@ -0,0 +1,2 @@ +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 1ac4277b..abfb98ea 100644 --- a/assets/methods_description_template.yml +++ b/assets/methods_description_template.yml @@ -1,13 +1,12 @@ -id: "nf-core-spatialxe-methods-description" +id: "nf-core-spatialaxe-methods-description" description: "Suggested text and references to use when describing pipeline usage within the methods section of a publication." -section_name: "nf-core/spatialxe Methods Description" -section_href: "https://github.com/nf-core/spatialxe" +section_name: "nf-core/spatialaxe Methods Description" +section_href: "https://github.com/nf-core/spatialaxe" 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

-

Data was processed using nf-core/spatialxe v${workflow.manifest.version} ${doi_text} of the nf-core collection of workflows (Ewels et al., 2020), utilising reproducible software environments from the Bioconda (Grüning et al., 2018) and Biocontainers (da Veiga Leprevost et al., 2017) projects.

+

Data was processed using nf-core/spatialaxe v${workflow.manifest.version} ${doi_text} of the nf-core collection of workflows (Ewels et al., 2020), utilising reproducible software environments from the Bioconda (Grüning et al., 2018) and Biocontainers (da Veiga Leprevost et al., 2017) projects.

The pipeline was executed with Nextflow v${workflow.nextflow.version} (Di Tommaso et al., 2017) with the following command:

${workflow.commandLine}

${tool_citations}

diff --git a/assets/multiqc_config.yml b/assets/multiqc_config.yml index a11d3d7f..47566390 100644 --- a/assets/multiqc_config.yml +++ b/assets/multiqc_config.yml @@ -1,15 +1,43 @@ 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/spatialaxe analysis pipeline. For information about how to interpret these results, please see the documentation. report_section_order: - "nf-core-spatialxe-methods-description": + "nf-core-spatialaxe-methods-description": order: -1000 software_versions: order: -1001 - "nf-core-spatialxe-summary": + "nf-core-spatialaxe-summary": order: -1002 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-spatialaxe_logo_light.png b/assets/nf-core-spatialaxe_logo_light.png new file mode 100644 index 0000000000000000000000000000000000000000..c7a25c982ad5a51c810d282d9b6a9788511000f9 GIT binary patch literal 79633 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 R0{e}~8;1WBUbB1re*i^tg%tn* literal 0 HcmV?d00001 diff --git a/assets/nf-core-spatialxe_logo_light.png b/assets/nf-core-spatialxe_logo_light.png deleted file mode 100644 index 8a8ca75bbb8c67fe1521e272cbd6ab71fce03182..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 80727 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 Zr0LVfWv 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
gijG40X;L3Fo^?@ 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 diff --git a/assets/samplesheet.csv b/assets/samplesheet.csv index 5f653ab7..72ad34f0 100644 --- a/assets/samplesheet.csv +++ b/assets/samplesheet.csv @@ -1,3 +1,2 @@ -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, +sample,bundle,image +test_run,https://raw.githubusercontent.com/nf-core/test-datasets/spatialaxe/xenium_bundle.tar.gz, diff --git a/assets/schema_input.json b/assets/schema_input.json index 7435ad64..c6cda688 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://raw.githubusercontent.com/nf-core/spatialxe/master/assets/schema_input.json", - "title": "nf-core/spatialxe pipeline - params.input schema", + "$id": "https://raw.githubusercontent.com/nf-core/spatialaxe/master/assets/schema_input.json", + "title": "nf-core/spatialaxe pipeline - params.input schema", "description": "Schema for the file provided with params.input", "type": "array", "items": { @@ -13,21 +13,17 @@ "errorMessage": "Sample name must be provided and cannot contain spaces", "meta": ["id"] }, - "fastq_1": { + "bundle": { "type": "string", - "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'" + "pattern": "^\\S+$", + "errorMessage": "Please provide a bundle as input data" }, - "fastq_2": { + "image": { "type": "string", - "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'" + "pattern": "^\\S+$", + "errorMessage": "You can provide an image. If you do not then please leave the field empty." } }, - "required": ["sample", "fastq_1"] + "required": ["sample", "bundle"] } } diff --git a/assets/sendmail_template.txt b/assets/sendmail_template.txt index cd864042..dd49eb59 100644 --- a/assets/sendmail_template.txt +++ b/assets/sendmail_template.txt @@ -9,12 +9,12 @@ Content-Type: text/html; charset=utf-8 $email_html --nfcoremimeboundary -Content-Type: image/png;name="nf-core-spatialxe_logo.png" +Content-Type: image/png;name="nf-core-spatialaxe_logo.png" Content-Transfer-Encoding: base64 Content-ID: -Content-Disposition: inline; filename="nf-core-spatialxe_logo_light.png" +Content-Disposition: inline; filename="nf-core-spatialaxe_logo_light.png" -<% out << new File("$projectDir/assets/nf-core-spatialxe_logo_light.png"). +<% out << new File("$projectDir/assets/nf-core-spatialaxe_logo_light.png"). bytes. encodeBase64(). toString(). diff --git a/assets/slackreport.json b/assets/slackreport.json new file mode 100644 index 00000000..a2991f65 --- /dev/null +++ b/assets/slackreport.json @@ -0,0 +1,34 @@ +{ + "attachments": [ + { + "fallback": "Plain-text summary of the attachment.", + "color": "<% if (success) { %>good<% } else { %>danger<%} %>", + "author_name": "nf-core/spatialaxe ${version} - ${runName}", + "author_icon": "https://www.nextflow.io/docs/latest/_static/favicon.ico", + "text": "<% if (success) { %>Pipeline completed successfully!<% } else { %>Pipeline completed with errors<% } %>", + "fields": [ + { + "title": "Command used to launch the workflow", + "value": "```${commandLine}```", + "short": false + } + <% + if (!success) { %> + , + { + "title": "Full error message", + "value": "```${errorReport}```", + "short": false + }, + { + "title": "Pipeline configuration", + "value": "<% out << summary.collect{ k,v -> k == "hook_url" ? "_${k}_: (_hidden_)" : ( ( v.class.toString().contains('Path') || ( v.class.toString().contains('String') && v.contains('/') ) ) ? "_${k}_: `${v}`" : (v.class.toString().contains('DateTime') ? ("_${k}_: " + v.format(java.time.format.DateTimeFormatter.ofLocalizedDateTime(java.time.format.FormatStyle.MEDIUM))) : "_${k}_: ${v}") ) }.join(",\n") %>", + "short": false + } + <% } + %> + ], + "footer": "Completed at <% out << dateComplete.format(java.time.format.DateTimeFormatter.ofLocalizedDateTime(java.time.format.FormatStyle.MEDIUM)) %> (duration: ${duration})" + } + ] +} diff --git a/bin/baysor_create_dataset.py b/bin/baysor_create_dataset.py new file mode 100755 index 00000000..4e5a263a --- /dev/null +++ b/bin/baysor_create_dataset.py @@ -0,0 +1,96 @@ +#!/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 new file mode 100755 index 00000000..2662f83c --- /dev/null +++ b/bin/baysor_preprocess_transcripts.py @@ -0,0 +1,126 @@ +#!/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 new file mode 100755 index 00000000..133fcede --- /dev/null +++ b/bin/divide_transcripts.py @@ -0,0 +1,1312 @@ +#!/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 new file mode 100755 index 00000000..2e0c687c --- /dev/null +++ b/bin/ficture_preprocess.py @@ -0,0 +1,101 @@ +#!/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 new file mode 100755 index 00000000..c031427d --- /dev/null +++ b/bin/segger_create_dataset.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python3 +""" +Run segger create_dataset with spatialaxe-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 new file mode 100755 index 00000000..b3e1f289 --- /dev/null +++ b/bin/segger_predict.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +""" +Run segger predict with spatialaxe-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 new file mode 100755 index 00000000..5359a7b3 --- /dev/null +++ b/bin/spatialdata_merge.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +"""Merge two spatialdata bundles to create a layered spatialdata object.""" + +import argparse +import json +import os +import shutil + + +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 new file mode 100755 index 00000000..20a9c0ef --- /dev/null +++ b/bin/spatialdata_meta.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +"""Add metadata to SpatialData bundle.""" + +import argparse +import json +import sys + +import pandas as pd +import spatialdata as sd + +# 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 new file mode 100755 index 00000000..3a4723e0 --- /dev/null +++ b/bin/spatialdata_write.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +"""Write spatialdata object from segmentation format.""" + +import argparse +import sys + +import pandas as pd +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 new file mode 100755 index 00000000..45f057e3 --- /dev/null +++ b/bin/stitch_transcripts.py @@ -0,0 +1,848 @@ +#!/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 new file mode 100755 index 00000000..955ad4b7 --- /dev/null +++ b/bin/utility_convert_mask_uint32.py @@ -0,0 +1,46 @@ +#!/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 new file mode 100755 index 00000000..8544ecf3 --- /dev/null +++ b/bin/utility_downscale_morphology.py @@ -0,0 +1,103 @@ +#!/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 new file mode 100755 index 00000000..3d60f563 --- /dev/null +++ b/bin/utility_extract_dapi.py @@ -0,0 +1,60 @@ +#!/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_preview_data.py b/bin/utility_extract_preview_data.py new file mode 100755 index 00000000..0ea737c2 --- /dev/null +++ b/bin/utility_extract_preview_data.py @@ -0,0 +1,208 @@ +#!/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

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 new file mode 100755 index 00000000..bfa19c40 --- /dev/null +++ b/bin/utility_parquet_to_csv.py @@ -0,0 +1,70 @@ +#!/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 new file mode 100755 index 00000000..6cca640d --- /dev/null +++ b/bin/utility_resize_tif.py @@ -0,0 +1,134 @@ +#!/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 new file mode 100755 index 00000000..22889e82 --- /dev/null +++ b/bin/utility_segger2xr.py @@ -0,0 +1,247 @@ +#!/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 new file mode 100755 index 00000000..275fbab1 --- /dev/null +++ b/bin/utility_split_transcripts.py @@ -0,0 +1,109 @@ +#!/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 new file mode 100755 index 00000000..6cc1694e --- /dev/null +++ b/bin/utility_upscale_mask.py @@ -0,0 +1,72 @@ +#!/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 new file mode 100755 index 00000000..7144b1ac --- /dev/null +++ b/bin/xenium_patch_stitch_postprocess.py @@ -0,0 +1,98 @@ +#!/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 new file mode 100755 index 00000000..d9fb8d41 --- /dev/null +++ b/bin/xenium_patch_stitch_transcripts.py @@ -0,0 +1,808 @@ +#!/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 187a154e..476c4dbe 100644 --- a/conf/base.config +++ b/conf/base.config @@ -1,6 +1,6 @@ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - nf-core/spatialxe Nextflow base config file + nf-core/spatialaxe Nextflow base config file ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A 'blank slate' config file, appropriate for general use on most high performance compute environments. Assumes that all software is installed and available on @@ -10,57 +10,94 @@ process { - // TODO nf-core: Check the defaults for all processes - cpus = { 1 * task.attempt } - memory = { 6.GB * task.attempt } - time = { 4.h * task.attempt } + cpus = { 1 * task.attempt } + memory = { 6.GB * task.attempt } + time = { 4.h * task.attempt } - errorStrategy = { task.exitStatus in ((130..145) + 104 + (175..177)) ? 'retry' : 'finish' } - maxRetries = 1 + // 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 maxErrors = '-1' - // 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 + // ========================================================================= + // Standard nf-core CPU labels + // ========================================================================= + 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 = { 36.GB * task.attempt } - time = { 8.h * task.attempt } + cpus = { 6 * task.attempt } + memory = { 42.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 } - withLabel: process_gpu { - ext.use_gpu = { workflow.profile.contains('gpu') } - accelerator = { workflow.profile.contains('gpu') ? 1 : null } + + // ========================================================================= + // 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 } } + } diff --git a/conf/containers_conda_lock_files_amd64.config b/conf/containers_conda_lock_files_amd64.config deleted file mode 100644 index d3ee1b4e..00000000 --- a/conf/containers_conda_lock_files_amd64.config +++ /dev/null @@ -1,2 +0,0 @@ -process { withName: 'FASTQC' { container = 'modules/nf-core/fastqc/.conda-lock/linux_amd64-bd-5cb1a2fa2f18c7c2_1.txt' } } -process { withName: 'MULTIQC' { container = 'modules/nf-core/multiqc/.conda-lock/linux_amd64-bd-db7c73dae76bc9e6_1.txt' } } diff --git a/conf/containers_conda_lock_files_arm64.config b/conf/containers_conda_lock_files_arm64.config deleted file mode 100644 index 2b90ac4f..00000000 --- a/conf/containers_conda_lock_files_arm64.config +++ /dev/null @@ -1,2 +0,0 @@ -process { withName: 'FASTQC' { container = 'modules/nf-core/fastqc/.conda-lock/linux_arm64-bd-e455e32f745abe68_1.txt' } } -process { withName: 'MULTIQC' { container = 'modules/nf-core/multiqc/.conda-lock/linux_arm64-bd-d167b8012595a136_1.txt' } } diff --git a/conf/containers_docker_amd64.config b/conf/containers_docker_amd64.config deleted file mode 100644 index 65f1814a..00000000 --- a/conf/containers_docker_amd64.config +++ /dev/null @@ -1,2 +0,0 @@ -process { withName: 'FASTQC' { container = 'community.wave.seqera.io/library/fastqc:0.12.1--5cb1a2fa2f18c7c2' } } -process { withName: 'MULTIQC' { container = 'community.wave.seqera.io/library/multiqc:1.34--db7c73dae76bc9e6' } } diff --git a/conf/containers_docker_arm64.config b/conf/containers_docker_arm64.config deleted file mode 100644 index 6c845ba4..00000000 --- a/conf/containers_docker_arm64.config +++ /dev/null @@ -1,2 +0,0 @@ -process { withName: 'FASTQC' { container = 'community.wave.seqera.io/library/fastqc:0.12.1--e455e32f745abe68' } } -process { withName: 'MULTIQC' { container = 'community.wave.seqera.io/library/multiqc:1.34--d167b8012595a136' } } diff --git a/conf/containers_singularity_https_amd64.config b/conf/containers_singularity_https_amd64.config deleted file mode 100644 index 838f2484..00000000 --- a/conf/containers_singularity_https_amd64.config +++ /dev/null @@ -1,2 +0,0 @@ -process { withName: 'FASTQC' { container = 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/f2/f20b021476d1d87658820f971ebecc1e8cdbde0f338eb0d9cea2b0a8fc54a54b/data' } } -process { withName: 'MULTIQC' { container = 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/1b/1bef8af6be88c5733461959c46ac8ef73d18f65277f62a1695d0e1633054f9c2/data' } } diff --git a/conf/containers_singularity_https_arm64.config b/conf/containers_singularity_https_arm64.config deleted file mode 100644 index 090173be..00000000 --- a/conf/containers_singularity_https_arm64.config +++ /dev/null @@ -1,2 +0,0 @@ -process { withName: 'FASTQC' { container = 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/46/46daf2dad0169afd2ae047c3e50ed3776259f664bf07e5e06b045dc23449e994/data' } } -process { withName: 'MULTIQC' { container = 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/9a/9a1fec9662a152683e6fcae440d0ce20920b3b89dc62d1e3a52e73f92eba0969/data' } } diff --git a/conf/containers_singularity_oras_amd64.config b/conf/containers_singularity_oras_amd64.config deleted file mode 100644 index 773f3698..00000000 --- a/conf/containers_singularity_oras_amd64.config +++ /dev/null @@ -1,2 +0,0 @@ -process { withName: 'FASTQC' { container = 'oras://community.wave.seqera.io/library/fastqc:0.12.1--5c4bd442468d75dd' } } -process { withName: 'MULTIQC' { container = 'oras://community.wave.seqera.io/library/multiqc:1.34--4fc8657c816047c0' } } diff --git a/conf/containers_singularity_oras_arm64.config b/conf/containers_singularity_oras_arm64.config deleted file mode 100644 index 798cc638..00000000 --- a/conf/containers_singularity_oras_arm64.config +++ /dev/null @@ -1,2 +0,0 @@ -process { withName: 'FASTQC' { container = 'oras://community.wave.seqera.io/library/fastqc:0.12.1--127a87fc06499035' } } -process { withName: 'MULTIQC' { container = 'oras://community.wave.seqera.io/library/multiqc:1.34--7fbd82d945c06726' } } diff --git a/conf/igenomes.config b/conf/igenomes.config deleted file mode 100644 index 3f114377..00000000 --- a/conf/igenomes.config +++ /dev/null @@ -1,440 +0,0 @@ -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 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 deleted file mode 100644 index b4034d82..00000000 --- a/conf/igenomes_ignored.config +++ /dev/null @@ -1,9 +0,0 @@ -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 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 d203d2b6..845f0df4 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -15,20 +15,370 @@ 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 }, ] - withName: FASTQC { - ext.args = '--quiet' + // ---------------------------- multiqc --------------------------------------------------- + + 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 }, + ] } - withName: 'MULTIQC' { - ext.args = { params.multiqc_title ? "--title \"$params.multiqc_title\"" : '' } + + // ---------------------------- 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}/multiqc" }, + 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, + ] + ext.args = {[ + // Disable boundary/interior stain when the param is falsy; keep tool default when truthy. + !params.boundary_stain ? "--boundary-stain=disable" : "", + !params.interior_stain ? "--interior-stain=disable" : "", + params.expansion_distance != null ? "--expansion-distance=${params.expansion_distance}" : "", + params.dapi_filter != null ? "--dapi-filter=${params.dapi_filter}" : "", + ].join(' ').trim()} + } + + withName: XENIUMRANGER_IMPORT_SEGMENTATION { + publishDir = [ + path: "${params.outdir}/${params.mode}/xeniumranger/import_segementation", + mode: params.publish_dir_mode, + ] + ext.args = {[ + params.expansion_distance != null ? "--expansion-distance=${params.expansion_distance}" : "", + ].join(' ').trim()} + } + + // ---------------------------- proseg --------------------------------------------------- + + withName: PROSEG { + publishDir = [ + path: "${params.outdir}/${params.mode}/proseg/preset", + mode: params.publish_dir_mode, + ] + ext.args = {[ + params.format != null ? "--${params.format}" : "", + ].join(' ').trim()} + } + + 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: BAYSOR_PREVIEW { + memory = { 240.GB * task.attempt } + 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" }, + 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, + ] + ext.args = {[ + params.format != null ? "--sample-type ${params.format}" : "", + params.tile_width != null ? "--tile-width ${params.tile_width}" : "", + params.tile_height != null ? "--tile-height ${params.tile_height}" : "", + ].join(' ').trim()} + } + + 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", + params.batch_size_train != null ? "--batch_size ${params.batch_size_train}" : "", + params.max_epochs != null ? "--max_epochs ${params.max_epochs}" : "", + params.segger_num_workers != null ? "--num_workers ${params.segger_num_workers}" : "", + ].join(' ').trim()} + } + + 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 }, + ] + ext.args = {[ + params.batch_size_predict != null ? "--batch-size ${params.batch_size_predict}" : "", + params.cc_analysis != null ? "--cc-analysis ${params.cc_analysis}" : "", + params.segger_knn_method != null ? "--knn-method ${params.segger_knn_method}" : "", + ].join(' ').trim()} + } + + // ---------------------------- ficture ------------------------------------------ + + withName: FICTURE_PREPROCESS { + publishDir = [ + path: "${params.outdir}/${params.mode}/ficture/preprocess", + mode: params.publish_dir_mode, + ] + ext.args = {"--negative-control-regex '${params.negative_control_regex}'"} + } + + // ---------------------------- 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, + ] + ext.args = { params.format ? "--format ${params.format}" : '' } + } + + 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 bfcc3864..e317612d 100644 --- a/conf/test.config +++ b/conf/test.config @@ -5,17 +5,23 @@ Defines input files and everything required to run a fast and simple pipeline test. Use as follows: - nextflow run nf-core/spatialxe -profile test, --outdir + nextflow run nf-core/spatialaxe -profile test, --mode --outdir ---------------------------------------------------------------------------------------- */ process { + resourceLimits = [ cpus: 4, - memory: '15.GB', - time: '1.h' + memory: '8.GB', + time: '2.h', ] + + withName: UNTAR { + ext.prefix = "test_run" + } + } params { @@ -23,8 +29,7 @@ params { config_profile_description = 'Minimal test dataset to check pipeline function' // Input data - // 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' + input = "${projectDir}/assets/samplesheet.csv" + outdir = 'results' + mode = 'coordinate' } diff --git a/conf/test_coordinate_mode.config b/conf/test_coordinate_mode.config new file mode 100644 index 00000000..cccdae57 --- /dev/null +++ b/conf/test_coordinate_mode.config @@ -0,0 +1,31 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 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/spatialaxe -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 4ddba9a2..68ce4186 100644 --- a/conf/test_full.config +++ b/conf/test_full.config @@ -5,7 +5,7 @@ Defines input files and everything required to run a full size pipeline test. Use as follows: - nextflow run nf-core/spatialxe -profile test_full, --outdir + nextflow run nf-core/spatialaxe -profile test_full, --outdir ---------------------------------------------------------------------------------------- */ @@ -14,11 +14,8 @@ params { config_profile_name = 'Full test profile' config_profile_description = 'Full test dataset to check pipeline function' - // 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' + // Input data + input = "${projectDir}/assets/samplesheet.csv" + outdir = 'results' + mode = 'coordinate' } diff --git a/conf/test_image_mode.config b/conf/test_image_mode.config new file mode 100644 index 00000000..ff9199d1 --- /dev/null +++ b/conf/test_image_mode.config @@ -0,0 +1,31 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 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/spatialaxe -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 new file mode 100644 index 00000000..5f312892 --- /dev/null +++ b/conf/test_preview_mode.config @@ -0,0 +1,31 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 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/spatialaxe -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 new file mode 100644 index 00000000..4576929d --- /dev/null +++ b/conf/test_segfree_mode.config @@ -0,0 +1,31 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 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/spatialaxe -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/CONTRIBUTING.md b/docs/CONTRIBUTING.md deleted file mode 100644 index f87187c8..00000000 --- a/docs/CONTRIBUTING.md +++ /dev/null @@ -1,185 +0,0 @@ ---- -title: Contributing -markdownPlugin: checklist ---- - -# `nf-core/spatialxe`: Contributing guidelines - -Hi there! -Thanks for taking an interest in improving nf-core/spatialxe. - -This page describes the recommended nf-core way to contribute to both nf-core/spatialxe and nf-core pipelines in general, including: - -- [General contribution guidelines](#general-contribution-guidelines): common procedures or guides across all nf-core pipelines. -- [Pipeline-specific contribution guidelines](#pipeline-specific-contribution-guidelines): procedures or guides specific to the development conventions of nf-core/spatialxe. - -> [!NOTE] -> If you need help using or modifying nf-core/spatialxe, ask on the nf-core Slack [#spatialxe](https://nfcore.slack.com/channels/spatialxe) channel ([join our Slack here](https://nf-co.re/join/slack)). - -## General contribution guidelines - -### Contribution quick start - -To contribute code to any nf-core pipeline: - -- [ ] Ensure you have Nextflow, nf-core tools, and nf-test installed. See the [nf-core/tools repository](https://github.com/nf-core/tools) for instructions. -- [ ] Check whether a GitHub [issue](https://github.com/nf-core/spatialxe/issues) about your idea already exists. If an issue does not exist, create one so that others are aware you are working on it. -- [ ] [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. -- [ ] Create a branch on your forked repository and make your changes following [pipeline conventions](#pipeline-contribution-conventions) (if applicable). -- [ ] To fix major bugs, name your branch `patch` and follow the [patch release](#patch-release) process. -- [ ] Update relevant documentation within the `docs/` folder, use nf-core/tools to update `nextflow_schema.json`, and update `CITATIONS.md`. -- [ ] Run and/or update tests. See [Testing](#testing) for more information. -- [ ] [Lint](#lint-tests) your code with nf-core/tools. -- [ ] Submit a pull request (PR) against the `dev` branch and request a review. - -If you are not used to this workflow with Git, see the [GitHub documentation](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests) or [Git resources](https://try.github.io/) for more information. - -## Use of AI and LLMs - -The nf-core stance on the use of AI and LLMs is that humans are still ultimately responsible for their submitted code, regardless of the tools they use. - -If you’re using AI tools, try to stick by these guidelines: - -- Keep PRs as small and focussed as possible -- Avoid any unnecessary changes, such as moving or refactoring code (unless that is the explicit intention of the PR) -- Review all generated code yourself before opening a PR, and ensure that you understand it -- Engage with the community review process and expect to make revisions - -For more detail, see the the [blog post](https://nf-co.re/blog/2026/statement-on-ai) for a statement from the nf-core/core team. - -### Getting help - -For further information and help, see the [nf-core/spatialxe documentation](https://nf-co.re/spatialxe/usage) or ask on the nf-core [#spatialxe](https://nfcore.slack.com/channels/spatialxe) Slack channel ([join our Slack here](https://nf-co.re/join/slack)). - -### GitHub Codespaces - -You can contribute to nf-core/spatialxe without installing a local development environment on your machine by using [GitHub Codespaces](https://github.com/codespaces). - -[GitHub Codespaces](https://github.com/codespaces) is an online developer environment that runs in your browser, complete with VS Code and a terminal. -Most nf-core repositories include a devcontainer configuration, which creates a GitHub Codespaces environment specifically for Nextflow development. -The environment includes pre-installed nf-core tools, Nextflow, and a few other helpful utilities via a Docker container. - -To get started, open the repository in [Codespaces](https://github.com/nf-core/spatialxe/codespaces). - -### Testing - -Once you have made your changes, run the pipeline with nf-test to test them locally. -For additional information, use the `--verbose` flag to view the Nextflow console log output. - -```bash -nf-test test --tag test --profile +docker --verbose -``` - -If you have added new functionality, ensure you update the test assertions in the `.nf.test` files in the `tests/` directory. -Update the snapshots with the following command: - -```bash -nf-test test --tag test --profile +docker --verbose --update-snapshots -``` - -When you create a pull request with changes, GitHub Actions will run automatic tests. -Pull requests are typically reviewed when these tests are passing. - -Two types of tests are typically run: - -#### Lint tests - -nf-core has a [set of guidelines](https://nf-co.re/docs/specifications/overview) which all pipelines must follow. -To enforce these, run linting with nf-core/tools: - -```bash -nf-core pipelines lint -``` - -If you encounter failures or warnings, follow the linked documentation printed to screen. -For more information about linting tests, see [nf-core/tools API documentation](https://nf-co.re/docs/nf-core-tools/api_reference/latest/pipeline_lint_tests/actions_awsfulltest). - -#### Pipeline tests - -Each nf-core pipeline should be set up with a minimal set of test data. -GitHub Actions runs the pipeline on this data to ensure it runs through and exits successfully. -If there are any failures then the automated tests fail. -These tests are run with the latest available version of Nextflow and the minimum required version specified in the pipeline code. - -### Patch release - -> [!WARNING] -> Only in the unlikely event of a release that contains a critical bug. - -- [ ] Create a new branch `patch` on your fork based on `upstream/main` or `upstream/master`. -- [ ] Fix the bug and use nf-core/tools to bump the version to the next semantic version, for example, `1.2.3` → `1.2.4`. -- [ ] Open a Pull Request from `patch` directly to `main`/`master` with the changes. - -### Pipeline contribution conventions - -nf-core semi-standardises how you write code and other contributions to make the nf-core/spatialxe code and processing logic more understandable for new contributors and to ensure quality. - -#### Add a new pipeline step - -To contribute a new step to the pipeline, follow the general nf-core coding procedure. -Please also refer to the [pipeline-specific contribution guidelines](#pipeline-specific-contribution-guidelines): - -- [ ] Define the corresponding [input channel](#channel-naming-schemes) into your new process from the expected previous process channel. -- [ ] Install a module with nf-core/tools, or write a local module (see [default processes resource requirements](#default-processes-resource-requirements)), and add it to the target `.nf`. -- [ ] Define the output channel if needed. Mix the version output channel into `ch_versions` and relevant files into `ch_multiqc`. -- [ ] Add new or updated parameters to `nextflow.config` with a [default value](#default-parameter-values). -- [ ] Add new or updated parameters and relevant help text to `nextflow_schema.json` with [nf-core/tools](#default-parameter-values). -- [ ] Add validation for relevant parameters to the pipeline utilisation section of `utils_nfcore_\_pipeline/main.nf` subworkflow. -- [ ] Perform local tests to validate that the new code works as expected. - - [ ] If applicable, add a new test in the `tests` directory. -- [ ] Update `usage.md`, `output.md`, and `citation.md` as appropriate. -- [ ] [Lint](lint) the code with nf-core/tools. -- [ ] Update any diagrams or pipeline images as necessary. -- [ ] Update MultiQC config `assets/multiqc_config.yml` so relevant suffixes, file name cleanup, and module plots are in the appropriate order. -- [ ] If applicable, create a [MultiQC](https://seqera.io/multiqc/) module. -- [ ] Add a description of the output files and, if relevant, images from the MultiQC report to `docs/output.md`. - -To update the minimum required Nextflow version, see the [Nextflow version bumping](#nextflow-version-bumping) section below. For more information about pipeline contributions, see [pipeline-specific contribution guidelines](#pipeline-specific-contribution-guidelines). - -#### Channel naming schemes - -Use the following naming schemes for channels to make the channel flow easier to understand: - -- Initial process channel: `ch_output_from_` -- Intermediate and terminal channels: `ch__for_` - -#### Default parameter values - -Parameters should be initialised and defined with default values within the `params` scope in `nextflow.config`. -They should also be documented in the pipeline JSON schema. - -To update `nextflow_schema.json`, run: - -```bash -nf-core pipelines schema build -``` - -The schema builder interface that loads in your browser should automatically update the defaults in the parameter documentation. - -#### Default processes resource requirements - -If you write a local module, specify a default set of resource requirements for the process. - -Sensible defaults for process resource requirements (CPUs, memory, time) should be defined in `conf/base.config`. -Specify these with generic `withLabel:` selectors, so they can be shared across multiple processes and steps of the pipeline. - -nf-core provides a set of standard labels that you should follow where possible, as seen in the [nf-core pipeline template](https://github.com/nf-core/tools/blob/main/nf_core/pipeline-template/conf/base.config). -These labels define resource defaults for single-core processes, modules that require a GPU, and different levels of multi-core configurations with increasing memory requirements. - -Values assigned within these labels can be dynamically passed to a tool using the the `${task.cpus}` and `${task.memory}` Nextflow variables in the `script:` block of a module (see an example in the [modules repository](https://github.com/nf-core/modules/blob/bd1b6a40f55933d94b8c9ca94ec8c1ea0eaf4b82/modules/nf-core/samtools/bam2fq/main.nf#L30)). - -#### Nextflow version bumping - -If you use a new feature from core Nextflow, bump the minimum required Nextflow version in the pipeline with: - -```bash -nf-core pipelines bump-version --nextflow . -``` - -#### Images and figures guidelines - -If you update images or graphics, follow the nf-core [style guidelines](https://nf-co.re/docs/community/brand/workflow-schematics). - -## Pipeline specific contribution guidelines - - diff --git a/docs/README.md b/docs/README.md index 2f7c904b..c7b0845a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ -# nf-core/spatialxe: Documentation +# nf-core/spatialaxe: Documentation -The nf-core/spatialxe documentation is split into the following pages: +The nf-core/spatialaxe documentation is split into the following pages: - [Usage](usage.md) - An overview of how the pipeline works, how to run it and a description of all of the different command-line flags. diff --git a/docs/images/nf-core-spatialaxe_logo_dark.png b/docs/images/nf-core-spatialaxe_logo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..52ecaa705a35b17ef9fa787c53abac0fd00f0178 GIT binary patch literal 31726 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%0A>XT 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�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 diff --git a/docs/images/spatialaxe-logo.png b/docs/images/spatialaxe-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..16876b2a392b2c08ea67296d17cad057233c5409 GIT binary patch literal 69003 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 + diff --git a/docs/images/spatialaxe-metromap.png b/docs/images/spatialaxe-metromap.png new file mode 100644 index 0000000000000000000000000000000000000000..860b331eb7c09834c6475f34740d1174980fd060 GIT binary patch literal 709694 zcmb@uby!tt7dDPM>KMovfV2T3-CYJD-CZKxNH;hrs0b)2hn8-o8wBajLxUjAp*w$T zGp{q_`~JS``{&!&rJS?RK6^jUde*wvz2Z4@+g3+rOW z@8{smL_*RW{JCKDMAZfhi~cV1@04SrfIXZfuoY6VmA5pobBal|RofAgq*b=$6;zs2O{5TlpuyM)JmtXC=DEG8Hw zO4+@&G2&_zt?d+x;|>v1Gy5=QNEP+*nwSVGAk~{Jx&_3X66JW3M%ReW#f_p6>5>lQXOvvG zDXqb-Nx3`65O|HEH(0MOf7PMN|>_?2f1pdFK0# z0%Md2<}%0S8WA}U(K8ga;umvRDsP+(+!9qIH%>A}Q7*!Vcb{}&0-Z(E7#hdg4&)-= z=ZSY|h^|X?+YS;~+!QX7S{fTs;M!hp4diE)w0Sl1-CL)pZ(zT$W{I$Evghc~-xxJE zNMdobPM^EjqfmZoxDvha`m`g!>HlPY?-N{ zN{`;-tz`+G8jrEc{kb1UBPt=5$5J2iRJtzXERMb4-bKx22JJg|M(rjxbPw!1e29W4 zzZ50BHzzD=r?9)VOb5?n*tTT?6*xJn+|RYnV0m`k^!{+)I5fdX8ZCG368VUQ^5jdM zn_lGQ6sc(>PWV;9tcqRtyIFlPzq4M}4NkZac(B+|iuEe+NC!2o$5VZ|#_9=*cJ85K z76)~ajYgMMH|xy6RfC(tpA&Fwb55n^@X*fT!8#S(ZDD*z7&t9%Vkt6ucJOv;wx(oU zWo4uOi-cyl;e!S8O%_R(UVp|KMs6`wW{_WR$jBqn$CBMG`N%g<1&)T^cn05Oq$sC1 zPVn2`ej?hQy0$zf6j=8$X*ZBWr*e6WLDX3##N)Uv)HcQ#m5cd({}B7Z@`9e1oqia# z@n>U{sQpU4OW#Cf%k{lpk(oLE+wJXDn_WJsaA%{n6@&(z<^#= z+*$o@R=uXL-oysJmIaGcJR1h;`v`JAEoF+<7Q>zMFMD6VAoI zkz#LFOzQR#&lQv->z)uuAoT7O>SA_6A056mM#X1bVY;kal;EKrCI9uv1T{^hI zme8p*qY(Q{lux4jpk@A64I}@Do_ZK#FimQdF)9MNbB%Tvp(tHuOX^b}JL}`4)}}vd z7}Mr{B=hv-GbjH^vG8NzUVGE~F#)Fub;TnNktpNhglMJZv7aJ}U5ni%#G&H|x>^k4*Hvg-AIh*M*P^RH#!UU=(wfsOjWr`qMvU(qB{fay9*j+wn~t ztdyrT%2^p;CWg}JRS{N`9;0`(yqSkF#|MVhx$XQz9ci0%Kf2={`&(wN&o$@2}8!UGz)al*B zo6oVHYO1uw4EE+E)i*RWTn!Ho*V;C7b#q&8X=>WzW@YUObz%%*D0kq4-OBP zscC69N(`@)k{-QJC|AlnZsiyQtL~HxMDs=T_xF3G{HQcUT;RzuYMLxXn&*V)aC~1z zvCG71%-9gi^I~XB3`5OIiN|&*`R>k6UQkb4+UDImcgnT~UcP#@38s*?Y1)(WJ-_`P zH}{^q=(c*kyOB`}nm^+8>(|efvMg_U!|P9#J-pY6EK6(yDJ%C4%a`b9`c$i~erbFQ5=8mYhPTmJrblPUQ)A1E-Yz#$e znBsznh=>a&H&r&i&MlCZCzXqqw#76?Qc&=lyi(Tk0s2j%AKL!zHj zacp4{wIiR3CUd? zF732#^RThMQoN2-kvUrOboQZ7W38N% zM6s^gUhw$Xo6liB*weL{sb`1bC2G&Kq)0cNK@)9*F7_5Z*KO`Al(Vw>{7hGuPF;YV zUFqxBuc4Wii90)vs&@0m-?qDVR~A0c^qq`saf~82$KIzVu9=~7IA=5S#?6~4=dcM? z3f#+`H*Z|K_H1BJ$)x{t-09P&Y1PNnwOIlkj@5pS5KByPefSiNug~rw7FL;9jW>30 zk|dFI*s9O4^Fn55R?NqbDTDc@>L{I%uU_ZXOWemQ+;DH+G&1(aJ$>d(Z?ZI*I7^26 zmQt#GvVT_jmKwY}iq01+wa47R$2T^p-raHRoa=J>n&&JY-`oo70;iKEPZasjD@>y%tx0luOP3B4#vT~6( zDPZX7vhl*929684bKS`SiIsR@vZ)@&M<6DO^vWbACZ^Ei=ppLSJ?Ha3=c+%^rM#2{ zQ0&=#Pdb`bKtMn`OqM>1+b*Xp4tZ42J;@x_nv2`RHUVk{X2!rEy!;i!&h~KN=Ye3$3rOcf)DAb^GdhEXQ-N$Y~KTI`R{zG+54VzB&Z_PH*PSXU6=}N6@ zk#BoqAD;0eWNTU@=Bcox83cTKyw3GgUST&;?WeG25PbkFt2gB*724Fk4ny6 zAvO~4d$qQ`jqXX6H|NqE{BC7*bg-*sGpGxYGTj=HoSt1VxRyb!m}XS_7T07jFLg>4 zH_je22+I*pmn@FXR99{erCUcye=_PycoWK?ot37Tt`-f*Ow$^{AsrVN7iy^Z(w~IK z1nyg~I0nkon`hizmc->TUiHFx(vQQe5g8t-+lFlRK&6NuVPZX*$8cyFXs>hYg;{rZ zcNDY!Te`|ID^6k*hh;Rn>;)}3xuDH-i|kN=IbHk3D_8o~rkX?EzklkaB$T3>n-of~ znYOpNpdPJQYHv=bU7=w&-}8kx{f>tHfWF$7XRqiy?p4gErL%e*xxRY!N;O*XD!1)y z?Ac3r4fXZ)-HAVCjYUL|WE{=IuFxfc({mat<=fWsShljZo1(Vs>Z>cnoa1xWZP&oC zU%vben}l1z>pV`uAz4MC*}x|%`J`EpTi1uXB4(}hl7Jc@t<>)|KItyoO9eC8nx#tF zQHPkJQ)OVUi+WzI>VQjQ2U(LswGYxl`SD(9$9y8Nn`jyzOzET<)^nG-o@A z^56&IzYPdLd!9POYSzzq_UzdK6s+2*axgfxVr%`xp~^F-PfIP26^=TmWA?WT%!fAS z)3d3ms8Z#Y#*(Fmmf#tf-G#o)Zqw}YB)f$^#iqkj^PvLg@fYWN;vSzLS`VXBPbwU9 zS101IP>_=H$L!8r$HldCJ}xNzUR+ePlX1wyz|h`z(A5KardeX!`TFYJ8`rPvn%Zk= zMUOhokbo5=cVWh^_)KOT4lPMv=W^ai(Q?7C(P@_a@;EMf7ajAcMKEA&uQZcWct63s$vaP zd*-pk|9s$yjt4CJuS13!vDyDR5J(_{sv%NP`K@_oibQen)Qi zG+ZfDZO(_x{l1-6ys%%M-GUNOu6`NZpU=8~J}ju6gp~R>tnsr;MU(#P?9t*~z^FEN zXJUpFPKFH>0xXb9ezKSxDC5E>mvy$E-^QmwmfnJbf`STcqQ@!8*yLbmm0jVlP_3*M z45o?A1MCA&@oySVeGS51timQ6)NYC0g6(2I)1~yk)Mu2OlCr*Q_vg*4SX}uuv9MyT zxgA%YhR@*P<1egyuL1Gre!in|xw^V~<8XVd^qtSkmyWTJGx^u?z^TLa3YJy#oNcJ7 zJFqgLknv{|3k&I?bn4C}m;ZGktO?lz@IF=S^#Nq)4AoqXx`m$9_y~&<(ZD>Nnsepr zZ9G7?V{A8OWov6|Q%%uXzz0fi5E44~xdF9wK0e&u959SAULGx9TVI!VbuDw=n2l4n zELW_k&>x$&wz0{#os)Ar+(ZxN==(A=GY3aRq&GA?K^7fwLJ^&%37#a~`YMW%MT65ZzFK)1f@R8z=cu3P!~$bdH} z0yOr9C~KwCYnG&dhn#ZcrW7!6Um7k(rUSm_7H~;!yZIFGAM<0Cm5&}h`eDnx*~YV6 zG)#2$Y8?3eU=$FVIB**Pz!hC>Q9N*W1n_}XNMaMR%7oEt_M|H_ge`OYvc8NG-MkKF zDdgxZMyx6d{n?JVy`9N`W?lgbt?2l9QB-n3#3%aM}&3ILvKXaW1h zk09fC5W947aq&zh1vRypH1Fz5VSi$(8b-0mu+Y#Xu#3B?Fp9A*)e#uD&x%2k&zjIStIsC=6kSwBL;j2D>W!q<3LweS45ddoMxib z@{B$V6`wkF3dL=g=8Z$9^$dfMm*g0CwFx_iYuB!AfOA$yYuHGp+9+qLC6+Fgi0^M< zY+TA=-Y6zrZ*Kd=6hO~H)d&tt`sas>i)M%GF~>bamX(={i;L@h*I?!9NnIomK1O^+ zBsKpGEKODMsK>F}h}-svezbsb7rEa}rt0dqKi8~^%&Ig2r;Ru^;&-g^=Kv@DpLHkz zlH^QG(y}U#oH3*2>X-0nJ0v+qY)1`N$vh5Z;Z@MZix6C)zyREw7C8l$Z#U+)ZM?fSMbSn@Pfza< z1%TNJlzpb)#gPQ4j?DJb$jOs|M5Td_16@)rUg*y^4O1($$OP^kHt0w%3aJ6-gVp-p zTtn%pA3uV5aDGY!`CC={1{}3#z1wE5^4>}fj(Aj9P*6O?M+^QuOZVmPt^^1ir8tba zp)vV?{&{+H0WGcVwy9XWKu1g4qkX)m+#1D$#%#jON8I-@5X95kWx{oc9!xa}xbA$m zC|eZ!7VY};>?K=3wy^z*rs?lKWc2da4{5T?J|VE!*w~oZ#wIBbVMK6*^sC}q zQpxhbZ(QD33UWHG#9EYZ_Awv20!Z5pnHPr82++|2gJBb|Sz1`9RnX|J-VO+^lrqa^V4#f~31W?h?G-C#l zkpxr8Z`2W48UYflt*#=xa;TWX3HVxYVBiGvCI}o`l?Cir&mLE9!I-| znc9^V--f}|=YVc>L2QzMXe-Xe}2>pepvo43v~2^2t(mrPI0r zd`Uo93()|H3CdbdLdQqjm3A{x4ozsZGNn}H{V)6u$Ou3uOWz?>1VF6s=x{#-ETGmG zA94w8`lG|eHf%0ii`M#odtOB5MV!0NCGeM=>fSwJpd+#nu#=0{`;*9bCrJXV-!5D) zby&_q=FP|`6PRa@QG>Hz^+)Uch!kaHZh3edck2Q`InO1A4QK5)+zpt0g|EeFYPPfT z-DIdBQ@mz-b+Vof7DnbQlaJw-WNOTQ^2L(6%N z0=Vz|M!@lIyT?4ZU^^a;`}dz9q03;QCBxj03ll)6pS!Fb0?Fvf(&PzSh2VW|GC)8H z!jglSCDjIZcEcMt9N`4o?((CHuFw zS0#D{0ci@U7H258Em_szcDlHS%`rS~pH*2|ISL7oZoiqpo(eY?Hp|greG5=fcc2fs zb>Q@eqgAvDEj0be1V&Gu3(?KdR6!`v*~rbwcK&?{2>0mM5#*kY4=8!JJ=^*C%=s&} zr3F0eGworVxti`0H>iqg7`!0DKMXD;+;xeG$? z%m?#Cc^sA$Ae;bx)Q+-QA|fSkoU^57VOh^eT803i&Ablsq_o=F+m(_F;G;)@r>*xn zHi1JQ@jrj=T#f{pWgmo^8QkKgtC5JeWhMRlPTz>V%ne$Q{F=X7fDZ{2Q*~WvKp*#=)Qmqpv zW=>(ZnWMs_-2H$L0MpXUVl(M!(9cLfhn4PA7y~gRghUem;YRm0qCD}SkPthibtiDa zLrzMST7bw((`;hum;Sw0pw%bt2_dQ&#}I18VgFK|O^rbC%uT^zA3n%takrI0CXAPl z?{MQd%+_Qpb$UY}&?PC*Wj=-85S0V0_Ei75Njae zb^Lr#8~uGaXgz>TV98z!%wJ4n(iPoSyKwg3BY7Z%gr4KiA)hyeA|obtedt6n5(85h z-4`oyHwbcl`ZRWki-L_HjAB~?lAQFdy&gTql+`Xlfm$ryIz{h7Ha6+o=cYXNhsMOr zc%`2_k%27T4FUouXS0DEeSL1KvV?AQ05O-`1fLHM8NJ!~6mIln+1akBAlaNbBavT;Fy9QyVQ9KUx-RS&;>#N(CA>|3a%7ZoW)P?A4xVVXs z*(iBexxZAJ2C2(%^oIn<$XmDmosC4qWVIRx;<0Kp26G!)1v3b9YzI>h4hx%MCM6|x zSGw-7KHa*xT2EHmq7Gp5#uU;yTdq^li7@U#08>@nBx7oNdMSk8u5(jxJ)d|9oM?hs zE7w4B_epv;_9z>6dDk`(gqu`he{bqp30b1y*o#aMn`EHBjc9fF~tk|6zrms2$i)w4mWdx=|oGudlL=&3qY=wSu$6`)OCBr z!FhKoY(%TMv9S{z!_wLqB<`9tCTpi50Jf+h3|ros_n1VTRZ& zhB?DlksN{-4*&h`!9d8}a1>55l&$>v*4MW=uMSo+b|%K7q9y#_dWl7^g%~nD7sy<> z(*01QPO@7ehTkpk>DSY^xVWN$a`N(Z@9%l4trH4l?VC6dotuhrPvkYP9@Mstc zl?G`naiANSkT;DFp-~RyT9;FXRV$oCq8m_xHp2b2W(K?E^6gM34Cp4}A~5;j&(k$a z?VTYIW_vy%h=inv``ZjQ-9_Y^;9NqXIYg}U&M_}8pBmsN zAo;88>|9b{F_H%65z2Fk64;3#xE_11R$ic7knzk%WCs-oQ&3X1N_V$#=~w}M;44Tt z*wxjgkp(_tFBBZ&e%*h5M59IC%q+MVIxZKR%{=O;PLSIWg5G*_CuOdL0DJa$8 zXB{sjJs!?tXfoH8$Qu329PVF5S{KCo5G@3kl>**KzoMS;GBz2%1_Yw~mbok7;(L;% zW8~08uzrZYW1fjw16pG8z4}!eFu&quJ5FMgj@XCA%ca319><4->}D#d>j)FmZw^fb z{F+j$1U?UW*hHvkh(}&0B1#9PO@k1?B3n6CF7cZSne*&zI`skx7y_-bW>+JcHyi(7 zlE%Uc{IP&QMwq{+Cl+X8mws7-_Ye|AM`_EQF$bSM+$NwMYnt+T!9VMw+bk>S?j03uQ#vYOLe#8-MD6Mz{iR0f4Z!oA*9`Faeh zqN2hhB{YZH_z=i846zH%djEa{<^YdQt%%nPvPOkA=?bY6_84GqVN5lQ8{j%dw(J6u?OEy8#sCamWFzxxUZb0%eb7*l08`?_Q z|1E4!aW7~|Db{^V(Yo<~4$D(a@88LmVhPEC5KSGOxUHp;^o#amH`2i;zQhHX`tH3 zfAeU6d10w^nST8MN?FDby5TAP-DPs*d6EIfLX6ew98~UV>+8cD3HQlxuV0V<`0?XK z&2<|#hnakNeb?nMPOv#a{mCCE0}_igs{D3l-VEtmi>0FrAgEkpbXbFx(a znd{jG6bXiTJ_l+g_qXS%e}rat+$+ zUAX0=WN zZ+(Z`xIRW?F+~{6*YKrp=;b~v;ZFO~dXgjdJVa;{)(WFfjcnE~2pEaaAfieDL{L@u zJ8vCEDxZXh(ay#%r0@7prmJ6KIP~SP1QQX~L$!C&&zxVy&MTfa>l*TKUZhV-#(hXpKoaBo_wSHM?Lty>VE?AzXIP_tE|bhx_m<)K3lTF$ z@^uUzvqy@l0>&d@Jhm=gZ6%e@9TZowGsdAz0RC+K@4?4`)ussf;t?`Edzsw)lNpW& z20Gu7XE5Ja@ZI*Hk)J-B88SoLq0Xah^!;d)a3n9lxi&B4cKn~zCzQ7R*J_J~^;+av z)NXT_^WjWl6J1kwN4`fhPnrlvMKaEIM%PCJFIz>unv0qz;U@2^U+Owmjg0yx17DXn zt3!A9HU+&CRU5-y*c@9g&0p`9^04d@={-|J+j=tL^iBx6{yFF`sQWK9-A7YyYRsS)eOEI$w?1XykLL_@4>46dAjP(B&U>Yi$SvZJP;OtU(ny zWd+J{xReCT;YOTjUawCO8*fF#l1aai`xP}VWEcus2XD?|jVnR;LmTzi&FHltI+PU@ zdFYefcz{iiv4jdd1c8jXGa!5_X%t zA5^xSmQ&Z#7BtfFk)!nDVkXwR>r@s zZBEs&o;;xH_#G*q8f1p~mLsz?OXrb>fYdwik;c2eM04fr-uC%Uzhx@Em* zlFEGY&~a?yD?WSM%h`~pLBeY;F0SNks7%bip^WYTmi}XTdHE|fSk-u@JKu*mLw8i- z412r0vwJ9AIOg%f+}J8dx==4_@T1!k%D29>tv0CI!H-|mZMNL#Jjk>)uaeUE=%}Dh zvXC=73UX`VXS|J3Gg5@q#z~sEr?99Qx>=Dn3_hUG*16jEA>6ryPp<$pk5`7|WPmGp zfEiF3Kx*I+8>S)@0HU{OdL$~=ntjQl-RRcN0nQ<2*d+H&s2)jg4NK??8sXi}c( z7W9x0`)qlQKuU-0pq$7fM>oDsv)HUGJWFv>9SP&jIqlfG{lx$Tn@APKJa0SJV*Bya`Ug*`k@3q^b2Y1xpL%1CPuhrN<_D@1-wL z`Y$&&_k0T`&*U#bhoZ<+5sj&Rc;h#{{7|gigHx`zlN!dl0wUls+%mdrJLluX$LiB6JsRBJ& zv$4PXnRKa_KO1a%zu!G@;>}JYQu*hg&aR%g<5tEYnr)_@5c!E$oq6H>OH&1j^LD=P zD#!Ev@uE8NyvF9^$J~Rvlah96?oO~K4cy9qZT9p;)Slu<6Yf7pthHJQD#I|yaN&Uk ziq7fb6#?w@0lDoyi$p9R%U4;2u zgL|pWmvYP--)&{5$=VI2D=S4c_YHWtYm+62mO|o_G$MlqSL|k4ulR+(JJZc@}yck z_-G!kuiw=3DbL*b`7sB#+(%t;gCY-HK1Wp?>^BSKWHmY`tAdp+{EKRTmR8cq8XYWt z#A5Fe_vgeN5Ta6Q91|0Ie!iom;D_ex>$R^??z5H7>u79h#;5{4_RFcMlqhVudcq3@ zKM*}!QA05EpJM^>3S=tT_ysM>Y6uE_uXyPES;`bU^?4#XYO$Q*I7Msp*iT7sF^?`6 zEYErEt@;i5#1iup!c}(h7p19E8%xA=yll0}$Mn0aueoCTSY&o;+?~&pe{JbW7p|ll zWB1_tR%W07bz=}8mn$3RmYSbgm$;&vm)VK<#@SAI$lt^3<8%eqVfi1{L4Wvrs1cc( zwC+3wHGdJPxxE!CiC=W+Q{(vxS>f*&)R|3x^PY=qw(RqCMmOl({L7kCHh#S)?!wF5 z2LN(Og$`~>yoN96!k@qRj)0sdKB=L2G$pakwCVf{yS@9HB3$Ms)0KDbC`prFR%kZy z`%7nJQvVAbys6}4NCs183SB>W70(sudGGx;J35$YtK2Hk*uL_GqPyEZrU3ykH#@Uz zIw$YTBQpEa&b&IFCg(*40}%v^mHV~pM5CxxWZSrUnrqK;k6wL^*t;iJ?K&T+O=jBe z*?~aSfeh{v5s;&j*b$TZ+MlimY#E){B^(tIr(F;JlA}!OuV`~r1PEq4zg7Deg7KXX zH6oD{(Vf>$zW24)yfMAy?OzzSIe%o=L{I;jB3DT!;ixYm?1a|Ugi0VQ5_0;N#g0Ar z57Q|xG5?a|E7c#U+AJXGkv=3<9*-IfoRM^=Y3S_nT3}@`&wYz=!tqp7HqBsCzAP8? z`KHThL>C&cf}2v5qrWvLLg@84|Mv%qwzj7497LT z)_6$2DUspZHB353Cs5}bWK9tW+w=5&Exuk{^Y|4qbmX8vHRf?#j?}A=n$gRb_gPsL zAo}fsssgVj(s$DKh2PxAIh!>5O9cPt#MR;Ul}=12UEZf%4530ff!_>nN|J|SdH?u1 zH!CM?whLJn;?sbRJv4Kwe#gcI{`k?KDXzn9JP$bmLuKwErDLe5rbGM+g*?dm+sQ%2 zGr1Y@sGvOkP@Z9P-Y|lcVX=k{CovN1L*u~YizfKgD#?wjat?wvliIlB!4sU6?psdy zSj9NR%%E7Clv+DCv{8M;CPveePr))=nMPW+c<%%#y=<-`s>CpDy~YLFuG|FhlN01)~tof>a6S)-2GnY;^M-YNrdOM^%%4$0+LqrXutSXk`TxWBe znQ^w6vBteoTA|Z%5!>s|hG6+Z4^u`3ozqSXtzpF|ciuymE61^?yN$&Sx>uYDgBWF$ zMc4``5U2Ox6%zT#*dv{P&;b=39j!GH$+KK0zg)KN@2B|J)$$taULbB)`X&8Jc9gmUq(TrYzy78PNvh$c+N}C`u`in_in(lI4T9LU8sgxzylR$$sV3qGo z_~I%5<#@_ORi$_Zx*>aNCQAVm=A_sZOd=iF$vWr08ENh}gL(8UFJ-{onLX>0-%N z{0cZRa#(%{3p1Q^g;&9v(YX&;Ja*->kINK_g?0<{>dpzzE(2>&*DHf1o^&O-f}J)h z!6C@nmFKmv*=Z@zWUMxHBO1cr01H=XvL)3-rq*+FX${_?Cuxclm6ISuOj43=U89>d z1ceg&$7MgD$f0IaZNJyv_hOh8pqkj7P$@TYDcvUj0GkZN$y5z%t9Q7-S|n^E`9w97 ztzM3{U7~F`UA@BnAxC{)XT0Qos`Jy2Qzzb*o*2Q)LP*xr{U=I8=4NWfhx&xX_>cFb z{`M7k-LkUoUkmiA2|u zqk0lnj935SIjV%l{~Zb^@tr@fshXcu_l-PF=n0+r+S353@6Su_WkmBI(Wzz=t0WT} zzDRUJk|rg(Z}3P|vO6XB)6_B0eS|E;&g3tMTICB2ZnJZ=7nS4o0_GSFptt<>z9trCq~pL{sVQVV3m=H5W|z)3~Z^bGQV zV4`04W&GjVj~80r1&dqA95jI3dF z-Ez}knu@%`TDew#(Pne24cFOn8=sn%K8J!OP7Iq*mO)n8r{@a=PDt48$0K(pLvE6$ z&-oms7hbWHasSfY)uQ{V*x7jDG~E-`bPradly9>Jf61Lw;1}kc^5d8KuNG>Ez`ND20a!?Xp)t=1h6vbKh4O0G#HpLIqF-}kZcGq86~9*|Pj~zW z;#W2U#5!IE_YOu!7&x7{7Mo3$5;k_(yAd05ietB$SrCyN;QX?r3XdwI8#)rqFXbB@ zI}b4r`Sf-7@1aw5WD?B7SKQ4XEtu$3o2x}uNVYE@tIfTlq-~|4wl|q(&^Bp&vAj{G z&td#TgHmb+@t9_h{xMW|8`5yWup(*sK*JKF7UyahM;DHL`tC-6^Bb|t0r?a-_+&mh zDGmzqc5jVR3USOkTzoua;^y*LS#ocwUw22vzuHHeO60yUQKTE~Itf>P;UY3rAOAPT ztuZa^(qua(7pSQZmCzWjc0J9?Jx#coIjV47zCy06vpm$$f~!QjFw03c>Qi<{|FoAP z2WQh7LdM4Beib?Cmi)9960H%j*bfEHFeXIzqGrjMNO0%y&BX7~)4^0W?I%aoB0@f` z?g!>A2$oERr^|VC?J+nQ8F{ri$F53V+`|6Mi#YV6o(@<4W5eaPN!^ld0sl&adsjaQ zJ-Xu?b?3{%qde9`#o)IaU4r=Y_LBvPLz(JgLUbdxU-El}OsT|*L&xZP)x<;>_mza6 z`8Ckzw6!b;^e{?3YkfC<=*r7W$};2HzP--p#XeouJ_FmL{&h6iSyNN`t-9bdQR-mh zrT0>?sXHm_KJnZCUDm?A_G-xEk5;t)1jz+D_A9M;l)3Uwy6ZO#h}CMzELvxmL(xU2o>EN5PdCUD_F9czSufc%0`s3-!HQ=zsuU9?RacfG3qQ!juPf z-YvRUvWw|9Z^@>QO@24YyxEg--rZvKN;c%e?%c6v>8_*}Lgw<%5jo!8a{b*PR|H$K zJx^lvsWT)rBXtpMOB5-QVe%$#HT;E1PqqAxX?&aaxa@l6pYg>6S27L1sHs|!hw4)n ztk{m6cdBqVY?>3kj7;D|`lpzL|9S%PeL4o;3;TFXlO19$O1|}|Jg3td?;pB_y%vpk(UFB;K;hSqP6<@mS$wQ7j4b5$eqe3sIMnQa$XlYeV( zCen1IpOp$_$2@kZ^e!}8;pJlUn;E+)rGnP7|7zoDY=fUO0sY0s`qosZrpk6=r9|9~ zZVK77KS=J;aByb0N;CF+a7_M0$`LQPV|Y|(EhjR}lIDNPiGjS|*|#&~?|Reb&?CoSCYRxXk7m+=i!e$F)> z(Op$o;sUjgB$cQ{O7`_fURk`dr6l*foVlPG?e*(mK>@n|dbmb>?%CpZW2LXNXr4vl z@#18Tx4(N&>e?B*Pe;N#_SudyvqZ4lQbyv#xRa65eQYB3yz;U9Wvt1H=%q!9CS2yH zv8CFN-Ho?yFr4*~ZF_3%*qT>HD~$OliNzkoO(>+c9WAiFmU~uHK!gf98tE8k-f`Y$IHHV$ALZibV>iGg~ z5m?OadaHFjy5UT#Zpqo2SyGZ5WOXBbuj~j!Zjw~KbsI<~(XtVmxY9?CiBryWapGb< zEsgKsgN7m$;L6qi!4>PXQU=M)_U*2Cq;G280x~XdWqo~5_`N4xW`Jt5DXo>qe!Y79 z!#9gTB|UAZ!M*nPzsJd0)KCGfr|BRRwbE@&!!d_qF*iJyAsYHEySN&JJxubk* zD6;;CKxl{YE+ZcC8#}R;GJ7YoiWIW1g|`ESyS}dXY+OEfh2(Lw&nMsC+M!o(cBqgc zyJFAe%U2sLe0+S^JEdQUsl9!-v7N!HA63gFaqf)lk)LW;0#5lEly3E^&Wb#j@#PV2 zzv%S(&s4jfH4AS}^jd%Zzahl>s!|3mUq$$~ed|>JfL?OX27B?U#!3_~t|QOX<$M3P z^{f1$0ej-|j5_~>$~@L*m*bds#|G!J`N!&So8s56J*R3{XIqX}4l#HqrP4UR;pl@N z{^0zvzR&eR{rJ$XbbR!0$|*lqP$%-y_p6@yFF{fChv2oRLCOL)$-{=4G720nGsky_6R}Q44^SP zZoVHQR(P^s3U`z{oBqcwP|@7r$N$F~kypFoFKS!f)zIwPwm)jSp~sVG@RUnhE7P9i zs(ai`M$SZo6OVK>c}I(E^|ojumHocNz3WgB1*GYJ)p$%#L*olgyiooi1k;6Pw9CYt ze|Q|Pzc~6o7)E{MM@~QA&L=URc(z-a?K`>)A8ni|@zQloqZ_2Y`?bn{Bs%7Kj1s)6 z_N7Gk(h0(97}hP6WBoep4>?bTChWQC$m&u^a-**CI&hS%$XVTNM)3zBk<-~Dh-lWX&GFcGL4_AW1~q3Ne$LsKqOTK7IVD&Hjvv|)*o`-=}b z%gp>2+7%}rhC2l^@pz0E%JW3YwR^ts53$gd*(^}7Tek<5{68=MCmUjZ*N@O0VhzpH z&$gEba($p|ZUY;-h6-T2|9j}a$%VZMSoRp6s+Uw_>rFQ0*_f9J62^;HtG46#R8iKR z+x{8TDi+?47Z5K%{6@dF5yPvTecf$=-!omdlNb?aaq%pr#; zp{l!J>n>&W09Wc>RNXVG1Ghuy;Wp|?xoTlyQNV;48%zcGCchDB$M>bXAdGAUlC1Uv#6920e zR}Gof>011?CF`|)m^a6*foC8jM+$TPPIf;+FCRgJHpeA>v@G!`AB)%KY@^oSiC`A$S_ zD6^ySkDC@#EViB^=GKBUKh(ZOkBV%E+$AWnBcv)0zmh#;!q~|Z%O#H5y4O_6BiCszqXdtkQlv zPp`RMU~P`aa$;)kYw2UH+U&q=^JCb4#Obt_Fv3K_V^4nTR@IjKUVyJkv3*T`R@OI3uB#kHajXG7P2cyW`2r0A?{vaVS|LF#c(rn5JRA}*p z-PgEC$Xr50OgR{JN1v$)9NaA3K6WY(Q%X~FRyK5xuXn6$^V%s^vT`gx?HitYKFsEr z;qlJWc(#<&#Gijr0zIzUTRX4~PQKjP?tIph z|E#yg4QQW(9S0GxIa$LxBOm|=7R%AmkyA$>6xy1miG60RmrQ_Vx9Xa8>#kI)DtaXB zE89}Mc`Z#T)v`gJQ@w@OP2QX*$u^$wlL{TsPUmaSU8t7%t+tlQVQKhIRGX%z=Ij^` z>Vwz0@hR1V_8=g-eK-PjdvrWzL(#CO$)fVmzVtctLp~cSST{Z3H28x-U}!(5ZPfsX zu<-IJ%N)I0!q}B{4y>D(9i5yRZMx3eT8wR#WOZI;ZYh@FuXtf8ZE?HZq+;V#Vr-l= zwl}MDaOuH3L`sM-8WiWTB}%Ql5H~270UYElbQ&Jz)G4LPy@H&zZqX~U>nD*H%yz5d zXwMWn7K_FGU0_3z6DA)W`S}hgNpY{rmz?n@v}B9XB63nC@+Nyl!85PYG95pO*+>-b z*aZdW**>e2dImld!yWbbJN9eX{fCdSz4HG3`zqMm=xfV;9${d00mRSHb7ka2g_uu@ zVMer})l%KiGxY7-xAAHF3L-Z#v5T-klpGxRSParQTUlBXRu4kN+dOh#50ypD9%Tnv zE&ld&YWev!udklIjo)5&TCOfm@?|v>l3-+{sF&-qp9$^hB=?%(cf5>~bdk;cGp5_* zFL(9g&Uc$`*wuH;pF;4*T*BEmpcabBQY00S1s=jT!(WG85NlOua5!}o5g>!ZI=%>+ zy+y^v@6yv}ePfd5+q?k{-hJ2@_)KoTAeq!)yslA#rzkS_Y*#%KS=5-tHh0OJq8aUlymjMrv3UAAbUeOMRv#3v*q z^kXhvNvVF>`FO`@=T_Q|Vo3(u+4D-@XIBN;m;13FNJ(A6x_$e00n^gb64(iJHlKf> zrNy5tmAumMxUKS>J@4ubuHs@x1uF-&);ID7YyFoj-v@?XQKC_*(qmnF>TB@h$JB@L zDC`^dkF*b(sS&dNV5pB{qms+y+u#E;A2e^I-P?iH^hWB2xNuAJ zCIU@jt-n0##g_90+C>ke+JyuY1Y!SEqUBfxK1nz1MS8y=9D%?> zSfKa;ZI@<4eJQec!9u=4>$W?IGW=-H9gg^KiwgY1EHSS{Chos$=hiaKp1+*s#DGn` zgGAj0#{2YI#LRE|C5PTFUb=AMHa9maHa7NJz~9!T0bM$nt>TiBj!FksK$oiK?#Z|4 z(7;eBFLq7NsKAS!zP^{B0r4(CDSJ|EtS9^s!TXONf4^bBrv<>YJ>rJOMVAsPrH2#> zL{^nvuc$W3zh>{ka)(FYf!@r1s%AXuu}O3zceBm0d&Iz{u>L+QnJ7<->tT%@;jCU z6>w^*T5%T+B^tAbZz z(xqhjpIFkqlM6N-edMyacOHk7HYa<8kB&gV`gg!-d&z|Z*LkzOW5bsiHwc4~p4pL) zuy1W0c2y6Ka+p{Vsk*G}xjTYvC9@zz|Nr=U3$QHHu50*46eI+dmPSHB>25(fMFf-%MUn0X2~ipm z=@JnU>F$yeY3c6n?r-1f%=7&3`+fg?%;C)8j0{}YdG5XT+H0?EE%%>vet*W~oAgM# zz_78^&dQ6lUp72tTk;d+g1WoRKS<;uqW=q_41{y+@I{*b)VDBuWMyN+WEBi^M|sb? zXw_=&M`hmR2421A0pdYlK2Jz$fBD4AOVh=Ko zH<5{}+Kex6wdaPvwOv2H9_z4z((XVhGOyK1I*~OObO^ydbN#uy0#|k7xyKGEzIflD zX%3+6PAxep3brrY*8l#@G!_foef6N+U9W!eR59JS zMwF-e>e!b)v~z0;cpx9lhN=RH&_ZapoE zxekT5r2F>+i)V;=?=^yvkp%*G+$&$TkKQ(Ukcrgp%vOiRN2-@Fd!vM4uE~?0GhZVg z*6ngOzt7Z-v#EH389eul7nsSGAY*fv(7zSp`#%*T(XH#kJfP1yS@(`X2~&I$ip-k- z`7}S3YZa*XE1%!&D|(^0Y(MssBh<>Syi*{DkK(g6^7N#BO5IL;K)F3aTF+;F@@Z6< zl9Cb%e2I<(q0Ox=Myp_OWI?7GwQm~t#&l8UY=%aA-uaFzesbtAFNt6}?cnFl!ITPN z$O9;Wu(r1LtgAWUgCGwC(B3*rMJ_c9OX8;)RR~JTUtdO-9~hUNZ_}ytaF|kWSeu&* z;&+9YKPgTS^p1?fM{|RUxXPpB>v?_JgRKGdg(f-c3s&uOglA{7MED!^A5)Qky!<$e>4?mn zv#|AeOGEUbtM=%i>)gddwS4hq0*$tBN@E@8!DgRi5B*2x2v$0se+PdL4$7SJWesGO z`0vlBpG-MX(N>Oh(|gRc93scv=oi)tLjBYdr1xDDh3Jw7bvgB<|}glt6bIs!A70UZYbYSsA&sd$G(#+m=>V z`k>stcrk9BMn_q>25efS<>fD8mbVBJ z1miItKJl9d6Kb|ie8h|NmT!-90}s5>O}B}$eLd(iE?r`^?nERUG}RZGdF*>sf165= z^n)a8zMO;8`SqSqS+V)smgf~y+q|){t-o~Dtu|&3J1$Qoc7?FM_^GU=%|iaBx-0&N zNc}-d?oYl-Jh`xgAVZ%+Et>$o32EoXxXSpzW0E82&o z&4>iUEv1TRPb=DJ}a?|V8r*mcmZ&| zA%-VhkA6O}PtYnYr;yv`#JuA+KI*V(_0t35{diG_DL2PfI1BON;=KJm2Oglr3SKDW z^A~0vX(2FwKAX#W+%zYGds;|{K;19KnD%R-oSfFSMn8#d+2eSN!x7dIN;K_0&T|3yPt8}i!Hhe6G+_jC#e;Gudhq4>+flo(BkwB9=zD&5_0 zNd(?vJgj2rP9K5N4MsP>u7sIiN=)nm46e^WK_jtOSe{0QCs`>`e`F_THyT-QklT4Q z*`!fDdy<$5fDX_;A)=C7+DJs(2pGsa=imx-}&Uf2}?!TtEjt;Y>@QxWfiPKyzMc~1H0Ng?f5B;mv zS#Jw7)Kfq5C>7x{)b!7P?U)u*)9c;SJ}I!`P`l+S=~*jUImGq&VD#dWu3^ooq<+JX z?)RvHG6z9;PSKIVN%*R^nKc2_xZC$v?W3^O$10H}?)@3||HZsM)s#GYOn;v1bKB!g ztSYtUHW#Urs=j5A<=WQQC4d&u4F>v9nL6H`Lsd-sG_(r9*2m8eQm@0v$cSv*G|&lm zo)o-+`5!`zEcGkcdONYGi=!NM)XUXcf3g4Y5g6GabA&)PxEzeRADh9D6k=m<%V^yq z00VQQddj)gT~ltp=~%yz(4LBi7`vcq-~Gu5!E4>w9|hGcWF6Pbs^{nC@+>#cn+0te zi&jn52y?Z>ytRq8yBFl(95n2wEdwMH!^%ztRow~1|J87@^?ENhtd z3)ut<)07R{D18AmkO$>2>^HXEUjH5B;GYpW8ku)^Z^%`byoKHXX+W>_qIXsp^ue+p zRr`e%QYaVO==H#Rn~si*W}S6ue>M-`9|L0&X7j1;+`*2#Uf233)6y%(H3O4Sl0%)4 zk%7gs=N9Os>ml1ps6ggS5qT4tH-h7hTHA#XGcz-Kti+cwFmi*e92}Yqa&y2N24TIu zB`qsMm$!YYuF7$1Ovcp`W9*j0Vq9Z5qDhL}N=uz)=H>|cj+jEnk|iF})UDMS*~=X}{CnZt>x>K0(cm3LWX2{K6-f z_Y2L$VX9VEupI&{Y4Ize<2~)X#z~R$iQ-wg7I~$7au@3GF6oq*-co@A6g#gASmogY zqSli14?o+!mBX#C!z)~u4ilt5XH799qkP86*`dWsB!_SWQU zj%6hRslU57Bfowf{ufb&Jas>U@dj@P4zPtIDNS4R##G!}mmFOBC-RSQp~FjU-Z&-T zIL-(J4zh(aDa?{%u!VDnNpeQ`z>}s}rM#x2fYI{WALD8^3zjEO;{%jZe{h-%c>#){ zr4V`DU{I^aOUVY+9qs4T{KcAkMU?*NOb5rR-y4j|z8$itU*h3;@+-5xtEc5t9G~HE zmB}a?qtx6bK*-O>$HFb)nkyKVVgD4_R8(J%3zB6&Et>JDN|YE92A ze`TT}GlVtPKeTX|rVMDs_`on$qRG+4q$6g)lQ}5oYbv z86qFp|D}S`Dp)M~E;EdzeR#MOVHaDpKIL0`^X+g&Wph<%=vCmXF)%S1Hyty2Wx;(E zh9l`XIC{(z;Ub^9NO1s=s1OvAbz4ns`6rh=^)u6inb_xGNHSUicXR0WZq=Tt=r2N z-|~~neiw6ZRoFw3XimJ{w|i7e>$#OC`4hiBhUWhLuO<=SO>EdQvMfppdrjE~AN1iImT{DgOwGQ1p8}?dP zQio>`CEx#hi-iaS8q5=ciUStOKE65tkCL(j+`hoEMlZ~5b?rrq&jX9D9QRQb?c$qe z17+bkS(T=Ve%aY9Ku4C8mJT)oo3a7Zmf?|+=$^%q3@#p;^2(kbZ?}cFhgd)VOt8Rm z@5eR!>8*Wp@krVDzHe)D#u!>wd3tIC+b9d?v*J~yhoWTvFDO@tPH+v7}dFqt2xly#(G|t^Eu=} zp;(uQ1MHWaf~OJKrBHyyL6PpO zSFebJB-;~&UX~+1yn9y%KRl9cg=brx(E7Jo6c+n4_*Xsl2ye8-nU>@;_oDh`k}I8U zEblv(r8R*0%`iF$tvP>k>$hEK3W-tXj4d`xRyjleQk5HY!1T2imcI+(n z$jIbYJ0+RuA@C%xOr}hFZ)9t=JV=|o`Uo{6)N<#dru`ZU6$1mhcdaNWT+IT@%LU+z z!)!TfXl$gEiY#MT{5p$$dn%L1d{CI7**cS>V>cbUzna6SO#D!xf1t?5{5^N=QvbNP zI28dFB^C>@Sk9*U)`0JLO)xiGj1{MQi|uaw?(um|TSLTG_Xs&M2Rdu(ZH+2724!N~ z^`ja$pW>?RnEA@C!?Kc|OzX=Ij+gQ|mho?23O-+-oc#9ih#YlKpMCz>SCnn?hz}ZdGCr#Ku5q zD<~@~ziraYRGXTbisCTFF>YsYz+C%B3lNw4zwmc?K5g3Pt|SGbcMp3I{}wJ62O9D5 z@mUSNUxqIBt2)u$KZz)ERBMqWnzK^mT?)f>GwO4{vi(nwmbq;gPpDp3k6sQ74{xow zgw@>AVmELIGrKwZt0wlQgL=ywI`Yph!i%${)1@e72)uFDzqii-Pdj&;aA@xIq_Ws) zZsc*Vc}S3tv{@ljdVX8;xXAp^ z5k{#Q9*zYwhxlB>4hI7mqZa^om5SgD@DoFR!br8NQa#sk&ScYc$Di4sm_P%_`-jt} zdpvqHVYhg#4Hezp_*ddhompMh%vc#TglEy+SE+T7j-B6Gr@lF}8L(BPCK4@p}` zM+f{Yh(xMiR)@yN$G3sQn2fwUWMp4xM^3MD5dIp3XCzv5|e+CODPP3u+tHW6JCj$Ft zwZ;#PH@ihB(e+yVx#XjT#<{D!{20vE*QHy_*OCk*vT3-Gra-mge&mj7^S}exn~$1e zB_tl3uhgwKx36+&-Z`mqVHw70=qa2fAF2KY71714pU?*a2BeyohsaJzo>dTY^!-m6| za3yA!%yHN4pw$v^aKs<9;Ep{!+3tAJ{Rsp5?=S)R8i$ji=dyaH=6Fy$yh~YMqwX|dfS2!MWep@I60$~8&5`4tiVWyIXNdMd%KuHmo$oa{( zsD9KbP_p!_tf6p@Y@k^K7VqvYTz}c(_m4cDB}^nP3r`*nU?^fFkg|5Bs|dN9bjL=1 z^}X$gV+#CUdAX3{pO!255)t#;-hwtT9a;xm9k5`9T`4)7-@+RzKcKF;W4wc5O{n3}5lpz^2b@rIKg zfl$zd$_=NBtTzi7bbo~;#o*OV5N1SNk}`0bh@?g;zXau8)xZ4x`hyhCmk9rar`VYp zqjD*R{`7~|JShXe!NgY2$fiCTG)Ia+Zx0x%n(bXa>JuoXOC&d@4`-it0W{c8X5x< z^KU{zjKN5R4D6+vtM<&Eu({7%g-Xa}(^msKD#Y1PCJWK`u{?9GPLKb^>|_ZM+Wv*W zhz^6DRz|$LJ3t{u2@Bmc9c_K6qM}h|dmGMfeo#6bVlaHB0{0#?DyS|kf)9yPYMaBR z8O&<8(XPZ^?f!ycHZ-Dbn&PJrTlCJUcHb%(q?7~r<)5Ydw^6by6oD*vvRGj-9nFj0 zOmuB7x*N+zj16qZ@In3CG-3gdYpXGwbFT;$EZL%&A9FCURBtrsrmg8zxh^ISD0~I+ zWnD!E$6YI|@ZjwEZ+zp)yX}NC?C#?Ufx&gOywr5tDHH{_(Dc^W-v zqHkWe?poj?obJ?)(WPd!JCSOVxwzPWd0HZ2#6BZOO99Sg7 zw20BIaAX6sp&U06TSX`dC9C;1-o>|L5usl4P;HE{J0x$*9Ax&@-@UICcrTjxv|VQ} zH+ZWyr~mQ6HYUIs&pL)7$Y}nao`~Tn82ib0KXukLe3foPNcimA`4PwC+WK(rRmz!! z95qLHFrOq96`9NT*K7_-sxm5b&) z!@E9(JM7zemk_Jke7ODS^w?=<%;j>r+U`fLQ>L@t(qT+uz4|_KTPLcKYiSN}y~{SV zI<@bs;!M`3ynO8F%+fctws1?ruY;CdubyaRmGZ|#V~EQ5@ls*3Fq@p zi4J{`c_5JGeTc}}!AByGzeVhiuOb{uTT7o=2E*OIgNBk+`@nc&Rglt^XsxjomuLJZ zx7m0@^_Z*QuBJWyAkp>fVz#!nZ^y?)Cm)Xf0kpxRc$)~VKaz$A=Ok~^q`Ee2t$s38 zWZ!Z$FaF?qbI!+KLuHh$SGs9J&GOOq``16Jguhpu(EuRBz;~ZVT0J3s{z<2&ro^B{ z63uHxqkQoAEFu?e0)(3%!7v8Y7#RaMnt~~tmOiCjjKabkz~4U}FH0aLJ{TgE;Ljt) zMx$`d!CPHhukkt*0S8DtX<7cKL}DF3+b5S(t5w>v*_DJkgZ=4F4!_`O5ci4fmwxjf z{jzcR=<==k26Q|AtuG#ik__Jr)YZBeZht5$lyO<8GNEPhyT9?%jNC3IX}K9~pH zmy$x=0JqHz=yns5gu`uxdjPP!wzoF|qVjbX|7S)3m+~d=df{DrR{j11&B7VFj$a zG44;T1U(%jpMn7?=6@1~U3t%SN9$D$_U-(5m2AV*VPe(FyS*cf{Lo>K{O^j}&2#WK z_uXt@(Oj(zz#B<@gAxmz;pnoc|1Zp+H|ks53*};wv|}sXt1wlsUK%^At~!3VwT1`C0^* z@N|V-|9I}oH=Qc{j9&j*y&65%4ds6GK;qLgx{$V(mS=nWJ%@YCEk@6tsStDI4tcFe zriArT?=BGbY6O!hzx!F}1*Y42%@PhNN z=k;F*l?+734UzeO#LF4Ahj~Qrj(!sIRl{!><>lwgC@2&j@9s}HL2`2fKeR4p&K!19J%Lq_$ipD7{2n4<3Pzl##9D)8(Ijfl8Z15TU9M1mp?Cw$t>d|xd0~SuRsw1tS${W10&3KiDBL3hzR5@Y+uD~>x|+XtiL>44kYak)79(M9o;6wmu5ln8 z^;|7a7}gwXTfG|-K`aOa9GQT9-94sa_0YtQ#sRLP99}e3Pzs(br$(|YA8gHzE^c=i zAFiLfCwGV>rn)-uXnKwUmzk?^;I=P3mcSjLfPQwz`lNJiUhipZu#k~a!fkd*=EN?& zq%;r158R9OJ@*_YOj!9U=0<$De#&XgZ}9()xQ748@MZ`ffUg^c;{-~%E>Nt~;F%0M zb0SZGzTx2F+4Jo&2P}|qdP}nUI8zOb`1o88SV7BBaLVtza|^l-XcMqr1x^g1i!1PH zrrFi7A%cdyyggdbDQ|Vxrb9^G$OBO6=EeqM0s`a&xP2D*HfMi_TGQNxoP^{p7z0W) zxwyE%-kBzY5EjK$*@)+(WxE8~pevY>gxS)c;1|Vf_hU1}4@t)~#gbOPKDg6#<}uoo zDkyI9nj`xG3&_obcxt(s0?5F>Q+rU*3xK#9-&~bBr0JEuF z*F$S&SCuj#2vHNOZxanzYBhnDam5|hoy38c z9Y9|(Q`0+Op?^saVm{PY2@nMX+M|Knf+bfkhKugPH}pv*RTinl1Nbv^%5OdMW;Lpy zpuk|>WVDP67-aMbh(>obi@r5Sfz#V>sV!i*w}F9o*gtEPS_|&}dRVj%Xqp*`h`=XJ zK2bQ{$HynYL)l{x)_bkmfu}h0auoPgrwmL7;K7V^7HHzEX^A*of!CWw7k#B#}{%+wXr2Xo{;7jyr4X~SWsis`E(UTZ%C1U26NZ9@w|HT zL?9r+cV`wnsi<$=`tqF*P^1%>8%BXf#khUIA;BE@i(&WZjW$`t7Zf;u%Z%bPWPRI&2HW2Y+zwN zi0@D(>;Mi);H13qG}a-=A|1RV~Z5|D2gV3^<(f5DAZt zR~StaMYNAD<(Q1V&d9ne(QCV{KVqhRz&u0w%OQIuhyU$hx~U@HM9v{H>CH4Id!&Lu ziH-mte*;|BL*SHdf~pmY;1Ze;Js8{wBZW-`nHoi=Bv1e`ARxYh*gWU^Ce#Fa zbI|Nm2R0G(EXTky6Ny#PaR$a4;JsvT1C(V4+oT_nEFSS{NC9_h8^BAC^W*XR5)vpm zP`oN82)-ctil$L)P7Z4UE*~EHj(!8gDQ;v$3x>XR5R-Lvb-^UqY?{((CaiUd_YB^RF{-QgK2~dm@Mywj#u*O+NUQcYv0kyA^B0Lf~SEzEX1l@ zhq40@?G{u+Jz&v_^onIvFIvZh9UL!Rw6nA}i7?OC>R3-2#P$$bSy_=>X~>^$36|we zhARn|&O{;A3t*zpkSPmCS5_QYM^l@%RCJwJ{IhOO`I5Yo!iBPSW2A!+kU=ORAi*Dx z3h@CrTemL7`9J+usE{8*9rU+Fvm}C=o!;YQ6v=9x>WWjH8ADPh7WcNf(7e3XK-ru{yVM`fn&N3wBeGTlCEK-k-ycx9|6Gh zKpByjAK?C0?++uzXS;9}?q-q8dnEK38ECf?_N&W+OJnv?j_4Rm#iC;K^L#5cZ$r8vVyvhffr5&E`7 zBp_I`wrTC?m

&)i-eN51N9qsj0qxy4xEf(kJ;_ZSuLrEkW~M>oe&!!?%rDwU0bC zMgK}2^?&S3BwU|fUX}y(9?Q@o8I`bb0!(ZW9@7pZIS>6`nyEdD6~t&f8~|OTa0TYd zugSebM^ndYtl@~H(DDZ`geO@o5&MS0RB1Pw>m?y5ZUGGaG&g;Ej$l;V$s7Iv9n8;n zHTlRA_4ZY%q=w3gK&HNW#wMX8j_a=;?+$E0BdDpwRnaZBM>n!Ljie!#|kkMTN zjje{0lM^zvHK28{6o7jP`K;sGcRgip?{=dO1so$lJ4mPzPjl>frmL-QJL?33l z!BmNFtty|-jUp(gQb|T;5(MmXuwu%7n(_E>`c!>>dt^kA+kB|*#5aJ?mUrw$E&K*q zk*NkcK3WG99`O`QvfG5k0tH!6gTF!-U^6~5&~b9RO2Bl4-^X*qEZHHW_s7f&5zCXA z+kRORNm=DAx;$@Y0*MpSqk6#=Xz+YHnQAf!lv86a>zDq*mq3^;&3W2Nn8bjo z)cAsx@Ry!zBI@(#Z(2VF4cm8tDrtekk~Qt2$||xY#R@(z&=c|Jyu#ar)G3g*=IGaJ zRJoRF*9`Xd`l2X_)Q#lz%+TSDi@yNYyc16h2{>Pa2M`#fAPV&ix%Ia8$2k2 zcg8~dH#UXGdtJfg`~+@g(LRBopjGz7tH?Bl^8J;k?G=Znwa41Jgm=53ix7c7M@mFVx9L4@8wGBQo!xpWRm$y(*y_ni@z00D_7T#n+2{Sd2m2HdP_k z%H2a1=X=&Cg&Uar^JOJtt{qP8d`hyFIzpw}WHdBiE0L=RyzM|HaHS8|T^UxDJzhLi z%hoZo#$~D8c}IM321Nhl+#LOy%if@|{>zsMxYA%F><_jx7Z8vMu=fD1;>vxvgMxth zF=DW|evUMio#%Hu;$+q;(X$l1cCBmrYe~t#N8|{xs!Q9q;VTbMhBV8A{3O-vaR}WG zZmU@g8L%#rAwXRPGdm9^KiMQ1Dc6AUY3qrY?z(w=j&qAvfD%7T$TuAvAA=Xs)SDG? zG_+dfSdYxFAw(G?YU-6x&R=Kc6ml3`ucCUz-ABM(8y!0EqHWZNqz@G%@{fTWRSbWBkUCmmI7nKOJ3nYOTpmsAWfr znU+}ls*Argx;SgK(6P(!yzqXtrlj|M)*|eQX=xfBaxAGUC1}!yhK4bm?OvWoKZh%f z8>=287jqo|X!&jjIRsfDddEPeAx;w}diCN+g%ci+#n^p4Jy-zc#L1`PIGvA#mH?{0h-9aJboCGrNH*ep* zQp;$7ogY3>A`c9_KT}#fIdMg5^ry-k*2j>$EK~cDkO@A{Bb@ddIrdn%mnwBaEbLe7 ze@d)J#@I54w_GJ8d=0VDnjs$5Q#MshRTUpLPpX-iIFjp9-ihbPlw^pHl%c8le2<&c z!wIGA824F62XkGa$eL(&^?0!k3zg7qNk6W`DguTPw@9tX!4R7v7qJX2HGk zcI}Lj@oW)F0(5C<+Ut(T2L@By;a^>aZ8mnc#P)M^cyl}wOIFPlnfpwK{38>|%|3Jr zku+&$`D6qM)z2fZr0P@bM7(PcqUGHRlGA@5*=;z-o-nfZekdU^F$4tSWW1ILQ0fvC z4-XGdpP#Lt%k~~09esrDlAt7it&=SU!{b%32LYyG6u&*l=sf_ZaaNdAY@W3q7#yS) z89%_~Ff}vVgt;v}D1|zob57m#I^J1)Xx@360D63borNx95qCjY{uKnS*b$h4#>U2m zPy-JD?)Q9$YX_Sibe52-Ld3wbKdFBixnUT-^$9o)65s~*J*N8X2n$vKTtGHo5uWk! z{o8tRZB1z z*UI5Z8n&#wP-Y}+8*_ncg5aBLN%ITJ~-4ckasH546i5M;}eqSpe za~wI&x}d$hca?Q_)gjkx9i?Kg&JlZcoYb1n@DB2Q-p3U<(?pg?aO(`GAP?1n$Np2* zOwf109;5SQx8poY?@Hh%kUaB~OhQ=QCI&|6U7(hLP288k>lHc2q{>Kum;ko35!xD- zt3zDjNAZGA?4Wt4R(x^66W}7T-8b3PI;ib)%t!eLhKEyNHRQ(L-fMY!aqu>P-i8mX zhs$*bzxm5JILvklK(H9Vj#o#pfTq5D0k&g?K=<_{n5b@SY={E&!K_<#r?1tB9kzWD z!ZaiMV&z}KL01Y3lK^^v@CkkfkJ&373*OY>wzezq(;?e%zVjt^%YyI$vM;EJD=8kJLv>aFC_IWg(W^z)vx@l;Wtptac!ir>5FJ-=u32Me>|fz zr)Ntg*2$}yhIP%<2PVf92s)VR?_9J!U|Tv?N#1tB4-U4pQla4zLUlepou;6@e8J$1 z7b{-TFTt%|AqLa^WCM3{7T#E)|2ebQCouYG1zX=&4F*!Tz{vqx)q%4OGlF}ysh zna@xv75&Cg(MSnIJ6hZBjRa*N))a&y^@e`(C}3u5ikX|^2pC!S|k=|B2IPSjW zJwE?lV9{|trBI`6$p+h*9s4|*74x)Cj53CkikzVS&c!B0@S*dk&dlUMH; za-pnE0&yro!ZZN7fxM?!z#Gb~F85?|2tVH97E5?;)LwK~va^{AKk_>s?RAVeA@b0O zuU~g~3uOb!U-(+?v$1Pshl@J&kaH`rBciSF`ZAWo9nJ!BCh?McKU^GJ_pJsq>X5Vq zXUw}#3OwOx!(4-bfozq})$M(*Z^zC8I!@;D>D!fCBg6wkKQ_1G8akKd+8s`RJYBHm z-y!A1W{>{%Q<%Mbor-VMzD11Y6{JfYC$qArV+9!Qd{)>*XM!wQtK?)|sZuNm&yYls zx|@<>sXv-}<%q6r&p)1-og*gm;1EW!s4Ty}t>)^LjOuK-br81DlbPR}R*DqVUlVF4 z76vr+(FDrFS_oAL1bnNau=*`R0C}q#0OmbVRz@I*1sz#o^T_krznZEyZx}!KFK@PG zxjDK`x-?h3JmBPqG>W`V08|ckI3r|z0l+~CFCX{WP02G6_ruSG*BCebo_z9<>tf&^ zCk1>fV(W>u)yu@uT!g>4J4dZweS2`Igvy!LiDS0W6RdFA#rfmGKCF85 z0lsQ8s+2wRPQ5UFs0j0Sc&^`X7RTHWL89e9?Z7ulms$+HyKP7 zoPWSfKu1S6g#GBmu-K}!*5=;T4OkMY55I8$9Y}1idE}LM(HA^B^e(*Q>(7$e1w7_OV+L>Q1_BcyM~~TKv#t^fmGCcIM{H?kAg4-ZxlS zSax8oo7Q++OG8{LWwIdTfQGq8t$H`Z-#)Ce&^a#uWn_tvE+Gi`7HEs0o3vrEpcLF* z!RmEZn%h{bJ8W)k#p}0Q>XQopeD_CYq%`dp&1z~iA)y-`^3Ipc1bjbWenqW`Ahn9B zG@?7v-2d*=KR40Qs(uF?T9BWJ-vtITYUh;!OB*NPfYa60g?wso*!*A-3qn*wgSgCg z(O?$mudM3Lj}LHrUu=I!?s=GEH5Wqc?FMZzM_3EZYS@TBCmSfr1x! zz|vpY+gG|ZqADdsVW#F736`!+I73-sh?}q zB_pRUQWu|KY|rhDjYM180fdLf7XG@$CNMfSlLKD4-s}; zOI?KX$h*|Xa;kdY?!mRHu~H(lBOEEc$g?PJ3lhZyK8vk&%->Hcy0LNS3bH7Jk9MUq z0jL*Q3bm7!^+02wwgCC&_ved&b0x+2%8*|_QI)z$cWF@26yD1R7PNhjvL z>GIQhgJtF~P|XaBH5Ys9(D^0hU9x?LiECx$a9=X-$CsWAtb&s8DBSTf#Rwvi&T#Jb zzeQ`1NS42Ag<#G?P&)EEMTlnfPQm$XfK3hbeNaIJlJQ+nblZC@6X4~kK35Sij%(^I zPtRo6UY*@^#?f0o<8U)a=CTp@YqpS9qH|Gq9cIsW8&2DVnXFW

Ut24m;EQ+`3OWIE^aA!1BYVU*o>_xPY2%f%BXhBdgdzjbq)-I;Y&69KTO@ z`IqhGON^5XH;FCc-#@+VNqgY{MieWO0KZsJ%l+)^Zan78Jx>P)b^}-o+1k7^+k-hd^Rb|k z!#IwYU79|5-n_qfhx>N#Zt>ot9gBro&DApO(#|GIB}c8(`LFIeMZc)IXk}$(7PX{c z6Cl5!1&QMchH$&9d-v{b=)7*-DB;=!EB)-yENmL7dUv*{QkaoaR&tyT9;pr%frQ^| zCDqBSKT~E}sdSH*GRA9FKYyl?&cu!lrX$sdXOj^E(!jW>0E$)o)F09S)fYfEHa|o$ z3PsO;Z;HA4@PSj>%b!DG?_KZZWSeRgU5GywaQjI@C*D?jdAIKIc%}+t`N?{E`@XjR z7V4g9Rm^XaByQUrf14t)2W-|UCQ?HkY2+=u=d~Svt=jsj98rcj!=uC8K&1JE9ovN={9kGJAFxQg|W^t6S-FUW;_U ze#La4m^t3ZbPy4__4@VehlcEG{=Fn-o$rgQ7g>tMb=nK?=pwm>9$2g|L2N1v8*vE~ zZVjzlw`MU%Pk8F7Sd(L_k*sqd7Wcu2H5#lteQYT^Ht0%4l3rlDhs<=r*Cfc zD^VpWqZ_7>Zhhy@o#Bd#lQ|4z%mauq7A#scNJ*(1hcS(Jc#WXA{X^aNa$~k6#%2A) zX6GQ_`_RNA-rhZgw<8_MLP`~l?caw}b5xw{^jAhsP7ltx#FfJt#P{ft6;>;J41qEd z2{_eRHx0zia;z2-VW|s@AMH0--V<%!VI);Ew7NtAaXRs@#XhME#{ZnU8)F$U90)9t zNHE-5_pHLDWEcKYg&#ibyMBGF#5Evb04BvgA7eEGI&JGZp4TY!upDbIqZ6!&5WK){?!7UO))ADqoGr#5lE5R0R?%49E(uK^EBRPa5&!VvL!Y*;}XgN*ZC7~ ze-ksC67_vaba13(OjoY#3d0vtWx`<*ADLt&H*8p1Pg`s2LlopiaHv&y^a1(r7jC-7 z`IDBGeirdjjEH3XK&Wa5O)gQN^--+5DpBgUfy>9$?8_4qZtNp;HcmlLrjMPCr0UHI z{R(_yrs1qBk()5#%tphdOZ)abfAM0T`R%`Q76|Rp^$cqZVZVk7VJ~|*{_mjlknJjZ z^DPe~O{V1~BB(*=_m|>|8asaZ`0*eruu1&t2TOPQ;jtElb6>_;&7R;?VfIB7AL8Hp z|013IApE%snaRb9ClMlDEWOiqY4q8+cwJD)5$1UNa{WJ|fuhV~iy*auSUXdIJd9ga zn127&D;a2lUN6lY!HvqQs!2q?V}5>*qKzDvz?FQRCK=I$g>o;|*+22u$2vGTI2HlfeC*WK)$5O33JW`kyh@n1)0>El4nIfq=6$}`BYetM5S>rix{(N)ux_QRNd&9#=ELga3mAN@oOGDCw z2Q2}Jy&XI2l05YoVZ25W0$Mw$w?b*rgYGEV~MWWKL})Q3TlDK!UA%K=iz`ljbH>6d zwj>Ek62;~XSdp0;=Rr2BvYU0Gq-d&199OC}X? z{%$QXxwpL+yPTbi$+5u^5r;uMVowWYyydCAL-?IK+o*K{O;n{eFbsPiG0qVGw(_QT zskD+9aLKV&>li3AlpJ1ioG8DaZph$~BRlcd=FXol-V)qTM+(KLRD82nSM-FqI!I?s zwE-16O`d}+lnuLdDHwVAlf1mki;r;6aM`uGvy#u8l$_j}i(D%cI)s`+4$n+d04Z-e zRF)Z_=GsGqCq*fpSe+A0z+JX%nKFN7ndLiKP(2$74s*)*;ggMwx}kOjb4d!?$oWiI zS>&Is>s;r`YZdFN?%tKfjQz>;=c#bDf>hJ@Y6g&Exm!fknLBUZeGUyl+NCQT6TH17Y(k$?)!3 zTUcJ6E*1Fo)y>21*&XCBbU1nH)Y!+boZW>jrPvAsUd*}l4-#9p8_HhgHI&jxv!Q{3 zpmdejvo#ZbUGxTRU~>)Ow-=hOkg>b_7;Py0|3X`BNKlYiT0Q=C9o>ujJW?6;wue*p zT)L!aID7V);Sr^ShYidBSoYcvcAyK_@c7!UY=Dqv4);ZM59@y4c%T1l2HJn>RBuEH zZMZAKcNKc*+O1m$LPCZjcHcx=J660N|Ljq{JB{jnclJ*%@*23o|8%+ALt#Pg79(;P0ds;Vjx=0Et-r9*J2!62x4SJR>2 zeapo5fhey|ojO?@spO0K$4weJa%2?3WN8^0v7kl#O$q{Km=|0<#Rf6qHr=^PmnfH} z?eS$VV%GPw@49MRWN1i85&2boD`-7sC@Jx+h#?f_t%w+63DlBh%Zg}un(I!VJjOIW z!{O2Zb(<3pCrlKyEVrv=*wRyGl-adfri_O;x##g$tA97`IF?4k+4bd*Z{J!U`b5DgssUb^c6VepSZ>)BAp#j=2ry(drhBC^+iX@+u9 zMny<5)?W5}cz8!_tVp~iInnUiZG0jN{3dwoIX6>U+*+9)~$;9S<`u zCS{_l--8-TFEiS`jy0v8PwZAExOj36)a7bqK$ptn$EPtn=eF_!-{b-D&yJ35+rWtD zlnXwe<)`jH=+Ui9m(?+!KYvbH8NcT>Y2WuVC88)Y*E(+-vikP^M!b&%;q?CQ?oxv6E7Ue;O7xZfE|wmKe&(=-g+$SThB+{KFz+_|GAqAz~`@p`EQtTajk;XV$ExSyCP zfnwQBfaV?uk8&v~se}Hz*msLS=_x`42W*q~@83Un?%YJAaev_*^Fw1`919hpLE2JP z2&-G-#J>1=9g*_cdw{C4qe;b1o?eD+^2CAK%p8|CoQoF+dD|!A=U5d0W4pPk%_xI6 ze=IMbOeE9S*ME|eBRp&We1<-K4maJkUF?11=1qaXaZLC9X*_*;JFaXp=Q(A-)v&Pk zNKW?z2eaB$&CJrWb+2B%M7{z$TF;fgbMfL3Ze9xsDv|Cx0?z(hTZ*GS^j|5(ct5dNdoA#)cl+(#%DcK%bm3?Kj!aQX}iiOzyWarz-fUt=B0 zdq^f37<7Pk6V{+SBH^|lVw?q@#I|;5mA+)2hHO_f@3f-YNnELM;oOy>>_{_{mo1aw z?up7m*naa^)jnzur0kQ4p2$2^a1@J4F$*>yFy7(zBECOgw16T);tJkWd7DwM>0GBw zMwUpnZrw`0Uj2g#LFH>UtYAT9y!c>O&!^LL`<3htZWwg1c`AV4-%y zsL`XvN|_}~mj)A934^5kw&?2St&ycJ6bnRzFS3W>+tcX}AGYUQ_)Od}js$mW%)gXUCT* z&N(;eoBY+idt$Wr9*`tzQcrq72OB@3yio45V({R>JdqJ5lX!TAwhIF&Xg6yb-#moDeD`huS>5(~kGBuRa~Cv> zrKN(=woz~cKmv0_oKPEoZO`p4-vw=If7uCJ4ao_8*r8!a(F9wG)=j&{mx%8g_~lJD2P($a}kouIM3B=`V?iBv8u z1#3k(kk;I+K$4}Vd+_rO_sIugQSQwZ&~9YLeK1l)Gd4HvqXL#wQ@fFMHcXB(Nm|!M z+o+~HNQpXX5&)bd#UjJ5s8i+<0H}KUh(Z+*;#7Kogt>}HE@A#_RmYmQZ{P0AYG9r4 zH?_3xL!i!(*v>FAnoi%&Y~w~RvEuH+h1N7aVt4!;$WUNO2#c ziPD5LkOoUr1=`2f@VW2E-m5X_*C714`Z*avjQtN@Il5?nQlWF>hji;9JPS=iT7jeh zgm1?&%^tr2NsO!{BOmf+-;oJWwnMjaU_7&GIGY)cfZCSDfIlC;%$I7q~d$iG98cdZo=&ZF=#i+@4L}vPC^RAvf^x>6n;t!`Lv8qonjFh}E0u>2KC>tuu{} z_s*(2jA#7)#`R`aRzshw@E|i(CJ7|?WE={iwdu}qwKRe|jr5c~b9Olm~`sG2|ny9Sy4LE=4QU_|T zU=`xOVc7}7wtiVT7fBG6sdL=r{H<-X z9zUMS#x*)N{Z5{dd(;0Qn#eOBWCuRZ%Cd?{aawtE#ZDU%mU`VAn&PWtSgqVT)o$Cw zZT9wi&z&>(mj)btdK=eg((F(Ly7t3|4?lRa-Orjaqep|{hXQ(1v`!uTLDQ@lc?lFh zjp24Ar4~HmyN=Z@K?@H^KJ!+r5I#rr(8d1a9_gwquS21!PZtn_Eb-l^EMD9PPoz33 zBHu5KgvBMdHuT5(PGM6d@u3LHEu)w|gBhNnFa-LG7WEeEtYFBTQTfmQ?gIB+P3E#6t0L;5tsqv~g!%sMOahA`jFrHkqI?lBzcWkg*W5odi?+`^XGiUzzj* z?@DHs+tP#*5Q4U_z;j=}y!A{e+$2&~C+CUq`|j{t>~H9^Zd^aq2Q0H&)#8ABcE^=z^}q{pOy8SdM*5O)>&v+W03u!qP*NdtId@55-wZ(&A}oF*Q=FR zRE)i8>ULw?<#Tm+Z`_dL(_nU|1D{p-DdHpx#WzO=PdzXN)U~$r&uKTW6%~423=JJj zW&yo!`N1^7`MY;bB4zi9Wias1c+0tQN<<-xp~O`GSeR<%*CdeA+XcoE44jC>BZ>jo z1N^gT&WszO>k_Lp1}D-&7`RlNJi7Jjb#kcg;>*8!2O@%2eLbpo#Jn~Ue5s7B3m5i! z??YM0y;Al<@Ey@D8zeNKG3}sGWq#c6A=0r~+{2*v6~n@7_mp3#*CcY(d(&-A;?7Ps*rMHDqco(Dwxpe7*uLrOzW)v_qvsL91h)H zMGr@82s7K@c1fLqPXT{PR4mEabbR6g$P<-)WmI>1TpjHdw~j?8*94p z=ET;Yko0eIrsKGEM44GyUH(Cw)?*>+GJd4h%qW^`Y@Eb&lv}E{b6s~dovr8QN=;ip zb)t<4!2OI}{h6tnQ(eys^7)K3_Rz`!I74p;e_zmo7XUI!k}P^S3rfmppy})?oKQ8@lWVD6Bc+d#T!!va*hpRnn@+ zP-%3J*BzR*B9Dc18C&h_qMp0}!I?n_KY8|S8n2G7XQGzotCufT^lE@JkWk%y_sWHb zhr=;R<{H0{rMoh$l!B~|Bi7=X$u-|`>t)_Y#ZGu@LdgZa@iG7EK(S|qZdTB>YuD~O zZB~TAX2sv@mzUpj;1;_YDC0s;auEWT_gKSw~Waxhj>RQ#)9$)Etpl{`i5b`m&eSvDPj zBU{b2?|E2Z!q8@-mh*&T(Mv|Hdv&7&-Q1QE`swWM+4EJJG;J0`&9ywV>gyBFpTl|N+>T~R}1Jy0{lBv56e)^HQ;``^1 zA0KY}kl@7};aJeh`_h+-VXSz<%3e%~Ogl0j@y0QgH%_RKms)DiHD0o!vRHfh9?rv) z2RkGgXS*fth@N~IwS`q}28w;L+kQ}X1Pm@Xp@l>T#u$>e`cuM|%HGj$7kure`=;g! z33mMx>Hj{IKq-C)rfCJTGaFPN|8B*D6vc`~tTySJbaj@7E+e5m85f!R^+7r1?XkD{ z`%seH2cWc&aJiF+^^SMeQ=bHumwc|M7*UnA5SRtwW=6Xe=-T=~(tTpSsBRkR3yYhM z|1GN+J1Z$&)gRrzA8ixlfkpI#BsT6<1VXX3fg>G3@^mJ_Bo@=H1I(&+%k7-vxTLl8x>`TH%hbsNJSx!k-3jzw8zPuZW@Bn zHbJHKV#F=DjJAV1F(fan-+G%Sc#Vu7qDu-$?&JwUp%HocvRI}8KR*awm-DsWVZgsn z`C(Z|{K$4LbY=}L4cQYLeK|b*k+DWs2~7uQFL1_GsP&xOT&H-!~hs(Xq4V{kMl!oXwY& zKsty{sw9yI5LWje_gA<#E_i=zrbg_c=vQ(-wUYoh*T9_H{m;i##cd5Zxg)@c6dkED z?XsKi@#9IV2Nq4pUN&%#`^PiJXU46cmb$6Wp&Hwu2Ti}x%p4x|tgNiK?A_6y!hNoG zj856_jLjNnxA&j*H+%l~Yro)xd0Rc@j?bY|7-0orsC4*NYy7#yOO^wwxA&q*e%ozlh~QZ zqvwsMxAku)QBzm8;Pb;v9>0Z4wDR@X^0@@CMMc?EF*j1*b@> zp=XaCKI`oQ#?Q@t86K5VMi5=?5*W2)6m69Kp%FfEOt)slC}?^8^$(tq58U%BD{!ab z*^~Y2GgY50ULR^N(axNhytJ@~>%%35O=zvn-mN51XKC_`**PdYbfLYnzixmSDBf)B33Y`;RIlK0QyqKe-NdN5WUdx zOk@a8#y zp%$?kGV`_H%4|x|1#Ux*NBoo~(q+WZP^ZD*GjuTFRIE0Ar~=7Sv2*1Yr*U=@BL69V z{^>yV9lg>y>+Qwou<2{p2~7)wuDqRjuMhPJD<3lk6ua0@yB$GQtOZ0Rpbw(v84149(0RmQnwF}(AkB;FFNyTs|nV!)EZGmu}V6dx0U@mvFYj?xebe> zLSjFTs3^Loq43nnpxGBBhLynL}MS^P>yc zldl&9A4?@hY4Oy_Wkl?IL=}-ko!;@h9h-isOXSqm7e+3W=-rz>Fd{s>XleZwU_6}s zzrRStsu?xjHq||6Hhn$uM(6Wab z-(?tlk?TNNf2Ks6(`pri2kIG1Ll-EbRW>${w6BDPLFglMyLSD$`G96{>L9Q3w{hc{ zwT3N?N@y+*2e_Uzg1yFN1@_0;LVuNZh9 z%6;r>x-d4U>`|t7}H+(25G25^qOS@~gZW~R! z1G|6uSq5`B`}!WNahrTUL&&>(>=L{3HoDN^w_Nl!_vzE!!NIGVycPb1CgKF=L@v8q zcQ$T9EefwTsn}GQF~U9i*!=#$Vv86H3k%;l&l|-ib;rg!yXX>_PdBQaR$=9XqG>8y zy{8U$8*t|M@x8?TLt6r9u&Tc9Qa)`!|NfpNQB zyjHtc;CLmw31joNerx9#XOQQ8tp0t=o<$D&8{H4P)~5L1B1pXWs%Mw?dsNFwl&G|0 z0WOQLDy?4qLBH&u&au3k^AVZ_zD|dzY^|0?F?ktKZs>w6owlr+jW%4e=-oP*#LWzn zlkGa)(r|siO>EQSeNH|DM2KEqkkuFm${^1H_9C_aAddPBvYSkVtlZi4i&HlD>hLEQ z(HQ;r1^@L0#3-$7CJ}TF<*0s6`Hk&V>Q*bSqV9-S5haE>1@$tGsp+^T+cT7g4A0t`uPw03*-RSlUl{VZO2c(L^2iXY%k!pr#$ z=|eliDlTT^ZPW(D_MzWn32W|oV1nPS{ z6J~C>Acz&wqK?gWp|#C>wgAP6?DB_3WAE?Ud)Xw+?U&ZGt+-!|46;e#6 zb_y!38HX-`Xe<^pl1d=(;92yLBMC_dn(5=AVQ=`(FK7(_Eb%qaJ%W8#|E7o!b9*R^;6l zBgz?|?dU4nCba*(=Y-}lMcTJ!J6v3~rxQW5=W&1kw-5TxT*-!c@zYP8Iwi3gqbzDy z5WdjTaZ-b>TFI@B z#mq&{Ao{tX64M3w_8Wv4P4GmZJRrk4*#D12NeVS=k6clis`N2}cpiE^Y{gM(7H(7` zn=%Ero|mTwuY(?`8}vXTQb1@%i3FJwYaM+QWj79|j(PF&Ws9)eeO==s?kMvWM8__B z^JQ`JVB_24u0-|_J!ArSD(Mv3vqeydMl*g`Nt6tZcb4XN?A+Nh22R9x$ev0IxYG=P4G{%E{}xNh?ciY;3le$Lg-h4%DStiS^Txi1upg za0jK^UVuuct-L|s_1FvDjpqnc%CEU7gW^{Ihw-L4-;5a1vipvc$-g)pd(j zY4@J8QQ2tLti33@slMzu8d?o2OiYxqI+v(>4u(ES`f1g~FA&@7snZ78FF^djpRRFa z40+SGZGB>|_oH&=s*CmCLbekgKC?t~Sp^4mwVX3bi~R(hyB8qn3AN+U)*Kz}D% zkAha)@&hM}8mSu%b#(=B&0C6)i;Mpb8skvy0K15$$m>f+1!OD#??F5IJa|K!_4a{t z_1nU4B*C0-V#kVPlIUwpOQ6LjI2!a%tLP4LcwGHHOwB&4*$UFV^orYq2UE1Se?NFg zW$ak71sbCD={lzwvu0WF+zmt5gGUf-xogiMh0<*W$e5pcHhRpM(jEEk;5-r5O#uKMJ8`0QP)I{Pn$tzY zu57)%OiwSRZNkQBu>?^uH$h{wIIbDais`>jHA|k%mgg^;Yt>MynigblL?0mLb@Z*? zDs#fvzNv~(sjFnkjkSM4@72At6s4h`Omv)uZr|XsO3OS1pA&E*-yoQ!2=R?VM&P_ zAzXXcZ&`7D5w(ESgMx!wP`rNnv<_?M_Ma`gK8qZ)4fi4fJ*lXh;A=q!tJbgYVr*>8 z*^wVHq9e~6>g7GlZAOhX;)Mz#p3vTo^T(mGV_sJb#K0)KK^7u>t3uz$Qj@GmAlANFOzhisVTMQ`Q&u` zTcc95qUqwzv1)3T8`KosZVb42b0Ck@dyd{HdEOGyxMb5KoW$_gn# z?%lw$yhLP4L2^(8(zdo^?)UD_1|b|BK4#2*>Rx;Z_V75xvS#PQhPHU(KFG-EENRZq zCtIQ$dzh6)g*{$$e(&!1*%j1+a1=GwbVOp}-ZK({Mk%Eba)w4Jj72eR95LDYr0l_L zPBYdN$ykfn;VUOJxBPv_pI`c;^bDe{Mzj~oBUIE=(%9u|p<^uZ*)XLx#XPEe@7}2n z%ggc&8sKFOr+=pvjXvt$;@ArYWd}|{&EkA^&A2w7y&U>#Xhaa zH4Q`IxdFm>fwvaK4Whn}H5HYW7Qhz6$qn43qbE+d;eyac@Jl^S(Vp^E3*fcI(}tiq zZ!c#$03gFGr#XC8k9sFt54e^h^d{0+qW-6)cWw!z?7<9vM=r@U6BAhi>Qlh=P+hOT z^?WKQDNBboqJ87(cG6BwOmzMF{Ybm+PA{=6L2V`4f-;BXk{CS!?*n%oGu3ETcO{GGAHFy=?=Me?ZzpMl+-TeM>zmTm~m0|YO`f= z?`ia0(Vbaztm`KE;J@5I7?P|S_k)QlFj#k0G&P~6#g+k4+(n%#qjp*g-lLe{#!+rX~>G+Yt)w>*>C zcCP*2NdUOS4}1BwDh!mDmc0OOV~-)Nu}O}WH`yZjjp5u0c8g!{zB@!nfCa4f^6EzC z0c}V%7hBxSCr_W==SXZ~(K~rInA)@MOWfCMC=G^c-eNT~FWe_v)CVl3Z%OdwQch)d z3j!FGc?wX#XLT@q>1&0uVWt86ZJK2+M;&Ck2n91?xEM53d*_(v-(|$N4@{fi2N1S+ z78!Dj8f-Uk`N%$m#*!eqeYIL%$Bz9a&Ki0d0oh}nYKB5Dv!A&gVge!Frg+xO-PtEG^}mVifFe+I zW{5)i05lOcu3qg%(MZl&1>e!KQ^{_>{FtqJdh=?eZq8Pdh+4(j0=J<@+I{RwM{Gec z={T?E7VHYWuSCY9ctU&le1&{Jq(*ySjR53Nsos2ieeb8HwxtrBOIuqbfDeRW|3Nf? zavB=aXdcDX8)9XllP)jsfS&2vl2Ng7al){fmU{ zJ9}^|@;pl`tCnGkD>0}Db5ph12~!z`i4A$tj5^w9?COpbVha9?qN$=)jD7B+M{xN3 zYT<*vJLAPn8_jXp?7>XA2z71GTqt)KADt6#TpXec>;hL@_3dE4^o3og?MTDq?2K z!|w2W2f+a2Dx6toWPLr)k0U@Yz7scdH;8cT3pO?0OFaqYBo>)taH0QU@$$OSUFj3? zZaR7P)!b}(Z_xsNp~bzxI3`hnn#-=f*~cQ#{g}Tz@nQP!u=*mnU)4VGtQd*fFjP=| zI&DDXftH0%m+4Cp+X}e(88$E7j-L8S#bp#zb@w?*Q5i|EEOW@mO;@>dck17uvMHe3 zDa6JQyBBi*3g4=y*rki7epBE%I`P~`I-4gRnpo10M|;UQ#M25>dzY}fyS?Czl1M5( zX#m9IX5%iO=41+wH)@wcWMLXCdw$VJO-G%x+lE04ghyK@Zfr{e01?Yef@oM){rHiJ z`i+sZgH*YLc-K51QKRuzg}gqve~=B3q7Xhr!@jgL^4jib{Lq81WwhkY8(FBI12=D~ zOA1isTe-h%GkPzS=7TqmayGRGU)k9xP4n~dY43?q$T%+cwQXNlayJJeb>#fbQ=sGmJh6b+&whVY7R_*9Um7?46$k_fhu zk&zjcrch!b+x5WG^^!0}sj#o%gGPHM6+XNDa=4~uUj&Mi&;k+#hn(p9_sJ$)mz7G`P;=2~7T-;`}&k8` z2~9PqL?l8EN;an{Jo0`<`})lj8RRUhW<#AP%AT3S`&DzOpDlg*5z64>o7<|?7Q=%h z#YLSTfdr2eBLs2CRXcV)&?+b-o-)~*vD#jD%nfMEVYJpVC?x7zH{C|L@6LdB=J;CA zjh{$vuc)mwNERb7D1t~4P-a4V_#~C-( z9_xut0IjPvO=O2+?xetTt*;L%dAt?Xov5e1qOV^EV=CVFm^nL;VOv+@ z5X1Y(XN}662Q26cgfOj~%S0WSTN5N z$}jHXQGw;AJnVz|sx!p`Jg^%?h49_QOF~qp;t2l!-LMf+2^|q>vTH0Bk;#6(%yIPP zV2cDS3a`Y1;lZyObirL2?S@I0NW@6a=FL2~A<%Zh zEdf4AjKeYb+_KZ#euJnxK`eR%MpwThpSjfsK)+nZy`o=8y-90YpZ^lRvkK11g5)s! z)KvSRg!ao-s85zGSz?Cv2ffHqk~DGMdY@4D2)<4X`29B)rUDE(wqjxXV<(W8%n&jx zdAEab+q1+RGd0A>@&)bX1FunRVj9{8QVHjUqZe{T(25^UeTw73wV0Sx8f9YW&FVc0 z)=kB!T|{NdxpIVw6FqAZ@u3n{$k~w2dyy~%EtVb6V39eTFf_qt*dHA~ae{MlCvC5{M2R6`tz8GM4xVsGn`yRRTg83B9(T zTIC#rOkysb|6%MUZfN>>&AB5#FPg@%J`KM#LPYham#So8Ii502-;)kDf1Xi_FhAQ| zKj_~UrTCZf7ZgRqoSz;cPNH)WYHVdL{hfJ!;<2uxodgJE{au7{>n;)~gf!S!pEQsz z>NovaArA#VOF>^L70}8R(8-3=EbZP4#vea>)`Oa(RIEy?5dYngBV8FL@&d*p1*oB7 zDdcuZB)Hk0)pqj!YCDFsfNWA@!h}{57_n;vgrVudx93(njTAGr;7#;e=lQotCr>)e z6ra7mHAmJE&XYIgJ!hZzHh;bn|Lb8NGj=4j@7U4XLB{Qmp3Iaz5sC|kstx@(U8!L)cOfaEp|6x?a+-|D^YLrnr`wIbI=0Q;w>aV_5C<> zC00|9U1OV9>X4>pF_MPsUuiX8k2&Sh-+ib$_r2`ep@X%zwqK`Sfq&YC&TU~&#B84S zjvt2q9n!x)bbW@MhODT2(W{H^^6k)$ZzUao1#?%f93xICpE(tGxoN3jCe_!-($-}; zv=+YgPnx)EjB2hKP*ce=mj-xSy@2BlPUBO_lT?Tj33J&>P#e<*ScrC4Ac9wUUEKtO z&DLGVde3qF^Vrlp1?Gdy7vhW?H=DigP3Y!T{jOQ}_ag=Q!xQHzw7+!u#;NB$oR?4U zH+q@W&Ds+&rxq>F3ttqGR<5~l;qpsg9tAHr6!U7t=$R=w-5YwlADuBn;jfoJ-z4ZZ zW}H+xZrOe4;~KjaS8TszH^uAztQ?>|tStWhu5z{a|7rp14f{_|``1D3gaxfl;cq3J zy1{i5w7R;E9mRS}Bc1vS@l8LL`x-``?;uIcd=@mh)!VJF*QY$kb@5%}FZ!h8Rp$q@ zoo?AWPfX3RE|53CMJno*sdB@_pKpvmIRgq$;iyIle^vg{O`egTX+9e;pw*))W6_%t zmuJ$=ryLovdEfTw=jvr8k|0^uI|u`>8`N4<2wGZrr~UaSNOsnoIg7|knB<#B70F3V zA`tC8#Nk{pfalLkBy+FS*VVmP$nH#Am!0Q5U*t!yy)NbXg75|Djao`d+2*D$F7aqx zGGFI!{yctrCW>3-gq3Xa<9lz*w3o_Q`KqdN55Me#A?-!VCY#!+>i_(9$&k@nTFWTi zjBRYf`DMe4p;SV6NXbuCB$DCz5CNaVYpSZ06S6Wg7Hrt?CDYv@mw-H6SNHwh6;*!j z<{MYcL+7-?@kmZ%K=e1p5Tl|C=*sjx!j}%m{_dlC_A) zS~13!HBm{WAqgdtB5h{oU{IlCDY9f~k4On+7^O|xQG`^|MyXV)-~9^b`#Im+@6X@w z_s{S9{^Oj{^6vG1y`InOd0mgk<9b||GW!o;h(qT~|6cXuYsO?P`4qhBefm85mx5U$ z>dFwLX0I3)-}zBT(#7%6(D?P?xjNd~cJInvZ;!Hj`L2@Ahwqt3QvciweyQ&5TlK~` zpLcg2A;$8zn9^Ex$scv2R;Em!?sw1r((4W}IZO@V@0cMX%ORB;@INj?%quxM^4O?c z-BhJRhYz2}c-;){{dYTwr?t@lhG*;VCaZNG0VfK6=EPGYK*4lp5sxfMt@5* zBHqfZKP*jEhiPhNm&Np2)psX`Ivd^idJ~q*gAs%_{7+f8- zz&fJOUm3}yRO#QI)BGR?CE!pO+`2Q@clN9H7xROJexT8TmMY+8k2%s-ZJ zZD+VR(cP`Sl47}6q1k|5y*7Idn>+K*sxP8KMuFRmI8g6|;DyOW`!_q6))UbW-@ShJ zi;QyqT&Wp7u;g#&Ii@GeXX|{OJT|8L&jiDKut%QHRHZ(6!omU!*&xTju(bL%GAFlN2^XI3uVT*Aa zrZnU(#+=#9@G(S4!~LIo4nxyGW;$q7jm#bq!|lz@rsgcv%Pfa!9+7%;=Pm|+LJiUY zUUX>o&VaM4o_x=xAKZo#KL5vk+NY=Bz=e#}+xzp6pJ-?@mM`LxEQ2f9e|+a;cNp`t z;%G|2+qnkPuQ6B_v)s3A4l&u>_VUy&jJ4)kty(2sx4pl5LF~)tNa=dq2H#R_?vL`W z*w2I^0}G{w6@z<99U$tF$C*ydj%mR$R|_*tQ~TXUPZ<17S*eHAPJ$20;UK&$Lx5vt z<`GC;IHqAkCr>tou`@C-@F6j9zg^CR*-#^ls>#%b{_w*zh6IRR9Nih4@sJ6j(*(?( z^z5*VM-?+fKyV+KL;mD-^M=8l`(gNgNHH4#w-#>K{|ry8{K~9Z1M@ILv}zA^QHWC+ z==%CsMn|5)-z+<85%DkwU8iganf?0+{M#K9#w ze!K32oN9>4>mX0JH9oX%V3bMfsP5eP$h!&0W%@ZL9)k`&TYa)~{T{^aZ4oK*?u=M` zV=|6UHg*JHyZE2MXr({vjo*#rZWa(^0h4U^eRI6e)QZ)bN9VZSLjj+KrRPn@HAQh3 zWh}3r0j0;1YfkHB&MaG;**IyN8DVoLDJhg}Sq2LQy4LA2mkJ1W!JT^p`u3G6RD|5Q ztdGTzq1|um!+hmlvRbH%J`SJibZPsCXCnjWU_8?HFqw@yKh-sA9*U%J%by3+mwWrS8{WO5@z2P)#Q+(aw zB{w(f&F#ORW9#WT&F;6Kcgdj)*J*rU*<;?S6V!Ny47nSYH4(>-NU&MIm&e#s!M}{# zaHuA+xV^=A`GGm_%9ruAlb5fRcu(Gp^N(KkU%S7Rd6->>41a>;_dHV}`8@!fky&}4 z|GKA)jh)9nUHK$|*&^K5>YLN=Ak@Km?F6n$b8PS7?(mZL7&LAmZ(OB0EqnYIU6y`$ z;0#?j6zE*0Bxcj`<=m4gW2w=UQB<@_KLHOyZ`hxt|oz6VzzrUuh z{{Q?B_L(K0AkIWAp{bknxFN>mPkHTtAE~QC`zG$~)4#v%MIFF2StvfaarTyCMTljs z!}PHquWx7{Jmq8%mpIBZNaBDh3ZT`+A2b>Fz;y6Gz(SMNd^x3dT*gY-fdpZAss z$Ag^kgR=f$N)`j!_ZM-*t5$&G^loMT?n&IuCjangL@bkL>MK9aPw`PjsVffCGTQ-1 z_n_yT=}TrW*tx(A=ePWwZz>YH$gNIe4G>2A z%B+Jd?ofnN)8>uhl8z<<2v(u-x&V z#AE8wZ#d?`+OoI0e+At%7UpaIkx_qUE!u8ig>|Jr*rQi@0S1Exz$CdGf}OeB$y=QP z?x5Pv6@(}IqJ!5KNGlFdLH_}tR#GYG3ZByvwzEsvjaBr25B#U*_R(w{F@|8C(TmG-p;j%VfW4zOW!*`{k4mo zL45SHWyL>#e7E4>J@F@GzHwM5cyL|J$?oN!-vs<)t)cCdWc=4>5L|mE8HZoj=vJ zluG7}YuKjJRBvzY(Dtsk?qtb5@1Jt}_U%`zu|?a15MzD5afrbTGK2$!K}?cQBP9kr zh0kwzv6$SWozIvN!_Xxc`=+I)8jxM9 zrsU)-X0GiCnhDSI+^V14Fh%HgS?!a{`_ufmO&SHs$+M_3#CadsCj1pnfHISTT^QyD zoFb>p9oUcrZdiJ27^598P8l*J*;S*E2|Xb6e#|%$-*22|X`m0Qzq~eQCP|CTh9SK< zi_8AB+Mv(lv}BZ_Z_1{O6zH94m2M<31?F2MfsyQoJPJSllfl75u^zrvz&DkieH zei<^)Y0!`%VzWTSxd;s9K=Pw)BMIp%?hs#7@*wwvzy z@MI({An(4?w_Eq_mzT9&Cy@K=y2X>A8^28bnk_$xZ9_wE6trPs_?x=AY@4VX=g_I% zW#aF;#1(zJe)(k<5SvT|cz7%bHqNM3B823pGH^)-ws4twD?7+)-!kE;cj1FiqLx%xc`0d-b z&l$X^uxv~?M6pZ=U>1k^=15kSOs|o|j1&KlacF5Fep=~S=@2F|z;GPC#f)Z{xcxN-hB-}Y>*J*H@N^Y?s z%3j-=S}efj5wWI*;du0*`O^j*l==9vImvh4=RLK5ap=HAFC0b9eC+0&9`{R7VaG5M zN*@hRL?i2amM~E(`hrzt^Z1$lYiC#xyy(XW^J_+|){E+fs-p+8IfU?w>8k$Hwb95k|T76`;KRdj5Eq+CGD}* zPJG0YivUM|WH^~JVp_kOKRBNdr9j6VE>s=4>YtqC*Z>&ZJax#1KVl7RrY^_r^eQ%jDD< z;{yAjfIKImnQe9{%pJ0OT))h2G9(Xtw#r{FVf?_(@OUS#qrG*+6_1vRAu@>)Jco6` zFt!+(QGY&g+Z=e8y&3%+@w7331|5-vaz|_POMMyqac^>Mu6Qat7O^hQZ{NOs<(T-m zxS<*v3psg01SFtc$^@X#$dUAVxYNjw5C3L{ed+QVe)BY)QtLP-_jr0<4prN#u?1nk zHgP^^EZkt4$Ml5CjPb>n-;E(By`~9hD1)Q2!2KeOOrjVJFUdAlwTy3jg(ag*{0O@$ zGYAoPxo8Z$IU<*tgIwq`&l`=8Y(QZS5q$^4&KVN1sZetRHY9|weoQhLp`(~_Vv2F zC_dZL++NoY_fuAusT2sQ)^V?TJ3{Q=85dAkSKQHVT$|7usk)YjEyD;9O0PC-f-{o5 zaKCC>U7Ci(@tTBIyLxvPBSQsef;%N^Su1RG;L0-A*|HdD;~ul2ytH(fy81lMItk8s zV{Gvm1Nu?JuR0-LHi5xwq1V53%g}){D!xRSeR3_w;w8@sA^ydp-NL zi^_#%U$3N=D@&^XO=dr6_3qnujpO?~+U`%HH8AxAKcGY}Tp11$qj~h**Z=x>+sz`1_!!n55I!b!vO}3CmHhhASI{_w0c1w`eD%+h)8Vn6 ztg}VWBS!&d5FwN#Ha!$?~j_8M3S*tG+v(Er?3tW!g+G z$#8h6+yt^m)H!(M$kH`QJm|1~V)cyE$zn|8(=7?2ATmbtD?WbO?ExAL(IQs#wuJtl zvv6VT(z6d(%!7SXR_=K2vx)H|>^W0U*e{bjK|#TddRtzpS!{8`Ky!OGt-;gemb|<2 z8$nnO#UitA@;O=@@{xA;doos#pJh~12S{6_xM7}PCLVa{rcHB1ab!h#^=h+ zq!-OWr%&5@TRh(X>7d?t>{dC)C+{f)az>8g@gh6hqT+?=Iqe* z^%>a_kWd+U?Jc2dEo)J#g*)xaTI-e$Sp2~CO444Jgh3D6&0aM3ChM-4DOvYa(XMSq zuO}0<1?plo>92C*mm7GKqp@h4BG$2atm+>i)E2PYsN{-rcDLbGe+tfU;6EV0-El7Q zN$Q5M^wiYhS(6mb7J$#m0AGl96Ykul=DmZB8P6brWJ{6#F6XLcf%XrTHENmJFxlg)CoZ*yRE{z(`zsagrOYb#=%u1!x5$PWRQ##> z#u)9AXf4*G^Y0b>zP|E@RKYVW;LkxaT4dQZrwC96O1DG{B0=v~62Q&1m-IWznP&!_crGA3rO519=?X{Z1{M)fl1d62!lrM$LlrjP0_ z%gR$qY%FT7%4ktiGG@)BW@cvo^yOy<2eZk@+42DI9ZAIs!$bgJoEN<4XP+fOGrw{JT`jX-ZVX?)hJM`*h*_g~ zFBR4H+nwuW<5x_!TIZnn$8R|4hao<>ztR?&y0Wfo9E`1P-YVSb)(6yAk$ei9yQ1&- z-pU^?T0ws#rp=2njS|S5B7LZ(Ono9<>TPlfk4$WK?yRhmxzQv%5eYX6?%X+h-`Y&Z zq0+(PVEL;o+I2@i-rhonJ5%WHjR3qV;4)4EvM^Qj&vhOE)G}gxuedka`~2WPrtUC`S!Wn_QPxYf%pKWqqu~(AnhYaD zbF~n-k)2~eAufmzH@rgVsVjPI*r05C_iPs_`R8pV13UCNAY`^AMIHF3z_C;=i3blH zSoE?#mnWzCod<{}Z1Wo#?!~O$=)N?;4AcDa<7a!CB*RNzEw@|Iw4Y$V-ccj=LRp<4 z^B{MN*awD*?rxn2-M&40G?}*4Gd6Yqv17Jc_krAPmvn?5^7m|IIN)!Pz%-i6RhO_w?km`>m%_`Z~ANytWQ z^Jh+<4ibNQ`aQ}~6E~8~0>>(L>1BF_udV9|25%0k@!?f;B*0B${GdUoHdf5Yhemwh z_l(dPFPc0@Tn+bldykABOf_9o`HzimV2SJVT-|AH2#DcWWf1K49yF-lF4yY*GY)Su z>MpAC9Ua?@_UdhFU2836eE;7&YuxXtKMg^Djric|Ni^qdh?ygo|KX*toEu?kEa-mT zJK(v}?SktC|IrEHDBhz>o$;#H4#g0Jd)`l)`tg^*gNV)diD9UXZ9J%m@TiPeqaXWHTZYC zX`#_~a&mIA7*5prIyo;c&?WoAUoQVNE32t-&qx-?P}nl6q7BndNAI#W`?(GCv z51t_bV;t2mc=`L}i!H$hXXp431y!YrA$N%#zA|JB2*bolpMaQyUm`^|Bqvbp)2AHF z(1<*5z<|$&D(-R7{Z>UK5+%Z1pYFG1PLL!0N2Xs#%zIr|=R*x0WXWG%^3QOoYE%7K zo}oyFXXN*XZea~%Q8db|Eal8@qSxwVR6jvUsedQk2X2@+Y35OcLuZkKfXC?%7tc{P z;*Ofk^z^Y)iWw5G;Z<&|gb^?m=KultPEPxP|4lyOTDKd@=P5|q0!~)R_m9qd|HnTS z9#{E!e$oPbxP^_|%8zf6eGY!-z;PrU&dhdwEKuVq*cxz!1AKYFfV1gq*}lvidj0{3 z`^WJa{KcX-4Go5~Xa50hck8Q3lSw3mWuRzS#Q7jB@rPGxFN~Rl$kMp+)(6}8{sYd> zTf^JosC9-i3@%qcRJ-o(kLjX#{0fHeEF}>O!)U4 zGsrHHQ?YeZMs=5byvz;KHJ7IICS z+f=;4qfISpwnJIbb#1;lb(o;Bi5=~x9AiFb;8I_~aQmErN0!DzZ_h1h#~8p2I}z*L z3VCVB87s9e08CmzLBX#T+oQinDFCBr1efbnQxpP*6VS9{=S~2z)jj5IsYKJX9Gdo^ zDr$u_Z=T=rQKeec+VI2-?8@7dyQTlNry#|j_382jr$*c$su;WF1D?OKh=*ogOp4zB z>&ip*O-->6YRJTPTpji$*DbHuJ;2#_#?M1X)G4)yx>e+hJ;bRyPCSTSefz#VId zL#RJ$vFqReUW4Yo$GnmWMZW*AoA+ntH+tJDBVzj>{Oe+*yRcwS`zl(7;I*;6BZ~aq z+xzK8k_#iUl+JL``Ch3v$f_ny`t3HPUrom6&h5&h`WZBy((M%vT4zpgp80TC0@>8m=|NU4HG8{AX znYISb{ru&GSWh4*625aib>B?|;dk2H|D+k)ez-V{ z8dSnZp6c`8p+xS&LB;=@XwZ3k6o#(-QPpkn#_f_O{co@Tuh_W%xA;AUIdh1j^kEFr zqX^MQV=&;@rzNJK@C9n*g}|WuRUf-GJ(f9Tzkkauc)IMq9BJD(iNT`ON2(zJsRC1V zep_x)+)+}HlW6m1&}Oesu8t&8gC(68X>N)({MF-YvA}a`78b?*CM|@=aPMf_B4b*( zfE<#;&FE`b-yu9hL5FZE4<(DK1eMb`754eYOE=mY3p>G|fO&&u0y#sIV_dxHUNeBF z?QJPF1&P;Ob!%q_FrWD+bj^LnV#U~b7gPKG*Uhv_1r*n^e?GlapU?{X)r&E*BoOO> zrUbAQut)wLW9pN%jt|k%F~_0hqAsJk&986GTIYc#1Bsgep{Q$?VVsnUUZAJc()5qU z*Hqrl==04tr(wJ1`e3vhOr<PhWncmmd=y0jYogw<@C z@7j(sZ20KWi+tWbv_3(vU*(}=vFf5lL_jr2Z=n0TQy>U09!%wqTlM;%v3I+sI7MZV zoBIU@+NmJyH=?Q%NR_}(+kz1X)FD;cC}mLfGKA9d0Iv0$#@O_RyFcz*1Mc0s1bqbH z=tbIV`~m{PK>gJq8>u!lyZ=lnY$VY8<_ZHHc&kBMo()}u1DYZEvZ_hA>sN?xAJmkI zy2(^4uy`F5ZWd^?svaor>E`SIr0cZ+slTe86dAwgaQO$#Kjen|XXopt3h835 ztXO}sfnshEVnx-p90FyTQpBIU9wdT?PcQvfI`!#exI>3FJNrF@a1JI7xuY_k8ES6O zNoCb2WY*ObUEPrnjkK8gv}M&Y3ZD8dMlFcwifel~z@7kuk(LSvdw`>Bc5F)VQ_R>K zP0w|GB`A8`xl&1%=k)ua88}OD6;_axh=kageVme+xe#pG5VUfT9vE<0)Q|}1LAGeM z|75UR(vxg92<1E2y#~UnB~aQ+AVT)AxZae&Vfv{`hQtwZK$Qz#fLER`-C=&DYLv^t z)QPVi#e_B67*?};&LUi-CNhslBto($!W_gsdbEZy>Ag@Z!}uWe8@1^iAE60j?acME z2|1Txk)#ITbR*#k1jUb}>NCq}U?P&hi|pMa>~p^pC!#oULQxY*MS>u6LxHwPvtT+^ z1uWG(Il8MMrZ09Muly;tCHe4_ZGQR%a7L7F9VD9goj3PP)ueOtj_fq&7m`@o5EmC9mNb4x70>Pd|)#ZpB3aQMn@#Br< zTt;q@cf)1NR|9p;KtL`zyRzCCIrn`bFNlwvQ>|KNTMllRe=Pe)h>5=$o?YJyV6p zLa}?X8tUJ{-|juWg>{>RyxY=Yxt{b4%DjvpM~;lV>x`^wrS58KV+N|lkB0TV&GnaE z2P>ROq&6=IJaAxz>+3(e%p|gQ%P={=U_goJBy4nDuEUL$UFATsqX}3E>FanQdjk{M zOi7&jdynBgk+T(S(~C5<_qGAX?YfBFY++;~9~|S1Dl4H~-iyc6kBBjRa}Bn7O6+2% z8(k>jlICjMScL>Uf;eL6aVqW7bbafww+#0NEq~Whd&|X(Xmtf$(cNo z(MUoAn*QV|JMOiN$^~kOx!Zc1f?b?c*fQ*mfXc(_CMzi^lbOq@vH2e9jS_;Im`d?# zSu+*`zPp}$rliTW*DZn36eneJZmQQ5mzS6Kqqx27oaoGUmj;DB+SY$gfuxPCT&C}& z=-T@d@=;43PgykyO`mN=>~`Bo7b^Siw?}oS8L|EWaPwa64NP($&DzBSKjd!5KM+1_ z05Qpa^^O}@*j9W|)sjHjJ6Xs!Zh}_TBqz*0oIJ|Fy&eSfiB35@M(UG9T=Js#?MO1t z+AAC)kxmD3X6#H^HGG^UzvgL`cIe@;A(JP!`acN^n{t~T#Fb1ReduV5f?rx7-9)BAYERT%)tRvHHV2O#3+IsJ5EXt;va!eY?kplHaZp7%r=z)( z{Xw7FnY6Hfs6&wOTylV}_n2o)_d$Fn+eVDP?)wuhhye46)xL)h8=rE-VE|O1jULT$ zd+kY0QSPX*&3%wGhgqL+GvZLmZFD?h!GlMS4y&kJ*Apx;7oaExJ@hr>=ev;+@bgiG0mWtC zGPD^h2EuvBUBxsRK91U1IE~&VXpxbqQePJt?m1GAndgf*5~If0r}$GJA+J-_t9$*r6Rdsl;tXMH zZ)*+km-6V9|CY9o`lZ6g0O$*2fU(*m?8p)F^cpxYiWo5*Xp(+D^>IHhS#ppm#Zf#^ zAXx!caWX$FKT*qgvt7(R$)cT1T~a-_g(g?;#90g+5vo zqh%M#R>D{xGG!iFl4!93-qv!@UuB=&E8Q_X^P`p0-B3KcKfj2~PtSmaru6yB$6a5L zu3a3^yZ37M-b-Qf$(H887|E4x+7wN7CH(?=*#rScj7kLGE-Ny21p*dDPe$=+6y;W6 zNp5%zETU0@?%2~TqKZGe49uk?b6tT8C&`%ffBzppp9ckLGP&lIvZ~r%C+C zM2B-c#1)YfT^hqjGhlY_M27!5WYpH@b&Yg&FV%@aPI?jKlh*9Vyc;G5nxaq&V$Jnd zi|ch|8>T6Jx^vXg=bI*cxjpUzKaL+?@O)WW=7|l!63N;Y9F<-WA=4asn>D{rZEZ*O z*^mDGvFry=f|C@%Nnf}@n zM%xd6XK|#qU%#YMMK@68^-tPG6Qp~OmGO>}!Bg_9ixgQ$0Ku z^)$7dEMgp5`c=40DGR`SfbACveRJ{Mpm2+Rbm&U0u<)^AuBQWrTaU z_wK!W$4pU+N0F9M(irXklPIaqLI-s_Kg-I7j$-Y13( zUtQ`TE0r2I33kCYpIlyvTM_fFYB$v2*mA#eQy))KJjG?k_Y*tXHdx|$oCI-ZS6}-b zt6=HHl{=nQSFaBw`6#|(V<#upt#zdi0w8RENZ802KLd>Y7uasBmg8SyCd z!U7tu@cB&M-};k4(KTYk1J{u$fN}<>VyJMB-O>1_S6S-(nR;@MJu%3bW+N$AneT6Fba$lC7c0~nMX7A#k8RedTKy)ZqxEFiN?Io!)*`kwCzC$b8Vm*Pntm!n{ ze#gj_V_n~Ebl6GnjWSA09q_^+H@-@{BV^0*eE(g3< zM#oZS?Y85Fuo+hS4ePJ!@gF{%@FL!;ho8DsT2f+p=?Zj-5MEXOTliXPdip_C(f2OY zX;^Y91{fk2t}MNf)-yFHcS>CvH~uOsc&?X?Rg&bSRr0|31$} zw1*7II3>My=fRCEb+7zV<|Bq1l*+kf2~>}3DdEeii8X422Hgq0NB0bh$f1p9oI=}b zZ#1PBQX<_jS7OG{D*K|m!3zV`eJkqr`Af;Jyh@|bK>6@=W~jNo86-aI#Z4%|r=5RCY8(d4!;h^f?2VwuobCM*if96DzocLsS(RZ9v^r z*@v86IAl0>qJ#D5Hv?R9=87Le!y26`e=q9$>go}Cl0ER3_offkBiGR9$le?Ac~|Ls zt3)B-r`iSqEm+Qj4z~D0pBFR}k*`10*#MV)=Ta2=aS1u((92J_Yn08SAcPtUoKA#I zpijdo52=O!6c_1=ku4os#$S>KCg)Z@kpa5M_4mrLN-d~Qv`MKTlOn??F01x&cVAWO zl1D4M4#hY}Zhke7ZaPXbg@c1T72|r@=Rd{ z8QR#yC$wc;Y-r@?;5e0u(DNpqJihKs^U+KtS4hq=g6Q z=v9RywV>-GHq?aLt~BcT0ogdx13V)zhrFBncG>xwYVnrNB>{I-koQ&kj1emr9iyf( zv9;ir&K)~{hW5YH5B$+%ro7*V55k>%m#tfAkW=w#AL!CFK*cbo-J@g7FxBtB|JQf_ zUhq$I{i##Wbp33)|JxttT>8P{;_2X%CuiwToj(1h!;9_zivK0(k6w$W{xr3O{gt!tgiHrwtj3Am7bm&?p7P4YJPRo4B(>0-mT+p zAKZU-W(YU)@;(c>yq%g-b1i&_mBtyJMPnxK=&G3gb5EUBiM8o*uYn%>*L*%<(|XOb z*fOBaEN*|tBWpklr{@E55Exdw8wq3uYEYlZ!Ev!`vTi-I0hX;U|5Nfee&>A2)j3J^ zwguigL7>6NX!LvpmXNiUT*o~secj*Ej&eRtcTpbiY`)CNt_ZZUvCt$&5w&mitszm= zND6hkJoS>v-&35*d^~0M^!N3ngLUl*ziY0|br0{Ax!nK*gGqJsiR_VX7I0Nt4ON!Y z(&bk-l~%=~#B#Y^4+wvun7`$h<9PX@ZM1sY+23bzO@4`q9^S1u_9CxL(k*ELvZ7vU z>Tlryn%zcA=p-U8h2NSJUd!XR89ehRE1hV#aMtI$vh$p#*gyZbYH~H2tcoQ?klT+o zj9K%M>wUk0ydr&b$13HLmc3vI_~*%e7|itlDeD{&VsJdwF|!6}+_798KZ1`-DbdeiO7OMB`1 zbK7P0H*eO}&m8M{#6K;BLppRi`?ME_m-}oos~)eWq>&U?9_Vsxn-|^aWG`pfNw{k0 z;dvTMdbvq){1Dj=;3e+OrF~@J1SWK5=M=wJt)NulaYXFjZ=biq`rbf2<0&2Y7W|>A z$H|#bn3S~q_(TUf?iUmoxl2nd)T_7e(8r$CdoQKdp*WL7om6XNPV+cD(K5AN3CdID zxoRt=ltV>j=lv95gCM*r*QWm?O*6ZCrw#qDeOqlN% zO4g-3R;xpoHe26;SEK!+1wXsZSfQQ&N6G;Eu{PXaNgx4C5AOXss2pWQm@^Wu_^yDC*g`Dqg_FRp%c+wpmyf{@4WQx`NGCNPLH(g}3T-|xLCqrps z{S729PeW!)Uy_ez&)yR!_jPEiJ5!K0;SbXID1a30f`-&1w#O84+S+CH!1hZ26PZgh zSw^eoxNk{{`AL0N^)t^OX6%^|W0B-L!X%?+Bc^}?HgMUGzsh`QJC2+9z|!QxCtfC*4e5`RrW^)3>oLW_#D4HkkCjBK zq5JOX=dEsha)AN{%{i#gIzLyhPpuylHPgl_Na;*RGR@Dl~u9 zxj78V&d!MXDt*H9HQUuJe{eXeX?1LwPyM!g_inkXQMp#d28><1>GtmK))Ra^tGiow zIjvc9*wBZ9>2~$*a@1zh!=4L@A?*}Y4EHW?Aiuh8;$Sv{f-vFzDOFC~?+b=}G3Sta z#jAB6AHm&Ss02gVg@wjH6WGEaDqR*^f%4|Lw?)*Z@i}Vz}XhPv%Z)EKT zx(50MG@Dt+7`$l+WI6Cd0FGnnRgl*7!nX2^fbzE!7g&=Fq_w-fs;-U@H4EV_(@8Ug zVEWKk_-s3CRYBkMj#?KbxGSYsO?aeH5*PsPrza&jo)gw*ML$s^GtugcC9aUWFf>p5?Y2WGQKYoMVLS4?aYr^v}2;j-I>c>Bp zOxOZrE&bGolQ%XN{mCA$iX9U;WWVMR{*#H+O*gJ$- z#F?<-Z~16fM{1Omk5yMszgwP*&vW{PVwH)T&QLWSDrqkm`95U<0i!y0Lz&ADY9vD< zEFYvtl_Gf`s#4$TcARVxZI27*83hGr2d>3BScSi?3jEDAJIcG??%r6-JkdFa)UCf z)sKgjwElQrY0Sq=>letr#zIjW4mn+(gx6nHERI&en+UCKks;iWLcJkvWnnakgZ{*WhZ5vtA2FE|y@Cnem+{UyxLnUKHM<+vr_q9r+ ze!2OT+WpndZ#Z#xB1mD=d*>PBw&7~cTE1u7)PqFX71Ol0+X0jXg^GQO#Ui_qM zjU0RCi%t6Mr5lG9@yaOk1XT+=K}(*ccgHXwCHD{nX8K##B$Gj$zU8CN1tkUzlYFlD zUeu-MtYUfH9mvI=;hcX^n}~i)Siqd9VoV4`yz;2Fr}EgLIqH8E98@IbyHPT(Uf4VKq3zLEs(PePJhT{_!w4Oo zj;evKF`a=eY9(ykon{enW{JH6qiSHj6^MpV4&J`mr{ZQiYk;B!5? z97P-d7LuR)L*tUO|AXVlv1uO)$()_{>#2RZEkiS3$($kKhbPut0zPZi3<48uOf1w= z>XOLM9<($;h1#R!Xf{Aq>lZzR-X{aBJ0pKyUUgF_ZUr~c{rnAVL>`}ndpps!S{e^P zuf>taopWYO`DXiu@Ek6 zOY^*&Aosb@!VX2{0?WiYP)-|OTzO|+rg50jd{~3%;(?=APWLV)>Ad-*5f9w)IwhNp zB9HgMaqRrUYvE=^?igo>09w;0`;r76kH+|rO`KTbBl56ToN_pXvquzY#MYMbWFKvq z>>u(TaL*}Db1HypRiB-BT~}CiwD5oufoystUK^8dPvto=e+3&5h<&DaskvozgtU0J zrV6JNJK0B2qsR0~tyuVv<#8KNEhpXV_YXbwv}}5wg1JqFO&KY2bp5^&R>qj}?3nWG z$y;BaJaE(``WrkWFjdHN?}#Z_p2ON2kT4QjPxwvBjpx}dp2}Lo3qK17Zmk3wyj<*2 zmoj8b#=Rf(wmfz`+}237cCflG6w>uo&iQ@hW@h@8MPTncrBAfA4JGV^yh9xQEdVo) zs8W6NK5nP|nZl6dynD$577|!mvrKAE08hZ8lczt>JvEE72@^`c((v-mZN>Arl&SB^ zpbMe$>2!7S9<%BM@UsxNkn52g^Tp}kKk*@E2%z1mztwqt^K(Lyp?8$tym`5Fjv_rC ze|{yF*yD2My-;$4={}m&a4FzLnb-Iy7mArw86w^rF1NPL6g3H?@!G^YrSk~P$vSu!Wng9 zLK$mIco0tc!=?2^hL~b}EaPt*eyuAyMkrrl=nBbNqV4x{%d-|qCB2E~UO1x`x^x}r}2Syw|8@GC^TbfF% zuF1Vufrk#AllEW={RMfP&QG15`W8?PDm2d`PZO5BHGPmM`ytd$i+v0^#~v>)s>m?V zR9&#EimT;;4inP!C}z>isYBz;J!G{OooNd_7ScE=;yR#K`t);9)d#_huLHf4)0h_Q z!P00)2ne3s!<@YLx{%exj%8FDB1_StFw$vx8MF>nDceN|o4B|M(jN{Wo{OWf*my|W zG@mAF?kgnrBczOsr`JzB1wxsiB~b|_PXkwD10c@D`)!U{&!OP_vy!x_2p}SjxYWzi z_bU*Rd``2goA&J9odqcLmXJ^h87(Reyl!fITFUbQ>SGx4&QZ1y&R2APvaA~*%y@hf z8#ZHvNu@Ru&No}$gTJn(t1EJ|#Uzz=XP!vZ;#HT#!$&d|3DKnI13+~aZqHb*OPZ_E zii^t-m1zz)yh^V;J8$=Fs201W^EG8shFJUH+8aR`qK0Y9Y7E#W zSu&+V(WuGs)T1^%pL`aOagOf^5hjul8beUYP3BqaletMF9V+|t0B)}U3B{XA#0o^y z5Gv;$X|?QTWFcqRz-!Pa3t2Vi;LuZup>UCz(ZbEIZq>z-+ z)toSCalmYC$*E~y9G~K`oO0zj^;I_)gKcp3X~rYI>fU_`Q7r}sWr`OPj4+OBd4g`) zhv@*9j<}Itdpp*XQ~BdRB|1Gy4N;~5(HWA8<Su zg%W)*MsScqFewf*n2wiCBAqzZunKZlf)X4 zW;htm(#r=*ACUk35UKZWgp>Q2?9;CM&0RX z{gYF#Shcja7B(^ZWAj!svXJQ7M5f%@HcfVGxtvE3H;ZjW4re#Cl1%5ylwwxHF2}zE zZjE@??vmzntT@m^H_8SVo$ZBpVZ0;wD3f#oAE0pRpS%z$NmV)No8|MV_y~$8TdmjjBTWuRv-V*~8rOe+^ z9qJ~#i5sM0u>SYPCvw=WR;EvoY50~{*8>V>T<*Z?VSxJ~IidR=JYdWFthFDNYtNFXS=PUd+b`u4c>jA><2VZ=h#6hS}nVt?jf& z-{;mSs;C66R}z6FpD8$5#bq9wH{qEx5Q{)1qMFdCJz`8L9>TxZ0td)jNEu+;)X>yq zOjaS??pO`RT|6YQnpd4KNPY+qwulz5O!bZ{Ltjv8SUenfIbF53oj56t`q zlD!QNG#EfF4k-lelLYjMhKltj^_ZV*SU)q@~q~o7g9LlPK?KDdc2X!a9?94?t3ArB}!@%pn2| z(jzeEs6}1>p%Y9Hw?1U&ulU=G$VqyEt6y-_GI4eDK^V3kKG2ESL9@ycu_AqleK}hNq|R4v558Hx#GYQ+y71 zxlnF}v#x#DrwgQT;jmB~I1nIdgW`x~OJL?4w)RL@B-n|Ve>iCy@y-BU?%d}yF)*a+ zt0Smzir6(D6Z%v97$IlnJ+CG__)C%4C2DiDx`l`OUVK`dmmYc zGPWrJmE0K=L&iR1ITazOUr`obD_tsejrPycli7b(>-rgg*`r7PfGtJOe4-KKDt`U- zpL-6!J0Wkx6(MM!W490C&kEAb!qU>6J9glsk#1E=`c)n`{K8~ZI2GWXsX21Iyy$Y( z!kB}4YBtWJXQfA)+)irR53f<{u?`n&wd$r>o_$XNtG!s^)%mU0u7WUbpQqdL z-ocF`f>i2mT{F`fY~(je{fw*jNuys~-Sgs4mZwwOHThVrT@O>bAU|9NI-pYU;hUMI z9}A*16u%9(x@o-hGhQxsDPcgfl4BRpbRbWzjWxxo7gJk|lo#(s)t-`QbiStMU%!5R zESQj%Y z!q4ccf;&+^ex@#NoV@Aw_cvFYNsbA1ON4g1u9 zrJ!II(YZXgmM)?z^M8A({97=g|9)-%xfA*Q?=SZM;eU1yeE8nee+d}>zrA*K(k1`> z#R=0p#Z3S8#hc&%uQl?YZ}d1IY07`UHvaK{F0u2D{GYq{abf(QwfONy{Qq*h>|eOt zSV7^JBu;QGyf4Y6=b{O{Tlwk%j#{CsQhe6oXr|));%i0qrT42HIamf>rX#5hdpO}{ z3QMe$X*(hhpt3lgOCt)u7O&+F2P}mGJn}T@2ZKv8Eh$(1?z^evjRMc-g1tc4#wIP) z)5}608ndZ%P8(uvyzT96-XD`g(R*?YII+$dRMc@g?Udf)%_&xJfa2J1IJgXm{uWz9 zs)2Rr5J~(F6_zCb_17A*U}^|o+#rA?3M=;?ha3+P;~}t3!vt67*yJgjUi`ttsSu=} z!|?;DesigW?*DHF%?YM!i{fnmo<15ijgzXl7r5(Yack-gJF4mG<180PU-5@0%YM9k zm&mH9M6)R|>#aH-!-3MhGiG5+EgbzCM8lJ4+(@WQWIUwY8qgBmV?O6YK4}5?IZjd= z@DWL?{Ei)~%6~WUOZW@$1M%a55=pOq!^A?S0 zc^IIvx@`+xZgbPNBvH+aH;niLmOB&<{s&F;!K{VG_Q^ws-mP|Iv|$jeNHEY3mGql) zX~QT@;Ov5cSt>+zL~Yg0=F1mT;PvFMN}kSCXV<)ek~E4RHL|Wy(3^OjcIgYn-Btka zj;JZAT}XHZS`&|9x~>d&e>13P`7Y{Sai@?bW|UeJW0Nzz>1d*A%Z+!mk%L|`YL@|0 zLP;eCRCMiZ``tV##7O2s;r;}V=bumW-;wN)*tfv;h!D)R$wiYYlWq)I!1RJVY0i~& zlcC2@Uh`09Wf~_A`TX4a+6*}KsyJQ_f<%Q$T1(a)?!;7 zThK{y;5vp( zVVXZf>+k!^)zjkLE3H3M&^Cg55_CKb>rTOq3QR#jW31A8$HvSTfQIL+J;l!Y3f$sS z3aZVfh1fu(3IG~7Fm{Y7eP`k5GGb#g$nzs6*Tnspxv%hOH8JT5GV!APaZ}pg1yKkc zO|Hiu|h*H>d&t7!A!!U~lvkHc!l z_a3n?3m=Ji(g?iSv;SN|O9X&heDzX=Ev;}5;m~Yl%}qOlGVrw$Xv?&4HT9{{)TvWD zKR!aEe4_E={E^Iv$(ODOcK(azi<2L*yDcTx=kk#tRUVk7lG1q(&^3qi)Z!!7Q^kiP zZ7XlKpL|IavpmCtTnxNMG;Bja=RJUIs^tB76K>K{_z`ZH_c)448@8e>4t@c*#*2i8 zL*>aeYp~+DM2S#-#p$AYS__K%aEiw=xgN|OB3Q9kBh0gNaw5Q2wodOb4_z{>l1RG6 zw9qWU^&u{ed@7a=>`Vlh$~CUBq$GkLm?Fz2g1RFg!EW`HD--b?$BN}f=h`FOn_}O{ zo7f^o+4ILAcjbfj1J8$ud3EKA337D=LjHW9HEP5?(b~ku?6oq{${IsX9B z+H$P78vR1Eq>UK5-75*y!IQHy3dp9PSIHdcnOw+LD_5s?Je@GaD$VN}6lMjw)J$}v z`z)zM#>>3|Gf(h;m~=qg;H7V%gg)l^;EEY7dTH_T6Vp~-i&uKV6$^hx_A*9!mX8=? z$nM$N-}uY!-Ep4^5I18bFQj2|bGQNh_R??=K~510UYxX2!C5-dg_lfN=J(eD@wpH& z|C;-mF3r8sK0vC0}P5O*0M88;tU`NWt@ZZ3G+t4TYZSF zy1Y44ytSKO_=@S~n+Za}e%@32>7uqK2($)7cS`%laQKTkKvGHE9*%K%nQ)K~<$YQ> zF$mo$Uk5M|eG_NT&8^{tP8qbq*XO`jVlX<6Fn({%G3sIoVJ#5wvhaxUm3)}d3E!B? zzylr|org+1Hg;|<-}GuKmSG6QuKK*xuHM@4mBMO!B{z$6FqDX^R=4Xv@;iBu?Ow^! zjE5hZR}#~kq;JYIN#W_Hwn0`cPkks-V-G-9%L;Hd_c9Y(D-;+uW0QFHT6hZujh?p^ z*j8ZlzFsl`x~ zpdtF*`a@CDAJ0=5uWb43)(+uny^=<)09F=cJgaZ*^?GmVX&t-sOJ$5}Xg+ezLnAKN|IKWm{@`({$=0bT{jZG3)dze8l@2H z?Gna7Jqe;qyax7FCPH`HiC%DxC(-I9ZG2-{5)g@;;WK7h&ck(+^5Zmr?>e+Qt@@r> zeMP>^g?RGNknBo!oYj}0kt6GwuaTb#t9`L9%IxZC@Fg+d8{7N#-;rg?a!?$;WK{1G z7G>zti>FOTENP>R$fMV;x^eogQ1*I* zWYXos7i@jQCg=qgdLZcg87HDq%C{G{)ze-HH(M%b7wJhPhd!TClddqE3^HdXdU9xVe zUDx8a)~6yd?EIAs&J?NcEK8GRM;Z2f$ER))L!zD&F{tK+y%OKnw_ zhjp#ft3)hrwKcz2ybH@@S{L7SyjS~Bm$$PXUOV3MqcQbu4mFsk--d11AD-cSM_T!4 zY4f?{fcei|Cb+y0OjoH5s5;tyZQG{8`ySTSgszzy;f!t@H|dG3PlZDkq519IZsw}G z3n!lHxc8(w*W(1@H8DmgeE!)+9l*k;<3@k1Afm$TbLI_c-nQt2G1Qw0-dDwFP z>{%tX!m+vwAC58K@?^ZTaf06E`HgD=9zcseFwzGY*&AE=qz@l-k1*>YEYyu>G)lyL?5X98(Tf%Be3C{u93q-<$}M zkB|W!Mm;v#$el{F7l}2$O!*%PXrQE)Nb>%+_~FxQgKJaA@4k|vvflRwIo%WF%BCfs z4W5U9L24sD=-31YrWkR-mHW(6Ru%RdX||NUeOXErY^Wp;UaW%~S0~f8EDiXpHl^x~ zxvx%3a8=g0hb3}rDpg=CC{>l!iqGIY>o;t1($Z%19&k%qyve!b=w@xJeeIKLQxio* zTBO`_yN#f`vA=S{I#(GS^;vRbo1;(+Ydj(QltvUEB+o5g^IHs65PdXue5`pL^C?@! zz*os@#h!{QYf00FFA4r-?9O3wW-N0@abtMlm!CLpu~sx-@pD>3SI-*!?|(2#27;NL z__j6sAfG3gKQhyD>$;A|p@wQfU*@Ack<;yB-TYUyU}e?*CTI#JS5oTul((&I`zOPE7*2Z7`j?Ve^IYz5$cNe_lMMu8*3mVf`Llg0&`3I5Q{TT=P`^}MC%AiQ+ zr%K5zp)1dA6+e~yjx1+X^G`KSfuFciVmp6q?gWCP+ONs=wXzwlG|!hDNpB&sE_mK_ z3=FVC`d;lu!^j|Xjw-k&R z{zewl7a>9*&54muehf--A^9TtDq}!Vr2jLY{Skjt-9Lv1Xubd8@DWej&vwu6(i;;Z zx2iv4cDOD)Ya*krcw?v+*$K@bR81-fTG1pbT)0nYCIdN-Tuf2+;~{J>^}lgAf%V^Y z&;CU6ZH1-I8(H3W8r9oIz`#Pys=bu$8^_J$anwHPu~7EN>c`^W{nofk5*9@ln+V{J7d3O5z=W!b%_v`pytkZ)Al4sjB03 zBp$1GSE+=da!4vVVMX&z>~}7*6Hn^&@WqeCr$^)=5SeB&U125OD~MgRre-RSvN>9@S05zWTU^wV+w9_-rqazm#cH^qdx6mUE=-c9 z8WENwd>66it7^{BoT|K}&mamxXs*CkO(jWL0El84o#=EtYvZkYHt(AsRHIzsdJk7{bRMio)4s_+x&BWnvSG86F)q= z%{rjJ^?_DiAN?zeDDf?!J**E1M>@}Q9qeHrL`m2DLy#2;70i*u&20$R!ui_T@cHxn zd{c=Lj->aTzuv@Qhz_| z9^o@`me3*1wqOCo09C_z7<`G}5_Z^BntgS_tba@#QjZ7mSMqf9;;>&!DaL?UNu3d7x6-=^P*YyYwIhg&!^+PL)0p9X5K zRJ15s*PEn4ydZ$g%JtWVanGMXCynN*n{d)a265CL*FA-E% zEXWPI%J^{@;Au6F4jL0(2D&#`p z*RP^U`~_f{aac|@XM49e(ZEIn_!aambgn?ZoL<>-44tsrFj@pXF#&27IshhCD5WiM zi-9si({Zw*dj}|giif2{ovAiaRUX-+2UX%E0&OHp7}0;8Fkv|(j@AdtZ!x7N=o3-3 zi;o%Bc@mW42HF&g48bBjMM4n_9ta=$AQYtXo%INU$QSu{}xg~^NynFFnve&S9!VYsgSjMUju|X6ncU$(1)?6hKVkz;0kniIM_L-Y2 zz8BDhTsU1QYETd6q`NvhI|uR#aosJxU7tT8JMGoegFQ!Fh6fZvE|Dg3-n-W0@WrDR z+i4*QtBsqCFHi3v*dz3S%cwGPdxgzDK8WVRgbC>zs95L9c+TC>;_g_y(|2T4th>SB zhA6X&re}*qw@KcEKUf+s1Vvy$Ay?-CJP%Ern?w;kPFX)d|x6W$cX=Zw6n1 z{8#;YGuFp97i4vUxkB@^zk44t5Z%V4ZkbT-0FrnAFUOWXlEYm0aY zh+c9zcKDJ(KLv^51*EWPoa5;;X9f_^C((L|{;lP7yJ-MbYo1d{|1jg}1+=KZF~aYV zClxc)OUR{Am=S@60cCEwYXh=$c-h5l8mO^q!Zy>62FFyi?~8zPxxLibHw|084B`S6 z&Uso2W7oeBj(I{QxpGWPxPz-)%;EC#zB`<>>sK^s@vYey9#K1Jw-B-*`b4quDxGi|eYcen|M;xjL`9P~Q1WUb+h zr_!*%u1s)*;+qLyQS0)UNO(Ciw6KHbV5_`$g@*V#z<$@@%E{m6&52aiUiI>W{PCS9 zio*kEvN|jb4;`4R^e>J3*q;E=BX_IWsl4Dp*v>~i<-Yv>^Lp}_1?ScUX@`%(cWbP^ z>Ul)$CPZ*mgTb*;fcq~bGCPZWx+9`ev;C`~k!%j8n*BwL!0T05#CTNzn}GX00ap9880d4kGYVXpSMKz%wGlD z|B7xh`Mq%SwwCHPm905!|nGs5QL%jpCr}S#~lAYMC-o){#THdScuNQ zo=9;1SAbLSk1%f%>pAIv#Sbxhd3OuKW+t0%qM>JVN946k9yJCS`cPNEcfETX7R!t= z`F-Zae0)2$Y`MoU5E$}0by3a|r!1y#?qz5Z>k$beBOllOrCv~DBcJpVAwLiXOL6(F z%uyK{258~ z_dn{o?AYb~H#65BP}xvseC1Mt*Q`wk7VkN?;qbxnA#298kkMG&v)i&E1NRSXdGPV> z?+|7igN^~YppXTMB3@9XPHul559@Nk!g33P_9 zJy6?_A_48i@oR7Bpt*ltpsSkz17gbr8PEK#aFt_$?E}23a?b^NjW9KNygVkh`@STm5%qMK?+_|&*#EGk^G=(Mkl2&i%)rH{Hz$i#t zW-6vGUVJ=t8wUmxt2N|`&)B#=cIxoo+po6siz{h93Deol^-ufGoj+L(`+G8uy}$Ii z;I-noNJWL(G5`G2mM8V$9>$Dop>=-{nyN(&1vp}{Wx`2&+rV{6)Du$u^ww5=?dnmj&m(DB(_`}aSB3Ku@R z8ULsn6 zoG7Q0J@gwRQH>-yy97el54dsrms4(peLLC)KC&LG?;y`+He_a{#^PK$Zo>Xb0i}cT z^;@?(@hRdV*6xr-F$w9)O1sVWP4sQNAoY_Pj(dAQ&+}xAeGR{FbH%?`_TxLhO{N#_ zy6oC}OuAiX?Z0nJ>P>3ubb8IDk)8hD>Sx1}o;+#IF}iu{*0SMwIXO{Bl?LsN3Pjpp z1WXyb7Cc?Z?L&LBhQ7)0;lq!~x#C}ZYsGyjIG*BMn53hS{ewU&{wvn5S(Czv_tA3h zMQ!@wh|~Aa-6#n>amezH07ktbrEQyOIfDgAeem<#f9TLsM_*hrA^+vLfCdZ?ElCU< z1q75;x4o3JtK1kM2FKIwxq%eAP7bfaUlE#{R;%@ywg7WE4MG}QG-N8{c?K)M& z17xe1@UFHFoG7a`Xi!T&-Pm^fGESX4cMxi~h{{NI(w3X6a_FnyL3Hn4(=ry7Gl60s z2cmtR^uG_~#*E`>5dojSew{XNUSO9%g}tdm`|QwdukdltowbQwbkF5iQO3YEnB|)@!PlEi;Igz%w$i%8>C+m5ktCn zAi3I>zaNjAnu3N#U*1v5n>V|>z2%s1I|#iPhx^eN(+>=u6;OTPPS&oGhQY?z(>cpML%OuTRcgaUn3!vcLZH_D=fMZ7=Hog!wHj zD;w!h;B=bZ0BtDqiZ3j10yoZ;)(l3RzaO~-8?C#ir>8H;<~u9HnnkpF9f*?+7LLY+ zYrz_8>8nPydOc%MH$>J)XkZQI%Wxcgyhwz?X-SsS1g6Hg=voME?QbxT*eDZSyeTjF5w;~a{SBj)~5)M zLonfF-o#9JWI?RGQ+*MKna|gW>7EVuyZ^m|e_G*pEE0zM8q6UK6Vtltl=h5Q9&f7M zNn!6X3eF9~K?s*CEji}3#&>%jnEqH&5Vgt5N{+v~Wg2sFvA%xE5tVV{)`8Muiyyi+ zX)~UxlxHA0eB?;y8gi?9>HAP~>356=#TliUcInb(K4hhi6F=1p=0~dd?wvcUsNQh7 zjhF4yXY&Krr@u@xUA{a)c&r`fpyWI}a`>X@vbzao--{67yND$WlX#g$NCD94>j%oUH z-jF$}E7z|l^mX|C!%nw9rdO+$EnDVT#3OsbhHK^S2lkT8&GBn`B`YUam2gx})#GFX zLWSMqJ^9-MM~|8jfN8ZiDHADAIUR*k`Sh`OX%EJI+u($-z&{fgm9cHxw)M#b2DbmB z#>SZgQ9s9+M*p@}dvp#ZgI)C`b#>)6AznRJE?yi*Wj~h5vR3aft21LR&BqJt(7c)+ z2c{>%JbT@}J9ff^QKOE4^$WGaKx3OX3%`b_5Kyz>omDM*$jiUp|8&hig9h!ELpYSw z@T2>0HO^yqyo&)|ZYJ!=#P0dO!k*OM)EaSd_xYJWpc`=8(W z3<-D#`)MD2|3htr!pBEdt1S+XzagMpb%!6P+WFErUt)GcLo##0f+G>&*B)|m&p&f6 zj^JuCg2ezz1`9WC9HGkRig2=s=AC%aIen>_fhAQ3?aTmgOGahqN^84Sa9iL&qayYd{V*SV^L49qD>R2kvFW*_M9 zuR^2{gM$ZuS&s?XSH?WkKeHOQjO|5L%OQ~ z=X*z)fX4KH4Unu#O-sAB^WKtc6WaN5^;89y`8I;AnTTMt!$nnrYM-@pZ|i$!os+Q zwSttZP6)htQyKiSB!s0LQ6cm_3d*Mmnx??1y0do4dC4?m;|mn2A08FMlPKAIBmUJS zWPB>Hk8Wr}#53Za&GO~FZdlPfiMs#m*RSegO0|;yx4yCQNQQSlB2)fpy-UEYTc@(> zm$?);Zq;$I+EM>#lHs1$q^;y^U*k02^T&_JHB9TQq@jW6kfM_HpZVTC9Q{S7%YclKU;)rVolE?T28NkXMdXjI7F< z))jAW?1-RglGEL(q1Qgy-wo*Ee|iUe{w(~u z-!`ap+5-w*iyCluHjm8`Vra>i9HLQWO-($d+Nt+8q}d+~BlkSW{P;vsjV~$_8##ln zO`xSvz<<5DNY*2cV)N>mu0frN(z06H$30nNcf0sDjI9J_o?1`;iwpdXCzvAb*Tf#p z*{3=rD`agus)-i;`}a?t0BedsQ4YjfyGTx9`eL}KiT(QZyZU}zV=jZ`?L9m^mRNE2 zN{+n3+w@nG^5Ipk`(t7zFVoYTSdLw}En(u`ydmB9dU)LRefi>r?bq~Cc{!+Kch4U3 z;og{)4?2~WmPR$Co;r5S4Yj6jc<)775k>r`d(1*sis68>VUNH-QS2qC8q>ZeH_k+Fu6X=e0|U~={QRz6EvQ8AKznY#dARG6^Qong5}xPu z*~AdZlxfpiu_@FIz(FcFKyuREMAh4nmzvs>Uzfxyx9`xQusjtoIHn?g58!zf)oWBO z<{91iKpS$xRYajkYGfV8<`0zn2r?VulaqA=8c}lU5Fc&6aV$QN2KGfqYeSla;30PE z^y#VusjB+=S<|PtzLER*aZ5TaZlKWb-@kX2lk0#JG9upDPenCNt2v$$?9?P{D>nMZ z_cq<3;K;LdNFdYTNoEf=78nu0Jl)Yz-R3vMMhH5qJS*`W!G){WuXjf`#2~_<`WC(% z0QlKwPS5J$xeE~VM@97S*H4*PYOrF(AmAcenJF`8wjE|I>h0{XF%YmX&fmCxuGY}N zV9Jp0A<`=X2Yf&m^nnt!_|@u7n+_wszXId(3?hX=W)^%N#%0yr;^qRqM5Ht$STuGi zAmAXW?gOaLCljnUn3aNH?5K#thY#aiC84~(4D1tC#J&6X7q3`xkldocix+{Z#Advj zT5lwlPhn7AKgi0?4wb$lujv@;wBbfj&`~^&Y%Om7>}rEm_S@&2c&pRy`fCQF3`_N%F^EVzdU@dz24jT@i$UAbt{z}ku9$DdXm*k6*Gn%cUdrzAElO$LVo zpE~|TQ94Sxl5ewJ*O-}kPyp5xk5^Tl23Nw2WUIKf`ZTt>*f>KG&>#3G+&3UV(R@&c z=;)~u5u<38D}*Y1qGH z(DBMYUZyKo_7$_|t2*y+38I+WfB5ip0Bs8iI610;6^GvS`*bBK*XH#mpAH{aILFvq z&1Qgzv4d@YQWr*uK;kFcYAj^|(2 zt@;byPsKWg2-Rrq++ctj78rRwL+W!oHQ((_+lOKocp$Jz;j~JxGHI2GjhC($7$sRB2ZN2N+QqTnwq~J;Q>2$o`{8 z6Cf4T8#?4BA|Mp(+L97AY<3(S9c>4ECo{k~i`shAdGZd_nxnld3zsv)Yzvm!uutir;aB z$#1(q7wJ!3YwOFkIp!ixuv{mbHEVW50eP3Omm#4c%$keL9rJ?)+my}o#=N^V876sT z8H9>(+Ho>B8~pE$c|veBrQzgqc4~Vk0g8(6(Xy=SQY>>|`i8exI}9_~P*-^_bYo59 zrT&iDY6HLKr|a}+we0n?pt6+sjMPqlHPC>0INai(2Q^)O;}tsY;Gj<;?i+DoOKv zsTR-jq=yA#AG4i-QKUuvoTj4#iSTSEH>>!<(A?Nq2_6!KIkR9DHf7$~rB;8S$FOyyHH_ZDWQO2R z;lJ-PXpSlFF_{&=2}6$c#-V1NOQxPXdbA62;3^aOnak-z9`EDWC8|*up68;P$%dzH zbdcM3(F_)DOHnVE~zho>l z{W>c5sPY}5>V5n4(OJ3j{JUheuHFl4|C}%VSZkbS^y=2!ojbJ~Og5x{b$DgG^p#;w z(-92Y>!V*>2nrfawo8okd3X%v=o)5^WLa*QT2_AFX_Q;oo*RMfUC!RScW+(sF}1Ib z#+GyhbG+V1S=;1A`Jaq(``$S4FCh%JPCQ~J2*>KFeW~hiyn6rsoLc&e7Y7}-t@CRq zj}mN&Z?JJ>Lf?|oQpF#4lAbrnyHnemL%h+Wz^8zu<-D2@_k}2U)y?oH|_Q3Mxv@XA9IJMiV z!4}=*b*T~d=9!t91;|mB2p$44#Vn4Iq`m7b4&~|%8=ggCXR|XUBjfD$4ILz8p#8AH zj~+dWTDsfce*~JG(W)~?_8Lq@{VwS8<=yNRsrmK}y%-i_7wc#B-ntQ1DN6bqJa=MW z(?-AEzLaCI-2*30>H{ammJJ<4L+{k_u2mn}n8#2-Qj4UO-Zjy2J%m5;a1kt}~oP>{L!4zd6v6gFp>&iA@W3wz|1<)ZQR_ZbMQ(pk;8{ zxl==JY&91}=KGaq+#3BKf6U9&wr+0uJZm>^!Gg{R!;0>8)T#`*fB%zJ?}Nb;vyY^_ zy|W`!M!y@eC@*SaMf_E&u+TlP^M{6w4Q#66Z{51pwQt{-=T{z&E8)YRZMZZ-OY1^?i=z;JTefa( zQy==^!On_3ue0xMjL%M1ijEVX)YcwA*U%AGn;@^y>(Io83z|+R`tJw|4t}k1U>Z7$HVvP8 z3dfneZP9a70`NnuO8@QG?N=8fRTqfZ3Lo|z_7*(E93%MRAr2|p|9wauZL8l}Yk2C< z+Qq(od!oMZNJ{)5zg;hMRCicZ-b`LXQzPktqfC z+GG;iJR11ORwuW5-Qstk%g-H2u802b7QX;3!weVSQ|+_|e0az~o`OC6)zhc$E?9@o z^E!8K^BP2h_`{38czRFQiESz%QvoyLRo8zXEvX%1`Uo zr%xY2ECRfCfu9AWctX3wX;>epU*G*x&3Qh$>}s9_d3u)X z5+rCg&9D~@ttx;yJdj=QI43OdmaYq(Jwy=7Hr7lk=;qIbuQz*r@wRO)q1$%z<`4H~ z&6u&8Vq|lD@U2@Y0EZP66vReW52)PMs}APH&gA}^F6ZrsNUzx|4z zNC2vza$Om7Wij7rXCEr8`9A91Xako;!fSuN|G7&K^E133KDfGtWk z*UR4jv&Hi&p-@`^4+z~l?>gv^g%NpS6 zc#v)|1j6Xbv8K2)kj8`VLS->FdR?fHuD)mD;{YAR&1=AKZe&9-xN+yf13*JtGWIU$ z7Dc@Vh>pT9U!HID1!;;V2Ps9AWu%UM(+5o@qHxsrp%)WH-?OBoWu=by83^bDHMy6C z^bDwVVrLN@%wELQwGH=}U+zj3kG3WG597+merY1={f%eJY!;MS#L!9c@$tEXp^o~b zNq1emV4n9MI53q8l#ic~T&&GpKynDGFm#-IU%Z&>UkBwE9uX1u{(T=aGqc%q=LV#u zm6x6alOwqe;ph0*ap-~40uIY+?@+&W`?jCI{{a!?h7Du*y(Muxkuq*EU`JyPX;4~> zqa}B$rRW06Cp7)3pSz{%Y5N~PyX1iDSKXXM2&&2E>p)3W(|=k3^HqQ0x`^E|(3!g! zxmyJo@Ua2|OLg8^p6cI2Yys7c3=LHka?XC*JG??ijTk`{8tt=9Pfss-pt*uj3}6U1 zQS|~$5th(LtTHk(g3s!e^{k>KmW#d-D3VG_wvq+Mjms zZpjc{PvV3-VTtuFNoneqOZ4><>6nUR?mQU}@U{iH|F0IrBxmt>@pWBK@s%JFCYCc| zHe=DEt`*u$I8AYKnt0<8ckB)gg%_{Xyck@M6q~Wr>b@TYF4PWwU?+qWfs+Xhl zMIC7qNo;<;yckYRwS*az${0Q!&}}iIsFa0co-h>8b0#A`Ev;eoU|Sp%bdAi+{()3} zd44U@U*wh_?y+j5CHj*xb9Q;5Cb9n@${CFx@)9CLqM^2|1SxVC`1U-PJa+&`MfX>d zO3^8P(1dd;Ex+6M`{bQH*QzYA@>O!{f(vGnckkX6-8=AfP2T#4fjxe$?9{$}LF0f* zgg{T|0Q5XK^nYoxsh(Q02ny;$`M#6FOH|#yxD%koDa zM3KhFPfO(wqf6?e?a~Lh*yHeO*_l*1dMIUVQN?(>cvozn{NGjVT5Wf^S-bJ938g_C zf)>XxWV0Py|NI=LkrKsSL2(*~X6H#?Q+vY>wNzbYBeiHKXWrY`;}3fds}eGzJmLL?^1`N!((VAt zm~D4lO-u$beC5IQ8h$Er0?F-oSR+{tH`%rtt(^I{7+1LATKpj0(oufo$dQiOO_(bP zdhfn5-Em-s{x4a+d*MA81nTzH@W<_?q#ncm2+{*SDnvyv_^R*sxx24U@=^h`f6y|S zI<*D!FUokUBKZ-jh`AZ_|MzrP(XSB_9I?fGQPhJ6(5C12Aqr^UWn5bcMK2b;ZP?p1 zwg!_BDTrE+GiT0>T|W&#jg?+IAtB)_Dc%phc5Nur-EjGfGiXrKwf0NZ=6ZXvBZb0n zwEBK$B%1pV9^8#R?vly>q@<)wq4%PKZ9`r;`4h38nxJF;k@kxo15qKylOgD0lae~o4D9*)D2jj%-DropFg)@yc`Mj}xie~bk*!_9Hd9uZaWJ~#b8M;ddDW<|tqEV<6 zqT8sRYX3d@Y6UrsZ6gs(yx3?1Wor`!sZ2$8_5F|KgInkQQTMT_Zb$g=9?zmtQD_n> zpMt8p4H$5g5}rqifTbhvd&9fyorw;jFB~WTFk{`ujRyoi0b8m&Rt`kre*D6P&O9u9 z{@Oscm$mN6oj$u_aqwJbG}aARizl6E(9>dZgQNl=?!J9twJd*K0!-eye}4vrXW@qr zyP=4ukk6D$&@2e{PdD=Jo}8aM*vlwz29!K{>|MOELfVLqu!S%WDCZ@_W!b)cui$X{ zIV*G+>BfT=+sjv0mX?R$aSD0=>dti|McaV(h|tOqwJ4%cTI{BoUshf&I5cx}b3V|s zB&F^Ll%1;AF|5=AyfSF20@4koj zhh|5QoP0hXGp!$8GT()oFHG{CERXg&ssV}P+|HV0wpr`eU41;yZxR|7p12nrtht*r zO9?`^qHH=BWNgJ+#umgG z++bm``2hwXJ%uuakh0jwC@ITi*|NQmDsedv9y}mI2AsU+l*Z;;yimi#!zLr|lQ>%k*lHfhZN9eez^+k149yKMWL?p@ELkjO2tO87l`{nR1d{qULo zoEurMQ9eegg5JncE{&0y5ug1{jBAs_B#(m!U&OqRnP9n?hFe>*bwcFWwN^0sCpA>- zBNyrExkpAu7Hq16SSq{_{`l$B8On5tZxl#*-4)CI=2i?o1VIPic?kdNbPE&7hO__~ zpfdw1E5AnkU*Ti7=vzs3GYWDe=5Z-fkV;*vtEsNm;WZW9(v{E(o0OMz&ytT!iU!Z-trp+MR^EP}E+x}dd2YrW+Cucg+^C4Ldp&{+&pa!e^jmVG7DE?0 zMUYSD)y|&te^|O>-4}Wwq7O*gDrBOPC3vlE%Jm`yx5aT*#--RS|w?2OU4Xrriy&T3_IcH*~pH>5=u$V+He82*Z-78EMK-`Jb^ zeC-g`e}6g6ADRcTjMMoUKIfC}$CW{0`0(}&kv`^iIhprWqvM20xeAtN-^;R=n&~3_ z%Bu(`$JqGzR($_`(4#^_hJX;-^#et( zkM=+bO_4o1Jq8K>|DfWC5xWdVn)!Z0?tB>`MXiy{->ZK2fMZ+xpOPO}23YV5)kinm z+Y_^QQN_A{V3gQ{Z?-MFr7VQ{CF9n=QJLY z(6+-fy0_I|ZV43fNwW%-wRTDJrMs++eeK$KZ z;>v0LQQ2OK8_6B)=}@b9Db~~0Fn>p@O*sY*wG>*jglQg*bL9 z2s@CIlasg6RA9}>Z4180sCH0zF#=^IeERwXdQ?+MR`1`xukcsYvFIJIOp{DrsO(Xl zqUX7j-AqHeci}bhN9TpwN&ei*o1#jRdNwSUtU>%vpltt?u)@HA8}Ll#YstP*H=~f7g@Ad z*g?LxD~N5;$5Hp0vn?%6F8yr3OvZ-AF&1}1g>H=Nc*Cv=74JY^Aq0e9qyILdjycS1 z`ZZ|pv4zb=T%UdqR>!vm{zdIOD87ER$_v{5G&)si8wsqBGgqx~_rFKg_a1@@zp$1{ zb2K1`)pu$Qc zhvvqY8)|Ye^Kdg9G*f_Xee!xsAteg{HZsRQ}De=u`Ku>S~LLKfAzTTfJ#3A7xbW*6QR&U`?AW$M*8^d zW%=uSRQp2sDzj!>*V)>7H11yAP}y^NBvW9hG&ME5%E+MTEw49LRFKlx{IPvQ9?S!g zZ>iSuzbcYvkf>Td4wFC{ZPkz)n-s_s-hLA$D*%tJD6t7&Iv!nL>Fkv_{Zu5(yYT^Wjz{U_ie9fxW}a3*+~dHPD+* zU;8f;5T(&95%-lP>5McA@5mIqg&(K=_ z_u4*-ywY6aH{W4(!SW$)7t zBLIbo&wEeCYCJ$t%%gUJ(jWwl(CaNhwKIsHO8qSEBAIHHg@qJ7TOjx)v>tP73wTbBjlagT?PeFXucA?JU+$FPK^Wz4bMWB7Rc2;w ziNE3pg3ARcQb)GejLM1bQA|7VCZUl%D9N{M*>WnV9E2S1RPzr6PaDFe!OE5KX=$g+ z6Gx61G1bmaMPe;)TH9cP*7yOhj&ZD6rRrS2MT-{~mVs0g>xS`-LmLBlm_ipN%I&!K z@{@NS7Nr$UPl<%9!MINxsqG8fQaH}s=V>t06kOul_wC!4e?4{Dw4w`}o27uqJUjg- zu5RwwA6qEe#I4+1auFnd)20B~^qZh_^}*JPybHN?l=iAT;3VR>ET7-M2nmT@_4TV) zmHGbbSrTz|O`r)FFbQpj7w08nJXd)vJjh`bZ?D?8E!NXpk4D2AKK(_~X=JPjhsN>^ zqn!9}y%2~fQ;nZmNxGVkSX~w2Qr`QdSFgNjq^KYU&O$Ac9nJn7_s!&~c7-owJX-X0 zb*Wk4qW>>5^E38>mh@nJ?@R+*llfD2`+Cr*7#U|DC==Zzid8vEow)ZBWzyVyy2f5GziH&aD!R4PPRYvf>i%@jdN9E?^crmE$9Who_ z)mvz|$&OJ@wAQ_tPgCZ6OQm5omk7eDEEb*1P~a$w@6E7m*c6Eq*s?At7rjfCHqmE(QKt;Os1WCv+!{m+xohjLF(32x!a zwQEn$zmAP{Gh|JZPUA!F9zyRcAL3JGErT9U9&(}iI0&lOxYp1j;PtbmOJR3$iob&e zaPoXDJ=Gh{6AW+smk#TP>6Y>hX1zp%ApGIOyQ*WWexlDTC@VXRh2gH%vT@;@NKx-0 z8tQg90udQXBu^5W7zk7oqfU6d{lMOW2&v>1sF+V`^0+Jj?=~C+KNZFsaYm*&oQZ?OF_fzAy^*n*?Ef zghq*E3P>h}V%xtQ818{Yh1uuz#r`*M#xeshXs*2SPVuW~3xCd!nGA!`rY0xGRi0k% z4~XOSb+^+rw^4ZtUx|oDw}XJe-H^T1<3sB+8CIIbJiw|XK=PXhRx6oRCw)d=r{>+1 z7bOREI}H=2q_rOV_Qk(>(+$R^IQI{KB1T0rvhw1Auhkv`a|6!3Q%5xgaSWGg(Uv3IB(+kzju6L$N%QQ_I7O$!!ttu05Oi-2FL4$i6x-p5-6{d!c+2h2-eMjcaovXu_JkA6}agHEq>GiHNGo$2f ziA-$u*?>pPvk9@|=us<;p-YA6_wCyy5;swb^q;gB3K=On|Mw&@aWI7wL-4!6=BpYx z2D>O~z(tZMm)YX1d?2xkSc{9^9ft~&%&e>yl8cjF#fh)ew0OY`5Q9F5{oTT(hfv7r zL)t1Zu1b0~KwHzPQ>Q!jCAww#F{XDWh=Uw+i@Zna1OOYH!VwkIKkV_Lf#zTFwmDrs zYbUgr1kUx7$t5oj>yyX$NMfXtZVwHyX%Le1YJ{SZ+B-87QIKfQsJ#YkWYu$M&P?=r z^a7BOSq~9<@;ya_2bVguRXDa}`+0$pnDC_1TgAW&Q?_OeEg)=y7ac9V8yYH3 zIDfG|TT4qY*_u0ldudf*y$k&=%lwENw`x@oRvUzrfsFi0fN!2CG5gaddkhe$V!EVt z+qQIzdP(x?^Z-g-8Gw!YNqkZPL;?$1NhqxjgQOJ@!187GA2~uaQo0lvc!)+$0numo z{o5vw`cU!G=$XNT$P%sOyXAUg{B|ahl8T^QNsLd`@^|ck(^KH+h+XHuv}v%dLUcxL z(^4`8aRdinSA?%Ij>;$1`0N3vccQKoi0Y%uc7`)Jtf31>Y$e=GE5 zOy>EkSD)?;8#+`4;I=7^6dkFls#?7k zLeZOuofl58cP7e-(mp>M8aiH_h7(R!IF!tD&HNUv+RpxT|L)y244bb17D`Z_5DoP> zHl_x;`&4Q0D8!6Um>O3>X|c)2*H^y6;oxf7V({HzsIk70BwBAVf%bEp-ty&RxsB#% z_?;T+&^US%%`=+kBYr54PdN8Z5MY1N_f<{DN(q2=?c~04a(?8M-BrK@F?GnD87XUD zk*7sv-dQrR`+-X6%F9BSA3_45wpcrv4ps1QC8d)G{iBV_sKpezA6R$sBvQPJvT_<@ z@lb&h2|WV!9Y2%>7h?~cF68$Cf!j*Z#qLvWaH8`iF2c^d2Uh1(H_c3AU1eJy%1J}f zN2oR>GPGuRhMRHLArieTeks???EhAoz8868pm|#p%)ua|W4YAFPM&N2xX@1c2n>{rMHQUI_0ejz1JyTBB2Zyh}OS6t>Gc~{hzRz zf(~GQ?mZJyYP?&Cq^zv$4(iC8H*NR1Gd5NLn4HA{kwh7{vEwmr_6$UNNi{4irL1oa zI;CYJ`nKM2p8Y27rhb!50n&w#8(^-hi|GM8W`x1-`t`O2xTx6Kg)B>=MPZAVXI%}@ zC(H1WW$F9-i2e-}!s_1JLL8t1Y;XOVN}tasi7MaISxV}}&kGnwRfv*Ag6cvr9urj{ z)SvRBBob&dgD~s<6%JC8bK^Gd#!&o@ubOqv@TRK-jvbrtPf+*$PYVD^eV8Fm_r@me z@d7&Aep-tha?fAVp9AyEbrI9TFtksi$+34J)R|O?ee>qH@x@*eu5m(xa^PG#a48I) zc_((1l3J4=iIgKN%g`)}&V7U1XWBc&$CK|(8e7c5yCTWxt`M%5dg--#=|0XPb-zp3 z<&2C{wam7XCK3~YB(dq~y%@1L=KVS`u^qf&#ga+lU_581;;ME>pWOw?QJqEqB|%^m z=E9_5daw)W71fHxs0xBsRy{5Og-7Jntot!JA(5aRm_2V^(GdoY;%Ve%y#|xyR?RkAGAqg(NZ=VJ!;aOjlkm#WV%C8@b!nEi&jQCh9t==NZcqqnaXM>AvO9q z!zv`{dSLR@yO~0brV41UyJ7K4#4jJy?eJv)BesB?9)<(GFA&s}>I4MqCPCv(CH=4%kcSNILmnQgrIuXd^V zCP%pva2E}y&E(|8-p(Xh!_dq60A}-ieOJd}LW<3se?l?PLIRP!n;DYGN2n&s8c2&` zH2}>?k0sJhL%JVGy;R3+iqbt_QWUnm?F-5W{wXbC$$DILut{)NYE<+&g>>Hzo)LPr z9n}HSNyo26MfDR%g@)nEur}R9Y&o`_a3HvqJS%34>FFi{C$K?kV-#-Ng25v7ZDq1R zo}Mqb$E!M9GhtN9;^_BUur|+aK!=ZiKN>DQiwVoI)k8_;ytHtTX zjZv*ywoHMw7K0e%0;v*snB#pkgXYUKdQ`&wDzbNQFb~svedF20uV23S5vb{ZoY<|W zwD9;OmUbma>wljl)I=_(_O<@JFHPsVPO;gBC7%`yU-A@KOzLh~S@(VdR?dEn;%WWH zK3f`g!+j0SkwZYO47(Y7k3H0BRvb0I$=OI!=@6+gB-EbB7g2Th%=X)mvQMJVs?FPe z8c2F)&+EVnX$jGFCQ(*URtFDC3EBp8Fx%61uhq{aIXe$@$uT z5~#oquCA`!*~{>90!)+1a11!YMTAF?!6NCHp?U2eRZ1S1?H5{V{LMIANhqpYS8hvr zbStjp;P+OFLXSaPOz?c78ld0_k=Ll7i0dJhnDFGultIUbf8TVpYjxpYgOO)VYb&C!Tv4oWvaUxipZ+FIrgiTY-P%m;v|p;_paoak2Tu7sW8jpQ zSHrv`VkXa=sdHgYv6)Ww&I_u;mR>B0@SUGv|1l!}r-?)|_tK4v+e`2E=mlgIw%y;t zi@MPDa*R4NEcfbY?Kxu4r)N3_Oi>MgILOseX7^XzXFPiiUmj#zpUkz+03TlghWkUwo;{+49SQ=7<2OEM_MlADcezcN5oVlh61WM| zvXNOCVc^>uiCnZ}=a&IF*EGu&EBr~n$ZY0AbHC#d42}4M24%D^^Uf!( zBElCQa@#*1pOs3*@G?Ez(Q}NNntpIp^}&pZ)_b+I7v?n_%0EP1p7V9B(JyVI-%fPl zzdv{GC>fqdsi!%>GAtXkcnx~}yjOcS!{ORugPdFUn=sATiB&ycadFF#@bE*a*K&P> zia*%(Hm&=y8z#vDwj4u(cY<%{k$svNw;5BXS3VP$BCSltg6bwUaB;*kyKUPZH0)fp zs$gs6EaT_b&K|y>wX?~*?(|sG=l6A2{ar6t@E&k>i%1Y2sr&kZM*ZCn_VLQ8JT8Yq zC!VUX;}`ypypB+@ypWAPNLl8*rK9S{e1_we-=MgSe4Z0mfiII6R{ew>TPN!=@^SQRJ zV6@px^S49_OEAs~(xkw^!USw#RXr*Q+VtZn5*84EwwGLRs9qP%qelddwy1j2G{%Q( zG?QNs@XfWZ{}rBB`SDTAk4Nl{jBcnY6DF#koObwKiTbFxGZ-5?{jYQQrPEOtTk+&C zdwytMgy;5!@0#2HRM1xc4~L9%42LNymL6PtYk458;wyaZXVzhs=nhsOkQe)3*(?#h zFg%IP=6D%y|9U$t$DZcS*J$~!T`#e(rRv<-%GxJSFB}7mYn?0lqAsky*vMzdv&ns0 z(tFYd)3U(e=(oVIA#=iVHXkCy-{N$cZq>4RI9{3-m$0RU2oTF8h4H?yOQb_so8R;& zL(3W~qYPsuW#vV*sM0|wr%#@|!kML6ueTy%^EKkquzm3g@yg@KpL(|p79(-H=lt!d zdwK8PHm07#oNdimPMSgsK-dLlm2VT?^p6sSRh>hP2aW*3Irvw}=rw}ou#*-Dk>%N?n;jg-?J0zum&kWuEeb(=1j9uTkKH6%ZKO~ z=Xz_;Vz{T5E=_O0P19c!nw3hETG&%E;dPpxY%uMFho-I-kLs&^)NkR<8hHr&qccGJ zlEp!m*z!j={5h?=#pF3$gVYYA=7;HaqU?i5%iazhV+tTow&lyS)M9+c*F(QBAUZ%^ zgR>!oJReI>G-9|v`-Ip>3s9SL0wG}laC3Xq^&tqo#cpAc?Fpvyu7ITc`TuVj@DV;G z7JGVE`1YT?Gv`Xn@8o6_P)Uv9xOAGVAGha~**jy0+-uZ+7Kk>5mm12a+~9>5CXHXX zuYxcY(e#H}lIz=Bcx2|(YvmTzZ1M4C70S2hCN4*C>{sL8PUwqEq=f67?Uf&Fi)6dm z!$S*$ZX1Mrcvu8Wf1lDrDY?FRy*bM%vD%(_;uKySB}0U{FqC}ZPCWS51bf=~^U5PD z#2}!P?q1_LEGXg^^b3mqUiee%;E2980N-NDYRPz!6&Zp06%W4}f4=<9AF9Ogc2M;p zUhIoP+YKD|Zy8!&w(rzp`urVfx~As}o@{F4b~w9SykJ=ksr2<@?un_ zUB>{F4Br|UDR~(W$CM}6#I}+SuZa0se0~z@@aUuVQ|He=92pM_)i>vToVGhxpl&OGA(gm|nb`$-dC9cU5`^?Ho2@;f)Fn{nLyFLxj9(RL1%cgH){1>Wcue4d*Q7Jp7QoJd|f; z!aL(Brm54XPyhI3x$AAM=nNC*1z6&>nPkG5+4^pQ+MLZUc4EmxM!zQ3mwx=BQ!$2; z#nv^QLd)w$k?T%8Ud6tXZ;7nEdAzk_6}w3njqUl$m9?|{bx>@bkJRY8-rU?0p(Hb^ z4?kpbjKxI2w?yuMgTt7ZRUFev5SCj#8@MbFH53d-o_RTGr53wTzV*-tZ1ll(3VaqE z$*6x2;L$Ow2L07`EPZo5hb=g_CCz#s==Zh#?tk}-=3x`C58zckdgcRzkysM{SYIT} z8tW&sh9|I>)UY8#KC@r#h5i|`MMTA9Hp6^dE85kpQp)g&B&=P#dex|2mSF!596L1P z;Cl+;b0xJ*jOXOaHNt&FnyuZ-HsNW&z(+$e8tNDeFs=U)T}M9ok`rN{(Lanzxc+Qg z#GiWTQ8a^r%VJyA$swS?+0#5aEuJ|uVQa>#=g$G{hekU`FY{j_mTKpC`m&@&sn_Pd z9)46>Mq%oY?xY6j%_BD3PaZI7%Z7_eQ%znjyDHla}H&7pH(xL?0hn6r87%qF%< z_3YVm$n&lKg~An^t2zm_-^CC*G8tfmajPGTkN`FqW*u@dZoy zv9EfTNXoeG`SanF@F#&C)&9DpP4huo%|$2LN9QORxjUob`WI44RZp@360%-dW{#7FJ+6c%4&OL* zL4?IGEXqqm)%+!|Z{$D>oG|}+@h)oz+mfWZbV*kIMqeR=S~AB|5WJiOBZO*plO4qN z-Y*MX-{LQq^IEGebM3>LWDTv~3tURDNnXkyDXeY9&ZF>KS<}tTu517LJeXB-X}NZh zfXRc04=1UPX0iu{#lAvoG@EC*qmewqYrL%zIbpIy%+&!^>IpazW!l@BXqHY&x`6iO>VtA|jwuL(Nr;K2dE0G#mUEYMIut*|NFUwpBM zO_snMVcre6H?n&KfryxFA;0nD$4SqZ0rDC5{S1}wG|3`kUkyOR%F;0$#sgfukwVNaK-$ z%n4fK*!*!N;ScA=RKHLfaHKQPqsKx`yRS-t=gJALd} zf%U2sP=Xfw>>k2UJ$hjonl+9sm5sH*u3INNWLt&#Q!Na5wNO_#9THv`7|&V4{_dXL zy7|%Fp)aZUI+K@dT>D5k$8UiaV-?+W-5X1VPCjpEgBAW&x*n^ImS^ViCBz!8^QLJx z7)cTfC0||CT6qFbkZU(@4i}FJ!K#zgbs7e)X-XaPSXe0z{eYc6R$oJ8s;!-ut4Rf` zgT|Mv^Lu^&Vw;FhXOx0^vnxZagWjiBvo^)JF4VwQ zTI_3D>S#?GB-;LejQt5TmTUV44nLZAb2}v!Lgt|oJ2NG-WK8B=kql90GPQT9ROTTv zRb&bqna2j1LK%}eB$+bL{Ev&?|N4F3THjiq^-kOCdG7nZ?&~^-<2;V@G`AlCeAEn_ zbP8uW2~=q(gKZc+fSF*?2;&DZqN6-xx^e69>YQzgZ%FB7RVk()oXL;8OZ!g-oa(pu_x#+{1a0GYBHJu$p*&#Lc*_itZ>pF_~lTx=N6Q1 zE})7sZo5?iO|ozKjGpOsQc>{*Px2XRIZ0^!uBC4UM%veOaK(G3 zJ{J72m}UOyGykg#jN8bmD>;hY4lC^-0o{I)S-<6Zz~-{>Mso&L1GZ+!SjcWr#&}Qc zs==2}Ey+f01nULnp#Fs&$v==2&_T!od)$n827?gD?3YAr4z|ahJuMn{`(Dn{FpzxJC|~+V`yOG#)bfio|Zn-EyAN=!G+*5hNH8)7OSklEr*ENzY5{+}uiR z%Hb+Va71hD(V~{j?8P>2L|ku+jsvh|Nyvoq(8;|K6|B^)PwO#n6_ph-UUG!@8+xkO zku(7D*R4Pv7yUUZu4)<@f#~WQ4~D>uInLV+!?|Bo$BH!o{H5l6fKho4 zvYWgbf|gY6SuBW~gX%5Vyz#~yh(iK=RSV?8Lo$sebug^8c>4 zxS@1y2>h%(GPT4D$%*gZpOvpejaLIRWhCO(QFf`i?A)tD2D^J4Y6&^XK0G5R~=7{< zRskuzIvDC$XpLewSNAd`NbDydp@}B12?0R*MedNo5nEw0(`Mk9``p8YT!bM7$C>em z(OW9v#bIQL(yiihT0F{Yz-;`gKMdfOyz=mmq`kg>1CkX1i!YyQAE>CH-b-SN1{h1O zBFpU7sSk&&evla@5@qxZcrb-^_FPoa2P!Hn5v>Q1V1N@enp$(>Fm8<&#^D6abI?&J zk`-x~Jxg2x8H_<@LqaCPg<%WOJAg+Lg>L1FK)dqG956xGM|Au?!(KdoNhbkO$cPS{ z-`WWm+zEkT;*o#3DQu!pQjCz~k&lBL^%{G@6*Xf2qe+d4fSH zWGVom^I-J=>``MZ1PW4gqK==$iV$Ow^kMaql)L`|P+@0CfL0CguCDtEt|uV!!`jS!OLQ`~8or z|J9YZ2Dtv;-%<2vep|Bt^IMO>=WB?}|M?x|hW>9n;{X0K<>4BD_bLFKp$RXeiBwZNc~WHq{@hFW_vNL%-gSS1 zH_9YV`^$f)Q50-~+|?+%RV>U9Zy6-;^Kb*|_*$|%HRDy@^fvsyYc$Mwj{n~r|38^M zk%jk0`Deu>a45sOMjgQ6CudaXYfV=_M)o+FNAULf|lRjtL}6lyy}iUhmBTNHo> z0pVJml{Q#DQ^3r~gn05uro7~@sri1t&zC2+OOcr^z;Y1e3BLQZK}5W?kUZyQbMb5#Ra&rPC_+=2-3 zXO;^j8>FW09h(K>(ej=jpOdVBMMGN~f&iN#6DFU7>Piby6_S- z@W1QYJ$v>Zt-*oc4vq@a!?tS}_M2!Sr2?A4Hbyd8MkO7?O=mpK*l(r*k4Zv` ztdnn_T>OHpA`!DA=_nk@K~yO=)u4XY_y4UyQD)l^V@;Ja`=HdZAfiwNhb@ojPuO`f ze)ZGO(T&>LuTH!V+OuQ^Rc9MAYQ;&GSTSHbFQRaJxT8=PTcZ(&e35P=>SKMQ z?klT^4JndL>wxZn`WLCSSluto^W$gLt$Qh^vF4^!w# zeC#euY>HO|stTm^hjB$y19=!`MFuIO+JTuv&tT}e`1v(1BxxeX1FBF!MwOvzZ-%{E z`SIgqOB&tId-P7ddw@V=44Q$sE)CqXE^^n`=zD=+kQCCu8r4KBX@`;-=aC~Nonw?) z%5QHMHH$1}3PWXPEee4?{(!$meI%j*LNT9HFL1o304R_$t4_|1XK*gt!uwEJA+s|{ ztptRrb*XUEqI;1N;>~l}E0AJC*CBG~afhzyKCJR2q8ce)aYK$OjXVP?fhV#PKsCYA zVs%B=Xp;Q_71QeCw$IlmIUy+lW5ZKB!ijg~LZ>AyE4zTGVDH5x5*T7ldl0)u5L}Gb zQ#INS@kjEBD<9BRN&jnsQwFNq9GH9Emc+XSentztU4bhfwv*J8!FAFP(z6TEdj78w zg6*K?izz8)Uj0gniU!CYU^^KQH1&2fL0(}UULqS9czmiu1*ozpuV_dN3;}Tp#1Im{ zhmL6mh-ckzi^N>^O7CRe>#sz%~@EONQf#*-Y;kEqUt+c|N`0N38ehr2}LP__hXtcvSPg zf-pxqQX>kRJ>EWw6pGAB1lD%m8A$-H3bq=C+ivcXg_$5AkLA25DkjmGJ1n*el;M80o z=43c1q@77!E07h{)B1zQ%*T^Lq)a#^s^v-hdisH(oT8c1W3-76po0SB#VZo_PXYY2ESQSPOW zx;`nB64x^rq-9W{^*O(OpaF8wz8iu{a^$81xBreE$NTVf7(w>Dc>;*xBpRp;?sA{~ z_m2co8hZK^ycttaQ|7`}$S6q^yTs9ub@k^7X%91x62Q2?3;?4Hkdj!RR)6o&=x`!) z7f~2KWhg|p5xhwA9g-wSR!IIXOqc`W{){SpK_t!L!>`DQF6^h1j6pMK#=wutB9Nup z!CWBuB>FKs!?||F646@G38kX%k7-XrI@nx1-gvWZJDCOwn#I2^eRG%}o`TeUuNqmJ zQik7VCAAQPv#f<9464&~k`X}}_;H99AHl#!k_rQHDvT6CU55|H^^lz3set!Oc#&47lu^9-r| zK!CNe2CPSkA@e%*JIXO|!rYr6c)QbvMdrQBvp=K>())TUHnrfI2@2yH5j=u#ul%k8uP)4GKYLse#R|B-f*h zO;V3y%g9s#0_CT^#E7tW{5AoOgma(<=A@PldbTEG2ZaTe&vdvC4SD%s z<1B=XWL$6z|Mz(KPryC{jp3k1sG=6l%7P`9y!pM_fNsGVF=H{Yx7Z)}X0o->6y=

eE=<-{orK##%9q>;pr1RBefR7S28F$2^1mnJcyw<@}?h=eH=ob|0YSOqv#Y_ z0eM-R9qRQc_7d|fdj&l|pTWGg_Tn{13x37?j^=hhM6Kr(uA~rJ5Zs4BwKmux@}~H> zN;u-uw>@b9_~Q}L;aRf)ucUrJ*HyjKr%$uSo@hpY^MZrLP?JM&6&kqup4{-BJT8AL zr7_gNYfKgTa5cjeK_bfP+%Y)N(Sl=HIeF-vYlab%xaa=sCUM}Qly~2FvikW_P1wsx zl)v>H+OGJBy~K9icLwl>N*k#88t}`Ljs>Nca~bm^4L@PSOUuYOi;5%ZMBVA+iPg}t zfS`4yXE7z3pg|H#3c zt>(;QQs^XtNxdb27O81g562>@Ey8O(4gBB&Ms40q$F0J!p5YEQ zvV8t6i}3edU|ctRCcp=x*a3i_4FG1TVI?8wfb@cl5ncAiQLy|Q_HE3}1!qc0K}`_R zH%UUtD;8xF7stfZO=O#!Ar9fs5Yl{o8CYq-qBbMeT=&5Ek3YI5b7qlWW(XYUyGF&X0q zSu~MaJS<&7Xy|dK>$p+xLH~NYmAB`cZ9IE@CtH_hlk*F8@(_YtJ<6+rz;V*dWidsJ z&!5g;Q!QFLnDWplLvFv#I5{Y%e&=XiGDZi~&irG$m}C3x&fxOgL`Y|PMr?0y=eCcv z5jJ8We%+1TJLN0KF1x^dQ19N|)p){-$x1|N?x79N6iG0uF(H>v0$uyI{ z{#EXJs{)8K0_O9_0Ck687}#fw6n{Zl)<7Pn0$v^}h1&@7iVS7KK@`stXk;(-!6`rd zfj#YmTt2nK^jU9tU6kJ?JQ`B+DGT9H|L~swZdGe1i$I32{Y-dDtGxncypE2oOE19uKN{r(uw}as}AjP*2_+@9W6}J&9_%m z@pYe|qA0wAG;x5%(LqZC$cS(ChyBEvY`y0<>HF=#*56qO`zaqV!QgR5P zFS-KVhf;k)v=;Me%1yo*i+!VHy(0mD94USI^cksB5KcJ>zyWYv`bn|%} z`yZ3mF3QT@972n4>fhSP=XEP)&I|{1xYXNmVW0QYCm2d3I9MedaD9f8^%`{N1msx4 z7!#yfhCEjmHSI8AJ2OUB_)y6Qm*r$Gr-c8JoAS>DcU=&>Nnx);DBz^sJy(kPdX4BM5AtE= zMS+ZU3I|i_iJPVr+5d{3tW#9WxQgmloy0QZ^;Y|L`y&p%%S<~Y;tkO@b1hD4Aa z{_h2?LV7~B_qjdgUii*0gee zq)AYw8>n>;wjcWCz!^1`6+e#x?8{zu3nUi-^#gMirI$5b+|oWOHTUPcyCT?~)lm|iFVYa$kV)$m53n*SOuYxJ>C63H&!rCys`rV8^B8yLZ0rvie;F^# z!BO%4C=Q2M<)sOo_4i+E zF)lg`4*S!)>*dW|)iU`J#AWqkXTR277*P0{Q2!VIIW63BTo7;f^$&<0sE_?#9lM`L zZ{{Q)Sp*fOa0zznDJ>4n=t@1j zGo_cRE&g{1Yenmclu0eyQmyAhU8|L1f6bgoP=eVlA+&Dm2t0hZy0!BiEWKy8@x!ET z&TvzfD{RWMB70CS4C(Hp34^Tj904jkE?4=6m)RJth)YkOOR$bC_w#b9wv5mHF~1X&c%STx`vd`OMbg-3);{*} zn#dpnXZb{J^@GA1^K&NdY$O4pP|B+;%bkuZPHR?JF9z~5$M#bHXiC;AOB(AKd9Y8K z-~ii?Ono*}OCv(NfD8f_<`%8=EwK9aXfn^Gk)4FZPBV$UGs%mj(ah;7;4#Sd<`;W!*SAXxl$&;K=k5F<*GqM~;z z`egM=8hgJhoV=PNEPU(cgQhb#t9aBh%~GN?6LTz2wI7w1S$2DP|He{*%2@-+a{%9G z==Vn{)&X&|`tfZ7>?dct2bU1G!p;X(hRj^|?KRS3bhb{nU0xi`jycM~_-3j_^yZoQ zDXoF>JsdpM*=4o6-uVXe1_kp3IL`zxkL^k@Rd+`Tocypnc;`DImzDHGG;Yhj9j)By zcP_^Moy7g-0>>97MUEtDBR`f;OfB4M(T#SUKMxwJRDbOlX`BT|=>m2YCp`FVpW$N= zT+Eoc)O%O&yK(Es)aI}HcYnyFs8$fM0vS}Kom_bK$KMs)YDt_y|b9;1+Zb+C<9 zaMFPV~&u%FHll}5 z(W2d1AP%r)xM!cLBA4(3Ki<`!P&7r!JzY666;S$oTM zk^|TMyeoHl1N1RqD{E2+dNz&qA%UAs(Lr7hm(xJ zOlstetIr5v1|Ulm3Mev}S@U(;2uF~KXIeG@0I#VXJf@j4l&A0V_Tzx9tKa3p3&1)} zs}Bx@80yv1Y>HD4J1ILqsct%Xt(iu|>yyc$)wQ7B1+Tj5S_HQ)eF^?V(XMPUdXS#$ zRr79LifWKv4uBf)In0ywa_f487|@e?hpx*qhMS>DE;3=z$|Fbrz1Tbd?es;-j((f% zLbWZ%;n`Jk)=S)hI|n>_Mg4jD9~|Cz_8y*I!*A{pc4pUy-yo%2isk3Mta20mG#w}Y z{U^qL7%Zxo>ET$2a@MU&k*Gai8O)OetiTL{Ir`dAfMN75zfNm&pK8*61VyGfkO_n8 zu^bV9iHpf8HFKXo*LA%~>4!}#S}~ZP)-`S6LsULtV`QS3TFhG=$x}G{oZlsfYwf`$ zsSboH3%OSyJhJSj<1c42FGZlRG0&@J>C@-7Lxj+RvsRthf8enzt=aO*?xcY`tse$-hetf2kL>h6Wai%il=@Ug~Sxp zM5OHZE*$_0w%pek4iz>a?b4XDtQ@)*e%8x65AqC@P?Nm6DQ?H=*sKrZQtz1I31tLJ#B9qyu8ns-`W%GUfaC@|s6PoADq@ST2@c}Ah;`kpr26E0BR^H z1WE+|qLBUD)G-yB(X=yIAylK|id@aRmUQR^fW~mNko*LrVxG~hB;EW2W9hp|09Z5pi*aS1PRAcHCzBTuV~U zCCXgEyYQ77k&q&5Lz&K*SH=70WjMRCRJ1GG9qDRPjZt;mIx}uY20b-yJ67;FRy7oL z9<(zbZ9EL83fLTAVi~u8rdqUxgJ(q(e!FIkrF$B*v_)3zm;TtLB3Z9O4x5h1#jTu?x5U6>obO>+`7u@CzB~~HvOv%u4_vOG z3Fblc25+7q1FTWZc;Z{G2iLpqV356?R4NVA#`)<9@)|pzaQ5HkRxj$sTl6^e8#ebU zetala*D9#PS&`4BKo_IL>Yhv26}i@(tj!UQqOpM%BTVonY6ic>QcyHk-O<=M0V>(g z2W*kQoDG1}+Ja2=)HnV<>M&u#jZ?-CAMB0gGj_>ePEc3tEUJrFRnrNxY9>%45F%=L z#)Wxm^{tn9`qmqpYs+LkxuKo3VP6t&zt#lG44nM@4M{?XZHW*PvMVKpAb0Qtf41jm zL;X+~xzJ`)8|oXsd!vp~@}XBH%6qon-#IoM!uqJQ_}8=G_PZbd#|5z1n{oshjmIH^ zdZ=tC^j^YeU!IJUAQF{%sN4E%(>V~X-rg?dJix-iD)Y6tGuu4-8Y6-R-7Gm&M~ry1 zR|*JBN0?}-{b2septc&+ZeYvXng=*?$6>+ahM~(a&y`m*LVbU3QcZh4Sezzx58(dM zW5-%u&UrGTULs({JKQ#qZ-yA{`7^@lB*muC9rX zsPCO`&J4_`Xi9u!;c&M?EyZYfIPjRE0bvL`8vycjo*qiTt%Uk=y5kg+d={10TVg;n zM>WwDSV^vL^Is2N*|~9@No8PaanZVCd9)=c|1W77wa+cJDD8NxXg{jTU)~YK-?~ei zLfL2w76d3FU>Hb;6gHtwL5S!u!V^-cp)Ga|X`6mI4wu@Q()!=Cq%Tnp$!-+8ya@jUoCoIG3FFGZ&nPV zBZ}Aj!E26aH@OE~g2fu(O2CIlLxOfoEF1Myog$aq&RQYXqetUGyIlpZWqx7M@7 zrE-E90yW_`Zn6MC+6vrI95nZqN5}*ciQU~K$iG!wl%jYuEqMTHJ)K&&KDLY@WP0f4 zzWT3%LUw7Wz^eoCnG0Kh8sG<%+4bn&=o0?(E8%H@UO9*orxr9~MNJ1lJ0e1Y9-~sZ zAkLIy<`ykXnWjDYcKsjyMslQ}_H-~uoH^S9uzd=xNevxfF~kV$PD82^i)DflTrfZg z(>u8j{H-c!d*6cYopJI7h4k60E#F9Eq`9KRZj`$k!KWo!QKUoS%Jzh)gn(ZYe#;JW z2A5=XFi1vSVE(U*dyN0s<(sTYyODoKkiy(c(Q;sF(~nKnvg+9eu`FF5&aT#UpoqeK zhsj>a#^HhdGnDWjCFB$XHth<&e1*j@;@6Kh?Rod<5fRZuKKGSb*Y8SOD>iJA+qZi| zfLy>%rhlmSTq@m^w4XDpzv*}7rtvC-b|-+v}&cL;9=khJA-NtVT2Kz*#9!%kJc zMA`Dz+~=Z?pR?>ro{$)aS~eb-qWARItYqlG2+xh!Xnke-moPCu@g|$0;^@lxMURC? zapod8E4*=B?V@%}`##4$7qxq#7iz*VQB}-+&OT3(hTvAwAD^LYa_I=z(cs&&kI#=5 zuM};lr%-mUJ9G-VG6e?rp_+oPnX#cKV{>g{V~#pp^+X@f`2kqb9cCZE3?_3b#ZY#} z+Vs_KY~iaPHBPusy{CwJw0JF6FjFPfUx zRi9GaCC;wxQVy$Inf0wes|>YAkN*PAtBRTQn?7(?-0jHSGwj#$mCZ^w>d^ttJsT4Y z3iDXcM{?Km)77E_YElYRDF`zZaWd^r=tn{n43tEU+yn?w|AYx(paR5Qb+JaqN_QAD zoE~8oAs^?4mHxLi+sjuv`CB$ev}^x~!`kHcNJDOPRH(!FyRAB#uLSa1aUSvBE z>r~qGH?1eX<9}dc)Y&y}lt?{HD_|Cg)bc>XBe0JiL*q!oHe_8>Fl?Nsn&lF`fhKZ$FkzUX2f6}SMoFAP>{n{r|KVU66Gx!PO z0x1C>t_2d?yqUoznk%(H4dwV|1oB6@%GYnPEMBrl5X~P?+g%brUF@*oMh~@ZF^{`- z-n!x(TQ)T;L?CU43c|__wOQ@COGmIUHE={TZOj{0Rce}}Z%-~SsD<#pspY)){Q2`q zJZ&Os#F!8BhglPmm;LStARcudio#sNiHD`(Vq`oGQ!dfN{}OJ7sN%jt$BPk$Dd?wu z?ZqLQql+Ji{*egIg){ViHpuH_$T-iNnl`X=?A~J8k=>~fV#r$^#?qM7uZ;4FhfTcT z=-*~|*dOBRNFxgzC^LvXi5~=;`=1)?N{CyN;dNRwos1(-jZ)jb zaK7^@iZ3So$ny5D{!{8Nhxiq*_8h3V`;;EGHG1;JD$h82DDC<{ZvXglkLU7IY<6cb zr`kXeQV$xgm=>d*oN9tC?YM~82P8QjEX3=T@X zzlB$6R_uKB7TO@rN}Z>S3-=}BwQonT#Rg=3+@I!O7VAMSlsU^q)Dj-)EX-1QV=@k2 z{f&w8QSQ91%5J7?OK z(Y>ktT5Op2Z7bxnT_)K7_|`x7ez~dE^z#v(!a(Y3#(~h}D;$yeB|T%9Gb=w|Ww@;N zy!MLrh2;fj7JX673I3OitV1iUKV2i!KgM_YWxIxp(nzDhu+!g$${p?7xks9iO6(qC zUsbQOi%bs(Ui%P?zo~s|N0pn3qeCGWpbv-{J-abAo#Mhwu zR#z{4GHOLGfK~pD+inazaA5n_B+8EEAh}=44L=@j36n~lD2|OS=L9&z#_bc?&lj>g zq@VWhopL5F9JbCowvwsS%yPP&Gzr;`($TRo(vMgX87u796iIZ(HLmt0k_{M2CnpF2#5>;$s#c=ME8)&|wX31}Mw(l4Pz zh%~5H`siBv1s@AR8~mqNTAdtZat3)T(%XJh*#5cFl71nU>%T#xPcK9>nfrzhurUYc zNy#7IE(I#mbHWFO2I`KmH#5Xc^(fhCJUeq&MV3ST(a$~P(U@ztn|ZswKen=U>kmAA z_am(A-dd9y|Yc#9ys?0va$dr=XZC&3F__rLDxmtCx zV*7~gRq7FFPd>S?EEqyNcBDbo>lK7&VTgjkq~WRGo+7DlKzkyx;Gsz3=9%SO*M?&& zGp1@gH`-!*-8ef(80?Y*S&nH9h55F#H*VM*JXIs235Jh78;#fdt}M{_$6Ur0*P!JRJS%F1}2HqsdiiS_!oh$22ve=6-BQUw6Jl{ z6`v1?pu53wfGq2_%2j+;A+=T_#CghJIed#{4V^=8L}W)!nn6)+j3cZ0ns$ zC-z=Yx$?*{wID<*P_=PT?Krh8O39N0aZzRxe{bLj@kN!NoraY)_Tul3$CyO=AwlmtuOjma zTswXcyy2{sNxzkXj!*7?1B{U(fhEM61O}0!+g#k7b-m**bA<55Q{XsFTpeoZ`QL3H z5#c(rlFr{^`et9P*wpjhLVjf#{zgv~uxFkULah7JprJ6xh-c_zNsk&dKX{E!f8@o| z?^q;bk6UCEIqBwuF`pCX;tw%$TJQgKl6os-LR^Zlma|W&o zj8UYLk(=q@+W61xjYRwFrWJn<!w zHtjU^r+V%=tr&T?RnuZj7nKgnO<@;{biDxGao1ITbJn$3K;dX$0feI<4xdG5iO;~p z33Zb&Z*|g*9zcFE;+S&jfLUG41IR94qWdCg6vZfHOvig-J4k~ucQ%;*P~l)JYY7*- zHNN%7w*90!_>^#pC?3`y#V=K?Na@^QJ0yExJZrilULoii_wjS;NIePtm>;TW;V1V?EVnHu@BVe89Km4e?if-oB8-vf^dy)H-+c zShfIpvLwe+0suwZI*ygX-xWH`tH&wqqI=MqZ5W=3=zhI=P>6Yj{1~5C>)Rk`UAElZGGU*cp8dXa)zf{V;}PRrxc^lS9!~$ ze>caDv`=jP@%T{w!POoj9^>W8+lm}5&)zc1nyQGumiJ_Pzk-WP0U5^z-oJuBGM`w) z*kB;5Vq!oO(sH}S*_%Xf58ZpPaYg?G{*?d?AEDyl;*itC6ICQA)$fa$7oWwi@I^Efm^`5Z zgM5Z=V{El?h`eHmg4ivE;>B+kA8xN#^PxxiOzraX{5gCD)>L-wIBeDu^*L#rHBoVY zI_JsuXSQ!P(){>m8?Oq0K`FY8mJg5jULUREie|98C@C=%A|V9(EB-)Y8sA{N)BP^l zce{0t6;iv5VHASt&_%C)Nhkj&PqZX=y%a36ifV{`_}Z=TRxY_Bx#UNWSAC{+6&^jb zGW${s=z7>A*Bd`*_RWqsFQeISV(|5zwQ+K_Z@6JL;zW<#-HoK);_=C8uCL%*j=Co! zslr$rbzGtJDHb}{`Ln211=pAy7Cd4%$a?)tmjN%uSdiLBd6iQd))8D?4vW2Cib$#WoH8*mmr!OS7 zjNYcGlRtK>=ijGP(L+fF`Z_r(KN~9Mw&T>Pm}w;IXhLWrZD0tf|IbwhK}K4}gtBP% z$$!}+wS0WD=}K*)l_un_az#Bd!;y7}(D?gS*28BM$}=b)BbZW=r+T7kgyQiX9{y|2 zwj_6^lIu;-2WIA2A94SYysAPaN@a>dP>fZPUrN!}xkpP2v5{f`n)DxTq_B&pHYZ^s z&VDCXyeoVQo_JZsn`iLeWVBa*q>kTs)|fm!-bZM^UqSK}V+}hu=j2RA#oJ;+yq@bQ zch21GNXi#}Qfo`ObGXk2AExv--T4E4;>zWiTJeD8MU!SGsx9l|-2Q5_4Va)UUgn(o z!HCvYSY^pHv9b4irStkmVnYlPlTz}29wzi-$s#@7nKNhVdsLv!(R~%AOgfV*HmPd z#E{R=j50j8rL=upbR*0yXhRUcG#haB^$NDSCpvayCgtZ+?y&i=rFvMlg5x3hG|f5| zeV;n+o=i&{`3};NJhVxZaGvH6!H_f$sVlLHP}JWMM))q7dnLKb*Y$YwnA`aXxzKa7 zw+^fDo;}Fdx?toQ#VV39%VI4ea)e7rzeBO-=rOG%ilnv%+G_#%Q^k8B?INHcOM1wg zCx_f|ryW0qmq05p=tjvG{j@}DYwWXDG{y>aPdV67ZLx~m`on)>-F&)n-Huaoh#S$X zDr5&mg&U|Bkp)T7!Rkb}rT=Bb@hj<_6w6U3GwDhT`l<*0l0uh>UG{;!G7({qj{2|H zFo4nO5QBsWC3^eBoC{LQEAdw->{O8?frsV#0C$l$Zw4|%%kq;0rcK;l{gm(%)dlP3 zd9nqg#)h-eOGrMr51WkqRNgOXve=jx$ho~{s{O|XQ`Mrx)yxtzmg1B}N73F#k5WlfAIPfYyJ?rb_?4d;z>D-B z60R>+ZTGBzY!hpL>G>Br`2KL6?SUpT;T%@xQ9br*upaA*!);Gd<1hb_S&hy;7Q7Gp zr4*=rEo^OB{&5Y>gapS9dx$8hJFmD7ynGs7dqBbfE%Dl`*1n!&zo0LdxxQ!5l1%0n zr`Gp%a+D)461BdwqpyH!tXj%tHF&Y&onJp?+|f+7Qt4Hf zMF!8DR%}(xmYhQ)-dv$?||b?Hk7ytey`v-x4?|*+^koT_zaeL?6;i8_`vlCY-vC@n`f=J13=!ZqIu{_*&RdDXp)zH&J zWr7?%MYf0tUW@mRaDo9$a8|QdfMYnB$q0TmkJBCBd{c{rB{}z2-!;WSbmGxu$E})~ zl|jS0c8C0s&6FC}zb=G{>6(3h+p_$MJHg7(v_}RPCeN{(ld7VaQ{O(U&%!;1F(cx< z*YkfU(G*XktouQ#ErZwcUD>soJe5ME@Ym5fj(udu9&xlP=6QUe+hwK4NAcY~a)qtx zx#^SDr{LH#mSnxW|N(P{yaw8+FHY@ zc$g7cp9;R4edn6*`{$(1OWRtpnwid9sc7<&hiiBxI@=8lrp5}}g%5WY>gIhv zIPm_D-;Z%)r}ooy>MuS2l@F}BnHa~N(FdN3^0m$$t-tZQeV#*o`sO_cGh9LP(^|fB z!*b4gAXxWkRrk{Z@S2tuI!CUq^Y|8TC^5QWl2bE$cx6m>;H!CT0Gs~f)daUBclQ@p zwcE)pHGduQZ{fS}!D-Kk=Q3=1=UI%@Y^2$+gb9{&-Anwp}Y_2ycE%lR&fFl``SZmG4jMs*aIOvUbVToYD2`D8FQfhCE&c4fuCz zPqe9X)s4xQ;&&gvpWyozLAQ0!m*C`*j>92)$*wzs4C%;r($-x(Dz$tN1K?HuF{My0 z@ra0|hUZ#FWGrbWDcJNY*bV04)U<)@JK3OKGqa0<_gwf71N1!DJ&ischD_#Elvyb^ z-KI+07Hulo*2kO%FV)eM#>Xj`bV7TO&p1w-_Z|X?k`}}P9>+0>EEe4B z3_0_sn7@ZeI^+hXlwC~cHtmu$9Z)k_K%}Cbfle{RV6;ZwQic#LVmNcSVBJDnF18fz zbP#Glzf05|T>zFLF`GeY44GQw- zSWrpHsP^;xm~~U_Xw(TRb|D#Qb%X-4w}xTp%=wPa6KsRUn{0tUqH5!y&LGlR_t_;P zh(})nw04^kQr<+_vJJ32>Z2KjaSGwh`u&!y6n6PI-mu&uK9hy|x)>Y=#E#@ly{%U~ zRu}`Vk^uq<6PHxPV9w~nIrV<;`8!8wDM$1+WElbbipi*c$w&$d7k_S~C`9%z$~pa% z75ZwvTAdBt^SMIb;frirOO`*mO;`b7HP9)+GT0dfyI5kJ_;D0 z6dkh*dcEz3h5HMcx^!a@A4k5gQ~3Ap zwph$QELy(3IR*hut|(>qCJaPPLV1UDt)cgkMcF_d{r5E>H&cNICGZjn>6?rMTP;ZB zEh@m=wqqk@V;alKF4J_MS-R_|bW1DM2S08T!3OUtPxfbBnx?tNxK8rGPe7!g%kBPo z0N~eulF#grv>c(xpFOQ{%?|>Gnuq;T_tZo;t7K^=bAmi-H^e^Np})AKo2}5S#|(K) zsbIeGahOpF(!U8bcZmkLpG)tRr>b-Y|8wN6usAF=cDN+hM6M$^ZQMNsb2Lrm)4`6j-+fepC z2ZrKIvLcOXC3QbdQ^8d5g+0-JPYewGg7W7RjxnQ8UPLkc&*mwY{*k|5UCB|!r&SF#mTda9%P*bu^RxuV0 zo@>?-HbT<$=5QtmCB#BSI+$#sNIoNUOw@xCagc~!-bFl$^gskpR57^kl^hmVh5q6o zpAo~D$X9a`1?=St<*%g*q`z8!fL61>ds;^~-H73%ozNZ_OH@dlhclZSUYn`|VgR9n zw9g?J57;jZ&=~4Tt|6-Qt-&J&>N>B>Tb8}{#cF2&SOJ#pxg&ty>3xeqYcQ0&VIs{q z6}zvJ&R~kXxo6SmE(Jn*Zu#`vSbTt}1mS|C!wqe>r| zd^tiO!uq;)QXNZ`z1V(A*UFcyWB@5Zv*BzsRp2{i}1Y)O3vf8K+CjEaG+5u zVpw4N0HAPsu{K;kHuRF*Xkq`NN#}#T2)Ln8g8lF~{tIH<7bV~3I3P!mzs+ET#R_Dz zPc&@G+g`R%{XLB@(NSM%17+mow?e){WhAWAPH!^KLvj6=F0!k}Hm0_@DX8Y0(TuU? zG!up8?&>Y5bhwV)we57RnE@$iqTZbXWHxD?aGH$sPxAkN-gsL*=UpHzT^z(GRERwwlusa z8I0G^(7=@1384Frmw0gY_0|^JTy-&rTR+>MAC`0C^|BYDrUY_lm_B+`Su@h~cXDl@ z>-$k$+e4#Z<|m*@j^e1OGD&Ue)s4%Z`j^F)lwXcLkL$jqku#ef54g068v+oMg%EB( z%VPblf}+oNQr?zV1eA*0PRSwsg|`ZlXg@TkDy2{GRe$8LA^j}R;6li`w^=lI!+o?vi6UsrN=MYUnp+IkdNo7Y9 zv`-F9)W))#WG|x6+W%rJc-p2{cC_B-M+ID7qG-gwi4v$S^I8^BhJW|p6BQn>G->}_ zB5;SSBL`wJO5mxrs5#$WsI@cR4+UE_bY9M-M86uWlMWO7Ja<>fs)(cT*FX88_-k17 zim!pOCG_{$q-Bu*&c0&M>+#H`cJz*zyTI#zK%mXmTs z)RFg5aGY`sHi$mkzAwYS&LFz-uzsW!h|fqm`90+VK@eoHn(4Aq`3w_9l)nycqr9zo z;iK@*Q>fLvwQgn6hyQxm{~qFvcp%pxTR+=WSoIa7_qWIqcxLj~W4}En!e7mo!Z{Xl zR^v{OUu$_L`z{OJgM4ShPL{PS-`_G&e?B&tryahh`)8xp1n=z4x9Ksb|BZ2)AO{gY z?Da!E!HY>JkG8hBolN^jv|mJo?f^wWpxzfe1?dB&ornH2PF4u9KD>MbCYJr)T3XQe zhS^7MFRNgy`GLCGu{uqy+=G8m-iDPip9?!`_p{f+UD?|fdoN4?2b)|aP~QHkP@$s00SkRq*gca7KfF2t&Tqml*LvG zP*)x4qw*MM64setw1E_~d z-$U#O<^jgrG7$V?*t0ee!RS@I*LO;$N)mhN8rD+9E-A7sttr z*@aV9!6|tPIys-59*{VMqVn?M;K_YoqIxVcRxv{6;E5lUK&d^RADhJILSp4FoY?ha z3~9J=`7Q0kG^6-L_{MXDi*lsx<5RYFmyBKqM5$wcBsb4n0&FW+l|T*PN4@aXWPk4u6rHrhwp zX0`Z!+<$_PQ++c<#f=teX7y#Gr6;OGh4!8-D`DFSB)=Mzn{@G+koyUDSt&%&uV$Z> zu|#9Q?z)hyfpGx1A<{Di@Rz;JSR4O26toqC+-Hh2I!Bw)10fo{Kq9N2NX}VT3)HzU zGt=&r$kZv)%Nr5ym*`);pIC#xo8`5iW*ip4M><xa^{eHe>K=BxwLc8jWD)z5)(f6+{0ffx5j zsUSvg$+d~wwEaLdHeC4hAZd>i={l`VU`8ziC*%8dkhW?lcs0DZXFD06v4my|5sr~) z$;^A_qxyNVAVX2ogBE=)>^hwKBhoNcRvA;XR0;HnC_xI{WVO&|Dc5^jpoEVEFP-2K zAQOmz;pjnZCVt1#Vlyzh{+R_bImR3uwK;d6kQgEjM*rM@NPi#F*o%I-a2MrLwfMJ? zOjiB$ZH5c7;13wHUbumXJkV}6C|hwbP0v_g+F_Yo9k|E5GTtayQjmnb4;(nCjwl@# zvOWu(Q{;uCTO~pv(uoZHt!EEI9U7PeOk<=y_T9aRVIS{c@dYJ7OYM4FLIJZzOph+o z7&vzCde~oVa=?@HP6q=z*hrKTS1=vM|F_4)5`!E2Qr|JP=vjs^DvC#)@sCf>NE>Tq zuE?m%+QFpBDR@x)8!Au>@N)fl46LW#Uo4TTjl^wN+&D!;xpSA@=i`O68XYw%-KIoa zjA!ZkvM_#BWr-o3tBxSrODi*}ea->y;v7{|`;q0gh$g zzLm&IS)q(FLL{QHLZT!gDj5-qRQ8q;rI6AS8QED4WtEY=_lPngTaxUAtp9oUe*f<{ z-uLS`deigV_wRR&^E$8dTrJ&Xv2s@Dx#VeZS#-_zLa6sEe?!(>32e|k1LQQJ;vXh@ z`868c@lIp!FA!ij4}RQXEG=MJ88j5Dz{57+kUf|8B!mNBK*en@D=5(0WQ{e;fdxdaKKHivQVY3v=uut2;VHxGgjme9u~ zv`GnhV!-}C!Alfz(#M0?tVP2Nb{xtLqXia)}rXL;A08|9gL|W1v||CS>9xBQ1(& z6)^WBv_N->o8B|>m{lS4Xr4+!lM1)}N16jBU9Ta&uZar&Zv~;(&}Lv$l#Jk~rFm>2 zlyCf1YHS;X5xqMi!>*yGH~vyf;)NjO-v>8|>qJBhVgl~rv8reB&9-9OvIHUT4*j{C zSU3WyPTK0Sx%s})dE1=~d(Xy+E=&r0&Ogd#*GU;!J4%d9>ysc%$!Zy6)k7=;qRvo^ z$jsiH<)!N2@!}o9n`{JxLQX+?GN=CpKk(qrs&!K3h_>hGT~^Ca&!)FQe<0# zaAb>MBnzLkbUwiWZ4S8da0~;<u0|Lu z6T9gc_$-$kfrJpjk%oh;nU4fFBwq+8i{&bZ;2JRVcfG=~CrnxP>}bV9aKiRCtMnM5 zJphx;=!RTeNNB_;i6J9F^ZTURc2b|InZIO%f3t4We(_dmbFgbzB$E`36n zZN(uu-y@vB)vg`@TQ56%K5)!AtfJE%iP>46on@(un=G)NAQA>70p`VrTF zsmwiLBR_;`DZ3%%%ntaQ7v6jNzen{HnyqcE2~*dUUST)tGCw$l-o*04WrAW(un1$< z@X3f1@Qg1qPKb2&!dtQTB?eaVy;l(0{fAdxd>h>Y{tm!Op?GpKt!fTX(v>?l zld0u%7<6nO&ywIMzAv%Ot!L?KrQKE$GQpiE=5hj`F-)c%x@Og=J)romApz&Iekb~) z9h6ern8@lGjs}jk91}QazU?-Y^_AC$LAm|ry|G4Q?**7N=s^L$9YS)UQ3i!aYhi?! z4jFT8f_6tJ%@x7m0K1rdmJ@g&si3aE!M%T)go|zo33`jh`IR;I0iUgNC2$#ei8vLH z`>L?fS!k4Qoi|8%z%Wk>B&hvur-z`jrL{i&7E2M&Az%o*uILRSXVj7Y2{lq6p1Oxoi@?Vu#E?j-Qy|+yL;H_K)RV& z8ZjahT>RfyLU7&402pDd{C5`x$PX&AAOEkx6$X(|_#M?d*Y%l-wsy^eoz%&M|LLD|sD=dM*USMT+3I@X(a#WsH3Dj}n07M}3}6g_ zYlrJ@cS~FM=1GV~L}BR`sMCSdNGQ*Z;PH^%d`4BZ70<-zdNpx=!K{5cS3dw0rRAXg zh7oE;OK+z%cFi!urg#NA=3| z7)$Gc2mHi{3gRfU8#c46_vSlq0jzVou|B)P2_G@>{gEJ50W1aC@fx~tGW4xl^8bzI z>U~G9LJY!k!)r|9J{`%(tGh^DaDgZiAnExMg*5*=ehe)FU~D{P&5}nU-Ig6ihi}f(28z4 zm`})oqmnW4nOK338CKO;^2|}8LclI8)aBCvc2a_erYgG<;2W z6LbX~h5AoM|BbP;^3F)374rVCF-`skrpe+|8;Q5sqSx+$q;>RFsXRZv`u8}4LekQD zgb*%>?4Kd*ic=kzDY}E|I>ZofAQR-=>b9?51l#EiXzi*`n-w-Zz*NdmZQ1ByDhK&# zti9Ofb?brC;g_BD60#9;Q(&jFxid}w!|O{DXWP#sZPFNKJC3)l)_|KVsC~M3VqrfN za-R~W8n~e>dBs>O@oQF=ZqG<~f1Px`AZMPPn47x9K=eTWU;lUKL~7vJD=%;|9=#aN z7yDX;X(ItZ8iL)T`joD& zhv(w@HG!)ZTLO!YWhk8bIzo!9Y`Ko}?e!naMD5Kho#e8+$-RbS5`wYAB7kaNLcj3_ ze0}sGiDnIRuhoz`czY4re&6gH4lGZ28^ks@Hp)%jn|)Eg`fa=cEA`94p7O>d^UA6! zmZi0)5pKVKe8N|SLn;iYioNKTi;ISsyjl*Wq2A0=%zI*rf3Y%L0~1U`*e z32oKpU&FqpwwJZ;gkzPM~!r8kwH20vmynua>sPH-P7@vU~1x zkfQ?exrP4sxvk)g^>M?iPIP^I*D?S@_vvGSV}e;SPCtLt3#Wg^KuYv9JW3iY`_pml z*N3qRcoV*X#}zCP2O)-IeY&lWC-z4nXo{Olj8Vw1)~p0xYCCZ2J36}X4Jm*)&-!Jx zZUqE@>;^v0QR`3{r+VRdV@J~ej!P=Rl_rUaJSTTb73N#Q8?)1zA&U$rS}7SZiu^c+0!Z zF@;k3_$$q~e;PSSu;F?V@jVlqNBlo$r-DUY*2E4enHF!s*b{`G39+S>c8u5(0)2F@ zWU4g*nd2zk36Q9C*E3hf$q7|G!y-xkGWE7Wqt&tERJdcDlUjki<2iNqX4e#!_(Wb0lcFlhC(YDZ0|AJn;icr({ z#Fo$L42YiHk1fd-MuIP_GvE<^nGx;b?G!mNNJ3`Dl9Z~ar*H?QG5A{tR$0)x%CPP7UHGtV3w8GgJprN zc3;8R8gOue|IuRUnamKBvaqozYY;z+1SbXo__1grsApA4Kq8OQi8cGEFf?Sg+L4;!w_DwsscbL85VNskkbQ7iCk<&n2Zwp6_D<_plfJX zWvMdXFW-qW^8=JuA-47tD<~a=%_7+3LO4*6)BOYzKVegfgZ3J|G!(^NCl$7LZPdan z_|4uE_kREm3k%gfiFu3*L?Mn6Xau2J3kOQV4S`lPPZ3^#i7>?avU$VJ$i9xBfLrD? zz2hKALM*1Bz0)BuC^Rjjv#y~S*`Q3m4*BZ8zp`8K-Zv3tyvhpqHbMNbTEsD@XYZZg z&P!v+|GVKJ!G;tUpBg1txOeQ!!N$9=Qfx}-{B6oI< zb%WVqCBwMmr(@(>Pez5lO=OB}|1t6ImA|(nwx;}|qYd{|JTdaT;6AWv(SEOpYmt$g z92doAH*QxhbYa+z`V=apL(@Ap>-x{X&KM%QdYz6~NdwVa3*ZdYDdb~d@IdT9;J|TO ztU(l55^~an@9$I4!0E9}Km6VaufPui#_sy0p0W8Drzvc-V}*u-u&SfkvKcMh0DH*> zn9(=k7?`@|eq4gkOa>wG2Q2=y;W9?Zn2*KYes`(FxYX(J4b_J`Pka8w>W_5Xe78j# zi>|7%SL9KmgD2DC`@>MOS~vb!Eg+RmUdR0QSn34Yt;gr;fVXR|KeK$SA-? z+FLAVHWA(LHlrdUs*XLiI>UR?cDw~4`9Cgzs`t8sZIwg1?sF;9ko3A15RKnZ;kW@X zkhrRFm@yu3$NfYld=nAg9Wl7Y{l$_H3cSb;G+qSd0;Th@37K}uFU6j>*C5Hz3Wvbv zkD@R(g#2D>w6p_Za0}ExNM7CuE&*^XOed6{0S^#30#w(V`gLIvklp;-Rc;p~Ify+$ zVu+0^LF{rkB1pVO0Ck5}`3Lwu;U}@UrSL&*K#SPCAZ!X?bhVH+0q09%_5Ts4>_t#g z8oA%SLV|+69Sgq14+V#K#b?52`aO7^rn!2fp9v?QIY>;@1<)|z0p*t#M;9`++!cz| zl^p%IRl@y^T+#Ac^w_g*xsKWO(sd5xQogZ`kNB?zFG&4su%vX`+LC%~!p$40tTnDC z{r+^S?a|SXjI@KVu)6yZFB818ML|^0hyVbYLooGCLWcp(R}AOX)YR3_Lk768Z>UI) zaN8wx(+N8OVA0vcS|Z$T1R)Fyf4qxOe;@?KQBm%6GbhX(kj_w5UoJSZVTWy{1PO$L zH`oW=nfD!mw9z{no)$kEfgk=`5QUfHdw>Om=EH;S++loB3U`BLq7R(Km^nGuHo6Pu zUEh4KdNdsO;g9@$tE*%SIm@R#=UydEgza0?x@XHEx=WCuzLnozsP2H7N{Vfi<2mMS zYHALe?I4ec=$NPg(g>|*}Nwl-tNM!2_9sbigEW|!-871?NY<)kGlTH1!ikM~WOuX{2G z8J(AB1^bynh$iaD@&4irD+Al%%Ib1~xYE&Z*^o2!TiPYTH{!w)F>T!vGHkOhgB)Q^ z{vFAT3~P)J(6>+?`;O&Q_@q&g8Ks9q8TtEdgLcXM0bklY{NvlacgTOn)^++=-onKQ z@x+RdDj+o|jWyf;(3&v|@E-}WeE$1aw?zL02-C9nEMb(Zyk5C^ zH;PgRc99xcYWL)U0fu*nTa;6{GOQuTLy?gRup;G6ewa!6re)$kP1%eCh8Dp9am z1CUNwJw92FjI&*w%F2FzfB#>7^Xmh+Xof}wc zs$nXkvwGB#xBek`3aWb71XgC#H7c63e0SBTQdRWqrvHAc-$mngT&Ud2^ zx7=o`1^oOik^(C1m-s!e87Ju{?zual9HL>B@drBZnwVC94y+Bnb~}enL*kQ z$mdhkRr7$0XR)^U{aGta52^sDupPO@hFc<^lGl9lp&#$H@n71qv_pmDK(>V0P4QrWGf%^&?kxzL}PLH`?hM51uEdN+rh>yA56 zyhie_0Jfz8dY++%5Ko!*vLSKeD~05_Hx{tAwKr#u>FQfd}l79svvnMtS_I_wS5 zG}L>4odM~JPh6aYHFUa)cxi>3JeI_u z_5;xfZIDvX&#MBMBi<~e(DtrxQ!#X2U(UF3A^win^AOp&$+x*zW=&mJtgd%92RQQZ zq&hWAk3p@6`$t!ofJNQWv*4lv!keX(^o!ijfA@)6}ivx>4B3vXLx z`w>ws#TJpPn>-En<@8rOXoOabD=YUhadI}nFt&{N2S2Q~sVN!B`<`X_VUqTVQ3oO< zF$nzX+jg{%>P$GL!bRmRh3hhOY&%vj@$f4uD{sLNoe-CQ{AhgE^?G~CHb`gK-BKqp z0{1JW`?0oCvASIL=)>)!jz6~~o}|#on{$m;738tkhg0?Ew-5nj!|~D%Te^jfN3%7? zku6Mj#-arqDW8A3sGRiv&2L zJ(rw_!TzDHHpSFUQL;L63KJqrL$oXiT9s~@nKS(x<=gDqMnSmy0T~A&T<;>0NR_HN7lEoUy)@~$64T0{{Q@w zHZr1kdbPt~@8QEsv-&yrF1(39@nC>>{?aVztVUk!=8bo*Z#CD}|H#~|z@4J8%}eCp zb7TnD+v+fL;7pmu{MMsSI!k-@(Pz8oNt7uoCv`~-9rM}z@u>gizkRn>C)zG3BGr*h zHUE19r1GV?Gg6C&5z_6Ev(|;;S_5x%Y9eRxp_)hT@7|m)yYQ1j;i4K1-L5U2Mzp6- zmjw&_`+nM=MRsqb(5>EV+k3RO+W20;;*eC3nvq3TfWK;hN$BE`6(?=%9-1EtV=wOz z7riQ0em2nGU+OA0FA2edTYZJ>aV=xc(H;fZ^>oE(&HvFOC)q&Nx+l+Rwkyzu2;99! zpo$>b{F#*>?(`A1{yn|@;Z6#Lz6adpx&43;$-fnBRfv@`M;;1d6ZZxC zHiSeP>@B5O>z%I`7PIx00R!< zQ^4f&S^)+o~CLDM+wslK_wQMfPWft_C#&E27(ImJdH& zaG-b2@yoqnEAi?~U3g4P6EgHX)oJsc_G~>24XDI%#UDO?;T>I^ zC#9x5wWi!A=O;QCV{S#n>@*}lSVBil-8>Si1d+w>7$aRnUvGWN=hv@N`&}RjFm!i! zuVU6zH9X5~B8EDr0fnV9c$BBr)g9rw1XcPV1lCN{%7nd*GY5&W?rYp#Z!^lJyKzPx zMA+w{EI-f4%pf!oRBz&{Up-{3v$XkR%#Vs(9+norOg56tt5<3Gvpd?G#UfAG+5PGg zf0s8zgTh<`>Hyy!^YZYhC{AnOTycn*R#N5iZW)Ed!zdzpYLB>bV0xu;-uS*B9ZmT| zZZ(fH%pVcKc1S*WD-!m0d9u`Z_vZ&6w5G8)MvphTYG213+j>?ocB~yqfDJPl{0QYF zI=4UF7H6)`u7c0Tk$}X>|JP-QonK3J$q~+tkB+AT$IK_G3R$a(!i2}IaleUcPbI}+ zhy0kKni3HfS^lLr4ytt_sDpU|$4aE_N2n07hwwJ7A*D^?i61|UO2rrD!Cnm5#|l@_ z))HzMF5;hzfzYs__LdvWgy9=8+~acpu{T^Bxjl>`y@Y=Iu`Q>9Wh7EeN(ahN?Zv;@ zhIeUazwo8?z0mJTT7`ACRo7FGaT;c-cl+qIw#dsai6@sLd55{Iz}?9VfNJx$*yl7o zxntgI+09U_(R9y4lK%DegYfh^XzT=6f*mwlCLw&p<{zM5w-k6Hl!y9<#1m#f@SY>N zcmqrMJ@qXL_FjxEETg7}NKeb;rz-GpWD}q0d|D?_$IhFI-Mo)*hKMI~lK)^It=X(+ zIAw8p%&)5!ddT!cR_u34JHFd02{o^~8n#HD1;{&O`dlXpwFw5=wtZ_D-`IY+pgRk% z9K@wT(?cjb4}Z9f*X|oiBH5L%p(|_$Y#81)0;I-j`oinD~5h6L5*PzSra{U=&9^f-dzlanV6Uy+UB>*&5&V!H43!e zWp50=Ex)&14pecrPUQ{Lv$l`4Ks$?2CJ-?$j)pMLY@I;dz^tpG+CJ^Z?kj7de%=QA zq*`T<@|=}>YbsVhA?$+id@S(=t4)uThp^7x+$Xv{Gu=xUQwJ@%?&_Tokb7pHV;arfoyc91JcT$7sx|ONG<#^p`cTIZsd`JzaP$MLZ@|L1mS)d(^G3n0mbm zBy=}wo3@2^k2O*ot_}ZubC+93gP?iH=b{=qyd8wG}r{9=zq z2+-(OfN4A$`w%P-HDT`rGrk$v#usp^Bz0x#tS)sXo25!Q6t?&;4p?$NWJp$e=I_6H zI5B$bw2Ix*#bhOoy|h1eE_tigXeYXf%oe@Hr-}V|t$gng^Z{2s+ z{vF2n)Gc!4zy*j)&EIfAw=CssYf*uN*;L;)#6?8T+fEF15|}vY>}+|Mh~v?Q)<$IF zbGwH5wQHZSf$s%T_5Z%8J=jhg=Fm{JuGt|ZFEV4(P?;-Pl{u8J$ID?;AVuWxBt+ zYF7YQMgn>aBJH49`^b5U@3;Iq8<)q9b;r7&@iE@=h4b{1H^2YN!+Tc&wqHtp{$Mc` z;zxIPb5Maj|9hM5l7eTO1#U!%(Actr8_qnM)OtG|w8SU?Evs{JYm4h2cw#mMknY6oUOz#}XttoujFHv%N*schzdOI9PG3nZJ z$As;@RZqv;>uy}G13rZTe6XT)gI{fP-a5v}Fx1`$8k1-s@l)>+HIER5diE7=!EL;G z*{_msb(MH4;P=hzp7K{#R}+s%GVH+J2i*75OPrkzEAHH$_Opodqw-|uAG!xzdlqH2 z{nCsHu*s9|?lNdzT;;gXXP^L1FwD1?yxw{TLt%!5jYZ{XGd3|t!PQ9{pRA>iPR&6# zduIOhpwhLSZ<<*e*81>T!Yuqa(gy0TXP}H1)>s!Wo1?!RGkx6;0jM3UtTiZM5fe@f zjC4wLf4IpCjfX3~Dswhh<*l!5=b=pWEjD6hAPumIbIDl^&!nqWH4A!d`lj(`*f=&Y^#a{Vw<7sx zSMi0=L_EPa9X-|o=O6Id2Jv~|t*5ilIh=!tD+ZpETMOnDt3f?~*Sy}9th*D?wn#lZ z%7!?KR(9Y4@7H~27{9>8$fy*mtcEqR4qL2f9KxKe(5T4jiT2emuNB8VKJkpzygNsp za`vw1MJen6jJzvN|1 zGjAH`u_4!`bsJAsbnB&-mKJl`U$|l&KjO8qW_v#}V^kT<%m|c$O#$@^9P7-wc7~NP zMjuEo*kAHUX9qt20Z5gC*;`}mvE9Kn!?(~#hU4i!fk0ZiQ;W<*+KyCG8;^uU2uLy6 zx4I93VDSsJ3Xd6QZCQylQx>i9_Z5k3cd6L<7iDjTY48l_h>}~BtKY@4tH#rhtEM?? z=B;W~yU4EWoPAe?EFp=J2ha_07o~iEfcJLDnl$2^URHAkVbk?jW(}K!xq3oQOKojD z*3F1?fbpozZj>>Gi8s@cI5uPXN?2=C5#G^Wb>8GA%xUtV0NDF@?@$nGcNd%MJsrzK z`6i`Sxnvb^Y5ObYT30$MuIaIkFrAdR-pV1PipmnWO-PMW0+FTLMAtVdJ#$Fd%U zad;}oBq$Nyw3;=>DZ~+yxAZBD3_@Hq_Gub53rTyhOF!OMBnvpf8FhDUw~&{Iu=Z^F zS(kUNQ{rgws@r)^R-sL^Gs^Q>9J5TKe9Pb$-2@Lei<+H6ql7ax=D3<5%jAJC zv4r$}!ajZhTQ?>U{4#X>q(WdJnD+WWFRf;gSKAz|;Y4S}k54;xe*e`4{Dn7I@vXj? zs~Csek6%m4wS@fNeh1bMXs^Mk-SL?X*#01BZZ3i(f7MxAuvi$wf;-z^z7#yWV-H1+ z!}@Z_Ezh=|Nk7`r zBE1xrVw(DwlWJdrdCw7Ik*PdV62VnQu7g z;^jfozJ*CP;JFYe4*0|FGA=DGji+Z301@g$He9xAAT|-StJ*&Yv|poSh!6+jW@zg( zKb)FUUK!B(l7WtsroctEB$&|}BMrCQ0>^RRYkC0TMh|$Ea5rQbUWSamyV7?k=xtfM z4TH33V8jAH?39%QA3`rQ9Qi%QjPWqxLSy%PPSC0nu~BnVBTfQ13h5KG^G!;9_$r zM?`YP@(Ws-2eZR_Wn?5*b_n@lp{)f+_YAtch@&=KIqkYUf_HF?xV%^G&;^y^hB{{G zBxUG-@SQHc%W8aY$#twoQ*(RJxA@}&YWrweW$<8s25;QI@8@U2M#-}Oe64|cI~2uq z!)t46pRD%feskVU=v8{$VqnOna|-l>Bo_iK02=U zr^>F#2<>Iz;?hu8SMRecLcOK!X1Eho>Ck@ z!$2&U@97<;v+21+wSApWa6*f|*hV`e_%Da*zM(V%cLV^N&v^pvjqTiLRBhTrAY6xl z2l-`^OCS=_+SC@pk5SJMbC*ksf5V zd@xxrK~=KnT=R7d&kR8kl3N}L0j{yRqE*C#hvH(9dpxsOj}<~`hMU9nKiT&DhTg~c z_)43OR!rueNIaR)9nNfk*;g_eV4}{16Gd-BL;a+tMgs?_)+tKb^pLjYx88JdF+;2Tk$bUWC0W@yGc{BaCd@6EUv%Rz(4 zT@e?G?D!&Nz8|Qnf%hlafICsRA&=mJ`I-~M6w>CN|Ejrt2M#n%3Tj`p>ZcEur*ZnJ z*k3$Tvf-EG3U)?@`Pul9cSHHAJ}99d;PNrA3@zK(J25P_{ULkZ~2xLx27IC(JEP`%UgS1?kQLgbC#2t-hP<$zjg2^RX;@^4O&q=k!DfqVb3CG{?|w(?`nyW{N%H#-%}e!WTsMWJ;n z+sRD0mc3W+r@yQE`VFB4|6YuKs2U;@Sqe z!=7-fig05}^AqXksoxbHv2|iD(mlY0rtvJ+yJuETh%pfjo)Cy{r>Fahy201San~^S z`Jk{BpM0rAvqFQq>~hdtZv5pTfNPUSCnd zjSFm2J9diFF*ho`a(8kaW$AiP4SmUfU-(wfcL2y&K_Tl(Sl>8&k4SHrHR_dk72&T? z%jvqrCP8_`1~oF7MCF6TLlT1(+Y@vTzBJ}W8&td^`g@wYl3?r;mJ8N3Pj)~8+Z^!5 zD)wk(>J*hL_oBLYobHQ(NN54)=ob8!0y?@`LQx(^kWM^|`G<{hPIo@<^vFKxigfsV za4)+t05?nXyQ*>r%t~)=QHa>q(Ra(^j810ojW$XlE?^i+cm}E(8aTf9?zD`VfoKT_ z}yKa~eEZZ}kFd+d_LyrKpREi|PjMA*d0 zzBl?rN4HKFn5wvbXYa}+pDL6PGkfFDjLm^wuuSAueq~#qva3XWH{P2sL9n`sCsXXc zaHX2AD^J|kK76p0)!h49zr5=oz3c6iKR^2%zbnb>F7yY;UM47Di1l}I5>KF?Q&m-s zhhD9u)z?tWAm>_7Y$wIWPC3glkQi;SmO+#NVDjW9jzmh?@nCXkj&TJ4->c?z3IxDu zMw$PR$QGr|SxQ;lynS~UpGwKGRw`a%@Yo6b#_oyU0S?Hf1W5(+4tHCwmx}zj*;ZK@ zn*RS>fW4=t^D8|;6G4?`?1Uro13T^7S}_Q{2kqwxilT3`b`ZN#1TE9DH%}aV$-Vpb-AB2GLx>N;Cm@h*)pG>k0ACeZJ$qq9TuDXnt)lSw|>MGV- z3=vOP!TA!9o0?yakg%XP_L!vrU1(_9d!yhCwMr#5Q3&D5-|8{PjI-*s2ffesgW%3y z!%>pM{t6-U?kd;ZNVVs)c>fE?`Bx0ar75{m*L2)eB zpN!Rr3OKf~Zh2Rxm<<>6mtSEetD9|#*)J6uNBQKqzGWVnP4B78+$D~*h+b&S9` zs$jw6ie}2O{G8C0)aA@SV^=0ye15ey+Q!z9WovAStv>gB963R4d$7-#FI!Nc(Q4G8 zGW&r``tLXe-%mGHw@dCyeDV4!qfCnB9B{C9h_aFqv@$U2m|RW-Bm(;Bgd*OP?jrBBx+{eJtm`!9D?4FK0li@xIxx+q{fM`C6ZwHyV+VoBo7!_=Xu2Nyuf(!db z8@-QpjwGj)m-~s?^S!s>j<9;K$sKX1DrRU|&!X(drwj*+E7cbOMEU+f({u=|{P>#F zs;UFnAu>gk4v(*J6uz&}NZy&5vBefv3qm`AR?xDFjs$|(FVh3C1cTr=vn&f=*p<}^ zz^0IPxU%M+H(UMUpx26rk8rx&s@QvLPFLgB55hsax1I@C688)?2wTIq4&so0ihKJB z0B-KEDwQ0lCs!>jHb&iHrpa&Yxp0Aj4-KY~D2VzsHNDsY8#a?E%MwAyavVbdj6u|k zjkuE{>ON%tF&{Pv3$!^+18Tz))|N-v@qeEY72UDFHNbvO{L7-CC8v~Wf#mp7mS(~~CPyBqdJ2-_H;>uw29<~#5B z@i|UB`v>qM(QuxGK5atH5|r@u-~#55po9g>1CA3NP{ef?Gb!H!vfQ+DQd`~F-Lp}^ zGEBa_F1?jvkaOn(9sT*kmW&bOt0_@?4z*26@+4Q=ge&LXjmVTjyurd?=@wvV0q08{^U=l7seVR zPaw*lh)@NiB_CU6I1E>iZT+~00b>xX3HlV8x9{Ie5`77vWug?$2T7~+fu@VrYlBlK8ZEGpdHR7XO6-E7k`XaCcabX|!f<9~(NOUY5q{lX^T! zV}!8=RNvLn#x8ApD4YuPPc);q0!{IO*dzaYPvzYZXhP3n5@!Kb*W-^qVNAn#L4KnN z|G$KDln+xAx4AljfZ$*kS?#)Kq)kyrD?U1b?vSR(bg8W!K$vU=RU-sQ1r6;~28#Wq zs>kN-zBzT02$m9L%72uN)P=md@FwhTorW{WUIgSn(_vsE4t*xjXJ`*MgG&c&J&`y2 z)x%mLNo%_F!p)k;D0EWGlTO+y(^cb4L!(Do+lX$GEMz@ z82Gk;ysviS#*O06egMW)NLtLADVA;6x{9w>J$v@RxzYLlTm8i`H`9y4uwWdGz)!+< zWIpFMJKpldbPcn$$s!bxCZST+<8jH}?jO!+l~#Ta~rK z9-;QcK-fHnN^To3kSn91XfR)Av1L@ic|z%m&8BzmUSL9^E~ciXr8VJUpl1269#yZe zt1}s@3?L>SPyu%6vC1`hp3;*Un9h6ZN)3Hi&Vd<+hMkv6xSXd%xFnv(sTpz@x^VX1 zwz;zJ0t0mbB%uRfMZ5EcL3(Ecm>F!x+$B#kupHS|F%V{8oq+!H_e5tfEDdu?%`Syi zA)+@!sz2b-qix647&gUg^Sl1oo44VGiZ?=zJ@8Do>vUh~p_?7k!BtfheGbtsM)8XM z872c3Ykzkz3>tMXHcOrr%^zXhe=&`!6oggOgvWL)EnZTK>+S9R$Ky(_z*@cKrxz5^ zdZxufZ!%P3txxTb$~ut7EB~uP#aL>q*bDVWcT$8CkKzMo;PG@4`yUKW?g;kQq3rPC z7^D2Nrmxf={wURi**wNp?1EDGDjeV$&OVpiv`1Dp3Y2M)Y3Y*w>5C%N3BDX}vaJmM zF{A*wAndf$b4Znf3E~QP&TY1e)e~FhfhNmot z!jyCU>dkJ(sXX#`CGuLEnMhBhZ3oCeFBkfpL!5o^GYzqN-+aG574=89>#Qj>a%(Z? zFdmp(ob0I~h!NQ>^1FQZ1V>1mu8uet4D6ODcR;%=zhF5DTT6r)?2hT%KS>o z@B7=x6>fg<-F0~LnaQtOMKY`UX<;FHVO6MRF6G=}$9nG(40MN~Tx% zstO&LbE~^KqA57{)Kr&9lW#n3ELH!paG2@iNDEr?eBf9FbebV@X1WtR5QcvTQuz+h ziFORq-c5aZIIAgtdXi{+e|H`_v_}>Oruq3#!YIebpF~m%3HhwBv-`xIKK&$u?Gmoq zti@^FVmZUx2YS6^-&;>Q?;;%TY(;)-h7K25L1CNegS+>u3=zPCwbEZgFk!(h^#?`A=QWoo< zk)Kgg8n8%K+p(K=cGd@msy-nTL&JMTL~Na&_b+)Qte-V>z;gAw!Z|gRN5e!D$RKq8 zf7zOV31QGNfQ)J@t37UV*R%J5=jR8{A|84+ZVvtZz3p?bYDZPFck=zTb0kIeGb5xMk!S$!$Ec ze6IdnG5|@k4{9}=t)il$8hC@{z-aVfe1YSp|LGHIZ6CB45z-P`%KbS_lUMyho-M4f zT&KC&FF!UXB`7D?OR|FkP93P+mS5k$;vFXFVB^`>8g>$OUwh)I?fW#MRZ+wDK0ZHp z)!|K%c&A#*l z*_qDfCX;p5Y3z^nj&9~XDWM;JM59T2YH@|ItNe#L1^rh!k%sn>yK-EoQ+JVmxEf%o zRNy?*cE0iG%V1<>L zCX@yzo~S9$y}ft<1h}QyQ4ZR< zgy&yJ{J9!vY@u)v4;!5ce}iwN{R>l&V25Vu2k7LWHT2Ut+O(lLD`WeZ>11tr$v)10 z-MSG`_39FtZ8vZ4bT)lgP~|{Q&<+j;RP@MmW&b0T@lag5NL|q*&Xs-c>h)cw^1E-k z94W36TDq8Gvi5YhZ;ZbIs+v>SUgcyL=H-mDt)Lp8D)U_HLpi;S z#+tV98w!JQVh7rXV_?DE4Va!~cNRZbGIfj2Iac#%E0D6M?}^3uj~Mji_*@%OzErUD zd-Jx&KuG2G)YYe#4TmaW<@ zJ=JpEOKp(Syost&hJo$VwfT}rgt|mIN)+%B7b<3L8mSDs`L_J3G+YhHza-Ap_;oPi zFz+5ii^rq7_)AEns)^^lpy$F*;Av!}-pfg>iB&<|YJLilCT3<8ZLDYc8fNk68=e-f z0GGXjJx2HxmT0Y@Dr)_Yyf<=Cgi?Pq`JLHPy>}@>yhB6rH+A#o)P8)Sg@KDK$$u3S zcqSUTkXvAJv&(z9`ZG!=Vzv%axv+{2!UGdAQU+-a)nZim5rK?qWtpN^E6-5w;yv?Y>916AXiq-i&0h9 z*ef9L0CUd_#U6ZXF>LnJB`$Gap292*251rh{>_VXXIIu5qouRE!Y5SfMJn!eyd`hw z%CsG!5;5Y4VDapelX7mHy!Tl3@5F`rH0*3jK&b&Mz2pCIK1%fX<@gIqF{SfSOc*>$ z5XJD#tyP4z3tXn<2iGwaMiEvqu>jSbv)I)Rs2R2sFrd^}2b2yHFfJfuB%G7( z^!7g-%xR%#uJZJ$`ooYnnY*{Mx;Y04wK;Q75B{~c#`o!`0}3B($uYGwe@hj#)Hc6( z7Db|z#2Y`eSeElNUpyA1B_)4tinZEdp#EAVJPTBsi=J#+7VZ z-JRF=O^V;(E@f+`*_H8Lc3-)b%gTY(*u#*5C6G@JySS-xfcpWqqt+al@QGvevfM`i zNk~$m%+%DjO^1D|#_nEtbEWQSY1S?Fq~!dV3@!U_^gDRG3mN^eS%%S&-!2QVP5#Yd z>@b`h!+zB$$lF!Zi*fcb1gh|(ds*!@4B-yh4OJGdU`~A%VGZ?XOV#=2EUPHIlqZ^F z%@$owi{hrGT0BkVnqF5{e)+PNSZ(&3%W5R!-Ak8kE@l4AR_q%+AEGm*8=c&vM6sJH zSFD;UWu7p#ee2fS`A1XllEO8U%7_xyPi7aC4>qk|dYNlg(5fr7ifRKyDK=br+x_So zr4;`?OAowac1#$h_4M?tPYH=b^Q5tg)wmIh6>8_l#T0Tj9lO)u!|G!`RV}nG$94TH z^Vb}|GY9=NV&cuu=IR8f?kz;*q|jGDZpMbneZjNW`S|#3QHx>h(dA*24UYzF#b^S; zIuW6+%rUl5&L6%d>;V@|=qKHDdgf2zqT86;i4pCnrIoF$B10F{W%?$sX#75_*48`2 z!*fmV#*O{ZD>4N#MSMU^rZjP2rQLrEf>55@W5@9<_E}kojeJFvgT+kc*~GlTtaa4X zOhGh880oToDHr4UA_c%+h(1V=2~^d_`=m=f*%|crbMDc@tfdRT&ap62j$*U$GeN3) z$xKl&&^KSkOrVox@f?HcYpKRo#Q%|N6D)~yGY?9ivAX!z~6)|xd;x(D(%F;=4Z z`W=Z!gtT?b13GpkM@L7WoMkMADTSU?4h-ttEu}YAw$)_HaA}lvXh#B+LD-j2K7 zfDZda;kh745+Q}Buc4{1rx6Mt5oU`>D?SiqTB3AiP_E8Ai_@JTyaHPw;LOg=7mPWjxv zbI5^|H)DIfYX`=J=1rTkql{hmo`3t(`49uO4+`lJl$+BFXvQAPPEnb>XvK2Up^ZiF zjT&ev&J{HwRaIfj;6#L)vf;UOqbuVZ6G0JP3ctF^qmGn)Z}N#lHZjQcy~c7nDULdN z_0q?Z`%H+=_pN#g7di~UJk7-5%oRg!wFQfqs)>@p^4qfZm&b0hxlAxcUyv`GROJu1 zymEey(){2tVLDzl6(Y7gt$aC9b=!{P56N&(rO8v( zb{dy1#oK<#*ehwxvDE$Y+?7{4Th%=S__QxFJ9f-taPkS=*^XrDqTM!ynTn(HP)>Oc zk&*I>{~p5Ftm4{VpJa=ces0Y(cddGe4!C%2F|H@KVzA=z$tAX0N5{$`_>B1A@6IEp zc4`^e9mUAJB#$#uS(rAxD$HuOIedD4{B?`#z|S(eDF@a;BhQ?Ma4M$xG{x>-^%>vP z-^>-rJ@xw$Z)}vc_^ATb1u z1`nK(2)nYr{{Hu#{3~wN_4RilXA$$2F|5UOCGR1(4|{2vjvRiX{72`>pOrR4fhYj*KFAvtV!HAG zvxj48CeI9mBO>74`ud)_T~Zpmcx*tt!Mbyn)elG>b}th?<9)xT?Xkqqw>6PDGn}yn zRwCc@!+RHX_cEDZxj2V{qvGYSD*po#op*@2n*)&8LMzkEkhM#)hcdnDoW`_0{!CwU z{Bw0H7wyr_{J&i_uLR6rsdSBxh}{~F6&OIp{|54&bKFBXECi{1uigyy{G>rT4+sj9 z8=M8=@i9~t^FLpGWdO#tVB!&oC`990sDlsU+7d|!r3FC&b14r9JwAU&C8h4$g&Sfo z+^vS>n!Op!swz!3OblfY?UFsjD7=qect52q4}NhZ!$_HLeU4=|{LP&U?|ASla*E#) z(o`Jk_HwxxWAFCrIv>Aq5R7-?wQvPL02q4+6^4h+{DSYapv4zDll=@r`TLLisc8** zTzCF?IOvy%T;1u2x|$gd&GHZrI$mLNUg3vFOV$F<^?N*S$3=q{a!7YhGm z5MKxq7*HCQ@?lJt$gxiu4=|coNWrPi3)M=x)t%$LwW#KIVgm)|@*-zTYto-?>r?iR z7%V9RFSE$`l)HJ%9&jRhz3XhFav%_xnqRt9ke%^o^iIp8oqxGUx(L`{`-aIy=k`*6 z3Gah1Y9iRgV@pNGMU7&vVXk5bNta*!B^&Q2m>uWhh5$QCTc6qi3U$g{KE{{C@ROj1 zKYglknX##{aRkyuyz=tJl`Ts9wVz(q-`T?5TN7I8D06;VGdqye_IFR@>e=d*t>Y!4 zsS0^}dF=OxOq+0>xLtP;s zeYU%J+?UlkgUy7O-8oGd-!^Oz4eYyl)F383M?l3F!RR3C#{>TN#nal9P9@Jik#oH; zR4?oyDK(gFsn;Jp#>P_U!(YETF^Pwmq%QX2tlZheS;@!cAjyA_rIlU#%*3Bpo`qxD zRkt1zdx+<({}J+OE1t&EHPzwHxP2$@ZAzJ^Sisu>c4=4rFT0#h`u8}*O)LG|ceZ$& z^&Elu^g$G)3gb^t@i6LJ=ARbnl8F3pN`5%&Ir%HD_O3})9v%vNxiMCel@aos)b^y) zOzPPq-p9Y;&~JVl7wWl3`LuG9h`~Yw$9ZB=tq2Qn!xH$}^Qdj+A@B!s-$&--`x%zc zzB?Fx+973TN|$?X#%a5)OK{I@M6>Z=My*N&+iWaTI9t;VVryf0;IP=l$t&mMOLeSnKrka=vyn9`_pLot0(th9;y?b zs{bOLB8rrawQ2A94^yau2n=`}3C>?9p!O9)@l(LmITno4Z3z1P$3N`su4 zsWYcQI-3bpeC^0piYXa!mde*v{W4-;G^oxd{(Ds!sMUbM&ckoYX#!K>8mt+GWA$!R z2A1>Tq-Cgap0Vqe#^3vsHM(|#ddDnvl8+imG)`*q@K}VJU1PbvhF<=G;OS2=WjyKA z(K2gsUogNw^jbc}>d{L`Mq$wGd~o$LsYDxFpxE7_QZzN?L3pr}K3A9FJSHQ0dq(Zu zEeDE&^t(>Q?EmnMdPrQ1hmPu~IL|i!L*g$QOIvDJxO!(_#-IU&y~Cr0DMM?$e=p<_zsQMxlqqOpBo6lMxsX*2v@X zqhF5He#p}Ey1p|i_(1ZBh4tvm#cz8^c!qD{lYhca6R}JmEp=_JYF5^q?rP)gn?3vS z=6NV|o|xU1c_*Op@ps!9;C1w1^Z(feN<~3KfLhq{8~Nb|r!#q}i!pK@?Y5?gW^=g- zHFx@D22&XFw);e$D57+IRC+Xzi`P~`B;zrIxnkZXhJ7cOxN}g%FbJ6k%^As!7P&t6 z^=xE~U|3V6ACVFd&m(Zpt^Z+XdB0r$?EOCFPI^1q)0#bXMZIF!nU8@KGNRv&a(v&u zIOC7uWbmL{J{Wh?*%Ut$#mOOF3E78Z@~)&;+5O+|&vU1mp8ufq$c$|V%Wki7Gm@PN zTgjh>#Gwl6;5kOF@x44ULA7~e>1KOcmif5or$9CoX z@1a*es@nN>ty~xFIQS2Am1y`A^aH=mAM;p__{d7w&6qb|SqIJ{4FP^)KLHJ~8CV`c z+zPQo?tTj({e*SV!bN1OlNhPv4G**?oWRokUChB_utPBN)*#*J7&=*MEEP{gfK!Xb zB0)e@D8l+oz-lM!1r*o|2|+0JWV$chsD8}Z029KKlV5!r3Lj&n2L2jU(3&AxunBO` zR`e)_eZ?T?YZ@AI}*ixBl(!rVxI^ zYO#DE_C(o6G;v9lldgm7JYPBlZ#wgp@A{F|MEk_68Y3u9zNl7*cGRVmQLO8^3l z?gQa+=)CM=SH~Z|gNf;cz5O9#2u@gi_x)N1+@k<^q^Tzba~+6FQry8bTpX~Xz(&`Z z5BonY{4g}zb1wf`ZDW(rZz=XVzy3chg@atC!pd63`jVq>ly!=KrYm?C87Jt|eHty_ zaNk25*VKqBeD?-VQ*oTg)miN-88KaZdN<~mi*z(`q(c7#VOsH?h^-c~=SOWcAdvKb zO#KHm*Z==Mjz5itk&wMd1KE;IR-_WL%E+vcl^Kz;vx|@V)Ux{~zPLHU>N5F46ZQUHD%Ubs&6712<> zoPaYy9ZQ?}pU>@dk80Z5P8)f3 zyqsmf47xdsw9Yomm&OOx)1dDo9TG?a-mxd)jtkC$d9|+?VP!XE4?H%sx>|`;|MqQR z)rc}UQzW1&I&ie5+`D%#DFh&qJ-EH{L8}L#<{>9R$YtuYu!0ZY2}V4r9vo^*tIIq* zJl5-qAW9osT^_;?1%4(WDJNHwREBq!rhG}d%5N?nr4KEaJUt5kN5Nl)hj}+PHhN3a zUyq+SCo0?GrQg`qTr8m_P}=F~Y+x*Gm}nJoZKkCzOSViptTt6)Muqq9FUiQv2MW8p zBbEZNo&^gGr*!$ACLWq`p)@YcewU(|`5;8mjLwI>ItMXV)>~9{pMXF&F-{V5!G^yg z{IG$h2jq7P1qB7@4sB6iq^3iy(s&3) zSa0|^2=lKI4PtY01VG5=!alAC<--Hb8VGrKiF#HNpcS_CRuZfYw(j1$mz;#HIQ2W| zf^!?~h0fQGwB)W;Bzh|D$f$|Jsnf=p7|fQtduhLfL`l_{t?QfJKKr}zp!hW^ub{v$ zC`f@KF)}rkRdsbjkd1@mYl@jQR7M#X&a9%Knmg^^qV(4Sf7aMgsC~X6649xiC>Gp+IVe3M%XNA19Zli+6oLy` zF^D&Vqg3}Aef7|GzhJTm#$DNC%Fnv5=_Ok~)cTcwdv5qOBg2f!`%xAf3LRb=`Ge2G z&h58IJIzU*oQ!?Ip_0Dcj+K4nBEUzVO6n=gEj@p}8L$dvi`$-qJUS;L;`KO!GspGo z*BgFM!uASpPYXook;zFacXxMGusqRimHGIL zdskXo+qxkp}0KQFt&!_6J3zSq{z zQlOjnu+ay_xXtNC-PPjal12lg@;M@tK-Hv^Z)DpP#Q+B2RuW#$Xq?;-$)j$}-`JRm ziFykRRw+0+IsdM#ylQQwiHL~6^%94KMYKYm2u#Zpvk zs`n$s%dV*b#eU|{p+h7H-3#wCd;QyS|p5ayfZwg&@@zi%<3Y zo9I-3a*uG^?W7Qlt`|mrRbVJhukPSZimp{(;`a%s7BbYZdVH&^FzAUPIPadzd3vjN#r z@jh0#7HCm=P+pT+3cdX8>Lg)MNeEQf8E;>9p!AlNN(|swRiSh zJ%URj&>cCs_1Ud{K&a_O70iHXSx71qIXyQQ?ZweX)G@8o4X)|q^Ep1oL3WK&+Blf_BxdPdJ4O(rO%HL?DP&zO-*cP6EzlI z-koM<{ZkE#m?Jk;bfKuIs1y{FeeLT7Mc(chSUmOxITASuFRs3|bvk=tr|(HWPtU@a zgC%Fw*x1;V4{RXM>$SRpFcmR;WwW+FrRjBUxZ}Y>c7^uPxQCq_dfBKjL6TGYIBWSNSiLy>Y;o- z#2JRWq&MKQgTrn#*9)o-E%8Z9Kh+;QllFm=f#G=hE*^&UZa_}bGLH&s%fk7)?3_fXlfq)v_T}9<;&*W&7QN{pt=`$sI+a{w!i=W0b}7Ibz=JHRLjPv zEr>b#y)B?m#Nsv~ks@f>fAy?vuE?wbI_fRg)X=c8wY`cNsFCgK_qO#fTUuFxS4?;E z)EBago|Es@7e`v%jJb^DKsXLoRLVzrlzL6a?;WPQ`1g*TJ-$&_hqUo^+Te2 zG$!N&)bou6#l(4?JXhqar05Rc-+I35EtObxMSa~fnPWv2zJwEo;$9YY2l17GBe;aSO1V5BK#MnPTt8XH1oM@$FK-~lEsuGrz=e>*^j zeT7SZWM+nzR1v(c2)u(5?_P+BS~uUwKQPc9shaN5qm7^s&kqy@WI4byf*{WzGm*Ub zO&6?h9<*#5ajWsCsIKLz$jI#4vSrJYr%$gvc(5;ceaQ?3gDs9w!ksI~y4cX#U2?_h zTB7sW-rl~Qj;N51{K3~v;?;At(xEAVD-nxZBR#YT~Bm9)(o63{1H9d^q?%(2* zD5G3S&ljq(i0{7k?!gHSto1d-+{fteF_0e8lwJKdZ3SMp zI+O>UbyqeOvm~$>Z?@HUXpAWWqP(NGg7B~IYaw%4y;^D8TJkqC%{$W;l{my3&w zhoc%^OvnQ3!3aZUtO+pzSWy&$f`Yg&!1#y_iH@eiHA*5ua9S8VihLi4p? zV#A-aJLDj`HTJc?bC#GasglFFlKP>DtO)fZK8c&T507ne3@69mlP@xVW7ZS;rdPlu z%lYo@{*>NoHWm|)esqkS@0eeP#xpM)rVjVO8evoR_gE_J?lwXPe}{7!2QxD>(P9O) zW(V>?J3JUEq@bW+Dn{MM^WU;-4S>AAu>9j=jE4Kj0n#6uZ*D_1(KuQCY$c|TO0g$! z6>KL-f~`oZA8m~6f(@SesZ+gdSKux|z~|uRSX#Xv^%Cl<-J9A_2=CK?3Gm9+WYWKzhv~1vJ9_(PUV^X=H~3UU1_I`yj=OU zn`dM3@$Um^xK&O~=#+YI{d(r5G4_u}1K zj6^{s`hPb+ItNlX-jFbDQ&m+(LC0c>*#os!5+tJ0R^h)O0M+VF#LhdaK^*%{AJ?O? zZewpxLBgIvqM3m)?ng;q<}UmeB6!rWU7N?|Y3S}|#B_Ct!4xneomXK4OhHXw--7^< z$-KP0i1P>2Hd$<{Z6{BDhPjNcyI4Sq-glW`ou!*)+5d&<1V@YP-0f2(;c3YOw`qh;}Dhsm+*`a*x z!U)fK3K>qf@nd=JIAVM$dM@juIoGdKg96Kq_uAk5pW ziA^)rtDI+D8~MBGvco>+?1wdzK?OIETnP*9->(U(^!z86l5AW?LSkaCQ6OPhbe(B} z!lhrkH8eHhVMdDp!zV0Eg=_g*dAXdpCGaM!sNK?E++@TW)#vuN6kAqaLs0w*$(9a$ zh7)hxxCZ{fJt)Z&L{$;N6cznuV}(!NumA927oaI(P4V3y(KJC7|FET{si_{-Gm1PN z)N!NVzJ=A)C__j#23A57PNz$o8k%B66i52|q^xWU3Gx2tsk;1tw}&o78APg2jL1_O zm0s=iyd5@Up)LP=NtSDMmBVuFF@1Lnh3o32hIfUda{@;Ya#uC;jRe)6F)6%#{pO`f z*qvm7Z@XCqC5kAuE13-*^_<8GL*Bv@{a<2!Ix#UZ1Y1jO*%MVJa6Gjhb;6%|XI!n< zI2~b6`{3>(9D?f+vJD?yMp#0`tS9Wdxz0SXd1apk7r6vLfCZ+D<3E0U1>vTkcc8i1 z!0x)_|4Y5Dlm*hX26hA3t*H6gwCcYfVzD64p&PFfLsG9=ZHgQ*jj^*=UadK72aP9xbH6 znZGdP<9N?U;!{!Ku0uCdu!WU{#SSV;pl$C@J`+mOgS36RaEwEcL2LU(I-Sj160hD3 zSa*tk-(%wcL`~PEGUx5g)0;^KA+cfPYo)8yw<8t#yr0Nj3#fQiZ&26AfQ8mIPsyit z*!@FLWn;taupQa#kodKnhfZty;4T;soAcujt>3i&e`!pzMWbrWTCCBxbID(Sy;35P zGsa8a{`l=bT`_EUk_S@8$5eOl%j?=Kj12WY?B;(0!GDmCxss-7l@eU)Z|g) zqZ+5|=PNPDsVSiT{`z9_H#2;jUsseEO|!pQZ^4@1lP@P1QJMNtXjOwPmQw-fMcM0$GgniABeK5?$oF7^PbB_&6eXS8<285KADJgN}2DMJ(eKqe`r z$!_ufyMY1oiS77wJ*0|?ijlDdHKLKr#}X4Be{w(S8vNdS>#3s3iG1T+WJ-w6LT^NC z>tt+A*)N_vnt0PF_S)m8TP^OH7z(s}%se7rEkord=1RYz;T5JlM^+d+*oiFhwg4)d zx2ay9|1`vJNDNEJkI#l5r|Dk?;=#j`g@CRSiUtg^{WMzqb%Dr-$9EQ_w_fP_cVI_yJw1$*Xu8beCb`N68PPHfMD zs{~r2+cV$iLkv@4olF0na%^uS4h`X3*nq)H6Y@(Mq71Lzy`?nO-+L%(P%~lwLt`T6 z`;u(;EssfXe<8}HEpLv9U(@^OalQLP@o?kvO4mT`8lxhuYlMF8?Z!{9jVI7~(#o?> zm!t6$4-v3e}t=~*r?KHy_X*E#4C0_s7 zYbvC$SxxUd)uP45cBE6ehoLC2TCkv+llJ(Eip25&w3Ne{DCd({3QYz%hMUMQYbwXP>mPc=<2G^dE${5rgV)Yt*9-hiG80MiKS&rwRXN0Qz?K@2QU zBdaNrJdjj2Wwssq@|Kp3Ei?2=h0N(CZyf_UGd4EQe^vrk)5CRYFyQq-BtNaD1FRE= zhe#2)xVe4OWoyVW^;O!Cd=KN4UkMFu1((X*#xg$A*(vL-e ze@}-zNkb?5^wSqFB5G?@Fhj^$cBrG{1j_#?-7{M#DQ!Sso`839r{VAG#(S??9ObMi zTdRIzfdcZ=(q$q(zx!^P*YtzRl>WYHAw!@%b7|zs(GNppgL_vROK!?=j6`fDVjvTi z4cjwrdY`n%(s>R)$-GGLbU6rNNT*Z*-P+n3(7LYEaZ8}m8^8NyA`tZYN-e%nD`Dh( zH?7K-y{~$c!)bS9)@VfU*Xbr6gqC*C_9zeaYBKBjoqiGK4UhD-s)%$M^P}*ctMofs z+uF3CoAy`(E$q~(Nuj}K&^vQNZFf^qtpv>zq~uTN7#X^9lbKEVmvu$j=Z>6>^a>C- zbLI@Hw{75ZMAX&Qb$S-i0jEP#EE?%TUjW#tU7;5)T$u0D;k88}lBhe7cZf4jnyN-) zW`x;p+p&r|>j1gR?Q*$uU(b3UL~zzVWouG9uBYh7dSraIp|WyM017JXan0R7h$f7o z=H3!xfmyAs?LUtk{+cc-#N04jtg#~@KYtID^)VRVW(OrS{ldwJfbUQY5S=+^SzkCS zDC%o!RsCPQ-8OJaX(Yd^#LlCHm5*g|ZrSP=^&TdZcT@kE_gs9lKN+Xd(A3f-s5&Q) zIzQY*Xn(;4ra{VYDS9Y@v{4C}4{+ulS340#w;W+FLzA7$AghSNMa#oO($3y~`gaY2 zE0lYTNLsEzJRRTN3mJN#BxtV%#y)4hSwFEMD-Yb=EtPK>{-~wf^Cg3IB4doUWT|vm zVG8Zxvs)9{kK!UBQD{;*95!OJcw=KD^vdj+mVt1VD(|{0Ai{~k>heyh3HBfuDiKq{ zk}@*>HF*M-)_^)$C7*zd^+i(b;BjkS;lV#MQxTJ0tl)pD{#7e^ruBKcGQGIpVdP)r zGs36$$$0ELxKqSrJ;><5;#vL|?Mk7I!`3=kSGMGsoRDM5CbDimy-MnZ2Qr>co7|R4 zcj+!|VXc~2KocSy!JCFV^sXT)cq!nl1flZ;*ZAygJgC9X?iw*9i9h*^)>r`xs{OF| zdw(3H(>odN>h8U1i#~%nRzXzs_Sx*GM#UOV>W)5A0iM=d2#iItnifHtsGNyZY24(H zRRU|mT@yong?RVtEeppN=H2WQ;sr0$e`(!e#I9t`;1p^u+Ke_Wp^7b-9til0S!NS3 z+QF5u-(S5R(2*p@sl56AOHr*HeiT3vD(H+D7)ampQ$}Xmci;J(8G9p;S)-+J7nc0- zo#Qr6?&L>x?9G+Z>ljr zOV4%iz&3;p9za3g(!eMqu-v6Pu~EMn`j-wo*g4#YtJxOuVLLT7qoUr;-Hb6KEH*pJ zNiN=5j3MY1G;O9wSL|ZPtsj24PN*ie=GfJ@PmOLp_zpx{Mtyxf zDZAjhxHPNsCAXl2Frs0*o%`y6-U9CFaM(Jk!cbyp1 z3=Iuw=yV4D7sVUw;m}Tx0_l7w`*dj2AzGS1d+cpB&pSvNYLRvg2_JTIrn)$n%qLgI zcF9lAL*m(Ub(Z_XFVjLlvwF?{8<&30`z;Rd!e-7%+*?$jmX(Jm9u6J0sL*|a-C$rs z6auh9Vn;8sJGkP3fe_PQ=pRb`7rrX8c2I;QB{9HeDL>Rf3lrcAo6GDciBv)^=Nad> zIp!-;2a+1v8@YUE&&qDNOuKFMvJlK3lRafu$_AZ7coM@V?QbjGWjJ&TI0a*Xz%`AG z7-@Ix=z=1OsE8pA8-3BLWxW63!GlkpKi9Ig%|>W8)wGsbTv~b!>06~$AceiNW0WsN zO{ZH3tFefzk=-JFpb#_brmnh^dVYIH)}?3k8ABVp+{pUa<^x;~?DXkbQ9!((cvEs{Fsgu&pquMgIys0`B)yBC+X#ZnI z#nU?k7)DrUW1|*=&kwyy;!cb@0Jq{$yf&4^4qbbDd!i|bi41j9MIoZ-jzL!f-Oj$d zm*U~P$OPQ)D$7X*SyyDxTI)B$)XIksshg&wMM@v7{lrHm^eD}KdD-%?XMjtBifqR0 zsFNy!H|qA>e0$d^GU7hmVNKBdz11nDyRFArHs*a{*VD2xZIs(Z%>kD#@nbGkOEgCo zyn4o9NX@JmVa*uxy5iz@YOu&pNsLc}6@}@Ie|tOul@o5W#C-<@sZUe7d#iQ|HPtz3 z2E1%!Ah}PE8A^;V&CSGal)Q-W@@frG;Kglo(imOb51-uyruy@?`dQ6qX$cT9;b8k- z0xe9riEweu_NN2+{(Za9&THuTL~kedcs-6+jR#oW2aTy_$Zg1xLM7@}#Q4}Zm8l5S z$}!ac*tYY@q5{6X7#lmgEyyL%f&P~9(Q$p}Ak7Xu1-*yX3Xe5qy$b`5d6%=7i#zgP zeSK66yOximyL$pixc9&dF(@?l2@bw=NrAAw#J|}f7AXF_W^G~;4!a>nuqY0l`uL2s zd>Q>du;f43N$1?(4?}N5CHK*p_6#8jH@4TkEWXViJDx;X9CfMKGEo1hjQg0VW5d~} zPYzjI-@V8FTN~}ku=qoY4KXLR@}j|GphSAF2_r8YzjjEoPq~MWm+`a2NI|+mFd{s> z9pqes1XH3J26~yVT3H#Mx_@Y9)VOIanDcGw#e1!efBv3K*Hz98_ob+vUz;&~@+9zA zdjb+3<3QokOY)VnWM%>%?in3?JUxNNO#Bdw$KT=9+SDyvHEp(YPIT#>>z0BC<;L~f z$}r(ohvlxEo#kgi=ga1`3`Cxl2ObhZHB z+reV+$+Ks>6!mY~*}BKxT5dkiK1xn9fR&W4P(9iyx=32eC4St?(Mr4a>`t=X%xPZ+ zow@|g)CI9lr(_8tLzgzRf+r9SP6ZhR<@0S`cNBf7`T6U1HXMs?$Y3LLJZis-u zjZakc*o08tmWaJZ$ZZZ!*ti499$B0H9QDzMGu*G$=b|d`JX||O=32YS9MwK{`jEi) zz(8ai=dw*HaE`_%{&-e+mDwIRmiVs8W4Y|wqbylVipQf^FYG|l z%I>jqNEaGYhvx+QHUb0w$*7VOI88~)pnLRdo7_7zyJhJk@W?T_qFhJ`(EqB?ZCSGc}Jy=yL7^xHBW>t-jc`IqgCH_r!+dGQBY zt6HcUpsOE%0;Ab0T1y}-GSVI*a3m7)7hEw2Denr-R1~`NfMy6cL8pBCC%D6 z+8#G9;Qa80#JIpkO)k{26VIQG-ZWRrXqZ#j_cu(j9#{q%Qejj^+kif9-?=l=a!9hO zQEvCvty^VZNXbF2SNp^0--=DVgs-d&zDiWU#%*k_d6q#WEh|N8m%4IV)m#*tnZY|3lt z$2_^Pa_y{?v}{OV;tnFKM?<%SUWtwEU`_Eyv>yp0`QkHm;@-(y3*p3y+(d{pLJ;J* zX&4L}W&alp^9AvI-047)3ChVIcZ^HQ{d*ZKmeW7`J|#MyJJ&CyO7x*^ZEW~#aw0J& znG3LE12{Zk@fs%pTa5H|Zv_C+>2r-2Y5MNW>KGzk$Y(}>>A4ZmYGi62u6gE6WAuBf ziQm^Gs*e9U$Dn8^hCQ+d3>U_Ute{Nic3A?_gIuctXI;|k$1x0K;Xu<#)4+fp1=q;v zD4C3mjJbtHE?5RIwt*APYwWiqfQBHR5GHCBesgzkSr4xFmqUoha(9?I%}Ekd{-9(8pR&-FG5r=iyI=BM^_Av;6mbO zUW$6(+3E<~X*(_LV{wAIwBGO^zU7>J?D%mhqJfYhS29#xa`OkO(fOTqXZW^~6og+_ zaHK~J2s-Ij7uVW#?<*8NIWo~Ke`jUxxK_gu4f1$iH9FG#@^Ukw(?FlK&Q}p?QtsV1BKj8!#fO6b zxqhkgp&`+u#T^(JNKjxfk&LNnQ;sN+jGzW>f*lm@4PkF4#;B8CM`^FrsjSxZaY)q- zH<$8$HB3HPytHVkayz2w*{KQH+C5eJzaz~bZ!@`oOKeH2$Y`vAS6Ke}>}@{n@%ERd zM6DhpspDVv0%^JB9Q$*E3In=PBchkb^317kzmGA(c>d&YH#<>cg`=-Y`wdL_YYh)e|HAAVg2WTNiP{q6)}u#-Moi=baVLC};} zRHWvPeBLzlfot#q<-LE&q;KbLebsZ|DCRmsAQzc#bu=WD$ndmazOvy>p>lugy$>|+ z`BNtS7+90U5uXI4r5Q2XxCN0UEIwB%TI(8Yzlm~BLx;b6KUg##Hb0py6ofx z%i!dDlChtFYhvQ#<9>~o0aUSutJ2gzAnOIFn&B`s!7fd^7`35O*Af_B^O){S*2pj%Oqll2ygJTpd4ru;qKGAAKc1o!`x}YEA=_ zO7edj5-h1Z@7sN=(*zZ(sCNZ<{O_)~f(tXJf~kPqE&1vvnNxPy{MX<#kdqq-Oz;0( z>2n;lG&*6#E+^dsTzYt61uRq>PzH>ZGoQS8Q5+n1_3Bk~*wDuj`@P$}MgvkOt+?wT z7eN?gfeMlClv0L(-P*eTw-xq$`8tU$tbg*)ujhPFOJOm#`;(gLanVI!^*z<$jNrQg zrJ1}*^gyRP0mTq70vdMoitREo2M#o$`6LBL((GLN$+)jI)LiC)Tcqj+BlPnBJ@ksc z%C<4ueqOGg1{J%Qs-`4q6)SCaC*ws&jep}{0mCF!86txQ)R0kqs3@D}Lbhm+QORH)_hk=Wn-|qzPmX%!UUt>Y zMJY2gMmGyq%cCIawtz`E4^=7^_k(szOG|>ogUgkMJ6u;n=wUMQItK7l3=Bp3S60vy z>k3X}J^g1PBs%}t`1pTMmlL5b%98DRueA4tvsXukX3xuY(!3}>TcyU2et;A6|DLJ9km$w) z-rKRYh9!2O(TS)^k=jknTrM=5vwhzqxPA8LRkD&Tjh{XmpBoqGHUmxVFC5(ox3e1b zg+Hw;vrS$x?38<@WzR^gE4_R2pW%$H-tfW^8R7-Z)({ z*1;`zFZq#ofVe#k`={kUS_4ncdMtlmNZmQ~YH8Qxgsd37)xN);O4d0#{%AZAt#X)}FoTM~pxADoo|a~Vi6TTT z#mBNZEZy}14YjfUj`p#`-Y;O4G36dNeppr~V_bz#=7$mCX~zHpjVJ>@T0t7C0V6#L z(ekT7T<>Dap3xN~Kk~47&upo7H+~@|E3}BS`YG{9NF>MohMR{rW3=DaQfFrB>?W`I z{7LXa+Rkf%mFgllCuZOqre%D^|8+dEVjN;EXIa>k%Xh54?51Y@^I+uLEv+xd#k*H8 z0T^B@6=9L8cC(+Tb{+l0RlR4|`pvpJHFR!I5cxC=wYBx*XMOkjvk(CsO7qe0-}iVk zG&j~8T${Z~L4UAqTV(&4 z#>p&q=b}KDsj|So&NJ$qRbR6_&y}uT>dOxFt23W)(%$+ zKq0uPb+eF-61YCaR@!{V(V$oNqMwWbl05(U1`T@by>Hk4 z%kP~c+UCvNeo#qoX*;qVwD=PDa1BNhJ#l3rK!xQK2?M8~y|t04~LTmk2aG zM!{&&L0&SwRHL=}5Y4(}(Yn~hz?(kOn`gAgacj*tTKv|6Lb|scb6a6d@80kKrTa$a z^C;cT9!J-b;^`3dPE#_g+3y!mT*J~mnO=-}bF{xba=2se_~EIgK!498`+Pn32nf_( zoW4vKY)v2fMd`I-`fy(_?Wldw!WUg$UB}DA8awxk7q9raqi~d+^|DHc(yb#9(Ey^4zkP;OK~JCMwz)Ks(SQlhjL6SVBi z$rX%ui`DrSA&-E}(Q)aIFN3MRa+vMh3(X07cd=c%ZDq;)H+(vFYmb_s|hxI*KGue!hv!ZbiW6etJYsyYU9E3Wk)Tg*)gM_^$2a z@O&UGIh9MdCMkySs*7IE!t^;-gNr2^(_TvdPG`^UR|iD!&gD!VLy~S7t~*}B$q_d} zQ~{m8PXaaqk$l1XMGB&`2l3p{$&gb}O!G4{Bn5jemo+4pRE@OvD$ktp*&>%CTep2F z_2I=W!Rs^1#oQ-aCN{l8!ZS;WEpcIHW=8UC8yjTmTS>tC;wmcSLBki|=O@EBaNI@e zpaorzi9z{9`ddgG&VrW(t@8zH%{+n+pgSE9*g-5;jzPQ2__f`3)b=nQ~ zqYSE_SO@q0YaFw7ra z=7%RE6yAO$lwv>kt5yDA>oYmOOI)By{7&Ntq0`Jz7d_jnpkfVgh^3#Phvntv_4u!$ zn!E}H2mv*N!m%1pq_=js^9%*?qY)iF$w3PDpJ7u|Q-hPw3!anMX}k%17Hck6azxQT z#GIPMlT}hyxvRrr^ikR`>AAUr$i;|z9a)zwmC1MNnT@YE`Xr7W z$+Pq38V$AnVA0Ultoz98>@=|TrWCkw-w801Woy=q4u52?6IG0Sr1L@cuFM~ODbo`Q*Q96~Iv+-6uoz(; znMyTh*T;bc&jZv?Tkon?(235(b)7p)udyvqQ`ELgLB$GQ4NE^k>{03Ycp0c6Ms*g< zq1T+Z=S$?RsDdPHwSlLOG+=|!?tgT9P&Us<7>w5_u(qD=;sO-85~1+5OsrPQ_Q*bS zg>>b0sxsp{W>?jO-E28*TU)xRkl?(3zoY# zO+C#d1e}i@PT+}y)`VY3Xd=4>+}%imPzFIX`EEhb{D5>wPR6vSJ;~d3OjLF_rSPHU z)%Wh3(LtN6G-Y~4;bD0DKDo({p8ShBYQFhPs)*%oYi$06jNtaUjXqx6#@nUycb+$3 z^SZgMb@C9o&A699&S5QqBmz@-1q9FYM;kj>01eH}TM5T`v)zEE_!wj8zkKEn9&ML@ zAw}0Bx9^bk%7nPLk590>bobX;+b<2{BVSv@E(%|~j@A{ag9n>|G>duD;>}KB9uyd_VZhPH@Tgc}Fj9jZ)D^K#Iv(zR{^eyY z?I$`$KY6~dy7#EJ9vKC}f=}BFdsVNSlTU2*6Q+qK5n^+hhxIPp?fbzLt$n`t!w!lw z?mJx0oh*8Pl#)c$@lAHiZF>Z|&;1uU!(bdwObgAnKX3}HU{~p^?%Lq>DlizP!cpx8 zopB0BLuS6^;eO+@y7ki5h;2@)Cg=Y+ zqWsZ$XaC%Q^(DX)ou>iq6Dlsir-jLhoXtuPCEa>bbbE zaN+85j&=;eDJArXr1AN|t9UXHt2 zY+nD>Osf#(djIK@i!jQHu3=+6+2xX#*X-4r)QkGs?=-VW|K2d;YiRka(H3!J3F=*P zXw@`YIv;LRo9AF)xQZH@CJpW;&*T258G#Bqw6IWE@2ISVM(;VyB`@eoNi2|Z0RTNr z7cajy=)<+i4}PxRn1Z|3xv@tJk&+9G`t#fE_EF(}CRmv3kDdqGz8h-g68upXd7>cH zcW>TlPha2SmsX~x5uoDHq#gc)3Jjn3gOu@A*A>n$sS?G zfNm(0B6aUGvzj<5XPJv? z^iu_={qs_LJ~43MHs^r@urFo?O;^I3HuJ_?hrjR`BF3?0=NhOS)z@!^)^T)d>N@-Z zZ2-OCrD$awbfsr!7xq>b=q68E9ZfJlL3(l|L=^ibKtfH87eklSO;Uci|6RITS}Jud z6j8`coG9h^ahC?KNmBb2|8R0t{lmw3Y*m>HVvy76h(x!Gy{d1PdW|XwJv^{!bJJWv zqh#{x#EQ|;s6EH$8dG;9X=K-f=HU5f)+#&qd?xTIBxw98zbNRAzsJN_fSO-gRA*;q zVljHYp})TzxrL#GplO??Hf1p7A%3( zNaDVYQ+l|I!SDkCQy<4=nCJEB)IZPpr}e-tp1*h|;j~v)mKj76f2|!^)-ux48X%hB z7ygqoIh3&-wpCR4(V(bLp(I7~$sNPYgr+zQi(37y=&$EWEwI_{2s~ z6*T1#Yo9H*My2?W)v#`;p(HC4e6NqUv(N*uUai}ncADK;m>oIsS>U^WH{x!B6W8Z2 zp0pkHTd2BwxW_(Cf$nICEeVWW&h3l;S9vh3;O9)aWlQf#X@Txe&DY~MRu;!My$oDP zjZ?yaUC|-P6+jt|{z8_4&X?HAnCo_xhZo=OPp+s?+Y)i#$bajzPP|g*5)c-L-z!`_ zx^4EnmECHd8*>^V^NGwV*$r_DG{a7`!n zWTXg=%82r|N8cAcjK-5K!YO)8tgQ9}lD7&OC!^1hHL3cC)E)otK_)}|qPT~?46&j= zdtNye#D`f4aod(FIc43)F&&Yk?r}!Axx%C*nnupbsY&T%&0;fAtUvrhvW1ld7?RwC znW(i6ABL6yz5w3DH4*ioSs>K)_$>p8(%0+tXQhAq_hMpLZXnAXg0kC1<<@k7*6RQ69%zK@9GpWK zcwe-H&j(` zu~8F~kPI3?o;tlSIXS6#i~vS|OQO$Ev*x<<`PAtBE@f0@=5i{GfY;9dw`aCq7h(2o zOK84ZA`bSp%Yjs!$~pF{Y0pKunptk2c_1JVa2xH*(+Th2zt?tj%%}T6pCn#NFpZET zv;ZR-TXGHRd|Xu9>@|q&9=C;1TVKt+L_B-ee;@qg?JEXH?D(2P`cK<8e{y@`eW1%o zQ6T35`1C#;CB`B?$pkBWVgbyJV}F&oYVVhY>HeNsOlEB@-AEZjSE;bp19^419*sz8 zaGzAuOjXf>rK$cOHPPO4hv~KKb_AO%5OG?Xho-fY%a`AB{OLWD}vf(OXo9qKSEo&o) zCGzFV`wNx`1pclr?$EqC^W$6-hjEM*DYZn&9GMJf?zy{~!e+Lufe%iF=xg<84OZTc zEc+mkly&N1m$Kd4^=X4$sXh^<;m6Ru#xI`c|Jp*`mA`&n1ZE?M=na97MhicqrHz?c zO;w>BSH`LH2Tp*J!Z|D-{z1v@zl|}Ofpqb4mJb)}a{hT`GazJX3&;J;2iRTs*$qD| z_y5aUyF6=*4Yqo`(AaeI*nSZaMnyWq(C<9wLqDqh4$G|mDASRR+G6FWTG_7+fQP?Z zmUgk5|0&585-aFA&jS)i%`dAHMKkqB^GqK-8s5gl!h(JX7K86UXfCN;?9@|Af=X0c z`U*1F@OaH0KJ*Cemz%A(&rIQ2p#rN5_9gpg+N@F3XC#l?od$=(Ty|yqjrPG_3~(2u zBEqVm>V=)r$w##}JBEUEv^{HsNSm|W>WWbXA=kDTc1GD~(i@8!zS2J+vg{@*)x~CU zTP#|Pky&YZ$ijdUT|#wZtu16uM#)1gzC&vM>BHg{r&!d3RLA|>+h+;qFV8+iA;A6(kDt4(R*$Rl>f{I_jw4lQIz8H%TW&}(cj zjGVYGKzu=dLBV!UPtT~B7)|)NTggRU{)+jK|NbPyL>@m+E9@+VZ0zReEfhZ#LmG=t zaa_Nr(j~vtqA7X#`k{Sci4y9EFY~<6uZjQt_qJo!f%AEB=}Ib{o*^kWP9Ki?<#6`w z)Ag0Ach65-TerV}rJS?@qehe{8hA5*&R)9op8WiFVsg#qT|GVLTU5!33EKVUyJUO= z?wRx-rtW_=E+hZqt18dk1T*W#?fhXqO6q~Rp5EVs=38BStsY#I)#TRPv8OEd@)5A~ zw$MdV&_%-3dT+0f7yk2Q4WYkx=rwd}FOIi!L+)RX+7isN!s#E0C%%B9@Udt8)*ZmiL&(4M`V!8hIcl!~4lUK;R~ zYq=&E)Ya3jdqhl7s}jST+n2}x%nhH}=Sr9%e}=Boa-S<-SmKFj;1K_6R}h@>b5@!z zUAk0y%0 z4lj9Ba?z$uA9}=ogF^w182<)ZTOZL`;J@WT(1B-M{!(PvAPuJ4s(FxdNvGq0(@C#Z+kdiDCQkJ2p%|lKTqoifg(` zIz$Urh{#%83Z)mY#;_j)Q`4=a5s!&Yb}YgPv(T+O1}@b?E#B*DzkMaFpukh=M}l~# zRA+sreF@XUP}`6XF1+g6aWv=LZ<}Fjbf}*a=8a4pY`nQS)K|bGrs5N8^?8SwQigD$ z!X~yfuwOE9Hhp=J!3gx#&6roh|(JT^=;uV$gV4 z1xAIH+*N#QL+bte_pbra^BJVoP(8vWzD<+GFRVCvZ}FftjM4jEYgGSxb|8U%tl-TW!(9WBUYMIYI5&cor9dEB#a#KS7RUQzywzS3CzqNx?9 zZTm}ACV&0pudp8%l91R&LjB$A=|b#(XQ4#}ytj@|7+pC(^HBz{+o9vfcUV<!BHK5zh}dt0{pR)@6yN$*iw}*{h~xp4^dudS(z-r!20jkJ9rH( zEn^>?$RdziOL10^W{FW=%&NX{#(=OeF*S9Yii*l}=U!?80(dKY)s{_5TO0f_GN`7B z^#LdOUcSk{pj~(+V4)MQL_%UG@k$OJj0g=4?QRx%XAJ)r5Ga@KDj~_eV z9hFv4VDHFM(}96Bz_Bhc3M9bf_Fk-^!Vnu6=LJ!h!w;!0+d{m z&@4$yYy}u%Vd(84%x#1x3!wrpc3YxI!hc8ZGO%HUK7VI%=0!L2w$@fnwBKO?cP~Fb z91ulAU!SS4&#qs>lZrW8J=6x;%#fIJqR6*6W~bpxVXB$zoUG~=xnVrPr?f5DCyV?M=wB5 zE<#Lf!jK>p>FH|7CuLCEC;hqqE9>P;Rbr^>UQi7khQW1yGEP`tN7%CcK%E^6#c6JF zvAA&uaFdVgK(QNV%i+6T`V-z?Y!+t$2jb(mfw0DAW@Z-AIJ&*J=>Gj?Vy^a)W5<{* z`XS$BhyTFgGiR~}BU`^;Uk{W%aKPc$;-ceA_pqLn&RmIfUakab1a7U-E04BW*{J5R z^ehz;A-(=*qeQwkH}mF>zZ;JP+p73e)7tWCs#KXPp(4KRdGX?nfw?X9&o6wuK@2l$ ztNQx&D;vZwr9lhtf;TV|^qZRn8ciRd2*5v~FDHXk2p{$smf&OXSqF!_fhuAe$t_5y zv5K}Y+=skOl2uwrLT(|_^n5X0DZ>eD6_VV$CIQ2eE(ak)4`g9xc$~2t3QlF*S!3-%p{DA`p z27;cKmAzj64kH<%=kCG5s>Gy?v*3#{c0@OhO5%hXwb%noW@bO>AzvlKiQ zEK;GW{(z~WXTh(s`y(eS>)26RQIRGrE`AnFdVyaVa0oQ;&4Q(jZx))%(M9EXd0}Gq zc6JStw2c_YC4=cCwz#vjJ~=^diaF@oUoUa6vrB;z5{qwLodD=(pzZ-S?Yq_O+Y8NR zGp{?c^x71=A4wf+o8C4zwR!;O_l>Z`qo@^^Zz&w_e+F&#GW__ucfsq}8AV9Xv!`p3 z!?yt!Y(L=Kqno!A+4Vc{7t%);S1$IxA74yI#Fa)!VV+8*-jI=#J4Z~OYHDeD_K&b? zbG=zzUHxU8ao4WK+5QSo`@Iq;yjB0e-RT&}j=STp!1IA^-@bikLA94oFf%ZyKkwoq zLtha^rhap(}u3H$&96FWTPzziA^{br(ep2+NM2yDt#6=?J@Xn z?TKF^4BN3PM~lW`I6Cw16K)Y=lVXTCyIOFKZVhE>tK;R%m(##Y%~rgte5rw!6t7j;W(RzJ4_-s;s?6UKMft%|1KPlY9E^NN4TBO~i^9yD%C$KRARH5$GTb zxqbU~=fQ*)`_}I$Bt=DO35h;jwzZZW*&>22Z*Bw;w7|2UfqoLiDZ)1dy}(5OZDLs+ z=RRjYco5msV@xoz!Lzk%ZF&9L7S}trHQ`9|`WCi8Ghu+u#wJu2hac$wcb=b)SCy7H z_KwH_O2L_fo7fR&D+L7wg4v2}1u47e@6%3B!h!L)lOgsYRP=!FwWk;^m&9DZKCliO zuO^;y=RtyM3hPHhBO?K$TPHOGa3x`CgaH&nT*PbkuuijvA?ly7i8=`#4X?f57UUp4H z?+wCX1h3}isad>`zlbqU;Fl8Z2wg-JN}TAQM@^}B?rec+wiIU6Uw|i%^Zh#@TH3!$ z79Lq#5hzJXNg2gmM%XM6y8oIQg6`7@sjI!77d3L`)-^cI2`%&Fo*XwnT z<9M#4U~PDPb<)v0*$6_&u{>MqehqQ6eHidDJ~2TCQNV!0!o?+0;C1K_wUUw&B$L!w zYaxTw6u^EJ0Lq9xC8rs;AmK+JD!MVlIS?81J%7lf<-RNQLHzCj#Nf%MBS^t@C#Uxo=l7YS#=YVkG!nP0zT}F}cTQ^41Pz zuUQKBN!RXCa7RBsW$|dAIwg45V|h^l^tU4p#4Y=LjQi5`{rZP+NgKbsOizTO2M%28 zm^Q((LE;X`Jw15~W!?Zv&}I=xT?GRtnFtU(cq#tAgr+t5_SPA%}!9lZ~#cU{0^C0 zX&yQmr&I|gY6339f=;L@NyHv%$fM8Gp*~JbOay&WiN}7(WCi8u!ZPSGi;AmM?tgzq zs3}y=yeut!=zX^44HZK>lg_#08mPU~(?#Kvs=XVALInv8*$|DA5wGUMY4dSM_t;x6 z?Nu3i^7i2K%I{7Ni=WmoSneN;jE0pp`-+bSfPt=H2<@G%g$#hOdtIk7ThPMDQ za8lsRtzkVA^cm%GeE7(S?erICl(i!^&GY4e8ys+l~V@zg#Pp2--jD^!OanZe6hN=hQ zTg#D#M_^vz#(TaedDa})fp*g}TCtJRFIdBt1t;&{zRg0McnS)AEA5+tDlxcL8ummI z(}4mq@+F(6i3xY`T2PVN5@KRq<45EFYV$Efk+Izc8$sedj?(;3dOL{E)0Q#*Q25flos%< zr4f*H&eAdw>4u!Utky#IQ;F*I@2^ui>gh%X2BvT%IJmk7p?kpXPX20f&UuTn7bk|k z@K8*laE5t`1I*2u*uhhVhN0LL7^cpJz?oVM%W2I#qlm7HA9`K9szVS!qi{#(_wLzA zo{o;|C%DLb6(*%m?d^3?wv&(<8M{tSBp1JKY^;Kun3apGQR0SR1L#tcJL&ord4nh& ze|}6|kE>AAz~DL(?Wk#K`#@0aY>`G|1i1+$^MPoLaQe3*w*+k+B1v-(t83KO)qy)81GxWO?t zk495EW)X!*4fn}|PnOqML`WJ9XNE~#@;o{6aI2r6ZLLI_kD7SJtxdb7ySlg1T75ZY z$BWk5vc!3W=up?HRrP7;Q{R`(!)!F1#0XzHN^}aP>8u-uS#A(dg0bL25)= zGlbICfSh36xKTJK^XgR!iQZ*0fZcXIj*{Ts2k$|0#>!4v%7(xQr*x%j~2rn2NCgg|M_v8NdE_{PM;=0c3>ncTU%K^Bp>&` zaf1;rnw4nNm**KV7x6H*0b((Oi24SP52pge$Rx)B)((n`iwhuyoTULjzg0vb2&{Mi!bCu#aS^0-p*4Z(Te)vI3M5mA^hi)BEOq2XBuWPgkeCyknThG? zH*jH)q)jYl_nF^YAttXkDJpU7rzHy=JSP2UIig}nlneMf9GA2d#J*K@b)6Y7fWXlF zD@h_kX-vK{^j59!-<#J55uy9H)si2r!|Nc2THmI*3klDO)+7VGrJzLmvO zBNOOKEz2Zzu}~_zy0&6S)EbJ+g;okNpCd2q@6;44B3K~h?7{U-Av@0k&To0&al3{t`PiZwuX#zrR9jm$ ze+~_q!xZroT9FqoUfhm}**<-EE9z9txZ*)&NSbdn!-tOzn*0 zqz$v|aSno?&VFy>`5V0<+H9tiMdR!5TgjK$FKYmW=Sec{d23;44 zvbhY#Emw@ljg)2mA-NZGz7E9-IrVa;umGOG zzHJ-NLAf&XI}C$DXbTDo0A3tIOHRH8+$MYi0*5a6;@P9;B(YhPt2oBZaEEAreLMK) z^CU$Ij>g1Kl4r}BY2%#bw_r9mHBP?l>}=D)6aD@DpNl^v# zVNauXQvnsQz}s_x&;v-ONx2V`udlD%ZIrM}a642WWwoZge(+D=suqPU%*a=0c*}uH zzaKON${bOY#TeB}2hTTN{$m{V+FuQ|wf*qO`>{N6P1M2uN5%-lDAI>e{0?M098*^( zhY4n{kyvG7HpbR~06GvA++%EX5-W_0kL1EqIQ}4R*sd1-`icnnR{qP7A`Ur1tFDqq21L=+Y8SoLxgh zLnDzd02K0B;t6lMc=2LE8JU}dG+x#nc!RO1=0g9T7o2K==yQioCN5-+W5+_(igC&9 z0=Z;ci~u(&3MZ0%lT%XEk`{5gaf*wJ7c3%CqlSB*sOSx8w0KQG+`=H;<}i{|QhG=l z{U(zlhXc07$d_YybP8Zl4A8V3OEQD;?nR7~?2uj$=mZkc4kzRcvKl3);jg3t3EWnA zyMS|xlR3DWVu}t%Kw|8EWF+5_wtDlRV zpLIC-t}GR{o=-?ju<%~TEfmtZ-w~WE;cACY*uyp!H))enJV4kRqvuh#-37d0I_%`= zXa=NMu)Vr}LcnJw9Vv&8EKt710N_X+y)yrIhfx{&X7eUQt6u->4`NKhbb45F@k4yS`o)JNE%Wfk=N`GYK89z{R$ceb!m56mKN2VAXSQMehmUzXDd%132}I{E z?yOi_v-j?|ll(VZl{hKMNJKMVNWgt{GdOsSQ6_YdY{L(Coxj!E*@$EKX+bZV1IXwD zTAlqS7%?b6#bdw*rlP{!a@~N}>F~5n(>uS?aOND5&ZCPPA8SMi@1s za$!-ywnHlCJL*r~oq7OSLOP1cp_pf>qpKSWiIj9&T3Ry#RchK_;ZiRs&Ckzoo_j4B zc;kk8{*P&aH%)9RLyvMaq7DAc$Ipkds9ZOWQ}zz!Js6~B{Pu3kpat-RS#0BdPEE?n z0BT5QX|>@Aas%LHQ@3(0L`h+RkOqp#-Mf38f7~=4g)x;3*R8R7x)Ul(?3qTp8%;8C z2qgqoK;byuV`(`7R8tjLgKC_lsAx4DcO9cRCM1az85 zGt{->;^J)EC)0>xW~T%ukL!%QHwf^uh8#iavbJLBBYe=C_eI>+s`{G98FnznH2&*al_i)b-_}CesVu9j-)NY)s={)=|Pj9H>#VOp56~E z23hkPJ>Hh?Xkl_3{#WhFF)zrnhgIiSN4E@f%!M#3exC z5WVndpRbAarQ7v@kOJ=7G1JlAj-hu){}m%+%~tXXWry62Oc@lMqIjaUW$oySP@YD1 z-90}gs-ia9JK;V)Imy=eT)z!`F!`CTg11_9!S9g|f|kL+>N6_IymH}4#Bf)&w1jtk zgNxdD6t!O!{z9;T#DBHEdq+jVsB&7|5PX(tU(0{BCi-{y)Zx?CzMYf1*uEC&bpaS1 zQga1sQ_yFH5#&P^t`bvobG|%V7g^MRkfy2n(?0&Q5v!JDRz9dz;^#m3yCv{;%NzYA zG4P9-4B=f2%25@*R#HyiI7Esq0h_Q#M@NNc+_BpxdU_)I+*&Sci}v3p7rq^sTYAwF z`}FD48s!v_R&sWoukDa9=YNJtQxo3vlC<@j5rStNmJdC|dVP!vK&7R)q@=pBkzs#r zxO?}-__VIoC7A@;$_zPo5Nno#L-t$3pW)LY*Uk&wHMovb}TSq7PEzgaM ziEr|Jyitj3h8ht@$W<#+;FnzoL04FZ1+qQ3k@|kF@Z_UCfoOXhZJm+fIRU`^#v?^E zeexiFC^|I;O5XGAs8Z^#v+~!p1Nh%LJti(8ablzhgJLDK_Tx8iAnaeqv(t$bSg?|i7>SStiZ`pD_iv88V~oJPOM z0$yC5Vi4+%lpBUN{#!{s4wfuyb6M-r9RNI9qbbd@7yWBSe&Fr-n(Q2AtKYwA{@kTy z_;i5kC+fC+fsbxgH|*nX-#*^iRmc=1_M47*<^#Rbnr-ZA`W~yTT)xB9fgXZFk+^pQ zph06N{AUWbty8!>53RZa*yOEp99HOBCP7K-mhVnL;;L*4Js}lj^S^a1^noD_O0pYF zu6J}zv^5KRR9^r+ZzzrZSYIFf%7LAY%@2)5>h~izIeDiZqF+Tyv%c#B4kYd2bn(!e zf1;jF+M@{k`|}#)07u2k$%AyhGB+vsySrR6zZ6|Nm<{oK4S0+`*Skz@Vo?#n?gsp@ zW@i&nzlKNby{D$&fyh13(74mIf+DxA2V2&us{$1u$+i#48vgYw5xw0=LD86&Q*}2)QmG{VaNugJc#y3!ny#9WJ`EC@8_B9qJnfyiZ20m3X7`H?&^E8^0=8c>`zO zOVJ{RTfMp6996!0QVh+AP$TnddpY8L->CrFw)i{YbY#8q~)uQSDe93yZ(U0%nd+~=O1*Ya9$NVff zRqHqSVJ+7y1Aob`nxdQT4`$j-i>@g3JE^;rk;b>-sDv9(eN5e?q!gL;Jd5{L!za%Go0Wk>lu^@^^cTu zAz-54MIPL_^XK$=nGfylcSS7<1qcl}(MLm+z035p`Z`w1QHK$n0YPh&VO>WD=a(;E zC?bB((ycN+efo3+S1OgxxxURvz`(cFf3qu%vq+aq%Jk~TtFc4ijybqAcHBYhr*=C` z-p{``e5;s>+L`yV?KNwC-3&gfzr8Hf6#Q}3qGI%_R(jr~GCIYr!FS5{QCI7WO+Kuq zq80ynmCAW*RA+8Dq4q}lv9U#>=oj#$MrsprMjvBxPfNy^D?VmG}z z3+>OwP5~av`m(#~io3g-_bZz^T~zU@-)UMDckMbgFa=TMG_v+WLNr;51b`FRf^C=F zwO?Crn=B>q`Om&DsTx2Wp~u{?X%MwQj^)6n!5tqU1L~+h7_>Ix^tbo#Bj1$jxcEBa z&Y?venQBSB&mY>ySm;VV#sM5_I-?hN<5zqxf~|Ly+5^7>PN-TohJxPARMT;_o0U~^ z>rA;o!+OM%|fA-_a~Bzi)$opYvZ;U7tPPW=mhd^ng-Q^TP|ixXPGQ^ z`g!RZYty5I3nw1AITm*)2y;GF4He7=?xOAdd5j^)0)5-F9o+8UYT8+LEp{c^Cuh{$ z^;U>_7re1FQyIIDEC2lB?8JytFS#j%oC%y#5@`WKkibU~J5f1X@|=v3qC!uQuJsjR zsf?5hA5$V)kLS1PcFkqr=`(uM>Sm2G+LgR~dF;$Y>U#jrH3dhuFflR76+aU2bI!qf zg<2u9#OqRvBb$nv_y0sN+RnI&+6L_(G7V#Q6_|E#A1YOV_`q z7JiVs9*I0DoUOXF3OdXp|IVE^6dyzYlV}$-_~U>79t!8)i|d{aWN>Od%i7L}{2DzM z+=n6=nP~u;gm*3QQ4$54anfpC9OX??e*gYdJ$rdv$F7wP<~+%wI`zR(t43lr-Q1+u zm3W?SLGcE}NTdHP!M&RD{R;P*DzAS(@l&R#*lF`L>zjuP>+yQlR7(-Q0?LA(fdT51 zCr=_Sc0CrSrF~~ql2eF&0-t9@(Nm+^hL)A?ov(3{5go$S(9MU?D}DY1N7=b{%L^Ci zz(1jqf9+yR+37#va`|!s_9=jb;>HK}@8^cshDlR*3xm5sdw3$#RY*`UC)_gZ0ktIG zQN9>!U1Pz?0a{fpMbM_#o!0p1=iWZjy8_&YXwVY+lC?ZC8*ZR;35Zm9tytcMOf$KDkG7ob z$A2vQrZEifBm2PCH&|{xQj%sX{A=$44kB}dhYb#5%)>dV`9ABYX{7>~z# zUFMcaJqpryTb~WziKS)D-U??9S&6#mzZpE&ecq@)1g>(oZP!bVZtt0`P|C4ivsP8*`lRkR)1;By_{T%dySJkH2HKo8O|u*s$rfuFHDT1Ae0(zr0>?#R zyBP;0>!wX1SULwGhO)D>g!!(;w-119VQB=t;XauRW|6*>`R5jbe;x z-v<2)Ln%AJu*D6%%?TN<7F~Ebj}_&J#YN#b@I%+XrMuL_AIt}Z!&w1&R%}M$=s1xv ze-o6#)%yNxE&u3I*urPw5l@6_28%4m`Putb??m(BR#&C7>gu;0Vs4D-);?FJAvJ5% z9`pF6FMr#kv!{M+5k@`1@J|Ez`ux*9r(4Pwek;n28{@Y9_U#)9-xU)VH^W&0ZAwb2 z?%GL01EPI zKJjfR#V%-WD8;Kg{nT%ZH8)ta&q8Qux)|TZI^MrksU-Hf=2q)0=WOpQEXiZzSMN$3 zw_}5Fi_LjA(~-Nu*IM*#wGCCnZsc4nNxXFO^Ckmj8pNev1=NJ`-<4L98KAoZP$Kui zE>CjFfzP*=xY;Kz(b(t8N=e-=7D$u#+qg|$_p$82D|gLq$(?j%Cz-fpSYGbumqhz! zDR(rL(Cf5u>8Yt^$QmWz|GwNM2FOl;jOJRzVlE@V<-bn~Bg{AfqUL;^yyZ(>Z>q$7 z^4BOIB%u4h%_Ub<>?fqb8Kzu30Fonx^R{Emv+_Y3K^{ry*RcmrH{s85X$Jwp>qi#} z@zlAq{S)pWMGgV7)_ZPn4Gu>#5k_T!@vU3Kf+ug*m-}D)9ps%p9e4H&xbC!6Aj{x% z%+cbLbKtb^sMcQeoLwQ#44bFRGNbQlkw%B< zc$2g%gV}83eb$flv9D7q9JRaU=<6DMKPYshDyiME*f?>H%hFgM5|L^&YK{O~rPli6 z>-OI}>qDja&K^*LX%mQ|#^)qH80k1$k&YIO-j(6aBgmmBDc3KOp(W@eD2j@TiA#9F zj?imRuvMRRrWfBhC9A0ngI;C;$NM^pp9P&qKMi&LQJJ&&WIP))GLGRczH#~2d%@{d zZJ-^JXyaBR#;L0Q!d(q0Cz0k}4O<{#&Yen5kSdn6!31XSz9o?feUuyv_+tOB99WYS*sf#ew`&f!+&6cGnry6Q+Z2bIy1{C5`d>I$Tw->=%EEqT#^S`;VafMNt ztuvlQrA!8k4oZkMnyk;q(f+G5DM^Znu^S%EmN;!_NQfpf_#TW3@j(}?I|b?yD2i^% z;xqZIH58DBcTWr9sit%bbR2<@VfBxKBZGl4VQ$J>xcp76UBLF+LV84Yt#Lik2p0h9(^hI zIb0n(o6Z(FB-YhGzl4Z5i+fLwA3IhB@N~FHHBz^5pQL;cOLM4Xh*E#)7p_x)z4g<# z0t7~qpJkyXP5XNEmUEoW(g@?Uw3kDmY2eG3FF#JAu~9i4Ywn%(rKZ=R=lgdWFhJAA zAo%E`e<9Aes54aE1HGPYtGD+?n6LF?8(ZFCAP1sN`7AHkg?lx~Z!zsS`KYJ0UIpKS zlU6=gX)W~Zr>CjvIr_Y`- z3eLhRn%6q)2Q5IQQ?0CVxz8C}Phb%rm4_OLid~=jp832%Q(IG`#_aDd0$a^Kd7-KW zRDYx&lAFX&L?0;;%Yiv_2$A*rSg^y-j1&FVDVtc}^@y4X>f%PnuC@tvJLbgBd-5e^ zrouYldgjfl)_7XM{*J~4(^>Yb0J$2YVq?~<#8z%^)*2CN3a$8)dDgh6IDr5R;px+! zUtd`JgMM5^0q#;`HIteNt}n>D6bMYIr?7&9e|T#W)^RncQ@uw0$Lg`aa zLdSe~rJW|@rHuB46(??!mPHRX(?K+F7C{U)r zHR~7_E-YW3Z{MvPVWF3gD6Z+`_h8@>2)>!+>F%EG$OID0HoH^+8djBt{WZ0WLJfN><^4!~mh@0qO;PB~!scmoAMT8r`qrQH;G zl-3)*y$GqVllXc)V`H(i2QN0@$O?phBqvLHt-r^gAH2}8?^jKTfR0g3fz(->Hc_P% zxeJd?8V7g{>3kH)?JM~8&cM}-zDlyLe`J3C0ev*^PFaBZ*_XZ?AF$lfUazh7-MsmJ z=7qs;It}9KJa*k>;`$1eK3QR5VXue5=j~dUg}|~7xZC%%!t87owCrpiW#Df{jva-% z4>ej*hcp8OKZt%~*}7Hy)5Oph%&Td>RdV8?{58;3Mwv@!VNeU$c@ueZQuKQax^O2A!N zA=HXVz-mVKASZNGi%Kit{$9R8a{(aE()Ozq6wMF~orxky$G4puCaIgPHtz4$9w=glvB=jGl9H0e zLw9PiDo|KcG;>s9rmV12$hh6=Hvi93aagg9kw*h^lSR;~(Z*pFwMeBx0k zIxz~zVERql-?*j*j{I}_3~>-q{747u{@QJCFmVX9JSxHi?(Xsc2hpOP?(fIg-rHv) zcSFgK5oj!ikGE3bC?It|k?W4+X5|AVXU6jix_56Q?k+J`(+3di10OdB{|<)g@z%Xp zUZ@pg$WiucTmu?KMMb9AXznY^uQZGCnUEGl1WpDoE@+8AjP?SHN+eJa=JcS-P&vSf zOPj>QIRX}%E(UX(vwAv>+W-BlSFd`<#==3IPLJ(C*l?y`3EkYslr(2)J8w6*!}N=8 zwLZxj?`HnII~`jsB6C{_^>UtH#Gg#f#HC`9EjLehaZGc^pYKu7%t^Y0=4t{dVFq zIoA;fOjaY)S;@#FP|qv~5GN`o^x}{R`9rA#MH+_>kMdnapb(VTybv}2bs{+c4$kM@ zk9-~|kb3bqeqmuuXjdlQefjnK_uJ_|lEy_>LE!e`*&s8O`|G`pTk4s(m1169;Na$F z#^)3e7G`Xl8}B94?s`ybJB9zDu4hNH(NX5DNYZ3wl2hysUzU}XMPR|ck!kpgNZ`1T<}(MbFodG9|J^LEBweN6IH&|*+H zu5p@}n4p4Y7rBE5mBcbY!b7|(cOJNsq04L5tRVvnq3vL&KO_k}0iB7oW8b0aVhrZc z1M9eM9r#TDE^IeBgu8Ex3`7S=7#VWBVCG)JKwbn7kN~jd5SII)Zlw4AhI0XGl=VaP z$76S@3iO)^;-^ec02RzJuVpYUb!Uax?A%J+cK$tcY=8jF8ufO^82)OM5UV4gVBBWE zNR{u;```dM%N0a*VsV~wa#gp8@sVM>A7BPH>*(yzNH6<}k`OEhMTc5LQ9+>>)(^hZ zuK?^h4!mY1PZ$aq?O|KMRUp8b6Zlq>W}~G{DCW?KEz0rhAOycW`h^4fk7#+fp)n*j zc$W`#rkZXbE(i6!$)y}1MT1)e@FMc(x5$PucSF^0CbUx@(}~#KG60 zK}KtVYz+pj2LUkBd$+LaMPzN2@I#_4d)?A< z1fI2$kI_A<8R6ul0oO>zE`u6{CNT(HAPHdw8ol1IzzxDwKm*}5FUh+G)pHFnBaY^n zJuYc#u@F&DXWFVL3#Sbk-(1L3bbUjAV5ItTARObPJ6>FF>zO<*Dp+_6_5Rw@KBtjb zH^xm9p){u*l@meJE0*aOd2<2cI0X4^Xre~9sDJ40( zE;RK2v;Z2hd_<|rhWpm!HMq=q7o|22$x?_i4g-FvQIy@wMAf3 z048v|-lR|<0OZLCLk~m_LRJNYo=S)Ma=d~w zEy|;Vc;65=T`#m3m0;g(^N|jsaAdKjG`lX2SFaYb`%g2VxalBiIcMj>QyOe>C|>days1KX~{qUGdKc} zBX|9{9mMJZ*vsud<#CJoV*)jP4>X^_IMx%0moFy={j>ypYj2m|uAmpgq%#r z>c3r&5t2lHiyZQV(|J~{V4*-|p*0I!m4_M5f>sQj*=DODPa#3ClnT&NIWT6&Ja|yu z*||lDdmT8R5ry{e_B5d5@G+#wh!s4#Sq$tmJ%4@;1fEKs(;62zHsMVzV*Ul&Fm&iJ z`K^Yj!Vl8?oZY6Q-(GEAFQkzY?F?4oo%F|wB=O9SJ-shgPB-6qysxLCPcbGtagAlC zV8Csgm*Z~K+z-844v zWH{4Npy=6n`sgbmtLB(%sG5iZ70lht45qhZOQ}Fhi+c0*+qYIt@C`e z_+*7Z?UhkPhC#h1H(mj{%L(Ve;T^=MtU zYbrw6ii(yb#~|0~^9^ChN-!jio+vw|?}R~d2$C`gpR1BUBg*{ztDzSL%y0z4@UK|| z4)W*_BCh16;uI!ANW_gFKpFc}7J#V6eHnWEXg@4z&Hd5Wt{s@_z8N0S^xYh{0ok0W zG^&;3k$*%+oCgQ@6RPFfAfDjH2NC&la|;U=n9Sw0#ufAp7dHR-6=hrXxAOnOTXGPHbZ9p8Il_k z&Am-to)ZVU#p)AKo{;gFxnt|r-GeAtJ1>gZ?W5^r>T`vA8+6&GX zGE55^oJ8az^!E1RYh-HfvxXv>WKdmp5j^kI4rM1Kbit@!)V=KK<{~+FU%I-($FB9g z?#RMa?WoQ}ldYKpdT?G0jyAS$57=^3%YEhUqc=SZg^W6Fx=|e>+Fwb6e~H@|1C7i^ zuuS06$!o*X-|TJ&aq^EhmMz`4;DNf}MbT+iXf%8Z$6o@V(~-uT`)iL4M*cVf;wcnK zS@drneJk^RaE7&u$g_;n{jqbP^&^>qC}$};0v$I(LJq*ksM#}zg25442(b;~N=izE z;6i~A?JWUro3u>~N`)_8)Id2lbXnC+1>edsP2$fQQ?aW{F0C7Iqcbo7rG$Lty99{T zZbormop?(tt3E&w0GiLmpVHS4hFhg%X?e+0379U$6x70zBlR|0`yn)y_nMU=OArU# z&*5Prxq-0`p@VxXzB@sP91ggIDuqts7oZ5Wz0gqubAdS^+ZpTEV+0bK?7?<;{c<*H z!6in}-2a@=+u-fSNM#UB2T*Lh!@odDQf_#f0|QG5h=@X#7n=f#AR?7Fd~x9#$mJ)H zSDUYS2}s6$a)6#vfs3OrvPhSVW=1zb!Ngg9SBlSg&jGpHwe|IDV4kO?96EBOpM;gd zz5^)I%*KWRs$?RPhw1URGwoIAQefJqJM(ju$lZXtEa6GsQFABNLydxDj6zTJ_(6iOh051FNKXuQlC*7R-o1Sy+?E*3V9P? z)>$FAd}|^PD(xBoZa67nPr8LtCXw-AG+M2R-h;LFPEhB4X51BtB&VFpNvJUb($WTM z*TN}HRt$w>x2@}N`|%$NDANOig8G0?5LOqrVCCQ+I1Ct}mnEr8+}zyS_Ki@wP;LI( z4kvN!wHcs{?thZ`F2*ED`)vy{W5G=fYB=%gEDlTJ|4v#2t>b`_7TflJ&URuU$B{*p z56}_e5tDjpf31xcvAlkz=!m6mS-+k?a_>KUFK>e}a}ivpJN?_qr~Tf@0hRk|V05bR znqdnN(E+IqA#0ELTma0y)N77ye{I@n^o@57%iyJlc$|h9682|Wc&)`VN61=K$>LS2 z)eeZvHne5s&2PbV@zbwVhLknnyu=ID6i87488#5E#6h^g1ASWH)Bij+o!0#<7mlx9 zfa~OLK(U&bni_&WgcvYzd$oS}uzH))8hzRArSAc|e^%f}9pHNQC|H^|w8~`KH-5?b z9kBj13Pq-jbQ5F!nKN45tDeAUZD7^4IsdSC|=WIVhy7qtg+viAUM10*4-nM#Eux;NZd45X%!P>*1qE z-<8kz`0kE+nw+dM{v{bVL+f!MHS{K3zQJdlRW#rBi@sQ?@EGltJMm)OaRk#CIP705 zfziJ1z7I|?Nn7WMew6veb?#CzoF%i1q1^{PGG`7SKAhWs2Q9_xd~;2B|1odkCT^6Z zXZp`~+FrPDo#;y2+BnemIh}pB@!`JJB9Zm7mqfZl??pTNz{7z^nl!lcF((k8kHmbt z{=SU2(oy8d|CQFmP7lf$RW&s_%DdUMM^VI)O^+qa3uO=vkqaH!ykB6~fq%YntC41i z87Eo0fSk!b#=cH?@?nA#8=QpJe!b#hVbmr46lyx4s6DR0d&pz5dt-lh z!vVGMmgqAEYmaZ0)tGTRvfgOUe;LKAy32CbbuLTQp}kMV=igp>QPT40>YMudlv-m0@VRYfeJS+G)A6{+oO5x_!*CIFNHsYozOdW~kpQUXfLP zG7;Y{R?V#8nK`~KjFbBEzKc2SObUbF9wihP5B|>`|=J|Mc|;G>7DokApe|9gs)t8*W`+qBg6C8VveG%Aq)M%C>*dIG&Epy;d@*keQ1i&j-yh1~PV z{9wq$tt_Xhjd>F10HS#HV;}lT$W#2&G3`v zn6;Astu8v=ecN75bH(Ib+~#$@d;P_2vcE3L-4a{tU;q2s?T9Bd(R925J46_T^^{mj zL?Vxl{otHAzfVp5)DI5}UBGi>Y!6gLUSHMJ)&I>NN5nz7*|~ooHsCf-H2UPatg+x% z+_-!1of%xlXOo}1D4#r4?hLv$uMTQP8O`|Zr6i<1y?)wr&4E)VRmls- z2b4JxCHUXkc)czytXNKH=@Z-goqb&fp&k!|@-A|Roaw!X4u4~81C6}QKK7@oeY&de z#}clk`mVeBT#4y}j2?RGO7zq4sn`FRLoZ|wPd2$O(bLX_J>ef%c>GwqaPt%z_bGPJ zl#qpeNJtR7@k=qI_Jq5a+dhs@g|DIC50tEtNEgp`x= z#*ntNTDr{KC2LeZsU0VuUG2jY2n3ysAhFyo^4^9uKt}hn{vGlT!~77YO8QIEr=c%r z+RDtk@8*g2dCrn#$)p-F+rJ}+SFM^pa!hVJpB9e6<}g8@efW46z7~kXv_y{xZX~|# zzT6QYvnf#FDS6dCRv`vU`AoXKy%q=6AMSQS5nqqmk<64QhBX0!9$@m28FNJ5xr4dq zp(JUux%qkGLBXVk53Q|NVS9W%io(t~{2e|aYalA9U70iR`C!4{=81fHi_L0;i%!tR zBWFJNp;!1CtN?itz8$a~vhOLYA3l#ufQii>0qHRPl9~cm5ZaJyELzbRiF3}3v#e$i z&*y{|?FTpJfNQDA0sJ_)l2hS1`5oQF;48rGspf>oDQUTs8=hx$^y~ z?(S#r`)eO$CuTkgAPegF0})RdK`>P$i22IY5Dkz!l)k$!s|9$ufu9l3R>X^foTM!7 z3Dy7~AzpW7ej=tw@&xo4EnGA{ck=Aa?R!cw{x-zNw=Tk=hO{Ez(x`I48v(^;}lN6VM}RW7~NaWLyl-j(aO)ck;k zzECSYHhn!4 z+7GSh z77eo`UEqwCr{UA|i1%n`_V3^SXkoM?=Lz>?-lShI3#Mq|jKemTln+O8|J+j)J+KOr zq_Nj9jcLLo4zhQ`s|?*?k=NW#Y=kmLoQ6l)?B9t0_ibOh9XZB8t^q*MA#LUVyNqIW zw)MV#U5KQqa0qoRfNC_40cDr~O-_3=^mg5jR!N4L7B80cES#u9b|Y*0<6g4ef@7#m z5+sj1mZ9tK&JbVAe>ZQs9_DMf{dCvhXsO+RMo+;+Y0ktmfd_jy7m z+3p5B!Kbg|#n0yzt~H|BKnZG=_HQei#??xGI_fymE)sJu8I6GFcLYceR$DmHsQ|r} zl1gZML2o^jGD+@o@*R2kMZibi9}i)%GUM8y ze(;8tfHU<-*) zp7_IGbpS>DfVZxmULPE(7~H%cY%CyC#A5jYjPP-J2!9uel{$8e0U2si5M%!Q`O|C4 zC*<7T^4P0iJxy+Obz2@?m$RNi?pBO_p$4yp}`E%P3$9I$T6Ku5cxj*Nl z?%g93BaqML^|jo0g&WaE#LEwDANnFwE35XN{)1!79^QjgMiHcsD#OcOBw$m_|Zz@P7*GXDMhm*knCQ<~@shsqC#B*deEL@Kpq z7|S}w2Q5KDZwJ@&!=? zkdc%*I9<8n#|wmr6K3rk?*&&V`{+Acz>CGi08FQ&TnGMyd4wbn4P62;NE1gb#B=Fr2GgedZ1)8S3PzA%Y$I8tu;jj-9h$1M1kRliaXY-`(pI;w@i9;K} zaeqlXTt&n;FYhwM4vvu-IAYu=j_sHj0tX;#3uo}8#@j6R83_atrAimeznoPz_->ms zKjUD_bR;YD-Sq(Uk6_iItM5yi4VRNj&zEy?)&s`tqi2WwU1Gx(k1lzM~tA5_&b$t zAeOwh{P9Lxxd+zR_yNG*VJ85Bld1Rhe!l~-TQaN;JVnj!Ab2Om&b(wFY>BVOYK44X zT*mIb0}i*on$R_-c{Q7zWq);TS-*Qa_=QZ@7zqHSquM_Azx2qe;DSUEQ~`Bmr`>AP z%$Mb4Xg10zSN`n2^6B8^q_V}C@)gU1si~>hwEc^SGOPwU3=1`iDXiN*voP&s$oZTV7Y9}e=Gz&n7+ zS1sJJ7ZxpMS@WZF{qW(vnl8?bmt4*oru|>O=Ih;`s**OExlvVN^-26!?teAK zg`w1nBH^)+>4Am5U%yD{`y(edb`#L5Ag}`mF`XMoq5IOjEDi~O)PD|BNLH&xw?^O~ zTnVcwsLo&|%{8oNm9rDFhI@p3I+UrVrlwbkK@2<*^fbUA$cl+6K)?k8Kn|Zc!Av0$ ziuf}>SUO~6Wwp>yLUBCuC7<#0=g&xBu7E$#fn?g#$f{MVE!|#Se%4RRq>3%Q+)Vhm z##bAr(;n9UiXd#=%kOrd#YuIjij9pe#*!A&scIds*$58AdLrwOLIDX6l0wemPymZo z{?OyVqbvC}5vpe9y3`)-yv+Bnh-5$DmA@7%^AN8LKv6GFa>ALy&bOL^K*KsnS*X8B zfgoeTwNK~bGG*8TPaz}L0x`RdjE+X26TmblyyQ(F(w#G=QQ6kPElSZ5&l44!TLc*0 zJdMB)USZ*SB?-_iK&7a31aEW$>w`8fe;Sj~&ysv0RE#Qc5VZ7TV)?_PNJ`bMEr)LB z;*M@49+(4{p9PDi1%U+iiUnkS2Jm85d>V%gfl}eS{D+6=fZxAgDe;iDelKv%yP@z@ zlo9Q|F_wYz?cN*&)5+Y_S-3Xg!6|AA3$&4)rO>uwPL&LoJRdpg9nAASBa1|dvj6a5 zT1fP5daiS=nP)zz_TiI9f^v|=NKm?Od5Uv)=uWv?63o?-!J~;6ll0cTi+I`d{D=5; z)DEb~hF*-psp}*dw=%eIr4k3(Hh%uXkq%r9M_mUJ2 z&ONnSJmjVaQC3k~O9StTAKpldD_K^^2yy=AjO!2e+r=;Y2Bs_&_ z>xn0~k+)YWEHi#s?Zbx#LmvLSQ|u)xqkFR^25d@Eftnk=xWJ0uw8#e_B|-b)*AbfN zgu#XQe(_~0mfpnf3O?VbfKR=LOq^}*A4(d(5A#n|=ig%Qg@OvfOL@7uWDGJz_vj?R zW8;=*NlAp3rkI+URqmR2cn3H&3BDelIa}sc36)B0J(A#3qTNj{A@YqVssA9-rV9)N zySp)kQ`(#wM{<2MAfNC_?F}fZYwx*#Yue5DB=wnJECE^Wtp66X$bzQ4&P(!yn%ae* z4nAsN4uf$&;r95}nuvqcIskU1Sm!D5%$xT-JZu3?z~8Mwx5p|fLbkr^&Uj?teZ#5A zp;eKS=5*ijW}(PC_wO?Rh)mp970UOq#yn>nK1r!?K!~?c&$Jy=8cagzh(bAJLA$IN zC1fv_z1R8{Dl+F<(nRO@dl+C>!)`Hm%eMfxHJbEu+{k0#5WDLhozJ+{Z_oc}0gf(h zZkYn02YzDgRtptG^j<*3Nb%eyGPx48+@4k10; zq-D|=F2&RR*O)#|+X4(G=%rm(+D$ZLCk+j`;Il$qSSMSIx*uEZHc#MiZrsmv8IpS4 z&j(bt7_y*SIF`A@O+!n&Gks-;h)5s&DcFDlWI8Ge&J7(_D4d$7@uwm$ykX%d_Atuq z-(TxZuxF;Ov4w2kAy!Y7Wtvn!i07rH`VTI7CYvz}?*N(;#tcRh!irzc8uCP6f8goH z8@ja*bt~&~p3d|Zj|so{l_oydb6xHKHN&^wSn*?Z^6c|D1W5@Tm%XE-VGxG(LKB5> zy7<=%k`fY!@tBC9K+x+VLXIIyM4t~HXYjQ21w>wgU&cqAn=?bu!tLk`@6U!^w3HT0 zPosPr8i)f>5%U6LfI6fSg)eGi!O4qMR{?uSef_6_dt{6o2)C)c=d{=dUkI1Hz77u! zJqEt%5*p&As4us#emFrOXTGtq2}y$&f978lB9P-*5;@aMEr`T(+FL>YJ#Blu0*3Xw z4XVz6*_u>0fUe2>+=k`3eqNzVF9M=o=a%0%K}LZe^J_Y-GfjP0|2yyYh*P!-QPDYpxXa z42ckgi17Bqha~Lo-fv99HrK8+;ADJ(XT|6~IqJrXq6T|b5 zFReR6e?*mT&Ja!tS_(1+=#KCCSG4gCcs@$aR#v@-XR85>isG&+V0b zIMy}T;=;3AII`ACzwe_x>-AeKWA?n_;(_$T3zbE}VWaZeaxa! zNOa`UCIP*++f@&>xBO+UlRPq#n4Dp-jx{pt9U2z$I6)SHnpH)0jw&J;Df)kGeJ2Nw z!nyev6x4}&*2{TTSmY>HD4M#xrB;&)=i5-OKl}6iCNl-i*+D21oOflSQv=>mjmnln zfjY4UON>M%;t}sZmgbtD2{h2kNMHZO113~dgP-F!n$XWC_3kZY3BI~yU-NsPmzLY* ze0#yl>vX)41a}7_vx)+!sJuzU=fAIX{n)9qG#}gyEW(elG^z(|WJ}rcT9ScFJjzVr zOA_~EekNWd@_e?#IkNh&_P#@#f}0SRT7aZa32-CLE4@3ek_QhoZ>Lpk%u zj#52YwxDuWJV@Rn9@*p)tMQMp{~n);jizQq?cYc3tshUZru*tS@iI3T2lp*Hp&An) zd78M)&AHc5Q*Xm?GQAQMbx3dvJgS5Vn!+T54X`9H6br-(Yild^hyQu5rYx>vbcm}d z$gT|pV=dhR^h=r}7#b(cw6ApAhq{@F=U{{*4pFQ4Hc}CTR43WjDCQB|o|+#{GG2|a zNt0$3zB*112A=IxvX-|$c*?=7e|^rvR}JaFsQ_jIyYTHGHin}|b#;&Y|Br~E^Bt!d zE%axo!sllBic5cOQdZKqW2>1|>9FAj>TvrCwV~kc{SM&TnhOBdp~)UzeDd_EZoH&F zonzCW?C%x+Z#F>`sdJeM^S5N~nxfjodh3>a`2pxq(g6_j#KTKT6A8q%$gyA7iNuZ& zh6*852T=^e7*+|nn}?9EppZ}>u1!)kL*WNTn;F#x^t`Cb4iC6v0}!Dr6uHo#J}D~V zLaVXx25x|2cd@IeB{a=1(~{8b;##3K6>FGk60mIcxP z67U`u$BiUzl%?wke;gW`pUzBBl#mcz4k|)yT-fgJ>I>Z(?i%`pG!a`o!J#dp`yfKB z#I__k_u%1S=B=uzpg;lxpXu$_N0A=pQRF(xMa118qtOx~C-0z<(PqTB{e)cvp3Fpi zjhqhpxo@e%+bb<*_RngpJEWD_Lv3WDZQ*xJ?aX)J0^XK$dhvymVcLTXOE*vZl3JKOTX|b^O4#TGQuZF zrGJ&!mPSTq3&LPRgkqEh?J0N&f{Q=_OM*C0(MTY1E7sr~v=e__aty@q+IxYasA*^% zf>MA$c#w4*IdTM2K1OdQaAAa5r|bu|2K6PaX1-qyS(^|nlC_CT7|*^Qd_|zivyYdd zI+K?sMR1eprW9yS?^KH3=+){v&+m1Aaeo&O^`g&rb7jw zhHzGFcF2_}$e}kbN znqIne#(w>N=&y)J8Q(^_8>(1a0`r<7zeK6NhZ=cr2Mr83{rvrjyc0qMjB=>JDuBrD zDqxD1v==a5xKAe8?1Yd&2+W7zUjux?=KM$a+fT*6$n)!mPn00YfQX>wB6EzNfR6@7 zl_`55hDYu}7%b6*t4CRZ4djO=3PNbm_5OHa5DyKNJb$nLy0G<4qAZ!d+%)L&Xdzdm zL%v`@`3HAypou5fYU?3%(6N`y(&uDp#q_#Frx~kb%z0X}iAdv*UqQPlhy=;^OPJW4VBFN;#ib?jRhoCQ1S%^ni-CTtv1^lL!~ z7-XAL0i?s%sBCiu_dp&pEHnwIAY|^M10&uLkSBw4i)1Dx!gXxF793AHw_EE>u@eo1 z1@*YQ+?C5A8w3_)jvv4$SbqN8Ia74g2(V13IJ6IvJxGmAk*Q%^-u=pRH$KT5Tbv{G zN+u}YPboOSON?m9$E}=!Bo`T0`m;A#)XK(MNJ+*3FCk+2Tp1uPd z>$d&>rj$r!SCnj_AqgQRgv!d!Rum~CGc%MCl0;=h%N`XWdlpJ5BTBYXvP0JYbM?Hx zf5-72@AE!y<-Wh)YnZ5SM#%_DMTO#%~&NOwy6N#`6pe! zievw#;fG`xhoHk_Pb1JmE~M`iUFM7K-tw3!T8Tv5-v0jTsWnS*sKPxHu=CUtb2ts$ zr-m5((%(F24e2RaBywV`hiEC&kBEWZ>puQ3cm!3U*Q_%lViA|fOd)egIS*c@Me$gB zi~o3`Fd9KzG81?zmY|4?MOM0YHxP*ytM`I4VCf`WsoXZ*Tb1X&V}@ zLl`>=AL$t`f`p1NNBD+LOUsZ7Hhnwu{=pHi`Zg(P85wb_U=#^vCZh)g1=nJ^)B%I@ z$X+vuKqlCY$by1HDx>1eEF=@v)ztxGh36&EZsD)qAflH!myT;x>+{?1H{zR>f~Cx6 z6}xU#In8-ouUP0X-*1<#1DNL0V_@M;u4m2+NXj1?oiR9nJ?N?+q6Fr(|U@;JnTrk;f?u>6|6g(wqmb zE+|Yv1aJ*KiNf7TP+9z@{g=Aq&)-i-__||Na24^}AWPR+vK}H?4BTmosR5KC16UAj zF|*e!!RAE`p9QMQ#@@N3j1u-erepvah|0JHkxJrgUHf(1D<2E4@2*?f=~Fcr)|KSo zU4IOq^S5sj@mU-r~W%^lA*$dID=nZB}) zD{j4~=Xg%Bhm5eip==8wS7v6G*Z*#9@4J}-#$~|QeJGlm!1 z^sVj9n-Jl0rk2&MXEU?KB7Z{Wxqj7}_2$`vSm1?B1t=JZEu;rdQ;Hw@TnqTpPxKW* zG~I4vk3~v`grk~RC;J?*G0L~C<2+-EQ zY;&8O04^pM1t8*zZZyQYZu z=dkqGg#`)|X#N5B6U&@<;mqXVaS}~1=9q&S4k+(qic~?8T?f?73)waybl<(J11T2Q z4>h~x!0RXgO)Kf`uW+)EpMF^P>KwA`CxBL02|IW!Z?ce#NNFdf@=o>?e~Y5 zA><|XJ;s4ibXU0m7*T|C2-}4brtTLn_LDFM*w4ri;WFXnzYrt&gL@Hctc5R)TUM46 zURV&RO87H}d=`V@5u{Gtx;QmHUJ=HgpRuI`P18S{k;MBV#9PA0(}%hUxhOR#Q^-@n zg#|jW?Y!z01=^R{0jDhn=z5QPOfh>c$|fcEPwnv*>Itt5~i{>vr_Q)qX# zAMzsJ*1h&WDK;gAlxudJHR!7kT;k^jJ;xxZU#*D4LjI zkpPV#p8qOMiMyXM8yQPU1v8%^HrRwAHNUK|8;LC|KXSxw8zn#e^JMksi0AT6XO%%N9PID3{KHrL9R`w-okAj6Im?(BAu%G*(kTbVYZ z(7cH(TLd5Bl5k*cI=3F+8#&~?B*hSC3Qfb!|J-t3i*t-PygN{#<36R*(Iqx?x#lHZ zb|ET1Fc(ONrAG4)^RXQ9$)Nd1iWy1uM)Jm_zWxQlfn$P@vAnsz0R59V)})H({cs`@ zWfPx|-Y*NZ-ul@EI7F7`hq>}*Yo+H;e4-mfnPF9c3@P5d^9i+(a)IJXOL{bGU71s5fGnH8cqi7=mF(?Y%vo*Gur8qJt^S8O^o zIuGzij7LeJ65xlutz(413>>*fuwD@66y1;wAjk~zENS$Wl!+pnf65aRICuZCyZHiz z6NV;4rClW>eh7?!db;1pryX!B3f4MQq!gX6fyU3ysAeshlzPT&i6_QlpOq!I?1mq$ zusj7Iowg4OBa~6;k+I6JH&!~mSm$%EP4(AudqHW%N4tqz^ufW1bY=qdIehR^=jYFk zyQJwUsLswFx{sJpf?bs@ARscSVl}a04 zOi`)#KnwibB%wz;3*GMtr-S$3) zZ%9UoPX5) zFN@z}Q5(vNoXzYRNoS0q5bi1WoS}nIg?+9Q=i%!U5iRiT;fz>aAPD7eeDwC9`A$~9 zkyF@cWYiMiXXleG7&ul9obs;YU0eYKq{i8cVw<#j{|wsRZOaJaCH^q+-u(!-LO_me zWDzPo>%QBNKcV&|G*?V82@d^IdW9WD7e#aCYxl>GQyId=AV*noFi_HO{RTV^om;bn zY5|0k9SHvhEK2iWgtHjEXkvXSaKm<&4#>RP2PU0UVa$o~f|`nodFaWwxVRq3!e#aE zXzT0O<54(({98oa+{{c1?YxCh`fH0$SG&~nVUk6I_UGubFFj=4a9jNFax6Ux)w(CLhN8$vS{&1M`cT__Dodb-hH=2+9oc zfXvh!nmI9vo&Rw!L`h}#DD+7tmOwVJgNG?suk7H*;5$22SG21H7Ysj& z?;St>GYE9?sr*uWQKF)1f17^~wDVd-3sMe4<&kCkBA%6l%n8t5A0MI`c(d5~?_&|q zSHW%(X_>1R+Yi+(IP~hi2$_TX>aK8KygaHSLdy~b3&Pj-#k_6(^eGn|h0(%?*4Dvv zU2bV?J1FI%%)_OvG{I zC9RixoPeIMG|d*G9q=5L;N#p9bsK+bDuI%cnRx?GOZ=O}r7wm4IKYiY)0t~21RJ3i zoVfV;SER>!at(xYwdIplFw6bUg6F(X1-*% z6^#?m(&$hrE-n;cx zOk*VR4i`8z1^lwRsTB#b1Edd@;dK(iT^ky%3f_7B=1r^TSC`T?7;uoy8i_KC$j)Th z^p@hM=GQR`XtwGr+)IUUI7qdOtp?~O#0lSa@JxVl7&q~Uif4}`+W-WdAPez_C_x=jUl{xPih^aYVw_bXF z`)u>LsY&R5AA;XtA5h_g8=XFFg5z!{(b@_iV9kk)ZUnq61!>mqR zw7_VIVIJgER(?trTH*H6XAlIDiFhX}c_?Jm5OnT=I5^vd?{M!#ElmdbOm#q+LTE%f zx^$OI5w(_>CPju>1wv%i?zT<499EMa89`_(O6Y@VKV}v&S&!!Xr|b0_ss7`wAMO0- z-I-U1HuzfFqqmVUF}e@c1JSJX7r%K2Ce0*n6r4qS`u=*=W5?D4%v!Cf`yp6A=n;0S zSQpm*i7P&co2cd6j9QWh@h#?8Oi`{B-4Q`FB3oxYhFhpkf(@)`jQ!Gj6o$2?^`0(} zn&N8BbT(!f1q;qh)j+dazuC0J{2F$WP9isZ5o%hc13f!VZ|pk4R@>U z2CNr2%{;l@j0NgfaraHk&R&1|)Fvy5q}1S&M&ER{6M@Wqz`%`%P1UiqiR_)S171|H zdDlQfty#aB0`maz2I8PNf^rq8h~PA5W7zfU$FleW@XSL#y~Kbygk}&e6by&!kOqu7 zxR!8akrBc;n@L~<1=TL;P~6r1ej@%7(?A>f_5o&3L8k#a`6|L6K!RJ?+lM36hoURl z0Os4&PJ+3m(D>_L>Ktmtj0m&n|JiE_z6J>fM_o&P^r%L1BFhME!CkelU^M{i^ms0# zzjMc!jwu5J>NtS+`U?ApU6f7UCd%Fad~r>wh^fA?CNSaDy5>pk^lBWbDY@G zo&oj4FPaVBwGRyp)bI(66Qw97F;P?CT6Gur;%JNT0%gR%=bnUhIBpb}1O0{5a&Kr; z)j|gz9}MB5;LurxDNX(h1IM)6cR00p8^^EXsfz*i~hbyPI0ZWKQ}cPju4O;(Q0 z?eSIy$iLV$=az8*x-#np#3dLvBV^23S8WMVxHX{}+@!?ZxoBZ$M=vfep6o54k=S&h z-AhACe}e~{$DpqGS$1+OvRq5pFXm%nrlLw;=F>CWFf{DWagV2j`}}2aQjW;%ANsow z91UtVWn%F~$BXXv0SY%wUELsTMU)Sv&SH z8OyV=u$W^MZ&P6+Y7&eB4Fuenf;z)XU-nKQo)Vcz(d2H9umKQ3fGpSHQxAD-!q<&j z0up4yOEDd)_CcQ?k0&tS`1MI4@2`ktbbV%Nz=JfwMHx;4gwpm#-hw}IK<3%`j{@T5AQ@;KAVrXL~^!nL!dZ4{| zX8JRKn@-7XK}O~C(c$6iKvuVxQ5H_##V{+cR4X$GX6eVv~s#K9;!OKk6)?y(xwT9O7&-J2D^S0lROU4rawU8 z3KRPm6E7i?&Y{IFAB~H67nv}pr4=RA+qMtD=l37FCkHSnbof=1w2kFKo4zX&D3-_w zp~$uCH}9D4Kx3>R=8H-U5?{4SBQ}7HZrbmqS z^i*636VG1j5LxZ$^M}4l`dY0W|6^M~%OtkYz)|86t|9BwpJr>Cnr;+XXUuy)#nw16 zX@NXd1QQ11Bnn$v3f-jbW-Y(P^1IiE-zJ}Gpo*D$wsvlg=V+By)Fs@?8;H6V7fM`= z9CQN21%r2jIc*VlcfeKf`lUnA^XKA7JqgAx5A*tkvzk{(DAf8b(v!8^FMF`76vfz;n~^a7${5&1zA(k0#J+-oGylJvCcm(BUdZxjy4g> zKq8A`?HXt^QZe9x%mG7%fg$Mtey+a6+9p7+70SxTEHGy zZFd$}+tvywrRV3*FknWQc1M6VVt~g3y$)W!vaO8`VVqbVEX8|3{uAE~g^Yj8eq+#z zI<28KaZ}6m-Fx>4)`X-eg1Qp%0H}0l@81&EsLiR9a()(Wx4B}PG#fvB`>&MXM74Cm zEIKSJH2?XX=*q=2Sg6hQM5t^dEgU0$dcXBZ<#sF+1U9COCz^Et0-*fm2*^xQGc(E9 zJz%2Fa{TB`aL8E#rK9*k5^Ro+m58-sVpt*7B6u^%Jo?f#yn(^Z)=tOHJZ0%yzPr)o z_i59Hs$>uDxSuxkrGHNL%eKWqP$1T?T9R$2NIX14iJ`eajnRma!k%<<2*dOzfgn$x z5<3;%(YbGBa)kau^qc>;Q~0gDBO{>@;{aE6{<_fT_N@h3V_@vZXJ_kCwL=WJ9u4>l z+g3X0*YSn?fWcy_-rd>2Z^C>qjhoGseeUQWgJMWkgP90K1_D6+L1yN9^5fv}Bv}DS z4w>wrVl*>!q=6y(NXzL7J@u^J@m8P0^~Cwe?HCnUmlUD`RlX>2yQ2`cZ#{V9AS3^g zMCZHiTDO{410oqLo~XW?x|??R@K&3u7pYPUpakelToYsOsRqVNW+|$kId{Rf`!e34 zX~L|QxP=*fW;hk9(7}^jxF>z{xGBd00Pb!42zAG{|5DVSs=T=#xamXcIbm~H-CZ?G z0~VWZ9h_~RyB)d8`M1R%s?$P}(K**?eL2y|<+Lv$B)#(E$5ZFdayj5^M7seaqRo5GsM zEbW)B*Z@D`2~6A*`LP>^C}0^#Ds~V-0%}|&>_br>G**VX>;^bsf8eKxFoQNTHNW#U zv`SPur8dz5Cz)g}DyZ~7%*xv3z?xE*zB{S+)?5kuu-99UW%glX6M+5_y9L!}rteu9 zob)!{*mJ1-7(_sr;h?@`D9VPywXkfgDN&9Cof<(cr?#UK)oa6GUADn%5j6 zbC-JqCukWH8e6u%o;MDZtb9|&(XOejxdCPc_iY6MPqvQ@nDcC1wJIQIIsPXQ{)7cc z0Jaie1;zw(WwDjIYP1NM5CGQ1m zIN;f|Wvz|w<0mT0%1_?C38IWOXu>W|=5r#AH3F)ZeiQ}Bub^sw6o8#y(bdI)!n6kW zhGLnaDlMaW<6#={*$q|QTl|II?a6Uo`4ndiTHd^^48iJ8j>mFEMMiFnu93EG+N>t9 z!!j&l?nZR^wSc!wd!|aF^A9O6*>V#(JLAm4UYl3<-gL!(%TX#-PyPJI?$!u2d&K)k z)T%f)h)x;+BHaVwqDIK=DI_uocS_;{CW2WOhzJW;Smr6-t7x1zxy$a@&2ePR#k&|T zTIUnM+Y{~iymz(26-tm*V21U`IwA!*a^SCeF;xKa#8?`UUtwG76aQFX+hNyV-tIfpfese*7{VE%{Y7In3V+b z;($gydAH=gB!`IwA*i`V;euE+^Z*ra{`f4!NU zICa!y+3#pqaGll7GOL}Jex+8d#;KF(bRc5S6~7KavzqSuT&1rTVqa98j{Fxap@y+d znKKer{v}e^RdR%JRXnhXU`;hD-M^7{&11 zUbAaw+7+e#ds5$+*}{TA;H&OmSAzc{ULYbA#Mqc8+_+5j}q8E*W9?a|Lnvb zafizj-g$=tC=c@Q{x7CMyNV-PnHwXR9s&Mc$+)9Cbw$obS4V3$HX-3{Ovz$?%yAUI zZ!?#1oi_3%LC7Dkx`}Jw8Cp1E-T#1+_XB#45)gm@QVYm*2><%b)YMI$av&WlbtDII zZ)5P~@W{$8lJbLckm83oKPk6+I9~^vJwRLhPPO89=NWOvXDI9CPz%w9)hc*+d=(69f0UK=_5wQNdt(Dqju`4{i4Q?^ z3x4ShBPfR;660~sZU$ov-6E)6k;|;?3MyZB3N>2Rz4Pyo7~J605*p~1%Drp%MxSo( z@h|eUlWMmU57RPkyKfTmv0SOg{#NK?qIgk41CgLPnBN4{kC=qV&h0X4zSCtx0ZN$9>DH<&|_@_7D1<=O>sXt+qey zt#O(VVT*o3A_XCkti(YBiPkEp_6G)ZbU&e1Q@~@{Dy7iE^0=hA64ix}2wToQM}#ai zbx7R27$GSJVJT1k75Ms|n_$fhyO43|N^0GGCF+_`V}V3faU2(ipv&6n0#tX*IXO9d zK3YRA<+T7kNZb5D6s6kV2U@NbUSq0938L@A$RR*#+I2uu$rPfGGp zp1}P~3@++@oL$j1o7slv|3a9`!y3?Y{W;w$p?*)egL%B08OhMm&)JdAJD(EKBu-#l zIHVxv-eab#Dh>63eztL1yA|^!@2Pg(sv9P&l2e7d)s&NX#2qbZ!p9gD6!qUM%m4W; zyLZTLiEHkPK(yRlzB7y=7gQ8NYLLL@y{FyixYqHkUFo}ZUCw8y>E;)7>c-fPhIguJ z=3U>R!o%@w?8-h>YaibM5qaU)O6X}^p>bTn#y&)!`Ho_XqEUAYT{Y>OoE7NZ>=Ib{yN+9l^PIYg6Sv+dd^ zZl(}S5Nw|uMh*F@Q%vM2CKQm7MhM2GQ1;y=1){E#eag#ln>}k6Z4yUntCNTO$Lsqt zG+%oJq!%CqiV^?u2YJ==F!GQBB(;9e7}S8CesprxVSZdQVE*&iu-46oMuPP=l5HNM z?r)uOUyuc!Z^c4KgVNk+xdb@-o<(w0-c)HgunmZ0AL48-mHbBQ)W8Ladu+3QjDct! zx~0aXOP`<7pw?5sP!~!CdNkB(zV_?o<;(Fb=5LhZV3Ul{`Xi%*yi(MtBJnw@%}&fA ziklHaAX5M1`*)0+f4Ml1qFdr#EFc{qfqKh};M8(BVN?p9?Y@$;IRE_OaI>2nmxQg3 z#|IPphnhmUkP)DxB2ePoK(q!i8<0YoZ0%`fW}fuY#FYdm0K}7TGjgVW=|uT9>(@=$ z87M4v6DiJRm&Q;skk|3=^!XSoxo`d@`xtn!9-mI;C(bS=y}?g!)Hwg)1?11>&=(Ij zo6TN{3I3#?6SYNo #|xog?`^TsmlG_4{T7)$wCoM`@dyKJkhx73aJP;y^xjd!#D53yaxmc-2X;j}%J1A~#E2A1nK*rH0= zGqz^n_wQ^c_O^}ki@A#vpjM(1F}^t7X3fU!#y-wFp;H58sUzJsb&Lj+BYN|{MuN;E z0g*8|GbO{3{a>+w=#`f5J<7;fPj0)T>k;(>mIHkAB(J=NRRum)t*zTL}`sSV_~P zCh$0u0Z)_)#3iG-%-OcGIQtxuFx}!S*^p~wHgL&{9Po;P3MfV%3rZ3yIMWW5@x^Al z`=B5!0mHKTn3XvV(jg@$^~0&>!Kr_LVmWNxe~v;SmI(Z4Gnr+St%V42o_E&uHAD10 zBgTtR{7_8AuDsKVNlW`de?J`Rm*76j)&rXO|Ekd9QXs}y==2plyYDlHytu9&On(g; z-T*wRLRm#thn(I5Kx;6vDErtWkKfzu#lDNThDl$VtM3^{#3$p23^@_m@@4%O4Pslb zbeVsWJhUmpeX9r~W18UJy_?VpX6G&o?A(dxccKDoA^+wU#&sdJ!-|U2dieB5Jz^h1 zgn~i3lQG9yes|{o4C4I_i~4EP%gLEr#I4DeJZ$I&vDE_PGri$c2s|+Cz3u?m2P0vz zw@46;!o+bUA}8YHTx|IXbTlx}(J&c;e->c0r+XI>HcLj-L-Ts6>D4~sJ4ErY3YQhh zg~s1EU+014TPK+0HK`r7h*j(73q45q6mGXrpaYb%CbUT|j|F+&eqRX@G@5bK_E;WV ziW)a_36o5P=T}WgL{>9Q-yldopsMye} z4Y~Gb1RSh&V#va2$o_(#QU#Q(AxQ)2vD#_Vj~$bI{+^#7UIOG+`abKg2ZUgo!QA1# zrQCMm<3jp=@g=ImhO;f$=c|Mk7MFj0;@h?@f&IXxvQSVc*Vb>Lrrc~C+$`l_9<)sz z?!pfUU;^Awp0~O4>(`|q%O#Y*yaEDGs`B97^G=?-0D*$8Hj69bpC*65%=ziLd;)|S zT$l_GGTp)4`d|7L?+L|MY?;hEjNyeO=u$~H%I8L|m$oy1tF}Q9TUZoh-;IGY6R zsZ&3=&_8~Es9gszZ9US2=Q_pEHjpHZGy4o#&nhXlhAZ+^;`cTtL0YvKbCL;h2&Nsm zAMr_{2ui(dA}RTj-7*r-z~RlR#}TRp)^L9Kd?|k3WS}!g&pJ zQCIqZL_qzpC;kv5=EwQP1$lH|aI|bfp!>tg#Ru|SmA<;I`;)j+?kREW_h?AaH*dS& zUR%g@P$8w>Nr;EX9?WMYe!AI00-I3Ac@LB4EIjGUbLYc?gS$N;YPk-s$nV)hLpfPl z63_z44GGCXyhSCNf22SZPErp;zEt{NNz+g}jRnS3(ekv*^OI-6&G$!Z9I6Cg2qD5X zgq5JcU5mNpRk+XyE`VP3Z;aA@WU4E{-9w=O0U%*6aCb-GVClsvGB`n40#LUyz8h8wuFPHpmrW6p*;C@#faK(&sf{B zM>lTF$zF)Nyy1tVhwxkWFs034uuI%(s#=<-80@p{l{{dU>S)*gy+Lw&wze7H1g12S`@Y9f0B3)UiS_x#rC1>;TXBMXy4PT#_|=X z|6mjg8*bQ*4>F@?F%%;V9ds|Y7lc0mDEF4l4{;DFB=oYBhI<=jSH{wW$$9I;V~JV` z7NsDnRcm@)e)FjYszN>_)M9;DzH`0Txk#i7nWvGFQPJeBIVg;DF|)0-mW!?EoSN5iY8{$3o4D=c814mb7H1{-(&9Tuzb zm&;3+oQgQ%=I~}cqo{bVO2ZgeWSg^_TRo0zLJ|;W9rq}{zsKMlezFB@`oeBnE!;L@`asgp!PlSK{YN{m4)(?P{IQ5{rqJ5A_S-_dAR{wYC{v{$qz&6H~!PL>2 zpx<82#7Kh`L>{Qa7_dNs7mD&7Zv>#VaaV?tlN+J6=Udl10Dtov&(YUF`?PSf{mpU-zHxSB#$3w8q^n-vR zT3Co($xJ)!XtJi%%;bfHzK5G!QRHDHTVi^&Yc^c09=-{b0HqEiw{g+orpnQ|C=ql* zfPlWja);n%#P0rNuB@n&Kzay(Xy`i#6sCpfIoxiRpd(l#UKSM8VL_PuIBA??dFBi` zTFEU(MpvQN!O;pUVvbXjP5C#B3i5~E27SdP?!}{L=$p0~F0}?B?3fC?(F5e!hXedC5vALJQr+lMBD7kERW281wR^=)<9r zI^7YfKFyPIFCd{g1nzYN!L7p)TDz9Zi*od6HC;Wf7V->%Uj>?fHNZt2h9&8>G}E5@ z($^(9T7~Z_9DhWl3S+`(Oh0Y}z+^t;gXPyZZROyw!q8EydzB#hShdF_P^KH(C%ATx zuQACc#|oKHI3|$X(bYxX9n}8|9Fg*fV5`F>h%Rb`tl$0MXMR7NYC`kpkp7536HT zpGK~_hT^n!@tT91SEBd)x^IyB?^;@%k&}7A%y{$e?Z|MhH9L0g)*sxwWPK%NbO$@t za~}J@7{Nm#x1?lb%BRJ*ZQDjR5AlFU3*_u^x~am4k0A30%>$$|rA|%cOvNO4I{H_E zn6TF|Sr5Yn7Fd$@R6=|_aW25s$WVG^_F8EvMx=|rf27Jx;?4-@i*C;Xt~4aRO?rT0 z&$;nqa4-mJHgntyfEUvoZU9<`ydVTx(h4x=Y4N%)h)yKygF@o-9v@&RpL!=l#tFdM zxSr#_ev%&v14N%uHf=&iAXP)O2;=HfLy=|PW?cPB;KKnJX{MDRhX|?gv+SnOyENN5 zXTCx35`bUr9~0mYw>Di1w!I1=#10hyNElSWRVl)01yw9AxjuYs@H+WB+Vf%P#7-l! zz!#7ZRY|w_6uxSlG^^}Ea zdK0dWYS7}Ec*^Z1M+a5;n7ojrj|PYi3ZL;#<~5JCcP>vQt(Zd=M0$35`6&SE!%4oF zt(4S$(eydu`Ee;RGBHh7{n(|bBcW)?sOgw6k9q8NWG}iK_+&l6}Q)qdqb2FvfmYbc2C&d<)a|+-g+!sfI zSB|u0h2fehd)A?-g3-eS$Hz0nbE8Hl51m6YZr8#3P7z*clwgff1O)|6{EiqkLbG8D zl>8ZLp2D9XY20u^RA2%Jh~4pt36D_*32yu=5`5GR* z^QT(ro3AvVi@3;qpDvD7Hy<%HY~W|%KZJsga4&w(c7GziGeyPK5LlDH8~s2`nCruo z6f#r*4HTLBg7TJpR{N-eO~g=vQ*GNnM?Ji7ioES1k&%1Ge2j8TdfF+dyweU2gSpAZJyaBu4~O@z6ADCy{3z%yN&Grb6jnSP(4V0D z_q#ZT525ZPhIzaO)FtQ!V1!(SY{*KqosZ+Y+3z05I6xE7M?>hWi67Q$DY zg;WrHA;O-58z#n-{**4{EQm9TKtdZ#1|)z0f)NL2=YX)V$@`pGR*tCHF>r_%u1XAh zMF@f-mhU~J9&PnX`Ei>}@Gtxx5wu4>!-@uf?Uha7J&6D$)k^MC)AP3XKH9vm2`cpbcQjk%R# zMfJ;bnT9|*_!XshmY?0cy5_k`WaBC6HT3U8y44j}5*H$zYH^`cTPt;rr}s^D?f{X2 zAPnLrSLDI4VHQ9U52EarFe8${T4+}p8oYesv*qxFR-KVm3&csyyu?T(&UMiQ~!HR1ZgT$%O0-oEsE#4MMa~kKIea*uBEv6k3d{;T3CZ*Dy0Y2(JzzBG?UtT513nBRkc3PdGtx= ztDiXcxt%fKkR45wN<~fZYS;cc268=9b})sEe_L7Be=G%^dj-K>Az15eZiZP0iNeGe zMImXR_&+l4ykKk1G2?yCc70sM=(tODaI=8R`n~hR?{XcZ=;F4JKfCj2%IIqH{c(#|G`wM9 zZg;JElIAb;`c4`P@0@|?8JfqNm$EkUd491wuEQZ$7JJQ}%j~ev9orRkir=x>HSS}L z1)D+)J=9x|Lj{HFkEX~t0vA~i&R(J2yVp<%Km$3jaBb=3A55rhkQ)&SEENvcEogVh z7eNn*F2dwc8vrXX^$>B|((rZyBSa)6z5UZ8{3&%K?t&BYk%7S{VyxmmiB)_$nPBnp{fUYu^4(MnMGpy*mz82$ z{q8IEsROQcL+Aa4+3$4IyN70RCDhNgmc5r8(%+ua$v3II&$X#6IAxJ3#%lkVZKPIX z=aJ0nD(7^cGyAqcpA0Ap!Uqg{PH1qf?R&;xNQhIEDDNQE@0G&&3+CwUP3# zCwRhKaAKnl<^a7(8atw=8CoVj|1~hR0GqJ-&JTYT?^_v8>pvE+iF+3H-_mNO5)mUG zJ2=%S&-rD=KvJv7;mDGx8{wy@_ntQq+E8^%?H`#7KaziYAg`+N@`72lJ-D0iqQcI~F0!SAmq zJNrN9O-X0tN({*of5$7Br8&4+`}k2ra&Ku<*QOH3(Zs`}8^S_YTW-d&sz-m%2nG97(OgQLk08~Hc=TYx+%>1`z$a>`y`)w8Il-%mMq?B}O=+*P8sR_?ro zwg4y*&wox=pyxeZ%gl4zWB&9cFLSMLv-RL3mciJbSm_n>_gD2mP9;}txg~%Wl%QI;A;};yf@lCR!b!}+uD>{o zVL+Uyb3ppxHs^pD;X~RfL4Yw&L!S$}4BEUI|LMDjds6wCkz@D7^Xj-U19xy)E;%Tp zZYqYxc3lsr8qZ*Jf>;&5JRf5bTuAiU{&^>jZS4=K7S>gVW&%|*Jzw&-+<<_JBy51i zhRJ0ID(O!VJK4f|fBXnRzea+c5qQA1<5bI|7lpZmhM7YRtQp?I06b zoUME)VF2!>PS2sksrvTKEa2an9i_Q_IodD5c~7$96VIo1nwY6xd<7CzeYIgO@6Y=KL*G$~t;b3uSD?!~)} zb!>mB&nhb_>XON|cylq^li-=<<-NF{C!MN}x?^UMo}8rVj|ZHjfy^TaVZFDvkC-4& zN+*bL_R~)ij&*)C&hIO9%dK5#>^k}_?)s7Q_ldnL}= z=wo-8Y&TV_$iYJ>RGFc^?9IDQ+4pCFK$&sZhXMdnmY_6=xfJRN3C~?H1gz&e#EGD$ zV<3L^d+yTi{(h8Im(o@>!B+{HN&efau4>+T`aJ%2Z62Sh#^51T2 zl|udtvCUzC9}+&P@GbUV=-71pj_8q#*`6E@IGxhzXEzj(_x4vgo&65mV61uluP~{C zp$#mPvG+!`Sdm#nBEddn8j3^dsNEkleE74?W8Apa@Rblk1=cL#a|J3!4pZmWA2^}o zTUr`&$@_$HSIB=Ckpx8sg<~sE`Qju!0YZU8(fMMYwiGgxXh1UQqMBoj%AI_ErsK~9 zjci%$6Fp8|ak(4Dog02{yFk-$L)Z$BYVOzbX5@sVjq0FDA>qiV#nxj=W~cgamJW|d z?KkQU85+!T(+V7yQxS;1~W0%|GlhFE;`=Q7_Ok*5xmle}wWvXJmy7 zXF;ps^r#MY4>jGCaM@E`!;@Y=tp-|LaZs}giF+oXZvY^)7HKOVU4)4i_kYy znFHrGD8782)mjM$5h=IgO7g_FzRcGWQTMsCzRSk6B2U6?hfOSBI4FX{pjIHR%C*&U z-#`HTjsklQtol5Q>NPkT?VnETy-|5xeNA=v({5RUc|Xf#AQxP4sxlH)t1(i`)X;sy zZPuq7q4nK&H9=4im(XDcqS?&SAd}; ztOe%cXm|b4fnt3Xq?CujXTa?KaP)FFom;b9?Z&Z$n&>M(Y-^*whlqI=ZS36O$|xj` zgES>QA7N{FpCyi8@OJWWehI(Eb(m@^<@u@hm`pJtd$Xk8hnX4+IOhPi` z{7$F)ca+iKj6jDss209UNT>pF}B}Y z{rX1Y8_0sLjFn!Ob{;~Eq)OM1k`DkY-lr(h6O5@V?UT`@HcdoUQGm%xX<(J{Ohns( zeIKh)LyUj#KK$SpzFTm5H)?tjxj&$8wl)Rg%Z?bz9H02G=c^`Ear@4y<%{0RVNg5c#A)b3FC)KnY}7># z$KfG^uZ-J{71Q0`My@Yie$oN9QiGIi`RF8hE{SJ17Yr*GZIZm#1YPyr7~(adJSh4!XCvDhKCp0&{ffAjl@+8K zf$yVo{I+36*o_;X%mwf!JGt}s)l83m?P=@Q%aWp_aX^`LAi11 zAk??&zb-|-6?mozzH|Qkif1fG3qQ^c&pPB=sb;Mgv$xaF*O2(v298>wt<>7};8%*q z$?pf%umdvzYV5TL-?RRPOKT|a9^IafPMMCq%c8fep3yY?9(9KN@bd33TOeiaR9E7s z3!+BMQ3cLJE#pj6b}r#p^H&DV`Fz~RR$GoeO54`_<|C7Mfm^`ifHcZy^3cx5?z3|- zZU^YusrF6f>N@%Pss;Jow)LU7r&@V6`r1P3sn7iR1D`1w%Nlbq;c;;h04hk$>#rG8 zqcqzz-nfeFx0>r`)3-GjshmN^z(Z`g>C72Rry7E zY32&qMJ>BS3Yd40f8t@fWpCx|Rf18^=%1c$-1jL|7pFT;#7$c_UGFvXtnO45*1VJ7 zj@$|{Fbv_Wc3qvFRVYpnVUBpjvuPjY8(i%^)0 zQCz=Z#!*<1A&3ug3a<43#U`BND?{*Gd_scO%Zn!H0gk{438fj4a-(91y7OfDyvNF& zn>YPHO7#IE^A8LAYclhV&8dib*zHhF+R0heq2=E>DT{T5$zz#po5G56kCC^^kvsPf zTL`w@mKMYPLeZ{>wo-&aWG_t$oxtd8W>NN%4V8s9eBKr^Xd=sdgu(B=+<-Xf47 zi;Ii@0;I5d0qh6p{2gJSfJH*e2!TMsgE$~aR@O(fX`HDP8^A)oU22~XCl8^PcQU_oA-;>W;b8G8JleS+@FXU5*)YN zua!_N|GdlGJVgXg=olJ$VhVy~sL~Qd8w)5R;;A9rEjYeXS1;I6h;|p{B%#}pQ5&F; zyc}{HlZ?N^d4*|0t3g8K_qV#Pi+tt)4QX(xNRu^Xm6gf*(_hwX|H?tj6iK#0`r7}Y zQ(9S%P}djgZez^tr&3?`(jVQeTC4O}s`&4L-|;?Ik0j*>dVf4febz_@XyOA{X<^XN zk-78gl?+nSuVP&FPM4!9qHNW`7Lc<+!VgfT5?41W9dqo^n{Uq84(5~KHAu9;1l!i8 zy;VCtFlVD7rG9Ax>!U7dih=Y!%O?mio9_QTE=3*j{THmlHU3Q35|#+tA{k0az06KEJayuc?0pG{eN~Bhklt>IAYm$ z8J7iN_;Jy-hJM67O*#{t@Q4B`=u8N%u@oaz9YiiEbW(xsi^9K0vGI%%@jO3^dz5zN|jWyOv4 zv`zf6k$z6{0YC-RV&|mktw#|{cpx%L2KtfI%e@{-FrJePRsI)ODTVJFbMC=3t zl+qLQA*-W6!j@;*BNpGyB5cs>k>R5rSNc@&U#%2uyYv4qp5c}jy63agiq!|II5W~U zDUvG@l?~kWjuvj4)y`CjzHZks^PXYF-qA_C1l|Iq94~T&k?87gAiAjEx`tt^Irds1 zrg?k*`IY7rPvlEmCDcYQ9?w^GI&r|&=kLY?>gx_`pSiO`%;j?JTyx2t%`ek=*PrxY zIY3?tkhrcBDNh4t@3>pNX3t7~DAH7{g;U2%etI=Ipi6G>;8`egb8|!KooFOREa(8d z`oi{<`4tt7Gpq)NO_Gw(vsp~^)!ItL6515xEU&q%#OYsO_g{!}XDj!0q(S3$wdOX8 zDD!M~H1=#@v#!>V<1c08`u}c}X}!H!c7AW<-IODDjveM>8#?iIQFP0q{@1!tf8YLw zN|o1xbVvHy_@F$lgLq3faSYk)Sfduu3IVL9w${tT^6iH+!l2YMzd}~0$y?v*|gn`WO-Rm{yz`~a9TKo`xKx7uIM>)6 z)x=NCePCC%PIr4f|FY?k7pH;eD*5n?RU+*Frv*3^bcM=MvxHybJGMyNA@73&POTT4 zKrx&gy|0vxZj+>C!OBPradeF3B1Sn&UH*Qv;%@7Dk1a~%PP{WC6YA@;@p_3)Vfi@P zkN)V=(w^^H`nhrccX(Wp@-VV&`@Q{Yf&=Ysi8!f6lf7jem)A=!hq7JHdU)_Om==H( zy7zwe_WDC|VgbDfwatt*oDj4dHYnrN(!G}in*kAz^kmvzyWPPFxQR968eP{3%#WGA zT=DCxoW-{qh4+QzC+f)M{qN4{J_t$Q#G7tY2YY>u_k|M{chT?48_4zX;Vh+A+BFtf z29O->?diuwpqA^w$bC8}pl?Ec@W1)vwh8XQbAyn};Qu_m%&CRW@$!tl=ucl3s&ZMs z%m<3O9fU>Q+fLt4)4lmWJWo+EKk`$#y{XJZ!}Ovn}Teb@hC@tz`gyEa?2c*?JzPmw@bNE4p0DJsNd zDj?VCyQ6V8S4dx#_K|-5wQy?~l(^@qIb#Dv;3S}rkFtef3W?XW@UlD}KIAdE8e-N@ zhxJ;IJ6%3Nw(q3{uG)DMi>qu2tNflnRPViJepQG~uL`J%~al96DPO0LUK zJ%qdJJ)TEbqdl)!f3NRHz|van6|3JL${+MBe!E zmv%+b5?T1r;!ZF2%MUUiHdUUL%ze>pCRMh)``NAA<;kZ^vM}8RY?NB_eSCsRFs9A-p(EJ(TJ05DnJ)*ohq-XYS86=eSl9i7ld}G# zi&$SqL0sVz=0|$1!(&9s#;CjP|5u(Z2k1{*c@q%z#e0Xl7!C?JV8c(9odqnKww#eH4SLZd)T{PTyOjv*A~e;=I?E?-lvx{Z}4(nh4Xo9kvhaw@9cg3 zoV&Ox&@GmD$~;I*+IGSC(bl3wO)@|dQlQ5}B`_aISbohw8U?Y8l`a3ZdptzOUn;XE z4L@S-TMlynA~VaC>JR0?iihu0 zy*t-dyZz~b?xXLM7`K|#Xhb%OlpCJauBYG@`S9rBE=a&DG}E8v{Y>w}+JwF&Ticmv z0b#wc72>skK7$Aq6aF82?;VeI|Nal3?Jf;bDl$qWlrAJGqai{mI}v4OB_x~1RUxD7 zt&FmXtgMC+LS#lMvyAM$e$Ur=)#v-U@87@oAHVy4+#c8Cs!r$mKHu-xI9|u`Jf6q% zIAVS0gq!MdSoVZQ;qO~$c5(}>qdma+?Ty5UYi@YL$PT5N`b@z$c~)X0UXhplHK_pr z?8y74>X$yuCUfyE(B1ak@hfVtcJ3V$eGW-I_Fe3o%NoDl)?cyt;6%pB&Dt7=Gt>T& zOq~r|9tTha41aE+GGq=xNvS(8^;QFLDXAEOF28Jz2(}WG@7fta4L-u`p z>85JRzFS;<_3hGvb_tS!Bq=NH9b#?CMaIL3^|uezL_SlL=?aJ9LDi7z?n+m9=M0f}d-`EKpJFrgM(3Tmh0++a3S zg_Iy^erwb2w9SS5qL*31nHlx!X)j;aUpDaP3gmsXCHP)kD7R~Vut(gtEnWJ`i|?=O z`msaiqrT(jX-|tpRpE20-!6W?Px8wdsV>iS>w@`x>i#{K4a{7% zK7~Y2nRWPRJ`MSGf zpPC*t1f5ToA+l0WfQ+JUsYmT)RR-P)-s7^Shv?b#zvI9Zb$aAAJ$z=%wg6Q-KFX8b zbkT#~?gJy>KGS#ACIdb38bl$oe6=lyALGa)RJg~vnxGd^37nY+i-$|T{utKMolY(E zeBWQLV)=z$otr(CYxk(VfV|ntVjVi{#WeGaYc_n1ziaPVb)|FPy~pYeVUBSFuOi^G zf|Hj_?vnIbo9$2KT?k)JS)A=Zkh~?bl1BCsedt|`Ch{$}pHF?FoBDf2$*IrkO`n8) zo{Sx~xnbXaHgN|h;F=yNTfGJ6kyoe@_dl!uJSR&UfH`s4{H#=LH6x^Wl1$)NkSJ=0bUYb?sAMHPhoO-&?*yC_T*AK;_ zdYs#KPFU~(Tf+-Ge+a2?(m(u&vZC1!S2xN%sIbYxFwSP99OUwwJFCo@4MAgrWmG$k zY!bmjnsYTS_dVmwaoNSnCES*rzIt;FVh|3|iW6_6_h~NmFQvX<4DEmSS)GPgS_voG zoBRm6n8y2mXY=2YVR0gNZX`;#Y15PXXdCn*}a$L*{jjy;By!Fx89zf<4;=*=KZ4*Q}5n9$rM#Nz2~BY zov*8_aTcxkGzDItq|wh%VMUcdC27~wLcfUv?1})6PayOh9_YUHtwY~L6zvGL14%lSsX?PER1f&^h)Xz3G9$CH_r3qqn6xb=0wOe) zdV9HfPFY;xrWtcbnmc-L(f0H^ihl296@rvJkk8n%)ZR(2-jaid-=bG}K1d_!jm_Tm zuv6Z@HhgqaRH`@8dp39jOKdBlfD^($Xo^iMMUMNXTFNcr+-f)dtOm9t5Ho;+1)232+$FkGgQSFR{ssumZvJPT0%KUbpf$R6rMx z&_pWqcJLDcv*%a(T?S*@~%d@HyME|UY#|iF)@t)_ckQa3k8{{cW6V2sA9UV zW@3DNxJtO4Nd2iqf*uY_M8i(^t+rrUh)rhMpwhWbIb+K@-X&yhekDNWH%QIHV`W2+ zw0?Vn@|rqBW)_y(LMMj?KGh8uG&G`tsGv?jFSd6d)0p7r3q^@gn4I4O{@4)j@jUL)XPxt4Xt7mVdd#d8X^>U!!^+6#0 zbqkxrjHS-CpqYi_J$^akZKd_Uh|12!uapdzT?LFYY}r@6^_}p{>+U` zLVs#dWPiLCFhMECnTVgG>BGrAzHg8w1lLa*N^6~w$K`VEUnF5l#K>S~9^O+9l* z+dYddo4s0}vqtBnjk4TJ$QzHU$X2auXgGzu)xn^%4L?3-CMuTn{=7GG{joiWA%ib0 z1@6^+lp1FJvD`_Qk82a%;~qCjc8G7$VqlSCB#$+|_4-8o8nsA!SBIe6at^zV8$N5@ zr|-9?5IA>(zM?e>8J^Wozl^fopdaN_9T9L<_$3|UISR|A7u1E5O-d!8(VH@O(Dn35 z+w34}ly&;@#HA{JM>pZs^G)lr&xw~-Me`GfYVc*fk6PGY>PE+7%kpw_-++~Qmr=zN z3@aWXt(F~j4dIgK)|_4Fi~jQM{?#tCyG&n)-T#f-Tz_~X6QOmXb=~J3xBrn=|12-J z|L465236j?J*q51=3kkiC<`-*4CRT#8@>%|@QAN@qd`;xR z&);oP-ry#=9IBE4%v6jWuNd*^#jg7JiNF53K}d8o?|p|g8nzP_lAq)!o+hS75Bibk zjtlsgn+P+xg!)Xd#=iTYP7;eYz+lh$E0QYIk8pT zc#tX&5;1c~!|{g^8?V+zFN;wXn%c92>}33aAj19{86^3L00N32cDCp*))k%SVqGQT z-=C0dCScdyv~zH9kbtXiOcUgRb@E|{MtWlDNsis1yVCd)vkZ1(Sr6m{Xo-;2OCA;e44V^85kZE?1Em*pxX!oI&j z{IJf^9cQK4^C=n{Y(JCASFDuNv5K*!+XA;P7y7p8*vWWSq`i+Twa;GdGhB1>6Vs!E zWDpv7{<9wFaqJL>&Rpj2%gzf_U!62pozMp^k_C`wk=JuXEg%_g1W@I7@{LQ zRDonY{kdCq74pc8-vElXjf_Z|zfjo43n!%2k_KP?xoKmL7Z+9{5q{X=V${$XT8>lt zyGNzySue>+byewg8J52cXQw>5z<2k=uGI1A&o5I9jg1rPr~6)IJx@$s!F5jVpsAUs zl$q(rZ{bIf8haVOmE3zl$)6Ze`7F4gwF!}%FHTr3r$B0H=coZq#QkTTKi}ObmiQof zMXTy?g&IeKn6QL#pF_Nf;qts^ThnZ^Z}~pwWXf0CMlZHCluEb7RgLa=KHPGMPmz>4 zBLM%4PTln{nGZ0#jjzz%&>nfUX?gG|5letQ~a?%TGC8SnU&){+QYKu3xihOmWYXoHXU=+#hZm z{Fowrm7HE>CGw*1vQF23!HWJ45IR-)XK6PN-t+g}GQkx5=IfW`_Z16xYVQ3R-wJur zq#*+@a2LB%8MXzm|GKi8es{(r=5WuLAGmziPCyvL0 zk`;UL6k9y##XWJ;u-LSlK5r{pwoGV#AP|wqhny5@3aVkppQFckEfxvfy$d3Rwwg&N zmg#Z!?iS={Ybjs#UY7fqs-k$h(mH~k+%aXOv2?Fo4Bf93|IR1E%Tibig0B9BQr3Qu z#_#B;unBEa8Lj#0Z?XQyWsZisPnBu!Kcl)~GAt{CTDHIa@OiDG+=`%9CNQSgt}cCb zM-%?uzdr}1n~tr)roBG;((${s`tiR6=tmz1D`#Z|1_ya3bvbrD+xB$lb5pScOG}ND zdaF2jG}TvLdLa%|UFdWfx|C zaW?P%$S>ATT0;{?sS_>z7l*7bQ7EsAo<>zLL|uLEqh0>I!O*&6R6|aDQJT%^C4-8; z1iX-a4T}KOf8NaMsv-<&-_1X#5Sw0q)=MD-)bUnKsh#(-Jg;t1w2SDe+l#=T_}kE#arQp zG7o>_B@}dUB;ef?N>;U&!=OsS0&?jgaXtffs9w>9kQwLKb~`cbTal_>ylG1!>J=y3 ztZL|oituH&6AfycRsC2tbIIezV;6SPk}E0ln(0m;Nfbmb^Znj7+03FbdkW>eWk}7k ztQGz|DzdL6C@9ka1R|Tcgth~bcq5@H8DCr#ym_Le=awKd zR4dKqiS%5c?GjQ3vq#raZYlCswLgj#qg_kcLy`XRr zi8qz_R7dCcyB=^Ptj6F-Q13l&FiV5^`s-z!pR+ByD=fxqrMXhpQkq$W zBPa;rO4w5XCP0z$oDCXzDR5FKrZq_2U+Qr37MpDT$DceIz+6J@lePW;KagI<_ba(! z>TnmKO$g^=5(9yuiMoT_^8j zV3IMPB{4F-vesveLLu+8lbuxZa?T!gz;L>OizBuU7q2QhR}IwkIIJj*eV-aS^g21)Ch4issA?btsCt+vfgS`KDg6PHu6geB%$ zYB0`9=foI>3g@mx8Q10hdezVFTkdDOgqmaIjvd~*Qz*?^SZ=%#j-%zxcD>=kdFL=G zx+on~h?Vu<2N509bFvOv=BSL0z1G1;%jz^<8zf?75bq_@6QoP8vI0ZJg@xDpcpYEP zPA&VNv4=ZccQ5{Iok3Btm&DRc%?PwKq(L#>$h(pSQYwCyp?3|FF+HaVsq!QP(>Cc&}UG;X&!|qy*#I!=Ej>l@z9k{ zByQC$fpO>rm!g|o6!yu|_-zKt>nvkbwdOA^&cz<-apQ_`#Sn2N#0`LPw+pQuHx1>F z$;W#+pe(r)edO(;60rGg(lI-rR36{5Ws4+eG*M|Et^c9iO1HqqE3AN-7D;ZYTgV7F|QB z&;;92&gsLdZJL|NlD}hI(da4s^+I3i7~K9>LI)@m{bwic(bOUgzS^AjwA707 z!Wnn9m6Yv!0jNh2#HDzz)+-NKI^M23a7G!Mj6(5C=1t8P0Fa2y*re^k!WDM?#6Imp zQ3VwZbNIZq(A=HIDwA^>X#B8>LfNE{=9XCfdzvYfTcRTkYTBnz{+Hdi;yKXefW-TQ zU`9xTiJ*(nIad58q;D)hA>fLmp}4vRNw_#eWn+&xdMCu;ZlOT13n~e$FpCw|Mik1U zY?um`b7B+9@!m(F6w`?@<5P3-re!|-H&Gq!>zw>-l4?6tFcpP~`3nz^yiDu%qc=^@ zgdZWY5}Tnz>JhmxVECevFR$y>!6)PL-mY`b(<`V3+|*xU{yVKji+-U0Y!0-@r@st2 zZ!4N@NTpX+IReIm-3{=l7ALcd{AZhCaEc$WId4BYkaqG;1?5|ND;7mZ1#hYc7UjZ^ zY7Qizys+X(t$RRpA9B=1P5>NgvACvz(8xKNf?`A6?6CAJ(Yq1$x8u$_K35`hlj!9# z`|~+OgIV=88DNJ;>Xpuwz(j0Aq3HbcNXS=-@PWmf)0dEl=F@*mH!RcmE?w0d@dh(y z>JMeqQxF7_bJE0SM^`gZzJl>Oy>T67d2CWDW|=|Cq6BS5#b zc>5QZsdeWJHr5kzLA$QU(ox{_NEnNHyF%;ntfs?YrUmp`dY=e9*_S&if*aK^L*>53 zkEahxK~Bi-n+C#WE!MPpoIbj%wX!r9rqL`UD-;s~kC;eqw;xV?te z6=u7jc>!jN4JlT4l*7E}R~`Jr1<-rS1N&MCitv$(+1FT5j^C2dY4h3WIk5&EanW8p zlMy^B^&d<}*2 zWB^n|5x%^E^knfxe>E*P6mXRy@t6JTcELda$EblHcSX7UHH-;t&=w>NY{(_&-ytn3 z_p*JPwj&*_*!cMlW2$Y${)V+!6BQSC6+-j*wiY-9c7s1gwL6SzQ@W7T^MVY+k&Q=w zJW0NWG0hpwYCRaS;u!l>o7FitrkdUCP?Yh>VcN<0?@IQC6oNPkd|x({9Z*l_0QGD8 z8kQU9VSEH%xE;M=mVvtWlI}olR$}a74a_DEL++hlr`R<~JD!EIc!`W0Na7be7QoOB zOJgFV$#Uc4@ev6aG7YDBLMW*|I^`I-0HtDny<WBa#m(cL+-pOu8<+K5A9!0IW&j| z=WFJM-w<_5Y$YA3?s9d!!KC^m9+m;H@_9=3<8K*;B=?%=OD*ky%5* z+GW1keC37X%%}iks;obcMKPVUBreeT=;TH>r^yyB9<`MG5?#2HTg>xEq>zu{$M9{# zvgPr;$j&Y=Hc?NOCGliFl&+*Kwt%L)750N~WRa{RtUQjSwK$nwa1ZVfNhsN2p08k_ z%L-?!J+ieG0*mCFE@=ec=%p>Tj4rS_exte=6sDXEuE#F(1?Syz;8lN$z5pvF%?RW$ zz6ORWuq9aGfXH2-J1_O$X3#GfxIcCK!{^c$zj=>_Axg$~5Amp^;VDNOHza`ee*Z$2 zsrtT4uMR@0Q+GGbd%jxq9nuqFqtskYbAUpd6` z=ssY%`QDnlbs(qH>*4cA=Of1TagX~I@gB;~cvMT5p!rAVx4X2}cux|Bi#h?A8mmh* zxQxxgNcd~#dp3%6J+l278yJNF%;nQhgwMJcru9XfVytY69QnpuNzE2zESI(wXnw`+5l9 zMj0N8#STGXPEgKFPbd=Zdz-rbWZ`3U;8ga+3D)&$3--vhnJp~%UOh78P&f8mS{z!c zen^{uG!h#C=9^)b72LUeINdRu&k*7eUmL_8S3Gq0t1unn!%BHEGwzo1aK+N-eucYi zPB9{8a%Vg`>*S19@OFmVe04jyQG}zZ+f)&2sMFhJUN;{%1>)vI%|LRqlb>$Hk}GTZj0tC4{*~%ggB~Y`~99RDwd*gVCM4S zb{EaAQt#RX{!SFy@t_58SPt^@RyX}eaCPF$l5{;8XGu6eR*Ga5l=aP6k!>ruMyIwC2(F?L5*_8(Y71vU#Y0jmW{~6 z#Fl|0q6-^0@_x5>@mGhTaM8`=i?X7gF!lQlACiY_B9~ z7l|*WGuw7Y{t>lmz^D3g8}qcOEPyDJ&)-h(cT^CG3;OpNw^H%gU0oH ziACCf@ue#<)_8bk(rSy53-0ep650wPE_-N_lDp?C1Wtxr8X>xogN4@_^^niqouOcS z1*KaXHY^$i9VmQ!?lL0Wpns(j( zBnT@;BX0o2cpn&4?bAcpgUtwGtTcB~j+GC<7`*6>a9I4&3t!|R=8{}g2TQO$vMCYY zx{;t1Spgd*Zws$_re{0iIEe*E2zQV@wO#m{rYPKtS}bp7m9><#LEIe!E>U64i1_6Y53!Jb zo)}Tn^pW`oz#cXcUpZZA6InRtlQ`u9Bq2lzhjQ^GF|(cC65$Nb7BECC)Ew^6VJ#RsZ~y)RFD`=jYf9s#X8<&-44J&di^G zZjt*x_oL_euxGknQL(L^>%!LkGapNgDmInnQ-tKPvmfK(QT&xi4kQW{I^+DB4bO(G zpnn=D+*IohH&~NIX+*TUc+LjB^RE1!J~(*cWA=~Qe-F?3YhgBD?KnT1;|O(Ck{99p zGtFUkJ;9uXA$Bq~%a(WhQlnG~rNR@=B9|AyjQziq(EAm(#{s|tmz)aqZZzu1_JcWh z|1$K?W_edwHG`+@I~jhDz26Sv$Kq_JTEQnzzDX}>0kuQ@fZqIkuSCspYw}31d)9CN zdnYiNlsNT_9`D6uL{YHQ`NR#gJkNMOfT!mbc^{6Or&#qR#BI#!@{!X)Xh7URx)<`E`%(T1&Ow@iPET--u}dGwwY zMqAZK4fFfCd3fBhxyuMb0}T!0Q0NO07}Oi+L1{*f_zt4G3FsrK$6N-Ghc04$FW_uK zI8nAS@EV}88`@iyBFgHF4b*Z+l%p?{cNjemJVB5sgF$40C{nfkVyJl`WA;~jlQR*b zA}HesnV8=nk9R?GkU9ZBK%;LQi*)!!L_KZu0U~r?TSx(qkvcf}!*8!b2J7F!@fICD zO+Dv-rVjM?M^M3Shyfzr z_zGIlze#j;xa-l$^azP7Z~w``b9Vx%^TNI8iMhz#Y_ll)0n|_PM(ruc4d=me^ZN*` zIwJ5zTmld^uB24XaFO9AK9Ra%I71nmi(_L`H1hX0={>h#BEUwmgE&29CA)@#hkr5BXm0g(n)>FmPtiW}Kn!WQ`FJrUd>fS&5f! zwr~=^fUHC;9YY}ptT2ljv8iGL8^d+%?px5qlOMd+c9?)k#OD}5P^n&}{X*wd+s#%p z>H$oi1DSuaVsW@iVbVGfI(R~V>ws}%QgFlkfx>Batop4rqGM;MVam0(AgtJUI2!|* zO2!j;{#UobK`!*AE=A_q}pd=n54 zC{$&{Kn3Ayxtc}Ttsl1^yn3E^4Oo3Z^~m03MQuK5VuCp1|X_!adYbhq2730if({Do}KVa!3em=Z3KNbyRk5L0Y(Pmk>e*}$R%Mg1h{%RZo zMZvkqncz~1Gtg&y2-v-N-GTf~1T<64c&G$Go2Yw^109uzCz6!feoVx_TyN33KXBR3 zR8d?z7-oXrJn+&@b=AI$c{k0T!T*j;*srF6O)iM_TwalnUH<|5QV`QyIn&8ku8MH@ z16-=~L&AhJ_6#njh)Wx13-pa11tZ{X>@h$;xL^o}iuHQuakL_Z;~y+!qKemnYXuWy zOJ3K&@gM*PrTTN*{7j8YwQ}-BA?o`_%Ge^|TU$5w^zLp?!^gIi_R`KthBK0I7&1$Dt&1NH$1@F#);P**YRR|qJjNpb- zUngr<5Rp1w$0;gsHsoMAd9p@CMIQi!;>oa%HJ`zrP@l|s)HhPE5UwZQFE-WY8;=R0 zUI%EoA^z^4nVSoDdCXv;@Y|hr=~>PI^~9|Zy!QU*21|FCf2hbmR=Brnt_6;$q@$;S-ol3?VC4E433{1}NIbC;{xtYor~ z%=oZDxb2a@T{&h4tH%HVr$6~bGX4@wE^T6!0Y~lw5W67im7a&+tRM)8iPhMfHEep5 z*koZ6zkEy)6;|fMTH%|4Gjb~-Or^e#Yq<2_0I2!g2K{LPB`i(q>u~RD2oh~#W!32^ z+*38{R4vl0XSP5vfF6m(P6^A!Ah~oEOq$gf>n44DErH-O1_&90F;>0v7ML##SPzc0 zs?Tj;#qYHOD%wqb9S2npnn~gdAx^sc6Xt^Yg*d3Iz)uvzjRWS@y9t<>`Z|i-Ya9XB z;>I!G97?bp)Yr-68z2Z!58d&k?ML8Dpw`%(58kPJ8xF8bbv>bFy_GzUtu2kv5V=SG zGUmmg5;%nyEqgC!wx>*Cxn_b6j;&|+`29d ziG(uDg66Lj@qA8+neuZTI5lORS2euQEh~Ld#;TE$!mbepabuBRMN5laBIqOLNuu*J z5lR6mulf>ldO}QmddYcU(Vv{Xr-oaO;E^Cv9WauDWkN7q0x!e2$1j$Kfrn}lfIZ2F zjKRi8flt>7q);wIDF6nR5jQO3EL4OO`xHw5E5E2DX2$@Sq=wJHp?s-Kc>-Pg=Ovyp zE5~};juu6-h0ag~7bht(OY4Tn4idex+W&akB|=Vja%1Hf5C%0(I%F@LM#BIMa1f+a z^!Yxlic1zf!G=Ar*6-a0o3TmTapaWSWHMqj;*gNTE=K5V(BC8j5uw&!i{XGU<)ZCq z7!aY|#7*lG(f?T|cffOLp?&FWgw;%UU>V!u@B{KPInAJ~%Ta?7KBrn;9_SDIMj|R^ zIEFG-kl@`RvGDVpE+7fam?GTj4#bKc7>}by)jphZI%vn>iMVZ_k-WMwQa}XinfX$R z6%c*++pQH9U&3dz^vHXVblg9~psY>Of4~tc8(5m$A<&&_J6g`=+e;!R3m{%(X5bJY zLict#6M<$P-$-sF5TiiQc5>=>iEu^O>WE@HV!&jfeTYEnkk^sExEK-S5*`u&52&m^ zR*(wpBS}wn*_>DcFIu-~Ve;WQ@fi->>1X69bOEX>VDrX^p!CIpB!s(vRnutHVKL%E z!cmAKWURCh_L3aJnDN3d>_?coQihM;+be(4S+vu-oDYgVLD_o}0V;RHB2I9UjpR(| zIf|k5CSz|TMN>52eZW^Z4V@&8q!RQSR*8)~;Aoh}wOmf>OVD`@CD@a1FQQ=~y9Qg< zkuS+&86&qlIRiC))I59X=up07FFO2sg^b|hNPgAx(lJ+8XfBrmF!eEj<{<}J;xUZe z^S^A0SHARG)|>cwy*xB^MybYUUj{Aj|6D7y|)BP0FV zOGoUg;UR=l-$srqRVH8OnM&;*Y7&OHqCh6h>pWaU&Bx+v53qNTtUNJ-Mx>lm7b!A) z<21ORY$!218!`gFn>23sF^IX2HP@^1Zc~hMh?N8C@LZSXik9RqoGpe+K4vLqkAz?c zOxd}R7w|B1I^22jnPHcuDHnc|#-tcVm@3)Dyx_NFe*jsK3mJhnYA?B~Nv$=%An}|} z&EI4it7yX9m0Mjy!kxS{{cAZ-%XCL~s`Er*{@hVZl1G6%x2Fn;B;rkUt8`v0w%K-` z5evS~gWwfNHAEcTO$tmfUV=Ubdi_>m>4&fm3ALfl15?LOCT+2OHhk9l8?nlXn2jO6 zTQ>og?x~q+#~%Ch%_Pr08m7H8+e9XnZUl}uaawSF-|}LJP>0#{PZ6LBqCAMK-t$w# z`!6?Jlk3YouwIUHF4e#@983Am-;pP*271PskP+C?JuDLbBou=G?Q780-zEy=Q6`iX z?KRklkDorGB)b4;r#G?;iG;9&yZ!5{ddUJBm3poW{4@B*{PPHV9@V4^kMW=cO^|3= z_;`}GyL+|(%JyB_0~Jj|#z?=7z~FLJy>H3|0)3{=_X}A!1deN&=8kn{(Rz7R0rAB; z%(zT}a}n0?2>cMPs?(ekCPe*b?AM9}*P+D#7w{{54Meer?3#8m(UClB3Bse$et{>N zB!Lw_NjjNC$rcJ?8+Ccg$H!=SV^IX=*K*&(p68Ir9tXPEb?KdCv5oe1xi8~EsYAg) zis|5YpAC7OYBh$uNU55(lfUWwnLWPqPU<}IHT(Ai?~g#xuGjh8Z+6OwJPj27sdv{V zIyaDk+7lma7yxVhGr6(kI3(@@#slR|B}mwXAKb0ogpk{S(|mnRQ*JO$Gq<6`nVF;3 z3-V6ajIdqoTI0fIrxuX;@rLOnSJu{E2U-3Z=F3OV!&4)|n(c5Z(Qhjz}qB;6Ba%4&LZM;tQj zdJM)sRQ0GwAF63H4e9;V6C7G4C>$DClX+4dg0~v+lc~r)V5#`l)SOMt4K=(*-WZYF zB03cOfBnes-v}Awde7{{F#t^){Whd7r?k4^u=7h$Plbcu#uG0Rc-OT(eeX-A?LtMfKCi;&TR-Xlmt4kPG zhnxYm>tuhqNyZ;pe3N?gyl9yQ{$Zn>pv+C(s>v^$%_IJ^lKQ8fsl=V%1zZRl2JXHT z!X01FNJWH{x`=9UMHtv<5Tmdz%E^M7y-4v3pC71S_Dvd)O3^ez53MLCpad-BAg;3~ z)RlbGy)8JwNgh!qq_)UqDW>x|PJ}>`0Il zz(fgFAKv|QdJU`X;d5F21A3P(>i0z{J;T$C`w!;h{z7wa=?JxX$d z$ZT;`oqmc$nY>81AD&90T{ad@EFx_07{-}b&e_O8klCXC!>}`B7n$}%)%`QQ8u2l> z9^C!;E-nd-t8C$7;R=0ef^q8s9)Y0N7~o7q-NY}gPq}o|Xg^6f$Q>v+Xe6c1yKkQd zaa^FH;M2owU9Uh06&zK2O$d}iIBYc{aI8C#cp{$n&98R7Ij~jmY^)npYH`-V<&4LX z!w*6AE)kdcPBs#%6VF#cnuP&oS|((~yXmE)i6j#ux6#ef1n&vG_KF*x6!D7*93mq~ zx)K&GPNe!8_+`4xr#!!A`2*HrwC7E_D&qI;M{Zml4a=xL!aF4I_3Py)`B>xO&llCS z?DP+!3%nhiKkSZ9A2~jmh_MF}^SkUaq|~wPHQb7Z^D>c8OCODhNz(5eng(oB29t*q z^j)3A|<3(nX19MpnS0QQeRsubqVA{$sK&(>wB#!YxV} zNtz{wap|yhKsx{KdxRM=qvB`NYL-n1UgZA4-XW6nbq8XlBq3_8i6C5v^NI#WZIZx%acYoQY1ikh!f2l^t`5kk3 zXt~8B!`@bUGg;2GtfGf+!UK8`?;$2BGfk114_!OcwP`4=35Wy9!YM}B$OgfM#oD4KBxe_a zdzXRH6XtJ^$S@6Z;HLL74-PbAV^{;RFbI@5l8j%)~yKly%7tYLY(Gn zNNk%I;79^Ob&}lKXueq{c3UxA5?^gP@s2!NVTXx626$5*$ftAFw5k#mizF)nF$akz z0>ZEWVYHY)xadE2E+RV_!YqsgT2>Wbc@-oDlHVgKNqD9*_${HoAj9?W?c4G8$7VS7 zhzTYsTf%C?}2t5MJS&Y}NH426pX>=RCL3c)A8}2Yj zWmunq{`eqLmbO4cJ4oytp>p9ukXpYDJYnK5BF1yzs7b&n!)*0|e*|hSrEpCji<)Z; zBL3xv{(q9At2W#alJo2Uoa_n5h7%8qawDrwC7yIOXX+P%a2WyFashJkaBEy>y@4%mmeBCX)JStUh~8{3q@o z$Z;viEsN1jxmewY;caz-(pXUu=mC?xg}@{ppo|=7jD{*G!4EPlxt_xcl;BNFBwdgB z#I$TB=@Izdggb`s1;H0hmUQ#6ZMvPs$b*yAn|hvx0>tHXpdZ5z>ygxD_7Gg6Vr15t zk;1t!nnZ2@p`tGEHIgLnvTIGiQxm2^Ge#N91&5^bWJY8O*n{*St)0PyBDYNF1!tYV z|KmEuv=&{Bh6x!%2Q3~jL@JnJUHGjEa1XlF;h}n$~kSuQL{d7f2e>N$!|M{s@YUQoeEz_kIH2du3@+Y<-IXp0yn-6wm$ zF%DUD_VB>-h}tzuG!7xYG)w1tn%`oU{iOpt#RGPWt0n<}9?9hpbe1?}LUO=Arb<$QB3)I znk2gc*3tt=@d8M^r)ak3*%_b|5J#sYDN$te+52Zgm2lwVG&oK@Ofbv_3sVNjJB&aO zs*KDdo71>L?h%=b@TgEeZ9$eh?5d$&_dg@F$-syaR29Nlap}Myb|*Z|!7)tEFv5Wd z!|UoU?{Y~<35u5vK2GGF0undBhw1YsIdtMnpw*UiV`^GD&_8M-f#hS*d76Z#$p3Hx z@^B|P89?U2^EB3>qp+WUcl!!i_V7iBn=45Hk=43}dd2>?fc zs-#~HNkidDbO?`}%ou?`Cng-=X7W2u50e`LA`eND_QPP_bv~C^Flrn_>W2mOjNSyg z0Vm84*^U-M`N8S#0XJF_J~Lo{*ob008Y5xg;2JKopCU`a$oADPpdI~!aDfT+&7{Nk ze)xi$VGPa@=<9QqS?I(?f)K+qJK6z?g)4^KFY&-qJ;;q@2O5T1NXe&!iK165nH60m zfx0cl`f#{}A=^dlR#GpBn_`4^XA4YE2aO!mYubHqD!5LIci+0>K$E?2ZPLuL!%s=Re3nHJ!8M%=fD>Y>39H0%${8?wfCaEF zX^1OjjOu@3n+2$<3-0I$NX*4h>eWhV9Kv@v7CEy47gVo_1nUC&!J=AD6Ac#-Z08zh zr6A@dKo$Sx*SDHM)y#-kBA5aa1azpiH6S?^@IpzeK2^T86RE@=5SSAqbhO)efe*eL z97ihQk8rtF3n}9#5!sb`vFspoM9x!W53k|e8V9WtD0LW#fK|tuk~OxG^N)aT#EM$x zI6rv)w0=l>^ds0=5Qnnw=5p-B89B>IYAv#GNTNa8?XzYSupXrusBY&R|y z{x_ZPPY!uLkxeMlaEkZHpBM0F*S~bpKd=0sOj-E%9{*p&vH1UQ`2S!6%>PFwCs8M5 z9SBV%HBBuK5_hr(hf6q@uh#)A+!r{VOGyNX%%~?^wxp)NVP??aQ<-Fn;2^js^_t5g zUmZnUR>bi?;LXb$+v2<;l?lWqK`_u--niM1kYEVK4lcDOthTsF{CC23#OF$&lBo;a zO(gz`5=mYY;y1cONy6F4h$zAvs7@ZJiGGE^V zKE6zZ@G{gigdn^-=KfidZ9NFR3j{PdgnWvT0xP!z4vZV}A2Dso?eJ6F@$AJoJ-}fA zUjfY$9Z7Z@THJRmGo9h%&dp8aA?Q-C(Z*1SPSh(Ao-nceC;+HM|Aw%nc8HdEfHooe z6;p{1p`j#l>q`TM5|!n%3pjO4L3U>Xyu=&WKSd|ajQ=boN@ zM?uQjq^470C7fklcW{a=KD`(I@kM3jy|V_^^>v&a}h)u4OdPq3R?3Ndzl^ z^UTQ`4By{_#3NAcfajeFe4_*Bvv>P^kFdgppzzREl42!L*KglElPn>Z>35JzwW_JQ z*aJ=|In?;!t|aMS^CZ|0af%SqRb1?WhpCIi^a#epV84|Kd~uWD6m;Dd1;50O zE8Lj)w{T{^cX#VQa)jJ4(FMe6QTbKV4hH{g&?CM*VC*?sj>2dzS$az1~s$*WE5&ZuA>-#LN-3{q;8&ozaL41x{W&gs*Mq(3Vlx(9i`| zK3(IosGAl>WIvJ6K+W!zv@p)^0|OrLy>{>2`~I?sH?znch@b~1Mpf|Ngh{Tr#%RiN%FHNN{83I$)%77TGrMT7~acN&hpevQ&apgec597v

>h^{B6kF{MP3J<{1!2}UZnqF2?=y_9)FE& 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 literal 0 HcmV?d00001 diff --git a/docs/images/spatialaxe-metromap.svg b/docs/images/spatialaxe-metromap.svg new file mode 100644 index 00000000..7a09ca75 --- /dev/null +++ b/docs/images/spatialaxe-metromap.svg @@ -0,0 +1,3270 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vcfvcftxtXenium onboardanalysis (XOA)vcfXR importsegmentationDatabundlehtmlQC reportjsonMetadatajsongene panelBundle(redefined)htmlMultiQCSpatialDatabundleXR relabelSpatialDataSpatialDataSpatialDataQuality ControlOnboardanalysis (OA)Default workflow for image modeDefault workflow for coordinate modeDefault workflow for segfree modeOptional stepsOutputsInputsmode: imageXR resegmentStarDistCellposeBaysorCellposevcfvcftiffimageBaysorpriorsegmentationmasksegmentationpolygon/csvsegmentationpolygon/csv/maskSeggerProsegBaysorvcfvcfparquettranscriptmoleculesreportBaysor previewhtmlparquettranscriptmoleculesmode: coordinatencvs/loomBaysor segfreeFicturevcfvcfparquettranscriptmoleculesmode: segfreemode: previewmode: coordinate diff --git a/docs/output.md b/docs/output.md index 2a27145d..68c0256a 100644 --- a/docs/output.md +++ b/docs/output.md @@ -1,33 +1,181 @@ -# nf-core/spatialxe: Output +# nf-core/spatialaxe: Output ## Introduction -This document describes the output produced by the pipeline. Most of the plots are taken from the MultiQC report, which summarises results at the end of the pipeline. +This document describes the output produced by the pipeline. The directories listed below will be created in the results directory after the pipeline has finished. All paths are relative to the top-level results directory. - - ## Pipeline overview The pipeline is built using [Nextflow](https://www.nextflow.io/) and processes data using the following steps: -- [FastQC](#fastqc) - Raw read QC -- [MultiQC](#multiqc) - Aggregate report describing results and QC from the whole pipeline -- [Pipeline information](#pipeline-information) - Report metrics generated during the workflow execution +- Mode specific output: + - [image mode](#image-mode) + - [cooridnate mode](#coordinate-mode) + - [segfree mode](#segfree-mode) + - [qc mode](#qc-mode) (or using `--run_qc`) + - [preview mode](#preview-mode) +- [Additional functionality of spatialaxe](#additional-functionality): + - [SpatialData](#spatialdata) + - [Xenium Ranger import segmentation](#xenium-ranger-import-segmentation) + - [MultiQC](#multiqc) - Aggregate report describing results and QC from the whole pipeline + - [Pipeline information](#pipeline-information) - Report metrics generated during the workflow execution -### FastQC +## Image mode

Output files -- `fastqc/` - - `*_fastqc.html`: FastQC report containing quality metrics. - - `*_fastqc.zip`: Zip archive containing the FastQC report, tab-delimited data file and plot images. +- `image/` + - `xeniumranger/` + - `resegment/` + - `${meta.id}/` Directory containing the output xenium bundle of Xenium + - `baysor/` + - `preprocess/` + - `*.csv` filtered transcripts CSV (for Baysor 0.7.1 Parquet.jl compatibility) + - `run/` + - `*segmentation.csv` results of segmentation + - `*.json` file with outlines of segmentation + - `segmentation_params.dump.toml` file with full list of parameters used for the model + - `segmentation_log.log` output file with metadata of running the workflow + - `segmentation_counts.loom` loom file with metadata + - `segmentation_cell_stats.csv` statistics of segmented cells + - `cellpose_cells/` + - `*masks.tif` labelled mask output from cellpose in tif format + - `*flows.tif` cell flow output from cellpose + - `*seg.npy` numpy array with cell segmentation data + - `stardist_nuclei/` + - `*.{tiff,tif}` labelled mask output from stardist in tif format + - `resolift/` + - `*.tiff` path to save the upscaled TIFF file
-[FastQC](http://www.bioinformatics.babraham.ac.uk/projects/fastqc/) gives general quality metrics about your sequenced reads. It provides information about the quality score distribution across your reads, per base sequence content (%A/T/G/C), adapter contamination and overrepresented sequences. For further reading and documentation see the [FastQC help pages](http://www.bioinformatics.babraham.ac.uk/projects/fastqc/Help/). +## Coordinate mode + +
+Output files + +- `coordinate/` + - `xenium_patch/` + - `patches/patch_grid.json` patch_grid.json metadata file + - `patches/patch_*/transcripts.parquet` per-patch transcripts.parquet files (one per patch) + - `output/xr-cell-polygons.geojson` stitched cell polygons + - `output/xr-transcript-metadata.csv` transcript metadata + - `proseg/` + - `preset/` + - `cell-polygons.geojson.gz` 2D polygons for each cell in GeoJSON format. These are flattened from 3D + - `expected-counts.csv.gz` cell-by-gene count matrix + - `cell-metadata.csv.gz` cell centroids, volume, and other information + - `transcript-metadata.csv.gz` transcript ids, genes, revised positions, assignment probability + - `gene-metadata.csv.gz` per-gene summary statistics + - `rates.csv.gz` cell-by-gene Poisson rate parameters + - `cell-polygons-layers.geojson.gz` a separate, non-overlapping cell polygon for each z-layer, preserving 3D segmentation + - `cell-hulls.geojson.gz` convex hulls around assigned transcripts + - `proseg2baysor/` + - `xr-cell-polygons.geojson` 2D polygons for each cell in GeoJSON format. These are flattened from 3D + - `xr-transcript-metadata.csv` transcript ids, genes, revised positions, assignment probability + - `segger/` + - `create_dataset/` + - `${meta.id}/` directory to save the processed Segger dataset (in PyTorch Geometric format) + - `train/` + - `${meta.id}/` directory to save the trained model and checkpoints + - `predict/` + - `${meta.id}/` directory to save the segmentation results, including cell boundaries and associations + - `baysor/` + - `run/` + - `*segmentation.csv` results of segmentation + - `*.json` file with outlines of segmentation + - `segmentation_params.dump.toml` file with full list of parameters used for the model + - `segmentation_log.log` output file with metadata of running the workflow + - `segmentation_counts.loom` loom file with metadata + - `segmentation_cell_stats.csv` statistics of segmented cells + +
+ +## Segfree mode + +
+Output files + +- `segfree/` + - `baysor/` + - `preprocess/` + - `*.csv` filtered transcripts CSV (for Baysor 0.7.1 Parquet.jl compatibility) + - `segfree/` + - `ncvs.loom` loom file with neighborhood results + - `ncvs_segfree_log.log` Log file with summary statistics + - `ficture/` + - `preprocess/` + - `processed_transcripts.tsv.gz` transcirpt file used for FICTURE + - `coordinate_minmax.tsv` listing the min and max of the coordinates used for FICTURE + - `feature.clean.tsv.gz` another file contains the (unique) names of genes that should be used for FICUTRE + - `${meta.id}/results/` files containing the results of FICTURE + +
+ +## QC mode + +
+Output files + +- `opt/` + - `flip/` + - `*.fa` the forward oriented fasta file + - `track/` + - `*.tsv` TSV file containing the gene and transcript information to which each probe aligns + - `stat/` + - `*.tsv` TSV file containing the summary stats +- `multiqc/` + - `multiqc_report.html`: a standalone HTML file that can be viewed in your web browser. + - `multiqc_data/`: directory containing parsed statistics from the different tools used in the pipeline. + - `multiqc_plots/`: directory containing static images from the report in various formats. + +
+ +## Preview mode + +
+Output files + +- `preview/` + - `baysor/` + - `preview/` + - `preview.html` segmentation preview + +
+ +## Additional Functionality + +### SpatialData + +The pipeline create spatialdata objects (data bundles) on various stages (see metromap in the [README](../README.md)) + +
+Output files + +- `spatialdata/` + - `write/${meta.id}/spatialdata/` spatialdata bundle of the raw data + - `meta/${meta.id}/spatialdata_spatialaxe_final/` spatialdata bundle of the final data with metadata + - `sdata['raw_table'].uns['spatialdata_attrs']` provenance metadata + - `sdata['raw_table'].uns['experiment_xenium']` experimental metadata + - `sdata['raw_table'].uns['gene_panel']` gene panel metadata + +
+ +### Xenium Ranger Import Segmentation) + +This step is needed to import segemntations from different methods into the xenium bundle and is called at different stages of the pipeline. + +
+Output files + +- `xeniumranger/` + - `import_segementation/` + - `${meta.id}/` directory containing the output xenium bundle of Xenium + +
### MultiQC @@ -43,7 +191,7 @@ The pipeline is built using [Nextflow](https://www.nextflow.io/) and processes d [MultiQC](http://multiqc.info) is a visualization tool that generates a single HTML report summarising all samples in your project. Most of the pipeline QC results are visualised in the report and further statistics are available in the report data directory. -Results generated by MultiQC collate pipeline QC from supported tools e.g. FastQC. The pipeline has special steps which also allow the software versions to be reported in the MultiQC output for future traceability. For more information about how to use MultiQC reports, see . +The pipeline has special steps which also allow the software versions to be reported in the MultiQC output for future traceability. For more information about how to use MultiQC reports, see . ### Pipeline information diff --git a/docs/usage.md b/docs/usage.md index 9050ce93..02523048 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -1,63 +1,155 @@ -# nf-core/spatialxe: Usage +# nf-core/spatialaxe: Usage -## :warning: Please read this documentation on the nf-core website: [https://nf-co.re/spatialxe/usage](https://nf-co.re/spatialxe/usage) +## :warning: Please read this documentation on the nf-core website: [https://nf-co.re/spatialaxe/usage](https://nf-co.re/spatialaxe/usage) > _Documentation of pipeline parameters is generated automatically from the pipeline schema and can no longer be found in markdown files._ ## Introduction - - ## Samplesheet input -You will need to create a samplesheet with information about the samples you would like to analyse before running the pipeline. Use this parameter to specify its location. It has to be a comma-separated file with 3 columns, and a header row as shown in the examples below. +You will need to create a samplesheet with information about the sample you would like to analyse before running the pipeline. It has to be a comma-separated file with 3 columns, and a header row as shown in the examples below. + +```csv title="samplesheet.csv" +sample,bundle,image +breast_cancer,/path/to/xenium/bundle,/path/to/morphology.ome.tif +``` + +| Column | Description | +| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `sample` | `Required`. Custom sample name. It is recommended to follow the same name from the output of the Xenium Onboard Analysis (XOA). Avoid using spaces in the sample name. | +| `bundle` | `Required`. Full path to the Xenium bundle, output of the Xenium Onboard Analysis. | +| `image` | `Optional`. Full path to morphology.ome.tif. If not provided, the morphology.ome.tif from the bundle is considered. | + +An [example samplesheet](../assets/example_samplesheet.csv) has been provided with the pipeline. + +#### Using the samplesheet ```bash --input '[path to samplesheet file]' ``` -### Multiple runs of the same sample +## Running the pipeline -The `sample` identifiers have to be the same when you have re-sequenced the same sample more than once e.g. to increase sequencing depth. The pipeline will concatenate the raw reads before performing any downstream analysis. Below is an example for the same sample sequenced across 3 lanes: +The typical command for running the pipeline is as follows: -```csv title="samplesheet.csv" -sample,fastq_1,fastq_2 -CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz -CONTROL_REP1,AEG588A1_S1_L003_R1_001.fastq.gz,AEG588A1_S1_L003_R2_001.fastq.gz -CONTROL_REP1,AEG588A1_S1_L004_R1_001.fastq.gz,AEG588A1_S1_L004_R2_001.fastq.gz +#### Image-based segmentation mode + +This runs the default image mode:
+`CELLPOSE ➔ BAYSOR ➔ XR-IMPORT-SEGMENTATION ➔ SPATIALDATA ➔ QC` + +```bash +nextflow run nf-core/spatialaxe \ + -profile + --input ./samplesheet.csv \ + --outdir ./results \ + --mode image ``` -### Full samplesheet +#### Coordinate-based (transcripts-based) segmentation mode + +This runs the default coordinate mode:
+`PROSEG ➔ PROSEG2BAYSOR ➔ XR-IMPORT-SEGMENTATION ➔ SPATIALDATA ➔ QC` -The pipeline will auto-detect whether a sample is single- or paired-end using the information provided in the samplesheet. The samplesheet can have as many columns as you desire, however, there is a strict requirement for the first 3 columns to match those defined in the table below. +```bash +nextflow run nf-core/spatialaxe \ + -profile + --input ./samplesheet.csv \ + --outdir ./results \ + --mode coordinate +``` -A final samplesheet file consisting of both single- and paired-end data may look something like the one below. This is for 6 samples, where `TREATMENT_REP3` has been sequenced twice. +### Segmenation free mode -```csv title="samplesheet.csv" -sample,fastq_1,fastq_2 -CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz -CONTROL_REP2,AEG588A2_S2_L002_R1_001.fastq.gz,AEG588A2_S2_L002_R2_001.fastq.gz -CONTROL_REP3,AEG588A3_S3_L002_R1_001.fastq.gz,AEG588A3_S3_L002_R2_001.fastq.gz -TREATMENT_REP1,AEG588A4_S4_L003_R1_001.fastq.gz, -TREATMENT_REP2,AEG588A5_S5_L003_R1_001.fastq.gz, -TREATMENT_REP3,AEG588A6_S6_L003_R1_001.fastq.gz, -TREATMENT_REP3,AEG588A6_S6_L004_R1_001.fastq.gz, +`BAYSOR_SEGFREE` + +```bash +nextflow run nf-core/spatialaxe \ + -profile \ + --input samplesheet.csv \ + --outdir \ + --mode segfree ``` -| Column | Description | -| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `sample` | Custom sample name. This entry will be identical for multiple sequencing libraries/runs from the same sample. Spaces in sample names are automatically converted to underscores (`_`). | -| `fastq_1` | Full path to FastQ file for Illumina short reads 1. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz". | -| `fastq_2` | Full path to FastQ file for Illumina short reads 2. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz". | +### Preview mode
-An [example samplesheet](../assets/samplesheet.csv) has been provided with the pipeline. +`BAYSOR_PREVIEW` -## Running the pipeline +```bash +nextflow run nf-core/spatialaxe \ + -profile \ + --input samplesheet.csv \ + --outdir \ + --mode preview +``` -The typical command for running the pipeline is as follows: +### Quality control
+ +It is possible to run the quality control with `--run_qc` to couple it with another mode like so: + +```bash +nextflow run nf-core/spatialaxe \ + -profile + --input ./samplesheet.csv \ + --outdir ./results \ + --mode image \ + --run_qc +``` + +It is also possible to run just he QC with: + +```bash +nextflow run nf-core/spatialaxe \ + -profile \ + --input samplesheet.csv \ + --outdir \ + --mode qc +``` + +- QC methods: + - [MultiQC Xenium Extra Plugin](https://github.com/MultiQC/xenium-extra) + - [OPT](https://github.com/JEFworks-Lab/off-target-probe-tracker) + +### Image-based Segmentation mode (--mode image):
+ +- cellpose +- baysor +- xeniumranger +- stardist + +### Coordinate-based (transcripts-based) Segmentation methods (--mode coordinate):
+ +- proseg +- baysor +- segger + +### Segmentation free methods (--mode segfree):
+ +- baysor +- ficture + +#### Run Segmentation with the methods methods mentioned above :
+ +eg: To run proseg segmentation use the `coordinate` mode and the `proseg` segmentation method (--method) + +```bash +nextflow run nf-core/spatialaxe \ + -profile + --input ./samplesheet.csv \ + --outdir ./results \ + --mode coordinate \ + --method proseg +``` + +eg: To run cellpose segmentation use the `image` mode and the `cellpose` segmentation method (--method) ```bash -nextflow run nf-core/spatialxe --input ./samplesheet.csv --outdir ./results --genome GRCh37 -profile docker +nextflow run nf-core/spatialaxe \ + -profile + --input ./samplesheet.csv \ + --outdir ./results \ + --mode image \ + --method cellpose ``` This will launch the pipeline with the `docker` configuration profile. See below for more information about profiles. @@ -76,12 +168,12 @@ 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/running/run-pipelines#configuring-pipelines), 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/usage/configuration#tuning-workflow-resources), other infrastructural tweaks (such as output directories), or module arguments (args). The above pipeline run specified with a params file in yaml format: ```bash -nextflow run nf-core/spatialxe -profile docker -params-file params.yaml +nextflow run nf-core/spatialaxe -profile docker -params-file params.yaml ``` with: @@ -89,7 +181,6 @@ with: ```yaml title="params.yaml" input: './samplesheet.csv' outdir: './results/' -genome: 'GRCh37' <...> ``` @@ -100,14 +191,14 @@ You can also generate such `YAML`/`JSON` files via [nf-core/launch](https://nf-c When you run the above command, Nextflow automatically pulls the pipeline code from GitHub and stores it as a cached version. When running the pipeline after this, it will always use the cached version if available - even if the pipeline has been updated since. To make sure that you're running the latest version of the pipeline, make sure that you regularly update the cached version of the pipeline: ```bash -nextflow pull nf-core/spatialxe +nextflow pull nf-core/spatialaxe ``` ### Reproducibility It is a good idea to specify the pipeline version when running the pipeline on your data. This ensures that a specific version of the pipeline code and software are used when you run your pipeline. If you keep using the same tag, you'll be running the same version of the pipeline, even if there have been changes to the code since. -First, go to the [nf-core/spatialxe releases page](https://github.com/nf-core/spatialxe/releases) and find the latest pipeline version - numeric only (eg. `1.3.1`). Then specify this when running the pipeline with `-r` (one hyphen) - eg. `-r 1.3.1`. Of course, you can switch to another version by changing the number after the `-r` flag. +First, go to the [nf-core/spatialaxe releases page](https://github.com/nf-core/spatialaxe/releases) and find the latest pipeline version - numeric only (eg. `1.0.0`). Then specify this when running the pipeline with `-r` (one hyphen) - eg. `-r 1.0.0`. Of course, you can switch to another version by changing the number after the `-r` flag. This version number will be logged in reports when you run the pipeline, so that you'll know what you used when you look back in the future. For example, at the bottom of the MultiQC reports. @@ -173,19 +264,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/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. +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. ### 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/running/configuration/nextflow-for-your-system#update-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/usage/configuration#updating-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/running/configuration/nextflow-for-your-system#modifying-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/usage/configuration#customising-tool-arguments) section of the nf-core website. ### nf-core/configs diff --git a/main.nf b/main.nf index 0ed1167a..ed5c9d42 100644 --- a/main.nf +++ b/main.nf @@ -1,11 +1,11 @@ #!/usr/bin/env nextflow /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - nf-core/spatialxe + nf-core/spatialaxe ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Github : https://github.com/nf-core/spatialxe - Website: https://nf-co.re/spatialxe - Slack : https://nfcore.slack.com/channels/spatialxe + Github : https://github.com/nf-core/spatialaxe + Website: https://nf-co.re/spatialaxe + Slack : https://nfcore.slack.com/channels/spatialaxe ---------------------------------------------------------------------------------------- */ @@ -15,21 +15,9 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -include { SPATIALXE } from './workflows/spatialxe' -include { PIPELINE_INITIALISATION } from './subworkflows/local/utils_nfcore_spatialxe_pipeline' -include { PIPELINE_COMPLETION } from './subworkflows/local/utils_nfcore_spatialxe_pipeline' -include { getGenomeAttribute } from './subworkflows/local/utils_nfcore_spatialxe_pipeline' - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - GENOME PARAMETER VALUES -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -// TODO nf-core: Remove this line if you don't need a FASTA file -// This is an example of how to use getGenomeAttribute() to fetch parameters -// from igenomes.config using `--genome` -params.fasta = getGenomeAttribute('fasta') +include { SPATIALAXE } from './workflows/spatialaxe.nf' +include { PIPELINE_INITIALISATION } from './subworkflows/local/utils_nfcore_spatialaxe_pipeline' +include { PIPELINE_COMPLETION } from './subworkflows/local/utils_nfcore_spatialaxe_pipeline' /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -40,7 +28,7 @@ params.fasta = getGenomeAttribute('fasta') // // WORKFLOW: Run main analysis pipeline depending on type of input // -workflow NFCORE_SPATIALXE { +workflow NFCORE_SPATIALAXE { take: samplesheet // channel: samplesheet read in from --input @@ -50,15 +38,50 @@ workflow NFCORE_SPATIALXE { // // WORKFLOW: Run pipeline // - SPATIALXE ( + SPATIALAXE ( samplesheet, + params.alignment_csv, + params.baysor_config, + params.baysor_prior, + params.baysor_scale, + params.baysor_tiling, + params.baysor_tiling_scale, + params.buffer_samples, + params.buffer_size, + params.cell_segmentation_only, + params.cellpose_downscale, + params.cellpose_model, + params.expansion_distance, + params.features, + params.gene_panel, + params.gene_synonyms, + params.max_x, + params.max_y, + params.method, + params.min_qv, + params.min_x, + params.min_y, + params.mode, params.multiqc_config, params.multiqc_logo, params.multiqc_methods_description, + params.nucleus_segmentation_only, + params.offtarget_probe_tracking, params.outdir, + params.probes_fasta, + params.qupath_polygons, + params.reference_annotations, + params.relabel_genes, + params.run_qc, + params.segger_model, + params.segmentation_mask, + params.sharpen_tiff, + params.stardist_nuclei_model, + params.tiling, + params.xeniumranger_only, ) emit: - multiqc_report = SPATIALXE.out.multiqc_report // channel: /path/to/multiqc_report.html + multiqc_report = SPATIALAXE.out.multiqc_report // channel: /path/to/multiqc_report.html } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -81,13 +104,26 @@ workflow { params.input, params.help, params.help_full, - params.show_hidden + params.show_hidden, + params.format, + params.gene_panel, + params.gene_synonyms, + params.image_seg_methods, + params.method, + params.mode, + params.nucleus_segmentation_only, + params.offtarget_probe_tracking, + params.probes_fasta, + params.reference_annotations, + params.relabel_genes, + params.segmentation_mask, + params.transcript_seg_methods, ) // // WORKFLOW: Run main workflow // - NFCORE_SPATIALXE ( + NFCORE_SPATIALAXE ( PIPELINE_INITIALISATION.out.samplesheet ) // @@ -99,7 +135,8 @@ workflow { params.plaintext_email, params.outdir, params.monochrome_logs, - NFCORE_SPATIALXE.out.multiqc_report + params.hook_url, + NFCORE_SPATIALAXE.out.multiqc_report ) } diff --git a/modules.json b/modules.json index 09082eaf..c215c2ed 100644 --- a/modules.json +++ b/modules.json @@ -1,18 +1,71 @@ { - "name": "nf-core/spatialxe", - "homePage": "https://github.com/nf-core/spatialxe", + "name": "nf-core/spatialaxe", + "homePage": "https://github.com/nf-core/spatialaxe", "repos": { "https://github.com/nf-core/modules.git": { "modules": { "nf-core": { - "fastqc": { + "cellpose": { "branch": "master", - "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", - "installed_by": ["modules"] + "git_sha": "0780b963d3ab087e861a4b74e9d0e404115e5352", + "installed_by": ["modules"], + "patch": "modules/nf-core/cellpose/cellpose.diff" }, "multiqc": { "branch": "master", - "git_sha": "008f9d3e61209bf995edac3ba531f54e269e1215", + "git_sha": "cb9f4bec379866ca560f7f79d9a9a06bea8c9b51", + "installed_by": ["modules"], + "patch": "modules/nf-core/multiqc/multiqc.diff" + }, + "opt/flip": { + "branch": "master", + "git_sha": "7d3e5c9d3d44", + "installed_by": ["modules"], + "patch": "modules/nf-core/opt/flip/opt-flip.diff" + }, + "opt/stat": { + "branch": "master", + "git_sha": "7d3e5c9d3d44", + "installed_by": ["modules"], + "patch": "modules/nf-core/opt/stat/opt-stat.diff" + }, + "opt/track": { + "branch": "master", + "git_sha": "7d3e5c9d3d44", + "installed_by": ["modules"], + "patch": "modules/nf-core/opt/track/opt-track.diff" + }, + "stardist": { + "branch": "master", + "git_sha": "4e783502ab661bed13f15189401b73c93966831f", + "installed_by": ["modules"], + "patch": "modules/nf-core/stardist/stardist.diff" + }, + "untar": { + "branch": "master", + "git_sha": "447f7bc0fa41dfc2400c8cad4c0291880dc060cf", + "installed_by": ["modules"], + "patch": "modules/nf-core/untar/untar.diff" + }, + "unzip": { + "branch": "master", + "git_sha": "4dd9d8439a429c7ee566e0e2347f76ddeef27e66", + "installed_by": ["modules"], + "patch": "modules/nf-core/unzip/unzip.diff" + }, + "xeniumranger/import-segmentation": { + "branch": "master", + "git_sha": "39365e944e936511e33b993cdd978e0f12adac9a", + "installed_by": ["modules"] + }, + "xeniumranger/relabel": { + "branch": "master", + "git_sha": "39365e944e936511e33b993cdd978e0f12adac9a", + "installed_by": ["modules"] + }, + "xeniumranger/resegment": { + "branch": "master", + "git_sha": "39365e944e936511e33b993cdd978e0f12adac9a", "installed_by": ["modules"] } } @@ -26,12 +79,12 @@ }, "utils_nfcore_pipeline": { "branch": "master", - "git_sha": "a3fb7351b1fdb2b1de282b765816bbea190e86a8", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", "installed_by": ["subworkflows"] }, "utils_nfschema_plugin": { "branch": "master", - "git_sha": "fdc08b8b1ae74f56686ce21f7ea11ad11990ce57", + "git_sha": "4b406a74dc0449c0401ed87d5bfff4252fd277fd", "installed_by": ["subworkflows"] } } diff --git a/modules/local/baysor/create_dataset/main.nf b/modules/local/baysor/create_dataset/main.nf new file mode 100644 index 00000000..7e15ce8b --- /dev/null +++ b/modules/local/baysor/create_dataset/main.nf @@ -0,0 +1,45 @@ +process BAYSOR_CREATE_DATASET { + tag "${meta.id}" + label 'process_medium' + + container "khersameesh24/baysor:0.7.1" + + input: + tuple val(meta), path(transcripts) + val sample_fraction + + output: + tuple val(meta), path("${prefix}/sampled_transcripts.csv"), emit: sampled_transcripts + tuple val("${task.process}"), val('baysor'), eval("baysor --version 2>&1 | grep -oP '\\d+\\.\\d+\\.\\d+' || echo unknown"), topic: versions, emit: versions_baysor + + when: + task.ext.when == null || task.ext.when + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("BAYSOR_CREATE_DATASET module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + prefix = task.ext.prefix ?: "${meta.id}" + + """ + baysor_create_dataset.py \\ + --transcripts ${transcripts} \\ + --sample-fraction ${sample_fraction} \\ + --prefix ${prefix} + """ + + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("BAYSOR_CREATE_DATASET module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p ${prefix} + touch "${prefix}/sampled_transcripts.csv" + """ +} diff --git a/modules/local/baysor/create_dataset/meta.yml b/modules/local/baysor/create_dataset/meta.yml new file mode 100644 index 00000000..f520c92b --- /dev/null +++ b/modules/local/baysor/create_dataset/meta.yml @@ -0,0 +1,82 @@ +name: "baysor_create_dataset" +description: Subsample a transcripts CSV to create a smaller Baysor input dataset. +keywords: + - xenium + - baysor + - dataset + - transcripts + - subsample +tools: + - "python": + description: | + Python programming language interpreter. + homepage: "https://www.python.org/" + documentation: "https://docs.python.org/3/" + tool_dev_url: "https://github.com/python/cpython" + doi: "no DOI available" + licence: ["PSF-2.0"] + identifier: "" + - "baysor": + description: | + Bayesian Segmentation of Spatial Transcriptomics Data. + homepage: "https://kharchenkolab.github.io/Baysor/dev/" + documentation: "https://kharchenkolab.github.io/Baysor/dev/" + tool_dev_url: "https://github.com/kharchenkolab/Baysor" + doi: "10.1038/s41587-021-01044-w" + licence: ["MIT"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - transcripts: + type: file + description: Transcripts CSV file to subsample for Baysor dataset creation. + pattern: "*.csv" + ontologies: [] + - sample_fraction: + type: float + description: Fraction of transcripts to retain in the subsampled dataset (0-1). + +output: + sampled_transcripts: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "${prefix}/sampled_transcripts.csv": + type: file + description: Subsampled transcripts CSV used as Baysor input dataset. + pattern: "*/sampled_transcripts.csv" + ontologies: [] + versions_baysor: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "baysor --version 2>&1 | grep -oP '\\d+\\.\\d+\\.\\d+' || echo unknown": + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - baysor: + type: string + description: The tool name + - "baysor --version 2>&1 | grep -oP '\\d+\\.\\d+\\.\\d+' || echo unknown": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/modules/local/baysor/create_dataset/tests/main.nf.test b/modules/local/baysor/create_dataset/tests/main.nf.test new file mode 100644 index 00000000..e5fc8214 --- /dev/null +++ b/modules/local/baysor/create_dataset/tests/main.nf.test @@ -0,0 +1,60 @@ +nextflow_process { + + name "Test Process BAYSOR CREATE_DATASET" + script "../main.nf" + process "BAYSOR_CREATE_DATASET" + config "./nextflow.config" + + tag "modules" + tag "modules_local" + tag "baysor" + tag "baysor/create_dataset" + + test("baysor create dataset - transcripts.parquet") { + + when { + process { + """ + input[0] = channel.of([ + [id: "test_run_baysor"], + file(params.modules_testdata_base_path + "spatial_omics/xenium/homo_sapiens/spatial_gene_expression.csv", checkIfExists: true) + ]) + input[1] = 0.3 + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert file(process.out.sampled_transcripts[0][1]).exists() }, + { assert file(process.out.sampled_transcripts[0][1]).name == "sampled_transcripts.csv" }, + { assert snapshot(process.out.versions_baysor).match("versions") } + ) + } + } + + test("baysor create dataset stub") { + + options "-stub" + + when { + process { + """ + input[0] = channel.of([ + [id: "test_run_baysor"], + file(params.modules_testdata_base_path + "spatial_omics/xenium/homo_sapiens/spatial_gene_expression.csv", checkIfExists: true) + ]) + input[1] = 0.3 + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.versions_baysor).match("versions_stub") } + ) + } + } +} diff --git a/modules/local/baysor/create_dataset/tests/main.nf.test.snap b/modules/local/baysor/create_dataset/tests/main.nf.test.snap new file mode 100644 index 00000000..1fb129a3 --- /dev/null +++ b/modules/local/baysor/create_dataset/tests/main.nf.test.snap @@ -0,0 +1,34 @@ +{ + "versions_stub": { + "content": [ + [ + [ + "BAYSOR_CREATE_DATASET", + "baysor", + "0.7.1" + ] + ] + ], + "timestamp": "2026-06-17T13:16:19.929465332", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.2" + } + }, + "versions": { + "content": [ + [ + [ + "BAYSOR_CREATE_DATASET", + "baysor", + "0.7.1" + ] + ] + ], + "timestamp": "2026-06-17T13:16:11.036406226", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.2" + } + } +} \ No newline at end of file diff --git a/modules/local/baysor/create_dataset/tests/nextflow.config b/modules/local/baysor/create_dataset/tests/nextflow.config new file mode 100644 index 00000000..f8b3a30a --- /dev/null +++ b/modules/local/baysor/create_dataset/tests/nextflow.config @@ -0,0 +1,9 @@ +process { + + resourceLimits = [ + cpus: 4, + memory: '8.GB', + time: '2.h', + ] + +} diff --git a/modules/local/baysor/preprocess/main.nf b/modules/local/baysor/preprocess/main.nf new file mode 100644 index 00000000..cfe6fe3b --- /dev/null +++ b/modules/local/baysor/preprocess/main.nf @@ -0,0 +1,55 @@ +process BAYSOR_PREPROCESS_TRANSCRIPTS { + tag "${meta.id}" + label 'process_medium' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/94/9409ce399922a5746bea1b7df5668c3d1d79b9af49a15950d9818c4fe45ac749/data' : + 'community.wave.seqera.io/library/pandas_procs_pyarrow:d8f882b65dfea451' }" + + input: + tuple val(meta), path(transcripts) + val min_qv + val max_x + val min_x + val max_y + val min_y + + output: + tuple val(meta), path("${prefix}/filtered_transcripts.csv"), emit: transcripts_file + tuple val("${task.process}"), val('python'), eval("python3 --version | sed 's/Python //'"), topic: versions, emit: versions_python + + when: + task.ext.when == null || task.ext.when + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("BAYSOR_PREPROCESS_TRANSCRIPTS module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + prefix = task.ext.prefix ?: "${meta.id}" + + """ + baysor_preprocess_transcripts.py \\ + --transcripts ${transcripts} \\ + --prefix ${prefix} \\ + --min-qv ${min_qv} \\ + --min-x ${min_x} \\ + --max-x ${max_x} \\ + --min-y ${min_y} \\ + --max-y ${max_y} + """ + + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("BAYSOR_PREPROCESS_TRANSCRIPTS module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p ${prefix} + touch ${prefix}/filtered_transcripts.csv + """ +} diff --git a/modules/local/baysor/preprocess/meta.yml b/modules/local/baysor/preprocess/meta.yml new file mode 100644 index 00000000..0bff8178 --- /dev/null +++ b/modules/local/baysor/preprocess/meta.yml @@ -0,0 +1,88 @@ +name: "baysor_preprocess" +description: Filter transcript.parquet file based on the specified thresholds +keywords: + - baysor + - transcripts + - filter_transcripts +tools: + - "baysor": + description: "Baysor is a tool that segments cells using spatial gene expression maps. Optionally, segmentation masks can be given as additional input." + homepage: "https://kharchenkolab.github.io/Baysor/dev/" + documentation: "https://kharchenkolab.github.io/Baysor/dev/" + tool_dev_url: "https://github.com/kharchenkolab/Baysor" + doi: "https://doi.org/10.1038/s41587-021-01044-w" + licence: ["MIT license"] + identifier: + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - transcripts: + type: file + description: transcripts.parquet file from the xenium bundle + pattern: "*.parquet" + + - min_qv: + type: float + description: minimum Q-Score to pass filtering (default - 20.0) + - max_x: + type: float + description: Only keep transcripts whose x-coordinate is less than specified limit + if no limit is specified, the default value will retain all + transcripts since Xenium slide is <24000 microns in x and y (default - 24000.0) + - min_x: + type: float + description: only keep transcripts whose x-coordinate is greater than specified limit + if no limit is specified, the default minimum value will be 0.0 + - max_y: + type: float + description: only keep transcripts whose y-coordinate is less than specified limit + if no limit is specified, the default value will retain all + transcripts since Xenium slide is <24000 microns in x and y (default - 24000.0) + - min_y: + type: float + description: only keep transcripts whose y-coordinate is greater than specified limit + if no limit is specified, the default minimum value will be 0.0 + +output: + transcripts_file: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "*.csv": + type: file + description: filtered transcripts CSV (for Baysor 0.7.1 Parquet.jl compatibility) + pattern: "filtered_transcripts.csv" + + versions_python: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "python3 --version | sed 's/Python //'": + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "python3 --version | sed 's/Python //'": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/modules/local/baysor/preprocess/tests/main.nf.test b/modules/local/baysor/preprocess/tests/main.nf.test new file mode 100644 index 00000000..f3112c25 --- /dev/null +++ b/modules/local/baysor/preprocess/tests/main.nf.test @@ -0,0 +1,68 @@ +nextflow_process { + + name "Test Process BAYSOR PREPROCESS TRANSCRIPTS" + script "../main.nf" + process "BAYSOR_PREPROCESS_TRANSCRIPTS" + config "./nextflow.config" + + tag "modules" + tag "modules_local" + tag "baysor" + tag "baysor/preprocess" + + test("baysor preprocess transcripts - transcripts.parquet") { + + when { + process { + """ + input[0] = channel.of([ + [id: "test_run_baysor"], + file("https://raw.githubusercontent.com/khersameesh24/test-datasets/baysor/transcripts.parquet", checkIfExists: true) + ]) + input[1] = 20 + input[2] = 24000.0 + input[3] = 0.0 + input[4] = 24000.0 + input[5] = 0.0 + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert file(process.out.transcripts_file[0][1]).exists() }, + { assert file(process.out.transcripts_file[0][1]).name == "filtered_transcripts.csv" }, + { assert snapshot(process.out.versions_python).match("versions") } + ) + } + } + + test("baysor preprocess transcripts stub") { + + options "-stub" + + when { + process { + """ + input[0] = channel.of([ + [id: "test_run_baysor"], + file("https://raw.githubusercontent.com/khersameesh24/test-datasets/baysor/transcripts.parquet", checkIfExists: true) + ]) + input[1] = 20 + input[2] = 24000.0 + input[3] = 0.0 + input[4] = 24000.0 + input[5] = 0.0 + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.versions_python).match("versions_stub") } + ) + } + } +} diff --git a/modules/local/baysor/preprocess/tests/main.nf.test.snap b/modules/local/baysor/preprocess/tests/main.nf.test.snap new file mode 100644 index 00000000..1baeceb1 --- /dev/null +++ b/modules/local/baysor/preprocess/tests/main.nf.test.snap @@ -0,0 +1,34 @@ +{ + "versions_stub": { + "content": [ + [ + [ + "BAYSOR_PREPROCESS_TRANSCRIPTS", + "python", + "3.14.4" + ] + ] + ], + "timestamp": "2026-04-29T18:32:45.74436808", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + }, + "versions": { + "content": [ + [ + [ + "BAYSOR_PREPROCESS_TRANSCRIPTS", + "python", + "3.14.4" + ] + ] + ], + "timestamp": "2026-04-29T18:32:39.447114547", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + } +} \ No newline at end of file diff --git a/modules/local/baysor/preprocess/tests/nextflow.config b/modules/local/baysor/preprocess/tests/nextflow.config new file mode 100644 index 00000000..f8b3a30a --- /dev/null +++ b/modules/local/baysor/preprocess/tests/nextflow.config @@ -0,0 +1,9 @@ +process { + + resourceLimits = [ + cpus: 4, + memory: '8.GB', + time: '2.h', + ] + +} diff --git a/modules/local/baysor/preview/main.nf b/modules/local/baysor/preview/main.nf new file mode 100644 index 00000000..b47bf43c --- /dev/null +++ b/modules/local/baysor/preview/main.nf @@ -0,0 +1,50 @@ +process BAYSOR_PREVIEW { + tag "${meta.id}" + label 'process_medium' + + container "khersameesh24/baysor:0.7.1" + + input: + tuple val(meta), path(transcripts), path(config) + + output: + tuple val(meta), path("${prefix}/preview.html"), emit: preview_html + tuple val("${task.process}"), val('baysor'), eval("baysor --version 2>&1 | grep -oP '\\d+\\.\\d+\\.\\d+' || echo unknown"), topic: versions, emit: versions_baysor + + when: + task.ext.when == null || task.ext.when + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("BAYSOR_PREVIEW module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + + """ + export JULIA_NUM_THREADS=${task.cpus} + + mkdir -p ${prefix} + + baysor preview \\ + ${transcripts} \\ + --config ${config} \\ + --output ${prefix}/preview.html + ${args} + """ + + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("BAYSOR_PREVIEW module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p ${prefix} + touch ${prefix}/preview.html + """ +} diff --git a/modules/local/baysor/preview/meta.yml b/modules/local/baysor/preview/meta.yml new file mode 100644 index 00000000..f23914e7 --- /dev/null +++ b/modules/local/baysor/preview/meta.yml @@ -0,0 +1,72 @@ +name: "baysor_preview" +description: Preview run for visualization of data. +keywords: + - exploratory-data-analysis +tools: + - "baysor": + description: "Baysor is a tool that segments cells using spatial gene expression maps. Optionally, segmentation masks can be given as additional input." + homepage: "https://kharchenkolab.github.io/Baysor/dev/" + documentation: "https://kharchenkolab.github.io/Baysor/dev/" + tool_dev_url: "https://github.com/kharchenkolab/Baysor" + doi: "https://doi.org/10.1038/s41587-021-01044-w" + licence: ["MIT license"] + identifier: + +## baysor_preview requires a transcript map of the data and a configuration file with argument values +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - transcripts_csv: + type: file + description: CSV file + pattern: "*.csv" + + - config_toml: + type: file + description: TOML file with config arguments + pattern: "*.toml" + +## segmentation results +output: + preview_html: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "preview.html": + type: file + description: segmentation preview + pattern: "preview.html" + versions_baysor: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "baysor --version 2>&1 | grep -oP '\\d+\\.\\d+\\.\\d+' || echo unknown": + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - baysor: + type: string + description: The tool name + - "baysor --version 2>&1 | grep -oP '\\d+\\.\\d+\\.\\d+' || echo unknown": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@sebgoti8" + - "@khersameesh24" +maintainers: + - "@sebgoti8" + - "@khersameesh24" diff --git a/modules/local/baysor/preview/tests/main.nf.test b/modules/local/baysor/preview/tests/main.nf.test new file mode 100644 index 00000000..d3f522d7 --- /dev/null +++ b/modules/local/baysor/preview/tests/main.nf.test @@ -0,0 +1,60 @@ +nextflow_process { + + name "Test Process BAYSOR PREVIEW" + script "../main.nf" + process "BAYSOR_PREVIEW" + + tag "modules" + tag "modules_local" + tag "baysor" + tag "baysor/preview" + tag "preview" + + test("baysor preview - transcripts.parquet") { + + when { + process { + """ + input[0] = channel.of([ + [id: "test_run_baysor"], + file("https://raw.githubusercontent.com/khersameesh24/test-datasets/baysor/transcripts.parquet", checkIfExists: true), + file("https://raw.githubusercontent.com/khersameesh24/test-datasets/baysor/config/xenium.toml", checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert file(process.out.preview_html[0][1]).exists() }, + { assert file(process.out.preview_html[0][1]).name == "preview.html" }, + { assert snapshot(process.out.versions_baysor).match("versions") } + ) + } + } + + test("baysor preview stub") { + + options "-stub" + + when { + process { + """ + input[0] = channel.of([ + [id: "test_run_baysor"], + file("https://raw.githubusercontent.com/khersameesh24/test-datasets/baysor/transcripts.parquet", checkIfExists: true), + file("https://raw.githubusercontent.com/khersameesh24/test-datasets/baysor/config/xenium.toml", checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.versions_baysor).match("versions_stub") } + ) + } + } +} diff --git a/modules/local/baysor/preview/tests/main.nf.test.snap b/modules/local/baysor/preview/tests/main.nf.test.snap new file mode 100644 index 00000000..08ec9503 --- /dev/null +++ b/modules/local/baysor/preview/tests/main.nf.test.snap @@ -0,0 +1,34 @@ +{ + "versions_stub": { + "content": [ + [ + [ + "BAYSOR_PREVIEW", + "baysor", + "0.7.1" + ] + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-03-21T03:58:47.202659888" + }, + "versions": { + "content": [ + [ + [ + "BAYSOR_PREVIEW", + "baysor", + "0.7.1" + ] + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-03-21T03:58:35.006511807" + } +} \ No newline at end of file diff --git a/modules/local/baysor/preview/tests/nextflow.config b/modules/local/baysor/preview/tests/nextflow.config new file mode 100644 index 00000000..f8b3a30a --- /dev/null +++ b/modules/local/baysor/preview/tests/nextflow.config @@ -0,0 +1,9 @@ +process { + + resourceLimits = [ + cpus: 4, + memory: '8.GB', + time: '2.h', + ] + +} diff --git a/modules/local/baysor/run/main.nf b/modules/local/baysor/run/main.nf new file mode 100644 index 00000000..53cdfeaa --- /dev/null +++ b/modules/local/baysor/run/main.nf @@ -0,0 +1,67 @@ +process BAYSOR_RUN { + tag "${meta.id}" + label 'process_high' + + container "khersameesh24/baysor:0.7.1" + + input: + tuple val(meta), path(transcripts), path(prior_segmentation), path(config), val(scale) + + output: + tuple val(meta), path("${prefix}/segmentation.csv"), path("${prefix}/segmentation_polygons_2d.json"), emit: segmentation + tuple val("${task.process}"), val('baysor'), eval("baysor --version 2>&1 | grep -oP '\\d+\\.\\d+\\.\\d+' || echo unknown"), topic: versions, emit: versions_baysor + + when: + task.ext.when == null || task.ext.when + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("BAYSOR_RUN module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + def args = task.ext.args ?: '' + // Column-based prior (e.g. :cell_id) takes precedence over file-based prior + def prior_col = task.ext.prior_column ? ":${task.ext.prior_column}" : '' + def prior_seg = prior_col ?: (prior_segmentation ? prior_segmentation : '') + def confidence = task.ext.prior_confidence != null ? "--prior-segmentation-confidence=${task.ext.prior_confidence}" : '' + def scaling_factor = scale ? "--scale=${scale}" : '' + def config_arg = config ? "--config=${config}" : '' + prefix = task.ext.prefix ?: "${meta.id}" + + // Build command parts, filtering out empty strings + def cmd_parts = [ + "baysor run", + "${transcripts}", + prior_seg, + scaling_factor, + confidence, + "--output=\"${prefix}/segmentation.csv\"", + config_arg, + "--plot", + "--polygon-format=GeometryCollectionLegacy", + args + ].findAll { cmd -> cmd } + + """ + export JULIA_NUM_THREADS=${task.cpus} + + mkdir -p ${prefix} + + ${cmd_parts.join(' \\\n ')} + """ + + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("BAYSOR_RUN module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p ${prefix} + touch "${prefix}/segmentation.csv" + touch "${prefix}/segmentation_polygons_2d.json" + """ +} diff --git a/modules/local/baysor/run/meta.yml b/modules/local/baysor/run/meta.yml new file mode 100644 index 00000000..1fbdbbaa --- /dev/null +++ b/modules/local/baysor/run/meta.yml @@ -0,0 +1,106 @@ +name: "baysor_run" +description: Bayesian segmentation of spatial transcriptomics data. +keywords: + - segmentation + - spatial transcriptomics + - cell clustering + - imaging +tools: + - "baysor": + description: "Baysor is a tool that segments cells using spatial gene expression maps. Optionally, segmentation masks can be given as additional input." + homepage: "https://kharchenkolab.github.io/Baysor/dev/" + documentation: "https://kharchenkolab.github.io/Baysor/dev/" + tool_dev_url: "https://github.com/kharchenkolab/Baysor" + doi: "https://doi.org/10.1038/s41587-021-01044-w" + licence: ["MIT license"] + identifier: + +## Baysor requires a transcript map of the data and a configuration file with argument values +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - transcripts_csv: + type: file + description: CSV file + pattern: "*.csv" + + - config_toml: + type: file + description: TOML file with config arguments + pattern: "*.toml" + +## segmentation results +output: + - segmentation: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "*segmentation.csv": + type: file + description: results of segmentation + pattern: "segmentation.csv" + + - polygons: + type: file + description: | + File with outlines of segmentation + pattern: "*.json" + + - params: + type: file + description: | + File with full list of parameters used for the model + pattern: "segmentation_params.dump.toml" + + - log: + type: file + description: | + Output file with metadata of running the workflow + pattern: "segmentation_log.log" + + - loom: + type: file + description: | + Loom file with metadata + pattern: "segmentation_counts.loom" + + - stats: + type: file + description: | + Statistics of segmented cells + pattern: "segmentation_cell_stats.csv" + + - versions_baysor: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "baysor --version 2>&1 | grep -oP '\\d+\\.\\d+\\.\\d+' || echo unknown": + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "baysor --version 2>&1 | grep -oP '\\d+\\.\\d+\\.\\d+' || echo unknown": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@sebgoti8" + - "@khersameesh24" +maintainers: + - "@sebgoti8" + - "@khersameesh24" diff --git a/modules/local/baysor/run/tests/main.nf.test b/modules/local/baysor/run/tests/main.nf.test new file mode 100644 index 00000000..37cda127 --- /dev/null +++ b/modules/local/baysor/run/tests/main.nf.test @@ -0,0 +1,67 @@ +nextflow_process { + + name "Test Process BAYSOR RUN" + script "../main.nf" + process "BAYSOR_RUN" + + tag "modules" + tag "modules_local" + tag "baysor" + tag "baysor/run" + tag "segmentation" + tag "cell_segmentation" + + test("baysor run - transcripts.parquet") { + + when { + process { + """ + input[0] = channel.of([ + [id: "test_run_baysor"], + file("https://raw.githubusercontent.com/khersameesh24/test-datasets/baysor/transcripts.parquet", checkIfExists: true), + [], + file("https://raw.githubusercontent.com/khersameesh24/test-datasets/baysor/config/xenium.toml", checkIfExists: true), + 30 + ]) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert file(process.out.segmentation[0][1]).exists() }, + { assert file(process.out.segmentation[0][1]).name == "segmentation.csv" }, + { assert file(process.out.segmentation[0][2]).exists() }, + { assert file(process.out.segmentation[0][2]).name == "segmentation_polygons_2d.json" }, + { assert snapshot(process.out.versions_baysor).match("versions") } + ) + } + } + + test("baysor run stub") { + + options "-stub" + + when { + process { + """ + input[0] = channel.of([ + [id: "test_run_baysor"], + file("https://raw.githubusercontent.com/khersameesh24/test-datasets/baysor/transcripts.parquet", checkIfExists: true), + [], + file("https://raw.githubusercontent.com/khersameesh24/test-datasets/baysor/config/xenium.toml", checkIfExists: true), + 30 + ]) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.versions_baysor).match("versions_stub") } + ) + } + } +} diff --git a/modules/local/baysor/run/tests/main.nf.test.snap b/modules/local/baysor/run/tests/main.nf.test.snap new file mode 100644 index 00000000..73d72688 --- /dev/null +++ b/modules/local/baysor/run/tests/main.nf.test.snap @@ -0,0 +1,34 @@ +{ + "versions_stub": { + "content": [ + [ + [ + "BAYSOR_RUN", + "baysor", + "0.7.1" + ] + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-03-21T03:59:56.882483481" + }, + "versions": { + "content": [ + [ + [ + "BAYSOR_RUN", + "baysor", + "0.7.1" + ] + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-03-21T03:59:44.964721046" + } +} \ No newline at end of file diff --git a/modules/local/baysor/segfree/main.nf b/modules/local/baysor/segfree/main.nf new file mode 100644 index 00000000..eb41fa87 --- /dev/null +++ b/modules/local/baysor/segfree/main.nf @@ -0,0 +1,50 @@ +process BAYSOR_SEGFREE { + tag "${meta.id}" + label 'process_high' + + container "khersameesh24/baysor:0.7.1" + + input: + tuple val(meta), path(transcripts), path(config) + + output: + tuple val(meta), path("${prefix}/ncvs.loom"), emit: ncvs + tuple val("${task.process}"), val('baysor'), eval("baysor --version 2>&1 | grep -oP '\\d+\\.\\d+\\.\\d+' || echo unknown"), topic: versions, emit: versions_baysor + + when: + task.ext.when == null || task.ext.when + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("BAYSOR_SEGFREE module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + + """ + export JULIA_NUM_THREADS=${task.cpus} + + mkdir -p ${prefix} + + baysor segfree \\ + ${transcripts} \\ + --config ${config} \\ + --output=${prefix}/ncvs.loom \\ + ${args} + """ + + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("BAYSOR_SEGFREE module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p ${prefix} + touch "${prefix}/ncvs.loom" + """ +} diff --git a/modules/local/baysor/segfree/meta.yml b/modules/local/baysor/segfree/meta.yml new file mode 100644 index 00000000..e8ccdad2 --- /dev/null +++ b/modules/local/baysor/segfree/meta.yml @@ -0,0 +1,75 @@ +name: "baysor_segfree" +description: Extract neighborhood composition vectors (NVCs) from a dataset. +keywords: + - neighborhood + - baysor + - segmentation_free +tools: + - "baysor": + description: "Baysor is a tool that segments cells using spatial gene expression maps. Optionally, segmentation masks can be given as additional input." + homepage: "https://kharchenkolab.github.io/Baysor/dev/" + documentation: "https://kharchenkolab.github.io/Baysor/dev/" + tool_dev_url: "https://github.com/kharchenkolab/Baysor" + doi: "https://doi.org/10.1038/s41587-021-01044-w" + licence: ["MIT license"] + identifier: + +## baysor_segfree requires a transcript map of the data and a configuration file with argument values +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - transcripts_csv: + type: file + description: CSV file + pattern: "*.csv" + - config_toml: + type: file + description: TOML file with config arguments + pattern: "*.toml" + +## neighborhood composition vectors +output: + ncvs: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "${prefix}/ncvs.loom": + type: file + description: | + Segmenation file in loom format + pattern: "${prefix}/ncvs.loom" + + versions_baysor: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "baysor --version 2>&1 | grep -oP '\\d+\\.\\d+\\.\\d+' || echo unknown": + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "baysor --version 2>&1 | grep -oP '\\d+\\.\\d+\\.\\d+' || echo unknown": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@sebgoti8" + - "@khersameesh24" +maintainers: + - "@sebgoti8" + - "@khersameesh24" diff --git a/modules/local/baysor/segfree/tests/main.nf.test b/modules/local/baysor/segfree/tests/main.nf.test new file mode 100644 index 00000000..1fb7d2bc --- /dev/null +++ b/modules/local/baysor/segfree/tests/main.nf.test @@ -0,0 +1,60 @@ +nextflow_process { + + name "Test Process BAYSOR SEGFREE" + script "../main.nf" + process "BAYSOR_SEGFREE" + + tag "modules" + tag "modules_local" + tag "baysor" + tag "baysor/segfree" + tag "segmentation-free" + + test("baysor segfree - transcripts.parquet") { + + when { + process { + """ + input[0] = channel.of([ + [id: "test_run_baysor"], + file("https://raw.githubusercontent.com/khersameesh24/test-datasets/baysor/transcripts.parquet", checkIfExists: true), + file("https://raw.githubusercontent.com/khersameesh24/test-datasets/baysor/config/xenium.toml", checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert file(process.out.ncvs[0][1]).exists() }, + { assert file(process.out.ncvs[0][1]).name == "ncvs.loom" }, + { assert snapshot(process.out.versions_baysor).match("versions") } + ) + } + } + + test("baysor run stub") { + + options "-stub" + + when { + process { + """ + input[0] = channel.of([ + [id: "test_run_baysor"], + file("https://raw.githubusercontent.com/khersameesh24/test-datasets/baysor/transcripts.parquet", checkIfExists: true), + file("https://raw.githubusercontent.com/khersameesh24/test-datasets/baysor/config/xenium.toml", checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.versions_baysor).match("versions_stub") } + ) + } + } +} diff --git a/modules/local/baysor/segfree/tests/main.nf.test.snap b/modules/local/baysor/segfree/tests/main.nf.test.snap new file mode 100644 index 00000000..075e13ea --- /dev/null +++ b/modules/local/baysor/segfree/tests/main.nf.test.snap @@ -0,0 +1,34 @@ +{ + "versions_stub": { + "content": [ + [ + [ + "BAYSOR_SEGFREE", + "baysor", + "0.7.1" + ] + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-03-21T04:00:54.745153684" + }, + "versions": { + "content": [ + [ + [ + "BAYSOR_SEGFREE", + "baysor", + "0.7.1" + ] + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-03-21T04:00:42.542466144" + } +} \ No newline at end of file diff --git a/modules/local/ficture/model/main.nf b/modules/local/ficture/model/main.nf new file mode 100644 index 00000000..34c2dbc0 --- /dev/null +++ b/modules/local/ficture/model/main.nf @@ -0,0 +1,43 @@ +process FICTURE { + tag "$meta.id" + label 'process_high' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/08/08f94799a8abd47d274654c49ed5ae225811b8a64bc9788739f4c5d23fa08230/data' : + 'community.wave.seqera.io/library/pip_ficture:ad8a1265a51b53cf' }" + + input: + tuple val(meta), path(transcripts) + path(coordinate_minmax) + path(features) + + output: + tuple val(meta), path("results/**"), emit: results + tuple val("${task.process}"), val('ficture'), eval("pip show ficture | sed -n 's/^Version: //p'"), topic: versions, emit: versions_ficture + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def features_list = features ? "--in-feature ${features}": "" + + """ + ficture run_together \\ + --in-tsv ${transcripts} \\ + --in-minmax ${coordinate_minmax} \\ + ${features_list} \\ + --out-dir results \\ + --train-width 12,18 \\ + --n-factor 6,12 \\ + --n-jobs ${task.cpus} \\ + --plot-each-factor \\ + --all \\ + ${args} + """ + + stub: + """ + mkdir -p results/ + """ +} diff --git a/modules/local/ficture/model/meta.yml b/modules/local/ficture/model/meta.yml new file mode 100644 index 00000000..c957639e --- /dev/null +++ b/modules/local/ficture/model/meta.yml @@ -0,0 +1,72 @@ +name: ficture +description: FICTURE is a software tool that performs segmentation-free analysis of submicron-resolution analysis of spatial transcriptomics data. +keywords: + - spatial + - segmentation_free + - imaging + - ficture +tools: + - ficture: + description: | + FICTURE is a software tool that performs segmentation-free analysis of submicron-resolution + analysis of spatial transcriptomics data. This tool executes ficture itself. + homepage: "https://seqscope.github.io/ficture/" + documentation: "https://seqscope.github.io/ficture/" + tool_dev_url: "https://github.com/seqscope/ficture" + licence: + - "Attribution-NonCommercial 4.0 International" + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing run information + e.g. [id:'sample'] + - transcripts: + type: file + description: One file contains the molecular or pixel level information, the required columns are X, Y, gene, and Count. (There could be other columns in the file which would be ignored.) + - - coordinate_minmax: + type: file + description: We also prefer to keep a file listing the min and max of the coordinates (this is primarily for visualizing very big tissue region where we do not read all data at once but would want to know the image dimension). The unit of the coordinates is micrometer. + - - features: + type: file + description: Another file contains the (unique) names of genes that should be used in analysis. The required columns is just gene (including the header), the naming of genes should match the gene column in the transcript file. If your data contain negative control probes or if you would like to remove certain genes this is where you can specify. +output: + results: + - - meta: + type: map + description: | + Groovy Map containing run information + e.g. [id:'sample'] + - results: + type: file + description: Files containing the results of FICTURE + pattern: "${meta.id}/results/*" + versions_ficture: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "pip show ficture | sed -n 's/^Version: //p'": + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "pip show ficture | sed -n 's/^Version: //p'": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@khersameesh24" + - "@heylf" +maintainers: + - "@khersameesh24" + - "@heylf" diff --git a/modules/local/ficture/preprocess/main.nf b/modules/local/ficture/preprocess/main.nf new file mode 100644 index 00000000..30b103a3 --- /dev/null +++ b/modules/local/ficture/preprocess/main.nf @@ -0,0 +1,38 @@ +process FICTURE_PREPROCESS { + tag "$meta.id" + label 'process_high' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/08/08f94799a8abd47d274654c49ed5ae225811b8a64bc9788739f4c5d23fa08230/data' : + 'community.wave.seqera.io/library/pip_ficture:ad8a1265a51b53cf' }" + + input: + tuple val(meta), path(transcripts) + path(features) + + output: + tuple val(meta), path("*processed_transcripts.tsv.gz"), emit: transcripts + path("*coordinate_minmax.tsv") , emit: coordinate_minmax + path("*feature.clean.tsv.gz") , optional:true, emit: features + tuple val("${task.process}"), val('ficture'), eval("pip show ficture | sed -n 's/^Version: //p'"), topic: versions, emit: versions_ficture + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def features_arg = features ? "--features ${features}" : "" + + """ + ficture_preprocess.py \\ + --transcripts ${transcripts} \\ + ${features_arg} \\ + ${args} + """ + + stub: + """ + touch processed_transcripts.tsv.gz + touch coordinate_minmax.tsv + """ +} diff --git a/modules/local/ficture/preprocess/meta.yml b/modules/local/ficture/preprocess/meta.yml new file mode 100644 index 00000000..4147cc90 --- /dev/null +++ b/modules/local/ficture/preprocess/meta.yml @@ -0,0 +1,80 @@ +name: ficture_preprocessing +description: FICTURE is a software tool that performs segmentation-free analysis of submicron-resolution analysis of spatial transcriptomics data. +keywords: + - spatial + - segmentation_free + - imaging + - preprocessing + - ficture +tools: + - ficture: + description: | + FICTURE is a software tool that performs segmentation-free analysis of submicron-resolution + analysis of spatial transcriptomics data. This tool executes ficture itself. + homepage: "https://seqscope.github.io/ficture/" + documentation: "https://seqscope.github.io/ficture/" + tool_dev_url: "https://github.com/seqscope/ficture" + licence: + - "Attribution-NonCommercial 4.0 International" + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing run information + e.g. [id:'sample'] + - transcripts: + type: file + description: One file contains the molecular or pixel level information, the required columns are X, Y, gene, and Count. (There could be other columns in the file which would be ignored.) + - - features: + type: file + description: Another txt file that contains the (unique) names of genes that should be used in analysis. If your data contain negative control probes or if you would like to remove certain genes this is where you can specify. Just list the genes. One gene per line. No header. +output: + transcripts: + - - meta: + type: map + description: | + Groovy Map containing run information + e.g. [id:'sample'] + - "*processed_transcripts.tsv.gz": + type: file + description: Transcirpt file used for FICTURE + pattern: "processed_transcripts.tsv.gz" + coordinate_minmax: + - "*coordinate_minmax.tsv": + type: file + description: Listing the min and max of the coordinates used for FICTURE + pattern: "coordinate_minmax.tsv" + features: + - "*feature.clean.tsv.gz": + type: file + description: Another file contains the (unique) names of genes that should be used for FICUTRE + pattern: "feature.clean.tsv.gz" + versions_ficture: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "pip show ficture | sed -n 's/^Version: //p'": + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "pip show ficture | sed -n 's/^Version: //p'": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@khersameesh24" + - "@heylf" +maintainers: + - "@khersameesh24" + - "@heylf" diff --git a/modules/local/parquet_to_csv/main.nf b/modules/local/parquet_to_csv/main.nf new file mode 100644 index 00000000..26c613e3 --- /dev/null +++ b/modules/local/parquet_to_csv/main.nf @@ -0,0 +1,33 @@ +process PARQUET_TO_CSV { + tag "$meta.id" + label 'process_low' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/f9/f9c8f3a2de4e2aa94500011f7d7d09276e9b6f2d79ee8737c9098fe22d4649bc/data' : + 'community.wave.seqera.io/library/sopa_procps-ng_pyarrow:c9ce8cd2ede79d72' }" + + input: + tuple val(meta), path(parquet) + + output: + tuple val(meta), path("transcripts.csv"), emit: csv + tuple val("${task.process}"), val('pyarrow'), eval("python3 -c 'import pyarrow; print(pyarrow.__version__)'"), topic: versions, emit: versions_pyarrow + + when: + task.ext.when == null || task.ext.when + + script: + """ + python3 -c " +import pyarrow.parquet as pq +import pyarrow.csv as pa_csv +t = pq.read_table('${parquet}') +pa_csv.write_csv(t, 'transcripts.csv') +" + """ + + stub: + """ + touch transcripts.csv + """ +} diff --git a/modules/local/parquet_to_csv/meta.yml b/modules/local/parquet_to_csv/meta.yml new file mode 100644 index 00000000..c3ab2506 --- /dev/null +++ b/modules/local/parquet_to_csv/meta.yml @@ -0,0 +1,70 @@ +name: "parquet_to_csv" +description: Convert parquet file to CSV for tools with old Parquet readers. +keywords: + - xenium + - parquet + - csv + - transcripts +tools: + - "pyarrow": + description: | + Python library providing a Pythonic API for Apache Arrow, + including fast Parquet and CSV I/O. + homepage: "https://arrow.apache.org/docs/python/" + documentation: "https://arrow.apache.org/docs/python/" + tool_dev_url: "https://github.com/apache/arrow" + doi: "no DOI available" + licence: ["Apache-2.0"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - parquet: + type: file + description: Parquet file to convert. + pattern: "*.parquet" + ontologies: [] + +output: + csv: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "transcripts.csv": + type: file + description: Converted CSV file. + pattern: "transcripts.csv" + ontologies: [] + versions_pyarrow: + - - ${task.process}: + type: string + description: The process the versions were collected from + - pyarrow: + type: string + description: The tool name + - "python3 -c 'import pyarrow; print(pyarrow.__version__)'": + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - pyarrow: + type: string + description: The tool name + - "python3 -c 'import pyarrow; print(pyarrow.__version__)'": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@an-altosian" +maintainers: + - "@an-altosian" diff --git a/modules/local/proseg/preset/main.nf b/modules/local/proseg/preset/main.nf new file mode 100644 index 00000000..553801ba --- /dev/null +++ b/modules/local/proseg/preset/main.nf @@ -0,0 +1,65 @@ +process PROSEG { + tag "${meta.id}" + label 'process_high' + + container "ghcr.io/dcjones/proseg:v3.1.0" + + input: + tuple val(meta), path(transcripts) + + output: + tuple val(meta), path("${prefix}/cell-polygons.geojson.gz"), path("${prefix}/transcript-metadata.csv.gz"), emit: seg_outs + tuple val(meta), path("${prefix}/proseg-output.zarr"), emit: zarr + tuple val("${task.process}"), val('proseg'), eval("proseg --version | sed 's/proseg //'"), topic: versions, emit: versions_proseg + + when: + task.ext.when == null || task.ext.when + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("PROSEG module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p ${prefix} + + proseg \\ + ${args} \\ + ${transcripts} \\ + --nthreads ${task.cpus} \\ + --output-expected-counts ${prefix}/expected-counts.csv.gz \\ + --output-cell-metadata ${prefix}/cell-metadata.csv.gz \\ + --output-transcript-metadata ${prefix}/transcript-metadata.csv.gz \\ + --output-gene-metadata ${prefix}/gene-metadata.csv.gz \\ + --output-rates ${prefix}/rates.csv.gz \\ + --output-cell-polygons ${prefix}/cell-polygons.geojson.gz \\ + --output-cell-polygon-layers ${prefix}/cell-polygons-layers.geojson.gz \\ + --output-union-cell-polygons ${prefix}/union-cell-polygons.geojson.gz \\ + --output-spatialdata ${prefix}/proseg-output.zarr + """ + + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("PROSEG module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p ${prefix}/ + touch "${prefix}/expected-counts.csv.gz" + touch "${prefix}/cell-metadata.csv.gz" + touch "${prefix}/transcript-metadata.csv.gz" + touch "${prefix}/gene-metadata.csv.gz" + touch "${prefix}/rates.csv.gz" + touch "${prefix}/cell-polygons.geojson.gz" + touch "${prefix}/cell-polygons-layers.geojson.gz" + touch "${prefix}/union-cell-polygons.geojson.gz" + mkdir -p "${prefix}/proseg-output.zarr" + """ +} diff --git a/modules/local/proseg/preset/meta.yml b/modules/local/proseg/preset/meta.yml new file mode 100644 index 00000000..524b5ca0 --- /dev/null +++ b/modules/local/proseg/preset/meta.yml @@ -0,0 +1,76 @@ +name: "proseg" +description: Probabilistic cell segmentation for in situ spatial transcriptomics +keywords: + - segmentation + - cell segmentation + - spatialomics + - probabilistic segmentation + - in situ spatial transcriptomics +tools: + - "proseg": + description: "Proseg (probabilistic segmentation) is a cell segmentation method for in situ spatial transcriptomics. Xenium, CosMx, and MERSCOPE platforms are currently supported." + homepage: "https://github.com/dcjones/proseg/tree/main" + documentation: "https://github.com/dcjones/proseg/blob/main/README.md" + tool_dev_url: "https://github.com/dcjones/proseg" + doi: "" + licence: ["GNU Public License"] + +input: + - - meta: + type: map + description: | + Groovy Map containing run information + e.g. `[ id:'run_id']` + - transcripts: + type: file + description: | + File containing the transcript position + pattern: "transcripts.csv.gz" + +output: + - - meta: + type: map + description: | + Groovy Map containing run information + e.g. `[ id:'run_id']` + - cell_polygons: + type: file + description: 2D polygons for each cell in GeoJSON format. These are flattened from 3D + pattern: "cell-polygons.geojson.gz" + - - expected_counts: + type: file + description: cell-by-gene count matrix + pattern: "expected-counts.csv.gz" + - - cell_metadata: + type: file + description: Cell centroids, volume, and other information + pattern: "cell-metadata.csv.gz" + - - transcript_metadata: + type: file + description: Transcript ids, genes, revised positions, assignment probability + pattern: "transcript-metadata.csv.gz" + - - gene_metadata: + type: file + description: Per-gene summary statistics + pattern: "gene-metadata.csv.gz" + - - rates: + type: file + description: Cell-by-gene Poisson rate parameters + pattern: "rates.csv.gz" + - - cell_polygon_layers: + type: file + description: A separate, non-overlapping cell polygon for each z-layer, preserving 3D segmentation + pattern: "cell-polygons-layers.geojson.gz" + - - cell_hulls: + type: file + description: Convex hulls around assigned transcripts + pattern: "cell-hulls.geojson.gz" + - - versions: + type: file + description: File containing software versions + pattern: "versions.yml" + +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/modules/local/proseg/preset/tests/main.nf.test b/modules/local/proseg/preset/tests/main.nf.test new file mode 100644 index 00000000..baa48c10 --- /dev/null +++ b/modules/local/proseg/preset/tests/main.nf.test @@ -0,0 +1,61 @@ +nextflow_process { + + name "Test Process PROSEG" + script "../main.nf" + process "PROSEG" + + tag "modules" + tag "modules_nfcore" + tag "proseg" + tag "proseg/proseg" + tag "segmentation" + tag "cell_segmentation" + + test("proseg - transcripts.csv") { + + when { + process { + """ + input[0] = [ + [id: "test_run_proseg"], + file(params.modules_testdata_base_path + "spatial_omics/xenium/homo_sapiens/spatial_gene_expression.csv", checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert file(process.out.seg_outs[0][1]).exists() }, + { assert file(process.out.seg_outs[0][1]).name == "cell-polygons.geojson.gz" }, + { assert file(process.out.seg_outs[0][2]).exists() }, + { assert file(process.out.seg_outs[0][2]).name == "transcript-metadata.csv.gz" }, + { assert snapshot(process.out.versions_proseg).match("versions") } + ) + } + } + + test("proseg stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [id: "test_run_proseg"], + file(params.modules_testdata_base_path + "spatial_omics/xenium/homo_sapiens/spatial_gene_expression.csv", checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.versions_proseg).match("versions_stub") } + ) + } + } +} diff --git a/modules/local/proseg/preset/tests/main.nf.test.snap b/modules/local/proseg/preset/tests/main.nf.test.snap new file mode 100644 index 00000000..944325aa --- /dev/null +++ b/modules/local/proseg/preset/tests/main.nf.test.snap @@ -0,0 +1,34 @@ +{ + "versions_stub": { + "content": [ + [ + [ + "PROSEG", + "proseg", + "3.1.0" + ] + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-03-21T04:01:19.610456233" + }, + "versions": { + "content": [ + [ + [ + "PROSEG", + "proseg", + "3.1.0" + ] + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-03-21T04:01:12.114004958" + } +} \ No newline at end of file diff --git a/modules/local/proseg/preset/tests/nextflow.config b/modules/local/proseg/preset/tests/nextflow.config new file mode 100644 index 00000000..f8b3a30a --- /dev/null +++ b/modules/local/proseg/preset/tests/nextflow.config @@ -0,0 +1,9 @@ +process { + + resourceLimits = [ + cpus: 4, + memory: '8.GB', + time: '2.h', + ] + +} diff --git a/modules/local/proseg/proseg2baysor/main.nf b/modules/local/proseg/proseg2baysor/main.nf new file mode 100644 index 00000000..1a0c8b38 --- /dev/null +++ b/modules/local/proseg/proseg2baysor/main.nf @@ -0,0 +1,47 @@ +process PROSEG2BAYSOR { + tag "$meta.id" + label 'process_high' + + container "ghcr.io/dcjones/proseg:v3.1.0" + + input: + tuple val(meta), path(zarr_dir) + + output: + tuple val(meta), path("${prefix}/cell-polygons.geojson") , emit: xr_polygons + tuple val(meta), path("${prefix}/transcript-metadata.csv"), emit: xr_metadata + tuple val("${task.process}"), val('proseg'), eval("proseg --version | sed 's/proseg //'"), topic: versions, emit: versions_proseg + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "PROSEG2BAYSOR module does not support Conda. Please use Docker / Singularity / Podman instead." + } + + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p ${prefix} + + proseg-to-baysor \\ + ${zarr_dir} \\ + --output-transcript-metadata ${prefix}/transcript-metadata.csv \\ + --output-cell-polygons ${prefix}/cell-polygons.geojson \\ + ${args} + """ + + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "PROSEG2BAYSOR module does not support Conda. Please use Docker / Singularity / Podman instead." + } + + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p ${prefix} + touch "${prefix}/transcript-metadata.csv" + touch "${prefix}/cell-polygons.geojson" + """ +} diff --git a/modules/local/proseg/proseg2baysor/meta.yml b/modules/local/proseg/proseg2baysor/meta.yml new file mode 100644 index 00000000..3d5cfac4 --- /dev/null +++ b/modules/local/proseg/proseg2baysor/meta.yml @@ -0,0 +1,80 @@ +name: "proseg2baysor" +description: Probabilistic cell segmentation for in situ spatial transcriptomics +keywords: + - segmentation + - cell segmentation + - spatialomics + - probabilistic segmentation + - in situ spatial transcriptomics +tools: + - "proseg": + description: "Proseg (probabilistic segmentation) is a cell segmentation method for in situ spatial transcriptomics. Xenium, CosMx, and MERSCOPE platforms are currently supported." + homepage: "https://github.com/dcjones/proseg/tree/main" + documentation: "https://github.com/dcjones/proseg/blob/main/README.md" + tool_dev_url: "https://github.com/dcjones/proseg" + doi: "" + licence: ["GNU Public License"] + +input: + - - meta: + type: map + description: | + Groovy Map containing run information + e.g. `[ id:'run_id']` + - cell_polygons: + type: file + description: | + Cell polygons output file from the proseg xenium (format) run + pattern: "cell-polygons.geojson.gz" + - - transcript_metadata: + type: file + description: | + Transcript metadata file output file from the proseg xenium (format) run + pattern: "transcript-metadata.csv.gz" +output: + xr_polygons: + - - meta: + type: map + description: | + Groovy Map containing run information + e.g. `[ id:'run_id']` + - "${prefix}/cell-polygons.geojson": + type: file + description: 2D polygons for each cell in GeoJSON format. These are flattened from 3D + pattern: "xr-cell-polygons.geojson" + xr_metadata: + - - meta: + type: map + description: | + Groovy Map containing run information + e.g. `[ id:'run_id']` + - "${prefix}/transcript-metadata.csv": + type: file + description: Transcript ids, genes, revised positions, assignment probability + pattern: "xr-transcript-metadata.csv" + versions_proseg: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "proseg --version | sed 's/proseg //'": + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "proseg --version | sed 's/proseg //'": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/modules/local/proseg/proseg2baysor/tests/main.nf.test b/modules/local/proseg/proseg2baysor/tests/main.nf.test new file mode 100644 index 00000000..039217e0 --- /dev/null +++ b/modules/local/proseg/proseg2baysor/tests/main.nf.test @@ -0,0 +1,72 @@ +nextflow_process { + + name "Test Process PROSEG" + script "../main.nf" + process "PROSEG2BAYSOR" + + tag "modules" + tag "modules_nfcore" + tag "proseg" + tag "segmentation" + tag "cell_segmentation" + + + setup { + run("PROSEG") { + script "modules/local/proseg/preset/main.nf" + process { + """ + input[0] = [ + [id: "test_run_proseg"], + file(params.modules_testdata_base_path + "spatial_omics/xenium/homo_sapiens/spatial_gene_expression.csv", checkIfExists: true) + ] + """ + } + } + } + + test("proseg2baysor - cell_polygons, transcript_metadata") { + + when { + process { + """ + input[0] = channel.of([ + [id: "test_run_proseg2baysor"], + ]).combine(PROSEG.out.seg_outs, by: 0) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + + test("proseg2baysor stub") { + + options "-stub" + + when { + process { + """ + input[0] = channel.of([ + [id: "test_run_proseg2baysor"], + ]).combine(PROSEG.out.seg_outs, by: 0) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/local/proseg/proseg2baysor/tests/main.nf.test.snap b/modules/local/proseg/proseg2baysor/tests/main.nf.test.snap new file mode 100644 index 00000000..7dff8302 --- /dev/null +++ b/modules/local/proseg/proseg2baysor/tests/main.nf.test.snap @@ -0,0 +1,60 @@ +{ + "proseg2baysor - cell_polygons, transcript_metadata": { + "content": [ + { + "0": [ + + ], + "1": [ + + ], + "2": [ + + ], + "versions_proseg": [ + + ], + "xr_metadata": [ + + ], + "xr_polygons": [ + + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-03-21T03:50:54.118409704" + }, + "proseg2baysor stub": { + "content": [ + { + "0": [ + + ], + "1": [ + + ], + "2": [ + + ], + "versions_proseg": [ + + ], + "xr_metadata": [ + + ], + "xr_polygons": [ + + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-03-21T03:51:01.546798675" + } +} \ No newline at end of file diff --git a/modules/local/proseg/proseg2baysor/tests/nextflow.config b/modules/local/proseg/proseg2baysor/tests/nextflow.config new file mode 100644 index 00000000..f8b3a30a --- /dev/null +++ b/modules/local/proseg/proseg2baysor/tests/nextflow.config @@ -0,0 +1,9 @@ +process { + + resourceLimits = [ + cpus: 4, + memory: '8.GB', + time: '2.h', + ] + +} diff --git a/modules/local/resolift/main.nf b/modules/local/resolift/main.nf new file mode 100644 index 00000000..f5f22d09 --- /dev/null +++ b/modules/local/resolift/main.nf @@ -0,0 +1,46 @@ +process RESOLIFT { + tag "${meta.id}" + label 'process_low' + + container "khersameesh24/resolift:1.0.0" + + input: + tuple val(meta), path(morphology_tiff) + + output: + tuple val(meta), path("${prefix}/morphology.ome.enhanced.tiff"), emit: enhanced_tiff + tuple val("${task.process}"), val('resolift'), eval("pip show resolift | sed -n 's/^Version: //p'"), topic: versions, emit: versions_resolift + + when: + task.ext.when == null || task.ext.when + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("RESOLIFT module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p ${prefix} + + resolift \\ + -i ${morphology_tiff} \\ + -o ${prefix}/morphology.ome.enhanced.tiff \\ + ${args} + """ + + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("RESOLIFT module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p ${prefix} + touch "${prefix}/morphology.ome.enhanced.tiff" + """ +} diff --git a/modules/local/resolift/meta.yml b/modules/local/resolift/meta.yml new file mode 100644 index 00000000..c6f3fbb0 --- /dev/null +++ b/modules/local/resolift/meta.yml @@ -0,0 +1,64 @@ +name: "resolift" +description: Upscale large TIFFs using chunk-based image processing and sharpening. +keywords: + - segmentation + - spatialomics + - image based segmentation +tools: + - "resolift": + description: "Upscale large TIFFs using chunk-based image processing and sharpening." + homepage: "https://github.com/khersameesh24/ResoLift" + documentation: "https://github.com/khersameesh24/ResoLift" + tool_dev_url: "https://github.com/khersameesh24/ResoLift" + doi: "" + licence: ["GNU Public License"] + +input: + - - meta: + type: map + description: | + Groovy Map containing run information + e.g. `[ id:'run_id']` + - input: + type: file + description: | + Path to the input TIFF file. + pattern: ".tiff" + +output: + enhanced_tiff: + - - meta: + type: map + description: | + Groovy Map containing run information + e.g. `[ id:'run_id']` + - "${prefix}/morphology.ome.enhanced.tiff": + type: file + description: Path to save the upscaled TIFF file. + pattern: ".tiff" + versions_resolift: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "pip show resolift | sed -n 's/^Version: //p'": + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "pip show resolift | sed -n 's/^Version: //p'": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@kubranarci" +maintainers: + - "@kubranarci" diff --git a/modules/local/resolift/tests/main.nf.test b/modules/local/resolift/tests/main.nf.test new file mode 100644 index 00000000..7130d733 --- /dev/null +++ b/modules/local/resolift/tests/main.nf.test @@ -0,0 +1,60 @@ +nextflow_process { + + name "Test Process RESOLIFT" + script "../main.nf" + process "RESOLIFT" + config "./nextflow.config" + + tag "modules" + tag "modules_local" + tag "resolift" + tag "segmentation" + tag "cell_segmentation" + + test("resolift tif") { + + when { + process { + """ + input[0] = [ + [ id:'test_run' ], + file(params.modules_testdata_base_path + 'imaging/segmentation/nuclear_image.tif', checkIfExists: true), + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() }, + ) + } + + } + + test("resolift stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test_run' ], + file(params.modules_testdata_base_path + 'imaging/segmentation/nuclear_image.tif', checkIfExists: true), + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/local/resolift/tests/main.nf.test.snap b/modules/local/resolift/tests/main.nf.test.snap new file mode 100644 index 00000000..8972d043 --- /dev/null +++ b/modules/local/resolift/tests/main.nf.test.snap @@ -0,0 +1,84 @@ +{ + "resolift stub": { + "content": [ + { + "0": [ + [ + { + "id": "test_run" + }, + "morphology.ome.enhanced.tiff:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + "RESOLIFT", + "resolift", + "1.0" + ] + ], + "enhanced_tiff": [ + [ + { + "id": "test_run" + }, + "morphology.ome.enhanced.tiff:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_resolift": [ + [ + "RESOLIFT", + "resolift", + "1.0" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-03-22T16:00:00.000000000" + }, + "resolift tif": { + "content": [ + { + "0": [ + [ + { + "id": "test_run" + }, + "morphology.ome.enhanced.tiff:md5,a9feec67b317d8e5c5c93bc45d5a8763" + ] + ], + "1": [ + [ + "RESOLIFT", + "resolift", + "1.0" + ] + ], + "enhanced_tiff": [ + [ + { + "id": "test_run" + }, + "morphology.ome.enhanced.tiff:md5,a9feec67b317d8e5c5c93bc45d5a8763" + ] + ], + "versions_resolift": [ + [ + "RESOLIFT", + "resolift", + "1.0" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-03-22T16:00:00.000000000" + } +} \ No newline at end of file diff --git a/modules/local/resolift/tests/nextflow.config b/modules/local/resolift/tests/nextflow.config new file mode 100644 index 00000000..f8b3a30a --- /dev/null +++ b/modules/local/resolift/tests/nextflow.config @@ -0,0 +1,9 @@ +process { + + resourceLimits = [ + cpus: 4, + memory: '8.GB', + time: '2.h', + ] + +} diff --git a/modules/local/segger/Dockerfile b/modules/local/segger/Dockerfile new file mode 100644 index 00000000..e907e350 --- /dev/null +++ b/modules/local/segger/Dockerfile @@ -0,0 +1,52 @@ +# GPU image for segger segmentation +# Base: PyTorch with CUDA 12.4 (consistent CUDA support) +FROM pytorch/pytorch:2.5.1-cuda12.4-cudnn9-runtime + +# Install system dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + git \ + procps \ + build-essential \ + && rm -rf /var/lib/apt/lists/* + +# Upgrade pip +RUN pip install --no-cache-dir --upgrade pip + +# Clone and install segger from fork with CUDA 12 support +# Using fork with fixed API (is_token_based, num_node_features) and data module exports +WORKDIR /workspace +RUN git clone https://github.com/an-altosian/segger_dev.git /workspace/segger_dev && \ + cd /workspace/segger_dev && \ + pip install --no-cache-dir -e ".[cuda12]" + +# Install missing dependencies for data.io module and training +RUN pip install --no-cache-dir dask-geopandas lightning pytorch_lightning + +# Fix squidpy/anndata compatibility - upgrade squidpy to version compatible with anndata 0.10+ +RUN pip install --no-cache-dir "squidpy>=1.4.0" + +# Make NVRTC discoverable for CuPy (needs libnvrtc.so.12 for JIT kernel compilation) +# The nvidia-cuda-nvrtc-cu12 pip package installs it under site-packages. +# Use both ldconfig AND symlinks for maximum reliability (ldconfig may not survive +# Wave container augmentation on Seqera Platform). +RUN NVRTC_LIB=$(python -c "import nvidia.cuda_nvrtc, pathlib; print(pathlib.Path(nvidia.cuda_nvrtc.__file__).parent / 'lib')") && \ + echo "$NVRTC_LIB" > /etc/ld.so.conf.d/nvidia-nvrtc.conf && \ + ln -sf "$NVRTC_LIB"/libnvrtc* /usr/lib/ && \ + ldconfig && \ + echo "NVRTC registered at: $NVRTC_LIB" && \ + ldconfig -p | grep nvrtc + +# Install faiss-cpu for vector search (faiss-gpu not available via pip) +# Note: Removed cuvs-cu12 and nvidia library force-reinstalls as they break PyTorch CUDA detection +RUN pip install --no-cache-dir faiss-cpu + +# Verify PyTorch was built with CUDA support (doesn't require GPU at build time) +RUN python -c "import torch; print(f'PyTorch version: {torch.__version__}'); print(f'CUDA compiled: {torch.version.cuda}')" + +# Set environment variables for segger +ENV PYTHONPATH=/workspace/segger_dev/src:$PYTHONPATH +ENV PATH="$PATH:/workspace/segger_dev/src/segger/cli/" +ENV CUPY_CACHE_DIR="/tmp/cupy_cache" + +# Set default shell +CMD ["/bin/bash"] diff --git a/modules/local/segger/create_dataset/main.nf b/modules/local/segger/create_dataset/main.nf new file mode 100644 index 00000000..d7a31c11 --- /dev/null +++ b/modules/local/segger/create_dataset/main.nf @@ -0,0 +1,49 @@ +process SEGGER_CREATE_DATASET { + tag "${meta.id}" + label 'process_xl' + + container "quay.io/dongzehe/segger:1.0.14" + + input: + tuple val(meta), path(base_dir) + + output: + tuple val(meta), path("${prefix}/"), emit: datasetdir + tuple val("${task.process}"), val('segger'), eval("pip show segger | sed -n 's/^Version: //p'"), topic: versions, emit: versions_segger + + when: + task.ext.when == null || task.ext.when + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("SEGGER_CREATE_DATASET module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + + """ + export NUMBA_CACHE_DIR=\$PWD/.numba_cache + mkdir -p \$NUMBA_CACHE_DIR + + segger_create_dataset.py \\ + --bundle-dir ${base_dir} \\ + --output-dir ${prefix} \\ + --n-workers ${task.cpus} \\ + ${args} + """ + + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("SEGGER_CREATE_DATASET module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p ${prefix}/ + touch "${prefix}/fake_file.txt" + """ +} diff --git a/modules/local/segger/create_dataset/meta.yml b/modules/local/segger/create_dataset/meta.yml new file mode 100644 index 00000000..1f948299 --- /dev/null +++ b/modules/local/segger/create_dataset/meta.yml @@ -0,0 +1,63 @@ +name: "segger_create_dataset" +description: Create a segger compatible dataset to speed up training and prediction of cell segmentations. +keywords: + - segmentation + - xenium + - imaging +tools: + - "segger": + description: "Segger uses graph neural networks and heterogeneous graphs to offer efficient cell segmentation at unmatched precision and accuracy." + homepage: "https://github.com/EliHei2/segger_dev" + documentation: "https://elihei2.github.io/segger_dev/user_guide/" + tool_dev_url: "https://github.com/EliHei2/segger_dev" + doi: "tbd" + licence: ["MIT"] + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'segger_run_id' ]` + - base_dir: + type: file + description: | + Directory containing the raw dataset (e.g., transcripts, boundaries). +output: + datasetdir: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'segger_run_id' ]` + - "${prefix}/": + description: | + Directory to save the processed Segger dataset (in PyTorch Geometric format). + versions_segger: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "pip show segger | sed -n 's/^Version: //p'": + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "pip show segger | sed -n 's/^Version: //p'": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@tobiaspk" + - "@khersameesh24" +maintainers: + - "@tobiaspk" + - "@khersameesh24" diff --git a/modules/local/segger/predict/main.nf b/modules/local/segger/predict/main.nf new file mode 100644 index 00000000..f7be984e --- /dev/null +++ b/modules/local/segger/predict/main.nf @@ -0,0 +1,51 @@ +process SEGGER_PREDICT { + tag "${meta.id}" + label 'process_xl' + label 'process_gpu' + + container "quay.io/dongzehe/segger:1.0.14" + + input: + tuple val(meta), path(segger_dataset) + path models_dir + path transcripts + + output: + tuple val(meta), path("benchmarks_dir"), emit: benchmarks + tuple val(meta), path("benchmarks_dir/*/segger_transcripts.parquet"), emit: transcripts + tuple val("${task.process}"), val('segger'), eval("pip show segger | sed -n 's/^Version: //p'"), topic: versions, emit: versions_segger + + when: + task.ext.when == null || task.ext.when + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("SEGGER_PREDICT module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + """ + segger_predict.py \\ + --models-dir ${models_dir} \\ + --segger-data-dir ${segger_dataset} \\ + --transcripts-file ${transcripts} \\ + --benchmarks-dir benchmarks_dir \\ + --num-workers ${task.cpus} \\ + ${args} + """ + + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("SEGGER_PREDICT module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p "benchmarks_dir" + touch "benchmarks_dir/fake_file.txt" + """ +} diff --git a/modules/local/segger/predict/meta.yml b/modules/local/segger/predict/meta.yml new file mode 100644 index 00000000..777f768a --- /dev/null +++ b/modules/local/segger/predict/meta.yml @@ -0,0 +1,86 @@ +name: "segger_predict" +description: Predict cell segmentation on new data using a trained Segger model. +keywords: + - segmentation + - xenium + - imaging + - prediction +tools: + - "segger": + description: "Segger uses graph neural networks and heterogeneous graphs to offer efficient cell segmentation at unmatched precision and accuracy." + homepage: "https://github.com/EliHei2/segger_dev" + documentation: "https://elihei2.github.io/segger_dev/user_guide/" + tool_dev_url: "https://github.com/EliHei2/segger_dev" + doi: "tbd" + licence: ["MIT"] + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'segger_run_id' ]` + - segger_dataset: + type: file + description: | + File or directory with input data to run predictions on. + - - models_dir: + type: directory + description: | + Directory containing the trained model for inference. + - - transcripts_file: + type: file + description: | + Path to the transcripts.parquet file. + - - benchmarks_dir: + type: directory + description: | + Directory to save the prediction outputs. +output: + benchmarks: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'segger_run_id' ]` + - "benchmarks_dir": + description: | + Directory to save the segmentation results, including cell boundaries and associations. + transcripts: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'segger_run_id' ]` + - "benchmarks_dir/*/segger_transcripts.parquet": + type: file + description: | + Path to the transcripts.parquet file. + versions_segger: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "pip show segger | sed -n 's/^Version: //p'": + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "pip show segger | sed -n 's/^Version: //p'": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@tobiaspk" + - "@khersameesh24" +maintainers: + - "@tobiaspk" + - "@khersameesh24" diff --git a/modules/local/segger/train/main.nf b/modules/local/segger/train/main.nf new file mode 100644 index 00000000..c441d186 --- /dev/null +++ b/modules/local/segger/train/main.nf @@ -0,0 +1,66 @@ +process SEGGER_TRAIN { + tag "${meta.id}" + label 'process_xl' + label 'process_gpu' + + container "quay.io/dongzehe/segger:1.0.14" + + input: + tuple val(meta), path(dataset_dir) + + output: + tuple val(meta), path("trained_models"), emit: trained_models + tuple val("${task.process}"), val('segger'), eval("pip show segger | sed -n 's/^Version: //p'"), topic: versions, emit: versions_segger + + when: + task.ext.when == null || task.ext.when + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("SEGGER_TRAIN module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + def args = task.ext.args ?: '' + def script_path = "/workspace/segger_dev/src/segger/cli/train_model.py" + prefix = task.ext.prefix ?: "${meta.id}" + def gpu_count = 2 * task.attempt + def cuda_visible = gpu_count == 1 ? "export CUDA_VISIBLE_DEVICES=0" : "" + def accelerator = task.accelerator ? 'gpu' : 'auto' + + """ + # Set numba cache directory to avoid caching issues in container + export NUMBA_CACHE_DIR=\$PWD/.numba_cache + mkdir -p \$NUMBA_CACHE_DIR + + # GPU detection logging + echo "=== GPU Detection (SEGGER_TRAIN) ===" + echo "Requested devices: ${gpu_count} (attempt ${task.attempt})" + echo "Accelerator: ${accelerator}" + nvidia-smi 2>/dev/null && echo "GPU available: yes" || echo "GPU available: no (nvidia-smi failed)" + python3 -c "import torch; print(f'PyTorch CUDA available: {torch.cuda.is_available()}'); print(f'CUDA device count: {torch.cuda.device_count()}')" 2>/dev/null || echo "PyTorch CUDA check failed" + echo "====================================" + + ${cuda_visible} + python3 ${script_path} \\ + --dataset_dir ${dataset_dir} \\ + --models_dir trained_models \\ + --sample_tag ${prefix} \\ + --devices ${gpu_count} \\ + --accelerator ${accelerator} \\ + ${args} + """ + + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("SEGGER_TRAIN module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p trained_models/ + touch trained_models/fakefile.txt + """ +} diff --git a/modules/local/segger/train/meta.yml b/modules/local/segger/train/meta.yml new file mode 100644 index 00000000..7127cdeb --- /dev/null +++ b/modules/local/segger/train/meta.yml @@ -0,0 +1,65 @@ +name: "segger_train_model" +description: Train a Segger cell segmentation model using a segger-created dataset. +keywords: + - segmentation + - xenium + - imaging + - model_training +tools: + - "segger": + description: "Segger uses graph neural networks and heterogeneous graphs to offer efficient cell segmentation at unmatched precision and accuracy." + homepage: "https://github.com/EliHei2/segger_dev" + documentation: "https://elihei2.github.io/segger_dev/user_guide/" + tool_dev_url: "https://github.com/EliHei2/segger_dev" + doi: "tbd" + licence: ["MIT"] + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'segger_run_id' ]` + - dataset_dir: + type: file + description: | + Directory to segger created dataset(s). +output: + trained_models: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'segger_run_id' ]` + - "trained_models": + type: directory + description: | + Directory to save the trained model and checkpoints. + versions_segger: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "pip show segger | sed -n 's/^Version: //p'": + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "pip show segger | sed -n 's/^Version: //p'": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@tobiaspk" + - "@khersameesh24" +maintainers: + - "@tobiaspk" + - "@khersameesh24" diff --git a/modules/local/spatialdata/merge/main.nf b/modules/local/spatialdata/merge/main.nf new file mode 100644 index 00000000..db614000 --- /dev/null +++ b/modules/local/spatialdata/merge/main.nf @@ -0,0 +1,48 @@ +process SPATIALDATA_MERGE { + tag "${meta.id}" + label 'process_high_memory' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/cb/cb8fc03fa657c164c5d83f075578bbb5d9c10f1178165f94e94f33c67efca1a1/data' : + 'community.wave.seqera.io/library/spatialdata-io_spatialdata:b264928c30680e87' }" + + input: + tuple val(meta), path(raw_bundle, stageAs: "*"), path(redefined_bundle, stageAs: "*") + val(outputfolder) + + output: + tuple val(meta), path("spatialdata/${prefix}/${outputfolder}"), emit: merged_bundle + tuple val("${task.process}"), val('spatialdata'), eval("pip show spatialdata | sed -n 's/^Version: //p'"), topic: versions, emit: versions_spatialdata + + when: + task.ext.when == null || task.ext.when + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("SPATIALDATA_MERGE module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + prefix = task.ext.prefix ?: "${meta.id}" + + """ + spatialdata_merge.py \\ + --raw-bundle ${raw_bundle} \\ + --redefined-bundle ${redefined_bundle} \\ + --prefix ${prefix} \\ + --output-folder ${outputfolder} + """ + + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("SPATIALDATA_MERGE module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p "spatialdata/${prefix}/${outputfolder}/" + touch "spatialdata/${prefix}/${outputfolder}/fake_file.txt" + """ +} diff --git a/modules/local/spatialdata/merge/meta.yml b/modules/local/spatialdata/merge/meta.yml new file mode 100644 index 00000000..248d5bc2 --- /dev/null +++ b/modules/local/spatialdata/merge/meta.yml @@ -0,0 +1,44 @@ +name: spatialdata_merge +description: Merge several SpatialData bundles together. +keywords: + - data + - spatialomics +tools: + - custom: + description: An open and universal framework for processing spatial omics data + homepage: https://github.com/scverse/spatialdata + documentation: https://spatialdata.scverse.org/en/latest/ + licence: ["BSD 3-Clause"] +input: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample' ] + - ref_bundle: + type: folder + description: | + Path to reference (raw) spatialdata bundle. + - add_bundle: + type: folder + description: | + Path to spatialdata bundle that should be added. + +output: + - spatialaxe_bundle: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample' ] + - "spatialdata_spatialaxe": + type: folder + description: Spatialdata folder + pattern: "${meta.id}/spatialdata_spatialaxe/*" + - versions: + type: file + description: File containing software versions + pattern: "versions.yml" + +authors: + - "@heylf" diff --git a/modules/local/spatialdata/meta/main.nf b/modules/local/spatialdata/meta/main.nf new file mode 100644 index 00000000..714bf797 --- /dev/null +++ b/modules/local/spatialdata/meta/main.nf @@ -0,0 +1,49 @@ +process SPATIALDATA_META { + tag "${meta.id}" + label 'process_high' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/cb/cb8fc03fa657c164c5d83f075578bbb5d9c10f1178165f94e94f33c67efca1a1/data' : + 'community.wave.seqera.io/library/spatialdata-io_spatialdata:b264928c30680e87' }" + + input: + tuple val(meta), path(spatialdata_bundle, stageAs: "*"), path(xenium_bundle, stageAs: "*") + val(outputfolder) + + output: + tuple val(meta), path("spatialdata/${prefix}/${outputfolder}"), emit: metadata + tuple val("${task.process}"), val('spatialdata'), eval("pip show spatialdata | sed -n 's/^Version: //p'"), topic: versions, emit: versions_spatialdata + + when: + task.ext.when == null || task.ext.when + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("SPATIALDATA_META module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + prefix = task.ext.prefix ?: "${meta.id}" + + """ + spatialdata_meta.py \\ + --spatialdata-bundle ${spatialdata_bundle} \\ + --xenium-bundle ${xenium_bundle} \\ + --prefix ${prefix} \\ + --metadata '${meta}' \\ + --output-folder ${outputfolder} + """ + + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("SPATIALDATA_META module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p "spatialdata/${prefix}/${outputfolder}/" + touch "spatialdata/${prefix}/${outputfolder}/fake_file.txt" + """ +} diff --git a/modules/local/spatialdata/meta/meta.yml b/modules/local/spatialdata/meta/meta.yml new file mode 100644 index 00000000..8450c932 --- /dev/null +++ b/modules/local/spatialdata/meta/meta.yml @@ -0,0 +1,44 @@ +name: spatialdata_meta +description: Add metadata to a SpatialData bundle. +keywords: + - data + - spatialomics +tools: + - custom: + description: An open and universal framework for processing spatial omics data + homepage: https://github.com/scverse/spatialdata + documentation: https://spatialdata.scverse.org/en/latest/ + licence: ["BSD 3-Clause"] +input: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample' ] + - spatialdata_bundle: + type: folder + description: | + Path to spatialdata bundle. + - xenium_bundle: + type: folder + description: | + Path to Xeniumranger bundle. + +output: + - spatialaxe_bundle: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample' ] + - "spatialdata_spatialaxe_final": + type: folder + description: Spatialdata folder + pattern: "${meta.id}/spatialdata_spatialaxe_final/*" + - versions: + type: file + description: File containing software versions + pattern: "versions.yml" + +authors: + - "@heylf" diff --git a/modules/local/spatialdata/write/main.nf b/modules/local/spatialdata/write/main.nf new file mode 100644 index 00000000..6caed6c1 --- /dev/null +++ b/modules/local/spatialdata/write/main.nf @@ -0,0 +1,53 @@ +process SPATIALDATA_WRITE { + tag "${meta.id}" + label 'process_high' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/cb/cb8fc03fa657c164c5d83f075578bbb5d9c10f1178165f94e94f33c67efca1a1/data' : + 'community.wave.seqera.io/library/spatialdata-io_spatialdata:b264928c30680e87' }" + + input: + tuple val(meta), path(bundle, stageAs: "*") + val(outputfolder) + val(segmented_object) + val(coordinate_space) + + output: + tuple val(meta), path("spatialdata/${prefix}/${outputfolder}"), emit: spatialdata + tuple val("${task.process}"), val('spatialdata'), eval("pip show spatialdata | sed -n 's/^Version: //p'"), topic: versions, emit: versions_spatialdata + + when: + task.ext.when == null || task.ext.when + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("SPATIALDATA_WRITE module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + + """ + spatialdata_write.py \\ + --bundle ${bundle} \\ + --prefix ${prefix} \\ + --output-folder ${outputfolder} \\ + --segmented-object ${segmented_object} \\ + --coordinate-space ${coordinate_space} \\ + ${args} + """ + + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("SPATIALDATA_WRITE module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p "spatialdata/${prefix}/${outputfolder}" + touch "spatialdata/${prefix}/${outputfolder}/fake_file.txt" + """ +} diff --git a/modules/local/spatialdata/write/meta.yml b/modules/local/spatialdata/write/meta.yml new file mode 100644 index 00000000..1c14653a --- /dev/null +++ b/modules/local/spatialdata/write/meta.yml @@ -0,0 +1,40 @@ +name: spatialdata_write +description: Convert 10x Xenium data bundle into a SpatialData bundle. +keywords: + - data + - spatialomics +tools: + - custom: + description: An open and universal framework for processing spatial omics data + homepage: https://github.com/scverse/spatialdata + documentation: https://spatialdata.scverse.org/en/latest/ + licence: ["BSD 3-Clause"] +input: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample' ] + - bundle: + type: folder + description: | + Path to spatialomics data bundle (e.g., from Xeniumranger) + +output: + - spatialdata: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample' ] + - "${outputfolder}": + type: folder + description: Spatialdata folder + pattern: "${meta.id}/spatialdata/*" + - versions: + type: file + description: File containing software versions + pattern: "versions.yml" + +authors: + - "@heylf" diff --git a/modules/local/utility/convert_mask_uint32/main.nf b/modules/local/utility/convert_mask_uint32/main.nf new file mode 100644 index 00000000..40e5c35c --- /dev/null +++ b/modules/local/utility/convert_mask_uint32/main.nf @@ -0,0 +1,48 @@ +/* + * CONVERT_MASK_UINT32: Convert segmentation mask to uint32 dtype. + * + * XeniumRanger import-segmentation requires uint32 masks. + * StarDist outputs int32 labels by default. + * + * Input: + * - meta: Sample metadata map + * - mask: Segmentation mask TIFF (any integer dtype) + * + * Output: + * - mask: uint32 segmentation mask TIFF + * - versions: Software versions + */ +process CONVERT_MASK_UINT32 { + tag "${meta.id}" + label 'process_low' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/d9/d964e0bef867bb2ff1a309c9c087d8d83ac734ce3aa315dd8311d4c1bfdafd8e/data' : + 'community.wave.seqera.io/library/python_pip_imagecodecs_nvidia-cublas-cu12_pruned:b668bcb6d531d350' }" + + input: + tuple val(meta), path(mask) + + output: + tuple val(meta), path("${prefix}_uint32_mask.tif"), emit: mask + tuple val("${task.process}"), val('python'), eval("python3 --version | sed 's/Python //'"), topic: versions, emit: versions_python + tuple val("${task.process}"), val('tifffile'), eval("python3 -c 'import tifffile; print(tifffile.__version__)'"), topic: versions, emit: versions_tifffile + tuple val("${task.process}"), val('numpy'), eval("python3 -c 'import numpy; print(numpy.__version__)'"), topic: versions, emit: versions_numpy + + when: + task.ext.when == null || task.ext.when + + script: + prefix = task.ext.prefix ?: "${meta.id}" + """ + utility_convert_mask_uint32.py \\ + --input ${mask} \\ + --output ${prefix}_uint32_mask.tif + """ + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}_uint32_mask.tif + """ +} diff --git a/modules/local/utility/convert_mask_uint32/meta.yml b/modules/local/utility/convert_mask_uint32/meta.yml new file mode 100644 index 00000000..0d6f4ff0 --- /dev/null +++ b/modules/local/utility/convert_mask_uint32/meta.yml @@ -0,0 +1,128 @@ +name: "convert_mask_uint32" +description: Convert a segmentation mask TIFF to uint32 dtype for downstream tools that require uint32 (e.g. XeniumRanger import-segmentation). +keywords: + - xenium + - segmentation + - mask + - tiff + - uint32 +tools: + - "python": + description: | + Python programming language interpreter, used here to load and rewrite + the segmentation mask with the required dtype. + homepage: "https://www.python.org/" + documentation: "https://docs.python.org/3/" + tool_dev_url: "https://github.com/python/cpython" + doi: "no DOI available" + licence: ["PSF-2.0"] + identifier: "" + - "tifffile": + description: | + Read and write TIFF files, including BigTIFF and OME-TIFF, in Python. + homepage: "https://github.com/cgohlke/tifffile" + documentation: "https://github.com/cgohlke/tifffile" + tool_dev_url: "https://github.com/cgohlke/tifffile" + doi: "no DOI available" + licence: ["BSD-3-Clause"] + identifier: "" + - "numpy": + description: | + The fundamental package for scientific computing with Python, + used to cast the mask array to uint32. + homepage: "https://numpy.org/" + documentation: "https://numpy.org/doc/stable/" + tool_dev_url: "https://github.com/numpy/numpy" + doi: "10.1038/s41586-020-2649-2" + licence: ["BSD-3-Clause"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - mask: + type: file + description: Segmentation mask TIFF (any integer dtype). + pattern: "*.{tif,tiff}" + ontologies: [] + +output: + mask: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "*_uint32_mask.tif": + type: file + description: Segmentation mask TIFF cast to uint32. + pattern: "*_uint32_mask.tif" + ontologies: [] + versions_python: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "python3 --version | sed 's/Python //'": + type: eval + description: The expression to obtain the version of the tool + versions_tifffile: + - - ${task.process}: + type: string + description: The process the versions were collected from + - tifffile: + type: string + description: The tool name + - "python3 -c 'import tifffile; print(tifffile.__version__)'": + type: eval + description: The expression to obtain the version of the tool + versions_numpy: + - - ${task.process}: + type: string + description: The process the versions were collected from + - numpy: + type: string + description: The tool name + - "python3 -c 'import numpy; print(numpy.__version__)'": + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "python3 --version | sed 's/Python //'": + type: eval + description: The expression to obtain the version of the tool + - - ${task.process}: + type: string + description: The process the versions were collected from + - tifffile: + type: string + description: The tool name + - "python3 -c 'import tifffile; print(tifffile.__version__)'": + type: eval + description: The expression to obtain the version of the tool + - - ${task.process}: + type: string + description: The process the versions were collected from + - numpy: + type: string + description: The tool name + - "python3 -c 'import numpy; print(numpy.__version__)'": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@an-altosian" +maintainers: + - "@an-altosian" diff --git a/modules/local/utility/downscale_morphology/main.nf b/modules/local/utility/downscale_morphology/main.nf new file mode 100644 index 00000000..ef5143ef --- /dev/null +++ b/modules/local/utility/downscale_morphology/main.nf @@ -0,0 +1,58 @@ +/* + * DOWNSCALE_MORPHOLOGY: Pre-downscale 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 use --diameter 30 (no internal rescale). + * + * Input: + * - meta: Sample metadata map + * - image: Morphology OME-TIFF + * + * Output: + * - downscaled: Downscaled TIFF image + * - scale_info: JSON with scale factor and original dimensions + * - versions: Software versions + */ +process DOWNSCALE_MORPHOLOGY { + tag "${meta.id}" + label 'process_medium' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/cb/cb670191b7ae1a9fd5449746453916c7014b9ea622942ca76a7cb40da7deee46/data' : + 'community.wave.seqera.io/library/python_pip_cellpose:fdf7a8c3a305a26e' }" + + input: + tuple val(meta), path(image) + + output: + tuple val(meta), path("${prefix}/downscaled.tif"), emit: downscaled + tuple val(meta), path("${prefix}/scale_info.json"), emit: scale_info + tuple val("${task.process}"), val('python'), eval("python3 --version | sed 's/Python //'"), topic: versions, emit: versions_python + tuple val("${task.process}"), val('tifffile'), eval("pip show tifffile 2>/dev/null | sed -n 's/^Version: //p'"), topic: versions, emit: versions_tifffile + tuple val("${task.process}"), val('scikit-image'), eval("pip show scikit-image 2>/dev/null | sed -n 's/^Version: //p'"), topic: versions, emit: versions_skimage + + when: + task.ext.when == null || task.ext.when + + script: + def diameter = task.ext.diameter ?: 9 + def diam_mean = 30 + prefix = task.ext.prefix ?: "${meta.id}" + """ + utility_downscale_morphology.py \\ + --image ${image} \\ + --diameter ${diameter} \\ + --diam-mean ${diam_mean} \\ + --prefix ${prefix} + """ + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + """ + mkdir -p ${prefix} + touch ${prefix}/downscaled.tif + echo '{"scale": 0.3}' > ${prefix}/scale_info.json + """ +} diff --git a/modules/local/utility/downscale_morphology/meta.yml b/modules/local/utility/downscale_morphology/meta.yml new file mode 100644 index 00000000..83408899 --- /dev/null +++ b/modules/local/utility/downscale_morphology/meta.yml @@ -0,0 +1,138 @@ +name: "downscale_morphology" +description: Pre-downscale a morphology image so that cellpose's internal rescaling does not exceed memory limits. +keywords: + - xenium + - morphology + - downscale + - image processing + - cellpose +tools: + - "python": + description: | + Python programming language interpreter. + homepage: "https://www.python.org/" + documentation: "https://docs.python.org/3/" + tool_dev_url: "https://github.com/python/cpython" + doi: "no DOI available" + licence: ["PSF-2.0"] + identifier: "" + - "tifffile": + description: | + Read and write TIFF files, including BigTIFF and OME-TIFF, in Python. + homepage: "https://github.com/cgohlke/tifffile" + documentation: "https://github.com/cgohlke/tifffile" + tool_dev_url: "https://github.com/cgohlke/tifffile" + doi: "no DOI available" + licence: ["BSD-3-Clause"] + identifier: "" + - "scikit-image": + description: | + Image processing library for Python (scikit-image), used here for + anti-aliased image resizing. + homepage: "https://scikit-image.org/" + documentation: "https://scikit-image.org/docs/stable/" + tool_dev_url: "https://github.com/scikit-image/scikit-image" + doi: "10.7717/peerj.453" + licence: ["BSD-3-Clause"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - image: + type: file + description: Morphology OME-TIFF image. + pattern: "*.{ome.tif,ome.tiff,tif,tiff}" + ontologies: [] + +output: + downscaled: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "*/downscaled.tif": + type: file + description: Downscaled TIFF image. + pattern: "*/downscaled.tif" + ontologies: [] + scale_info: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "*/scale_info.json": + type: file + description: JSON file with scale factor and original/new dimensions. + pattern: "*/scale_info.json" + ontologies: [] + versions_python: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "python3 --version | sed 's/Python //'": + type: eval + description: The expression to obtain the version of the tool + versions_tifffile: + - - ${task.process}: + type: string + description: The process the versions were collected from + - tifffile: + type: string + description: The tool name + - 'python3 -c "import tifffile; print(tifffile.__version__)"': + type: eval + description: The expression to obtain the version of the tool + versions_skimage: + - - ${task.process}: + type: string + description: The process the versions were collected from + - scikit-image: + type: string + description: The tool name + - 'python3 -c "import skimage; print(skimage.__version__)"': + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "python3 --version | sed 's/Python //'": + type: eval + description: The expression to obtain the version of the tool + - - ${task.process}: + type: string + description: The process the versions were collected from + - tifffile: + type: string + description: The tool name + - 'python3 -c "import tifffile; print(tifffile.__version__)"': + type: eval + description: The expression to obtain the version of the tool + - - ${task.process}: + type: string + description: The process the versions were collected from + - scikit-image: + type: string + description: The tool name + - 'python3 -c "import skimage; print(skimage.__version__)"': + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@an-altosian" +maintainers: + - "@an-altosian" diff --git a/modules/local/utility/extract_dapi/main.nf b/modules/local/utility/extract_dapi/main.nf new file mode 100644 index 00000000..ef9a88bd --- /dev/null +++ b/modules/local/utility/extract_dapi/main.nf @@ -0,0 +1,50 @@ +/* + * EXTRACT_DAPI: Extract DAPI channel (channel 0) from multi-channel OME-TIFF. + * + * Xenium morphology_focus.ome.tif has multiple channels (DAPI, boundary, interior); + * StarDist 2D_versatile_fluo expects single-channel input. + * + * Input: + * - meta: Sample metadata map + * - image: Multi-channel OME-TIFF morphology image + * + * Output: + * - dapi: Single-channel DAPI TIFF + * - versions: Software versions + */ +process EXTRACT_DAPI { + tag "${meta.id}" + label 'process_low' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/d9/d964e0bef867bb2ff1a309c9c087d8d83ac734ce3aa315dd8311d4c1bfdafd8e/data' : + 'community.wave.seqera.io/library/python_pip_imagecodecs_nvidia-cublas-cu12_pruned:b668bcb6d531d350' }" + + input: + tuple val(meta), path(image) + + output: + tuple val(meta), path("${prefix}_dapi.tif"), emit: dapi + tuple val("${task.process}"), val('python'), eval("python3 --version | sed 's/Python //'"), topic: versions, emit: versions_python + tuple val("${task.process}"), val('tifffile'), eval("python3 -c 'import tifffile; print(tifffile.__version__)'"), topic: versions, emit: versions_tifffile + tuple val("${task.process}"), val('numpy'), eval("python3 -c 'import numpy; print(numpy.__version__)'"), topic: versions, emit: versions_numpy + + when: + task.ext.when == null || task.ext.when + + script: + prefix = task.ext.prefix ?: "${meta.id}" + def channel_index = task.ext.channel_index ?: 0 + """ + utility_extract_dapi.py \\ + --input ${image} \\ + --output ${prefix}_dapi.tif \\ + --channel-index ${channel_index} + """ + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}_dapi.tif + """ +} diff --git a/modules/local/utility/extract_dapi/meta.yml b/modules/local/utility/extract_dapi/meta.yml new file mode 100644 index 00000000..11ce4342 --- /dev/null +++ b/modules/local/utility/extract_dapi/meta.yml @@ -0,0 +1,126 @@ +name: "extract_dapi" +description: Extract a single channel (default DAPI / channel 0) from a multi-channel OME-TIFF morphology image. +keywords: + - xenium + - dapi + - morphology + - ome-tiff + - image processing +tools: + - "python": + description: | + Python programming language interpreter. + homepage: "https://www.python.org/" + documentation: "https://docs.python.org/3/" + tool_dev_url: "https://github.com/python/cpython" + doi: "no DOI available" + licence: ["PSF-2.0"] + identifier: "" + - "tifffile": + description: | + Read and write TIFF files, including BigTIFF and OME-TIFF, in Python. + homepage: "https://github.com/cgohlke/tifffile" + documentation: "https://github.com/cgohlke/tifffile" + tool_dev_url: "https://github.com/cgohlke/tifffile" + doi: "no DOI available" + licence: ["BSD-3-Clause"] + identifier: "" + - "numpy": + description: | + The fundamental package for scientific computing with Python. + homepage: "https://numpy.org/" + documentation: "https://numpy.org/doc/stable/" + tool_dev_url: "https://github.com/numpy/numpy" + doi: "10.1038/s41586-020-2649-2" + licence: ["BSD-3-Clause"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - image: + type: file + description: Multi-channel OME-TIFF morphology image. + pattern: "*.{ome.tif,ome.tiff,tif,tiff}" + ontologies: [] + +output: + dapi: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "*_dapi.tif": + type: file + description: Single-channel TIFF containing the extracted channel. + pattern: "*_dapi.tif" + ontologies: [] + versions_python: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "python3 --version | sed 's/Python //'": + type: eval + description: The expression to obtain the version of the tool + versions_tifffile: + - - ${task.process}: + type: string + description: The process the versions were collected from + - tifffile: + type: string + description: The tool name + - "python3 -c 'import tifffile; print(tifffile.__version__)'": + type: eval + description: The expression to obtain the version of the tool + versions_numpy: + - - ${task.process}: + type: string + description: The process the versions were collected from + - numpy: + type: string + description: The tool name + - "python3 -c 'import numpy; print(numpy.__version__)'": + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "python3 --version | sed 's/Python //'": + type: eval + description: The expression to obtain the version of the tool + - - ${task.process}: + type: string + description: The process the versions were collected from + - tifffile: + type: string + description: The tool name + - "python3 -c 'import tifffile; print(tifffile.__version__)'": + type: eval + description: The expression to obtain the version of the tool + - - ${task.process}: + type: string + description: The process the versions were collected from + - numpy: + type: string + description: The tool name + - "python3 -c 'import numpy; print(numpy.__version__)'": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@an-altosian" +maintainers: + - "@an-altosian" diff --git a/modules/local/utility/extract_preview_data/main.nf b/modules/local/utility/extract_preview_data/main.nf new file mode 100644 index 00000000..821effc5 --- /dev/null +++ b/modules/local/utility/extract_preview_data/main.nf @@ -0,0 +1,49 @@ +process EXTRACT_PREVIEW_DATA { + tag "${meta.id}" + label 'process_low' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/c6/c6ebf365fbfd7bdde9e1453d646f45c39eddde92df5922b9881785f347bdbc2b/data' : + 'community.wave.seqera.io/library/beautifulsoup4_pandas:a3f88f59088edad5' }" + + input: + tuple val(meta), path(preview_html) + + output: + tuple val(meta), path("${prefix}/*_mqc.tsv"), emit: mqc_data + tuple val(meta), path("${prefix}/*_mqc.png"), emit: mqc_img + tuple val("${task.process}"), val('python'), eval("python3 --version | sed 's/Python //'"), topic: versions, emit: versions_python + + when: + task.ext.when == null || task.ext.when + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("EXTRACT_PREVIEW_DATA module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + prefix = task.ext.prefix ?: "${meta.id}" + + """ + utility_extract_preview_data.py \\ + --preview-html ${preview_html} \\ + --prefix ${prefix} + """ + + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("EXTRACT_PREVIEW_DATA module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p ${prefix} + touch ${prefix}/noise_distribution_mqc.tsv + touch ${prefix}/gene_structure_mqc.tsv + touch ${prefix}/umap_mqc.tsv + touch ${prefix}/transcript_plots_mqc.png + touch ${prefix}/noise_level_mqc.png + """ +} diff --git a/modules/local/utility/extract_preview_data/meta.yml b/modules/local/utility/extract_preview_data/meta.yml new file mode 100644 index 00000000..d1b448e0 --- /dev/null +++ b/modules/local/utility/extract_preview_data/meta.yml @@ -0,0 +1,99 @@ +name: "extract_preview_data" +description: Parse a Xenium preview HTML report and extract MultiQC-compatible TSV tables and embedded plot images. +keywords: + - xenium + - preview + - multiqc + - html + - parsing +tools: + - "python": + description: | + Python programming language interpreter. + homepage: "https://www.python.org/" + documentation: "https://docs.python.org/3/" + tool_dev_url: "https://github.com/python/cpython" + doi: "no DOI available" + licence: ["PSF-2.0"] + identifier: "" + - "beautifulsoup4": + description: | + Python library for pulling data out of HTML and XML files. + homepage: "https://www.crummy.com/software/BeautifulSoup/" + documentation: "https://www.crummy.com/software/BeautifulSoup/bs4/doc/" + tool_dev_url: "https://git.launchpad.net/beautifulsoup" + doi: "no DOI available" + licence: ["MIT"] + identifier: "" + - "pandas": + description: | + Powerful data structures for data analysis, time series, and statistics. + homepage: "https://pandas.pydata.org/" + documentation: "https://pandas.pydata.org/docs/" + tool_dev_url: "https://github.com/pandas-dev/pandas" + doi: "10.5281/zenodo.3509134" + licence: ["BSD-3-Clause"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - preview_html: + type: file + description: Xenium preview HTML report to extract data from. + pattern: "*.html" + ontologies: [] + +output: + mqc_data: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "${prefix}/*_mqc.tsv": + type: file + description: MultiQC-compatible TSV tables extracted from the preview HTML. + pattern: "*_mqc.tsv" + ontologies: [] + mqc_img: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "${prefix}/*_mqc.png": + type: file + description: MultiQC-compatible PNG plot images extracted from the preview HTML. + pattern: "*_mqc.png" + ontologies: [] + versions_python: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "python3 --version | sed 's/Python //'": + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "python3 --version | sed 's/Python //'": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/modules/local/utility/get_coordinates/main.nf b/modules/local/utility/get_coordinates/main.nf new file mode 100644 index 00000000..2b672239 --- /dev/null +++ b/modules/local/utility/get_coordinates/main.nf @@ -0,0 +1,42 @@ +process GET_TRANSCRIPTS_COORDINATES { + tag "${meta.id}" + label 'process_low' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/94/9409ce399922a5746bea1b7df5668c3d1d79b9af49a15950d9818c4fe45ac749/data' : + 'community.wave.seqera.io/library/pandas_procs_pyarrow:d8f882b65dfea451' }" + + input: + tuple val(meta), path(transcripts) + + output: + stdout() + tuple val("${task.process}"), val('python'), eval("python3 --version | sed 's/Python //'"), topic: versions, emit: versions_python + + when: + task.ext.when == null || task.ext.when + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("GET_TRANSCRIPTS_COORDINATES module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + prefix = task.ext.prefix ?: "${meta.id}" + + """ + utility_get_coordinates.py \\ + --transcripts ${transcripts} + """ + + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("GET_TRANSCRIPTS_COORDINATES module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + prefix = task.ext.prefix ?: "${meta.id}" + + """ + echo "0,0,1000,1000" + """ +} diff --git a/modules/local/utility/get_coordinates/meta.yml b/modules/local/utility/get_coordinates/meta.yml new file mode 100644 index 00000000..cf631eda --- /dev/null +++ b/modules/local/utility/get_coordinates/meta.yml @@ -0,0 +1,81 @@ +name: "get_transcripts_coordinates" +description: Read a transcripts parquet file and emit the transcript bounding-box coordinates to stdout. +keywords: + - xenium + - transcripts + - coordinates + - parquet + - bounding-box +tools: + - "python": + description: | + Python programming language interpreter. + homepage: "https://www.python.org/" + documentation: "https://docs.python.org/3/" + tool_dev_url: "https://github.com/python/cpython" + doi: "no DOI available" + licence: ["PSF-2.0"] + identifier: "" + - "pandas": + description: | + Powerful data structures for data analysis, time series, and statistics. + homepage: "https://pandas.pydata.org/" + documentation: "https://pandas.pydata.org/docs/" + tool_dev_url: "https://github.com/pandas-dev/pandas" + doi: "10.5281/zenodo.3509134" + licence: ["BSD-3-Clause"] + identifier: "" + - "pyarrow": + description: | + Python bindings for Apache Arrow, used to read Parquet files efficiently. + homepage: "https://arrow.apache.org/" + documentation: "https://arrow.apache.org/docs/python/" + tool_dev_url: "https://github.com/apache/arrow" + doi: "no DOI available" + licence: ["Apache-2.0"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - transcripts: + type: file + description: Transcripts parquet file from the Xenium bundle. + pattern: "*.parquet" + ontologies: [] + +output: + stdout: + - stdout: + type: string + description: Comma-separated transcript bounding-box coordinates printed to stdout. + versions_python: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "python3 --version | sed 's/Python //'": + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "python3 --version | sed 's/Python //'": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/modules/local/utility/parquet_to_csv/main.nf b/modules/local/utility/parquet_to_csv/main.nf new file mode 100644 index 00000000..865408bc --- /dev/null +++ b/modules/local/utility/parquet_to_csv/main.nf @@ -0,0 +1,46 @@ +process PARQUET_TO_CSV { + tag "$meta.id" + label 'process_low' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/94/9409ce399922a5746bea1b7df5668c3d1d79b9af49a15950d9818c4fe45ac749/data' : + 'community.wave.seqera.io/library/pandas_procs_pyarrow:d8f882b65dfea451' }" + + input: + tuple val(meta), path(transcripts) + val(extension) + + output: + tuple val(meta), path("${prefix}/*.csv*"), emit: transcripts_csv + tuple val("${task.process}"), val('pyarrow'), eval("pip show pyarrow 2>/dev/null | sed -n 's/^Version: //p'"), topic: versions, emit: versions_pyarrow + + when: + task.ext.when == null || task.ext.when + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "PARQUET_TO_CSV module does not support Conda. Please use Docker / Singularity / Podman instead." + } + prefix = task.ext.prefix ?: "${meta.id}" + + """ + utility_parquet_to_csv.py \\ + --transcripts ${transcripts} \\ + --extension ${extension} \\ + --prefix ${prefix} + """ + + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "PARQUET_TO_CSV module does not support Conda. Please use Docker / Singularity / Podman instead." + } + + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p ${prefix} + touch "${prefix}/${transcripts}.csv" + """ +} diff --git a/modules/local/utility/parquet_to_csv/meta.yml b/modules/local/utility/parquet_to_csv/meta.yml new file mode 100644 index 00000000..60d58fcc --- /dev/null +++ b/modules/local/utility/parquet_to_csv/meta.yml @@ -0,0 +1,42 @@ +name: "parquet_to_csv" +description: Tool suite for spatial omics data conversions. +keywords: + - xenium +tools: + - "parquet_to_csv": + description: "Collects functions to convert data formats for various types of data processing and analysis for spatial omics data." + homepage: "https://github.com/heylf/spatialconverter" + documentation: "https://github.com/heylf/spatialconverter" + tool_dev_url: "https://github.com/heylf/spatialconverter" + doi: "tbd" + licence: [""] + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'segger_run_id' ]` + - transcripts: + type: file + description: | + Transcript file in parquet format. +output: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'segger_run_id' ]` + - transcripts_csv: + type: file + description: | + Transcript file in csv format. + - - versions: + type: file + description: File containing software versions + pattern: "versions.yml" + +authors: + - "@heylf" +maintainers: + - "@heylf" diff --git a/modules/local/utility/reconstruct_patches/main.nf b/modules/local/utility/reconstruct_patches/main.nf new file mode 100644 index 00000000..c0e87b1b --- /dev/null +++ b/modules/local/utility/reconstruct_patches/main.nf @@ -0,0 +1,68 @@ +/* + * RECONSTRUCT_PATCHES: Reconstruct the patches directory structure from + * individually staged patch files for stitch_transcripts.py. + * + * Inputs: + * meta - sample metadata map + * grid_json - patch_grid.json from XENIUM_PATCH_DIVIDE + * patch_ids - list of patch identifiers (e.g. patch_0000, patch_0001, ...) + * csv_files - per-patch Baysor segmentation.csv files (staged into csv_?/ dirs) + * geojson_files - per-patch Baysor segmentation_polygons.json files (staged into geo_?/ dirs) + * + * Outputs: + * patches_dir - reconstructed patches/ directory containing patch_grid.json plus + * one subdirectory per patch with segmentation.csv and segmentation_polygons.json + * versions - topic-channel version emission for coreutils (cp) + */ +process RECONSTRUCT_PATCHES { + tag "$meta.id" + label 'process_single' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/b9/b900c562dadb26dedce5254f88ae85440d7a08cd5e7f72cc4c3ce5aef89b5aa8/data' : + 'community.wave.seqera.io/library/pip_pandas:257725bfe0d2df83' }" + + input: + tuple val(meta), path(grid_json), val(patch_ids), path(csv_files, stageAs: 'csv_?/*'), path(geojson_files, stageAs: 'geo_?/*') + + output: + tuple val(meta), path("patches"), emit: patches_dir + tuple val("${task.process}"), val('coreutils'), eval("cp --version | head -n1 | awk '{print \$NF}'"), topic: versions, emit: versions_coreutils + + when: + task.ext.when == null || task.ext.when + + script: + def ids = patch_ids instanceof List ? patch_ids : [patch_ids] + def csvs = csv_files instanceof List ? csv_files : [csv_files] + def geos = geojson_files instanceof List ? geojson_files : [geojson_files] + + def reconstruct_script = ids.withIndex().collect { pid, idx -> + [ + "mkdir -p patches/${pid}", + "cp '${csvs[idx]}' patches/${pid}/segmentation.csv", + "cp '${geos[idx]}' patches/${pid}/segmentation_polygons.json", + ].join('\n ') + }.join('\n ') + """ + mkdir -p patches + cp '${grid_json}' patches/patch_grid.json + + ${reconstruct_script} + """ + + stub: + def ids = patch_ids instanceof List ? patch_ids : [patch_ids] + def stub_files = ids.collect { pid -> + [ + "mkdir -p patches/${pid}", + "touch patches/${pid}/segmentation.csv", + "touch patches/${pid}/segmentation_polygons.json", + ].join('\n ') + }.join('\n ') + """ + mkdir -p patches + touch patches/patch_grid.json + ${stub_files} + """ +} diff --git a/modules/local/utility/reconstruct_patches/meta.yml b/modules/local/utility/reconstruct_patches/meta.yml new file mode 100644 index 00000000..b218c15a --- /dev/null +++ b/modules/local/utility/reconstruct_patches/meta.yml @@ -0,0 +1,87 @@ +name: "reconstruct_patches" +description: Reconstruct the patches directory structure from individually staged per-patch Baysor segmentation files for downstream stitching. +keywords: + - baysor + - xenium + - patches + - stitching + - segmentation +tools: + - "coreutils": + description: | + GNU core utilities (cp, mkdir) used to assemble the patches directory. + homepage: "https://www.gnu.org/software/coreutils/" + documentation: "https://www.gnu.org/software/coreutils/manual/coreutils.html" + tool_dev_url: "https://git.savannah.gnu.org/cgit/coreutils.git" + doi: "no DOI available" + licence: ["GPL-3.0-or-later"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample1' ] + - grid_json: + type: file + description: patch_grid.json describing the tile layout from XENIUM_PATCH_DIVIDE. + pattern: "*.json" + ontologies: [] + - patch_ids: + type: list + description: | + Ordered list of patch identifiers + (e.g. patch_0000, patch_0001, ...). + - csv_files: + type: list + description: Per-patch Baysor segmentation.csv files, one per patch_id. + pattern: "*.csv" + ontologies: [] + - geojson_files: + type: list + description: Per-patch Baysor segmentation_polygons.json files, one per patch_id. + pattern: "*.json" + ontologies: [] + +output: + patches_dir: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample1' ] + - "patches": + type: directory + description: | + Reconstructed patches directory containing patch_grid.json plus one + subdirectory per patch with segmentation.csv and + segmentation_polygons.json. + pattern: "patches" + versions_coreutils: + - - ${task.process}: + type: string + description: The process the versions were collected from + - coreutils: + type: string + description: The tool name + - "cp --version | head -n1 | awk '{print $NF}'": + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - coreutils: + type: string + description: The tool name + - "cp --version | head -n1 | awk '{print $NF}'": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@an-altosian" +maintainers: + - "@an-altosian" diff --git a/modules/local/utility/resize_tif/main.nf b/modules/local/utility/resize_tif/main.nf new file mode 100644 index 00000000..00fe2134 --- /dev/null +++ b/modules/local/utility/resize_tif/main.nf @@ -0,0 +1,48 @@ +process RESIZE_TIF { + tag "${meta.id}" + label 'process_low' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/6d/6d5aedb8fcf066eecd9f0dfac93bfffc8161bdae65b4502509d9953db2036a7e/data' : + 'community.wave.seqera.io/library/numpy_pandas_pyarrow_scikit-image_tifffile:131397039376b375' }" + + input: + tuple val(meta), path(transcripts), path(mask), path(metadata) + + output: + tuple val(meta), path("${meta.id}/resized_*.tif"), emit: resized_mask + tuple val("${task.process}"), val('python'), eval("python3 --version | sed 's/Python //'"), topic: versions, emit: versions_python + tuple val("${task.process}"), val('tifffile'), eval("pip show tifffile 2>/dev/null | sed -n 's/^Version: //p'"), topic: versions, emit: versions_tifffile + + when: + task.ext.when == null || task.ext.when + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("RESIZE_TIF module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + + prefix = task.ext.prefix ?: "${meta.id}" + + """ + utility_resize_tif.py \\ + --mask ${mask} \\ + --transcripts ${transcripts} \\ + --metadata ${metadata} \\ + --prefix ${prefix} \\ + --mask-filename ${mask} + """ + + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("RESIZE_TIF module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p ${prefix} + touch "${prefix}/resized_${mask}.tif" + """ +} diff --git a/modules/local/utility/resize_tif/meta.yml b/modules/local/utility/resize_tif/meta.yml new file mode 100644 index 00000000..482c7fd9 --- /dev/null +++ b/modules/local/utility/resize_tif/meta.yml @@ -0,0 +1,126 @@ +name: "resize_tif" +description: Resize a segmentation mask TIFF to match the full-resolution Xenium morphology image dimensions. +keywords: + - xenium + - tif + - resize + - mask + - segmentation +tools: + - "python": + description: | + Python programming language interpreter. + homepage: "https://www.python.org/" + documentation: "https://docs.python.org/3/" + tool_dev_url: "https://github.com/python/cpython" + doi: "no DOI available" + licence: ["PSF-2.0"] + identifier: "" + - "tifffile": + description: | + Read and write TIFF files, including BigTIFF and OME-TIFF, in Python. + homepage: "https://github.com/cgohlke/tifffile" + documentation: "https://github.com/cgohlke/tifffile" + tool_dev_url: "https://github.com/cgohlke/tifffile" + doi: "no DOI available" + licence: ["BSD-3-Clause"] + identifier: "" + - "numpy": + description: | + The fundamental package for scientific computing with Python. + homepage: "https://numpy.org/" + documentation: "https://numpy.org/doc/stable/" + tool_dev_url: "https://github.com/numpy/numpy" + doi: "10.1038/s41586-020-2649-2" + licence: ["BSD-3-Clause"] + identifier: "" + - "scikit-image": + description: | + Image processing routines for SciPy. + homepage: "https://scikit-image.org/" + documentation: "https://scikit-image.org/docs/stable/" + tool_dev_url: "https://github.com/scikit-image/scikit-image" + doi: "10.7717/peerj.453" + licence: ["BSD-3-Clause"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - transcripts: + type: file + description: Transcripts parquet file from the Xenium bundle. + pattern: "*.parquet" + ontologies: [] + - mask: + type: file + description: Segmentation mask TIFF to resize. + pattern: "*.{tif,tiff}" + ontologies: [] + - metadata: + type: file + description: Xenium experiment metadata file (used to read full-resolution dimensions). + pattern: "*" + ontologies: [] + +output: + resized_mask: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "${meta.id}/resized_*.tif": + type: file + description: Segmentation mask TIFF resized to the full-resolution morphology image dimensions. + pattern: "*/resized_*.tif" + ontologies: [] + versions_python: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "python3 --version | sed 's/Python //'": + type: eval + description: The expression to obtain the version of the tool + versions_tifffile: + - - ${task.process}: + type: string + description: The process the versions were collected from + - tifffile: + type: string + description: The tool name + - 'python3 -c "import tifffile; print(tifffile.__version__)"': + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "python3 --version | sed 's/Python //'": + type: eval + description: The expression to obtain the version of the tool + - - ${task.process}: + type: string + description: The process the versions were collected from + - tifffile: + type: string + description: The tool name + - 'python3 -c "import tifffile; print(tifffile.__version__)"': + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/modules/local/utility/segger2xr/main.nf b/modules/local/utility/segger2xr/main.nf new file mode 100644 index 00000000..073748d7 --- /dev/null +++ b/modules/local/utility/segger2xr/main.nf @@ -0,0 +1,50 @@ +process SEGGER2XR { + tag "$meta.id" + label 'process_medium' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/cb/cb8fc03fa657c164c5d83f075578bbb5d9c10f1178165f94e94f33c67efca1a1/data' : + 'community.wave.seqera.io/library/spatialdata-io_spatialdata:b264928c30680e87' }" + + input: + tuple val(meta), path(transcripts) + + output: + tuple val(meta), path("${meta.id}/segmentation.csv") , emit: segmentation_csv + tuple val(meta), path("${meta.id}/transcripts.parquet") , emit: transcripts_parquet + tuple val(meta), path("${meta.id}/segmentation_polygons.json") , emit: viz_polygons + tuple val("${task.process}"), val('python'), eval("python3 --version | sed 's/Python //'"), topic: versions, emit: versions_python + + when: + task.ext.when == null || task.ext.when + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "SEGGER2XR module does not support Conda. Please use Docker / Singularity / Podman instead." + } + + def min_transcripts = task.ext.min_transcripts_per_cell ?: 3 + + """ + utility_segger2xr.py \\ + --transcripts ${transcripts} \\ + --prefix ${meta.id} \\ + --min-transcripts ${min_transcripts} + """ + + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "SEGGER2XR module does not support Conda. Please use Docker / Singularity / Podman instead." + } + + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p ${prefix} + echo 'transcript_id,x,y,z,gene,cell,is_noise' > "${prefix}/segmentation.csv" + touch "${prefix}/transcripts.parquet" + echo '{"type":"FeatureCollection","features":[]}' > "${prefix}/segmentation_polygons.json" + """ +} diff --git a/modules/local/utility/segger2xr/meta.yml b/modules/local/utility/segger2xr/meta.yml new file mode 100644 index 00000000..9a7364bf --- /dev/null +++ b/modules/local/utility/segger2xr/meta.yml @@ -0,0 +1,101 @@ +name: "segger2xr" +description: Convert SEGGER segmentation output (transcripts parquet with cell assignments) into XeniumRanger-compatible segmentation files. +keywords: + - xenium + - segger + - xeniumranger + - segmentation + - conversion +tools: + - "python": + description: | + Python programming language interpreter. + homepage: "https://www.python.org/" + documentation: "https://docs.python.org/3/" + tool_dev_url: "https://github.com/python/cpython" + doi: "no DOI available" + licence: ["PSF-2.0"] + identifier: "" + - "segger": + description: | + SEGGER cell segmentation for spatial transcriptomics data. + homepage: "https://github.com/EliHei2/segger_dev" + documentation: "https://github.com/EliHei2/segger_dev" + tool_dev_url: "https://github.com/EliHei2/segger_dev" + doi: "no DOI available" + licence: ["MIT"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - transcripts: + type: file + description: Transcripts parquet file output by SEGGER with per-transcript cell assignments. + pattern: "*.parquet" + ontologies: [] + +output: + segmentation_csv: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "${meta.id}/segmentation.csv": + type: file + description: Per-transcript segmentation table (transcript_id, x, y, z, gene, cell, is_noise). + pattern: "*/segmentation.csv" + ontologies: [] + transcripts_parquet: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "${meta.id}/transcripts.parquet": + type: file + description: Transcripts parquet file in XeniumRanger-compatible format. + pattern: "*/transcripts.parquet" + ontologies: [] + viz_polygons: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "${meta.id}/segmentation_polygons.json": + type: file + description: GeoJSON FeatureCollection of cell segmentation polygons for visualization. + pattern: "*/segmentation_polygons.json" + ontologies: [] + versions_python: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "python3 --version | sed 's/Python //'": + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "python3 --version | sed 's/Python //'": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@heylf" +maintainers: + - "@heylf" diff --git a/modules/local/utility/split_transcripts/main.nf b/modules/local/utility/split_transcripts/main.nf new file mode 100644 index 00000000..f7057e31 --- /dev/null +++ b/modules/local/utility/split_transcripts/main.nf @@ -0,0 +1,46 @@ +process SPLIT_TRANSCRIPTS { + tag "$meta.id" + label 'process_low' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/b9/b900c562dadb26dedce5254f88ae85440d7a08cd5e7f72cc4c3ce5aef89b5aa8/data' : + 'community.wave.seqera.io/library/pip_pandas:257725bfe0d2df83' }" + + input: + tuple val(meta), path(transcripts) + val(x_bins) + val(y_bins) + + output: + tuple val(meta), path("${meta.id}/splits.csv"), emit: splits_csv + tuple val("${task.process}"), val('python'), eval("python3 --version | sed 's/Python //'"), topic: versions, emit: versions_python + + when: + task.ext.when == null || task.ext.when + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "SPLIT_TRANSCRIPTS module does not support Conda. Please use Docker / Singularity / Podman instead." + } + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + utility_split_transcripts.py \\ + --transcripts ${transcripts} \\ + --x-bins ${x_bins} \\ + --y-bins ${y_bins} \\ + --prefix ${prefix} + """ + + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "SPLIT_TRANSCRIPTS module does not support Conda. Please use Docker / Singularity / Podman instead." + } + def prefix = task.ext.prefix ?: "${meta.id}" + """ + mkdir -p ${prefix} + touch "${prefix}/splits.csv" + """ +} diff --git a/modules/local/utility/split_transcripts/meta.yml b/modules/local/utility/split_transcripts/meta.yml new file mode 100644 index 00000000..8317c052 --- /dev/null +++ b/modules/local/utility/split_transcripts/meta.yml @@ -0,0 +1,54 @@ +name: "split_transcripts" +description: Split transcripts along x & y axes +keywords: + - baysor + - transcripts + - split_transcripts +tools: + - "baysor": + description: "Utility package to split transcripts for Baysor. Baysor is a tool that segments cells using spatial gene expression maps. Optionally, segmentation masks can be given as additional input." + homepage: "https://kharchenkolab.github.io/Baysor/dev/" + documentation: "https://kharchenkolab.github.io/Baysor/dev/" + tool_dev_url: "https://github.com/kharchenkolab/Baysor" + doi: "https://doi.org/10.1038/s41587-021-01044-w" + licence: ["MIT license"] + identifier: + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - transcripts: + type: file + description: transcripts.parquet file from the xenium bundle + pattern: "*.parquet" + - x_bins: + type: integer + description: number of slices along the x axis (default - 10) + - y_bins: + type: integer + description: number of slices along the y axis (default - 10) + +output: + - - splits_csv: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "*.csv": + type: file + description: filtered transcripts.parquet + pattern: "splits.csv" + + - - versions: + type: file + description: File containing software versions + pattern: "versions.yml" + +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/modules/local/utility/upscale_mask/main.nf b/modules/local/utility/upscale_mask/main.nf new file mode 100644 index 00000000..2fc896e2 --- /dev/null +++ b/modules/local/utility/upscale_mask/main.nf @@ -0,0 +1,50 @@ +/* + * UPSCALE_MASK: Restore cellpose masks to original image resolution + * + * Uses nearest-neighbor interpolation to upscale segmentation masks + * back to original dimensions (from scale_info.json). + * + * Input: + * - meta: Sample metadata map + * - mask: Cellpose mask TIFF (downscaled resolution) + * - scale_info: JSON with original dimensions + * + * Output: + * - upscaled_mask: Mask at original resolution + * - versions: Software versions + */ +process UPSCALE_MASK { + tag "${meta.id}" + label 'process_medium' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/cb/cb670191b7ae1a9fd5449746453916c7014b9ea622942ca76a7cb40da7deee46/data' : + 'community.wave.seqera.io/library/python_pip_cellpose:fdf7a8c3a305a26e' }" + + input: + tuple val(meta), path(mask), path(scale_info) + + output: + tuple val(meta), path("${prefix}/upscaled_*.tif"), emit: upscaled_mask + tuple val("${task.process}"), val('python'), eval("python3 --version | sed 's/Python //'"), topic: versions, emit: versions_python + tuple val("${task.process}"), val('tifffile'), eval("pip show tifffile 2>/dev/null | sed -n 's/^Version: //p'"), topic: versions, emit: versions_tifffile + + when: + task.ext.when == null || task.ext.when + + script: + prefix = task.ext.prefix ?: "${meta.id}" + """ + utility_upscale_mask.py \\ + --mask ${mask} \\ + --scale-info ${scale_info} \\ + --prefix ${prefix} + """ + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + """ + mkdir -p ${prefix} + touch ${prefix}/upscaled_mask.tif + """ +} diff --git a/modules/local/utility/upscale_mask/meta.yml b/modules/local/utility/upscale_mask/meta.yml new file mode 100644 index 00000000..92c85e12 --- /dev/null +++ b/modules/local/utility/upscale_mask/meta.yml @@ -0,0 +1,103 @@ +name: "upscale_mask" +description: Restore segmentation masks to the original image resolution using nearest-neighbor interpolation. +keywords: + - xenium + - mask + - upscale + - segmentation + - cellpose +tools: + - "python": + description: | + Python programming language interpreter. + homepage: "https://www.python.org/" + documentation: "https://docs.python.org/3/" + tool_dev_url: "https://github.com/python/cpython" + doi: "no DOI available" + licence: ["PSF-2.0"] + identifier: "" + - "tifffile": + description: | + Read and write TIFF files, including BigTIFF and OME-TIFF, in Python. + homepage: "https://github.com/cgohlke/tifffile" + documentation: "https://github.com/cgohlke/tifffile" + tool_dev_url: "https://github.com/cgohlke/tifffile" + doi: "no DOI available" + licence: ["BSD-3-Clause"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - mask: + type: file + description: Cellpose segmentation mask TIFF at downscaled resolution. + pattern: "*.tif" + ontologies: [] + - scale_info: + type: file + description: JSON file containing original image dimensions (orig_h, orig_w). + pattern: "*.json" + ontologies: [] + +output: + upscaled_mask: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "*/upscaled_*.tif": + type: file + description: Segmentation mask upscaled to the original image resolution. + pattern: "*/upscaled_*.tif" + ontologies: [] + versions_python: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "python3 --version | sed 's/Python //'": + type: eval + description: The expression to obtain the version of the tool + versions_tifffile: + - - ${task.process}: + type: string + description: The process the versions were collected from + - tifffile: + type: string + description: The tool name + - 'python3 -c "import tifffile; print(tifffile.__version__)"': + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "python3 --version | sed 's/Python //'": + type: eval + description: The expression to obtain the version of the tool + - - ${task.process}: + type: string + description: The process the versions were collected from + - tifffile: + type: string + description: The tool name + - 'python3 -c "import tifffile; print(tifffile.__version__)"': + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@an-altosian" +maintainers: + - "@an-altosian" diff --git a/modules/local/xenium_patch/divide/main.nf b/modules/local/xenium_patch/divide/main.nf new file mode 100644 index 00000000..957b1624 --- /dev/null +++ b/modules/local/xenium_patch/divide/main.nf @@ -0,0 +1,57 @@ +/* + * XENIUM_PATCH_DIVIDE: Split transcripts.parquet into overlapping patches. + * + * Input: + * - meta: Sample metadata map + * - transcripts: transcripts.parquet file + * - image: morphology image (for getting dimensions) + * + * Output: + * - grid: patch_grid.json metadata file + * - patch_transcripts: per-patch transcripts.parquet files (one per patch) + * - versions: Software versions + */ +process XENIUM_PATCH_DIVIDE { + tag "$meta.id" + label 'process_medium' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/f9/f9c8f3a2de4e2aa94500011f7d7d09276e9b6f2d79ee8737c9098fe22d4649bc/data' : + 'community.wave.seqera.io/library/sopa_procps-ng_pyarrow:c9ce8cd2ede79d72' }" + + input: + tuple val(meta), path(transcripts), path(image) + + output: + tuple val(meta), path("patches/patch_grid.json") , emit: grid + tuple val(meta), path("patches/patch_*/transcripts.parquet") , emit: patch_transcripts + tuple val("${task.process}"), val('python'), eval("python3 --version | sed 's/Python //'"), topic: versions, emit: versions_python + tuple val("${task.process}"), val('pyarrow'), eval("pip show pyarrow 2>/dev/null | sed -n 's/^Version: //p'"), topic: versions, emit: versions_pyarrow + + when: + task.ext.when == null || task.ext.when + + script: + def tile_width = task.ext.tile_width ?: 2000 + def overlap = task.ext.overlap ?: 50 + def balanced = task.ext.balanced + def balanced_flag = balanced == true || balanced == 'true' ? '--balanced' : '' + """ + divide_transcripts.py \\ + --transcripts ${transcripts} \\ + --output patches \\ + --tile-width ${tile_width} \\ + --overlap ${overlap} \\ + ${balanced_flag} \\ + --image-width \$(python3 -c "import tifffile; print(tifffile.imread('${image}').shape[-1])") \\ + --image-height \$(python3 -c "import tifffile; print(tifffile.imread('${image}').shape[-2])") + + """ + + stub: + """ + mkdir -p patches/patch_0_0 + touch patches/patch_0_0/transcripts.parquet + echo '{}' > patches/patch_grid.json + """ +} diff --git a/modules/local/xenium_patch/divide/meta.yml b/modules/local/xenium_patch/divide/meta.yml new file mode 100644 index 00000000..28983c90 --- /dev/null +++ b/modules/local/xenium_patch/divide/meta.yml @@ -0,0 +1,115 @@ +name: "xenium_patch_divide" +description: Split transcripts.parquet into overlapping spatial patches for tiled segmentation processing. +keywords: + - xenium + - patches + - tiling + - transcripts + - parquet +tools: + - "python": + description: | + Python programming language interpreter. + homepage: "https://www.python.org/" + documentation: "https://docs.python.org/3/" + tool_dev_url: "https://github.com/python/cpython" + doi: "no DOI available" + licence: ["PSF-2.0"] + identifier: "" + - "pyarrow": + description: | + Python bindings for Apache Arrow, used to read and write Parquet + files efficiently. + homepage: "https://arrow.apache.org/docs/python/" + documentation: "https://arrow.apache.org/docs/python/" + tool_dev_url: "https://github.com/apache/arrow" + doi: "no DOI available" + licence: ["Apache-2.0"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - transcripts: + type: file + description: transcripts.parquet file from the Xenium bundle. + pattern: "*.parquet" + ontologies: [] + - image: + type: file + description: Morphology image (used to read full-resolution dimensions). + pattern: "*.{ome.tif,ome.tiff,tif,tiff}" + ontologies: [] + +output: + grid: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "patches/patch_grid.json": + type: file + description: JSON file describing the patch grid (offsets, sizes, overlaps). + pattern: "patches/patch_grid.json" + ontologies: [] + patch_transcripts: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "patches/patch_*/transcripts.parquet": + type: file + description: Per-patch transcripts.parquet files (one per patch). + pattern: "patches/patch_*/transcripts.parquet" + ontologies: [] + versions_python: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "python3 --version | sed 's/Python //'": + type: eval + description: The expression to obtain the version of the tool + versions_pyarrow: + - - ${task.process}: + type: string + description: The process the versions were collected from + - pyarrow: + type: string + description: The tool name + - 'python3 -c "import pyarrow; print(pyarrow.__version__)"': + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "python3 --version | sed 's/Python //'": + type: eval + description: The expression to obtain the version of the tool + - - ${task.process}: + type: string + description: The process the versions were collected from + - pyarrow: + type: string + description: The tool name + - 'python3 -c "import pyarrow; print(pyarrow.__version__)"': + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@an-altosian" +maintainers: + - "@an-altosian" diff --git a/modules/local/xenium_patch/stitch/main.nf b/modules/local/xenium_patch/stitch/main.nf new file mode 100644 index 00000000..83d7fed5 --- /dev/null +++ b/modules/local/xenium_patch/stitch/main.nf @@ -0,0 +1,56 @@ +/* + * XENIUM_PATCH_STITCH: Stitch per-patch segmentation results into unified output. + * + * Uses sopa's solve_conflicts() to resolve overlapping cells at patch boundaries. + * + * Input: + * - meta: Sample metadata map + * - patches: Directory containing patch subdirectories and patch_grid.json + * + * Output: + * - xr_polygons_transcript: Stitched cell polygons and transcript metadata + * - versions: Software versions + */ +process XENIUM_PATCH_STITCH { + tag "$meta.id" + label 'process_medium' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/f9/f9c8f3a2de4e2aa94500011f7d7d09276e9b6f2d79ee8737c9098fe22d4649bc/data' : + 'community.wave.seqera.io/library/sopa_procps-ng_pyarrow:c9ce8cd2ede79d72' }" + + input: + tuple val(meta), path(patches) + + output: + tuple val(meta), + path("output/xr-cell-polygons.geojson"), + path("output/xr-transcript-metadata.csv") , emit: xr_polygons_transcript + tuple val("${task.process}"), val('python'), eval("python3 --version | sed 's/Python //'"), topic: versions, emit: versions_python + tuple val("${task.process}"), val('sopa'), eval("pip show sopa 2>/dev/null | sed -n 's/^Version: //p'"), topic: versions, emit: versions_sopa + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + """ + xenium_patch_stitch_transcripts.py \\ + --patches ${patches} \\ + --output output \\ + ${args} + + # Post-process: ensure all GeoJSON geometries are Polygon and + # reconcile dropped cells in the transcript CSV. + xenium_patch_stitch_postprocess.py \\ + --geojson output/xr-cell-polygons.geojson \\ + --csv output/xr-transcript-metadata.csv + """ + + stub: + """ + mkdir -p output + echo '{"type":"FeatureCollection","features":[]}' > output/xr-cell-polygons.geojson + echo 'transcript_id,x,y,z,gene,cell,is_noise' > output/xr-transcript-metadata.csv + """ +} diff --git a/modules/local/xenium_patch/stitch/meta.yml b/modules/local/xenium_patch/stitch/meta.yml new file mode 100644 index 00000000..958ef907 --- /dev/null +++ b/modules/local/xenium_patch/stitch/meta.yml @@ -0,0 +1,104 @@ +name: "xenium_patch_stitch" +description: Stitch per-patch segmentation results into a unified output using sopa's solve_conflicts() to resolve overlapping cells at patch boundaries. +keywords: + - xenium + - patches + - stitching + - sopa + - segmentation +tools: + - "python": + description: | + Python programming language interpreter. + homepage: "https://www.python.org/" + documentation: "https://docs.python.org/3/" + tool_dev_url: "https://github.com/python/cpython" + doi: "no DOI available" + licence: ["PSF-2.0"] + identifier: "" + - "sopa": + description: | + Spatial-omics pipeline that handles tile-based segmentation, conflict + resolution, and aggregation across multiple platforms (Xenium, + MERSCOPE, CosMx, etc.). + homepage: "https://gustaveroussy.github.io/sopa/" + documentation: "https://gustaveroussy.github.io/sopa/" + tool_dev_url: "https://github.com/gustaveroussy/sopa" + doi: "10.1038/s41467-024-48981-z" + licence: ["BSD-3-Clause"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - patches: + type: directory + description: Directory containing per-patch subdirectories and a patch_grid.json file. + pattern: "patches" + +output: + xr_polygons_transcript: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "output/xr-cell-polygons.geojson": + type: file + description: Stitched cell polygons in GeoJSON format compatible with XeniumRanger. + pattern: "output/xr-cell-polygons.geojson" + ontologies: [] + - "output/xr-transcript-metadata.csv": + type: file + description: Per-transcript cell assignments and noise flags after stitching. + pattern: "output/xr-transcript-metadata.csv" + ontologies: [] + versions_python: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "python3 --version | sed 's/Python //'": + type: eval + description: The expression to obtain the version of the tool + versions_sopa: + - - ${task.process}: + type: string + description: The process the versions were collected from + - sopa: + type: string + description: The tool name + - 'python3 -c "import sopa; print(sopa.__version__)"': + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - python: + type: string + description: The tool name + - "python3 --version | sed 's/Python //'": + type: eval + description: The expression to obtain the version of the tool + - - ${task.process}: + type: string + description: The process the versions were collected from + - sopa: + type: string + description: The tool name + - 'python3 -c "import sopa; print(sopa.__version__)"': + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@an-altosian" +maintainers: + - "@an-altosian" diff --git a/modules/nf-core/cellpose/Dockerfile b/modules/nf-core/cellpose/Dockerfile new file mode 100644 index 00000000..34053226 --- /dev/null +++ b/modules/nf-core/cellpose/Dockerfile @@ -0,0 +1,25 @@ +# GPU image for cellpose segmentation +# Base: PyTorch with CUDA 12.4 (consistent CUDA support) +FROM pytorch/pytorch:2.5.1-cuda12.4-cudnn9-runtime + +# Install system dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + procps \ + libgl1 \ + libglib2.0-0 \ + && rm -rf /var/lib/apt/lists/* + +# Upgrade pip +RUN pip install --no-cache-dir --upgrade pip + +# Install cellpose and its dependencies +RUN pip install --no-cache-dir \ + cellpose==4.0.8 \ + numpy \ + scipy \ + matplotlib \ + scikit-image \ + opencv-python-headless + +# Set default shell +CMD ["/bin/bash"] diff --git a/modules/nf-core/cellpose/cellpose.diff b/modules/nf-core/cellpose/cellpose.diff new file mode 100644 index 00000000..f04e6f11 --- /dev/null +++ b/modules/nf-core/cellpose/cellpose.diff @@ -0,0 +1,32 @@ +Changes in component 'nf-core/cellpose' +'modules/nf-core/cellpose/environment.yml' is unchanged +'modules/nf-core/cellpose/meta.yml' is unchanged +'modules/nf-core/cellpose/tests/main.nf.test' is unchanged +'modules/nf-core/cellpose/tests/main.nf.test.snap' is unchanged +'modules/nf-core/cellpose/tests/nextflow_wflows.config' is unchanged +Changes in 'cellpose/main.nf': +--- modules/nf-core/cellpose/main.nf ++++ modules/nf-core/cellpose/main.nf +@@ -1,7 +1,7 @@ + process CELLPOSE { + tag "${meta.id}" +- label 'process_medium' +- label 'process_gpu' ++ label 'process_high' ++ label 'process_gpu_single' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? +@@ -45,6 +45,11 @@ + ${model_command} \\ + ${args} + ++ # Fail fast if cellpose detected zero cells ++ if grep -q "No cell pixels found" .cellpose/run.log 2>/dev/null; then ++ echo "ERROR: cellpose detected 0 cells" >&2; exit 1 ++ fi ++ + mkdir -p ${prefix} + mv *masks.tif ${prefix}/ + mv *flows.tif ${prefix}/ 2>/dev/null || true +************************************************************ diff --git a/modules/nf-core/cellpose/environment.yml b/modules/nf-core/cellpose/environment.yml new file mode 100644 index 00000000..e22d2601 --- /dev/null +++ b/modules/nf-core/cellpose/environment.yml @@ -0,0 +1,9 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - conda-forge::python=3.12.12 + - pip: + - cellpose==4.0.9 diff --git a/modules/nf-core/cellpose/main.nf b/modules/nf-core/cellpose/main.nf new file mode 100644 index 00000000..57fda819 --- /dev/null +++ b/modules/nf-core/cellpose/main.nf @@ -0,0 +1,68 @@ +process CELLPOSE { + tag "${meta.id}" + label 'process_high' + label 'process_gpu_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/cb/cb670191b7ae1a9fd5449746453916c7014b9ea622942ca76a7cb40da7deee46/data' : + 'community.wave.seqera.io/library/python_pip_cellpose:fdf7a8c3a305a26e' }" + + input: + tuple val(meta), path(image) + path(model) + + output: + tuple val(meta), path("${prefix}/*masks.tif"), emit: mask + tuple val(meta), path("${prefix}/*flows.tif"), emit: flows, optional: true + tuple val(meta), path("${prefix}/*seg.npy"), emit: cells, optional: true + tuple val("${task.process}"), val('cellpose'), eval("cellpose --version | sed -n 's/cellpose version:[[:space:]]*//p' | tr -d '[:space:]'"), topic: versions, emit: versions_cellpose + tuple val("${task.process}"), val('python'), eval("cellpose --version | sed -n 's/python version:[[:space:]]*//p' | tr -d '[:space:]'"), topic: versions, emit: versions_python + tuple val("${task.process}"), val('torch'), eval("cellpose --version | sed -n 's/torch version:[[:space:]]*//p' | tr -d '[:space:]'"), topic: versions, emit: versions_torch + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def model_command = model ? "--pretrained_model ${model}" : "" + def gpu_flag = task.accelerator ? "--use_gpu" : "" + prefix = task.ext.prefix ?: "${meta.id}" + """ + export OMP_NUM_THREADS=${task.cpus} + export MKL_NUM_THREADS=${task.cpus} + # Container runs as root with HOME=/ which is not writable + export HOME=\$PWD + export MPLCONFIGDIR=\$PWD/.matplotlib + export CELLPOSE_LOCAL_MODELS_PATH=\$PWD/.cellpose + mkdir -p \$MPLCONFIGDIR \$CELLPOSE_LOCAL_MODELS_PATH + + cellpose \\ + --image_path ${image} \\ + --save_tif \\ + --verbose \\ + ${gpu_flag} \\ + ${model_command} \\ + ${args} + + # Fail fast if cellpose detected zero cells + if grep -q "No cell pixels found" .cellpose/run.log 2>/dev/null; then + echo "ERROR: cellpose detected 0 cells" >&2; exit 1 + fi + + mkdir -p ${prefix} + mv *masks.tif ${prefix}/ + mv *flows.tif ${prefix}/ 2>/dev/null || true + mv *seg.npy ${prefix}/ 2>/dev/null || true + """ + + stub: + def name = image.name + def base = name.lastIndexOf('.') != -1 ? name[0..name.lastIndexOf('.') - 1] : name + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p ${prefix} + touch ${prefix}/${base}_cp_masks.tif + """ +} diff --git a/modules/nf-core/cellpose/meta.yml b/modules/nf-core/cellpose/meta.yml new file mode 100644 index 00000000..cb7a89a7 --- /dev/null +++ b/modules/nf-core/cellpose/meta.yml @@ -0,0 +1,158 @@ +name: "cellpose" +description: cellpose segments cells in images using GPU-accelerated deep learning +keywords: + - segmentation + - image + - cellpose + - gpu + - spatial-transcriptomics +tools: + - "cellpose": + description: "cellpose is an anatomical segmentation algorithm written in Python + 3 by Carsen Stringer and Marius Pachitariu" + homepage: "https://github.com/MouseLand/cellpose" + documentation: "https://cellpose.readthedocs.io/en/latest/command.html" + tool_dev_url: "https://github.com/MouseLand/cellpose" + doi: 10.1038/s41592-022-01663-4 + licence: ["BSD 3-Clause"] + identifier: biotools:cellpose +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + (sample id) + - image: + type: file + description: tif file ready for segmentation + pattern: "*.{tif,tiff}" + ontologies: [] + - model: + type: file + description: | + Optional custom cellpose model file. When provided, passed as + --pretrained_model to cellpose. Pass [] (empty list) to use the + default model (cpsam in cellpose 4). + pattern: "*" + ontologies: [] +output: + mask: + - - meta: + type: map + description: | + Groovy Map containing sample information + [sample id] + - "${prefix}/*masks.tif": + type: file + description: labelled mask output from cellpose in tif format + pattern: "${prefix}/*masks.tif" + ontologies: [] + flows: + - - meta: + type: map + description: | + Groovy Map containing sample information + [sample id] + - "${prefix}/*flows.tif": + type: file + description: cell flow output from cellpose + pattern: "${prefix}/*flows.tif" + ontologies: [] + cells: + - - meta: + type: map + description: | + Groovy Map containing sample information + [sample id] + - "${prefix}/*seg.npy": + type: file + description: numpy array with cell segmentation data + pattern: "${prefix}/*seg.npy" + ontologies: [] + versions_cellpose: + - - ${task.process}: + type: string + description: The name of the process + - cellpose: + type: string + description: The name of the tool + - "cellpose --version | sed -n 's/cellpose version:[[:space:]]*//p' | tr -d '[:space:]'": + type: eval + description: The expression to obtain the version of the tool + versions_python: + - - ${task.process}: + type: string + description: The name of the process + - python: + type: string + description: The name of the tool + - "cellpose --version | sed -n 's/python version:[[:space:]]*//p' | tr -d '[:space:]'": + type: eval + description: The expression to obtain the version of the tool + versions_torch: + - - ${task.process}: + type: string + description: The name of the process + - torch: + type: string + description: The name of the tool + - "cellpose --version | sed -n 's/torch version:[[:space:]]*//p' | tr -d '[:space:]'": + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - cellpose: + type: string + description: The name of the tool + - "cellpose --version | sed -n 's/cellpose version:[[:space:]]*//p' | tr -d '[:space:]'": + type: eval + description: The expression to obtain the version of the tool + - - ${task.process}: + type: string + description: The name of the process + - python: + type: string + description: The name of the tool + - "cellpose --version | sed -n 's/python version:[[:space:]]*//p' | tr -d '[:space:]'": + type: eval + description: The expression to obtain the version of the tool + - - ${task.process}: + type: string + description: The name of the process + - torch: + type: string + description: The name of the tool + - "cellpose --version | sed -n 's/torch version:[[:space:]]*//p' | tr -d '[:space:]'": + type: eval + description: The expression to obtain the version of the tool +notes: | + When `accelerator` is set (e.g. `accelerator = 1`), the module + automatically passes `--use_gpu` to cellpose. The container (built via Seqera + Containers) includes PyTorch 2.10.0 with CUDA 12.8 and falls back to CPU + automatically when no GPU is available. Use the `process_gpu` label to request + GPU resources from your executor. When running with conda/mamba, GPU support + depends on having a CUDA-enabled PyTorch installation in your environment. + + Model selection via the model input channel: + - Custom model file: file("/path/to/model") + - Default (cpsam): [] + + Additional cellpose CLI arguments can be passed via `task.ext.args`: + ext.args = '--diameter 30 --flow_threshold 0.4 --cellprob_threshold 0' + + Deprecated in cellpose 4.0.1+: `--chan`, `--chan2`, `--invert`, `--all_channels`, + `--diam_mean`, `--pretrained_model_ortho`. Do not pass these via ext.args. + + Model weights are not bundled in the container. Cellpose downloads them on first + use to `$CELLPOSE_LOCAL_MODELS_PATH` (set to the work directory). +authors: + - "@josenimo" + - "@FloWuenne" + - "@dongzehe" +maintainers: + - "@josenimo" + - "@FloWuenne" + - "@kbestak" diff --git a/modules/nf-core/cellpose/tests/main.nf.test b/modules/nf-core/cellpose/tests/main.nf.test new file mode 100644 index 00000000..6a7688ba --- /dev/null +++ b/modules/nf-core/cellpose/tests/main.nf.test @@ -0,0 +1,63 @@ +nextflow_process { + + name "Test Process CELLPOSE" + script "../main.nf" + process "CELLPOSE" + + tag "modules" + tag "modules_nfcore" + tag "cellpose" + + test("cellpose - with flows, no model") { + + when { + config "./nextflow_wflows.config" + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'imaging/segmentation/cycif_tonsil_registered.ome.tif', checkIfExists: true) + ] + input[1] = [] + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out.mask).match("mask") }, + { assert snapshot(process.out.flows).match("flows") }, + { assert snapshot(process.out.versions).match("versions") } + ) + } + + } + + + test("cellpose - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'imaging/segmentation/cycif_tonsil_registered.ome.tif', checkIfExists: true) + ] + input[1] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/nf-core/cellpose/tests/main.nf.test.snap b/modules/nf-core/cellpose/tests/main.nf.test.snap new file mode 100644 index 00000000..e76ca251 --- /dev/null +++ b/modules/nf-core/cellpose/tests/main.nf.test.snap @@ -0,0 +1,82 @@ +{ + "flows": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-07-04T13:21:35.054592365" + }, + "versions": { + "content": [ + [ + "versions.yml:md5,398393e73a80fc622873765256d5ec79" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-07-04T13:21:35.145644688" + }, + "cellpose - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "cycif_tonsil_registered.ome_cp_masks.tif:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + + ], + "2": [ + "versions.yml:md5,398393e73a80fc622873765256d5ec79" + ], + "flows": [ + + ], + "mask": [ + [ + { + "id": "test" + }, + "cycif_tonsil_registered.ome_cp_masks.tif:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,398393e73a80fc622873765256d5ec79" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-07-04T13:21:52.227498747" + }, + "mask": { + "content": [ + [ + [ + { + "id": "test" + }, + "cycif_tonsil_registered.ome_cp_masks.tif:md5,b151d6718fcb770b2fa3989da632d96e" + ] + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-07-04T13:21:34.95450341" + } +} \ No newline at end of file diff --git a/modules/nf-core/cellpose/tests/nextflow_wflows.config b/modules/nf-core/cellpose/tests/nextflow_wflows.config new file mode 100644 index 00000000..773c97bd --- /dev/null +++ b/modules/nf-core/cellpose/tests/nextflow_wflows.config @@ -0,0 +1,5 @@ +process { + withName: "CELLPOSE" { + ext.args = '--pretrained_model nuclei --diameter 9 --channel_axis 0 --no_npy --save_flows' + } +} 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 deleted file mode 100644 index 7770ccd5..00000000 --- a/modules/nf-core/fastqc/.conda-lock/linux_amd64-bd-5cb1a2fa2f18c7c2_1.txt +++ /dev/null @@ -1,822 +0,0 @@ - -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 deleted file mode 100644 index cdc434ca..00000000 --- a/modules/nf-core/fastqc/.conda-lock/linux_arm64-bd-e455e32f745abe68_1.txt +++ /dev/null @@ -1,769 +0,0 @@ - -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 deleted file mode 100644 index 10851264..00000000 --- a/modules/nf-core/fastqc/main.nf +++ /dev/null @@ -1,57 +0,0 @@ -process FASTQC { - tag "${meta.id}" - label 'process_low' - - conda "${moduleDir}/environment.yml" - 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, stageAs: '?/*') - - output: - tuple val(meta), path("*.html"), emit: html - 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}" - // 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(' ') - - // 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.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 - [ -f "\${new_name}" ] || ln -s \$old_name \$new_name - done - - fastqc \\ - ${args} \\ - --threads ${task.cpus} \\ - ${fastqc_memory_arg} \\ - ${renamed_files} - """ - - stub: - def prefix = task.ext.prefix ?: "${meta.id}" - """ - touch ${prefix}.html - touch ${prefix}.zip - """ -} diff --git a/modules/nf-core/fastqc/meta.yml b/modules/nf-core/fastqc/meta.yml deleted file mode 100644 index 2f6cfef6..00000000 --- a/modules/nf-core/fastqc/meta.yml +++ /dev/null @@ -1,111 +0,0 @@ -name: fastqc -description: Run FastQC on sequenced reads -keywords: - - quality control - - qc - - adapters - - fastq -tools: - - fastqc: - description: | - FastQC gives general quality metrics about your reads. - It provides information about the quality score distribution - across your reads, the per base sequence content (%A/C/G/T). - - You get information about adapter contamination and other - overrepresented sequences. - homepage: https://www.bioinformatics.babraham.ac.uk/projects/fastqc/ - documentation: https://www.bioinformatics.babraham.ac.uk/projects/fastqc/Help/ - licence: ["GPL-2.0-only"] - identifier: biotools:fastqc -input: - - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - reads: - type: file - description: | - List of input FastQ files of size 1 and 2 for single-end and paired-end data, - respectively. - ontologies: [] -output: - html: - - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - "*.html": - type: file - description: FastQC report - pattern: "*_{fastqc.html}" - ontologies: [] - zip: - - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - "*.zip": - type: file - 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: - - - ${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" - - "@ewels" - - "@FelixKrueger" -maintainers: - - "@drpatelh" - - "@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 deleted file mode 100644 index 66c44da9..00000000 --- a/modules/nf-core/fastqc/tests/main.nf.test +++ /dev/null @@ -1,309 +0,0 @@ -nextflow_process { - - name "Test Process FASTQC" - script "../main.nf" - process "FASTQC" - - tag "modules" - tag "modules_nfcore" - tag "fastqc" - - test("sarscov2 single-end [fastq]") { - - when { - process { - """ - input[0] = Channel.of([ - [ id: 'test', single_end:true ], - [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ] - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - // NOTE The report contains the date inside it, which means that the md5sum is stable per day, but not longer than that. So you can't md5sum it. - // looks like this:
Mon 2 Oct 2023
test.gz
- // https://github.com/nf-core/modules/pull/3903#issuecomment-1743620039 - { 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(sanitizeOutput(process.out).findAll { key, val -> key != 'html' && key != 'zip' }).match() } - ) - } - } - - test("sarscov2 paired-end [fastq]") { - - when { - process { - """ - input[0] = Channel.of([ - [id: 'test', single_end: false], // meta map - [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) ] - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert process.out.html[0][1][0] ==~ ".*/test_1_fastqc.html" }, - { assert process.out.html[0][1][1] ==~ ".*/test_2_fastqc.html" }, - { assert process.out.zip[0][1][0] ==~ ".*/test_1_fastqc.zip" }, - { 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(sanitizeOutput(process.out).findAll { key, val -> key != 'html' && key != 'zip' }).match() } - ) - } - } - - test("sarscov2 interleaved [fastq]") { - - when { - process { - """ - input[0] = Channel.of([ - [id: 'test', single_end: false], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_interleaved.fastq.gz', checkIfExists: true) - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { 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(sanitizeOutput(process.out).findAll { key, val -> key != 'html' && key != 'zip' }).match() } - ) - } - } - - test("sarscov2 paired-end [bam]") { - - when { - process { - """ - input[0] = Channel.of([ - [id: 'test', single_end: false], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { 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(sanitizeOutput(process.out).findAll { key, val -> key != 'html' && key != 'zip' }).match() } - ) - } - } - - test("sarscov2 multiple [fastq]") { - - when { - process { - """ - input[0] = Channel.of([ - [id: 'test', single_end: false], // meta map - [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_1.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_2.fastq.gz', checkIfExists: true) ] - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert process.out.html[0][1][0] ==~ ".*/test_1_fastqc.html" }, - { assert process.out.html[0][1][1] ==~ ".*/test_2_fastqc.html" }, - { assert process.out.html[0][1][2] ==~ ".*/test_3_fastqc.html" }, - { assert process.out.html[0][1][3] ==~ ".*/test_4_fastqc.html" }, - { assert process.out.zip[0][1][0] ==~ ".*/test_1_fastqc.zip" }, - { assert process.out.zip[0][1][1] ==~ ".*/test_2_fastqc.zip" }, - { assert process.out.zip[0][1][2] ==~ ".*/test_3_fastqc.zip" }, - { assert process.out.zip[0][1][3] ==~ ".*/test_4_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 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(sanitizeOutput(process.out).findAll { key, val -> key != 'html' && key != 'zip' }).match() } - ) - } - } - - test("sarscov2 custom_prefix") { - - when { - process { - """ - input[0] = Channel.of([ - [ id:'mysample', single_end:true ], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { 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(sanitizeOutput(process.out).findAll { key, val -> key != 'html' && key != 'zip' }).match() } - ) - } - } - - test("sarscov2 single-end [fastq] - stub") { - - options "-stub" - when { - process { - """ - input[0] = Channel.of([ - [ id: 'test', single_end:true ], - [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ] - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - } - - test("sarscov2 paired-end [fastq] - stub") { - - options "-stub" - when { - process { - """ - input[0] = Channel.of([ - [id: 'test', single_end: false], // meta map - [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) ] - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - } - - test("sarscov2 interleaved [fastq] - stub") { - - options "-stub" - when { - process { - """ - input[0] = Channel.of([ - [id: 'test', single_end: false], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_interleaved.fastq.gz', checkIfExists: true) - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - } - - test("sarscov2 paired-end [bam] - stub") { - - options "-stub" - when { - process { - """ - input[0] = Channel.of([ - [id: 'test', single_end: false], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - } - - test("sarscov2 multiple [fastq] - stub") { - - options "-stub" - when { - process { - """ - input[0] = Channel.of([ - [id: 'test', single_end: false], // meta map - [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_1.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_2.fastq.gz', checkIfExists: true) ] - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - } - - test("sarscov2 custom_prefix - stub") { - - options "-stub" - when { - process { - """ - input[0] = Channel.of([ - [ id:'mysample', single_end:true ], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - } -} diff --git a/modules/nf-core/fastqc/tests/main.nf.test.snap b/modules/nf-core/fastqc/tests/main.nf.test.snap deleted file mode 100644 index c8ee120f..00000000 --- a/modules/nf-core/fastqc/tests/main.nf.test.snap +++ /dev/null @@ -1,476 +0,0 @@ -{ - "sarscov2 custom_prefix": { - "content": [ - { - "versions_fastqc": [ - [ - "FASTQC", - "fastqc", - "0.12.1" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.10.0" - }, - "timestamp": "2025-10-28T16:39:14.518503" - }, - "sarscov2 single-end [fastq] - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test", - "single_end": true - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test", - "single_end": true - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - [ - "FASTQC", - "fastqc", - "0.12.1" - ] - ], - "html": [ - [ - { - "id": "test", - "single_end": true - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions_fastqc": [ - [ - "FASTQC", - "fastqc", - "0.12.1" - ] - ], - "zip": [ - [ - { - "id": "test", - "single_end": true - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.10.0" - }, - "timestamp": "2025-10-28T16:39:19.309008" - }, - "sarscov2 custom_prefix - stub": { - "content": [ - { - "0": [ - [ - { - "id": "mysample", - "single_end": true - }, - "mysample.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "mysample", - "single_end": true - }, - "mysample.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - [ - "FASTQC", - "fastqc", - "0.12.1" - ] - ], - "html": [ - [ - { - "id": "mysample", - "single_end": true - }, - "mysample.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions_fastqc": [ - [ - "FASTQC", - "fastqc", - "0.12.1" - ] - ], - "zip": [ - [ - { - "id": "mysample", - "single_end": true - }, - "mysample.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.10.0" - }, - "timestamp": "2025-10-28T16:39:44.94888" - }, - "sarscov2 interleaved [fastq]": { - "content": [ - { - "versions_fastqc": [ - [ - "FASTQC", - "fastqc", - "0.12.1" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.10.0" - }, - "timestamp": "2025-10-28T16:38:45.168496" - }, - "sarscov2 paired-end [bam]": { - "content": [ - { - "versions_fastqc": [ - [ - "FASTQC", - "fastqc", - "0.12.1" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.10.0" - }, - "timestamp": "2025-10-28T16:38:53.268919" - }, - "sarscov2 multiple [fastq]": { - "content": [ - { - "versions_fastqc": [ - [ - "FASTQC", - "fastqc", - "0.12.1" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.10.0" - }, - "timestamp": "2025-10-28T16:39:05.050305" - }, - "sarscov2 paired-end [fastq]": { - "content": [ - { - "versions_fastqc": [ - [ - "FASTQC", - "fastqc", - "0.12.1" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.10.0" - }, - "timestamp": "2025-10-28T16:38:37.2373" - }, - "sarscov2 paired-end [fastq] - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - [ - "FASTQC", - "fastqc", - "0.12.1" - ] - ], - "html": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions_fastqc": [ - [ - "FASTQC", - "fastqc", - "0.12.1" - ] - ], - "zip": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.10.0" - }, - "timestamp": "2025-10-28T16:39:24.450398" - }, - "sarscov2 multiple [fastq] - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - [ - "FASTQC", - "fastqc", - "0.12.1" - ] - ], - "html": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions_fastqc": [ - [ - "FASTQC", - "fastqc", - "0.12.1" - ] - ], - "zip": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.10.0" - }, - "timestamp": "2025-10-28T16:39:39.758762" - }, - "sarscov2 single-end [fastq]": { - "content": [ - { - "versions_fastqc": [ - [ - "FASTQC", - "fastqc", - "0.12.1" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.10.0" - }, - "timestamp": "2025-10-28T16:38:29.555068" - }, - "sarscov2 interleaved [fastq] - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - [ - "FASTQC", - "fastqc", - "0.12.1" - ] - ], - "html": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions_fastqc": [ - [ - "FASTQC", - "fastqc", - "0.12.1" - ] - ], - "zip": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.10.0" - }, - "timestamp": "2025-10-28T16:39:29.193136" - }, - "sarscov2 paired-end [bam] - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - [ - "FASTQC", - "fastqc", - "0.12.1" - ] - ], - "html": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions_fastqc": [ - [ - "FASTQC", - "fastqc", - "0.12.1" - ] - ], - "zip": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.10.0" - }, - "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 deleted file mode 100644 index 76190304..00000000 --- a/modules/nf-core/multiqc/.conda-lock/linux_amd64-bd-c1f4a7982b743963_1.txt +++ /dev/null @@ -1,1552 +0,0 @@ - -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 deleted file mode 100644 index a55a4d49..00000000 --- a/modules/nf-core/multiqc/.conda-lock/linux_amd64-bd-db7c73dae76bc9e6_1.txt +++ /dev/null @@ -1,126 +0,0 @@ - -# 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 deleted file mode 100644 index a58231a0..00000000 --- a/modules/nf-core/multiqc/.conda-lock/linux_arm64-bd-40bf3b435e89dc22_1.txt +++ /dev/null @@ -1,1502 +0,0 @@ - -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 deleted file mode 100644 index f787dbe1..00000000 --- a/modules/nf-core/multiqc/.conda-lock/linux_arm64-bd-d167b8012595a136_1.txt +++ /dev/null @@ -1,125 +0,0 @@ - -# 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 37e7612d..009874d4 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.34 + - bioconda::multiqc=1.33 diff --git a/modules/nf-core/multiqc/main.nf b/modules/nf-core/multiqc/main.nf index e80e8cd8..ae8f5c86 100644 --- a/modules/nf-core/multiqc/main.nf +++ b/modules/nf-core/multiqc/main.nf @@ -3,9 +3,7 @@ process MULTIQC { label 'process_single' conda "${moduleDir}/environment.yml" - 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'}" + container "community.wave.seqera.io/library/multiqc-xenium-extra_multiqc_polars_scanpy_scipy:4e27199c6ca05c8b" input: tuple val(meta), path(multiqc_files, stageAs: "?/*"), path(multiqc_config, stageAs: "?/*"), path(multiqc_logo), path(replace_names), path(sample_names) diff --git a/modules/nf-core/multiqc/meta.yml b/modules/nf-core/multiqc/meta.yml index 2facc627..ef434a9a 100644 --- a/modules/nf-core/multiqc/meta.yml +++ b/modules/nf-core/multiqc/meta.yml @@ -110,24 +110,24 @@ maintainers: containers: conda: linux/amd64: - lock_file: modules/nf-core/multiqc/.conda-lock/linux_amd64-bd-db7c73dae76bc9e6_1.txt + lock_file: https://wave.seqera.io/v1alpha1/builds/bd-ee7739d47738383b_1/condalock linux/arm64: - lock_file: modules/nf-core/multiqc/.conda-lock/linux_arm64-bd-d167b8012595a136_1.txt + lock_file: https://wave.seqera.io/v1alpha1/builds/bd-58d7dee710ab3aa8_1/condalock docker: linux/amd64: - name: community.wave.seqera.io/library/multiqc:1.34--db7c73dae76bc9e6 - build_id: bd-db7c73dae76bc9e6_1 - scan_id: sc-66fc7138dbf1cf48_1 + build_id: bd-ee7739d47738383b_1 + name: community.wave.seqera.io/library/multiqc:1.33--ee7739d47738383b + scanId: sc-6ddec592dcadd583_4 linux/arm64: - name: community.wave.seqera.io/library/multiqc:1.34--d167b8012595a136 - build_id: bd-d167b8012595a136_1 - scan_id: sc-ac701dfa631a2af9_1 + build_id: bd-58d7dee710ab3aa8_1 + name: community.wave.seqera.io/library/multiqc:1.33--58d7dee710ab3aa8 + scanId: sc-a04c42273e34c55c_2 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 + build_id: bd-e3576ddf588fa00d_1 + https: https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/34/34e733a9ae16a27e80fe00f863ea1479c96416017f24a907996126283e7ecd4d/data + name: oras://community.wave.seqera.io/library/multiqc:1.33--e3576ddf588fa00d 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 + build_id: bd-2537ca5f8445e3c2_1 + https: https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/78/78b89e91d89e9cc99ad5ade5be311f347838cb2acbfb4f13bc343b170be09ce4/data + name: oras://community.wave.seqera.io/library/multiqc:1.33--2537ca5f8445e3c2 diff --git a/modules/nf-core/multiqc/multiqc.diff b/modules/nf-core/multiqc/multiqc.diff new file mode 100644 index 00000000..a208f7c0 --- /dev/null +++ b/modules/nf-core/multiqc/multiqc.diff @@ -0,0 +1,23 @@ +Changes in component 'nf-core/multiqc' +Changes in 'multiqc/main.nf': +--- modules/nf-core/multiqc/main.nf ++++ modules/nf-core/multiqc/main.nf +@@ -3,9 +3,7 @@ + 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/34/34e733a9ae16a27e80fe00f863ea1479c96416017f24a907996126283e7ecd4d/data' +- : 'community.wave.seqera.io/library/multiqc:1.33--ee7739d47738383b'}" ++ container "community.wave.seqera.io/library/multiqc-xenium-extra_multiqc_polars_scanpy_scipy:4e27199c6ca05c8b" + + input: + tuple val(meta), path(multiqc_files, stageAs: "?/*"), path(multiqc_config, stageAs: "?/*"), path(multiqc_logo), path(replace_names), path(sample_names) + +'modules/nf-core/multiqc/environment.yml' is unchanged +'modules/nf-core/multiqc/meta.yml' is unchanged +'modules/nf-core/multiqc/tests/nextflow.config' is unchanged +'modules/nf-core/multiqc/tests/main.nf.test' is unchanged +'modules/nf-core/multiqc/tests/main.nf.test.snap' is unchanged +'modules/nf-core/multiqc/tests/custom_prefix.config' is unchanged +************************************************************ diff --git a/modules/nf-core/multiqc/tests/main.nf.test.snap b/modules/nf-core/multiqc/tests/main.nf.test.snap index 7c2f370f..3bfc524f 100644 --- a/modules/nf-core/multiqc/tests/main.nf.test.snap +++ b/modules/nf-core/multiqc/tests/main.nf.test.snap @@ -81,7 +81,7 @@ [ "MULTIQC", "multiqc", - "1.34" + "1.33" ] ] } @@ -175,7 +175,7 @@ [ "MULTIQC", "multiqc", - "1.34" + "1.33" ] ] } @@ -221,7 +221,7 @@ [ "MULTIQC", "multiqc", - "1.34" + "1.33" ] ] } @@ -314,7 +314,7 @@ [ "MULTIQC", "multiqc", - "1.34" + "1.33" ] ] } @@ -408,7 +408,7 @@ [ "MULTIQC", "multiqc", - "1.34" + "1.33" ] ] } diff --git a/modules/nf-core/opt/flip/main.nf b/modules/nf-core/opt/flip/main.nf new file mode 100644 index 00000000..66be07d0 --- /dev/null +++ b/modules/nf-core/opt/flip/main.nf @@ -0,0 +1,60 @@ +process OPT_FLIP { + tag "$meta.id" + label 'process_high' + + container "khersameesh24/opt:v0.0.1" + + input: + tuple val(meta), path(probes_fasta) + tuple val(meta2), path(ref_annot_gff), path(ref_annot_fa) + + output: + tuple val(meta), path("${prefix}/fwd_oriented.fa"), emit: fwd_oriented_fa + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "OPT_FLIP module does not support Conda. Please use Docker / Singularity / Podman instead." + } + + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + + """ + opt \\ + -o ${prefix} \\ + -p ${task.cpus} \\ + flip \\ + -q ${probes_fasta} \\ + -a ${ref_annot_gff} \\ + -t ${ref_annot_fa} \\ + ${args} + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + opt: \$(opt --version) + END_VERSIONS + """ + + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "OPT_FLIP module does not support Conda. Please use Docker / Singularity / Podman instead." + } + + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p ${prefix} + touch "${prefix}/fwd_oriented.fa" + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + opt: \$(opt --version) + END_VERSIONS + """ +} diff --git a/modules/nf-core/opt/flip/meta.yml b/modules/nf-core/opt/flip/meta.yml new file mode 100644 index 00000000..f0e4c57c --- /dev/null +++ b/modules/nf-core/opt/flip/meta.yml @@ -0,0 +1,68 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "opt_flip" +description: "flip corrects probes that are aligning to the opposite strand of their intended target genes by reverse complementing them" +keywords: + - opt + - opt flip + - transcripts + - off-target probes + - align probes +tools: + - "opt": + description: "opt is a simple program that aligns probe sequences to transcript sequences to detect potential off-target probe activity" + homepage: "https://github.com/JEFworks-Lab/off-target-probe-tracker" + documentation: "https://github.com/JEFworks-Lab/off-target-probe-tracker/blob/main/README.md" + tool_dev_url: "https://github.com/JEFworks-Lab/off-target-probe-tracker" + licence: [GPL-3.0 license] + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information of the probe panel sequences used for the xenium experiment + e.g. `[ id:'breast_cancer_probe_panel_sequences' ]` + - probes_fasta: + type: file + description: Fasta file for the probe sequences used in the xenium experiment + pattern: "*.fasta" + ontologies: [] + - - meta2: + type: map + description: | + Groovy Map containing the information of the genomic features and fasta files used as references + e.g. `[ id:'gencode_references' ]` + - ref_annot_gff: + type: file + description: Reference annotations in gff format + pattern: "*.gff" + ontologies: [] + - ref_annot_fa: + type: file + description: Reference annotations in fasta format + pattern: "*.fa" + ontologies: [] + +output: + fwd_oriented_fa: + - - meta: + type: map + description: | + Groovy Map containing information of the forward oriented fasta generated with the probes panel sequences 'opt flip' + e.g. `[ id:'breast_cancer_probe_panel_sequences' ]` + - "${meta.id}/fwd_oriented.fa": + type: file + description: The forward oriented fasta file + pattern: "*.fa" + ontologies: [] + versions: + - "versions.yml": + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: "http://edamontology.org/format_3750" # YAML + +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/modules/nf-core/opt/flip/opt-flip.diff b/modules/nf-core/opt/flip/opt-flip.diff new file mode 100644 index 00000000..09c6098d --- /dev/null +++ b/modules/nf-core/opt/flip/opt-flip.diff @@ -0,0 +1,48 @@ +Changes in component 'nf-core/opt/flip' +Changes in 'opt/flip/main.nf': +--- modules/nf-core/opt/flip/main.nf ++++ modules/nf-core/opt/flip/main.nf +@@ -9,8 +9,8 @@ + tuple val(meta2), path(ref_annot_gff), path(ref_annot_fa) + + output: +- tuple val(meta), path("${meta.id}/fwd_oriented.fa"), emit: fwd_oriented_fa +- path "versions.yml" , emit: versions ++ tuple val(meta), path("${prefix}/fwd_oriented.fa"), emit: fwd_oriented_fa ++ path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when +@@ -20,9 +20,10 @@ + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "OPT_FLIP module does not support Conda. Please use Docker / Singularity / Podman instead." + } +- def args = task.ext.args ?: '' +- def prefix = task.ext.prefix ?: "${meta.id}" + ++ def args = task.ext.args ?: '' ++ prefix = task.ext.prefix ?: "${meta.id}" ++ + """ + opt \\ + -o ${prefix} \\ +@@ -40,8 +41,13 @@ + """ + + stub: +- def prefix = task.ext.prefix ?: "${meta.id}" ++ // Exit if running this module with -profile conda / -profile mamba ++ if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { ++ error "OPT_FLIP module does not support Conda. Please use Docker / Singularity / Podman instead." ++ } + ++ prefix = task.ext.prefix ?: "${meta.id}" ++ + """ + mkdir -p ${prefix} + touch "${prefix}/fwd_oriented.fa" + +'modules/nf-core/opt/flip/meta.yml' is unchanged +'modules/nf-core/opt/flip/tests/main.nf.test' is unchanged +'modules/nf-core/opt/flip/tests/main.nf.test.snap' is unchanged +************************************************************ diff --git a/modules/nf-core/opt/flip/tests/main.nf.test b/modules/nf-core/opt/flip/tests/main.nf.test new file mode 100644 index 00000000..77fd9ef4 --- /dev/null +++ b/modules/nf-core/opt/flip/tests/main.nf.test @@ -0,0 +1,61 @@ +nextflow_process { + + name "Test Process OPT_FLIP" + script "../main.nf" + process "OPT_FLIP" + + tag "modules" + tag "modules_nfcore" + tag "opt" + tag "opt/flip" + + test("testrun panel probe sequences") { + + when { + process { + """ + input[0] = [ + [ id:'test_run' ], + file('https://raw.githubusercontent.com/khersameesh24/test-datasets/opt/testdata/panel_probe_sequences.fasta', checkIfExists: true) + ] + input[1] = [ + [ id:'test_run' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.gtf', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("testrun panel probe sequences -stub") { + options "-stub" + when { + process { + """ + input[0] = [ + [ id:'test_run' ], + file('https://raw.githubusercontent.com/khersameesh24/test-datasets/opt/testdata/panel_probe_sequences.fasta', checkIfExists: true) + ] + input[1] = [ + [ id:'test_run' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.gtf', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/modules/nf-core/opt/flip/tests/main.nf.test.snap b/modules/nf-core/opt/flip/tests/main.nf.test.snap new file mode 100644 index 00000000..4bc5b35e --- /dev/null +++ b/modules/nf-core/opt/flip/tests/main.nf.test.snap @@ -0,0 +1,68 @@ +{ + "testrun panel probe sequences -stub": { + "content": [ + { + "0": [ + [ + { + "id": "test_run" + }, + "fwd_oriented.fa:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,57dbc672cf0bf40854c18d241f2d7d2e" + ], + "fwd_oriented_fa": [ + [ + { + "id": "test_run" + }, + "fwd_oriented.fa:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,57dbc672cf0bf40854c18d241f2d7d2e" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-14T12:43:08.105988801" + }, + "testrun panel probe sequences": { + "content": [ + { + "0": [ + [ + { + "id": "test_run" + }, + "fwd_oriented.fa:md5,535289c04851ad94e091ec7c14ff6bcd" + ] + ], + "1": [ + "versions.yml:md5,57dbc672cf0bf40854c18d241f2d7d2e" + ], + "fwd_oriented_fa": [ + [ + { + "id": "test_run" + }, + "fwd_oriented.fa:md5,535289c04851ad94e091ec7c14ff6bcd" + ] + ], + "versions": [ + "versions.yml:md5,57dbc672cf0bf40854c18d241f2d7d2e" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-14T12:47:41.966183182" + } +} \ No newline at end of file diff --git a/modules/nf-core/opt/stat/main.nf b/modules/nf-core/opt/stat/main.nf new file mode 100644 index 00000000..e8de5860 --- /dev/null +++ b/modules/nf-core/opt/stat/main.nf @@ -0,0 +1,60 @@ +process OPT_STAT { + tag "$meta.id" + label 'process_high' + + container "khersameesh24/opt:v0.0.1" + + input: + tuple val(meta), path(probe_targets) + tuple val(meta2), path(fwd_oriented_probes) + path(gene_synonyms) + + output: + tuple val(meta), path("${prefix}/collapsed_summary.tsv"), emit: summary + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "OPT_STAT module does not support Conda. Please use Docker / Singularity / Podman instead." + } + def args = task.ext.args ?: '' + def synonyms = gene_synonyms ? "-s ${gene_synonyms}": "" + prefix = task.ext.prefix ?: "${meta.id}" + + """ + opt \\ + -o ${prefix} \\ + stat \\ + -i ${probe_targets} \\ + -q ${fwd_oriented_probes} \\ + ${synonyms} \\ + ${args} + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + opt: \$(opt --version) + END_VERSIONS + """ + + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "OPT_STAT module does not support Conda. Please use Docker / Singularity / Podman instead." + } + + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p ${prefix} + touch "${prefix}/collapsed_summary.tsv" + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + opt: \$(opt --version) + END_VERSIONS + """ +} diff --git a/modules/nf-core/opt/stat/meta.yml b/modules/nf-core/opt/stat/meta.yml new file mode 100644 index 00000000..ae2d712b --- /dev/null +++ b/modules/nf-core/opt/stat/meta.yml @@ -0,0 +1,72 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "opt_stat" +description: "stat summarizes opt binding predictions" +keywords: + - opt + - opt stat + - transcripts + - binding predictions + - off-target probes + - align probes + - summary stats +tools: + - "opt": + description: "opt is a simple program that aligns probe sequences to transcript + sequences to detect potential off-target probe activity" + homepage: "https://github.com/JEFworks-Lab/off-target-probe-tracker" + documentation: "https://github.com/JEFworks-Lab/off-target-probe-tracker/blob/main/README.md" + tool_dev_url: "https://github.com/JEFworks-Lab/off-target-probe-tracker" + licence: [GPL-3.0 license] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing information of the probe targets generated from the panel sequences with `opt track` + e.g. `[ id:'breast_cancer_probe_panel_sequences' ]` + - probe_targets: + type: file + description: Generated probe targets + pattern: "*.tsv" + ontologies: [] + - - meta2: + type: map + description: | + Groovy Map containing information of the forward oriented fasta generated with the probes panel sequences 'opt flip' + e.g. `[ id:'breast_cancer_probe_panel_sequences' ]` + - fwd_oriented_probes: + type: file + description: The forward oriented fasta file + pattern: "*.fa" + ontologies: [] + - gene_synonyms: + type: file + description: Gene synonyms that may have been counted as off-targets but + simply differ in name (optional input) + pattern: "*.csv" + ontologies: [] +output: + summary: + - - meta: + type: map + description: | + Groovy Map containing summary of the forward oriented probes generated with the panel sequences 'opt flip and track' + e.g. `[ id:'breast_cancer_probe_panel_sequences' ]` + - "${meta.id}/collapsed_summary.tsv": + type: file + description: tsv file containing the summary stats + pattern: "*.tsv" + ontologies: [] + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: "http://edamontology.org/format_3750" # YAML + +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/modules/nf-core/opt/stat/opt-stat.diff b/modules/nf-core/opt/stat/opt-stat.diff new file mode 100644 index 00000000..348e77b5 --- /dev/null +++ b/modules/nf-core/opt/stat/opt-stat.diff @@ -0,0 +1,44 @@ +Changes in component 'nf-core/opt/stat' +Changes in 'opt/stat/main.nf': +--- modules/nf-core/opt/stat/main.nf ++++ modules/nf-core/opt/stat/main.nf +@@ -10,8 +10,8 @@ + path(gene_synonyms) + + output: +- tuple val(meta), path("${meta.id}/collapsed_summary.tsv"), emit: summary +- path "versions.yml" , emit: versions ++ tuple val(meta), path("${prefix}/collapsed_summary.tsv"), emit: summary ++ path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when +@@ -22,8 +22,8 @@ + error "OPT_STAT module does not support Conda. Please use Docker / Singularity / Podman instead." + } + def args = task.ext.args ?: '' +- def prefix = task.ext.prefix ?: "${meta.id}" + def synonyms = gene_synonyms ? "-s ${gene_synonyms}": "" ++ prefix = task.ext.prefix ?: "${meta.id}" + + """ + opt \\ +@@ -41,7 +41,12 @@ + """ + + stub: +- def prefix = task.ext.prefix ?: "${meta.id}" ++ // Exit if running this module with -profile conda / -profile mamba ++ if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { ++ error "OPT_STAT module does not support Conda. Please use Docker / Singularity / Podman instead." ++ } ++ ++ prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p ${prefix} + +'modules/nf-core/opt/stat/meta.yml' is unchanged +'modules/nf-core/opt/stat/tests/main.nf.test' is unchanged +'modules/nf-core/opt/stat/tests/main.nf.test.snap' is unchanged +************************************************************ diff --git a/modules/nf-core/opt/stat/tests/main.nf.test b/modules/nf-core/opt/stat/tests/main.nf.test new file mode 100644 index 00000000..5204a81f --- /dev/null +++ b/modules/nf-core/opt/stat/tests/main.nf.test @@ -0,0 +1,63 @@ +nextflow_process { + + name "Test Process OPT_STAT" + script "../main.nf" + process "OPT_STAT" + + tag "modules" + tag "modules_nfcore" + tag "opt" + tag "opt/stat" + + test("testrun panel probe sequences") { + + when { + process { + """ + input[0] = [ + [ id:'test_run' ], + file('https://raw.githubusercontent.com/khersameesh24/test-datasets/opt/testdata/probe2targets.tsv', checkIfExists: true) + ] + input[1] = [ + [ id:'test_run' ], + file('https://raw.githubusercontent.com/khersameesh24/test-datasets/opt/testdata/fwd_oriented.fa', checkIfExists: true) + ] + input[2] = [] + // input[2] = [file('https://raw.githubusercontent.com/khersameesh24/test-datasets/opt/testdata/gene_synonyms.csv', checkIfExists: true)] + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("testrun panel probe sequences -stub") { + options "-stub" + when { + process { + """ + input[0] = [ + [ id:'test_run' ], + file('https://raw.githubusercontent.com/khersameesh24/test-datasets/opt/testdata/probe2targets.tsv', checkIfExists: true) + ] + input[1] = [ + [ id:'test_run' ], + file('https://raw.githubusercontent.com/khersameesh24/test-datasets/opt/testdata/fwd_oriented.fa', checkIfExists: true) + ] + input[2] = [] + // input[2] = [file('https://raw.githubusercontent.com/khersameesh24/test-datasets/opt/testdata/gene_synonyms.csv', checkIfExists: true)] + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/modules/nf-core/opt/stat/tests/main.nf.test.snap b/modules/nf-core/opt/stat/tests/main.nf.test.snap new file mode 100644 index 00000000..b14671fe --- /dev/null +++ b/modules/nf-core/opt/stat/tests/main.nf.test.snap @@ -0,0 +1,68 @@ +{ + "testrun panel probe sequences -stub": { + "content": [ + { + "0": [ + [ + { + "id": "test_run" + }, + "collapsed_summary.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,a23b08ea3b2da18863a5611dd0adbaa1" + ], + "summary": [ + [ + { + "id": "test_run" + }, + "collapsed_summary.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,a23b08ea3b2da18863a5611dd0adbaa1" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-14T12:23:32.959930982" + }, + "testrun panel probe sequences": { + "content": [ + { + "0": [ + [ + { + "id": "test_run" + }, + "collapsed_summary.tsv:md5,b2884d9c8c89124d3cbbbc1223d81c99" + ] + ], + "1": [ + "versions.yml:md5,a23b08ea3b2da18863a5611dd0adbaa1" + ], + "summary": [ + [ + { + "id": "test_run" + }, + "collapsed_summary.tsv:md5,b2884d9c8c89124d3cbbbc1223d81c99" + ] + ], + "versions": [ + "versions.yml:md5,a23b08ea3b2da18863a5611dd0adbaa1" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-14T12:58:57.17786325" + } +} \ No newline at end of file diff --git a/modules/nf-core/opt/track/main.nf b/modules/nf-core/opt/track/main.nf new file mode 100644 index 00000000..ff92645e --- /dev/null +++ b/modules/nf-core/opt/track/main.nf @@ -0,0 +1,59 @@ +process OPT_TRACK { + tag "$meta.id" + label 'process_high' + + container "khersameesh24/opt:v0.0.1" + + input: + tuple val(meta), path(fwd_oriented_fa) + tuple val(meta2), path(ref_annot_gff), path(ref_annot_fa) + + output: + tuple val(meta), path("${prefix}/probe2targets.tsv"), emit: probes2target + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "OPT_TRACK module does not support Conda. Please use Docker / Singularity / Podman instead." + } + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + + """ + opt \\ + -o ${prefix} \\ + -p ${task.cpus} \\ + track \\ + -q ${fwd_oriented_fa} \\ + -a ${ref_annot_gff} \\ + -t ${ref_annot_fa} \\ + ${args} + + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + opt: \$(opt --version) + END_VERSIONS + """ + + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "OPT_TRACK module does not support Conda. Please use Docker / Singularity / Podman instead." + } + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p ${prefix} + touch "${prefix}/probe2targets.tsv" + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + opt: \$(opt --version) + END_VERSIONS + """ +} diff --git a/modules/nf-core/opt/track/meta.yml b/modules/nf-core/opt/track/meta.yml new file mode 100644 index 00000000..6024d773 --- /dev/null +++ b/modules/nf-core/opt/track/meta.yml @@ -0,0 +1,69 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "opt_track" +description: "track aligns query probe sequences to any target transcriptome" +keywords: + - opt + - opt track + - transcripts + - off-target probes + - align probes + - traget transcriptome +tools: + - "opt": + description: "opt is a simple program that aligns probe sequences to transcript sequences to detect potential off-target probe activity" + homepage: "https://github.com/JEFworks-Lab/off-target-probe-tracker" + documentation: "https://github.com/JEFworks-Lab/off-target-probe-tracker/blob/main/README.md" + tool_dev_url: "https://github.com/JEFworks-Lab/off-target-probe-tracker" + licence: [GPL-3.0 license] + +input: + - - meta: + type: map + description: | + Groovy Map containing information of the forward oriented fasta generated with the probes panel sequences generated with `opt flip` + e.g. `[ id:'breast_cancer_probe_panel_sequences' ]` + - fwd_oriented_fa: + type: file + description: Forward oriented fasta file generated by the opt flip command + pattern: "*.fa" + ontologies: [] + - - meta2: + type: map + description: | + Groovy Map containing the information of the genomic features and fasta files used as references + e.g. `[ id:'gencode_references' ]` + - ref_annot_gff: + type: file + description: Reference annotation in gff format + pattern: "*.gff" + ontologies: [] + - ref_annot_fa: + type: file + description: Reference annotation in fasta format + pattern: "*.fa" + ontologies: [] + +output: + probes2target: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "${meta.id}/probe2targets.tsv": + type: file + description: TSV file containing the gene and transcript information to which each probe aligns + pattern: "*.tsv" + ontologies: [] + versions: + - "versions.yml": + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: "http://edamontology.org/format_3750" # YAML + +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/modules/nf-core/opt/track/opt-track.diff b/modules/nf-core/opt/track/opt-track.diff new file mode 100644 index 00000000..6a0098ad --- /dev/null +++ b/modules/nf-core/opt/track/opt-track.diff @@ -0,0 +1,44 @@ +Changes in component 'nf-core/opt/track' +Changes in 'opt/track/main.nf': +--- modules/nf-core/opt/track/main.nf ++++ modules/nf-core/opt/track/main.nf +@@ -9,8 +9,8 @@ + tuple val(meta2), path(ref_annot_gff), path(ref_annot_fa) + + output: +- tuple val(meta), path("${meta.id}/probe2targets.tsv"), emit: probes2target +- path "versions.yml" , emit: versions ++ tuple val(meta), path("${prefix}/probe2targets.tsv"), emit: probes2target ++ path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when +@@ -21,7 +21,7 @@ + error "OPT_TRACK module does not support Conda. Please use Docker / Singularity / Podman instead." + } + def args = task.ext.args ?: '' +- def prefix = task.ext.prefix ?: "${meta.id}" ++ prefix = task.ext.prefix ?: "${meta.id}" + + """ + opt \\ +@@ -41,8 +41,12 @@ + """ + + stub: +- def prefix = task.ext.prefix ?: "${meta.id}" +- ++ // Exit if running this module with -profile conda / -profile mamba ++ if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { ++ error "OPT_TRACK module does not support Conda. Please use Docker / Singularity / Podman instead." ++ } ++ prefix = task.ext.prefix ?: "${meta.id}" ++ + """ + mkdir -p ${prefix} + touch "${prefix}/probe2targets.tsv" + +'modules/nf-core/opt/track/meta.yml' is unchanged +'modules/nf-core/opt/track/tests/main.nf.test' is unchanged +'modules/nf-core/opt/track/tests/main.nf.test.snap' is unchanged +************************************************************ diff --git a/modules/nf-core/opt/track/tests/main.nf.test b/modules/nf-core/opt/track/tests/main.nf.test new file mode 100644 index 00000000..4fbf9a19 --- /dev/null +++ b/modules/nf-core/opt/track/tests/main.nf.test @@ -0,0 +1,60 @@ +nextflow_process { + + name "Test Process OPT_TRACK" + script "../main.nf" + process "OPT_TRACK" + + tag "modules" + tag "modules_nfcore" + tag "opt" + tag "opt/track" + + test("testrun panel probe sequences") { + + when { + process { + """ + input[0] = [ + [ id:'test_run' ], file('https://raw.githubusercontent.com/khersameesh24/test-datasets/opt/testdata/fwd_oriented.fa', checkIfExists: true) + ] + input[1] = [ + [ id:'test_run' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.gtf', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("testrun panel probe sequences -stub") { + options "-stub" + when { + process { + """ + input[0] = [ + [ id:'test_run' ], file('https://raw.githubusercontent.com/khersameesh24/test-datasets/opt/testdata/fwd_oriented.fa', checkIfExists: true) + ] + + input[1] = [ + [ id:'test_run' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.gtf', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/modules/nf-core/opt/track/tests/main.nf.test.snap b/modules/nf-core/opt/track/tests/main.nf.test.snap new file mode 100644 index 00000000..3dda7a91 --- /dev/null +++ b/modules/nf-core/opt/track/tests/main.nf.test.snap @@ -0,0 +1,68 @@ +{ + "testrun panel probe sequences -stub": { + "content": [ + { + "0": [ + [ + { + "id": "test_run" + }, + "probe2targets.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,daf660f286a817fa0eed7703a5f65706" + ], + "probes2target": [ + [ + { + "id": "test_run" + }, + "probe2targets.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,daf660f286a817fa0eed7703a5f65706" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-14T12:20:29.220245783" + }, + "testrun panel probe sequences": { + "content": [ + { + "0": [ + [ + { + "id": "test_run" + }, + "probe2targets.tsv:md5,e15465df3845d7a6acf64dd3be04391b" + ] + ], + "1": [ + "versions.yml:md5,daf660f286a817fa0eed7703a5f65706" + ], + "probes2target": [ + [ + { + "id": "test_run" + }, + "probe2targets.tsv:md5,e15465df3845d7a6acf64dd3be04391b" + ] + ], + "versions": [ + "versions.yml:md5,daf660f286a817fa0eed7703a5f65706" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-14T12:20:19.999359259" + } +} \ No newline at end of file diff --git a/modules/nf-core/stardist/environment.yml b/modules/nf-core/stardist/environment.yml new file mode 100644 index 00000000..ddba86ea --- /dev/null +++ b/modules/nf-core/stardist/environment.yml @@ -0,0 +1,26 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - conda-forge::python=3.12.12 + - pip: + - stardist==0.9.2 + - tifffile==2026.3.3 + - imagecodecs==2026.1.14 + # For conda GPU support, use tensorflow[and-cuda]==2.20.0 instead + # The followings are needed in seqera containers UI + - tensorflow==2.20.0 + - nvidia-cublas-cu12==12.9.1.4 + - nvidia-cuda-cupti-cu12==12.9.79 + - nvidia-cuda-nvcc-cu12==12.9.86 + - nvidia-cuda-nvrtc-cu12==12.9.86 + - nvidia-cuda-runtime-cu12==12.9.79 + - nvidia-cudnn-cu12==9.19.0.56 + - nvidia-cufft-cu12==11.4.1.4 + - nvidia-curand-cu12==10.3.10.19 + - nvidia-cusolver-cu12==11.7.5.82 + - nvidia-cusparse-cu12==12.5.10.65 + - nvidia-nccl-cu12==2.29.7 + - nvidia-nvjitlink-cu12==12.9.86 diff --git a/modules/nf-core/stardist/main.nf b/modules/nf-core/stardist/main.nf new file mode 100644 index 00000000..01f4638c --- /dev/null +++ b/modules/nf-core/stardist/main.nf @@ -0,0 +1,41 @@ +process STARDIST { + tag "$meta.id" + label 'process_high' + label 'process_gpu_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/d9/d964e0bef867bb2ff1a309c9c087d8d83ac734ce3aa315dd8311d4c1bfdafd8e/data' : + 'community.wave.seqera.io/library/python_pip_imagecodecs_nvidia-cublas-cu12_pruned:b668bcb6d531d350' }" + + input: + tuple val(meta), path(image) + tuple val(model_name), path(model_path) + + output: + tuple val(meta), path("*.stardist.tif"), emit: mask + tuple val("${task.process}"), val('stardist'), eval("pip show stardist | sed -n 's/^Version: //p'"), topic: versions, emit: versions_stardist + tuple val("${task.process}"), val('python'), eval("python --version | sed 's/Python //'"), topic: versions, emit: versions_python + tuple val("${task.process}"), val('tensorflow'), eval("pip show tensorflow | sed -n 's/^Version: //p'"), topic: versions, emit: versions_tensorflow + tuple val("${task.process}"), val('tifffile'), eval("pip show tifffile | sed -n 's/^Version: //p'"), topic: versions, emit: versions_tifffile + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def model_command = model_path ? "-m ${model_path}" : model_name ? "-m ${model_name}" : "" + """ + stardist-predict2d \\ + -i $image \\ + -o . \\ + $model_command \\ + $args + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}.stardist.tif + """ +} diff --git a/modules/nf-core/stardist/meta.yml b/modules/nf-core/stardist/meta.yml new file mode 100644 index 00000000..a7ecb790 --- /dev/null +++ b/modules/nf-core/stardist/meta.yml @@ -0,0 +1,157 @@ +name: "stardist" +description: Cell and nuclear segmentation with star-convex shapes +keywords: + - stardist + - segmentation + - image + - gpu + - spatial-transcriptomics +tools: + - "stardist": + description: "Stardist is an cell segmentation tool developed in Python by Martin + Weigert and Uwe Schmidt" + homepage: "https://stardist.net/" + documentation: "https://stardist.net/faq/" + tool_dev_url: "https://github.com/stardist/stardist" + doi: "10.1109/ISBIC56247.2022.9854534" + licence: + - "BSD 3-Clause" + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - image: + type: file + description: Single channel nuclear image + pattern: "*.{tiff,tif}" + ontologies: [] + - - model_name: + type: string + description: | + Name of a pretrained StarDist model (e.g. '2D_versatile_fluo', + '2D_versatile_he'). Used when model_path is not provided. + Pass '' (empty string) if providing a custom model path or + passing -m via ext.args. + - model_path: + type: file + description: | + Optional path to a custom StarDist model directory. When provided, + takes precedence over model_name. Pass [] (empty list) to use a + pretrained model name instead. + pattern: "*" + ontologies: [] +output: + mask: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*.stardist.tif": + type: file + description: labelled mask output from stardist in tif format. + pattern: "*.{tiff,tif}" + ontologies: [] + versions_stardist: + - - ${task.process}: + type: string + description: The name of the process + - stardist: + type: string + description: The name of the tool + - "pip show stardist | sed -n 's/^Version: //p'": + type: eval + description: The expression to obtain the version of the tool + versions_python: + - - ${task.process}: + type: string + description: The name of the process + - python: + type: string + description: The name of the tool + - python --version | sed 's/Python //': + type: eval + description: The expression to obtain the version of the tool + versions_tensorflow: + - - ${task.process}: + type: string + description: The name of the process + - tensorflow: + type: string + description: The name of the tool + - "pip show tensorflow | sed -n 's/^Version: //p'": + type: eval + description: The expression to obtain the version of the tool + versions_tifffile: + - - ${task.process}: + type: string + description: The name of the process + - tifffile: + type: string + description: The name of the tool + - "pip show tifffile | sed -n 's/^Version: //p'": + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - stardist: + type: string + description: The name of the tool + - "pip show stardist | sed -n 's/^Version: //p'": + type: eval + description: The expression to obtain the version of the tool + - - ${task.process}: + type: string + description: The name of the process + - python: + type: string + description: The name of the tool + - python --version | sed 's/Python //': + type: eval + description: The expression to obtain the version of the tool + - - ${task.process}: + type: string + description: The name of the process + - tensorflow: + type: string + description: The name of the tool + - "pip show tensorflow | sed -n 's/^Version: //p'": + type: eval + description: The expression to obtain the version of the tool + - - ${task.process}: + type: string + description: The name of the process + - tifffile: + type: string + description: The name of the tool + - "pip show tifffile | sed -n 's/^Version: //p'": + type: eval + description: The expression to obtain the version of the tool +authors: + - "@migueLib" + - "@dongzehe" +maintainers: + - "@migueLib" +notes: | + GPU support: The container (built via Seqera Containers) includes TensorFlow 2.20.0 + with CUDA support and falls back to CPU automatically when no GPU is available. + Use the `process_gpu` label to request GPU resources from your executor. + When running with conda/mamba, GPU support depends on having a CUDA-enabled + TensorFlow installation in your environment. + + Model selection via the model input channel [model_name, model_path]: + - Pretrained model: [ '2D_versatile_fluo', [] ] + - Custom model directory: [ '', file("/path/to/model") ] + - Via ext.args only: [ '', [] ] (then set ext.args = '-m ...') + + Additional stardist CLI arguments can be passed via `task.ext.args`: + ext.args = '--n_tiles 4,4 --prob_thresh 0.5 --nms_thresh 0.3' + + Model weights are not bundled in the container. StarDist downloads pretrained + models on first use to ~/.keras/models/. diff --git a/modules/nf-core/stardist/stardist.diff b/modules/nf-core/stardist/stardist.diff new file mode 100644 index 00000000..aa1a361b --- /dev/null +++ b/modules/nf-core/stardist/stardist.diff @@ -0,0 +1,13 @@ +Changes in module 'nf-core/stardist' +--- modules/nf-core/stardist/main.nf ++++ modules/nf-core/stardist/main.nf +@@ -1,7 +1,7 @@ + process STARDIST { + tag "$meta.id" +- label 'process_medium' +- label 'process_gpu' ++ label 'process_high' ++ label 'process_gpu_single' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? diff --git a/modules/nf-core/stardist/tests/main.nf.test b/modules/nf-core/stardist/tests/main.nf.test new file mode 100644 index 00000000..5b3e2089 --- /dev/null +++ b/modules/nf-core/stardist/tests/main.nf.test @@ -0,0 +1,59 @@ +nextflow_process { + name "Test Process STARDIST" + script "../main.nf" + process "STARDIST" + config "./nextflow.config" + + tag "modules" + tag "modules_nfcore" + tag "stardist" + + test("stardist2d - tif") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'imaging/segmentation/nuclear_image.tif', checkIfExists: true) + ] + input[1] = [ '2D_versatile_fluo', [] ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + + test("stardist2d - tif - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'imaging/segmentation/nuclear_image.tif', checkIfExists: true) + ] + input[1] = [ '2D_versatile_fluo', [] ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/nf-core/stardist/tests/main.nf.test.snap b/modules/nf-core/stardist/tests/main.nf.test.snap new file mode 100644 index 00000000..acbbc472 --- /dev/null +++ b/modules/nf-core/stardist/tests/main.nf.test.snap @@ -0,0 +1,168 @@ +{ + "stardist2d - tif - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.stardist.tif:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + "STARDIST", + "stardist", + "0.9.2" + ] + ], + "2": [ + [ + "STARDIST", + "python", + "3.12.12" + ] + ], + "3": [ + [ + "STARDIST", + "tensorflow", + "2.20.0" + ] + ], + "4": [ + [ + "STARDIST", + "tifffile", + "2026.3.3" + ] + ], + "mask": [ + [ + { + "id": "test" + }, + "test.stardist.tif:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_python": [ + [ + "STARDIST", + "python", + "3.12.12" + ] + ], + "versions_stardist": [ + [ + "STARDIST", + "stardist", + "0.9.2" + ] + ], + "versions_tensorflow": [ + [ + "STARDIST", + "tensorflow", + "2.20.0" + ] + ], + "versions_tifffile": [ + [ + "STARDIST", + "tifffile", + "2026.3.3" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-03-04T22:33:02.205105908" + }, + "stardist2d - tif": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "nuclear_image.stardist.tif:md5,ca29a05346d9e0d4c15bb133122560ce" + ] + ], + "1": [ + [ + "STARDIST", + "stardist", + "0.9.2" + ] + ], + "2": [ + [ + "STARDIST", + "python", + "3.12.12" + ] + ], + "3": [ + [ + "STARDIST", + "tensorflow", + "2.20.0" + ] + ], + "4": [ + [ + "STARDIST", + "tifffile", + "2026.3.3" + ] + ], + "mask": [ + [ + { + "id": "test" + }, + "nuclear_image.stardist.tif:md5,ca29a05346d9e0d4c15bb133122560ce" + ] + ], + "versions_python": [ + [ + "STARDIST", + "python", + "3.12.12" + ] + ], + "versions_stardist": [ + [ + "STARDIST", + "stardist", + "0.9.2" + ] + ], + "versions_tensorflow": [ + [ + "STARDIST", + "tensorflow", + "2.20.0" + ] + ], + "versions_tifffile": [ + [ + "STARDIST", + "tifffile", + "2026.3.3" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-03-06T15:21:31.45100204" + } +} diff --git a/modules/nf-core/stardist/tests/nextflow.config b/modules/nf-core/stardist/tests/nextflow.config new file mode 100644 index 00000000..5926218d --- /dev/null +++ b/modules/nf-core/stardist/tests/nextflow.config @@ -0,0 +1,3 @@ +docker { + fixOwnership = true +} diff --git a/modules/nf-core/untar/environment.yml b/modules/nf-core/untar/environment.yml new file mode 100644 index 00000000..9b926b1f --- /dev/null +++ b/modules/nf-core/untar/environment.yml @@ -0,0 +1,12 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - conda-forge::coreutils=9.5 + - conda-forge::grep=3.11 + - conda-forge::gzip=1.13 + - conda-forge::lbzip2=2.5 + - conda-forge::sed=4.8 + - conda-forge::tar=1.34 diff --git a/modules/nf-core/untar/main.nf b/modules/nf-core/untar/main.nf new file mode 100644 index 00000000..dc9a056f --- /dev/null +++ b/modules/nf-core/untar/main.nf @@ -0,0 +1,93 @@ +process UNTAR { + tag "${archive}" + 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/52/52ccce28d2ab928ab862e25aae26314d69c8e38bd41ca9431c67ef05221348aa/data' + : 'community.wave.seqera.io/library/coreutils_grep_gzip_lbzip2_pruned:838ba80435a629f8'}" + + input: + tuple val(meta), path(archive) + + output: + tuple val(meta), path("${prefix}"), emit: untar + tuple val("${task.process}"), val('untar'), eval('tar --version 2>&1 | head -1 | sed "s/tar (GNU tar) //; s/ Copyright.*//"'), emit: versions_untar, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def args2 = task.ext.args2 ?: '' + prefix = task.ext.prefix ?: (meta.id ? "${meta.id}" : archive.baseName.toString().replaceFirst(/\.tar$/, "")) + + """ + mkdir ${prefix} + + ## Ensures --strip-components only applied when top level of tar contents is a directory + ## If just files or multiple directories, place all in prefix + if [[ \$(tar -taf ${archive} | grep -o -P "^.*?\\/" | uniq | wc -l) -eq 1 ]]; then + tar \\ + -C ${prefix} --strip-components 1 \\ + -xavf \\ + ${args} \\ + ${archive} \\ + ${args2} + else + tar \\ + -C ${prefix} \\ + -xavf \\ + ${args} \\ + ${archive} \\ + ${args2} + fi + + """ + + stub: + prefix = task.ext.prefix ?: (meta.id ? "${meta.id}" : archive.toString().replaceFirst(/\.[^\.]+(.gz)?$/, "")) + """ + mkdir ${prefix} + ## Emit a valid empty file. nf-test's snapshot md5 path decompresses .gz files, + ## so a 0-byte .gz (from `touch`) throws EOF in GZIPInputStream and the snapshot + ## falls back to dumping non-deterministic File metadata (freeSpace, etc). + ## `: | gzip -n` produces a 20-byte deterministic empty gzip that md5s consistently. + emit_stub() { + local f="\$1" + if [[ "\$f" == *.gz ]]; then + : | gzip -n > "\$f" + else + touch "\$f" + fi + } + + ## Dry-run untaring the archive to get the files and place all in prefix + if [[ \$(tar -taf ${archive} | grep -o -P "^.*?\\/" | uniq | wc -l) -eq 1 ]]; then + ## Single top-level dir in the archive: mirror `--strip-components 1` from the real + ## extraction so the stubbed file layout matches what a real run would produce. + for i in `tar -tf ${archive}`; + do + stripped=\${i#*/} + [ -z "\$stripped" ] && continue + if [[ \$(echo "\${i}" | grep -E "/\$") == "" ]]; + then + mkdir -p ${prefix}/\$(dirname "\$stripped") + emit_stub ${prefix}/\$stripped + else + mkdir -p ${prefix}/\$stripped + fi + done + else + for i in `tar -tf ${archive}`; + do + if [[ \$(echo "\${i}" | grep -E "/\$") == "" ]]; + then + emit_stub ${prefix}/\${i} + else + mkdir -p ${prefix}/\${i} + fi + done + fi + """ +} diff --git a/modules/nf-core/untar/meta.yml b/modules/nf-core/untar/meta.yml new file mode 100644 index 00000000..571d8078 --- /dev/null +++ b/modules/nf-core/untar/meta.yml @@ -0,0 +1,73 @@ +name: untar +description: Extract files from tar, tar.gz, tar.bz2, tar.xz archives +keywords: + - untar + - uncompress + - extract +tools: + - untar: + description: | + Extract tar, tar.gz, tar.bz2, tar.xz files. + documentation: https://www.gnu.org/software/tar/manual/ + licence: ["GPL-3.0-or-later"] + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - archive: + type: file + description: File to be untarred + pattern: "*.{tar,tar.gz,tar.bz2,tar.xz}" + ontologies: + - edam: http://edamontology.org/format_3981 # TAR format + - edam: http://edamontology.org/format_3989 # GZIP format +output: + untar: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + pattern: "*/" + - ${prefix}: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + pattern: "*/" + versions_untar: + - - ${task.process}: + type: string + description: The name of the process + - untar: + type: string + description: The name of the tool + - tar --version 2>&1 | head -1 | sed "s/tar (GNU tar) //; s/ Copyright.*//": + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - untar: + type: string + description: The name of the tool + - tar --version 2>&1 | head -1 | sed "s/tar (GNU tar) //; s/ Copyright.*//": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@joseespinosa" + - "@drpatelh" + - "@matthdsm" + - "@jfy133" +maintainers: + - "@joseespinosa" + - "@drpatelh" + - "@matthdsm" + - "@jfy133" diff --git a/modules/nf-core/untar/tests/main.nf.test b/modules/nf-core/untar/tests/main.nf.test new file mode 100644 index 00000000..c957517a --- /dev/null +++ b/modules/nf-core/untar/tests/main.nf.test @@ -0,0 +1,85 @@ +nextflow_process { + + name "Test Process UNTAR" + script "../main.nf" + process "UNTAR" + tag "modules" + tag "modules_nfcore" + tag "untar" + + test("test_untar") { + + when { + process { + """ + input[0] = [ [], file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/db/kraken2.tar.gz', checkIfExists: true) ] + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() }, + ) + } + } + + test("test_untar_onlyfiles") { + + when { + process { + """ + input[0] = [ [], file(params.modules_testdata_base_path + 'generic/tar/hello.tar.gz', checkIfExists: true) ] + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() }, + ) + } + } + + test("test_untar - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ [], file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/db/kraken2.tar.gz', checkIfExists: true) ] + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() }, + ) + } + } + + test("test_untar_onlyfiles - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ [], file(params.modules_testdata_base_path + 'generic/tar/hello.tar.gz', checkIfExists: true) ] + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() }, + ) + } + } +} diff --git a/modules/nf-core/untar/tests/main.nf.test.snap b/modules/nf-core/untar/tests/main.nf.test.snap new file mode 100644 index 00000000..ceb91b79 --- /dev/null +++ b/modules/nf-core/untar/tests/main.nf.test.snap @@ -0,0 +1,158 @@ +{ + "test_untar_onlyfiles": { + "content": [ + { + "0": [ + [ + [ + + ], + [ + "hello.txt:md5,e59ff97941044f85df5297e1c302d260" + ] + ] + ], + "1": [ + "versions.yml:md5,6063247258c56fd271d076bb04dd7536" + ], + "untar": [ + [ + [ + + ], + [ + "hello.txt:md5,e59ff97941044f85df5297e1c302d260" + ] + ] + ], + "versions": [ + "versions.yml:md5,6063247258c56fd271d076bb04dd7536" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-10T12:04:28.231047" + }, + "test_untar_onlyfiles - stub": { + "content": [ + { + "0": [ + [ + [ + + ], + [ + "hello.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "1": [ + "versions.yml:md5,6063247258c56fd271d076bb04dd7536" + ], + "untar": [ + [ + [ + + ], + [ + "hello.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "versions": [ + "versions.yml:md5,6063247258c56fd271d076bb04dd7536" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-10T12:04:45.773103" + }, + "test_untar - stub": { + "content": [ + { + "0": [ + [ + [ + + ], + [ + "hash.k2d:md5,d41d8cd98f00b204e9800998ecf8427e", + "opts.k2d:md5,d41d8cd98f00b204e9800998ecf8427e", + "taxo.k2d:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "1": [ + "versions.yml:md5,6063247258c56fd271d076bb04dd7536" + ], + "untar": [ + [ + [ + + ], + [ + "hash.k2d:md5,d41d8cd98f00b204e9800998ecf8427e", + "opts.k2d:md5,d41d8cd98f00b204e9800998ecf8427e", + "taxo.k2d:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "versions": [ + "versions.yml:md5,6063247258c56fd271d076bb04dd7536" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-10T12:04:36.777441" + }, + "test_untar": { + "content": [ + { + "0": [ + [ + [ + + ], + [ + "hash.k2d:md5,8b8598468f54a7087c203ad0190555d9", + "opts.k2d:md5,a033d00cf6759407010b21700938f543", + "taxo.k2d:md5,094d5891cdccf2f1468088855c214b2c" + ] + ] + ], + "1": [ + "versions.yml:md5,6063247258c56fd271d076bb04dd7536" + ], + "untar": [ + [ + [ + + ], + [ + "hash.k2d:md5,8b8598468f54a7087c203ad0190555d9", + "opts.k2d:md5,a033d00cf6759407010b21700938f543", + "taxo.k2d:md5,094d5891cdccf2f1468088855c214b2c" + ] + ] + ], + "versions": [ + "versions.yml:md5,6063247258c56fd271d076bb04dd7536" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-10T12:04:19.377674" + } +} \ No newline at end of file diff --git a/modules/nf-core/untar/untar.diff b/modules/nf-core/untar/untar.diff new file mode 100644 index 00000000..71fea68f --- /dev/null +++ b/modules/nf-core/untar/untar.diff @@ -0,0 +1,54 @@ +Changes in component 'nf-core/untar' +Upstream git_sha: 447f7bc0fa41dfc2400c8cad4c0291880dc060cf +'modules/nf-core/untar/meta.yml' is unchanged +Changes in 'untar/main.nf': +--- modules/nf-core/untar/main.nf ++++ modules/nf-core/untar/main.nf +@@ -49,15 +49,33 @@ + prefix = task.ext.prefix ?: (meta.id ? "${meta.id}" : archive.toString().replaceFirst(/\.[^\.]+(.gz)?$/, "")) + """ + mkdir ${prefix} ++ ## Emit a valid empty file. nf-test's snapshot md5 path decompresses .gz files, ++ ## so a 0-byte .gz (from `touch`) throws EOF in GZIPInputStream and the snapshot ++ ## falls back to dumping non-deterministic File metadata (freeSpace, etc). ++ ## `: | gzip -n` produces a 20-byte deterministic empty gzip that md5s consistently. ++ emit_stub() { ++ local f="\$1" ++ if [[ "\$f" == *.gz ]]; then ++ : | gzip -n > "\$f" ++ else ++ touch "\$f" ++ fi ++ } ++ + ## Dry-run untaring the archive to get the files and place all in prefix + if [[ \$(tar -taf ${archive} | grep -o -P "^.*?\\/" | uniq | wc -l) -eq 1 ]]; then ++ ## Single top-level dir in the archive: mirror `--strip-components 1` from the real ++ ## extraction so the stubbed file layout matches what a real run would produce. + for i in `tar -tf ${archive}`; + do ++ stripped=\${i#*/} ++ [ -z "\$stripped" ] && continue + if [[ \$(echo "\${i}" | grep -E "/\$") == "" ]]; + then +- touch \${i} ++ mkdir -p ${prefix}/\$(dirname "\$stripped") ++ emit_stub ${prefix}/\$stripped + else +- mkdir -p \${i} ++ mkdir -p ${prefix}/\$stripped + fi + done + else +@@ -65,7 +83,7 @@ + do + if [[ \$(echo "\${i}" | grep -E "/\$") == "" ]]; + then +- touch ${prefix}/\${i} ++ emit_stub ${prefix}/\${i} + else + mkdir -p ${prefix}/\${i} + fi + +'modules/nf-core/untar/environment.yml' is unchanged +************************************************************ diff --git a/modules/nf-core/fastqc/environment.yml b/modules/nf-core/unzip/environment.yml similarity index 85% rename from modules/nf-core/fastqc/environment.yml rename to modules/nf-core/unzip/environment.yml index f9f54ee9..24615895 100644 --- a/modules/nf-core/fastqc/environment.yml +++ b/modules/nf-core/unzip/environment.yml @@ -4,4 +4,4 @@ channels: - conda-forge - bioconda dependencies: - - bioconda::fastqc=0.12.1 + - conda-forge::p7zip=16.02 diff --git a/modules/nf-core/unzip/main.nf b/modules/nf-core/unzip/main.nf new file mode 100644 index 00000000..d9417fb3 --- /dev/null +++ b/modules/nf-core/unzip/main.nf @@ -0,0 +1,50 @@ +process UNZIP { + tag "$archive" + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/p7zip:16.02' : + 'biocontainers/p7zip:16.02' }" + + input: + tuple val(meta), path(archive) + + output: + tuple val(meta), path("${prefix}/"), emit: unzipped_archive + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + if ( archive instanceof List && archive.name.size > 1 ) { error "[UNZIP] error: 7za only accepts a single archive as input. Please check module input." } + prefix = task.ext.prefix ?: ( meta.id ? "${meta.id}" : archive.baseName) + """ + 7za \\ + x \\ + -o"${prefix}"/ \\ + $args \\ + $archive + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + 7za: \$(echo \$(7za --help) | sed 's/.*p7zip Version //; s/(.*//') + END_VERSIONS + """ + + stub: + if ( archive instanceof List && archive.name.size > 1 ) { error "[UNZIP] error: 7za only accepts a single archive as input. Please check module input." } + prefix = task.ext.prefix ?: ( meta.id ? "${meta.id}" : archive.baseName) + """ + mkdir -p "${prefix}/morphology_focus" + touch "${prefix}/transcripts.parquet" + touch "${prefix}/morphology_focus/morphology_focus_0000.ome.tif" + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + 7za: \$(echo \$(7za --help) | sed 's/.*p7zip Version //; s/(.*//') + END_VERSIONS + """ +} diff --git a/modules/nf-core/unzip/meta.yml b/modules/nf-core/unzip/meta.yml new file mode 100644 index 00000000..ba1eb912 --- /dev/null +++ b/modules/nf-core/unzip/meta.yml @@ -0,0 +1,50 @@ +name: unzip +description: Unzip ZIP archive files +keywords: + - unzip + - decompression + - zip + - archiving +tools: + - unzip: + description: p7zip is a quick port of 7z.exe and 7za.exe (command line version + of 7zip, see www.7-zip.org) for Unix. + homepage: https://sourceforge.net/projects/p7zip/ + documentation: https://sourceforge.net/projects/p7zip/ + tool_dev_url: https://sourceforge.net/projects/p7zip" + licence: ["LGPL-2.1-or-later"] + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - archive: + type: file + description: ZIP file + pattern: "*.zip" + ontologies: + - edam: http://edamontology.org/format_3987 # ZIP format +output: + unzipped_archive: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - ${prefix}/: + type: directory + description: Directory contents of the unzipped archive + pattern: "${archive.baseName}/" + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML +authors: + - "@jfy133" +maintainers: + - "@jfy133" diff --git a/modules/nf-core/unzip/tests/main.nf.test b/modules/nf-core/unzip/tests/main.nf.test new file mode 100644 index 00000000..238b68d8 --- /dev/null +++ b/modules/nf-core/unzip/tests/main.nf.test @@ -0,0 +1,54 @@ +nextflow_process { + + name "Test Process UNZIP" + script "../main.nf" + process "UNZIP" + + tag "modules" + tag "modules_nfcore" + tag "unzip" + + test("generic [tar] [tar_gz]") { + + when { + process { + """ + input[0] = [ + [ id: 'hello' ], + file(params.modules_testdata_base_path + 'generic/tar/hello.tar.gz', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("generic [tar] [tar_gz] stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id: 'hello' ], + file(params.modules_testdata_base_path + 'generic/tar/hello.tar.gz', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/modules/nf-core/unzip/tests/main.nf.test.snap b/modules/nf-core/unzip/tests/main.nf.test.snap new file mode 100644 index 00000000..cdd2ab16 --- /dev/null +++ b/modules/nf-core/unzip/tests/main.nf.test.snap @@ -0,0 +1,76 @@ +{ + "generic [tar] [tar_gz] stub": { + "content": [ + { + "0": [ + [ + { + "id": "hello" + }, + [ + + ] + ] + ], + "1": [ + "versions.yml:md5,52c55ce814e8bc9edc5a6c625ed794b8" + ], + "unzipped_archive": [ + [ + { + "id": "hello" + }, + [ + + ] + ] + ], + "versions": [ + "versions.yml:md5,52c55ce814e8bc9edc5a6c625ed794b8" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2024-06-30T19:16:37.11550986" + }, + "generic [tar] [tar_gz]": { + "content": [ + { + "0": [ + [ + { + "id": "hello" + }, + [ + "hello.tar:md5,80c66db79a773bc87b3346035ff9593e" + ] + ] + ], + "1": [ + "versions.yml:md5,52c55ce814e8bc9edc5a6c625ed794b8" + ], + "unzipped_archive": [ + [ + { + "id": "hello" + }, + [ + "hello.tar:md5,80c66db79a773bc87b3346035ff9593e" + ] + ] + ], + "versions": [ + "versions.yml:md5,52c55ce814e8bc9edc5a6c625ed794b8" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2024-06-30T19:16:25.120242571" + } +} \ No newline at end of file diff --git a/modules/nf-core/unzip/unzip.diff b/modules/nf-core/unzip/unzip.diff new file mode 100644 index 00000000..e4a293c0 --- /dev/null +++ b/modules/nf-core/unzip/unzip.diff @@ -0,0 +1,18 @@ +Changes in component 'nf-core/unzip' +'modules/nf-core/unzip/environment.yml' is unchanged +'modules/nf-core/unzip/meta.yml' is unchanged +Changes in 'unzip/main.nf': +--- modules/nf-core/unzip/main.nf ++++ modules/nf-core/unzip/main.nf +@@ -38,7 +38,9 @@ + if ( archive instanceof List && archive.name.size > 1 ) { error "[UNZIP] error: 7za only accepts a single archive as input. Please check module input." } + prefix = task.ext.prefix ?: ( meta.id ? "${meta.id}" : archive.baseName) + """ +- mkdir "${prefix}" ++ mkdir -p "${prefix}/morphology_focus" ++ touch "${prefix}/transcripts.parquet" ++ touch "${prefix}/morphology_focus/morphology_focus_0000.ome.tif" + + cat <<-END_VERSIONS > versions.yml + "${task.process}": +************************************************************ diff --git a/modules/nf-core/xeniumranger/import-segmentation/main.nf b/modules/nf-core/xeniumranger/import-segmentation/main.nf new file mode 100644 index 00000000..264b8a72 --- /dev/null +++ b/modules/nf-core/xeniumranger/import-segmentation/main.nf @@ -0,0 +1,68 @@ +process XENIUMRANGER_IMPORT_SEGMENTATION { + tag "$meta.id" + label 'process_high' + + container "nf-core/xeniumranger:4.0" + + input: + tuple val(meta), path(xenium_bundle, stageAs: "bundle/"), path(transcript_assignment), path(viz_polygons), path(nuclei), path(cells), path(coordinate_transform), val(units) + + output: + tuple val(meta), path("${prefix}"), emit: outs + tuple val("${task.process}"), val("xeniumranger"), eval("xeniumranger -V | sed -e 's/.*xenium-//'"), emit: versions_xeniumranger, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "XENIUMRANGER_IMPORT_SEGMENTATION module does not support Conda. Please use Docker / Singularity / Podman instead." + } + + prefix = task.ext.prefix ?: "${meta.id}" + + // nuclei and cells are for image segmentation results + // transcript_assignment and viz_polygons are for transcript assignment results + // they are mutually exclusive + if ((nuclei || cells) && (transcript_assignment || viz_polygons)) { + error "--nuclei and --cells are for image segmentation results, which are mutually exclusive with --transcript-assignment and --viz-polygons for transcript assignment results. Please use only one of them." + } + + def assembled_args = [] + if (task.ext.args) { assembled_args << task.ext.args.trim() } + if (nuclei) { assembled_args << "--nuclei=\"${nuclei}\"" } + if (cells) { assembled_args << "--cells=\"${cells}\"" } + if (transcript_assignment) { assembled_args << "--transcript-assignment=\"${transcript_assignment}\"" } + if (viz_polygons) { assembled_args << "--viz-polygons=\"${viz_polygons}\"" } + if (coordinate_transform) { + assembled_args << "--coordinate-transform=\"${coordinate_transform}\"" + // if coordinate_transform is provided, units must be microns + assembled_args << "--units=\"microns\"" + } else if (units) { + assembled_args << "--units=\"${units}\"" + } + + def args = assembled_args ? assembled_args.join(" \\\n ") : "" + + """ + xeniumranger import-segmentation \\ + --id="XENIUMRANGER_IMPORT_SEGMENTATION" \\ + --xenium-bundle="${xenium_bundle}" \\ + --localcores=${task.cpus} \\ + --localmem=${task.memory.toGiga()} \\ + ${args} + + rm -rf "${prefix}" + mv XENIUMRANGER_IMPORT_SEGMENTATION/outs "${prefix}" + """ + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + """ + mkdir -p "${prefix}" + touch "${prefix}/experiment.xenium" + """ + +} diff --git a/modules/nf-core/xeniumranger/import-segmentation/meta.yml b/modules/nf-core/xeniumranger/import-segmentation/meta.yml new file mode 100644 index 00000000..e222df58 --- /dev/null +++ b/modules/nf-core/xeniumranger/import-segmentation/meta.yml @@ -0,0 +1,141 @@ +name: xeniumranger_import_segmentation +description: | + The xeniumranger import-segmentation module runs `xeniumranger import-segmentation` + to recompute Xenium Onboard Analysis outputs using external segmentation results. + It supports two execution modes mirroring the Xenium Ranger CLI: an image-based + mode that accepts nuclei and/or cell masks (TIFF/NPY) or GeoJSON polygons together + with optional coordinate transforms and unit definitions, and a transcript-based + mode that ingests Baysor-style transcript assignment CSV files plus visualization + polygons. Use the image-based inputs when providing label masks or polygons, or + switch to the transcript-based inputs when supplying transcript-level assignments + so the appropriate command-line arguments are passed to Xenium Ranger. +keywords: + - spatial + - segmentation + - import segmentation + - nuclear segmentation + - cell segmentation + - xeniumranger + - imaging +tools: + - xeniumranger: + description: | + Xenium Ranger is a set of analysis pipelines that process Xenium In Situ Gene Expression data to relabel, resegment, or import new segmentation results from community-developed tools. Xenium Ranger provides flexible off-instrument reanalysis of Xenium In Situ data. Relabel transcripts, resegment cells with the latest 10x segmentation algorithms, or import your own segmentation data to assign transcripts to cells. + homepage: "https://www.10xgenomics.com/support/software/xenium-ranger/latest" + documentation: "https://www.10xgenomics.com/support/software/xenium-ranger/latest/getting-started" + tool_dev_url: "https://www.10xgenomics.com/support/software/xenium-ranger/latest/analysis" + licence: + - "10x Genomics EULA" + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing run information + e.g. [ id:'xenium_sample' ] + - xenium_bundle: + type: directory + description: Path to the Xenium output bundle generated by the Xenium + Onboard Analysis pipeline + - transcript_assignment: + type: file + optional: true + description: | + Transcript assignment CSV with cell assignment, such as from Baysor v0.6, (transcript-based mode). + Mutually exclusive with image-based inputs (`nuclei`, `cells`). Required when using + transcript-based mode. Passed to `--transcript-assignment`. + pattern: "*.csv" + ontologies: + - edam: http://edamontology.org/format_3752 # CSV + - viz_polygons: + type: file + optional: true + description: | + Cell boundary polygons (GeoJSON) for visualization, such as from Baysor v0.6 (transcript-based mode). + Mutually exclusive with image-based inputs (`nuclei`, `cells`). Required when using + `transcript_assignment`. Passed to `--viz-polygons`. + pattern: "*.{json,geojson}" + ontologies: + - edam: http://edamontology.org/format_3464 # JSON + - nuclei: + type: file + optional: true + description: | + Nucleus segmentation input as label mask (TIFF/NPY), polygons (GeoJSON), or Xenium Onboard + Analysis cells.zarr.zip (image-based mode). + Mutually exclusive with transcript-based inputs + (`transcript_assignment`, `viz_polygons`). Passed to `--nuclei`. + pattern: "*.{tif,tiff,npy,json,geojson,zarr.zip}" + ontologies: + - edam: http://edamontology.org/format_4003 # NumPy format + - edam: http://edamontology.org/format_3464 # JSON + - cells: + type: file + optional: true + description: | + Cell segmentation input as label mask (TIFF/NPY), polygons (GeoJSON), or Xenium Onboard + Analysis cells.zarr.zip (image-based mode). + Mutually exclusive with transcript-based inputs + (`transcript_assignment`, `viz_polygons`). Passed to `--cells`. + pattern: "*.{tif,tiff,npy,json,geojson,zarr.zip}" + ontologies: + - edam: http://edamontology.org/format_4003 # NumPy format + - edam: http://edamontology.org/format_3464 # JSON + - coordinate_transform: + type: file + optional: true + description: | + Image alignment file containing similarity transform matrix (e.g., `_imagealignment.csv` from + Xenium Explorer). Only used with image-based mode inputs (`nuclei`, `cells`). `units` will be automatically set to "microns". Passed to `--coordinate-transform`. + pattern: "*.csv" + ontologies: + - edam: http://edamontology.org/format_3752 # CSV + - units: + type: string + optional: true + description: | + Units for segmentation results. Must be one of two options: "microns" (physical space) or + "pixels" (pixel space). Can be used with both image-based and transcript-based modes. + Default: "pixels". Must be "microns" if `coordinate_transform` is used. For Baysor v0.6 + inputs, must be "microns". Passed to `--units`. + enum: + - "microns" + - "pixels" +output: + outs: + - - meta: + type: map + description: Groovy Map containing sample information e.g. [ id:'test' ] + - ${prefix}: + type: directory + description: Directory containing the output xenium bundle of Xenium + Ranger + pattern: "${prefix}" + versions_xeniumranger: + - - ${task.process}: + type: string + description: The process the versions were collected from + - xeniumranger: + type: string + description: The tool name + - xeniumranger -V | sed -e 's/.*xenium-//': + type: string + description: The command used to generate the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - xeniumranger: + type: string + description: The tool name + - xeniumranger -V | sed -e 's/.*xenium-//': + type: string + description: The command used to generate the version of the tool +authors: + - "@khersameesh24" + - "@dongzehe" +maintainers: + - "@khersameesh24" + - "@dongzehe" diff --git a/modules/nf-core/xeniumranger/import-segmentation/tests/main.nf.test b/modules/nf-core/xeniumranger/import-segmentation/tests/main.nf.test new file mode 100644 index 00000000..71198099 --- /dev/null +++ b/modules/nf-core/xeniumranger/import-segmentation/tests/main.nf.test @@ -0,0 +1,314 @@ +nextflow_process { + + name "Test Process XENIUMRANGER_IMPORT_SEGMENTATION" + script "../main.nf" + process "XENIUMRANGER_IMPORT_SEGMENTATION" + config "./nextflow.config" + + tag "modules" + tag "modules_nfcore" + tag "xeniumranger" + tag "xeniumranger/import-segmentation" + tag "unzip" + + setup { + run("UNZIP") { + script "modules/nf-core/unzip/main.nf" + process { + """ + input[0] = [[], file('https://raw.githubusercontent.com/nf-core/test-datasets/spatialaxe/Xenium_Prime_Mouse_Ileum_tiny_outs.zip', checkIfExists: true)] + """ + } + } + } + + test("xeniumranger import-segmentation nuclei npy") { + when { + process { + """ + input[0] = channel.of([ + [id: "test_xeniumranger_import-segmentation"], + ]).combine(UNZIP.out.unzipped_archive.map { it[1] }) + input[1] = 0 + input[2] = [] + input[3] = UNZIP.out.unzipped_archive.map { it[1] } + "/segmentations/nuclei.npy" + input[4] = [] + input[5] = [] + input[6] = [] + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.versions, + process.out.outs.get(0).get(1).findAll { file(it).name !in [ + 'analysis_summary.html', + 'metrics_summary.csv', + 'cell_boundaries.csv.gz', + 'cell_boundaries.parquet', + 'nucleus_boundaries.csv.gz', + 'nucleus_boundaries.parquet', + 'cells.csv.gz', + 'cells.parquet', + 'cells.zarr.zip', + 'transcripts.parquet', + 'transcripts.zarr.zip', + 'clusters.csv', + 'differential_expression.csv', + 'components.csv', + 'projection.csv', + 'variance.csv', + 'analysis.zarr.zip', + 'experiment.xenium', + 'cell_feature_matrix.zarr.zip' + ]} + ).match() + }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'analysis_summary.html' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'metrics_summary.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cell_boundaries.csv.gz' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cell_boundaries.parquet' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'nucleus_boundaries.csv.gz' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'nucleus_boundaries.parquet' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cells.csv.gz' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cells.parquet' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cells.zarr.zip' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'transcripts.parquet' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'clusters.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'differential_expression.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'components.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'projection.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'variance.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'analysis.zarr.zip' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'experiment.xenium' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cell_feature_matrix.zarr.zip' }).exists() }, + ) + } + } + + + test("xeniumranger import-segmentation nuclei tif") { + when { + process { + """ + input[0] = channel.of([ + [id: "test_xeniumranger_import-segmentation"], + ]).combine(UNZIP.out.unzipped_archive.map { it[1] }) + input[1] = 0 + input[2] = [] + input[3] = UNZIP.out.unzipped_archive.map { it[1] } + "/segmentations/nuclei.npy" + input[4] = [] + input[5] = [] + input[6] = [] + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.versions, + process.out.outs.get(0).get(1).findAll { file(it).name !in [ + 'analysis_summary.html', + 'metrics_summary.csv', + 'cell_boundaries.csv.gz', + 'cell_boundaries.parquet', + 'nucleus_boundaries.csv.gz', + 'nucleus_boundaries.parquet', + 'cells.csv.gz', + 'cells.parquet', + 'cells.zarr.zip', + 'transcripts.parquet', + 'transcripts.zarr.zip', + 'clusters.csv', + 'differential_expression.csv', + 'components.csv', + 'projection.csv', + 'variance.csv', + 'analysis.zarr.zip', + 'experiment.xenium', + 'cell_feature_matrix.zarr.zip' + ]} + ).match() + }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'analysis_summary.html' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'metrics_summary.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cell_boundaries.csv.gz' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cell_boundaries.parquet' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'nucleus_boundaries.csv.gz' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'nucleus_boundaries.parquet' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cells.csv.gz' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cells.parquet' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cells.zarr.zip' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'transcripts.parquet' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'clusters.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'differential_expression.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'components.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'projection.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'variance.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'analysis.zarr.zip' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'experiment.xenium' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cell_feature_matrix.zarr.zip' }).exists() }, + ) + } + } + + test("xeniumranger import-segmentation segmentation csv") { + when { + process { + """ + input[0] = channel.of([ + [id: "test_xeniumranger_import-segmentation"], + ]).combine(UNZIP.out.unzipped_archive.map { it[1] }) + input[1] = 0 + input[2] = UNZIP.out.unzipped_archive.map { it[1] } + "/segmentations/imagealignment.csv" + input[3] = [] + input[4] = [] + input[5] = UNZIP.out.unzipped_archive.map { it[1] } + "/segmentations/segmentation.csv" + input[6] = UNZIP.out.unzipped_archive.map { it[1] } + "/segmentations/segmentation_polygons.json" + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.versions, + process.out.outs.get(0).get(1).findAll { file(it).name !in [ + 'analysis_summary.html', + 'metrics_summary.csv', + 'cell_boundaries.csv.gz', + 'cell_boundaries.parquet', + 'nucleus_boundaries.csv.gz', + 'nucleus_boundaries.parquet', + 'cells.csv.gz', + 'cells.parquet', + 'cells.zarr.zip', + 'transcripts.parquet', + 'transcripts.zarr.zip', + 'clusters.csv', + 'differential_expression.csv', + 'components.csv', + 'projection.csv', + 'variance.csv', + 'analysis.zarr.zip', + 'experiment.xenium', + 'cell_feature_matrix.zarr.zip' + ]} + ).match() + }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'analysis_summary.html' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'metrics_summary.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cell_boundaries.csv.gz' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cell_boundaries.parquet' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'nucleus_boundaries.csv.gz' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'nucleus_boundaries.parquet' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cells.csv.gz' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cells.parquet' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cells.zarr.zip' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'transcripts.parquet' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'clusters.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'differential_expression.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'components.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'projection.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'variance.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'analysis.zarr.zip' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'experiment.xenium' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cell_feature_matrix.zarr.zip' }).exists() }, + ) + } + } + + test("xeniumranger import-segmentation") { + when { + process { + """ + input[0] = channel.of([ + [id: "test_xeniumranger_import-segmentation"], + ]).combine(UNZIP.out.unzipped_archive.map { it[1] }) + input[1] = 0 + input[2] = [] + input[3] = UNZIP.out.unzipped_archive.map { it[1] } + "/cells.zarr.zip" + input[4] = [] + input[5] = [] + input[6] = [] + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.versions, + process.out.outs.get(0).get(1).findAll { file(it).name !in [ + 'analysis_summary.html', + 'metrics_summary.csv', + 'cell_boundaries.csv.gz', + 'cell_boundaries.parquet', + 'nucleus_boundaries.csv.gz', + 'nucleus_boundaries.parquet', + 'cells.csv.gz', + 'cells.parquet', + 'cells.zarr.zip', + 'transcripts.parquet', + 'transcripts.zarr.zip', + 'clusters.csv', + 'differential_expression.csv', + 'components.csv', + 'projection.csv', + 'variance.csv', + 'analysis.zarr.zip', + 'experiment.xenium', + 'cell_feature_matrix.zarr.zip' + ]} + ).match() + }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'analysis_summary.html' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'metrics_summary.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cell_boundaries.csv.gz' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cell_boundaries.parquet' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'nucleus_boundaries.csv.gz' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'nucleus_boundaries.parquet' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cells.csv.gz' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cells.parquet' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cells.zarr.zip' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'transcripts.parquet' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'clusters.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'differential_expression.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'components.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'projection.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'variance.csv' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'analysis.zarr.zip' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'experiment.xenium' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cell_feature_matrix.zarr.zip' }).exists() }, + ) + } + } + + test("xeniumranger import-segmentation stub") { + options "-stub" + when { + process { + """ + input[0] = channel.of([ + [id: "test_xeniumranger_import-segmentation"], + ]).combine(UNZIP.out.unzipped_archive.map { it[1] }) + input[1] = 0 + input[2] = [] + input[3] = UNZIP.out.unzipped_archive.map { it[1] } + "/cells.zarr.zip" + input[4] = [] + input[5] = [] + input[6] = [] + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} \ No newline at end of file diff --git a/modules/nf-core/xeniumranger/import-segmentation/tests/main.nf.test.snap b/modules/nf-core/xeniumranger/import-segmentation/tests/main.nf.test.snap new file mode 100644 index 00000000..1c312ae0 --- /dev/null +++ b/modules/nf-core/xeniumranger/import-segmentation/tests/main.nf.test.snap @@ -0,0 +1,127 @@ +{ + "xeniumranger import-segmentation": { + "content": [ + [ + "versions.yml:md5,d76e870d71abf94ed9ae972a08b83f63" + ], + [ + "dispersion.csv:md5,e8b1abb880ece8fb730ce34a15f958b4", + "features_selected.csv:md5,c5e32d69f001f938ed316d2108a21e00", + "cell_feature_matrix.h5:md5,96cb400f1b1dd6f8796daea0ad5c74e6", + "barcodes.tsv.gz:md5,04ea06796d6b28517c288904ca043582", + "features.tsv.gz:md5,7862242129681900a9cc4086dc83b62e", + "matrix.mtx.gz:md5,489f86fbd8d65d6b973bb9cc7c5a76f1", + "gene_panel.json:md5,8890dd5fd90706e751554ac3fdfdedde", + "morphology.ome.tif:md5,6b65fff28a38a001b8f25061737fbf9b", + "morphology_focus_0000.ome.tif:md5,90e796ad634d14e62cf2ebcadf2eaf98" + ] + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-30T00:13:13.575888" + }, + "xeniumranger import-segmentation nuclei npy": { + "content": [ + [ + "versions.yml:md5,d76e870d71abf94ed9ae972a08b83f63" + ], + [ + "dispersion.csv:md5,e8b1abb880ece8fb730ce34a15f958b4", + "features_selected.csv:md5,c5e32d69f001f938ed316d2108a21e00", + "cell_feature_matrix.h5:md5,96cb400f1b1dd6f8796daea0ad5c74e6", + "barcodes.tsv.gz:md5,04ea06796d6b28517c288904ca043582", + "features.tsv.gz:md5,7862242129681900a9cc4086dc83b62e", + "matrix.mtx.gz:md5,489f86fbd8d65d6b973bb9cc7c5a76f1", + "gene_panel.json:md5,8890dd5fd90706e751554ac3fdfdedde", + "morphology.ome.tif:md5,6b65fff28a38a001b8f25061737fbf9b", + "morphology_focus_0000.ome.tif:md5,90e796ad634d14e62cf2ebcadf2eaf98" + ] + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-29T23:03:26.726334" + }, + "xeniumranger import-segmentation segmentation csv": { + "content": [ + [ + "versions.yml:md5,d76e870d71abf94ed9ae972a08b83f63" + ], + [ + "dispersion.csv:md5,e8b1abb880ece8fb730ce34a15f958b4", + "features_selected.csv:md5,c5e32d69f001f938ed316d2108a21e00", + "cell_feature_matrix.h5:md5,5d74ea595561e0300b6c3e5ec8d06fff", + "barcodes.tsv.gz:md5,97496a9b448d9380cff0575b8e7a6f57", + "features.tsv.gz:md5,7862242129681900a9cc4086dc83b62e", + "matrix.mtx.gz:md5,f93ed82a2a74c154392fc6237642f1d2", + "gene_panel.json:md5,8890dd5fd90706e751554ac3fdfdedde", + "morphology.ome.tif:md5,6b65fff28a38a001b8f25061737fbf9b", + "morphology_focus_0000.ome.tif:md5,90e796ad634d14e62cf2ebcadf2eaf98" + ] + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-29T23:22:58.158857" + }, + "xeniumranger import-segmentation stub": { + "content": [ + { + "0": [ + [ + { + "id": "test_xeniumranger_import-segmentation" + }, + "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,d76e870d71abf94ed9ae972a08b83f63" + ], + "outs": [ + [ + { + "id": "test_xeniumranger_import-segmentation" + }, + "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,d76e870d71abf94ed9ae972a08b83f63" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-30T22:49:39.204133" + }, + "xeniumranger import-segmentation nuclei tif": { + "content": [ + [ + "versions.yml:md5,d76e870d71abf94ed9ae972a08b83f63" + ], + [ + "dispersion.csv:md5,e8b1abb880ece8fb730ce34a15f958b4", + "features_selected.csv:md5,c5e32d69f001f938ed316d2108a21e00", + "cell_feature_matrix.h5:md5,96cb400f1b1dd6f8796daea0ad5c74e6", + "barcodes.tsv.gz:md5,04ea06796d6b28517c288904ca043582", + "features.tsv.gz:md5,7862242129681900a9cc4086dc83b62e", + "matrix.mtx.gz:md5,489f86fbd8d65d6b973bb9cc7c5a76f1", + "gene_panel.json:md5,8890dd5fd90706e751554ac3fdfdedde", + "morphology.ome.tif:md5,6b65fff28a38a001b8f25061737fbf9b", + "morphology_focus_0000.ome.tif:md5,90e796ad634d14e62cf2ebcadf2eaf98" + ] + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-29T23:11:37.18721" + } +} \ No newline at end of file diff --git a/modules/nf-core/xeniumranger/import-segmentation/tests/nextflow.config b/modules/nf-core/xeniumranger/import-segmentation/tests/nextflow.config new file mode 100644 index 00000000..e69de29b diff --git a/modules/nf-core/xeniumranger/import-segmentation/tests/tags.yml b/modules/nf-core/xeniumranger/import-segmentation/tests/tags.yml new file mode 100644 index 00000000..90c2b805 --- /dev/null +++ b/modules/nf-core/xeniumranger/import-segmentation/tests/tags.yml @@ -0,0 +1,2 @@ +xeniumranger/import-segmentation: + - "modules/nf-core/xeniumranger/import-segmentation/**" diff --git a/modules/nf-core/xeniumranger/relabel/main.nf b/modules/nf-core/xeniumranger/relabel/main.nf new file mode 100644 index 00000000..bf04a971 --- /dev/null +++ b/modules/nf-core/xeniumranger/relabel/main.nf @@ -0,0 +1,47 @@ +process XENIUMRANGER_RELABEL { + tag "$meta.id" + label 'process_high' + + container "nf-core/xeniumranger:4.0" + + input: + tuple val(meta), path(xenium_bundle, stageAs: "bundle/"), path(panel) + + output: + tuple val(meta), path("${prefix}"), emit: outs + tuple val("${task.process}"), val("xeniumranger"), eval("xeniumranger -V | sed -e 's/.*xenium-//'"), emit: versions_xeniumranger, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "XENIUMRANGER_RELABEL module does not support Conda. Please use Docker / Singularity / Podman instead." + } + + prefix = task.ext.prefix ?: "${meta.id}" + def args = task.ext.args ?: "" + + """ + xeniumranger relabel \\ + --id="XENIUMRANGER_RELABEL" \\ + --xenium-bundle="${xenium_bundle}" \\ + --panel="${panel}" \\ + --localcores=${task.cpus} \\ + --localmem=${task.memory.toGiga()} \\ + ${args} + + rm -rf "${prefix}" + mv XENIUMRANGER_RELABEL/outs "${prefix}" + """ + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p "${prefix}" + touch "${prefix}/experiment.xenium" + """ +} diff --git a/modules/nf-core/xeniumranger/relabel/meta.yml b/modules/nf-core/xeniumranger/relabel/meta.yml new file mode 100644 index 00000000..8b61e0bb --- /dev/null +++ b/modules/nf-core/xeniumranger/relabel/meta.yml @@ -0,0 +1,72 @@ +name: xeniumranger_relabel +description: The xeniumranger relabel module allows you to change the gene + labels applied to decoded transcripts. +keywords: + - spatial + - relabel + - gene labels + - transcripts + - xeniumranger +tools: + - xeniumranger: + description: | + Xenium Ranger is a set of analysis pipelines that process Xenium In Situ Gene Expression data to relabel, resegment, or import new segmentation results from community-developed tools. Xenium Ranger provides flexible off-instrument reanalysis of Xenium In Situ data. Relabel transcripts, resegment cells with the latest 10x segmentation algorithms, or import your own segmentation data to assign transcripts to cells. + homepage: "https://www.10xgenomics.com/support/software/xenium-ranger/latest" + documentation: "https://www.10xgenomics.com/support/software/xenium-ranger/latest/getting-started" + tool_dev_url: "https://www.10xgenomics.com/support/software/xenium-ranger/latest/analysis" + licence: + - "10x Genomics EULA" + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing run information + e.g. [ id:'xenium_bundle_path' ] + - xenium_bundle: + type: directory + description: Path to the xenium output bundle generated by the Xenium + Onboard Analysis pipeline + - panel: + type: file + description: Path to the gene panel file + pattern: "*.json" + ontologies: + - edam: http://edamontology.org/format_3464 # JSON +output: + outs: + - - meta: + type: map + description: Groovy Map containing sample information e.g. [ id:'test' ] + - "${prefix}": + type: directory + description: Directory containing the output xenium bundle of Xenium + Ranger + pattern: "${prefix}" + versions_xeniumranger: + - - "${task.process}": + type: string + description: The process the versions were collected from + - xeniumranger: + type: string + description: The tool name + - "xeniumranger -V | sed -e 's/.*xenium-//'": + type: string + description: The command used to generate the version of the tool +topics: + versions: + - - "${task.process}": + type: string + description: The process the versions were collected from + - xeniumranger: + type: string + description: The tool name + - "xeniumranger -V | sed -e 's/.*xenium-//'": + type: string + description: The command used to generate the version of the tool +authors: + - "@khersameesh24" + - "@dongzehe" +maintainers: + - "@khersameesh24" + - "@dongzehe" diff --git a/modules/nf-core/xeniumranger/relabel/tests/main.nf.test b/modules/nf-core/xeniumranger/relabel/tests/main.nf.test new file mode 100644 index 00000000..e00f1caa --- /dev/null +++ b/modules/nf-core/xeniumranger/relabel/tests/main.nf.test @@ -0,0 +1,93 @@ +nextflow_process { + + name "Test Process XENIUMRANGER_RELABEL" + script "../main.nf" + process "XENIUMRANGER_RELABEL" + config "./nextflow.config" + + tag "modules" + tag "modules_nfcore" + tag "xeniumranger" + tag "xeniumranger/relabel" + tag "unzip" + + setup { + run("UNZIP") { + script "modules/nf-core/unzip/main.nf" + process { + """ + input[0] = [[], file('https://raw.githubusercontent.com/nf-core/test-datasets/spatialaxe/Xenium_Prime_Mouse_Ileum_tiny_outs.zip', checkIfExists: true)] + """ + } + } + } + + test("xeniumranger relabel") { + when { + process { + """ + input[0] = channel.of([ + [id: "test_xeniumranger_relabel"], + ]).combine(UNZIP.out.unzipped_archive.map { it[1] }) + input[1] = UNZIP.out.unzipped_archive.map { it[1] } + "/gene_panel.json" + """ + } + } + then { + assertAll( + { assert process.success }, + { assert process.out.outs != null }, + { + assert snapshot( + process.out.versions, + process.out.outs.get(0).get(1).findAll { file(it).name !in [ + "analysis.zarr.zip", + "experiment.xenium", + "transcripts.zarr.zip", + "analysis_summary.html", + "cell_feature_matrix.zarr.zip", + "differential_expression.csv", + "components.csv", + "projection.csv", + "variance.csv", + "metrics_summary.csv", + "clusters.csv" + ]} + ).match() + }, + { assert path(process.out.outs.get(0).get(1).find { file(it).name == 'analysis_summary.html' }).exists() }, + { assert path(process.out.outs.get(0).get(1).find { file(it).name == 'analysis.zarr.zip' }).exists() }, + { assert path(process.out.outs.get(0).get(1).find { file(it).name == 'experiment.xenium' }).exists() }, + { assert path(process.out.outs.get(0).get(1).find { file(it).name == 'transcripts.zarr.zip' }).exists() }, + { assert path(process.out.outs.get(0).get(1).find { file(it).name == 'analysis_summary.html' }).exists() }, + { assert path(process.out.outs.get(0).get(1).find { file(it).name == 'cell_feature_matrix.zarr.zip' }).exists() }, + { assert path(process.out.outs.get(0).get(1).find { file(it).name == 'differential_expression.csv' }).exists() }, + { assert path(process.out.outs.get(0).get(1).find { file(it).name == 'components.csv' }).exists() }, + { assert path(process.out.outs.get(0).get(1).find { file(it).name == 'projection.csv' }).exists() }, + { assert path(process.out.outs.get(0).get(1).find { file(it).name == 'variance.csv' }).exists() }, + { assert path(process.out.outs.get(0).get(1).find { file(it).name == 'metrics_summary.csv' }).exists() }, + { assert path(process.out.outs.get(0).get(1).find { file(it).name == 'clusters.csv' }).exists() }, + ) + } + } + + test("xeniumranger relabel stub") { + options "-stub" + when { + process { + """ + input[0] = channel.of([ + [id: "test_xeniumranger_relabel"], + ]).combine(UNZIP.out.unzipped_archive.map { it[1] }) + input[1] = UNZIP.out.unzipped_archive.map { it[1] } + "/gene_panel.json" + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} \ No newline at end of file diff --git a/modules/nf-core/xeniumranger/relabel/tests/main.nf.test.snap b/modules/nf-core/xeniumranger/relabel/tests/main.nf.test.snap new file mode 100644 index 00000000..7c70cfc2 --- /dev/null +++ b/modules/nf-core/xeniumranger/relabel/tests/main.nf.test.snap @@ -0,0 +1,66 @@ +{ + "xeniumranger relabel": { + "content": [ + [ + "versions.yml:md5,ab2584177544560d5a9e9c36f7d24354" + ], + [ + "dispersion.csv:md5,e8b1abb880ece8fb730ce34a15f958b4", + "features_selected.csv:md5,c5e32d69f001f938ed316d2108a21e00", + "cell_boundaries.csv.gz:md5,8b4f2aa455a6fb14b2669a42db32ea7e", + "cell_boundaries.parquet:md5,e55d6a7fbec336103994baad8c8e4a9a", + "cell_feature_matrix.h5:md5,96cb400f1b1dd6f8796daea0ad5c74e6", + "barcodes.tsv.gz:md5,04ea06796d6b28517c288904ca043582", + "features.tsv.gz:md5,7862242129681900a9cc4086dc83b62e", + "matrix.mtx.gz:md5,489f86fbd8d65d6b973bb9cc7c5a76f1", + "cells.csv.gz:md5,3cef2d7cc8cfba1d47bdb7c65c3d5d5f", + "cells.parquet:md5,9b30b35ab961d2d243a1426e8dc980fe", + "cells.zarr.zip:md5,556e47d5b14150239b10b2f801defa2b", + "gene_panel.json:md5,8890dd5fd90706e751554ac3fdfdedde", + "morphology.ome.tif:md5,6b65fff28a38a001b8f25061737fbf9b", + "morphology_focus_0000.ome.tif:md5,90e796ad634d14e62cf2ebcadf2eaf98", + "nucleus_boundaries.csv.gz:md5,e417b6e293298870956d42c7106cbd0c", + "nucleus_boundaries.parquet:md5,bacbfc3c2e956d899e1d8ccba5dd7c5e", + "transcripts.parquet:md5,c0f40d5c61b87404bc9efb84ff0563a8" + ] + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-29T21:06:09.082129" + }, + "xeniumranger relabel stub": { + "content": [ + { + "0": [ + [ + { + "id": "test_xeniumranger_relabel" + }, + "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,ab2584177544560d5a9e9c36f7d24354" + ], + "outs": [ + [ + { + "id": "test_xeniumranger_relabel" + }, + "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,ab2584177544560d5a9e9c36f7d24354" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-22T15:22:34.353444" + } +} \ No newline at end of file diff --git a/modules/nf-core/xeniumranger/relabel/tests/nextflow.config b/modules/nf-core/xeniumranger/relabel/tests/nextflow.config new file mode 100644 index 00000000..e69de29b diff --git a/modules/nf-core/xeniumranger/relabel/tests/tags.yml b/modules/nf-core/xeniumranger/relabel/tests/tags.yml new file mode 100644 index 00000000..1cb37a80 --- /dev/null +++ b/modules/nf-core/xeniumranger/relabel/tests/tags.yml @@ -0,0 +1,2 @@ +xeniumranger/relabel: + - "modules/nf-core/xeniumranger/relabel/**" diff --git a/modules/nf-core/xeniumranger/resegment/main.nf b/modules/nf-core/xeniumranger/resegment/main.nf new file mode 100644 index 00000000..d52eba0e --- /dev/null +++ b/modules/nf-core/xeniumranger/resegment/main.nf @@ -0,0 +1,45 @@ +process XENIUMRANGER_RESEGMENT { + tag "$meta.id" + label 'process_high' + + container "nf-core/xeniumranger:4.0" + + input: + tuple val(meta), path(xenium_bundle, stageAs: "bundle/") + + output: + tuple val(meta), path("${prefix}"), emit: outs + tuple val("${task.process}"), val("xeniumranger"), eval("xeniumranger -V | sed -e 's/.*xenium-//'"), emit: versions_xeniumranger, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "XENIUMRANGER_RESEGMENT module does not support Conda. Please use Docker / Singularity / Podman instead." + } + + prefix = task.ext.prefix ?: "${meta.id}" + def args = task.ext.args ?: "" + + """ + xeniumranger resegment \\ + --id="XENIUMRANGER_RESEGMENT" \\ + --xenium-bundle="${xenium_bundle}" \\ + --localcores=${task.cpus} \\ + --localmem=${task.memory.toGiga()} \\ + ${args} + + rm -rf "${prefix}" + mv XENIUMRANGER_RESEGMENT/outs "${prefix}" + """ + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + """ + mkdir -p "${prefix}" + touch "${prefix}/experiment.xenium" + """ +} diff --git a/modules/nf-core/xeniumranger/resegment/meta.yml b/modules/nf-core/xeniumranger/resegment/meta.yml new file mode 100644 index 00000000..687c6723 --- /dev/null +++ b/modules/nf-core/xeniumranger/resegment/meta.yml @@ -0,0 +1,69 @@ +name: xeniumranger_resegment +description: The xeniumranger resegment module allows you to generate a new + segmentation of the morphology image space by rerunning the Xenium Onboard + Analysis (XOA) segmentation algorithms with modified parameters. +keywords: + - spatial + - resegment + - morphology + - segmentation + - xeniumranger +tools: + - xeniumranger: + description: | + Xenium Ranger is a set of analysis pipelines that process Xenium In Situ Gene Expression data to relabel, resegment, or import new segmentation results from community-developed tools. Xenium Ranger provides flexible off-instrument reanalysis of Xenium In Situ data. Relabel transcripts, resegment cells with the latest 10x segmentation algorithms, or import your own segmentation data to assign transcripts to cells. + homepage: "https://www.10xgenomics.com/support/software/xenium-ranger/latest" + documentation: "https://www.10xgenomics.com/support/software/xenium-ranger/latest/getting-started" + tool_dev_url: "https://www.10xgenomics.com/support/software/xenium-ranger/latest/analysis" + licence: + - "10x Genomics EULA" + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing run information + e.g. [ id:'xenium_experiment' ] + - xenium_bundle: + type: directory + description: Path to the xenium output bundle generated by the Xenium + Onboard Analysis pipeline +output: + outs: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "${prefix}": + type: directory + description: Directory containing the output xenium bundle of Xenium + Ranger + pattern: "${prefix}" + versions_xeniumranger: + - - "${task.process}": + type: string + description: The process the versions were collected from + - xeniumranger: + type: string + description: The tool name + - "xeniumranger -V | sed -e 's/.*xenium-//'": + type: string + description: The command used to generate the version of the tool +topics: + versions: + - - "${task.process}": + type: string + description: The process the versions were collected from + - xeniumranger: + type: string + description: The tool name + - "xeniumranger -V | sed -e 's/.*xenium-//'": + type: string + description: The command used to generate the version of the tool +authors: + - "@khersameesh24" + - "@dongzehe" +maintainers: + - "@khersameesh24" + - "@dongzehe" diff --git a/modules/nf-core/xeniumranger/resegment/tests/main.nf.test b/modules/nf-core/xeniumranger/resegment/tests/main.nf.test new file mode 100644 index 00000000..241f084c --- /dev/null +++ b/modules/nf-core/xeniumranger/resegment/tests/main.nf.test @@ -0,0 +1,77 @@ +nextflow_process { + + name "Test Process XENIUMRANGER_RESEGMENT" + script "../main.nf" + process "XENIUMRANGER_RESEGMENT" + config "./nextflow.config" + + tag "modules" + tag "modules_nfcore" + tag "xeniumranger" + tag "xeniumranger/resegment" + tag "unzip" + + setup { + run("UNZIP") { + script "modules/nf-core/unzip/main.nf" + process { + """ + input[0] = [[], file('https://raw.githubusercontent.com/nf-core/test-datasets/spatialaxe/Xenium_Prime_Mouse_Ileum_tiny_outs.zip', checkIfExists: true)] + """ + } + } + } + + test("xeniumranger resegment") { + when { + process { + """ + input[0] = channel.of([ + [id: "test_xeniumranger_resegment"], + ]).combine(UNZIP.out.unzipped_archive.map { it[1] }) + input[1] = [] + input[2] = [] + input[3] = [] + input[4] = [] + """ + } + } + then { + assertAll( + { assert process.success }, + { assert process.out.outs != null }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'analysis_summary.html' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cells.csv.gz' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cells.parquet' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'cells.zarr.zip' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'transcripts.parquet' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'transcripts.zarr.zip' }).exists() }, + { assert file(process.out.outs.get(0).get(1).find { file(it).name == 'analysis.zarr.zip' }).exists() }, + { assert path(process.out.outs.get(0).get(1).find { file(it).name == 'cell_feature_matrix.zarr.zip' }).exists() } + ) + } + } + + test("xeniumranger resegment stub") { + options "-stub" + when { + process { + """ + input[0] = channel.of([ + [id: "test_xeniumranger_resegment"], + ]).combine(UNZIP.out.unzipped_archive.map { it[1] }) + input[1] = [] + input[2] = [] + input[3] = [] + input[4] = [] + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} \ No newline at end of file diff --git a/modules/nf-core/xeniumranger/resegment/tests/main.nf.test.snap b/modules/nf-core/xeniumranger/resegment/tests/main.nf.test.snap new file mode 100644 index 00000000..16f94c14 --- /dev/null +++ b/modules/nf-core/xeniumranger/resegment/tests/main.nf.test.snap @@ -0,0 +1,35 @@ +{ + "xeniumranger resegment stub": { + "content": [ + { + "0": [ + [ + { + "id": "test_xeniumranger_resegment" + }, + "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,4671141281357e0ce26d9cb35fed23a8" + ], + "outs": [ + [ + { + "id": "test_xeniumranger_resegment" + }, + "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,4671141281357e0ce26d9cb35fed23a8" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-30T23:22:35.438329" + } +} \ No newline at end of file diff --git a/modules/nf-core/xeniumranger/resegment/tests/nextflow.config b/modules/nf-core/xeniumranger/resegment/tests/nextflow.config new file mode 100644 index 00000000..e69de29b diff --git a/modules/nf-core/xeniumranger/resegment/tests/tags.yml b/modules/nf-core/xeniumranger/resegment/tests/tags.yml new file mode 100644 index 00000000..99f47c82 --- /dev/null +++ b/modules/nf-core/xeniumranger/resegment/tests/tags.yml @@ -0,0 +1,2 @@ +xeniumranger/resegment: + - "modules/nf-core/xeniumranger/resegment/**" diff --git a/nextflow.config b/nextflow.config index e081c0c2..cf75ae06 100644 --- a/nextflow.config +++ b/nextflow.config @@ -1,6 +1,6 @@ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - nf-core/spatialxe Nextflow config file + nf-core/spatialaxe Nextflow config file ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Default config options for all compute environments ---------------------------------------------------------------------------------------- @@ -9,14 +9,113 @@ // Global default params, used in configs params { - // TODO nf-core: Specify your pipeline's command line flags // Input options - input = null + input = null // path to the samplesheet.csv containing meta,bundle,image + outdir = null // path to generate pipeline results at + mode = null // please check nextflow_schema.json for the modes you can select + method = null // name of the method to run for image or coordinate or segfree approaches + gene_panel = null // path to gene panel json file if `relabel_genes` is true + qupath_polygons = null // Path to qupath segmentation results in GeoJSON format + alignment_csv = null // image alignment file format a 3x3 transformation matrix, where the last row is [0,0,1] + cellpose_model = null // custom cellpose model to use for running or starting training + stardist_model = '2D_versatile_fluo' // stardist pretrained model for cell segmentation + stardist_nuclei_model = '2D_versatile_fluo' // stardist pretrained model for nuclei segmentation + stardist_prob_thresh = null // stardist probability threshold + stardist_nms_thresh = null // stardist NMS threshold + stardist_n_tiles = "8 8" // tiling for large images (Xenium images are ~20K×25K) + segmentation_mask = null // prior segmentation mask + probes_fasta = null // Fasta file for the probe sequences used in the xenium experiment + reference_annotations = null // Path to the genomic features (.gff) and fasta (.fa) files used as reference annotations + gene_synonyms = null // Gene synonyms that may have been counted as off-targets but simply differ in name - // References - genome = null - igenomes_base = 's3://ngi-igenomes/igenomes/' - igenomes_ignore = false + // GPU flag (set to true by the gpu profile) + use_gpu = false + gpu_queue = null // AWS Batch queue for GPU tasks (e.g., SEGGER, ProSeg) + cellpose_queue = null // AWS Batch queue for Cellpose (single large GPU) + + // execution specific + sharpen_tiff = false // wether to sharpen the morphology-focus tiff + nucleus_segmentation_only = false // to only run nucleus segmentation while running segmentation methods & XR_IMP-SEG + cell_segmentation_only = true // to only run cell segmentation while running segmentation methods & XR_IMP-SEG + cellpose_downscale = false // pre-downscale morphology image to avoid cellpose OOM on large images + + // Xeniumranger specific + xeniumranger_only = false // to generate redefined bundle with just changing the xr specific params + relabel_genes = false // wether to correct gene names with gene_panel.json + expansion_distance = 5 // default nuclear expansion distance in XOA v2.0 & later + dapi_filter = 100 // adjust the minimum peak intensity to use more nuclei + interior_stain = true // interior stain is enabled by default - false to disable + boundary_stain = true // boundary stain is enabled by default - false to disable + + // Segger specific + segmentation_refinement = false // wether to run segmentation refinement step (segger) + segger_accelerator = 'cpu' // either 'cuda' or 'cpu' + segger_knn_method = 'kd_tree' // 'cuda' - ensure your system has CUDA installed and configured properly + segger_num_workers = 4 // number of data-loader workers for segger + segger_model = null // path to a pre-trained segger model checkpoint + + // Proseg specific + format = 'xenium' // preset value set as `xenium` + + // Segmentation methods + image_seg_methods = ["cellpose", "xeniumranger", "baysor", "stardist"] + transcript_seg_methods = ["proseg", "segger", "baysor"] + segfree_methods = ["ficture", "baysor"] + + // Ficture specific + negative_control_regex = null + features = null + + // Baysor specific + filter_transcripts = false + min_qv = 20 + max_x = 24000.0 + min_x = 0.0 + max_y = 24000.0 + min_y = 0.0 + + // Generic tiling parameters (for proseg and other methods) + tiling = false // enable tiled segmentation (divide → parallel segmentation → stitch) + patch_grid = '3x3' // grid layout for tiling (rows x cols) + patch_overlap = 50 // overlap between patches in microns + patch_filter_method = null // post-stitch cell filtering: 'empirical', 'distribution', 'both', or null + patch_filter_iqr_multiplier = 3.0 // IQR multiplier for empirical cell size filtering + patch_filter_z_threshold = 4.0 // z-score threshold for distribution cell size filtering + + // Baysor-specific parameters + baysor_scale = 30 // Baysor --scale for non-tiled runs + baysor_config = null // path to baysor config TOML (optional) + baysor_tiling = true // enable tiled Baysor (divide → per-patch Baysor → stitch) + baysor_tiling_micron = 1200 // tile width in microns for Baysor tiling + baysor_tiling_overlap = 200 // overlap between Baysor patches in microns + baysor_tiling_balanced = true // balance transcripts across tiles (merge sparse tiles) + baysor_tiling_scale = 39 // Baysor --scale for tiled runs (larger to compensate for EM on tiles) + baysor_tiling_min_mols_per_cell = 120 // --min-molecules-per-cell for tiled Baysor + baysor_tiling_min_transcripts_per_cell = 50 // post-stitch cell filtering threshold + + // Baysor prior segmentation + // null — no prior (random EM init) + // 'cells' — Xenium bundle cell_id column (column-based, works with tiling) + // 'cellpose' — run Cellpose cell mask as image prior (non-tiled only) + baysor_prior = null + baysor_prior_confidence = 0.2 // prior-segmentation-confidence [0-1] + + // Segger specific + tile_width = 120 + tile_height = 120 + batch_size_train = 4 // larger batch size can speed up training, but requires more memory + devices = 4 // Use multiple GPUs by increasing the devices parameter to further accelerate training + max_epochs = 200 // increasing #epochs can improve model performance with more learning cycles, but extends training time + batch_size_predict = 1 // larger batch size can speed up training, but requires more memory + cc_analysis = false // to control connected component analysis + + // qc specific + run_qc = true // whether to run the qc layer of pipeline + offtarget_probe_tracking = false // whether to run off-target probe tracking (provide probe_fasta, reference sequences, gene synonyms ) + + // utility modules + csplit_x_bins = 2 // number of tiles along the x axis (total number of bins is product of x_bins * y_bins) + csplit_y_bins = 2 // number of tiles along the y axis // MultiQC options multiqc_config = null @@ -25,13 +124,18 @@ params { max_multiqc_email_size = '25.MB' multiqc_methods_description = null + // pipeline dev and testing option + buffer_samples = false // process one sample at a time from the multi-sample samplesheet + buffer_size = 1 // buffer size 0 means no buffering of samples + // Boilerplate options - outdir = null publish_dir_mode = 'copy' email = null email_on_fail = null plaintext_email = false + monochromeLogs = false monochrome_logs = false + hook_url = System.getenv('HOOK_URL') help = false help_full = false show_hidden = false @@ -47,15 +151,13 @@ params { custom_config_base = "https://raw.githubusercontent.com/nf-core/configs/${params.custom_config_version}" config_profile_contact = null config_profile_url = null + test_data_base = 'https://raw.githubusercontent.com/nf-core/test-datasets/spatialaxe' + // Schema validation default options 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' @@ -67,7 +169,7 @@ profiles { nextflow.enable.configProcessNamesValidation = true } conda { - conda.enabled = true + conda.enabled = false docker.enabled = false singularity.enabled = false podman.enabled = false @@ -77,8 +179,8 @@ profiles { apptainer.enabled = false } mamba { - conda.enabled = true - conda.useMamba = true + conda.enabled = false + conda.useMamba = false docker.enabled = false singularity.enabled = false podman.enabled = false @@ -87,32 +189,23 @@ profiles { apptainer.enabled = false } docker { - docker.enabled = true - conda.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false - apptainer.enabled = false - docker.runOptions = '-u $(id -u):$(id -g)' + docker.enabled = true + docker.fixOwnership = true + conda.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + docker.runOptions = '-u $(id -u):$(id -g)' } - arm64 { - process.arch = 'arm64' - // TODO https://github.com/nf-core/modules/issues/6694 - // For now if you're using arm64 you have to use wave for the sake of the maintainers - // wave profile - apptainer.ociAutoPull = true - singularity.ociAutoPull = true - wave.enabled = true - wave.freeze = true - wave.strategy = 'conda,container' - } - emulate_amd64 { - docker.runOptions = '-u $(id -u):$(id -g) --platform=linux/amd64' + arm { + docker.runOptions = '-u $(id -u):$(id -g) --platform=linux/amd64' } singularity { singularity.enabled = true singularity.autoMounts = true + singularity.pullTimeout = '100 min' conda.enabled = false docker.enabled = false podman.enabled = false @@ -164,25 +257,44 @@ profiles { wave.freeze = true wave.strategy = 'conda,container' } + gitpod { + executor.name = 'local' + executor.cpus = 8 + executor.memory = 16.GB + } gpu { + params.use_gpu = true docker.runOptions = '-u $(id -u):$(id -g) --gpus all' apptainer.runOptions = '--nv' singularity.runOptions = '--nv' } + aws { + process { + withLabel:process_gpu { + // Must repeat base.config label properties — profile withLabel replaces, not merges + ext.use_gpu = { params.use_gpu } + accelerator = { params.use_gpu ? 1 : null } + containerOptions = { "--shm-size ${task.memory.toGiga()}g" } + queue = { params.gpu_queue ?: null } + } + withLabel:process_gpu_single { + ext.use_gpu = { params.use_gpu } + accelerator = { params.use_gpu ? 1 : null } + containerOptions = { "--shm-size ${task.memory.toGiga()}g" } + queue = { params.cellpose_queue ?: params.gpu_queue ?: null } + } + } + } test { includeConfig 'conf/test.config' } test_full { includeConfig 'conf/test_full.config' } } -// 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. -// Load nf-core/spatialxe custom profiles from different institutions. +// Load nf-core custom profiles from different institutions │ includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !params.custom_config_base.startsWith('http')) ? "${params.custom_config_base}/nfcore_custom.config" : "/dev/null" - -// Load nf-core/spatialxe custom profiles from different institutions. -// TODO nf-core: Optionally, you can add a pipeline-specific nf-core config at https://github.com/nf-core/configs -// includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !params.custom_config_base.startsWith('http')) ? "${params.custom_config_base}/pipeline/spatialxe.config" : "/dev/null" +// Load nf-core/spatialaxe custom profiles from different institutions. +includeConfig !System.getenv('NXF_OFFLINE') && params.custom_config_base ? "${params.custom_config_base}/pipeline/spatialaxe.config" : "/dev/null" // Set default registry for Apptainer, Docker, Podman, Charliecloud and Singularity independent of -profile // Will not be used unless Apptainer / Docker / Podman / Charliecloud / Singularity are enabled @@ -193,18 +305,19 @@ podman.registry = 'quay.io' singularity.registry = 'quay.io' charliecloud.registry = 'quay.io' -// Load igenomes.config if required -includeConfig !params.igenomes_ignore ? 'conf/igenomes.config' : 'conf/igenomes_ignored.config' - // Export these variables to prevent local Python/R libraries from conflicting with those in the container // The JULIA depot path has been adjusted to a fixed path `/usr/local/share/julia` that needs to be used for packages in the container. // See https://apeltzer.github.io/post/03-julia-lang-nextflow/ for details on that. Once we have a common agreement on where to keep Julia packages, this is adjustable. env { + PYTORCH_CUDA_ALLOC_CONF = "expandable_segments:True" PYTHONNOUSERSITE = 1 R_PROFILE_USER = "/.Rprofile" R_ENVIRON_USER = "/.Renviron" JULIA_DEPOT_PATH = "/usr/local/share/julia" + MPLCONFIGDIR = "./tmp" + NUMBA_CACHE_DIR = "./tmp" + NUMBA_DISABLE_CACHE = 1 } // Set bash options @@ -219,6 +332,7 @@ process.shell = [ // Disable process selector warnings by default. Use debug profile to enable warnings. nextflow.enable.configProcessNamesValidation = false +nextflow.enable.moduleBinaries = true timeline { enabled = true @@ -229,8 +343,9 @@ report { file = "${params.outdir}/pipeline_info/execution_report_${params.trace_report_suffix}.html" } trace { - enabled = true - file = "${params.outdir}/pipeline_info/execution_trace_${params.trace_report_suffix}.txt" + enabled = true + overwrite = true + file = "${params.outdir}/pipeline_info/execution_trace_${params.trace_report_suffix}.txt" } dag { enabled = true @@ -238,32 +353,39 @@ dag { } manifest { - name = 'nf-core/spatialxe' + name = 'nf-core/spatialaxe' contributors = [ - // TODO nf-core: Update the field with the details of the contributors to your pipeline. New with Nextflow version 24.10.0 [ name: 'Sameesh Kher', - affiliation: '', - email: '', - github: '', - contribution: [], // List of contribution types ('author', 'maintainer' or 'contributor') - orcid: '' + affiliation: 'German Cancer Research Center (DKFZ), Heidelberg, DE', + email: 'sameesh.kher@dkfz-heidelberg.de', + github: '@khersameesh24', + contribution: ['author', 'maintainer'], + orcid: '0009-0008-2420-6464' ], [ - name: ' Florian Heyl', - affiliation: '', - email: '', - github: '', - contribution: [], // List of contribution types ('author', 'maintainer' or 'contributor') + name: 'Florian Heyl', + affiliation: 'German Cancer Research Center (DKFZ), Heidelberg, DE', + email: 'florian.heyl@dkfz-heidelberg.de', + github: '@heylf', + contribution: ['author', 'maintainer'], orcid: '' ], + [ + name: 'Dongze He', + affiliation: 'Altos Labs, San Diego, USA', + email: 'dongzehe.zaza@gmail.com', + github: '@dongzehe', + contribution: ['contributor'], + orcid: '0000-0001-8259-7434' + ] ] - homePage = 'https://github.com/nf-core/spatialxe' - description = """A pipeline for spatialomics Xenium In Situ data.""" + homePage = 'https://github.com/nf-core/spatialaxe' + description = """A pipeline for spatialomics 10x Xenium In Situ data.""" mainScript = 'main.nf' defaultBranch = 'master' - nextflowVersion = '!>=25.10.4' - version = '1.0.0' + nextflowVersion = '!>=25.04.0' + version = '1.1.0dev' doi = '' } @@ -276,5 +398,6 @@ 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 5d9dff39..58a47276 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -1,8 +1,8 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://raw.githubusercontent.com/nf-core/spatialxe/master/nextflow_schema.json", - "title": "nf-core/spatialxe pipeline parameters", - "description": "A pipeline for spatialomics Xenium In Situ data.", + "$id": "https://raw.githubusercontent.com/nf-core/spatialaxe/master/nextflow_schema.json", + "title": "nf-core/spatialaxe pipeline parameters", + "description": "A pipeline to process spatialomics data from 10x Xenium In Situ or 10x Atera.", "type": "object", "$defs": { "input_output_options": { @@ -10,7 +10,7 @@ "type": "object", "fa_icon": "fas fa-terminal", "description": "Define where the pipeline should find input data and save output data.", - "required": ["input", "outdir"], + "required": ["input", "outdir", "mode"], "properties": { "input": { "type": "string", @@ -19,8 +19,8 @@ "schema": "assets/schema_input.json", "mimetype": "text/csv", "pattern": "^\\S+\\.csv$", - "description": "Path to comma-separated file containing information about the samples in the experiment.", - "help_text": "You will need to create a design file with information about the samples in your experiment before running the pipeline. Use this parameter to specify its location. It has to be a comma-separated file with 3 columns, and a header row. See [usage docs](https://nf-co.re/spatialxe/usage#samplesheet-input).", + "description": "Path to comma-separated file containing information about the Xenium experiment. (eg; meta,path-to-xenium-bundle,path-to-morphology.ome.tif))", + "help_text": "You will need to create a design file with information about the samples in your experiment before running the pipeline. Use this parameter to specify its location. It has to be a comma-separated file with 3 columns, and a header row.", "fa_icon": "fas fa-file-csv" }, "outdir": { @@ -29,6 +29,79 @@ "description": "The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure.", "fa_icon": "fas fa-folder-open" }, + "mode": { + "type": "string", + "description": "Mode in which the pipeline is to be run. Either image-based segmentation, coordinate-based segmentation, segmentation-free analysis or data preview.", + "enum": ["image", "coordinate", "segfree", "preview", "qc"] + }, + "method": { + "type": "string", + "enum": ["cellpose", "xeniumranger", "baysor", "proseg", "segger", "ficture", "stardist"], + "description": "Segmentation method to run." + }, + "gene_panel": { + "type": "string", + "format": "file-path", + "description": "Path to gene panel JSON file to use for relabeling transcripts with the correct gene." + }, + "qupath_polygons": { + "type": "string", + "description": "Path to qupath segmentation file in GeoJSON format.", + "format": "file-path" + }, + "alignment_csv": { + "type": "string", + "description": "Image alignment file containing similarity transform matrix. (e.g., the _imagealignment.csv file exported from Xenium Explorer)", + "format": "file-path" + }, + "cellpose_model": { + "type": "string", + "description": "Model to use for running or starting training.", + "format": "file-path" + }, + "stardist_model": { + "type": "string", + "default": "2D_versatile_fluo", + "description": "StarDist pretrained model for cell segmentation (e.g., '2D_versatile_fluo', '2D_versatile_he')." + }, + "stardist_nuclei_model": { + "type": "string", + "default": "2D_versatile_fluo", + "description": "StarDist pretrained model for nuclei segmentation." + }, + "stardist_prob_thresh": { + "type": "number", + "description": "StarDist object probability threshold. Lower values detect more objects." + }, + "stardist_nms_thresh": { + "type": "number", + "description": "StarDist non-maximum suppression threshold. Lower values reduce overlapping detections." + }, + "stardist_n_tiles": { + "type": "string", + "default": "8 8", + "description": "StarDist tiling for large images (e.g., '4 4'). Reduces memory usage." + }, + "segmentation_mask": { + "type": "string", + "description": "Prior segmentation mask from other segmentation methods.", + "format": "file-path" + }, + "probes_fasta": { + "type": "string", + "description": "Fasta file for the probe sequences used in the xenium experiment.", + "format": "file-path" + }, + "reference_annotations": { + "type": "string", + "description": "Path to the directory containing genomic features (.gff) and fasta (.fa) files used as reference annotations.", + "format": "file-path" + }, + "gene_synonyms": { + "type": "string", + "description": "Gene synonyms that may have been counted as off-targets but simply differ in name.", + "format": "file-path" + }, "email": { "type": "string", "description": "Email address for completion summary.", @@ -43,41 +116,309 @@ } } }, - "reference_genome_options": { - "title": "Reference genome options", + "segmentation_options": { + "title": "Segmentation options", "type": "object", - "fa_icon": "fas fa-dna", - "description": "Reference genome related files and options required for the workflow.", + "description": "Options for the segmentation layer of the spatialaxe pipeline", + "default": "", "properties": { - "genome": { + "run_qc": { + "type": "boolean", + "description": "Whether to run the qc layer in the pipeline.", + "default": true + }, + "offtarget_probe_tracking": { + "type": "boolean", + "description": "Whether to run the off-target probe tracking.", + "default": false + }, + "segmentation_refinement": { + "type": "boolean", + "description": "Whether to run refinement on the image-based segmentation methods. Runs coordinate-based methods after the initial image-based segmentation run." + }, + "relabel_genes": { + "type": "boolean", + "description": "Whether to relabel genes with gene_panel.json file. True when gene_panel is provided." + }, + "xeniumranger_only": { + "type": "boolean", + "description": "Whether to run vanilla xeniumranger workflow." + }, + "cell_segmentation_only": { + "type": "boolean", + "description": "Whether to only run nucleus segmentation." + }, + "nucleus_segmentation_only": { + "type": "boolean", + "description": "Whether to only run nucleus segmentation." + }, + "expansion_distance": { + "type": "integer", + "default": 5, + "description": "Nuclei boundary expansion distance in µm. Default: 5 (Min: 0, Max: 15 if either boundary-stain or interior-stain are enabled and 100 if nucleus-expansion only)" + }, + "dapi_filter": { + "type": "integer", + "default": 100, + "description": "Minimum intensity in photoelectrons (pe) to filter nuclei. Default: 100. (appropriate range of values is 0 to 99th percentile of image stack or 1000, whichever is larger)" + }, + "interior_stain": { + "type": "boolean", + "default": true, + "description": "Specify the name of the interior stain to use or disable. Supported for cell segmentation staining workflow output bundles. Possible options are: \\\"18S\\\" (default) or \\\"disable\\\"" + }, + "boundary_stain": { + "type": "boolean", + "default": true, + "description": "Specify the name of the boundary stain to use or disable. Supported for cell segmentation staining workflow output bundles. Possible options are: \\\"ATP1A1/CD45/E-Cadherin\\\" (default) or \\\"disable\\\"" + }, + "use_gpu": { + "type": "boolean", + "default": false, + "description": "Enable GPU acceleration (set automatically by the gpu profile)." + }, + "gpu_queue": { "type": "string", - "description": "Name of iGenomes reference.", - "fa_icon": "fas fa-book", - "help_text": "If using a reference genome configured in the pipeline using iGenomes, use this parameter to give the ID for the reference. This is then used to build the full paths for all required reference genome files e.g. `--genome GRCh38`. \n\nSee the [nf-core website docs](https://nf-co.re/usage/reference_genomes) for more details." + "description": "AWS Batch queue for GPU tasks (e.g., Segger, ProSeg)." }, - "fasta": { + "cellpose_queue": { + "type": "string", + "description": "AWS Batch queue for Cellpose (single large GPU)." + }, + "cellpose_downscale": { + "type": "boolean", + "default": false, + "description": "Pre-downscale morphology image to avoid Cellpose OOM on large images." + }, + "sharpen_tiff": { + "type": "boolean", + "description": "Whether to enhance the morphology.ome.tif file." + }, + "segger_accelerator": { + "type": "string", + "default": "cpu", + "enum": ["cpu", "cuda"], + "description": "Device used for training. (e.g., cuda for GPU or cpu)" + }, + "segger_knn_method": { + "type": "string", + "default": "kd_tree", + "enum": ["kd_tree", "cuda"], + "description": "Method for KNN computation. (e.g., cuda for GPU-based computation)" + }, + "segger_num_workers": { + "type": "integer", + "default": 4, + "description": "Number of data-loader workers for Segger." + }, + "segger_model": { "type": "string", "format": "file-path", - "exists": true, - "mimetype": "text/plain", - "pattern": "^\\S+\\.fn?a(sta)?(\\.gz)?$", - "description": "Path to FASTA genome file.", - "help_text": "This parameter is *mandatory* if `--genome` is not specified. If you don't have a BWA index available this will be generated for you automatically. Combine with `--save_reference` to save BWA index for future runs.", - "fa_icon": "far fa-file-code" + "description": "Path to a pre-trained Segger model checkpoint." + }, + "format": { + "type": "string", + "default": "xenium", + "enum": ["xenium", "cosmx", "merscope"], + "description": "Input data platform. Used by proseg, segger, and spatialdata modules." + }, + "image_seg_methods": { + "type": "array", + "items": { + "type": "string", + "enum": ["cellpose", "xeniumranger", "baysor", "stardist"] + }, + "description": "List of image-based segmentation methods." }, - "igenomes_ignore": { + "transcript_seg_methods": { + "type": "array", + "items": { + "type": "string", + "enum": ["proseg", "segger", "baysor"] + }, + "description": "List of transcript-based segmentation methods." + }, + "segfree_methods": { + "type": "array", + "items": { + "type": "string", + "enum": ["ficture", "baysor"] + }, + "description": "List of segmentation-free methods." + }, + "negative_control_regex": { + "type": "string", + "description": "Regex used to identify or match negative control samples in a dataset." + }, + "features": { + "type": "string", + "description": "List of features to be passed to the ficture method. (eg: TP53,OCIAD1,BCAS3,SOX)" + }, + "filter_transcripts": { "type": "boolean", - "description": "Do not load the iGenomes reference config.", - "fa_icon": "fas fa-ban", - "hidden": true, - "help_text": "Do not load `igenomes.config` when running the pipeline. You may choose this option if you observe clashes between custom parameters and those supplied in `igenomes.config`." + "description": "Whether to filter the transcripts.parquet file before running Baysor segmentation.", + "default": false }, - "igenomes_base": { + "baysor_scale": { + "type": "integer", + "default": 30, + "description": "Baysor --scale parameter for non-tiled runs." + }, + "baysor_config": { "type": "string", - "description": "The base path to the igenomes reference files", - "fa_icon": "fas fa-ban", - "hidden": true, - "default": "s3://ngi-igenomes/igenomes/" + "format": "file-path", + "description": "Path to Baysor config TOML file (optional)." + }, + "baysor_tiling": { + "type": "boolean", + "default": true, + "description": "Enable tiled Baysor segmentation (divide transcripts into patches, run Baysor per patch, stitch results)." + }, + "baysor_tiling_micron": { + "type": "integer", + "default": 1200, + "description": "Tile width in microns for Baysor tiling." + }, + "baysor_tiling_overlap": { + "type": "integer", + "default": 200, + "description": "Overlap between Baysor patches in microns." + }, + "baysor_tiling_balanced": { + "type": "boolean", + "default": true, + "description": "Balance transcripts across tiles by merging sparse tiles." + }, + "baysor_tiling_scale": { + "type": "integer", + "default": 39, + "description": "Baysor --scale for tiled runs (larger to compensate for EM on smaller tiles)." + }, + "baysor_tiling_min_mols_per_cell": { + "type": "integer", + "default": 120, + "description": "Minimum molecules per cell (--min-molecules-per-cell) for tiled Baysor." + }, + "baysor_tiling_min_transcripts_per_cell": { + "type": "integer", + "default": 50, + "description": "Post-stitch cell filtering threshold: minimum transcripts per cell." + }, + "baysor_prior": { + "type": "string", + "enum": ["cells", "cellpose"], + "description": "Prior segmentation type for Baysor. 'cells' uses Xenium bundle cell_id column; 'cellpose' uses Cellpose mask as image prior." + }, + "baysor_prior_confidence": { + "type": "number", + "default": 0.2, + "description": "Baysor prior-segmentation-confidence (0-1)." + }, + "min_qv": { + "type": "number", + "default": 20, + "description": "Minimum Q-Score to pass filtering." + }, + "min_x": { + "type": "number", + "description": "only keep transcripts whose x-coordinate is greater than specified limit, if no limit is specified, the default minimum value will be 0.0" + }, + "max_x": { + "type": "number", + "description": "only keep transcripts whose x-coordinate is less than specified limit, if no limit is specified, the default value will retain all transcripts since Xenium slide is <24000 microns in x and y (default: 24000.0)" + }, + "min_y": { + "type": "number", + "description": "only keep transcripts whose y-coordinate is greater than specified limit, if no limit is specified, the default minimum value will be 0.0" + }, + "max_y": { + "type": "number", + "description": "only keep transcripts whose y-coordinate is less than specified limit, if no limit is specified, the default value will retain all transcripts since Xenium slide is <24000 microns in x and y (default: 24000.0)" + }, + "tiling": { + "type": "boolean", + "description": "Enable tiled segmentation for large datasets. Divides transcripts into overlapping patches, runs segmentation in parallel per patch, then stitches results.", + "default": false + }, + "patch_grid": { + "type": "string", + "description": "Grid layout for tiling (rows x cols), e.g. '3x3', '4x4'.", + "default": "3x3" + }, + "patch_overlap": { + "type": "integer", + "description": "Overlap between adjacent patches in microns.", + "default": 50 + }, + "patch_filter_method": { + "type": "string", + "description": "Post-stitch cell size filtering method. Options: 'empirical' (IQR-based), 'distribution' (z-score), 'both', or null to disable.", + "enum": ["empirical", "distribution", "both"] + }, + "patch_filter_iqr_multiplier": { + "type": "number", + "description": "IQR multiplier for empirical cell size filtering during stitching.", + "default": 3.0 + }, + "patch_filter_z_threshold": { + "type": "number", + "description": "Z-score threshold for distribution-based cell size filtering during stitching.", + "default": 4.0 + }, + "csplit_x_bins": { + "type": "integer", + "default": 2, + "description": "Number of tiles along the x axis for cell-type separability." + }, + "csplit_y_bins": { + "type": "integer", + "default": 2, + "description": "Number of tiles along the y axis for cell-type separability." + }, + "tile_width": { + "type": "integer", + "description": "Width of the tiles in pixels", + "default": 120 + }, + "tile_height": { + "type": "integer", + "description": "Height of the tiles in pixels", + "default": 120 + }, + "batch_size_train": { + "type": "integer", + "description": "Number of samples to process per training batch", + "default": 4 + }, + "devices": { + "type": "integer", + "description": "Number of devices (GPUs) to use during training", + "default": 4 + }, + "max_epochs": { + "type": "integer", + "description": "Number of training epochs", + "default": 200 + }, + "batch_size_predict": { + "type": "integer", + "description": "Number of samples to process per batch during prediction", + "default": 1 + }, + "cc_analysis": { + "type": "boolean", + "description": "Whether to use connected components for grouping transcripts without direct nucleus association", + "default": false + }, + "buffer_samples": { + "type": "boolean", + "description": "Process only one sample at a time from a multi-sample samplesheet.", + "default": false + }, + "buffer_size": { + "type": "integer", + "description": "Number of sample(s) to process at a time from a multi-sample samplesheet. Works if buffered_samples is true.", + "default": 1 } } }, @@ -126,6 +467,11 @@ "description": "Institutional config URL link.", "hidden": true, "fa_icon": "fas fa-users-cog" + }, + "test_data_base": { + "type": "string", + "default": "https://raw.githubusercontent.com/nf-core/test-datasets/spatialaxe", + "description": "Base path / URL for data used in the test profiles." } } }, @@ -136,6 +482,12 @@ "description": "Less common options for the pipeline, typically set in a config file.", "help_text": "These options are common to all nf-core pipelines and allow you to customise some of the core preferences for how the pipeline runs.\n\nTypically these options would be set in a Nextflow config file loaded for all pipeline runs, such as `~/.nextflow/config`.", "properties": { + "help": { + "type": "boolean", + "description": "Display help text.", + "fa_icon": "fas fa-question-circle", + "hidden": true + }, "version": { "type": "boolean", "description": "Display version and exit.", @@ -179,6 +531,13 @@ "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", @@ -204,23 +563,23 @@ "fa_icon": "fas fa-check-square", "hidden": true }, + "monochromeLogs": { + "type": "boolean", + "description": "Do not use coloured log outputs", + "hidden": true + }, "pipelines_testdata_base_path": { "type": "string", - "fa_icon": "far fa-check-circle", - "description": "Base URL or local path to location of pipeline test dataset files", "default": "https://raw.githubusercontent.com/nf-core/test-datasets/", + "description": "Base URL or local path to location of pipeline test dataset files", "hidden": true }, "trace_report_suffix": { "type": "string", "fa_icon": "far calendar", - "description": "Suffix to add to the trace report filename. Default is the date and time in the format yyyy-MM-dd_HH-mm-ss.", + "description": "Suffix to add to the trace report filename. Default is the date and time in the format yyyy-MM-dd_HH-mm-ss", "hidden": true }, - "help": { - "type": ["boolean", "string"], - "description": "Display the help message." - }, "help_full": { "type": "boolean", "description": "Display the full detailed help message." @@ -237,7 +596,7 @@ "$ref": "#/$defs/input_output_options" }, { - "$ref": "#/$defs/reference_genome_options" + "$ref": "#/$defs/segmentation_options" }, { "$ref": "#/$defs/institutional_config_options" diff --git a/nf-test.config b/nf-test.config index f7aaeb4a..3a1fff59 100644 --- a/nf-test.config +++ b/nf-test.config @@ -1,35 +1,21 @@ 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 = [ - '.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', - ] + triggers 'nextflow.config', 'nf-test.config', 'conf/test.config', 'tests/nextflow.config', 'tests/.nftignore' // load the necessary plugins plugins { diff --git a/ro-crate-metadata.json b/ro-crate-metadata.json index 2d0ac5c2..e09dc9f3 100644 --- a/ro-crate-metadata.json +++ b/ro-crate-metadata.json @@ -21,9 +21,9 @@ { "@id": "./", "@type": "Dataset", - "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", + "creativeWorkStatus": "InProgress", + "datePublished": "2026-06-17T15:15:09+00:00", + "description": "

\n \n \n \"nf-core/spatialaxe\"\n \n

\n\n[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new/nf-core/spatialaxe)\n[![GitHub Actions CI Status](https://github.com/nf-core/spatialaxe/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/spatialaxe/actions/workflows/nf-test.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-core/spatialaxe/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/spatialaxe/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/spatialaxe/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 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/spatialaxe)\n\n[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23spatialaxe-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/spatialaxe)[![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/spatialaxe** is a bioinformatics best-practice processing and quality control pipeline for Xenium (and soon Atera) 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**.\n\n![nf-core/spatialaxe-metromap](docs/images/spatialaxe-metromap.png)\n\n> [!NOTE]\n> We are currently extending the pipeline for the [10x Atera system](https://www.10xgenomics.com/platforms/atera).\n\n## Tools supported\n\nThe pipeline supports the following tools:\n\n- Segmenation methods:\n - [Baysor](https://doi.org/10.1038/s41587-021-01044-w)\n - [Cellpose](https://doi.org/10.1038/s41592-020-01018-x)\n - [Xenium ranger (XR)](https://www.10xgenomics.com/support/software/xenium-ranger/latest)\n - [StarDist](https://doi.org/10.48550/arXiv.2203.02284)\n- Segmentation free methods:\n - [Ficture](https://doi.org/10.1038/s41592-024-02415-2)\n - [Baysor](https://doi.org/10.1038/s41587-021-01044-w)\n- Transcript assignment methods:\n - [Segger](https://doi.org/10.1101/2025.03.14.643160)\n - [Proseg](https://doi.org/10.1038/s41592-025-02697-0)\n- Utility methods:\n - [SpatialData](https://doi.org/10.1038/s41592-024-02212-x)\n - [Baysor](https://doi.org/10.1038/s41587-021-01044-w)\n- QC methods:\n - [MultiQC Xenium Extra Plugin](https://github.com/MultiQC/xenium-extra)\n - [OPT](https://github.com/JEFworks-Lab/off-target-probe-tracker)\n\n## Usage\n\nOn 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/spatialaxe/results).\n\n> [!NOTE]\n> The pipeline does not support conda currently. We are working on it.\n\n## Quick Start\n\n`samplesheet.csv`:\n\n```csv\nsample,bundle,image\ntest_sample,/path/to/xenium-bundle,/path/to/morphology.ome.tif\n```\n\nNow, you can run the pipeline using:\n\n### Run image-based segmentation mode
\n\n`CELLPOSE -> BAYSOR -> XR-IMPORT_SEGMENTATION -> SPATIALDATA -> QC`\n\n```bash\nnextflow run nf-core/spatialaxe \\\n -profile \\\n --input samplesheet.csv \\\n --outdir \\\n --mode \n```\n\n### Run coordinate-based segmentation mode
\n\n`PROSEG -> PROSEG2BAYSOR -> XR-IMPORT_SEGMENTATION -> SPATIALDATA -> QC`\n\n```bash\nnextflow run nf-core/spatialaxe \\\n -profile \\\n --input samplesheet.csv \\\n --outdir \\\n --mode coordinate\n```\n\n### Run segfree mode
\n\n`BAYSOR_SEGFREE`\n\n```bash\nnextflow run nf-core/spatialaxe \\\n -profile \\\n --input samplesheet.csv \\\n --outdir \\\n --mode segfree\n```\n\n### Run preview mode
\n\n`BAYSOR_PREVIEW`\n\n```bash\nnextflow run nf-core/spatialaxe \\\n -profile \\\n --input samplesheet.csv \\\n --outdir \\\n --mode preview\n```\n\n### Run just the quality control
\n\n```bash\nnextflow run nf-core/spatialaxe \\\n -profile \\\n --input samplesheet.csv \\\n --outdir \\\n --mode qc\n```\n\n### Additional information\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/spatialaxe/usage) and the [parameter documentation](https://nf-co.re/spatialaxe/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/spatialaxe/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/spatialaxe/output).\n\n## Runtime and resource estimations\n\n| Tool | Compute | Runtime (min / med / max) | Peak RSS (min / med / max) |\n| ------------------------- | ------- | ------------------------- | -------------------------- |\n| Cellpose | GPU | 1m / 4m / 1.4h | 10 GB / 26 GB / 554 GB |\n| Cellpose | CPU | 1.3h / 2.3h / 6.5h | 161 GB / 426 GB / 1115 GB |\n| StarDist | GPU | 1m / 4m / 7m | 5 GB / 12 GB / 18 GB |\n| StarDist | CPU | 5m / 6m / 7m | 18 GB / 18 GB / 18 GB |\n| Segger (create_dataset) | GPU | 2m / 9m / 31m | 1.7 GB / 14 GB / 50 GB |\n| Segger (create_dataset) | CPU | 13m / 21m / 46m | 13 GB / 19 GB / 49 GB |\n| Segger (train) | GPU | 10m / 43m / 2.9h | 30 GB / 33 GB / 60 GB |\n| Segger (predict) | GPU | 2m / 16m / 59m | 10 GB / 25 GB / 87 GB |\n| Baysor (whole-image) | CPU | 2m / 30m / 17h | 6 GB / 10 GB / 650 GB |\n| Baysor (tiled) | CPU | 1m / 18m / 13h | 0.2 GB / 34 GB / 530 GB |\n| Proseg | CPU | 1m / 18m / 6.8h | 279 MB / 3.8 GB / 136 GB |\n| XeniumRanger (resegment) | CPU | 18m / 39m / 3.7h | 28 GB / 54 GB / 60 GB |\n| XeniumRanger (import_seg) | CPU | 2m / 7m / 2.7h | 2.6 GB / 11 GB / 51 GB |\n| Ficture (preprocess) | CPU | 3m / 4m / 13m | 331 MB / 357 MB / 21 GB |\n\n- Cellpose GPU vs CPU: 35x faster on GPU (4m median vs 2.3h), 16x less memory (26 GB vs 426 GB)\n- Segger: Only tool that truly requires GPU for all 3 steps (create_dataset, train, predict)\n- StarDist: Very fast on CPU, GPU is not necessary to run its default model\n\n## Credits\n\nnf-core/spatialaxe is mainly developed by [Sameesh Kher](https://github.com/khersameesh24), [Dongze He](https://github.com/dongzehe), and [Florian Heyl](https://github.com/heylf).\n\nWe thank the following people for their extensive assistance in the development of this pipeline:\n\n- Tobias Krause\n- Krešimir Beštak (kbestak)\n- Matthias Hörtenhuber (mashehu)\n- Maxime Garcia (maxulysse)\n- Kübra Narcı (kubranarci)\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 `#spatialaxe` channel](https://nfcore.slack.com/channels/spatialaxe) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## Citations\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" @@ -31,6 +31,9 @@ { "@id": "assets/" }, + { + "@id": "bin/" + }, { "@id": "conf/" }, @@ -43,6 +46,9 @@ { "@id": "modules/" }, + { + "@id": "modules/local/" + }, { "@id": "modules/nf-core/" }, @@ -92,17 +98,17 @@ "@id": ".prettierignore" } ], - "isBasedOn": "https://github.com/nf-core/spatialxe", + "isBasedOn": "https://github.com/nf-core/spatialaxe", "license": "MIT", "mainEntity": { "@id": "main.nf" }, "mentions": [ { - "@id": "#1c52c7cb-def3-43c9-9e10-865c2cf0ba78" + "@id": "#7c4a8872-f8d4-4f4b-a7f2-c2754d484851" } ], - "name": "nf-core/spatialxe" + "name": "nf-core/spatialaxe" }, { "@id": "ro-crate-metadata.json", @@ -121,22 +127,24 @@ }, { "@id": "main.nf", - "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], - "contributor": [ + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "creator": [ { "@id": "https://orcid.org/0009-0008-2420-6464" - }, - { - "@id": "https://orcid.org/0000-0002-3651-5685" } ], "dateCreated": "", - "dateModified": "2026-04-30T13:33:23Z", + "dateModified": "2026-06-17T17:15:09Z", "dct:conformsTo": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE/", "keywords": [ "nf-core", "nextflow", "10x-genomics", + "atera", "image-processing", "spatial", "spatial-data-analysis", @@ -144,16 +152,30 @@ "transcriptomics", "xenium" ], - "license": ["MIT"], - "name": ["nf-core/spatialxe"], + "license": [ + "MIT" + ], + "maintainer": [ + { + "@id": "https://orcid.org/0009-0008-2420-6464" + } + ], + "name": [ + "nf-core/spatialaxe" + ], "programmingLanguage": { "@id": "https://w3id.org/workflowhub/workflow-ro-crate#nextflow" }, "sdPublisher": { "@id": "https://nf-co.re/" }, - "url": ["https://github.com/nf-core/spatialxe", "https://nf-co.re/spatialxe/1.0.0/"], - "version": ["1.0.0"] + "url": [ + "https://github.com/nf-core/spatialaxe", + "https://nf-co.re/spatialaxe/dev/" + ], + "version": [ + "1.1.0dev" + ] }, { "@id": "https://w3id.org/workflowhub/workflow-ro-crate#nextflow", @@ -165,26 +187,26 @@ "url": { "@id": "https://www.nextflow.io/" }, - "version": "!>=25.10.4" + "version": "!>=25.04.0" }, { - "@id": "#1c52c7cb-def3-43c9-9e10-865c2cf0ba78", + "@id": "#7c4a8872-f8d4-4f4b-a7f2-c2754d484851", "@type": "TestSuite", "instance": [ { - "@id": "#162edd3a-9c52-463a-94f5-78ed109f8513" + "@id": "#3ab54b2a-9518-45a0-8008-3839d03402c0" } ], "mainEntity": { "@id": "main.nf" }, - "name": "Test suite for nf-core/spatialxe" + "name": "Test suite for nf-core/spatialaxe" }, { - "@id": "#162edd3a-9c52-463a-94f5-78ed109f8513", + "@id": "#3ab54b2a-9518-45a0-8008-3839d03402c0", "@type": "TestInstance", - "name": "GitHub Actions workflow for testing nf-core/spatialxe", - "resource": "repos/nf-core/spatialxe/actions/workflows/nf-test.yml", + "name": "GitHub Actions workflow for testing nf-core/spatialaxe", + "resource": "repos/nf-core/spatialaxe/actions/workflows/nf-test.yml", "runsOn": { "@id": "https://w3id.org/ro/terms/test#GithubService" }, @@ -203,6 +225,11 @@ "@type": "Dataset", "description": "Additional files" }, + { + "@id": "bin/", + "@type": "Dataset", + "description": "Scripts that must be callable from a pipeline process" + }, { "@id": "conf/", "@type": "Dataset", @@ -223,6 +250,11 @@ "@type": "Dataset", "description": "Modules used by the pipeline" }, + { + "@id": "modules/local/", + "@type": "Dataset", + "description": "Pipeline-specific modules" + }, { "@id": "modules/nf-core/", "@type": "Dataset", @@ -312,12 +344,8 @@ { "@id": "https://orcid.org/0009-0008-2420-6464", "@type": "Person", + "email": "khersameesh24@gmail.com", "name": "Sameesh Kher" - }, - { - "@id": "https://orcid.org/0000-0002-3651-5685", - "@type": "Person", - "name": "Florian Heyl" } ] -} +} \ No newline at end of file diff --git a/subworkflows/local/baysor_generate_preview/main.nf b/subworkflows/local/baysor_generate_preview/main.nf new file mode 100644 index 00000000..2494fcbd --- /dev/null +++ b/subworkflows/local/baysor_generate_preview/main.nf @@ -0,0 +1,49 @@ +// +// Run baysor create_dataset & preview +// + +include { BAYSOR_PREVIEW } from '../../../modules/local/baysor/preview/main' +include { BAYSOR_CREATE_DATASET } from '../../../modules/local/baysor/create_dataset/main' +include { EXTRACT_PREVIEW_DATA } from '../../../modules/local/utility/extract_preview_data/main' +include { PARQUET_TO_CSV } from '../../../modules/local/utility/parquet_to_csv/main' + +workflow BAYSOR_GENERATE_PREVIEW { + take: + ch_transcripts_file // channel: [ val(meta), ["path-to-transcripts.parquet"] ] + ch_config // channel: ["path-to-xenium.toml"] + + main: + + ch_preview_mqc_html = channel.empty() + ch_preview_mqc_png = channel.empty() + + + // run parquet to csv + PARQUET_TO_CSV(ch_transcripts_file, ".csv") + + // generate randomised sample data + BAYSOR_CREATE_DATASET(PARQUET_TO_CSV.out.transcripts_csv, 0.3) + + // run baysor preview if param - generate_preview is true + ch_sampled_transcripts = BAYSOR_CREATE_DATASET.out.sampled_transcripts + ch_baysor_preview_input = ch_sampled_transcripts + .combine(ch_config) + .map { meta, transcripts, config -> + tuple( + meta, + transcripts, + config + ) + } + BAYSOR_PREVIEW(ch_baysor_preview_input) + + // clean the preview html file generated + EXTRACT_PREVIEW_DATA(BAYSOR_PREVIEW.out.preview_html) + + ch_preview_mqc_html = EXTRACT_PREVIEW_DATA.out.mqc_data + ch_preview_mqc_png = EXTRACT_PREVIEW_DATA.out.mqc_img + + emit: + preview_html = ch_preview_mqc_html // channel: [ val(meta), ["*_mqc.tsv"] ] + preview_img = ch_preview_mqc_png // channel: [ val(meta), ["*_mqc.png"] ] +} diff --git a/subworkflows/local/baysor_generate_preview/meta.yml b/subworkflows/local/baysor_generate_preview/meta.yml new file mode 100644 index 00000000..5807c233 --- /dev/null +++ b/subworkflows/local/baysor_generate_preview/meta.yml @@ -0,0 +1,34 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "baysor_generate_preview" +description: quick preview to get meaning from the data and to get some guesses about the parameters of the full baysor run +keywords: + - baysor + - preview + - transcripts + - report + - preview_html + - html_report + - visualization +components: + - baysor/preview + - baysor/create/dataset + - parquet/to/csv + - extract/preview/data +input: + - ch_transcripts_parquet: + description: | + input parquet file from the xenium bundle + Structure: [ val(meta), path("path-to-transcripts.parquet") ] + - ch_config: + description: | + config file for the xenium baysor run (stored in assets/config/xenium.toml) + Structure: [ path("path-to-xenium.toml") ] +output: + - preview_html: + description: | + Preview html file generated with the baysor preview command + Structure: [ val(meta), path("path-to-preview.html") ] +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/subworkflows/local/baysor_generate_segfree/main.nf b/subworkflows/local/baysor_generate_segfree/main.nf new file mode 100644 index 00000000..74a160f0 --- /dev/null +++ b/subworkflows/local/baysor_generate_segfree/main.nf @@ -0,0 +1,52 @@ +// +// Run baysor segfree +// + +include { BAYSOR_PREPROCESS_TRANSCRIPTS } from '../../../modules/local/baysor/preprocess/main' +include { BAYSOR_SEGFREE } from '../../../modules/local/baysor/segfree/main' +// include a module to process the output loom file with scapny or anndata + +workflow BAYSOR_GENERATE_SEGFREE { + take: + ch_transcripts_file // channel: [ val(meta), ["transcripts.parquet"] ] + ch_config // channel: [ ["path-to-xenium.toml"] ] + max_x // value: spatial filter upper x bound + max_y // value: spatial filter upper y bound + min_qv // value: minimum transcript QV + min_x // value: spatial filter lower x bound + min_y // value: spatial filter lower y bound + + main: + + ch_transcripts = channel.empty() + + // Always preprocess transcripts.parquet to CSV for Baysor 0.7.1 compatibility. + // Baysor's Julia Parquet.jl cannot read zstd-compressed parquet files from Xenium bundles. + // Also applies optional spatial/QV filtering when filter_transcripts is true. + BAYSOR_PREPROCESS_TRANSCRIPTS( + ch_transcripts_file, + min_qv, + max_x, + min_x, + max_y, + min_y, + ) + ch_transcripts = BAYSOR_PREPROCESS_TRANSCRIPTS.out.transcripts_file + + // run baysor segfree + ch_baysor_segfree_input = ch_transcripts + .combine(ch_config) + .map { meta, transcripts, config -> + tuple( + meta, + transcripts, + config + ) + } + BAYSOR_SEGFREE( + ch_baysor_segfree_input + ) + + emit: + ncvs = BAYSOR_SEGFREE.out.ncvs // channel: [ val(meta), ["ncvs.loom"] ] +} diff --git a/subworkflows/local/baysor_generate_segfree/meta.yml b/subworkflows/local/baysor_generate_segfree/meta.yml new file mode 100644 index 00000000..b312ff00 --- /dev/null +++ b/subworkflows/local/baysor_generate_segfree/meta.yml @@ -0,0 +1,28 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "baysor_generate_segfree" +description: with segfree analyses that don't require segmentation, can be run on local neighborhoods instead (Neighborhood Composition Vectors (NCVs)) +keywords: + - baysor + - segfree + - neighborhoods + - loom +components: + - baysor/segfree +input: + - ch_transcripts_parquet: + description: | + input parquet file from the xenium bundle + Structure: [ val(meta), path("path-to-transcripts.parquet") ] + - ch_config: + description: | + config file for the xenium baysor run (stored in assets/config/xenium.toml) + Structure: [ path("path-to-xenium.toml") ] +output: + - ncvs: + description: | + loom file generated with the baysor segfree command + Structure: [ val(meta), path("path-to-ncvs.loom") ] +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/subworkflows/local/baysor_run_prior_segmentation_mask/main.nf b/subworkflows/local/baysor_run_prior_segmentation_mask/main.nf new file mode 100644 index 00000000..d5acc0a1 --- /dev/null +++ b/subworkflows/local/baysor_run_prior_segmentation_mask/main.nf @@ -0,0 +1,83 @@ +// +// Run baysor run & import-segmentation +// + +include { BAYSOR_PREPROCESS_TRANSCRIPTS } from '../../../modules/local/baysor/preprocess/main' +include { BAYSOR_RUN } from '../../../modules/local/baysor/run/main' +include { XENIUMRANGER_IMPORT_SEGMENTATION } from '../../../modules/nf-core/xeniumranger/import-segmentation/main' + + +workflow BAYSOR_RUN_PRIOR_SEGMENTATION_MASK { + take: + ch_bundle_path // channel: [ val(meta), ["path-to-xenium-bundle"] ] + ch_transcripts_file // channel: [ val(meta), ["path-to-transcripts.parquet"] ] + ch_segmentation_mask // channel: [ ["path-to-prior-segmentation-mask"] ] + ch_config // channel: [ "path-to-xenium.toml" ] + max_x // value: spatial filter upper x bound + max_y // value: spatial filter upper y bound + min_qv // value: minimum transcript QV + min_x // value: spatial filter lower x bound + min_y // value: spatial filter lower y bound + + main: + + ch_transcripts = channel.empty() + + ch_redefined_bundle = channel.empty() + ch_coordinate_space = channel.value("pixels") + + // Always preprocess transcripts.parquet to CSV for Baysor 0.7.1 compatibility. + // Baysor's Julia Parquet.jl cannot read zstd-compressed parquet files from Xenium bundles. + // Also applies optional spatial/QV filtering when filter_transcripts is true. + BAYSOR_PREPROCESS_TRANSCRIPTS( + ch_transcripts_file, + min_qv, + max_x, + min_x, + max_y, + min_y, + ) + ch_transcripts = BAYSOR_PREPROCESS_TRANSCRIPTS.out.transcripts_file + + + // run baysor with prior segmentation mask + ch_baysor_input = ch_transcripts + .combine(ch_segmentation_mask) + .combine(ch_config) + .map { meta, transcripts, mask, config -> + tuple( + meta, + transcripts, + mask, + config, + 30, + ) + } + BAYSOR_RUN(ch_baysor_input) + + + // run import-segmentation with baysor outs + ch_imp_seg_inputs = ch_bundle_path + .combine(BAYSOR_RUN.out.segmentation, by: 0) + .map { meta, bundle, _segmentation_csv, polygons2d -> + tuple( + meta, + bundle, + [], + [], + polygons2d, + polygons2d, + [], + ch_coordinate_space.val, + ) + } + XENIUMRANGER_IMPORT_SEGMENTATION( + ch_imp_seg_inputs + ) + + ch_redefined_bundle = XENIUMRANGER_IMPORT_SEGMENTATION.out.outs + + emit: + coordinate_space = ch_coordinate_space // channel: [ "pixels" ] + redefined_bundle = ch_redefined_bundle // channel: [ val(meta), ["redefined-xenium-bundle"] ] +} diff --git a/subworkflows/local/baysor_run_prior_segmentation_mask/meta.yml b/subworkflows/local/baysor_run_prior_segmentation_mask/meta.yml new file mode 100644 index 00000000..f01f44c7 --- /dev/null +++ b/subworkflows/local/baysor_run_prior_segmentation_mask/meta.yml @@ -0,0 +1,58 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "baysor_run_prior_segmentation_mask" +description: to run the `baysor run` command if a prior segmentation mask is available +keywords: + - baysor + - baysor run + - segmentation + - segmentation mask + - xeniumranger import-segmentation + - image-based segmentation + - transcript filtering + - polygons +components: + - baysor/preprocess/transcripts + - baysor/run + - xeniumranger/import/segmentation +input: + - ch_bundle_path: + description: | + path to the xenium bundle + Structure: [ val(meta), path("path-to-xenium-bundle") ] + - ch_transcripts_parquet: + description: | + input parquet file from the xenium bundle + Structure: [ val(meta), path("path-to-transcripts.parquet") ] + - ch_segmentation_mask: + description: | + prior segmentation mask filepath + Structure: [ path("path-to-prior-segmentation-mask.tif") ] + - ch_config: + description: | + config file for the xenium baysor run (stored in assets/config/xenium.toml) + Structure: [ path("path-to-xenium.toml") ] +output: + - segmentation: + description: | + the segmentation.csv file generated from the baysor run command + Structure: [ val(meta), path("segmentation.csv") ] + - polygons2d: + description: | + the segmentation_polygons_2d.json file generated from the baysor run command + Structure: [ val(meta), path("segmentation_polygons_2d.json") ] + - htmls: + description: | + the html files generated from the baysor run command + Structure: [ val(meta), path("*.html") ] + - coordinate_space: + description: | + the coordinate space in which xeniumranger import-segmentation was run + Structure: [ val("microns") ] + - redefined_bundle: + description: | + the redefined xenium bundle generated with the segmentation results from baysor + Structure: [ val(meta), ["redefined-xenium-bundle"] ] +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/subworkflows/local/baysor_run_transcripts_parquet/main.nf b/subworkflows/local/baysor_run_transcripts_parquet/main.nf new file mode 100644 index 00000000..aff27051 --- /dev/null +++ b/subworkflows/local/baysor_run_transcripts_parquet/main.nf @@ -0,0 +1,166 @@ +// +// Unified Baysor subworkflow: handles both tiled and non-tiled paths. +// +// When baysor_tiling=true: divide → per-patch Baysor → stitch → xeniumranger +// When baysor_tiling=false: preprocess → Baysor → xeniumranger +// +// Prior segmentation support: +// Column-based (cells): works with both tiled and non-tiled +// Image-based (cellpose): non-tiled only (mask passed to Baysor) +// + +include { XENIUM_PATCH_DIVIDE } from '../../../modules/local/xenium_patch/divide/main' +include { PARQUET_TO_CSV } from '../../../modules/local/parquet_to_csv/main' +include { BAYSOR_RUN } from '../../../modules/local/baysor/run/main' +include { BAYSOR_PREPROCESS_TRANSCRIPTS } from '../../../modules/local/baysor/preprocess/main' +include { XENIUM_PATCH_STITCH } from '../../../modules/local/xenium_patch/stitch/main' +include { RECONSTRUCT_PATCHES } from '../../../modules/local/utility/reconstruct_patches/main' +include { XENIUMRANGER_IMPORT_SEGMENTATION } from '../../../modules/nf-core/xeniumranger/import-segmentation/main' + + +workflow BAYSOR_RUN_TRANSCRIPTS_PARQUET { + + take: + ch_bundle_path // channel: [ val(meta), ["path-to-xenium-bundle"] ] + ch_transcripts_file // channel: [ val(meta), ["transcripts.parquet"] ] + ch_morphology_image // channel: [ val(meta), ["morphology_focus.ome.tif"] ] + ch_config // channel: ["path-to-xenium.toml"] + ch_prior_mask // channel: [ val(meta), ["resized_mask.tif"] ] or empty (cellpose) + baysor_config // value: path to baysor config TOML (or null) + baysor_scale // value: Baysor --scale for non-tiled runs + baysor_tiling // value: bool — enable tiling + baysor_tiling_scale // value: Baysor --scale for tiled runs + max_x // value: spatial filter upper x bound + max_y // value: spatial filter upper y bound + min_qv // value: minimum transcript QV + min_x // value: spatial filter lower x bound + min_y // value: spatial filter lower y bound + + main: + + ch_coordinate_space = channel.value("microns") + + if ( baysor_tiling ) { + + // ── TILED PATH ────────────────────────────────────────────────── + + // Step 1: Divide transcripts into overlapping patches + ch_divide_input = ch_transcripts_file + .join(ch_morphology_image, by: 0) + + XENIUM_PATCH_DIVIDE ( ch_divide_input ) + + // Step 2: Fan out patches for parallel processing + ch_patches = XENIUM_PATCH_DIVIDE.out.patch_transcripts + .transpose() + .map { meta, parquet_file -> + def patch_id = parquet_file.parent.name + def patch_meta = meta.clone() + patch_meta.sample_id = meta.id + patch_meta.patch_id = patch_id + patch_meta.id = "${meta.id}_${patch_id}" + tuple(patch_meta, parquet_file) + } + + // Step 2b: Convert parquet to CSV (Baysor Julia Parquet.jl incompatibility) + PARQUET_TO_CSV ( ch_patches ) + + // Step 3: Run Baysor on each patch independently + // Use baysor_tiling_scale (larger than baysor_scale) to compensate for EM + // convergence producing smaller cells on tile-sized datasets. + BAYSOR_RUN ( + PARQUET_TO_CSV.out.csv.map { meta, transcripts -> + tuple(meta, transcripts, [], baysor_config ? file(baysor_config) : [], baysor_tiling_scale) + } + ) + + // Step 4: Gather patch results per sample and reconstruct patches directory + ch_baysor_results = BAYSOR_RUN.out.segmentation + .map { patch_meta, csv, polygons -> + tuple(patch_meta.sample_id, [patch_meta.patch_id, csv, polygons]) + } + .groupTuple(by: 0) + .map { sample_id, patch_data -> + def sorted = patch_data.sort { it -> it[0] } + def patch_ids = sorted.collect { it -> it[0] } + def csvs = sorted.collect { it -> it[1] } + def geojsons = sorted.collect { it -> it[2] } + tuple(sample_id, patch_ids, csvs, geojsons) + } + + ch_stitch_input = ch_baysor_results + .join( + XENIUM_PATCH_DIVIDE.out.grid + .map { meta, grid -> tuple(meta.id, grid) } + ) + .map { sample_id, patch_ids, csvs, geojsons, grid_json -> + def meta = [id: sample_id] + tuple(meta, grid_json, patch_ids, csvs, geojsons) + } + + // Step 5: Stitch patch results + RECONSTRUCT_PATCHES ( ch_stitch_input ) + XENIUM_PATCH_STITCH ( RECONSTRUCT_PATCHES.out.patches_dir ) + + // Step 6: xeniumranger import-segmentation (tiled) + // spatialaxe signature: meta, bundle, transcript_assignment, viz_polygons, nuclei, cells, coordinate_transform, units + ch_xr = ch_bundle_path + .combine(XENIUM_PATCH_STITCH.out.xr_polygons_transcript, by: 0) + .map { + meta, bundle, xr_cell_polygons, xr_transcript_metadata -> tuple( + meta, bundle, + xr_transcript_metadata, + xr_cell_polygons, + [], [], [], + "microns" + ) + } + + XENIUMRANGER_IMPORT_SEGMENTATION (ch_xr) + + } else { + + // ── NON-TILED PATH ────────────────────────────────────────────── + + // Preprocess: parquet → CSV with optional spatial/QV filtering + BAYSOR_PREPROCESS_TRANSCRIPTS( + ch_transcripts_file, + min_qv, + max_x, + min_x, + max_y, + min_y, + ) + + // Run Baysor on full transcripts (with optional image-based prior mask) + ch_csv_with_mask = BAYSOR_PREPROCESS_TRANSCRIPTS.out.transcripts_file + .join(ch_prior_mask, by: 0, remainder: true) + .map { meta, transcripts, mask -> + tuple(meta, transcripts, mask ?: []) + } + ch_baysor_input = ch_csv_with_mask + .combine(ch_config) + .map { meta, transcripts, mask, config -> + tuple(meta, transcripts, mask, config, baysor_scale) + } + BAYSOR_RUN(ch_baysor_input) + + // xeniumranger import-segmentation (non-tiled) + // spatialaxe signature: meta, bundle, transcript_assignment, viz_polygons, nuclei, cells, coordinate_transform, units + ch_xr = ch_bundle_path + .combine(BAYSOR_RUN.out.segmentation, by: 0) + .map { meta, bundle, segmentation_csv, polygons2d -> + tuple(meta, bundle, + segmentation_csv, + polygons2d, + [], [], [], + ch_coordinate_space.val) + } + + XENIUMRANGER_IMPORT_SEGMENTATION(ch_xr) + } + + emit: + redefined_bundle = XENIUMRANGER_IMPORT_SEGMENTATION.out.outs + coordinate_space = ch_coordinate_space +} diff --git a/subworkflows/local/baysor_run_transcripts_parquet/meta.yml b/subworkflows/local/baysor_run_transcripts_parquet/meta.yml new file mode 100644 index 00000000..ccc4a816 --- /dev/null +++ b/subworkflows/local/baysor_run_transcripts_parquet/meta.yml @@ -0,0 +1,54 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "baysor_run_transcripts_parquet" +description: to run the `baysor run` command with the transcripts.parquet file as a coordinate-based segmentation run +keywords: + - baysor + - baysor run + - segmentation + - xeniumranger import-segmentation + - coordinate-based segmentation + - transcript filtering + - polygons +components: + - baysor/preprocess/transcripts + - baysor/run + - xeniumranger/import/segmentation + - split/transcripts +input: + - ch_bundle_path: + description: | + path to the xenium bundle + Structure: [ val(meta), path("path-to-xenium-bundle") ] + - ch_transcripts_parquet: + description: | + input parquet file from the xenium bundle + Structure: [ val(meta), path("path-to-transcripts.parquet") ] + - ch_config: + description: | + config file for the xenium baysor run (stored in assets/config/xenium.toml) + Structure: [ path("path-to-xenium.toml") ] +output: + - segmentation: + description: | + the segmentation.csv file generated from the baysor run command + Structure: [ val(meta), path("segmentation.csv") ] + - polygons2d: + description: | + the segmentation_polygons_2d.json file generated from the baysor run command + Structure: [ val(meta), path("segmentation_polygons_2d.json") ] + - htmls: + description: | + the html files generated from the baysor run command + Structure: [ val(meta), path("*.html") ] + - coordinate_space: + description: | + the coordinate space in which xeniumranger import-segmentation was run + Structure: [ val("microns") ] + - redefined_bundle: + description: | + the redefined xenium bundle generated with the segmentation results from baysor + Structure: [ val(meta), ["redefined-xenium-bundle"] ] +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/subworkflows/local/baysor_run_transcripts_parquet_tiled/main.nf b/subworkflows/local/baysor_run_transcripts_parquet_tiled/main.nf new file mode 100644 index 00000000..70045331 --- /dev/null +++ b/subworkflows/local/baysor_run_transcripts_parquet_tiled/main.nf @@ -0,0 +1,104 @@ +// +// Runs baysor with tiling: divide transcripts -> preprocess per patch -> baysor per patch -> stitch -> xeniumranger +// + +include { XENIUM_PATCH_DIVIDE } from '../../../modules/local/xenium_patch/divide/main' +include { BAYSOR_PREPROCESS_TRANSCRIPTS } from '../../../modules/local/baysor/preprocess/main' +include { BAYSOR_RUN } from '../../../modules/local/baysor/run/main' +include { XENIUM_PATCH_STITCH } from '../../../modules/local/xenium_patch/stitch/main' +include { XENIUMRANGER_IMPORT_SEGMENTATION } from '../../../modules/nf-core/xeniumranger/import-segmentation/main' + +workflow BAYSOR_RUN_TRANSCRIPTS_PARQUET_TILED { + + take: + ch_bundle_path // channel: [ val(meta), ["xenium-bundle"] ] + ch_transcripts_file // channel: [ val(meta), ["transcripts.parquet"] ] + ch_config // channel: ["path-to-xenium.toml"] + max_x // value: spatial filter upper x bound + max_y // value: spatial filter upper y bound + min_qv // value: minimum transcript QV + min_x // value: spatial filter lower x bound + min_y // value: spatial filter lower y bound + + main: + + ch_coordinate_space = channel.value("microns") + + // Step 1: Divide transcripts into overlapping patches + XENIUM_PATCH_DIVIDE ( ch_transcripts_file ) + + // Step 2: Fan out patches for parallel processing + ch_patches = XENIUM_PATCH_DIVIDE.out.patch_transcripts + .transpose() + .map { meta, parquet_file -> + def patch_id = parquet_file.parent.name + def patch_meta = meta.clone() + patch_meta.sample_id = meta.id + patch_meta.patch_id = patch_id + patch_meta.id = "${meta.id}_${patch_id}" + tuple(patch_meta, parquet_file) + } + + // Step 3: Preprocess each patch's parquet to CSV for Baysor 0.7.1 compatibility + // Baysor's Julia Parquet.jl cannot read zstd-compressed parquet files + BAYSOR_PREPROCESS_TRANSCRIPTS ( + ch_patches, + min_qv, + max_x, + min_x, + max_y, + min_y, + ) + + // Step 4: Run Baysor on each patch independently + ch_baysor_input = BAYSOR_PREPROCESS_TRANSCRIPTS.out.transcripts_file + .combine(ch_config) + .map { meta, transcripts, config -> + tuple(meta, transcripts, [], config, 30) + } + + BAYSOR_RUN ( ch_baysor_input ) + + // Step 5: Gather patch results per sample for stitching + ch_for_stitch = BAYSOR_RUN.out.segmentation + .map { patch_meta, csv, polygons -> + tuple(patch_meta.sample_id, [patch_meta.patch_id, csv, polygons]) + } + .groupTuple(by: 0) + .map { sample_id, patch_data -> + def sorted = patch_data.sort { it -> it[0] } + def patch_ids = sorted.collect { it -> it[0] } + def csvs = sorted.collect { it -> it[1] } + def geojsons = sorted.collect { it -> it[2] } + tuple(sample_id, patch_ids, csvs, geojsons) + } + + // Combine with grid metadata from DIVIDE + ch_stitch_input = ch_for_stitch + .join( + XENIUM_PATCH_DIVIDE.out.grid + .map { meta, grid -> tuple(meta.id, grid) } + ) + .map { sample_id, patch_ids, csvs, geojsons, grid_json -> + def meta = [id: sample_id] + tuple(meta, grid_json, patch_ids, csvs, geojsons) + } + + // Step 6: Stitch patch results into unified segmentation output + XENIUM_PATCH_STITCH ( ch_stitch_input ) + + // Step 7: Run xeniumranger import-segmentation + // Note: Cell size filtering is handled inline by STITCH via --filter-method + ch_xr = ch_bundle_path + .combine(XENIUM_PATCH_STITCH.out.xr_polygons_transcript, by: 0) + .combine(ch_coordinate_space) + .map { meta, bundle, geojson, csv, coord_space -> + tuple(meta, bundle, csv, geojson, [], [], [], coord_space) + } + + XENIUMRANGER_IMPORT_SEGMENTATION ( ch_xr ) + + emit: + coordinate_space = ch_coordinate_space // channel: [ "microns" ] + redefined_bundle = XENIUMRANGER_IMPORT_SEGMENTATION.out.outs // channel: [ val(meta), ["redefined-xenium-bundle"] ] +} diff --git a/subworkflows/local/cellpose_baysor_import_segmentation/main.nf b/subworkflows/local/cellpose_baysor_import_segmentation/main.nf new file mode 100644 index 00000000..38bbcc74 --- /dev/null +++ b/subworkflows/local/cellpose_baysor_import_segmentation/main.nf @@ -0,0 +1,191 @@ +// +// Run the cellpose, baysor and import-segmentation flow +// + +include { RESOLIFT } from '../../../modules/local/resolift/main' +include { BAYSOR_RUN } from '../../../modules/local/baysor/run/main' +include { CELLPOSE as CELLPOSE_CELLS } from '../../../modules/nf-core/cellpose/main' +include { EXTRACT_DAPI } from '../../../modules/local/utility/extract_dapi/main' +include { STARDIST as STARDIST_NUCLEI } from '../../../modules/nf-core/stardist/main' +include { CONVERT_MASK_UINT32 } from '../../../modules/local/utility/convert_mask_uint32/main' +include { BAYSOR_PREPROCESS_TRANSCRIPTS } from '../../../modules/local/baysor/preprocess/main' +include { RESIZE_TIF } from '../../../modules/local/utility/resize_tif/main' +include { GET_TRANSCRIPTS_COORDINATES } from '../../../modules/local/utility/get_coordinates/main' +include { XENIUMRANGER_IMPORT_SEGMENTATION } from '../../../modules/nf-core/xeniumranger/import-segmentation/main' + +workflow CELLPOSE_BAYSOR_IMPORT_SEGMENTATION { + take: + ch_morphology_image // channel: [ val(meta), ["path-to-morphology.ome.tif"] ] + ch_bundle_path // channel: [ val(meta), ["path-to-xenium-bundle"] ] + ch_transcripts_file // channel: [ val(meta), ["path-to-transcripts.parquet"] ] + ch_experiment_metadata // channel: [ val(meta), ["path-to-experiment.xenium"] ] + ch_config // channel: ["path-to-xenium.toml"] + cell_segmentation_only // value: bool + cellpose_model // value: path to cellpose model (or null) + max_x // value: spatial filter upper x bound + max_y // value: spatial filter upper y bound + min_qv // value: minimum transcript QV + min_x // value: spatial filter lower x bound + min_y // value: spatial filter lower y bound + nucleus_segmentation_only // value: bool + sharpen_tiff // value: bool + stardist_nuclei_model // value: stardist pretrained model name + + main: + + ch_transcripts = channel.empty() + ch_imp_seg_inputs = channel.empty() + ch_coordinate_space = channel.value("microns") + + + // Use empty list when no model is provided; path input for official cellpose module + cellpose_model_path = cellpose_model ? file(cellpose_model) : [] + stardist_model = stardist_nuclei_model ?: '2D_versatile_fluo' + + // sharpen morphology tiff if param - sharpen_tiff is true + if (sharpen_tiff) { + + RESOLIFT(ch_morphology_image) + + ch_image = RESOLIFT.out.enhanced_tiff + } + else { + + ch_image = ch_morphology_image + } + + + // run cellpose on the morphology (enhanced) tiff + if (cell_segmentation_only) { + + CELLPOSE_CELLS(ch_image, cellpose_model_path) + } + + if (nucleus_segmentation_only) { + + // Extract DAPI channel, run StarDist, convert to uint32 + EXTRACT_DAPI(ch_image) + + STARDIST_NUCLEI(EXTRACT_DAPI.out.dapi, [stardist_model, []]) + + CONVERT_MASK_UINT32(STARDIST_NUCLEI.out.mask) + } + + + // Always preprocess transcripts.parquet to CSV for Baysor 0.7.1 compatibility. + // Baysor's Julia Parquet.jl cannot read zstd-compressed parquet files from Xenium bundles. + // Also applies optional spatial/QV filtering when filter_transcripts is true. + BAYSOR_PREPROCESS_TRANSCRIPTS( + ch_transcripts_file, + min_qv, + max_x, + min_x, + max_y, + min_y, + ) + ch_transcripts = BAYSOR_PREPROCESS_TRANSCRIPTS.out.transcripts_file + + + // run baysor with cellpose results + if (nucleus_segmentation_only) { + + // check if the size of the segmentation mask matches the max transcripts coordinate range + ch_resizetif_input = ch_transcripts + .combine(CONVERT_MASK_UINT32.out.mask, by: 0) + .combine(ch_experiment_metadata, by: 0) + .map { meta, transcripts, mask, exp_meta -> + tuple( + meta, + transcripts, + mask, + exp_meta, + ) + } + RESIZE_TIF(ch_resizetif_input) + + // run baysor with nuclei mask + ch_baysor_input = ch_transcripts + .combine(RESIZE_TIF.out.resized_mask, by: 0) + .combine(ch_config) + .map { meta, transcripts, mask, config -> + tuple( + meta, + transcripts, + mask, + config, + 30, + ) + } + BAYSOR_RUN(ch_baysor_input) + } + else if (cell_segmentation_only) { + + // check if the size of the segmentation mask matches the max transcripts coordinate range + ch_resizetif_input = ch_transcripts + .combine(CELLPOSE_CELLS.out.mask, by: 0) + .combine(ch_experiment_metadata, by: 0) + .map { meta, transcripts, mask, exp_meta -> + tuple( + meta, + transcripts, + mask, + exp_meta, + ) + } + RESIZE_TIF(ch_resizetif_input) + + // run baysor with cell mask + ch_baysor_input = ch_transcripts + .combine(RESIZE_TIF.out.resized_mask, by: 0) + .combine(ch_config) + .map { meta, transcripts, mask, config -> + tuple( + meta, + transcripts, + mask, + config, + 30, + ) + } + BAYSOR_RUN(ch_baysor_input) + } + else { + + // run baysor without cell/nuclei mask + ch_baysor_input = ch_transcripts + .combine(ch_config) + .map { meta, transcripts, config -> + tuple( + meta, + transcripts, + [], + config, + 30, + ) + } + BAYSOR_RUN(ch_baysor_input) + } + + + // run import-segmentation with baysor outs + ch_imp_seg_inputs = ch_bundle_path + .combine(BAYSOR_RUN.out.segmentation, by: 0) + .map { meta, bundle, segmentation_csv, polygons2d -> + tuple( + meta, + bundle, + segmentation_csv, + polygons2d, + [], + [], + [], + ch_coordinate_space.val, + ) + } + + XENIUMRANGER_IMPORT_SEGMENTATION(ch_imp_seg_inputs) + + emit: + coordinate_space = ch_coordinate_space // channel: [ val("microns") ] + redefined_bundle = XENIUMRANGER_IMPORT_SEGMENTATION.out.outs // channel: [ val(meta), ["redefined-xenium-bundle"] ] +} diff --git a/subworkflows/local/cellpose_baysor_import_segmentation/meta.yml b/subworkflows/local/cellpose_baysor_import_segmentation/meta.yml new file mode 100644 index 00000000..0c18e495 --- /dev/null +++ b/subworkflows/local/cellpose_baysor_import_segmentation/meta.yml @@ -0,0 +1,86 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "cellpose_baysor_import_segmentation" +description: | + combine image-based segmentation approach with cellpose and integrate results from coordinate-based segmentation + through baysor to run import-segmentation in micron coordinate space +keywords: + - baysor + - cellpose + - baysor run + - segmentation + - xeniumranger import-segmentation + - image-based segmentation + - coordinate-based segmentation + - transcript filtering + - polygons +components: + - cellpose + - resolift + - resize/tif + - get/transcripts/coordinates + - baysor/preprocess/transcripts + - baysor/run + - xeniumranger/import/segmentation + - split/transcripts +input: + - ch_morphology_image: + description: | + path to the morphology.ome.tif file + Structure: [ val(meta), path("path-to-morphology.ome.tif") ] + - ch_bundle_path: + description: | + path to the xenium bundle + Structure: [ val(meta), path("path-to-xenium-bundle") ] + - ch_transcripts_parquet: + description: | + input parquet file from the xenium bundle + Structure: [ val(meta), path("path-to-transcripts.parquet") ] + - ch_config: + description: | + config file for the xenium baysor run (stored in assets/config/xenium.toml) + Structure: [ path("path-to-xenium.toml") ] +output: + - cells_mask: + description: | + cell segmentation mask generated by running Cellpose with the cpsam algorithm + Structure: [ val(meta), path("*masks.tif") ] + - cells_flows: + description: | + cell flows generated by running Cellpose with the cpsam algorithm + Structure: [ val(meta), path("*flows.tif") ] + - cells_cells: + description: | + cell segmentation mask as a numpy array generated by running Cellpose with the cpsam algorithm + Structure: [ val(meta), path("*seg.npy") ] + - nuclei_mask: + description: | + nuclei segmentation mask generated by running Cellpose with the nuclei algorithm + Structure: [ val(meta), path("*masks.tif") ] + - nuclei_flows: + description: | + nuclei flows generated by running Cellpose with the nuclei algorithm + Structure: [ val(meta), path("*masks.tif") ] + - nuclei_cells: + description: | + nuclei segmentation mask as a numpy array generated by running Cellpose with the nuclei algorithm + Structure: [ val(meta), path("*seg.npy") ] + - segmentation: + description: | + the segmentation.csv file generated from the baysor run command + Structure: [ val(meta), path("*segmentation.csv") ] + - polygons2d: + description: | + the segmentation_polygons_2d.json file generated from the baysor run command + Structure: [ val(meta), path("*segmentation_polygons_2d.json") ] + - coordinate_space: + description: | + the coordinate space in which xeniumranger import-segmentation was run + Structure: [ val("microns") ] + - redefined_bundle: + description: | + the redefined xenium bundle generated with the segmentation results from baysor + Structure: [ val(meta), ["redefined-xenium-bundle"] ] +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/subworkflows/local/cellpose_resolift_morphology_ome_tif/main.nf b/subworkflows/local/cellpose_resolift_morphology_ome_tif/main.nf new file mode 100644 index 00000000..6bb38ded --- /dev/null +++ b/subworkflows/local/cellpose_resolift_morphology_ome_tif/main.nf @@ -0,0 +1,141 @@ +// +// Run cellpose on the morphology tiff +// + +include { RESOLIFT } from '../../../modules/local/resolift/main' +include { DOWNSCALE_MORPHOLOGY } from '../../../modules/local/utility/downscale_morphology/main' +include { UPSCALE_MASK as UPSCALE_CELLS } from '../../../modules/local/utility/upscale_mask/main' +include { CELLPOSE as CELLPOSE_CELLS } from '../../../modules/nf-core/cellpose/main' +include { EXTRACT_DAPI } from '../../../modules/local/utility/extract_dapi/main' +include { STARDIST as STARDIST_NUCLEI } from '../../../modules/nf-core/stardist/main' +include { CONVERT_MASK_UINT32 } from '../../../modules/local/utility/convert_mask_uint32/main' +include { XENIUMRANGER_IMPORT_SEGMENTATION } from '../../../modules/nf-core/xeniumranger/import-segmentation/main' + +workflow CELLPOSE_RESOLIFT_MORPHOLOGY_OME_TIF { + take: + ch_morphology_image // channel: [ val(meta), ["path-to-morphology.ome.tiff"] ] + ch_bundle_path // channel: [ val(meta), ["path-to-xenium-bundle"] ] + cellpose_downscale // value: bool + cellpose_model // value: path to cellpose model (or null) + nucleus_segmentation_only // value: bool + sharpen_tiff // value: bool + stardist_nuclei_model // value: stardist pretrained model name + + main: + + ch_imp_seg_inputs = channel.empty() + ch_coordinate_space = channel.value("pixels") + + // Use empty list when no model is provided; path input for official cellpose module + cellpose_model_path = cellpose_model ? file(cellpose_model) : [] + stardist_model = stardist_nuclei_model ?: '2D_versatile_fluo' + + // sharpen morphology tiff if param - sharpen_tiff is true + if (sharpen_tiff) { + + RESOLIFT(ch_morphology_image) + + ch_image = RESOLIFT.out.enhanced_tiff + } + else { + + ch_image = ch_morphology_image + } + + // Optional pre-downscale for large images to avoid cellpose OOM + // Only needed when running cellpose for cells (not nucleus_segmentation_only) + if (cellpose_downscale && !nucleus_segmentation_only) { + + DOWNSCALE_MORPHOLOGY(ch_image) + + ch_cellpose_input = DOWNSCALE_MORPHOLOGY.out.downscaled + ch_scale_info = DOWNSCALE_MORPHOLOGY.out.scale_info + } + else { + + ch_cellpose_input = ch_image + ch_scale_info = channel.empty() + } + + // run cellpose on morphology tiff (or downscaled version) + if (!nucleus_segmentation_only) { + CELLPOSE_CELLS(ch_cellpose_input, cellpose_model_path) + } + + // StarDist for nuclei — extract DAPI first, then run on original resolution + EXTRACT_DAPI(ch_image) + + STARDIST_NUCLEI(EXTRACT_DAPI.out.dapi, [stardist_model, []]) + + // Convert StarDist mask to uint32 for XeniumRanger compatibility + CONVERT_MASK_UINT32(STARDIST_NUCLEI.out.mask) + + ch_nuclei_mask = CONVERT_MASK_UINT32.out.mask + + // Upscale cellpose cells mask back to original resolution if downscaled + // StarDist nuclei mask is already at original resolution (no upscale needed) + if (cellpose_downscale) { + + if (!nucleus_segmentation_only) { + ch_cells_for_upscale = CELLPOSE_CELLS.out.mask + .combine(ch_scale_info, by: 0) + UPSCALE_CELLS(ch_cells_for_upscale) + ch_cells_mask = UPSCALE_CELLS.out.upscaled_mask + } + } + else { + + if (!nucleus_segmentation_only) { + ch_cells_mask = CELLPOSE_CELLS.out.mask + } + } + + // run import-segmentation with cellpose results + if (nucleus_segmentation_only) { + + ch_imp_seg_inputs = ch_bundle_path + .combine(ch_nuclei_mask, by: 0) + .combine(ch_coordinate_space) + .map { meta, bundle, nuclei_seg, coord_space -> + tuple( + meta, + bundle, + [], + [], + nuclei_seg, + [], + [], + coord_space, + ) + } + XENIUMRANGER_IMPORT_SEGMENTATION( + ch_imp_seg_inputs + ) + } + else { + + ch_imp_seg_inputs = ch_bundle_path + .combine(ch_cells_mask, by: 0) + .combine(ch_nuclei_mask, by: 0) + .combine(ch_coordinate_space) + .map { meta, bundle, cells_seg, nuclei_seg, coord_space -> + tuple( + meta, + bundle, + [], + [], + nuclei_seg, + cells_seg, + [], + coord_space, + ) + } + XENIUMRANGER_IMPORT_SEGMENTATION( + ch_imp_seg_inputs + ) + } + + emit: + coordinate_space = ch_coordinate_space // channel: [ ["pixels"] ] + redefined_bundle = XENIUMRANGER_IMPORT_SEGMENTATION.out.outs // channel: [ val(meta), ["redefined-xenium-bundle"] ] +} diff --git a/subworkflows/local/cellpose_resolift_morphology_ome_tif/meta.yml b/subworkflows/local/cellpose_resolift_morphology_ome_tif/meta.yml new file mode 100644 index 00000000..b0c99c85 --- /dev/null +++ b/subworkflows/local/cellpose_resolift_morphology_ome_tif/meta.yml @@ -0,0 +1,59 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "cellpose_resolift_morphology_ome_tif" +description: | + image-based segmentation approach with cellpose to run import-segmentation in pixel coordinate space +keywords: + - cellpose + - segmentation + - xeniumranger import-segmentation + - image-based segmentation +components: + - cellpose + - resolift + - xeniumranger/import/segmentation +input: + - ch_morphology_image: + description: | + path to the morphology.ome.tif file + Structure: [ val(meta), path("path-to-morphology.ome.tif") ] + - ch_bundle_path: + description: | + path to the xenium bundle + Structure: [ val(meta), path("path-to-xenium-bundle") ] +output: + - cells_mask: + description: | + cell segmentation mask generated by running Cellpose with the cpsam algorithm + Structure: [ val(meta), path("*masks.tif") ] + - cells_flows: + description: | + cell flows generated by running Cellpose with the cpsam algorithm + Structure: [ val(meta), path("*flows.tif") ] + - cells_cells: + description: | + cell segmentation mask as a numpy array generated by running Cellpose with the cpsam algorithm + Structure: [ val(meta), path("*seg.npy") ] + - nuclei_mask: + description: | + nuclei segmentation mask generated by running Cellpose with the nuclei algorithm + Structure: [ val(meta), path("*masks.tif") ] + - nuclei_flows: + description: | + nuclei flows generated by running Cellpose with the nuclei algorithm + Structure: [ val(meta), path("*masks.tif") ] + - nuclei_cells: + description: | + nuclei segmentation mask as a numpy array generated by running Cellpose with the nuclei algorithm + Structure: [ val(meta), path("*seg.npy") ] + - coordinate_space: + description: | + the coordinate space in which xeniumranger import-segmentation was run + Structure: [ val("pixels") ] + - redefined_bundle: + description: | + the redefined xenium bundle generated with the segmentation results from baysor + Structure: [ val(meta), ["redefined-xenium-bundle"] ] +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/subworkflows/local/ficture_preprocess_model/main.nf b/subworkflows/local/ficture_preprocess_model/main.nf new file mode 100644 index 00000000..c45713ec --- /dev/null +++ b/subworkflows/local/ficture_preprocess_model/main.nf @@ -0,0 +1,39 @@ +// +// Run ficture preprocess and model modules +// + +include { FICTURE_PREPROCESS } from '../../../modules/local/ficture/preprocess/main' +include { FICTURE } from '../../../modules/local/ficture/model/main' +include { PARQUET_TO_CSV } from '../../../modules/local/utility/parquet_to_csv/main' + + + +workflow FICTURE_PREPROCESS_MODEL { + take: + ch_transcripts_file // channel: [ val(meta), [ "transcripts.parquet" ] ] + ch_features // channel: [ ["features"] ] + features // value: path to features list (or null) + + main: + + // convert parquet to csv + PARQUET_TO_CSV(ch_transcripts_file, ".csv") + + // run ficture preprocessing + ch_transcripts = PARQUET_TO_CSV.out.transcripts_csv + + FICTURE_PREPROCESS(ch_transcripts, ch_features) + + // run the ficture wrapper pipeline + ch_features_clean = features ? FICTURE_PREPROCESS.out.features : channel.value([]) + FICTURE( + FICTURE_PREPROCESS.out.transcripts, + FICTURE_PREPROCESS.out.coordinate_minmax, + ch_features_clean, + ) + emit: + transcripts = FICTURE_PREPROCESS.out.transcripts // channel: [ val(meta), [ "*processed_transcripts.tsv.gz" ] ] + coordinate_minmax = FICTURE_PREPROCESS.out.coordinate_minmax // channel: [ "*coordinate_minmax.tsv" ] + features = FICTURE_PREPROCESS.out.features // channel: [ "*feature.clean.tsv.gz" ] + results = FICTURE.out.results // channel: [ val(meta), [ "results/** ] ] +} diff --git a/subworkflows/local/ficture_preprocess_model/meta.yml b/subworkflows/local/ficture_preprocess_model/meta.yml new file mode 100644 index 00000000..17840559 --- /dev/null +++ b/subworkflows/local/ficture_preprocess_model/meta.yml @@ -0,0 +1,34 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "ficture_preprocess_model" +description: Scalable segmentation-free analysis of sub-micron resolution spatial transcriptomics +keywords: + - ficture + - ficture preprocess + - segmentation-free analysis + - pixel level factor analysis +components: + - ficture/preprocess + - ficture + - parquet/to/csv +input: + - ch_transcripts_parquet: + description: | + file containing the molecular or pixel level information, the required columns are X, Y, gene, and Count + Structure: [ val(meta), path("transcripts.parquet") ] + - ch_features: + description: | + unique names of genes that should be used in analysis + Structure: [ [gene1,gene2,gene3,gene4] ] +output: + - transcripts: + description: xyz + - coordinate_minmax: + description: xyz + - features: + description: xyz + - results: + description: xyz +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/subworkflows/local/opt_flip_track_stat/main.nf b/subworkflows/local/opt_flip_track_stat/main.nf new file mode 100644 index 00000000..839768d2 --- /dev/null +++ b/subworkflows/local/opt_flip_track_stat/main.nf @@ -0,0 +1,34 @@ +include { OPT_FLIP } from '../../../modules/nf-core/opt/flip/main' +include { OPT_TRACK } from '../../../modules/nf-core/opt/track/main' +include { OPT_STAT } from '../../../modules/nf-core/opt/stat/main' + + +workflow OPT_FLIP_TRACK_STAT { + take: + ch_probe_fasta // channel: [ val(meta), [ "panel_probes_sequences.fasta" ] ] + ch_references // channel: [ val(meta), ["reference_annotations.gff"], ["reference_annotations.fa"] ] + ch_gene_synonyms // channel: [ "path-to-gene-synonyms" ] + + main: + + ch_versions = channel.empty() + ch_summary = channel.empty() + + // correct probes that are aligning to opposite strand with `flip` + OPT_FLIP(ch_probe_fasta, ch_references) + ch_versions = ch_versions.mix(OPT_FLIP.out.versions) + + // align query probe sequences to target transcriptome + OPT_TRACK(OPT_FLIP.out.fwd_oriented_fa, ch_references) + ch_versions = ch_versions.mix(OPT_TRACK.out.versions) + + // summarizes opt binding predictions + OPT_STAT(OPT_TRACK.out.probes2target, OPT_FLIP.out.fwd_oriented_fa, ch_gene_synonyms) + ch_versions = ch_versions.mix(OPT_STAT.out.versions) + + ch_summary = OPT_STAT.out.summary + + emit: + summary = ch_summary // channel: [ val(meta), ["collapsed_summary.tsv", "other-summary-files"]] + versions = ch_versions // channel: [ versions.yml ] +} diff --git a/subworkflows/local/opt_flip_track_stat/meta.yml b/subworkflows/local/opt_flip_track_stat/meta.yml new file mode 100644 index 00000000..ea17599b --- /dev/null +++ b/subworkflows/local/opt_flip_track_stat/meta.yml @@ -0,0 +1,45 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "opt_flip_track_stat" +description: opt is a simple program that aligns probe sequences to transcript sequences + to detect potential off-target probe activity +keywords: + - opt + - flip + - track + - stat + - off-target probes +components: + - opt/flip + - opt/track + - opt/stat +input: + - ch_probe_fasta: + type: file + description: | + Input channel containing the sample info. and associated probe panel sequences fasta file + Structure: [ val(meta), path("porbe_panel_seqeunces.fasta") ] + pattern: "*.fasta" + - ch_references: + type: file + description: | + Input channel containing the sample info. and the references to be used + Structure: [ val(meta), path("reference_annotations.gff"), path("reference_annotations.fa") ] + pattern: "*.{fa,gff}" + - ch_gene_synonyms: + type: file + description: | + Input channel containing the Gene synonyms that may have been counted as off-targets but + simply differ in name (optional input) + Structure: [ val(meta), path("gene_synonyms.csv") ] + pattern: "*.csv" +output: + - summary: + type: file + description: | + Groovy Map containing summary of the forward oriented probes generated with the panel sequences opt flip and track + Structure: [ val(meta), path("collapsed_summary.tsv") ] + pattern: "*.tsv" +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/subworkflows/local/proseg_preset_proseg2baysor/main.nf b/subworkflows/local/proseg_preset_proseg2baysor/main.nf new file mode 100644 index 00000000..3f9d8c99 --- /dev/null +++ b/subworkflows/local/proseg_preset_proseg2baysor/main.nf @@ -0,0 +1,50 @@ +// +// Runs proseg for the xenium format and proseg2baysor to generate cell ploygons +// + +include { PROSEG } from '../../../modules/local/proseg/preset/main' +include { PROSEG2BAYSOR } from '../../../modules/local/proseg/proseg2baysor/main' +include { XENIUMRANGER_IMPORT_SEGMENTATION } from '../../../modules/nf-core/xeniumranger/import-segmentation/main' + +workflow PROSEG_PRESET_PROSEG2BAYSOR { + take: + ch_bundle_path // channel: [ val(meta), ["path-to-xenium-bundle"] ] + ch_transcripts_file // channel: [ val(meta), [ "transcripts.parquet" ] ] + + main: + + ch_coordinate_space = channel.value("microns") + + // run proseg with the xenium format + PROSEG(ch_transcripts_file) + + + // run proseg-to-baysor on the zarr output from proseg v3 + PROSEG2BAYSOR(PROSEG.out.zarr) + + + // run xeniumranger import-segmentation + ch_imp_seg_inputs = ch_bundle_path + .combine(PROSEG2BAYSOR.out.xr_metadata, by: 0) + .combine(PROSEG2BAYSOR.out.xr_polygons, by: 0) + .map { meta, bundle, metadata, polygons2d -> + tuple( + meta, + bundle, + metadata, + polygons2d, + [], + [], + [], + ch_coordinate_space.val, + ) + } + + XENIUMRANGER_IMPORT_SEGMENTATION( + ch_imp_seg_inputs + ) + + emit: + coordinate_space = ch_coordinate_space // channel: [ "microns" ] + redefined_bundle = XENIUMRANGER_IMPORT_SEGMENTATION.out.outs // channel: [ val(meta), ["redefined-xenium-bundle"] ] +} diff --git a/subworkflows/local/proseg_preset_proseg2baysor/meta.yml b/subworkflows/local/proseg_preset_proseg2baysor/meta.yml new file mode 100644 index 00000000..6eb3f091 --- /dev/null +++ b/subworkflows/local/proseg_preset_proseg2baysor/meta.yml @@ -0,0 +1,48 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "proseg_preset_proseg2baysor" +description: to run proseg with the transcripts.parquet file as a coordinate-based segmentation run +keywords: + - proseg + - segmentation + - xeniumranger import-segmentation + - coordinate-based segmentation + - polygons + - metadata +components: + - proseg + - proseg2baysor + - xeniumranger/import/segmentation +input: + - ch_bundle_path: + description: | + path to the xenium bundle + Structure: [ val(meta), path("path-to-xenium-bundle") ] + - ch_transcripts_parquet: + description: | + input parquet file from the xenium bundle + Structure: [ val(meta), path("path-to-transcripts.parquet") ] +output: + - cell_polygons_2d: + description: | + the cell-polygons.geojson.gz file generated from proseg + Structure: [ val(meta), path("cell-polygons.geojson.gz") ] + - xr_polygons: + description: | + xeniumranger-compatible polygon file generated from the proseg command + Structure: [ val(meta), path("xr-cell-polygons.geojson") ] + - xr_metadata: + description: | + xeniumranger-compatible metadata file generated from the proseg command + Structure: [ val(meta), path("xr-transcript-metadata.csv") ] + - coordinate_space: + description: | + the coordinate space in which xeniumranger import-segmentation was run + Structure: [ val("microns") ] + - redefined_bundle: + description: | + the redefined xenium bundle generated with the segmentation results from baysor + Structure: [ val(meta), ["redefined-xenium-bundle"] ] +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/subworkflows/local/proseg_preset_proseg2baysor_tiled/main.nf b/subworkflows/local/proseg_preset_proseg2baysor_tiled/main.nf new file mode 100644 index 00000000..7ff6b783 --- /dev/null +++ b/subworkflows/local/proseg_preset_proseg2baysor_tiled/main.nf @@ -0,0 +1,86 @@ +// +// Runs proseg with tiling: divide transcripts -> proseg per patch -> proseg2baysor -> stitch -> xeniumranger +// + +include { XENIUM_PATCH_DIVIDE } from '../../../modules/local/xenium_patch/divide/main' +include { PROSEG } from '../../../modules/local/proseg/preset/main' +include { PROSEG2BAYSOR } from '../../../modules/local/proseg/proseg2baysor/main' +include { XENIUM_PATCH_STITCH } from '../../../modules/local/xenium_patch/stitch/main' +include { XENIUMRANGER_IMPORT_SEGMENTATION } from '../../../modules/nf-core/xeniumranger/import-segmentation/main' + +workflow PROSEG_PRESET_PROSEG2BAYSOR_TILED { + + take: + ch_bundle_path // channel: [ val(meta), ["path-to-xenium-bundle"] ] + ch_transcripts_file // channel: [ val(meta), [ "transcripts.parquet" ] ] + + main: + + ch_coordinate_space = channel.value("microns") + + // Step 1: Divide transcripts into overlapping patches + XENIUM_PATCH_DIVIDE ( ch_transcripts_file ) + + // Step 2: Fan out patches for parallel processing + // transpose() emits one item per patch file: [meta, parquet_path] + ch_patches = XENIUM_PATCH_DIVIDE.out.patch_transcripts + .transpose() + .map { meta, parquet_file -> + def patch_id = parquet_file.parent.name + def patch_meta = meta.clone() + patch_meta.sample_id = meta.id + patch_meta.patch_id = patch_id + patch_meta.id = "${meta.id}_${patch_id}" + tuple(patch_meta, parquet_file) + } + + // Step 3: Run proseg on each patch independently + PROSEG ( ch_patches ) + + // Step 4: Convert proseg output to baysor format per patch + PROSEG2BAYSOR ( PROSEG.out.zarr ) + + // Step 5: Gather patch results per sample for stitching + ch_for_stitch = PROSEG2BAYSOR.out.xr_polygons + .join(PROSEG2BAYSOR.out.xr_metadata, by: 0) + .map { patch_meta, geojson, csv -> + tuple(patch_meta.sample_id, [patch_meta.patch_id, csv, geojson]) + } + .groupTuple(by: 0) + .map { sample_id, patch_data -> + def sorted = patch_data.sort { it -> it[0] } + def patch_ids = sorted.collect { it -> it[0] } + def csvs = sorted.collect { it -> it[1] } + def geojsons = sorted.collect { it -> it[2] } + tuple(sample_id, patch_ids, csvs, geojsons) + } + + // Combine with grid metadata from DIVIDE + ch_stitch_input = ch_for_stitch + .join( + XENIUM_PATCH_DIVIDE.out.grid + .map { meta, grid -> tuple(meta.id, grid) } + ) + .map { sample_id, patch_ids, csvs, geojsons, grid_json -> + def meta = [id: sample_id] + tuple(meta, grid_json, patch_ids, csvs, geojsons) + } + + // Step 6: Stitch patch results into unified segmentation output + XENIUM_PATCH_STITCH ( ch_stitch_input ) + + // Step 7: Run xeniumranger import-segmentation + // Note: Cell size filtering is handled inline by STITCH via --filter-method + ch_xr = ch_bundle_path + .combine(XENIUM_PATCH_STITCH.out.xr_polygons_transcript, by: 0) + .combine(ch_coordinate_space) + .map { meta, bundle, geojson, csv, coord_space -> + tuple(meta, bundle, csv, geojson, [], [], [], coord_space) + } + + XENIUMRANGER_IMPORT_SEGMENTATION ( ch_xr ) + + emit: + coordinate_space = ch_coordinate_space // channel: [ "microns" ] + redefined_bundle = XENIUMRANGER_IMPORT_SEGMENTATION.out.outs // channel: [ val(meta), ["redefined-xenium-bundle"] ] +} diff --git a/subworkflows/local/segger_create_train_predict/main.nf b/subworkflows/local/segger_create_train_predict/main.nf new file mode 100644 index 00000000..3f832d61 --- /dev/null +++ b/subworkflows/local/segger_create_train_predict/main.nf @@ -0,0 +1,77 @@ +// +// Run segger create_dataset, train and predict modules & parquet_to_csv +// + +include { SEGGER2XR } from '../../../modules/local/utility/segger2xr/main' +include { SEGGER_TRAIN } from '../../../modules/local/segger/train/main' +include { SEGGER_PREDICT } from '../../../modules/local/segger/predict/main' +include { SEGGER_CREATE_DATASET } from '../../../modules/local/segger/create_dataset/main' +include { XENIUMRANGER_IMPORT_SEGMENTATION } from '../../../modules/nf-core/xeniumranger/import-segmentation/main' + +workflow SEGGER_CREATE_TRAIN_PREDICT { + take: + ch_bundle // channel: [ val(meta), ["path-to-xenium-bundle"] ] + ch_transcripts_file // channel: [ val(meta), [bundle + "/transcripts.parquet"]] + segger_model // value: path to a pre-trained segger model checkpoint (or null) + + main: + + // Note: spatialaxe uses "pixels" but per 10x docs, transcript-based segmentation + // (like Baysor/Segger) must use "microns" since Xenium coordinates are in microns + ch_coordinate_space = channel.value("microns") + + // create dataset (always needed for predict step) + SEGGER_CREATE_DATASET(ch_bundle) + + // Determine model source and join all PREDICT inputs by meta. + // Without meta-based join, queue channels align by emission order, + // which is non-deterministic and causes cross-sample input mispairing. + if (segger_model) { + // Use pre-trained model - skip training + def model_path = file(segger_model) + ch_predict_paired = SEGGER_CREATE_DATASET.out.datasetdir + .join(ch_transcripts_file) + .map { meta, dataset, tx -> [meta, dataset, model_path, tx] } + } else { + // Train a new model per sample, join all inputs by meta + SEGGER_TRAIN(SEGGER_CREATE_DATASET.out.datasetdir) + ch_predict_paired = SEGGER_CREATE_DATASET.out.datasetdir + .join(SEGGER_TRAIN.out.trained_models) + .join(ch_transcripts_file) + } + // ch_predict_paired: [meta, dataset_dir, models_dir, transcripts] + + SEGGER_PREDICT( + ch_predict_paired.map { meta, dataset, _m, _tx -> [meta, dataset] }, + ch_predict_paired.map { _meta, _dataset, models, _tx -> models }, + ch_predict_paired.map { _meta, _dataset, _m, tx -> [tx] }, + ) + // convert parquet to XR compatible form + SEGGER2XR(SEGGER_PREDICT.out.transcripts) + + // run xeniumranger import-segmentation with Baysor-format CSV + viz polygons + // xeniumranger 4.0 expects Baysor CSV (with is_noise column) for --transcript-assignment + ch_imp_seg_inputs = ch_bundle + .combine(SEGGER2XR.out.segmentation_csv, by: 0) + .combine(SEGGER2XR.out.viz_polygons, by: 0) + .map { meta, bundle, segmentation_csv, polygons -> + tuple( + meta, + bundle, + segmentation_csv, // transcript_assignment (Baysor-format CSV) + polygons, // viz_polygons (GeoJSON cell boundaries) + [], // nuclei + [], // cells + [], // coordinate_transform + ch_coordinate_space.val, + ) + } + + XENIUMRANGER_IMPORT_SEGMENTATION( + ch_imp_seg_inputs + ) + + emit: + coordinate_space = ch_coordinate_space // channel: [ "microns" ] + redefined_bundle = XENIUMRANGER_IMPORT_SEGMENTATION.out.outs // channel: [ val(meta), ["redefined-xenium-bundle"] ] +} diff --git a/subworkflows/local/segger_create_train_predict/meta.yml b/subworkflows/local/segger_create_train_predict/meta.yml new file mode 100644 index 00000000..34d59770 --- /dev/null +++ b/subworkflows/local/segger_create_train_predict/meta.yml @@ -0,0 +1,55 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "segger_create_train_predict" +description: | + segger is a cutting-edge tool for cell segmentation in single-molecule spatial omics datasets, + subworkflow is the implementation of segger modules run in the recommended sequence +keywords: + - segger + - segmentation + - xeniumranger import-segmentation + - coordinate-based segmentation +components: + - segger + - segger/create/dataset + - segger/train + - segger/predict + - segger2xr + - xeniumranger/import/segmentation +input: + - ch_bundle_path: + description: | + Directory containing the raw dataset - xenium bundle (e.g., transcripts, boundaries). + Structure: [ val(meta), path("path-to-xenium-bundle") ] + - ch_transcripts_parquet: + description: | + input parquet file from the xenium bundle + Structure: [ val(meta), path("path-to-transcripts.parquet") ] +output: + - datasetdir: + description: | + Directory generated by the segger create dataset module + Structure: [ val(meta), path(datasetdir) ] + - trained_models: + description: | + The model trained on the data by the segger training module + Structure: [ val(meta), path(trained_models) ] + - benchmarks: + description: | + benchmarks generated from the segger training and prediction steps + Structure: [ val(meta), path(benchmarks) ] + - segger_transcripts: + description: | + transcripts parquet file generated after segger prediction conatining the segger ids + Structure: [ path(transcripts.parquet) ] + - coordinate_space: + description: | + the coordinate space in which xeniumranger import-segmentation was run + Structure: [ val("pixels") ] + - redefined_bundle: + description: | + the redefined xenium bundle generated with the segmentation results from baysor + Structure: [ val(meta), ["redefined-xenium-bundle"] ] +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/subworkflows/local/spatialdata_write_meta_merge/main.nf b/subworkflows/local/spatialdata_write_meta_merge/main.nf new file mode 100644 index 00000000..24ce9e90 --- /dev/null +++ b/subworkflows/local/spatialdata_write_meta_merge/main.nf @@ -0,0 +1,81 @@ +// +// generate spatialdata object from the spatialaxe layers +// + +include { SPATIALDATA_META } from '../../../modules/local/spatialdata/meta/main' +include { SPATIALDATA_WRITE as SPATIALDATA_WRITE_RAW_BUNDLE } from '../../../modules/local/spatialdata/write/main' +include { SPATIALDATA_MERGE as SPATIALDATA_MERGE_RAW_REDEFINED } from '../../../modules/local/spatialdata/merge/main' +include { SPATIALDATA_WRITE as SPATIALDATA_WRITE_REDEFINED_BUNDLE } from '../../../modules/local/spatialdata/write/main' + +workflow SPATIALDATA_WRITE_META_MERGE { + take: + ch_bundle_path // channel: [ val(meta), [ "path-to-xenium-bundle" ] ] + ch_redefined_bundle // channel: [ val(meta), [ "redefined-xenium-bundle" ] ] + ch_coordinate_space // channel: [ "pixels" or "microns" ] + cell_segmentation_only // value: bool + mode // value: pipeline mode (image/coordinate/...) + nucleus_segmentation_only // value: bool + + main: + + ch_segmented_object = channel.empty() + + // check segmentation - only nuclei, cells or both cells & nuclei + if (mode == 'image') { + + if (nucleus_segmentation_only && cell_segmentation_only) { + ch_segmented_object = channel.value('cells_and_nuclei') + } + else if (nucleus_segmentation_only) { + ch_segmented_object = channel.value('nuclei') + } + else if (cell_segmentation_only) { + ch_segmented_object = channel.value('cells') + } + else { + ch_segmented_object = channel.value([]) + } + } + + // set all boundaries as false - default + if (mode == 'coordinate') { + ch_segmented_object = channel.value([]) + } + + // write spatialdata object from the raw xenium bundle + SPATIALDATA_WRITE_RAW_BUNDLE( + ch_bundle_path, + 'raw_bundle', + ch_segmented_object, + ch_coordinate_space, + ) + + + // write spatialdata object after running IMP_SEG + SPATIALDATA_WRITE_REDEFINED_BUNDLE( + ch_redefined_bundle, + 'redefined_bundle', + ch_segmented_object, + ch_coordinate_space, + ) + + + // merge raw & redefined spatialdata objects + SPATIALDATA_MERGE_RAW_REDEFINED( + SPATIALDATA_WRITE_RAW_BUNDLE.out.spatialdata.combine(ch_redefined_bundle, by: 0), + 'merged_bundle' + ) + + + // write metadata with spatialdata object + SPATIALDATA_META( + SPATIALDATA_MERGE_RAW_REDEFINED.out.merged_bundle.combine(ch_bundle_path, by: 0), + 'metadata' + ) + + emit: + sd_raw_bundle = SPATIALDATA_WRITE_RAW_BUNDLE.out.spatialdata // channel: [ val(meta), "spatialdata_raw" ] + sd_redefined_bundle = SPATIALDATA_WRITE_REDEFINED_BUNDLE.out.spatialdata // channel: [ val(meta), "spatialdata_redefined" ] + sd_merged_bundle = SPATIALDATA_MERGE_RAW_REDEFINED.out.merged_bundle // channel: [ val(meta), "spatialdata_merged" ] + sd_metadata = SPATIALDATA_META.out.metadata // channel: [ val(meta), "spatialdata_meta" ] +} diff --git a/subworkflows/local/spatialdata_write_meta_merge/meta.yml b/subworkflows/local/spatialdata_write_meta_merge/meta.yml new file mode 100644 index 00000000..0168d010 --- /dev/null +++ b/subworkflows/local/spatialdata_write_meta_merge/meta.yml @@ -0,0 +1,50 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "spatialdata_write_meta_merge" +description: | + SpatialData is a data framework that comprises a FAIR storage format and a collection of python libraries for + performant access, alignment, and processing of uni- and multi-modal spatial omics datasets +keywords: + - spatialdata + - xenium + - xeniumranger import-segmentation + - spatialdata object + - metadata + - merge spatialdata objects +components: + - spatialdata/write + - spatialdata/meta + - spatialdata/merge +input: + - ch_bundle_path: + description: | + Directory containing the raw dataset + Structure: [ val(meta), path("path-to-xenium-bundle") ] + - ch_redefined_bundle: + description: | + Directory containing the redefined xenium bundle after running xeniumranger import-segmentation + Structure: [ val(meta), path("redefined-xenium-bundle") ] + - ch_coordinate_space: + description: | + the coordinate space in which xeniumranger import-segmentation was run + Structure: [ val("pixels" or "microns") ] +output: + - sd_raw_bundle: + description: | + spatialdata object generated from the raw xenium bundle + Structure: [ val(meta), path(spatialdata_raw) ] + - sd_redefined_bundle: + description: | + spatialdata object generated from the redefined xenium bundle + Structure: [ val(meta), path(spatialdata_redefined) ] + - sd_merged_bundle: + description: | + spatialdata object generated from merging the spatialdata objects from raw and redefined xenium bundles + Structure: [ val(meta), path(spatialdata_merged) ] + - sd_metadata: + description: | + spatialdata object containing the metadata info. + Structure: [ path(transcripts.parquet) ] +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/subworkflows/local/stardist_resolift_morphology_ome_tif/main.nf b/subworkflows/local/stardist_resolift_morphology_ome_tif/main.nf new file mode 100644 index 00000000..bc255409 --- /dev/null +++ b/subworkflows/local/stardist_resolift_morphology_ome_tif/main.nf @@ -0,0 +1,70 @@ +// +// Run stardist nuclei segmentation on the morphology tiff +// + +include { RESOLIFT } from '../../../modules/local/resolift/main' +include { EXTRACT_DAPI } from '../../../modules/local/utility/extract_dapi/main' +include { STARDIST as STARDIST_NUCLEI } from '../../../modules/nf-core/stardist/main' +include { CONVERT_MASK_UINT32 } from '../../../modules/local/utility/convert_mask_uint32/main' +include { XENIUMRANGER_IMPORT_SEGMENTATION } from '../../../modules/nf-core/xeniumranger/import-segmentation/main' + +workflow STARDIST_RESOLIFT_MORPHOLOGY_OME_TIF { + take: + ch_morphology_image // channel: [ val(meta), ["path-to-morphology.ome.tiff"] ] + ch_bundle_path // channel: [ val(meta), ["path-to-xenium-bundle"] ] + sharpen_tiff // value: bool + stardist_nuclei_model // value: stardist pretrained model name + + main: + + ch_imp_seg_inputs = channel.empty() + ch_coordinate_space = channel.value("pixels") + + // Use default model when no model is provided + stardist_model = stardist_nuclei_model ?: '2D_versatile_fluo' + + // sharpen morphology tiff if param - sharpen_tiff is true + if (sharpen_tiff) { + + RESOLIFT(ch_morphology_image) + + ch_image = RESOLIFT.out.enhanced_tiff + } + else { + + ch_image = ch_morphology_image + } + + // Extract DAPI channel for StarDist (expects single-channel input) + EXTRACT_DAPI(ch_image) + + // Run StarDist nuclei segmentation on DAPI channel + STARDIST_NUCLEI(EXTRACT_DAPI.out.dapi, [stardist_model, []]) + + // Convert mask to uint32 for XeniumRanger compatibility + CONVERT_MASK_UINT32(STARDIST_NUCLEI.out.mask) + + // Run import-segmentation with nuclei only + // XeniumRanger expands nuclei by expansion_distance to create cell boundaries + ch_imp_seg_inputs = ch_bundle_path + .combine(CONVERT_MASK_UINT32.out.mask, by: 0) + .map { meta, bundle, nuclei_seg -> + tuple( + meta, + bundle, + [], + [], + nuclei_seg, + [], + [], + ch_coordinate_space.val, + ) + } + XENIUMRANGER_IMPORT_SEGMENTATION( + ch_imp_seg_inputs + ) + + emit: + coordinate_space = ch_coordinate_space // channel: [ ["pixels"] ] + redefined_bundle = XENIUMRANGER_IMPORT_SEGMENTATION.out.outs // channel: [ val(meta), ["redefined-xenium-bundle"] ] +} diff --git a/subworkflows/local/utils_nfcore_spatialaxe_pipeline/main.nf b/subworkflows/local/utils_nfcore_spatialaxe_pipeline/main.nf new file mode 100644 index 00000000..801174e2 --- /dev/null +++ b/subworkflows/local/utils_nfcore_spatialaxe_pipeline/main.nf @@ -0,0 +1,351 @@ +// +// Subworkflow with functionality specific to the nf-core/spatialaxe pipeline +// + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT FUNCTIONS / MODULES / SUBWORKFLOWS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +include { UTILS_NFSCHEMA_PLUGIN } from '../../nf-core/utils_nfschema_plugin' +include { paramsSummaryMap } from 'plugin/nf-schema' +include { samplesheetToList } 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' + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SUBWORKFLOW TO INITIALISE PIPELINE +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow PIPELINE_INITIALISATION { + take: + version // boolean: Display version and exit + validate_params // boolean: Boolean whether to validate parameters against the schema at runtime + monochrome_logs // boolean: Do not use coloured log outputs + nextflow_cli_args // array: List of positional nextflow CLI args + outdir // string: The output directory where the results will be saved + input // string: Path to input samplesheet + help // boolean: Display help message and exit + help_full // boolean: Show the full help message + show_hidden // boolean: Show hidden parameters in the help message + format // string: input data platform (xenium | cosmx | merscope) + gene_panel // string: path to gene panel + gene_synonyms // string: path to gene synonyms + image_seg_methods // list: valid image-mode segmentation methods + method // string: chosen segmentation method + mode // string: pipeline mode + nucleus_segmentation_only // boolean + offtarget_probe_tracking // boolean + probes_fasta // string: path to probes fasta + reference_annotations // string: path to reference annotations + relabel_genes // boolean + segmentation_mask // string: path to segmentation mask + transcript_seg_methods // list: valid coordinate-mode segmentation methods + + main: + + // + // Print version and exit if required and dump pipeline parameters to JSON file + // + UTILS_NEXTFLOW_PIPELINE( + version, + true, + outdir, + workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1, + ) + + // + // Validate parameters and generate parameter summary to stdout + // + before_text = """ +-\033[2m----------------------------------------------------\033[0m- + \033[0;32m,--.\033[0;30m/\033[0;32m,-.\033[0m +\033[0;34m ___ __ __ __ ___ \033[0;32m/,-._.--~\'\033[0m +\033[0;34m |\\ | |__ __ / ` / \\ |__) |__ \033[0;33m} {\033[0m +\033[0;34m | \\| | \\__, \\__/ | \\ |___ \033[0;32m\\`-._,-`-,\033[0m + \033[0;32m`._,._,\'\033[0m +\033[0;35m nf-core/spatialaxe ${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" : ""} +* The nf-core framework + https://doi.org/10.1038/s41587-020-0439-x + +* Software dependencies + https://github.com/nf-core/spatialaxe/blob/master/CITATIONS.md +""" + command = "nextflow run ${workflow.manifest.name} -profile --input samplesheet.csv --mode --outdir " + + UTILS_NFSCHEMA_PLUGIN( + workflow, + validate_params, + null, + help, + help_full, + show_hidden, + before_text, + after_text, + command, + ) + + // + // Check config provided to the pipeline + // + UTILS_NFCORE_PIPELINE( + nextflow_cli_args + ) + + // + // Custom validation for pipeline parameters + // + validateInputParameters( + input, + mode, + method, + format, + image_seg_methods, + transcript_seg_methods, + relabel_genes, + gene_panel, + nucleus_segmentation_only, + segmentation_mask, + offtarget_probe_tracking, + probes_fasta, + reference_annotations, + gene_synonyms, + ) + log.info("✅ Pipeline parameters validated.") + + // + // Create channel from input file provided through --input + // + try { + + channel.fromList(samplesheetToList(input, "${projectDir}/assets/schema_input.json")) + .map { meta, bundle, image -> + return [[id: meta.id], bundle, image] + } + .set { ch_samplesheet } + + log.info("✅ Samplesheet validated.") + } + catch (Exception e) { + + error("❌ Samplesheet validation failed: ${e.message}") + } + + + // Xenium bundle file-presence validation now runs in the main workflow + // (workflows/spatialaxe.nf) AFTER UNTAR staging, so it works uniformly for + // both directory inputs and tarball inputs. + + emit: + samplesheet = ch_samplesheet +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SUBWORKFLOW FOR PIPELINE COMPLETION +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow PIPELINE_COMPLETION { + take: + email // string: email address + email_on_fail // string: email address sent on pipeline failure + 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: + summary_params = paramsSummaryMap(workflow, parameters_schema: "nextflow_schema.json") + def multiqc_reports = multiqc_report.toList() + + // + // Completion email and summary + // + workflow.onComplete { + if (email || email_on_fail) { + completionEmail( + summary_params, + email, + email_on_fail, + plaintext_email, + outdir, + monochrome_logs, + multiqc_reports.getVal(), + ) + } + + completionSummary(monochrome_logs) + if (hook_url) { + imNotification(summary_params, hook_url) + } + } + + workflow.onError { + error("❌ Pipeline failed. Please refer to troubleshooting docs: https://nf-co.re/docs/usage/troubleshooting") + } +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ +// +// Check and validate pipeline parameters +// +def validateInputParameters( + input, + mode, + method, + format, + image_seg_methods, + transcript_seg_methods, + relabel_genes, + gene_panel, + nucleus_segmentation_only, + segmentation_mask, + offtarget_probe_tracking, + probes_fasta, + reference_annotations, + gene_synonyms +) { + + // check if conda profile is provided + if (workflow.profile.contains('conda')) { + error("❌ Error: `nf-core/spatialaxe` does not support running the pipeline with profile: conda ") + } + + // check if the samplesheet provided with the test config is assets/samplesheet.csv + if (workflow.profile.contains('test') && !"${input}".endsWith("assets/samplesheet.csv")) { + error("❌ Error: Use the samplesheet at: ${projectDir}/assets/samplesheet.csv with `--input` when running the pipeline in test profile.") + } + + // check if the segmentation method provided is valid for a mode + if (mode == 'image' && method) { + if (!image_seg_methods.contains(method)) { + error("❌ Error: Invalid segmentation method: ${method} provided for the `image` based mode. Options: ${image_seg_methods}") + } + } + + if (mode == 'coordinate' && method) { + if (!transcript_seg_methods.contains(method)) { + error("❌ Error: Invalid segmentation method: `${method}` provided for the `coordinate` based mode. Options: ${transcript_seg_methods}") + } + } + + // check method-format compatibility (schema enum constrains the universe; this enforces the method-specific subset) + def valid_segger_formats = ['xenium'] + def valid_proseg_formats = ['xenium', 'cosmx', 'merscope'] + if (method == 'segger' && !(format in valid_segger_formats)) { + error("❌ Error: Invalid --format '${format}' for segger. Valid: ${valid_segger_formats}") + } + if (method == 'proseg' && !(format in valid_proseg_formats)) { + error("❌ Error: Invalid --format '${format}' for proseg. Valid: ${valid_proseg_formats}") + } + + // check if --relabel_genes is true but --gene_panel is not provided + if (relabel_genes && !gene_panel) { + log.warn("⚠️ Relabel genes is enabled, but gene panel is not provided with the `--gene_panel`. Using `gene_panel.json` in the xenium bundle.") + } + + // check if --relabel_genes is true but --gene_panel is not provided + if (gene_panel && !relabel_genes) { + log.warn("⚠️ Gene panel provided, but relabel genes is disabled. Using `gene_panel.json` only to generate metadata.") + } + + // check if segmentation method is xeniumranger and nucleus_ony_segmentation is enabled + if (method == 'xeniumranger' && !nucleus_segmentation_only) { + log.warn("⚠️ Nucleus segmentation is disabled. Running xeniumranger resegment module to redefine xenium bundle without nucleus segmentation.") + log.warn("⚠️ Use --nucleus_segmentation_only to enable nucleus segmentation to redefine xenium bundle with import-segmentation module.") + } + + // check if segmentation mask is provided in image mode and baysor method + if (mode == 'image' && method == 'baysor') { + if (!segmentation_mask) { + log.warn("⚠️ Missing segmentation mask with `--segmentation_mask` when pipeline is run in ${mode} and with the ${method}. Running in coordinate mode.") + } + } + + // check if required arguments are provided for off-target probe tracking + if (!mode && offtarget_probe_tracking) { + if(!probes_fasta || !reference_annotations || !gene_synonyms) { + error("❌ Error: Missing required param(s) for off-target-proebe detection.") + } + error("❌ Error: Use --mode qc and --offtraget_probe_tracking to run off-target probe tracking.") + } +} + +// +// Generate methods description for MultiQC +// +def toolCitationText() { + // Can use ternary operators to dynamically construct based conditions, e.g. params["run_xyz"] ? "Tool (Foo et al. 2023)" : "", + // Uncomment function in methodsDescriptionText to render in MultiQC report + def citation_text = [ + "Tools used in the workflow included:", + "MultiQC (Ewels et al. 2016)", + ".", + ].join(' ').trim() + + return citation_text +} + +def toolBibliographyText() { + // Can use ternary operators to dynamically construct based conditions, e.g. params["run_xyz"] ? "
  • Author (2023) Pub name, Journal, DOI
  • " : "", + // Uncomment function in methodsDescriptionText to render in MultiQC report + def reference_text = [ + "
  • Ewels, P., Magnusson, M., Lundin, S., & Käller, M. (2016). MultiQC: summarize analysis results for multiple tools and samples in a single report. Bioinformatics , 32(19), 3047–3048. doi: /10.1093/bioinformatics/btw354
  • " + ].join(' ').trim() + + return reference_text +} + +def methodsDescriptionText(mqc_methods_yaml) { + // Convert to a named map so can be used as with familiar NXF ${workflow} variable syntax in the MultiQC YML file + def meta = [:] + meta.workflow = workflow.toMap() + meta["manifest_map"] = workflow.manifest.toMap() + + // Pipeline DOI + if (meta.manifest_map.doi) { + // Using a loop to handle multiple DOIs + // Removing `https://doi.org/` to handle pipelines using DOIs vs DOI resolvers + // Removing ` ` since the manifest.doi is a string and not a proper list + def temp_doi_ref = "" + def manifest_doi = meta.manifest_map.doi.tokenize(",") + manifest_doi.each { doi_ref -> + temp_doi_ref += "(doi:
    ${doi_ref.replace("https://doi.org/", "").replace(" ", "")}), " + } + meta["doi_text"] = temp_doi_ref.substring(0, temp_doi_ref.length() - 2) + } + else { + meta["doi_text"] = "" + } + meta["nodoi_text"] = meta.manifest_map.doi ? "" : "
  • If available, make sure to update the text to include the Zenodo DOI of version of the pipeline used.
  • " + + // Tool references + meta["tool_citations"] = "" + meta["tool_bibliography"] = "" + + // Only uncomment below if logic in toolCitationText/toolBibliographyText has been filled! + // meta["tool_citations"] = toolCitationText().replaceAll(", \\.", ".").replaceAll("\\. \\.", ".").replaceAll(", \\.", ".") + // meta["tool_bibliography"] = toolBibliographyText() + + def methods_text = mqc_methods_yaml.text + + def engine = new groovy.text.SimpleTemplateEngine() + def description_html = engine.createTemplate(methods_text).make(meta) + + return description_html.toString() +} diff --git a/subworkflows/local/utils_nfcore_spatialxe_pipeline/main.nf b/subworkflows/local/utils_nfcore_spatialxe_pipeline/main.nf deleted file mode 100644 index c60249c8..00000000 --- a/subworkflows/local/utils_nfcore_spatialxe_pipeline/main.nf +++ /dev/null @@ -1,295 +0,0 @@ -// -// Subworkflow with functionality specific to the nf-core/spatialxe pipeline -// - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - IMPORT FUNCTIONS / MODULES / SUBWORKFLOWS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -include { UTILS_NFSCHEMA_PLUGIN } from '../../nf-core/utils_nfschema_plugin' -include { paramsSummaryMap } from 'plugin/nf-schema' -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 { UTILS_NFCORE_PIPELINE } from '../../nf-core/utils_nfcore_pipeline' -include { UTILS_NEXTFLOW_PIPELINE } from '../../nf-core/utils_nextflow_pipeline' - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - SUBWORKFLOW TO INITIALISE PIPELINE -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -workflow PIPELINE_INITIALISATION { - - take: - version // boolean: Display version and exit - validate_params // boolean: Boolean whether to validate parameters against the schema at runtime - monochrome_logs // boolean: Do not use coloured log outputs - nextflow_cli_args // array: List of positional nextflow CLI args - outdir // string: The output directory where the results will be saved - input // string: Path to input samplesheet - help // boolean: Display help message and exit - help_full // boolean: Show the full help message - show_hidden // boolean: Show hidden parameters in the help message - - main: - - ch_versions = channel.empty() - - // - // Print version and exit if required and dump pipeline parameters to JSON file - // - UTILS_NEXTFLOW_PIPELINE ( - version, - true, - outdir, - workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1 - ) - - // - // 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 -\033[0;34m ___ __ __ __ ___ \033[0;32m/,-._.--~\'\033[0m -\033[0;34m |\\ | |__ __ / ` / \\ |__) |__ \033[0;33m} {\033[0m -\033[0;34m | \\| | \\__, \\__/ | \\ |___ \033[0;32m\\`-._,-`-,\033[0m - \033[0;32m`._,._,\'\033[0m -\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 { 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 - -* 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 ( - workflow, - validate_params, - null, - help, - help_full, - show_hidden, - before_text, - after_text, - command - ) - - // - // Check config provided to the pipeline - // - UTILS_NFCORE_PIPELINE ( - nextflow_cli_args - ) - - // - // Custom validation for pipeline parameters - // - validateInputParameters() - - // - // Create channel from input file provided through params.input - // - - channel - .fromList(samplesheetToList(input, "${projectDir}/assets/schema_input.json")) - .map { - meta, fastq_1, fastq_2 -> - if (!fastq_2) { - return [ meta.id, meta + [ single_end:true ], [ fastq_1 ] ] - } else { - return [ meta.id, meta + [ single_end:false ], [ fastq_1, fastq_2 ] ] - } - } - .groupTuple() - .map { samplesheet -> - validateInputSamplesheet(samplesheet) - } - .map { - meta, fastqs -> - return [ meta, fastqs.flatten() ] - } - .set { ch_samplesheet } - - emit: - samplesheet = ch_samplesheet - versions = ch_versions -} - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - SUBWORKFLOW FOR PIPELINE COMPLETION -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -workflow PIPELINE_COMPLETION { - - take: - email // string: email address - email_on_fail // string: email address sent on pipeline failure - 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 - multiqc_report // string: Path to MultiQC report - - main: - summary_params = paramsSummaryMap(workflow, parameters_schema: "nextflow_schema.json") - def multiqc_reports = multiqc_report.toList() - - // - // Completion email and summary - // - workflow.onComplete { - if (email || email_on_fail) { - completionEmail( - summary_params, - email, - email_on_fail, - plaintext_email, - outdir, - monochrome_logs, - multiqc_reports.getVal(), - ) - } - - completionSummary(monochrome_logs) - - } - - workflow.onError { - log.error "Pipeline failed. Please refer to troubleshooting docs for common issues: https://nf-co.re/docs/running/troubleshooting" - } -} - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - FUNCTIONS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ -// -// Check and validate pipeline parameters -// -def validateInputParameters() { - genomeExistsError() -} - -// -// Validate channels from input samplesheet -// -def validateInputSamplesheet(input) { - def (metas, fastqs) = input[1..2] - - // Check that multiple runs of the same sample are of the same datatype i.e. single-end / paired-end - def endedness_ok = metas.collect{ meta -> meta.single_end }.unique().size == 1 - if (!endedness_ok) { - error("Please check input samplesheet -> Multiple runs of a sample must be of the same datatype i.e. single-end or paired-end: ${metas[0].id}") - } - - return [ metas[0], fastqs ] -} -// -// Get attribute from genome config file e.g. fasta -// -def getGenomeAttribute(attribute) { - if (params.genomes && params.genome && params.genomes.containsKey(params.genome)) { - if (params.genomes[ params.genome ].containsKey(attribute)) { - return params.genomes[ params.genome ][ attribute ] - } - } - return null -} - -// -// Exit pipeline if incorrect --genome key provided -// -def genomeExistsError() { - if (params.genomes && params.genome && !params.genomes.containsKey(params.genome)) { - def error_string = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + - " Genome '${params.genome}' not found in any config files provided to the pipeline.\n" + - " Currently, the available genome keys are:\n" + - " ${params.genomes.keySet().join(", ")}\n" + - "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - error(error_string) - } -} -// -// Generate methods description for MultiQC -// -def toolCitationText() { - // TODO nf-core: Optionally add in-text citation tools to this list. - // Can use ternary operators to dynamically construct based conditions, e.g. params["run_xyz"] ? "Tool (Foo et al. 2023)" : "", - // Uncomment function in methodsDescriptionText to render in MultiQC report - def citation_text = [ - "Tools used in the workflow included:", - "FastQC (Andrews 2010),", - "MultiQC (Ewels et al. 2016)", - "." - ].join(' ').trim() - - return citation_text -} - -def toolBibliographyText() { - // TODO nf-core: Optionally add bibliographic entries to this list. - // Can use ternary operators to dynamically construct based conditions, e.g. params["run_xyz"] ? "
  • Author (2023) Pub name, Journal, DOI
  • " : "", - // Uncomment function in methodsDescriptionText to render in MultiQC report - def reference_text = [ - "
  • Andrews S, (2010) FastQC, URL: https://www.bioinformatics.babraham.ac.uk/projects/fastqc/).
  • ", - "
  • Ewels, P., Magnusson, M., Lundin, S., & Käller, M. (2016). MultiQC: summarize analysis results for multiple tools and samples in a single report. Bioinformatics , 32(19), 3047–3048. doi: /10.1093/bioinformatics/btw354
  • " - ].join(' ').trim() - - return reference_text -} - -def methodsDescriptionText(mqc_methods_yaml) { - // Convert to a named map so can be used as with familiar NXF ${workflow} variable syntax in the MultiQC YML file - def meta = [:] - meta.workflow = workflow.toMap() - meta["manifest_map"] = workflow.manifest.toMap() - - // Pipeline DOI - if (meta.manifest_map.doi) { - // Using a loop to handle multiple DOIs - // Removing `https://doi.org/` to handle pipelines using DOIs vs DOI resolvers - // Removing ` ` since the manifest.doi is a string and not a proper list - def temp_doi_ref = "" - def manifest_doi = meta.manifest_map.doi.tokenize(",") - manifest_doi.each { doi_ref -> - temp_doi_ref += "(doi: ${doi_ref.replace("https://doi.org/", "").replace(" ", "")}), " - } - meta["doi_text"] = temp_doi_ref.substring(0, temp_doi_ref.length() - 2) - } else meta["doi_text"] = "" - meta["nodoi_text"] = meta.manifest_map.doi ? "" : "
  • If available, make sure to update the text to include the Zenodo DOI of version of the pipeline used.
  • " - - // Tool references - meta["tool_citations"] = "" - meta["tool_bibliography"] = "" - - // TODO nf-core: Only uncomment below if logic in toolCitationText/toolBibliographyText has been filled! - // meta["tool_citations"] = toolCitationText().replaceAll(", \\.", ".").replaceAll("\\. \\.", ".").replaceAll(", \\.", ".") - // meta["tool_bibliography"] = toolBibliographyText() - - - def methods_text = mqc_methods_yaml.text - - def engine = new groovy.text.SimpleTemplateEngine() - def description_html = engine.createTemplate(methods_text).make(meta) - - return description_html.toString() -} diff --git a/subworkflows/local/xeniumranger_import_segmentation_redefine_bundle/main.nf b/subworkflows/local/xeniumranger_import_segmentation_redefine_bundle/main.nf new file mode 100644 index 00000000..4c7b41d5 --- /dev/null +++ b/subworkflows/local/xeniumranger_import_segmentation_redefine_bundle/main.nf @@ -0,0 +1,133 @@ +// +// Run xeniumranger import-segmentation +// + +include { XENIUMRANGER_IMPORT_SEGMENTATION as IMP_SEG_COUNT_MATRIX_EXP_DISTANCE } from '../../../modules/nf-core/xeniumranger/import-segmentation/main' +include { XENIUMRANGER_IMPORT_SEGMENTATION as IMP_SEG_POLYGON_GEOJSON_INPUT } from '../../../modules/nf-core/xeniumranger/import-segmentation/main' +include { XENIUMRANGER_IMPORT_SEGMENTATION as IMP_SEG_TRANS_MATRIX_INPUT } from '../../../modules/nf-core/xeniumranger/import-segmentation/main' + + +workflow XENIUMRANGER_IMPORT_SEGMENTATION_REDEFINE_BUNDLE { + take: + ch_bundle_path // channel: [ val(meta), [ "path-to-xenium-bundle" ] ] + alignment_csv // value: path to alignment csv (or null) + expansion_distance // value: nuclear expansion distance + nucleus_segmentation_only // value: bool + qupath_polygons // value: path to qupath polygons dir (or null) + + main: + + ch_versions = channel.empty() + ch_redefined_bundle = channel.empty() + ch_coordinate_space = channel.empty() + + cells = ch_bundle_path.map { meta, bundle -> + return [meta, bundle + "/cells.zarr.zip"] + } + + // scenario - 1 change nuclear expansion distance / create a nucleus-only count matrix(--expansion_distance=0) + if (expansion_distance == 0 || expansion_distance != 5) { + ch_coordinate_space = "microns" + ch_imp_seg_inputs = ch_bundle_path + .combine(cells, by: 0) + .map { meta, bundle, cells_zarr -> + tuple( + meta, + bundle, + [], + [], + cells_zarr, + [], + [], + ch_coordinate_space.val, + ) + } + + IMP_SEG_COUNT_MATRIX_EXP_DISTANCE( + ch_imp_seg_inputs + ) + ch_redefined_bundle = IMP_SEG_COUNT_MATRIX_EXP_DISTANCE.out.outs + } + + // scenario - 2 polygon input - geojson format (from QuPath) + if (qupath_polygons && nucleus_segmentation_only) { + + ch_coordinate_space = "microns" + ch_imp_seg_inputs = ch_bundle_path + .combine(qupath_polygons) + .map { meta, bundle, polygons_geojson -> + tuple( + meta, + bundle, + [], + [], + polygons_geojson, + [], + [], + ch_coordinate_space.val, + ) + } + + IMP_SEG_POLYGON_GEOJSON_INPUT( + ch_imp_seg_inputs + ) + ch_redefined_bundle = IMP_SEG_POLYGON_GEOJSON_INPUT.out.outs + } + else if (qupath_polygons) { + + ch_coordinate_space = "microns" + ch_imp_seg_inputs = ch_bundle_path + .combine(qupath_polygons) + .map { meta, bundle, polygons_geojson -> + tuple( + meta, + bundle, + [], + [], + polygons_geojson, + polygons_geojson, + [], + ch_coordinate_space.val, + ) + } + + IMP_SEG_POLYGON_GEOJSON_INPUT( + ch_imp_seg_inputs + ) + ch_redefined_bundle = IMP_SEG_POLYGON_GEOJSON_INPUT.out.outs + } + + // scenario 3 - mask input - included in the cellpose subworkflow + + // scenario 4 - transcript assignment input - included in the baysor & proseg subworkflows + + // scenario 5 - transformation matrix input + if (qupath_polygons && alignment_csv) { + + ch_imp_seg_inputs = ch_bundle_path + .combine(qupath_polygons) + .combine(alignment_csv) + .map { meta, bundle, polygons_geojson, alignment_csv_file -> + tuple( + meta, + bundle, + [], + [], + polygons_geojson, + polygons_geojson, + alignment_csv_file, + ch_coordinate_space.val, + ) + } + + IMP_SEG_TRANS_MATRIX_INPUT( + ch_imp_seg_inputs + ) + ch_redefined_bundle = IMP_SEG_TRANS_MATRIX_INPUT.out.outs + } + + emit: + redefined_bundle = ch_redefined_bundle // channel: [ val(meta), ["redefined-xenium-bundle"] ] + coordinate_space = ch_coordinate_space // channel: [ ["pixels"] ] + versions = ch_versions // channel: [ versions.yml ] +} diff --git a/subworkflows/local/xeniumranger_import_segmentation_redefine_bundle/meta.yml b/subworkflows/local/xeniumranger_import_segmentation_redefine_bundle/meta.yml new file mode 100644 index 00000000..fb040abc --- /dev/null +++ b/subworkflows/local/xeniumranger_import_segmentation_redefine_bundle/meta.yml @@ -0,0 +1,32 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "xeniumranger_import_segmentation_redefine_bundle" +description: | + The import-segmentation pipeline allows you to specify 2D nuclei and/or cell segmentation results to use for + assigning transcripts to cells and recalculate all Xenium Onboard Analysis (XOA) outputs that depend on segmentation. +keywords: + - xenium + - xeniumranger import-segmentation + - qupath + - expansion distance + - segmentation + - polygons +components: + - xeniumranger/import/segmentation +input: + - ch_bundle_path: + description: | + Directory containing the raw dataset + Structure: [ val(meta), path("path-to-xenium-bundle") ] +output: + - redefined_bundle: + description: | + the redefined xenium bundle generated with the segmentation results from baysor + Structure: [ val(meta), ["redefined-xenium-bundle"] ] + - coordinate_space: + description: | + the coordinate space in which xeniumranger import-segmentation was run + Structure: [ val("microns") ] +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/subworkflows/local/xeniumranger_relabel_resegment/main.nf b/subworkflows/local/xeniumranger_relabel_resegment/main.nf new file mode 100644 index 00000000..424224a7 --- /dev/null +++ b/subworkflows/local/xeniumranger_relabel_resegment/main.nf @@ -0,0 +1,29 @@ +// +// run xeniumranger relabel & resegment to redine the xenium bundle +// + +include { XENIUMRANGER_RELABEL } from '../../../modules/nf-core/xeniumranger/relabel/main' +include { XENIUMRANGER_RESEGMENT } from '../../../modules/nf-core/xeniumranger/resegment/main' + +workflow XENIUMRANGER_RELABEL_RESEGMENT { + take: + ch_bundle_path // channel: [ val(meta), [ "path-to-xenium-bundle" ] ] + ch_gene_panel // channel: [ val(meta), ["path-to-gene_panel.json"] ] + + main: + + ch_versions = channel.empty() + + // Combine bundle path with gene panel into a single tuple for relabel + XENIUMRANGER_RELABEL( + ch_bundle_path.combine(ch_gene_panel, by: 0), + ) + + XENIUMRANGER_RESEGMENT( + XENIUMRANGER_RELABEL.out.outs + ) + + emit: + redefined_bundle = XENIUMRANGER_RESEGMENT.out.outs // channel: [ val(meta), ["redefined-xenium-bundle"] ] + versions = ch_versions // channel: [ versions.yml ] +} diff --git a/subworkflows/local/xeniumranger_relabel_resegment/meta.yml b/subworkflows/local/xeniumranger_relabel_resegment/meta.yml new file mode 100644 index 00000000..5195335e --- /dev/null +++ b/subworkflows/local/xeniumranger_relabel_resegment/meta.yml @@ -0,0 +1,33 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "xeniumranger_relabel_resegment" +description: | + The relabel pipeline allows you to change the gene labels applied to decoded transcripts. + The resegment pipeline allows you to generate a new segmentation of the morphology image space by rerunning the + Xenium Onboard Analysis (XOA) segmentation algorithms with modified parameters. +keywords: + - xenium + - xeniumranger resegment + - expansion distance + - gene panel + - relabel +components: + - xeniumranger/relabel + - xeniumranger/resegment +input: + - ch_bundle_path: + description: | + Directory containing the raw dataset + Structure: [ val(meta), path("path-to-xenium-bundle") ] + - ch_gene_panel: + description: | + The gene panel JSON file to use for relabeling decoded transcripts + Structure: [ path("path-to-gene_panel.json") ] +output: + - redefined_bundle: + description: | + the redefined xenium bundle generated with the segmentation results from baysor + Structure: [ val(meta), ["redefined-xenium-bundle"] ] +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/subworkflows/local/xeniumranger_resegment_morphology_ome_tif/main.nf b/subworkflows/local/xeniumranger_resegment_morphology_ome_tif/main.nf new file mode 100644 index 00000000..bcd97584 --- /dev/null +++ b/subworkflows/local/xeniumranger_resegment_morphology_ome_tif/main.nf @@ -0,0 +1,61 @@ +// +// Run xeniumranger resegment +// + +include { XENIUMRANGER_RESEGMENT } from '../../../modules/nf-core/xeniumranger/resegment/main' +include { XENIUMRANGER_IMPORT_SEGMENTATION } from '../../../modules/nf-core/xeniumranger/import-segmentation/main' + +workflow XENIUMRANGER_RESEGMENT_MORPHOLOGY_OME_TIF { + take: + ch_bundle_path // channel: [ val(meta), ["path-to-xenium-bundle"] ] + nucleus_segmentation_only // value: bool + + main: + + ch_redefined_bundle = channel.empty() + ch_coordinate_space = channel.value("pixels") + + // run resegment with changed config values + XENIUMRANGER_RESEGMENT(ch_bundle_path) + + + // run import segmentation to redine xenium bundle along with nuclear segmentation + // Keep meta in the cells channel for proper per-sample joining + def cells = XENIUMRANGER_RESEGMENT.out.outs.map { meta, bundle -> + return [meta, bundle + "/cells.zarr.zip"] + } + + // adjust the nuclear expansion distance without altering nuclei detection + if (nucleus_segmentation_only) { + + def ch_imp_seg_inputs = ch_bundle_path + .join(XENIUMRANGER_RESEGMENT.out.outs, by: 0) + .join(cells, by: 0) + .map { meta, bundle, _reseg_bundle, cells_zarr -> + tuple( + meta, + bundle, + [], + [], + [], + cells_zarr, + [], + "pixels", + ) + } + + XENIUMRANGER_IMPORT_SEGMENTATION( + ch_imp_seg_inputs + ) + + ch_redefined_bundle = XENIUMRANGER_IMPORT_SEGMENTATION.out.outs + } + else { + + ch_redefined_bundle = XENIUMRANGER_RESEGMENT.out.outs + } + + emit: + redefined_bundle = ch_redefined_bundle // channel: [ val(meta), ["redefined-xenium-bundle"] ] + coordinate_space = ch_coordinate_space // channel: [ ["pixels"] ] +} diff --git a/subworkflows/local/xeniumranger_resegment_morphology_ome_tif/meta.yml b/subworkflows/local/xeniumranger_resegment_morphology_ome_tif/meta.yml new file mode 100644 index 00000000..3af4f3aa --- /dev/null +++ b/subworkflows/local/xeniumranger_resegment_morphology_ome_tif/meta.yml @@ -0,0 +1,33 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "xeniumranger_resegment_morphology_ome_tif" +description: | + The resegment pipeline allows you to generate a new segmentation of the morphology image space by rerunning the + Xenium Onboard Analysis (XOA) segmentation algorithms with modified parameters. +keywords: + - xenium + - xeniumranger resegment + - xeniumranger import-segmentation + - expansion distance + - cell segmentation + - nucleus segmentation +components: + - xeniumranger/import/segmentation + - xeniumranger/resegment +input: + - ch_bundle_path: + description: | + Directory containing the raw dataset + Structure: [ val(meta), path("path-to-xenium-bundle") ] +output: + - redefined_bundle: + description: | + the redefined xenium bundle generated with the segmentation results from baysor + Structure: [ val(meta), ["redefined-xenium-bundle"] ] + - coordinate_space: + description: | + the coordinate space in which xeniumranger import-segmentation was run + Structure: [ val("pixels") ] +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test b/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test index 68718e4f..897d6681 100644 --- a/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test +++ b/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test @@ -40,9 +40,9 @@ nextflow_function { } } - test("Test Function checkCondaChannels") { + test("Test Function checkCondachannels") { - function "checkCondaChannels" + function "checkCondachannels" then { assertAll( diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap b/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap index e3f0baf4..f03a352a 100644 --- a/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap +++ b/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap @@ -9,7 +9,7 @@ }, "timestamp": "2024-02-28T12:02:05.308243" }, - "Test Function checkCondaChannels": { + "Test Function checkCondachannels": { "content": null, "meta": { "nf-test": "0.8.4", diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/main.nf b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf index afca5439..bfd25876 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 } /* @@ -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())) } // @@ -353,3 +353,67 @@ 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 deleted file mode 100644 index 8940d32d..00000000 --- a/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.nf.test +++ /dev/null @@ -1,29 +0,0 @@ -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 deleted file mode 100644 index 859d1030..00000000 --- a/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.nf.test.snap +++ /dev/null @@ -1,19 +0,0 @@ -{ - "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 1df8b76f..ee4738c8 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 != "true") ? params.help : "", + params.help instanceof String ? params.help : "", ) exit 0 } @@ -71,3 +71,4 @@ 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 f6537cc3..8d8c7371 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.6.1" + id "nf-schema@2.5.1" } validation { diff --git a/tests/.nftignore b/tests/.nftignore index e128a128..2e57a91e 100644 --- a/tests/.nftignore +++ b/tests/.nftignore @@ -8,5 +8,5 @@ multiqc/multiqc_data/multiqc_software_versions.txt multiqc/multiqc_data/llms-full.txt multiqc/multiqc_plots/{svg,pdf,png}/*.{svg,pdf,png} multiqc/multiqc_report.html -fastqc/*_fastqc.{html,zip} pipeline_info/*.{html,json,txt,yml} +**/proseg/preset/** diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..a8eb3c10 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,9 @@ +"""Pytest configuration for nf-xenium-processing tests.""" + +from __future__ import annotations + +import sys +from pathlib import Path + +# Add bin/ to path so skill_*.py modules can be imported +sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "bin")) diff --git a/tests/coordinate_mode.nf.test b/tests/coordinate_mode.nf.test new file mode 100644 index 00000000..e0ff2ac4 --- /dev/null +++ b/tests/coordinate_mode.nf.test @@ -0,0 +1,36 @@ +nextflow_pipeline { + + name "Test pipeline for the `coordinate` mode, test run the proseg subworkflow" + script "../main.nf" + tag "pipeline" + config "../conf/test_coordinate_mode.config" + + test("-profile test stub") { + + options "-stub" + + when { + params { + outdir = "$outputDir" + } + } + + 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') + 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_spatialaxe_software_mqc_versions.yml"), + // All stable path name, with a relative path + stable_name, + // All files with stable contents + stable_path + ).match() } + ) + } + } +} diff --git a/tests/coordinate_mode.nf.test.snap b/tests/coordinate_mode.nf.test.snap new file mode 100644 index 00000000..72a452ec --- /dev/null +++ b/tests/coordinate_mode.nf.test.snap @@ -0,0 +1,152 @@ +{ + "-profile test stub": { + "content": [ + { + "PROSEG2BAYSOR": { + "proseg": "3.1.0" + }, + "PROSEG": { + "proseg": "3.1.0" + }, + "SPATIALDATA_MERGE_RAW_REDEFINED": { + "spatialdata": "0.7.2" + }, + "SPATIALDATA_META": { + "spatialdata": "0.7.2" + }, + "SPATIALDATA_WRITE_RAW_BUNDLE": { + "spatialdata": "0.7.2" + }, + "SPATIALDATA_WRITE_REDEFINED_BUNDLE": { + "spatialdata": "0.7.2" + }, + "UNTAR": { + "untar": 1.34 + }, + "Workflow": { + "nf-core/spatialaxe": "v1.1.0dev" + }, + "XENIUMRANGER_IMPORT_SEGMENTATION": { + "xeniumranger": "4.0.1.1" + } + }, + [ + "coordinate", + "coordinate/multiqc", + "coordinate/multiqc/raw_bundle", + "coordinate/multiqc/raw_bundle/multiqc_data", + "coordinate/multiqc/raw_bundle/multiqc_data/.stub", + "coordinate/multiqc/raw_bundle/multiqc_plots", + "coordinate/multiqc/raw_bundle/multiqc_plots/.stub", + "coordinate/multiqc/raw_bundle/multiqc_report.html", + "coordinate/multiqc/redefined_bundle", + "coordinate/multiqc/redefined_bundle/multiqc_data", + "coordinate/multiqc/redefined_bundle/multiqc_data/.stub", + "coordinate/multiqc/redefined_bundle/multiqc_plots", + "coordinate/multiqc/redefined_bundle/multiqc_plots/.stub", + "coordinate/multiqc/redefined_bundle/multiqc_report.html", + "coordinate/proseg", + "coordinate/proseg/preset", + "coordinate/proseg/preset/test_run", + "coordinate/proseg/preset/test_run/cell-polygons.geojson.gz", + "coordinate/proseg/preset/test_run/proseg-output.zarr", + "coordinate/proseg/preset/test_run/transcript-metadata.csv.gz", + "coordinate/proseg/proseg2baysor", + "coordinate/proseg/proseg2baysor/test_run", + "coordinate/proseg/proseg2baysor/test_run/cell-polygons.geojson", + "coordinate/proseg/proseg2baysor/test_run/transcript-metadata.csv", + "coordinate/spatialdata", + "coordinate/spatialdata/merge", + "coordinate/spatialdata/merge/spatialdata", + "coordinate/spatialdata/merge/spatialdata/test_run", + "coordinate/spatialdata/merge/spatialdata/test_run/merged_bundle", + "coordinate/spatialdata/merge/spatialdata/test_run/merged_bundle/fake_file.txt", + "coordinate/spatialdata/meta", + "coordinate/spatialdata/meta/spatialdata", + "coordinate/spatialdata/meta/spatialdata/test_run", + "coordinate/spatialdata/meta/spatialdata/test_run/metadata", + "coordinate/spatialdata/meta/spatialdata/test_run/metadata/fake_file.txt", + "coordinate/spatialdata/write", + "coordinate/spatialdata/write/spatialdata", + "coordinate/spatialdata/write/spatialdata/test_run", + "coordinate/spatialdata/write/spatialdata/test_run/raw_bundle", + "coordinate/spatialdata/write/spatialdata/test_run/raw_bundle/fake_file.txt", + "coordinate/spatialdata/write/spatialdata/test_run/redefined_bundle", + "coordinate/spatialdata/write/spatialdata/test_run/redefined_bundle/fake_file.txt", + "coordinate/untar", + "coordinate/untar/test_run", + "coordinate/untar/test_run/.end-of-run", + "coordinate/untar/test_run/analysis.tar.gz", + "coordinate/untar/test_run/analysis.zarr.zip", + "coordinate/untar/test_run/analysis_summary.html", + "coordinate/untar/test_run/aux_outputs.tar.gz", + "coordinate/untar/test_run/cell_boundaries.csv.gz", + "coordinate/untar/test_run/cell_boundaries.parquet", + "coordinate/untar/test_run/cell_feature_matrix.h5", + "coordinate/untar/test_run/cell_feature_matrix.tar.gz", + "coordinate/untar/test_run/cell_feature_matrix.zarr.zip", + "coordinate/untar/test_run/cells.csv.gz", + "coordinate/untar/test_run/cells.parquet", + "coordinate/untar/test_run/cells.zarr.zip", + "coordinate/untar/test_run/experiment.xenium", + "coordinate/untar/test_run/gene_panel.json", + "coordinate/untar/test_run/metrics_summary.csv", + "coordinate/untar/test_run/morphology.ome.tif", + "coordinate/untar/test_run/morphology_focus", + "coordinate/untar/test_run/morphology_focus/morphology_focus_0000.ome.tif", + "coordinate/untar/test_run/nucleus_boundaries.csv.gz", + "coordinate/untar/test_run/nucleus_boundaries.parquet", + "coordinate/untar/test_run/transcripts.parquet", + "coordinate/untar/test_run/transcripts.zarr.zip", + "coordinate/xeniumranger", + "coordinate/xeniumranger/import_segementation", + "coordinate/xeniumranger/import_segementation/test_run", + "coordinate/xeniumranger/import_segementation/test_run/experiment.xenium", + "pipeline_info", + "pipeline_info/nf_core_spatialaxe_software_mqc_versions.yml" + ], + [ + ".stub:md5,d41d8cd98f00b204e9800998ecf8427e", + ".stub:md5,d41d8cd98f00b204e9800998ecf8427e", + "multiqc_report.html:md5,d41d8cd98f00b204e9800998ecf8427e", + ".stub:md5,d41d8cd98f00b204e9800998ecf8427e", + ".stub:md5,d41d8cd98f00b204e9800998ecf8427e", + "multiqc_report.html:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell-polygons.geojson:md5,d41d8cd98f00b204e9800998ecf8427e", + "transcript-metadata.csv:md5,d41d8cd98f00b204e9800998ecf8427e", + "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e", + "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e", + "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e", + "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e", + ".end-of-run:md5,d41d8cd98f00b204e9800998ecf8427e", + "analysis.tar.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "analysis.zarr.zip:md5,d41d8cd98f00b204e9800998ecf8427e", + "analysis_summary.html:md5,d41d8cd98f00b204e9800998ecf8427e", + "aux_outputs.tar.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_boundaries.csv.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_boundaries.parquet:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_feature_matrix.h5:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_feature_matrix.tar.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_feature_matrix.zarr.zip:md5,d41d8cd98f00b204e9800998ecf8427e", + "cells.csv.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "cells.parquet:md5,d41d8cd98f00b204e9800998ecf8427e", + "cells.zarr.zip:md5,d41d8cd98f00b204e9800998ecf8427e", + "experiment.xenium:md5,d41d8cd98f00b204e9800998ecf8427e", + "gene_panel.json:md5,d41d8cd98f00b204e9800998ecf8427e", + "metrics_summary.csv:md5,d41d8cd98f00b204e9800998ecf8427e", + "morphology.ome.tif:md5,d41d8cd98f00b204e9800998ecf8427e", + "morphology_focus_0000.ome.tif:md5,d41d8cd98f00b204e9800998ecf8427e", + "nucleus_boundaries.csv.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "nucleus_boundaries.parquet:md5,d41d8cd98f00b204e9800998ecf8427e", + "transcripts.parquet:md5,d41d8cd98f00b204e9800998ecf8427e", + "transcripts.zarr.zip:md5,d41d8cd98f00b204e9800998ecf8427e", + "experiment.xenium:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "timestamp": "2026-05-18T21:58:08.945192717", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + } +} \ No newline at end of file diff --git a/tests/default.nf.test b/tests/default.nf.test index d5000f57..84c71fc2 100644 --- a/tests/default.nf.test +++ b/tests/default.nf.test @@ -1,10 +1,13 @@ nextflow_pipeline { - name "Test pipeline" + name "Test pipeline - runs the default pipeline tests (coordinate mode)" script "../main.nf" tag "pipeline" + config "../conf/test.config" - test("-profile test") { + test("-profile test stub") { + + options "-stub" when { params { @@ -13,19 +16,19 @@ nextflow_pipeline { } then { - // 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 + // 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') 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"), + removeNextflowVersion("$outputDir/pipeline_info/nf_core_spatialaxe_software_mqc_versions.yml"), // All stable path name, with a relative path - stable_path, + stable_name, // All files with stable contents - stable_content + stable_path ).match() } ) } diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap new file mode 100644 index 00000000..3289c2a0 --- /dev/null +++ b/tests/default.nf.test.snap @@ -0,0 +1,152 @@ +{ + "-profile test stub": { + "content": [ + { + "PROSEG2BAYSOR": { + "proseg": "3.1.0" + }, + "PROSEG": { + "proseg": "3.1.0" + }, + "SPATIALDATA_MERGE_RAW_REDEFINED": { + "spatialdata": "0.7.2" + }, + "SPATIALDATA_META": { + "spatialdata": "0.7.2" + }, + "SPATIALDATA_WRITE_RAW_BUNDLE": { + "spatialdata": "0.7.2" + }, + "SPATIALDATA_WRITE_REDEFINED_BUNDLE": { + "spatialdata": "0.7.2" + }, + "UNTAR": { + "untar": 1.34 + }, + "Workflow": { + "nf-core/spatialaxe": "v1.1.0dev" + }, + "XENIUMRANGER_IMPORT_SEGMENTATION": { + "xeniumranger": "4.0.1.1" + } + }, + [ + "coordinate", + "coordinate/multiqc", + "coordinate/multiqc/raw_bundle", + "coordinate/multiqc/raw_bundle/multiqc_data", + "coordinate/multiqc/raw_bundle/multiqc_data/.stub", + "coordinate/multiqc/raw_bundle/multiqc_plots", + "coordinate/multiqc/raw_bundle/multiqc_plots/.stub", + "coordinate/multiqc/raw_bundle/multiqc_report.html", + "coordinate/multiqc/redefined_bundle", + "coordinate/multiqc/redefined_bundle/multiqc_data", + "coordinate/multiqc/redefined_bundle/multiqc_data/.stub", + "coordinate/multiqc/redefined_bundle/multiqc_plots", + "coordinate/multiqc/redefined_bundle/multiqc_plots/.stub", + "coordinate/multiqc/redefined_bundle/multiqc_report.html", + "coordinate/proseg", + "coordinate/proseg/preset", + "coordinate/proseg/preset/test_run", + "coordinate/proseg/preset/test_run/cell-polygons.geojson.gz", + "coordinate/proseg/preset/test_run/proseg-output.zarr", + "coordinate/proseg/preset/test_run/transcript-metadata.csv.gz", + "coordinate/proseg/proseg2baysor", + "coordinate/proseg/proseg2baysor/test_run", + "coordinate/proseg/proseg2baysor/test_run/cell-polygons.geojson", + "coordinate/proseg/proseg2baysor/test_run/transcript-metadata.csv", + "coordinate/spatialdata", + "coordinate/spatialdata/merge", + "coordinate/spatialdata/merge/spatialdata", + "coordinate/spatialdata/merge/spatialdata/test_run", + "coordinate/spatialdata/merge/spatialdata/test_run/merged_bundle", + "coordinate/spatialdata/merge/spatialdata/test_run/merged_bundle/fake_file.txt", + "coordinate/spatialdata/meta", + "coordinate/spatialdata/meta/spatialdata", + "coordinate/spatialdata/meta/spatialdata/test_run", + "coordinate/spatialdata/meta/spatialdata/test_run/metadata", + "coordinate/spatialdata/meta/spatialdata/test_run/metadata/fake_file.txt", + "coordinate/spatialdata/write", + "coordinate/spatialdata/write/spatialdata", + "coordinate/spatialdata/write/spatialdata/test_run", + "coordinate/spatialdata/write/spatialdata/test_run/raw_bundle", + "coordinate/spatialdata/write/spatialdata/test_run/raw_bundle/fake_file.txt", + "coordinate/spatialdata/write/spatialdata/test_run/redefined_bundle", + "coordinate/spatialdata/write/spatialdata/test_run/redefined_bundle/fake_file.txt", + "coordinate/untar", + "coordinate/untar/test_run", + "coordinate/untar/test_run/.end-of-run", + "coordinate/untar/test_run/analysis.tar.gz", + "coordinate/untar/test_run/analysis.zarr.zip", + "coordinate/untar/test_run/analysis_summary.html", + "coordinate/untar/test_run/aux_outputs.tar.gz", + "coordinate/untar/test_run/cell_boundaries.csv.gz", + "coordinate/untar/test_run/cell_boundaries.parquet", + "coordinate/untar/test_run/cell_feature_matrix.h5", + "coordinate/untar/test_run/cell_feature_matrix.tar.gz", + "coordinate/untar/test_run/cell_feature_matrix.zarr.zip", + "coordinate/untar/test_run/cells.csv.gz", + "coordinate/untar/test_run/cells.parquet", + "coordinate/untar/test_run/cells.zarr.zip", + "coordinate/untar/test_run/experiment.xenium", + "coordinate/untar/test_run/gene_panel.json", + "coordinate/untar/test_run/metrics_summary.csv", + "coordinate/untar/test_run/morphology.ome.tif", + "coordinate/untar/test_run/morphology_focus", + "coordinate/untar/test_run/morphology_focus/morphology_focus_0000.ome.tif", + "coordinate/untar/test_run/nucleus_boundaries.csv.gz", + "coordinate/untar/test_run/nucleus_boundaries.parquet", + "coordinate/untar/test_run/transcripts.parquet", + "coordinate/untar/test_run/transcripts.zarr.zip", + "coordinate/xeniumranger", + "coordinate/xeniumranger/import_segementation", + "coordinate/xeniumranger/import_segementation/test_run", + "coordinate/xeniumranger/import_segementation/test_run/experiment.xenium", + "pipeline_info", + "pipeline_info/nf_core_spatialaxe_software_mqc_versions.yml" + ], + [ + ".stub:md5,d41d8cd98f00b204e9800998ecf8427e", + ".stub:md5,d41d8cd98f00b204e9800998ecf8427e", + "multiqc_report.html:md5,d41d8cd98f00b204e9800998ecf8427e", + ".stub:md5,d41d8cd98f00b204e9800998ecf8427e", + ".stub:md5,d41d8cd98f00b204e9800998ecf8427e", + "multiqc_report.html:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell-polygons.geojson:md5,d41d8cd98f00b204e9800998ecf8427e", + "transcript-metadata.csv:md5,d41d8cd98f00b204e9800998ecf8427e", + "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e", + "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e", + "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e", + "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e", + ".end-of-run:md5,d41d8cd98f00b204e9800998ecf8427e", + "analysis.tar.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "analysis.zarr.zip:md5,d41d8cd98f00b204e9800998ecf8427e", + "analysis_summary.html:md5,d41d8cd98f00b204e9800998ecf8427e", + "aux_outputs.tar.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_boundaries.csv.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_boundaries.parquet:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_feature_matrix.h5:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_feature_matrix.tar.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_feature_matrix.zarr.zip:md5,d41d8cd98f00b204e9800998ecf8427e", + "cells.csv.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "cells.parquet:md5,d41d8cd98f00b204e9800998ecf8427e", + "cells.zarr.zip:md5,d41d8cd98f00b204e9800998ecf8427e", + "experiment.xenium:md5,d41d8cd98f00b204e9800998ecf8427e", + "gene_panel.json:md5,d41d8cd98f00b204e9800998ecf8427e", + "metrics_summary.csv:md5,d41d8cd98f00b204e9800998ecf8427e", + "morphology.ome.tif:md5,d41d8cd98f00b204e9800998ecf8427e", + "morphology_focus_0000.ome.tif:md5,d41d8cd98f00b204e9800998ecf8427e", + "nucleus_boundaries.csv.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "nucleus_boundaries.parquet:md5,d41d8cd98f00b204e9800998ecf8427e", + "transcripts.parquet:md5,d41d8cd98f00b204e9800998ecf8427e", + "transcripts.zarr.zip:md5,d41d8cd98f00b204e9800998ecf8427e", + "experiment.xenium:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "timestamp": "2026-05-18T21:55:37.544217665", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + } +} \ No newline at end of file diff --git a/tests/image_mode.nf.test b/tests/image_mode.nf.test new file mode 100644 index 00000000..4a594afa --- /dev/null +++ b/tests/image_mode.nf.test @@ -0,0 +1,36 @@ +nextflow_pipeline { + + name "Test pipeline for the `image` mode, test run the cellpose->baysor subworkflow" + script "../main.nf" + tag "pipeline" + config "../conf/test_image_mode.config" + + test("-profile test stub") { + + options "-stub" + + when { + params { + outdir = "$outputDir" + } + } + + 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') + 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_spatialaxe_software_mqc_versions.yml"), + // All stable path name, with a relative path + stable_name, + // All files with stable contents + stable_path + ).match() } + ) + } + } +} diff --git a/tests/image_mode.nf.test.snap b/tests/image_mode.nf.test.snap new file mode 100644 index 00000000..a9b73c2c --- /dev/null +++ b/tests/image_mode.nf.test.snap @@ -0,0 +1,167 @@ +{ + "-profile test stub": { + "content": [ + { + "BAYSOR_PREPROCESS_TRANSCRIPTS": { + "python": "3.14.4" + }, + "BAYSOR_RUN": { + "baysor": "0.7.1" + }, + "CELLPOSE_CELLS": { + "torch": "2.10.0+cu128" + }, + "RESIZE_TIF": { + "tifffile": "2026.2.24" + }, + "SPATIALDATA_MERGE_RAW_REDEFINED": { + "spatialdata": "0.7.2" + }, + "SPATIALDATA_META": { + "spatialdata": "0.7.2" + }, + "SPATIALDATA_WRITE_RAW_BUNDLE": { + "spatialdata": "0.7.2" + }, + "SPATIALDATA_WRITE_REDEFINED_BUNDLE": { + "spatialdata": "0.7.2" + }, + "UNTAR": { + "untar": 1.34 + }, + "Workflow": { + "nf-core/spatialaxe": "v1.1.0dev" + }, + "XENIUMRANGER_IMPORT_SEGMENTATION": { + "xeniumranger": "4.0.1.1" + } + }, + [ + "coordinate", + "coordinate/xeniumranger", + "coordinate/xeniumranger/import_segementation", + "coordinate/xeniumranger/import_segementation/test_run", + "coordinate/xeniumranger/import_segementation/test_run/experiment.xenium", + "image", + "image/baysor", + "image/baysor/preprocess", + "image/baysor/preprocess/test_run", + "image/baysor/preprocess/test_run/filtered_transcripts.csv", + "image/baysor/run", + "image/baysor/run/test_run", + "image/baysor/run/test_run/segmentation.csv", + "image/baysor/run/test_run/segmentation_polygons_2d.json", + "image/cellpose_cells", + "image/cellpose_cells/test_run", + "image/cellpose_cells/test_run/morphology_focus_0000.ome_cp_masks.tif", + "image/multiqc", + "image/multiqc/raw_bundle", + "image/multiqc/raw_bundle/multiqc_data", + "image/multiqc/raw_bundle/multiqc_data/.stub", + "image/multiqc/raw_bundle/multiqc_plots", + "image/multiqc/raw_bundle/multiqc_plots/.stub", + "image/multiqc/raw_bundle/multiqc_report.html", + "image/multiqc/redefined_bundle", + "image/multiqc/redefined_bundle/multiqc_data", + "image/multiqc/redefined_bundle/multiqc_data/.stub", + "image/multiqc/redefined_bundle/multiqc_plots", + "image/multiqc/redefined_bundle/multiqc_plots/.stub", + "image/multiqc/redefined_bundle/multiqc_report.html", + "image/spatialdata", + "image/spatialdata/merge", + "image/spatialdata/merge/spatialdata", + "image/spatialdata/merge/spatialdata/test_run", + "image/spatialdata/merge/spatialdata/test_run/merged_bundle", + "image/spatialdata/merge/spatialdata/test_run/merged_bundle/fake_file.txt", + "image/spatialdata/meta", + "image/spatialdata/meta/spatialdata", + "image/spatialdata/meta/spatialdata/test_run", + "image/spatialdata/meta/spatialdata/test_run/metadata", + "image/spatialdata/meta/spatialdata/test_run/metadata/fake_file.txt", + "image/spatialdata/write", + "image/spatialdata/write/spatialdata", + "image/spatialdata/write/spatialdata/test_run", + "image/spatialdata/write/spatialdata/test_run/raw_bundle", + "image/spatialdata/write/spatialdata/test_run/raw_bundle/fake_file.txt", + "image/spatialdata/write/spatialdata/test_run/redefined_bundle", + "image/spatialdata/write/spatialdata/test_run/redefined_bundle/fake_file.txt", + "image/untar", + "image/untar/test_run", + "image/untar/test_run/.end-of-run", + "image/untar/test_run/analysis.tar.gz", + "image/untar/test_run/analysis.zarr.zip", + "image/untar/test_run/analysis_summary.html", + "image/untar/test_run/aux_outputs.tar.gz", + "image/untar/test_run/cell_boundaries.csv.gz", + "image/untar/test_run/cell_boundaries.parquet", + "image/untar/test_run/cell_feature_matrix.h5", + "image/untar/test_run/cell_feature_matrix.tar.gz", + "image/untar/test_run/cell_feature_matrix.zarr.zip", + "image/untar/test_run/cells.csv.gz", + "image/untar/test_run/cells.parquet", + "image/untar/test_run/cells.zarr.zip", + "image/untar/test_run/experiment.xenium", + "image/untar/test_run/gene_panel.json", + "image/untar/test_run/metrics_summary.csv", + "image/untar/test_run/morphology.ome.tif", + "image/untar/test_run/morphology_focus", + "image/untar/test_run/morphology_focus/morphology_focus_0000.ome.tif", + "image/untar/test_run/nucleus_boundaries.csv.gz", + "image/untar/test_run/nucleus_boundaries.parquet", + "image/untar/test_run/transcripts.parquet", + "image/untar/test_run/transcripts.zarr.zip", + "image/utility", + "image/utility/resize_tif", + "image/utility/resize_tif/test_run", + "image/utility/resize_tif/test_run/resized_morphology_focus_0000.ome_cp_masks.tif.tif", + "pipeline_info", + "pipeline_info/nf_core_spatialaxe_software_mqc_versions.yml" + ], + [ + "experiment.xenium:md5,d41d8cd98f00b204e9800998ecf8427e", + "filtered_transcripts.csv:md5,d41d8cd98f00b204e9800998ecf8427e", + "segmentation.csv:md5,d41d8cd98f00b204e9800998ecf8427e", + "segmentation_polygons_2d.json:md5,d41d8cd98f00b204e9800998ecf8427e", + "morphology_focus_0000.ome_cp_masks.tif:md5,d41d8cd98f00b204e9800998ecf8427e", + ".stub:md5,d41d8cd98f00b204e9800998ecf8427e", + ".stub:md5,d41d8cd98f00b204e9800998ecf8427e", + "multiqc_report.html:md5,d41d8cd98f00b204e9800998ecf8427e", + ".stub:md5,d41d8cd98f00b204e9800998ecf8427e", + ".stub:md5,d41d8cd98f00b204e9800998ecf8427e", + "multiqc_report.html:md5,d41d8cd98f00b204e9800998ecf8427e", + "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e", + "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e", + "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e", + "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e", + ".end-of-run:md5,d41d8cd98f00b204e9800998ecf8427e", + "analysis.tar.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "analysis.zarr.zip:md5,d41d8cd98f00b204e9800998ecf8427e", + "analysis_summary.html:md5,d41d8cd98f00b204e9800998ecf8427e", + "aux_outputs.tar.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_boundaries.csv.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_boundaries.parquet:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_feature_matrix.h5:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_feature_matrix.tar.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_feature_matrix.zarr.zip:md5,d41d8cd98f00b204e9800998ecf8427e", + "cells.csv.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "cells.parquet:md5,d41d8cd98f00b204e9800998ecf8427e", + "cells.zarr.zip:md5,d41d8cd98f00b204e9800998ecf8427e", + "experiment.xenium:md5,d41d8cd98f00b204e9800998ecf8427e", + "gene_panel.json:md5,d41d8cd98f00b204e9800998ecf8427e", + "metrics_summary.csv:md5,d41d8cd98f00b204e9800998ecf8427e", + "morphology.ome.tif:md5,d41d8cd98f00b204e9800998ecf8427e", + "morphology_focus_0000.ome.tif:md5,d41d8cd98f00b204e9800998ecf8427e", + "nucleus_boundaries.csv.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "nucleus_boundaries.parquet:md5,d41d8cd98f00b204e9800998ecf8427e", + "transcripts.parquet:md5,d41d8cd98f00b204e9800998ecf8427e", + "transcripts.zarr.zip:md5,d41d8cd98f00b204e9800998ecf8427e", + "resized_morphology_focus_0000.ome_cp_masks.tif.tif:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "timestamp": "2026-05-18T22:00:39.030306506", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + } +} \ No newline at end of file diff --git a/tests/nextflow.config b/tests/nextflow.config index caf25a7b..2c1bfd32 100644 --- a/tests/nextflow.config +++ b/tests/nextflow.config @@ -4,11 +4,10 @@ ======================================================================================== */ -// TODO nf-core: Specify any additional parameters here // 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/spatialaxe' } aws.client.anonymous = true // fixes S3 access issues on self-hosted runners diff --git a/tests/preview_mode.nf.test b/tests/preview_mode.nf.test new file mode 100644 index 00000000..5a423490 --- /dev/null +++ b/tests/preview_mode.nf.test @@ -0,0 +1,36 @@ +nextflow_pipeline { + + name "Test pipeline for the `preview` mode, test run the basyor-preview subworkflow" + script "../main.nf" + tag "pipeline" + config "../conf/test_preview_mode.config" + + test("-profile test stub") { + + options "-stub" + + when { + params { + outdir = "$outputDir" + } + } + + 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') + 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_spatialaxe_software_mqc_versions.yml"), + // All stable path name, with a relative path + stable_name, + // All files with stable contents + stable_path + ).match() } + ) + } + } +} diff --git a/tests/preview_mode.nf.test.snap b/tests/preview_mode.nf.test.snap new file mode 100644 index 00000000..3d364f04 --- /dev/null +++ b/tests/preview_mode.nf.test.snap @@ -0,0 +1,120 @@ +{ + "-profile test stub": { + "content": [ + { + "BAYSOR_CREATE_DATASET": { + "baysor": "0.7.1" + }, + "BAYSOR_PREVIEW": { + "baysor": "0.7.1" + }, + "EXTRACT_PREVIEW_DATA": { + "python": "3.14.4" + }, + "PARQUET_TO_CSV": { + "pyarrow": "24.0.0" + }, + "UNTAR": { + "untar": 1.34 + }, + "Workflow": { + "nf-core/spatialaxe": "v1.1.0dev" + } + }, + [ + "pipeline_info", + "pipeline_info/nf_core_spatialaxe_software_mqc_versions.yml", + "preview", + "preview/baysor", + "preview/baysor/create_dataset", + "preview/baysor/create_dataset/test_run", + "preview/baysor/create_dataset/test_run/sampled_transcripts.csv", + "preview/baysor/preview", + "preview/baysor/preview/test_run", + "preview/baysor/preview/test_run/preview.html", + "preview/multiqc", + "preview/multiqc/multiqc_data", + "preview/multiqc/multiqc_data/.stub", + "preview/multiqc/multiqc_plots", + "preview/multiqc/multiqc_plots/.stub", + "preview/multiqc/multiqc_report.html", + "preview/untar", + "preview/untar/test_run", + "preview/untar/test_run/.end-of-run", + "preview/untar/test_run/analysis.tar.gz", + "preview/untar/test_run/analysis.zarr.zip", + "preview/untar/test_run/analysis_summary.html", + "preview/untar/test_run/aux_outputs.tar.gz", + "preview/untar/test_run/cell_boundaries.csv.gz", + "preview/untar/test_run/cell_boundaries.parquet", + "preview/untar/test_run/cell_feature_matrix.h5", + "preview/untar/test_run/cell_feature_matrix.tar.gz", + "preview/untar/test_run/cell_feature_matrix.zarr.zip", + "preview/untar/test_run/cells.csv.gz", + "preview/untar/test_run/cells.parquet", + "preview/untar/test_run/cells.zarr.zip", + "preview/untar/test_run/experiment.xenium", + "preview/untar/test_run/gene_panel.json", + "preview/untar/test_run/metrics_summary.csv", + "preview/untar/test_run/morphology.ome.tif", + "preview/untar/test_run/morphology_focus", + "preview/untar/test_run/morphology_focus/morphology_focus_0000.ome.tif", + "preview/untar/test_run/nucleus_boundaries.csv.gz", + "preview/untar/test_run/nucleus_boundaries.parquet", + "preview/untar/test_run/transcripts.parquet", + "preview/untar/test_run/transcripts.zarr.zip", + "preview/utility", + "preview/utility/parquet_to_csv", + "preview/utility/parquet_to_csv/test_run", + "preview/utility/parquet_to_csv/test_run/transcripts.parquet.csv", + "preview/utility/preview_data", + "preview/utility/preview_data/test_run", + "preview/utility/preview_data/test_run/gene_structure_mqc.tsv", + "preview/utility/preview_data/test_run/noise_distribution_mqc.tsv", + "preview/utility/preview_data/test_run/noise_level_mqc.png", + "preview/utility/preview_data/test_run/transcript_plots_mqc.png", + "preview/utility/preview_data/test_run/umap_mqc.tsv" + ], + [ + "sampled_transcripts.csv:md5,d41d8cd98f00b204e9800998ecf8427e", + "preview.html:md5,d41d8cd98f00b204e9800998ecf8427e", + ".stub:md5,d41d8cd98f00b204e9800998ecf8427e", + ".stub:md5,d41d8cd98f00b204e9800998ecf8427e", + "multiqc_report.html:md5,d41d8cd98f00b204e9800998ecf8427e", + ".end-of-run:md5,d41d8cd98f00b204e9800998ecf8427e", + "analysis.tar.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "analysis.zarr.zip:md5,d41d8cd98f00b204e9800998ecf8427e", + "analysis_summary.html:md5,d41d8cd98f00b204e9800998ecf8427e", + "aux_outputs.tar.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_boundaries.csv.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_boundaries.parquet:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_feature_matrix.h5:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_feature_matrix.tar.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_feature_matrix.zarr.zip:md5,d41d8cd98f00b204e9800998ecf8427e", + "cells.csv.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "cells.parquet:md5,d41d8cd98f00b204e9800998ecf8427e", + "cells.zarr.zip:md5,d41d8cd98f00b204e9800998ecf8427e", + "experiment.xenium:md5,d41d8cd98f00b204e9800998ecf8427e", + "gene_panel.json:md5,d41d8cd98f00b204e9800998ecf8427e", + "metrics_summary.csv:md5,d41d8cd98f00b204e9800998ecf8427e", + "morphology.ome.tif:md5,d41d8cd98f00b204e9800998ecf8427e", + "morphology_focus_0000.ome.tif:md5,d41d8cd98f00b204e9800998ecf8427e", + "nucleus_boundaries.csv.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "nucleus_boundaries.parquet:md5,d41d8cd98f00b204e9800998ecf8427e", + "transcripts.parquet:md5,d41d8cd98f00b204e9800998ecf8427e", + "transcripts.zarr.zip:md5,d41d8cd98f00b204e9800998ecf8427e", + "transcripts.parquet.csv:md5,d41d8cd98f00b204e9800998ecf8427e", + "gene_structure_mqc.tsv:md5,d41d8cd98f00b204e9800998ecf8427e", + "noise_distribution_mqc.tsv:md5,d41d8cd98f00b204e9800998ecf8427e", + "noise_level_mqc.png:md5,d41d8cd98f00b204e9800998ecf8427e", + "transcript_plots_mqc.png:md5,d41d8cd98f00b204e9800998ecf8427e", + "umap_mqc.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "timestamp": "2026-06-17T11:15:30.764424637", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.2" + } + } +} \ No newline at end of file diff --git a/tests/segfree_mode.nf.test b/tests/segfree_mode.nf.test new file mode 100644 index 00000000..854b17d5 --- /dev/null +++ b/tests/segfree_mode.nf.test @@ -0,0 +1,36 @@ +nextflow_pipeline { + + name "Test pipeline" + script "../main.nf" + tag "pipeline" + config "../conf/test_segfree_mode.config" + + test("-profile test stub") { + + options "-stub" + + when { + params { + outdir = "$outputDir" + } + } + + 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') + 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_spatialaxe_software_mqc_versions.yml"), + // All stable path name, with a relative path + stable_name, + // All files with stable contents + stable_path + ).match() } + ) + } + } +} diff --git a/tests/segfree_mode.nf.test.snap b/tests/segfree_mode.nf.test.snap new file mode 100644 index 00000000..ac663a43 --- /dev/null +++ b/tests/segfree_mode.nf.test.snap @@ -0,0 +1,97 @@ +{ + "-profile test stub": { + "content": [ + { + "BAYSOR_PREPROCESS_TRANSCRIPTS": { + "python": "3.14.4" + }, + "BAYSOR_SEGFREE": { + "baysor": "0.7.1" + }, + "UNTAR": { + "untar": 1.34 + }, + "Workflow": { + "nf-core/spatialaxe": "v1.1.0dev" + } + }, + [ + "pipeline_info", + "pipeline_info/nf_core_spatialaxe_software_mqc_versions.yml", + "segfree", + "segfree/baysor", + "segfree/baysor/preprocess", + "segfree/baysor/preprocess/test_run", + "segfree/baysor/preprocess/test_run/filtered_transcripts.csv", + "segfree/baysor/segfree", + "segfree/baysor/segfree/test_run", + "segfree/baysor/segfree/test_run/ncvs.loom", + "segfree/multiqc", + "segfree/multiqc/multiqc_data", + "segfree/multiqc/multiqc_data/.stub", + "segfree/multiqc/multiqc_plots", + "segfree/multiqc/multiqc_plots/.stub", + "segfree/multiqc/multiqc_report.html", + "segfree/untar", + "segfree/untar/test_run", + "segfree/untar/test_run/.end-of-run", + "segfree/untar/test_run/analysis.tar.gz", + "segfree/untar/test_run/analysis.zarr.zip", + "segfree/untar/test_run/analysis_summary.html", + "segfree/untar/test_run/aux_outputs.tar.gz", + "segfree/untar/test_run/cell_boundaries.csv.gz", + "segfree/untar/test_run/cell_boundaries.parquet", + "segfree/untar/test_run/cell_feature_matrix.h5", + "segfree/untar/test_run/cell_feature_matrix.tar.gz", + "segfree/untar/test_run/cell_feature_matrix.zarr.zip", + "segfree/untar/test_run/cells.csv.gz", + "segfree/untar/test_run/cells.parquet", + "segfree/untar/test_run/cells.zarr.zip", + "segfree/untar/test_run/experiment.xenium", + "segfree/untar/test_run/gene_panel.json", + "segfree/untar/test_run/metrics_summary.csv", + "segfree/untar/test_run/morphology.ome.tif", + "segfree/untar/test_run/morphology_focus", + "segfree/untar/test_run/morphology_focus/morphology_focus_0000.ome.tif", + "segfree/untar/test_run/nucleus_boundaries.csv.gz", + "segfree/untar/test_run/nucleus_boundaries.parquet", + "segfree/untar/test_run/transcripts.parquet", + "segfree/untar/test_run/transcripts.zarr.zip" + ], + [ + "filtered_transcripts.csv:md5,d41d8cd98f00b204e9800998ecf8427e", + "ncvs.loom:md5,d41d8cd98f00b204e9800998ecf8427e", + ".stub:md5,d41d8cd98f00b204e9800998ecf8427e", + ".stub:md5,d41d8cd98f00b204e9800998ecf8427e", + "multiqc_report.html:md5,d41d8cd98f00b204e9800998ecf8427e", + ".end-of-run:md5,d41d8cd98f00b204e9800998ecf8427e", + "analysis.tar.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "analysis.zarr.zip:md5,d41d8cd98f00b204e9800998ecf8427e", + "analysis_summary.html:md5,d41d8cd98f00b204e9800998ecf8427e", + "aux_outputs.tar.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_boundaries.csv.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_boundaries.parquet:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_feature_matrix.h5:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_feature_matrix.tar.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "cell_feature_matrix.zarr.zip:md5,d41d8cd98f00b204e9800998ecf8427e", + "cells.csv.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "cells.parquet:md5,d41d8cd98f00b204e9800998ecf8427e", + "cells.zarr.zip:md5,d41d8cd98f00b204e9800998ecf8427e", + "experiment.xenium:md5,d41d8cd98f00b204e9800998ecf8427e", + "gene_panel.json:md5,d41d8cd98f00b204e9800998ecf8427e", + "metrics_summary.csv:md5,d41d8cd98f00b204e9800998ecf8427e", + "morphology.ome.tif:md5,d41d8cd98f00b204e9800998ecf8427e", + "morphology_focus_0000.ome.tif:md5,d41d8cd98f00b204e9800998ecf8427e", + "nucleus_boundaries.csv.gz:md5,d41d8cd98f00b204e9800998ecf8427e", + "nucleus_boundaries.parquet:md5,d41d8cd98f00b204e9800998ecf8427e", + "transcripts.parquet:md5,d41d8cd98f00b204e9800998ecf8427e", + "transcripts.zarr.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "timestamp": "2026-05-18T22:02:39.947804998", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + } +} \ No newline at end of file diff --git a/tests/test_xenium_patch/__init__.py b/tests/test_xenium_patch/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_xenium_patch/test_divide_transcripts.py b/tests/test_xenium_patch/test_divide_transcripts.py new file mode 100644 index 00000000..859fd5ae --- /dev/null +++ b/tests/test_xenium_patch/test_divide_transcripts.py @@ -0,0 +1,720 @@ +"""Tests for divide_transcripts.py — grid computation + transcript division.""" + +import importlib.util +import json +import math +import sys +from pathlib import Path + +import numpy as np +import pyarrow as pa +import pyarrow.parquet as pq +import pytest + +# --------------------------------------------------------------------------- +# Import the standalone script from bin/ +# --------------------------------------------------------------------------- + +_BIN_DIR = Path(__file__).resolve().parents[2] / "bin" +_SCRIPT = _BIN_DIR / "divide_transcripts.py" +_spec = importlib.util.spec_from_file_location("divide_transcripts", _SCRIPT) +_mod = importlib.util.module_from_spec(_spec) +sys.modules["divide_transcripts"] = _mod +_spec.loader.exec_module(_mod) + +from divide_transcripts import ( # noqa: E402 + Bounds, + PatchInfo, + _compute_uniform_grid, + _count_transcripts_per_tile, + _find_adjacent_patches, + compute_density_quadtree_grid, + compute_tilewidth_uniform_grid, + divide_transcripts, + merge_sparse_tiles, + save_grid_metadata, +) + +PIXEL_SIZE = 0.2125 + + +# --------------------------------------------------------------------------- +# Fixtures +# --------------------------------------------------------------------------- + + +@pytest.fixture +def synthetic_transcripts(tmp_path: Path) -> Path: + """Write a synthetic transcripts.parquet with 1000 rows, uniform spatial distribution.""" + rng = np.random.default_rng(42) + n = 1000 + table = pa.table( + { + "transcript_id": pa.array([f"tx_{i}" for i in range(n)], type=pa.string()), + "cell_id": pa.array(["UNASSIGNED"] * n, type=pa.string()), + "overlaps_nucleus": pa.array([0] * n, type=pa.int32()), + "feature_name": pa.array( + [f"gene_{i % 50}" for i in range(n)], type=pa.string() + ), + "x_location": pa.array(rng.uniform(0.0, 1275.0, n), type=pa.float32()), + "y_location": pa.array(rng.uniform(0.0, 1275.0, n), type=pa.float32()), + "z_location": pa.array(rng.uniform(0.0, 10.0, n), type=pa.float32()), + "qv": pa.array(rng.uniform(20.0, 40.0, n), type=pa.float32()), + } + ) + path = tmp_path / "transcripts.parquet" + pq.write_table(table, str(path)) + return path + + +@pytest.fixture +def dense_corner_transcripts(tmp_path: Path) -> Path: + """90% of transcripts in the top-left corner, 10% uniform.""" + rng = np.random.default_rng(99) + n_dense = 900 + n_sparse = 100 + n = n_dense + n_sparse + + x_dense = rng.uniform(0.0, 50.0, n_dense) + y_dense = rng.uniform(0.0, 50.0, n_dense) + x_sparse = rng.uniform(0.0, 1275.0, n_sparse) + y_sparse = rng.uniform(0.0, 1275.0, n_sparse) + + table = pa.table( + { + "transcript_id": pa.array([f"tx_{i}" for i in range(n)], type=pa.string()), + "cell_id": pa.array(["UNASSIGNED"] * n, type=pa.string()), + "overlaps_nucleus": pa.array([0] * n, type=pa.int32()), + "feature_name": pa.array( + [f"gene_{i % 50}" for i in range(n)], type=pa.string() + ), + "x_location": pa.array( + np.concatenate([x_dense, x_sparse]).astype(np.float32), + type=pa.float32(), + ), + "y_location": pa.array( + np.concatenate([y_dense, y_sparse]).astype(np.float32), + type=pa.float32(), + ), + "z_location": pa.array( + rng.uniform(0.0, 10.0, n).astype(np.float32), type=pa.float32() + ), + "qv": pa.array( + rng.uniform(20.0, 40.0, n).astype(np.float32), type=pa.float32() + ), + } + ) + path = tmp_path / "transcripts_dense.parquet" + pq.write_table(table, str(path)) + return path + + +# --------------------------------------------------------------------------- +# Uniform grid tests +# --------------------------------------------------------------------------- + + +class TestUniformGridBasic: + def test_uniform_grid_basic(self): + """3x3 grid from 2000um tile width on a 6000x6000um image.""" + # Use exact pixel count: 6000um / 0.2125um/px = 28235.29... -> round to 28235 + # image_um = 28235 * 0.2125 = 5999.9375; ceil(5999.9375 / 2000) = 3 + image_px = int(6000.0 / PIXEL_SIZE) + extent = Bounds(0.0, 6000.0, 0.0, 6000.0) + + patches, rows, cols, overlap_px = compute_tilewidth_uniform_grid( + image_height_px=image_px, + image_width_px=image_px, + tile_width_um=2000.0, + overlap_um=50.0, + pixel_size_um=PIXEL_SIZE, + transcript_extent_um=extent, + ) + + assert rows == 3 + assert cols == 3 + assert len(patches) == 9 + + def test_uniform_grid_single_tile(self): + """Tile width larger than image produces a 1x1 grid.""" + image_px = 1000 + extent = Bounds(0.0, image_px * PIXEL_SIZE, 0.0, image_px * PIXEL_SIZE) + + patches, rows, cols, _ = compute_tilewidth_uniform_grid( + image_height_px=image_px, + image_width_px=image_px, + tile_width_um=50000.0, + overlap_um=50.0, + pixel_size_um=PIXEL_SIZE, + transcript_extent_um=extent, + ) + + assert rows == 1 + assert cols == 1 + assert len(patches) == 1 + + def test_uniform_grid_overlap(self): + """Global bounds extend beyond core by the overlap amount.""" + image_px = 1000 + overlap_um = 50.0 + + patches = _compute_uniform_grid( + image_height_px=image_px, + image_width_px=image_px, + grid_rows=2, + grid_cols=2, + overlap_px=int(math.ceil(overlap_um / PIXEL_SIZE)), + pixel_size_um=PIXEL_SIZE, + ) + + # Interior patch boundary: global should extend beyond core + for p in patches: + assert p.global_bounds_px.x_min <= p.core_bounds_px.x_min + assert p.global_bounds_px.x_max >= p.core_bounds_px.x_max + assert p.global_bounds_px.y_min <= p.core_bounds_px.y_min + assert p.global_bounds_px.y_max >= p.core_bounds_px.y_max + + +# --------------------------------------------------------------------------- +# Quadtree grid tests +# --------------------------------------------------------------------------- + + +class TestQuadtreeGrid: + def test_quadtree_uniform_density(self): + """When density is uniform with high threshold, quadtree should not subdivide.""" + rng = np.random.default_rng(42) + image_px = 1000 + image_um = image_px * PIXEL_SIZE + + x = rng.uniform(0, image_um, 1000) + y = rng.uniform(0, image_um, 1000) + + patches, _, _, _ = compute_density_quadtree_grid( + image_height_px=image_px, + image_width_px=image_px, + tile_width_um=100.0, + overlap_um=10.0, + pixel_size_um=PIXEL_SIZE, + x_coords_um=x, + y_coords_um=y, + max_transcripts_per_patch=10000, + ) + + # ceil(212.5/100)=3 -> 3x3 = 9 initial patches, no subdivision + assert len(patches) == 9 + + def test_quadtree_dense_region(self): + """Put 90% of transcripts in one corner, verify subdivision produces more patches.""" + rng = np.random.default_rng(42) + image_px = 1000 + image_um = image_px * PIXEL_SIZE + + x_sparse = rng.uniform(0, image_um, 100) + y_sparse = rng.uniform(0, image_um, 100) + x_dense = rng.uniform(0, image_um * 0.2, 5000) + y_dense = rng.uniform(0, image_um * 0.2, 5000) + x = np.concatenate([x_sparse, x_dense]) + y = np.concatenate([y_sparse, y_dense]) + + patches, _, _, _ = compute_density_quadtree_grid( + image_height_px=image_px, + image_width_px=image_px, + tile_width_um=100.0, + overlap_um=10.0, + pixel_size_um=PIXEL_SIZE, + x_coords_um=x, + y_coords_um=y, + max_transcripts_per_patch=500, + min_tile_width_um=10.0, + max_depth=4, + ) + + # Should have subdivided beyond the initial 9 + assert len(patches) > 9 + + def test_quadtree_max_depth(self): + """Verify subdivision stops at max_depth: deeper depth -> more patches.""" + rng = np.random.default_rng(42) + image_px = 1000 + image_um = image_px * PIXEL_SIZE + + x = rng.normal(image_um / 2, 5.0, 10000) + y = rng.normal(image_um / 2, 5.0, 10000) + + patches_d1, _, _, _ = compute_density_quadtree_grid( + image_height_px=image_px, + image_width_px=image_px, + tile_width_um=100.0, + overlap_um=10.0, + pixel_size_um=PIXEL_SIZE, + x_coords_um=x, + y_coords_um=y, + max_transcripts_per_patch=10, + min_tile_width_um=1.0, + max_depth=1, + ) + + patches_d4, _, _, _ = compute_density_quadtree_grid( + image_height_px=image_px, + image_width_px=image_px, + tile_width_um=100.0, + overlap_um=10.0, + pixel_size_um=PIXEL_SIZE, + x_coords_um=x, + y_coords_um=y, + max_transcripts_per_patch=10, + min_tile_width_um=1.0, + max_depth=4, + ) + + assert len(patches_d4) > len(patches_d1) + + def test_quadtree_min_tile_width(self): + """Verify subdivision stops at min_tile_width: all cores >= min width.""" + rng = np.random.default_rng(42) + image_px = 1000 + image_um = image_px * PIXEL_SIZE + min_tile_um = 30.0 + + x = rng.normal(image_um / 2, 5.0, 10000) + y = rng.normal(image_um / 2, 5.0, 10000) + + patches, _, _, _ = compute_density_quadtree_grid( + image_height_px=image_px, + image_width_px=image_px, + tile_width_um=100.0, + overlap_um=10.0, + pixel_size_um=PIXEL_SIZE, + x_coords_um=x, + y_coords_um=y, + max_transcripts_per_patch=10, + min_tile_width_um=min_tile_um, + max_depth=10, + ) + + for p in patches: + # Allow 1um rounding tolerance from pixel conversion + assert p.core_bounds_um.width >= min_tile_um - 1.0, ( + f"Patch {p.patch_id} width {p.core_bounds_um.width:.1f} < min {min_tile_um}" + ) + assert p.core_bounds_um.height >= min_tile_um - 1.0, ( + f"Patch {p.patch_id} height {p.core_bounds_um.height:.1f} < min {min_tile_um}" + ) + + +# --------------------------------------------------------------------------- +# Division tests +# --------------------------------------------------------------------------- + + +class TestDivideTranscripts: + def test_divide_transcripts_basic( + self, synthetic_transcripts: Path, tmp_path: Path + ): + """Divide synthetic parquet, verify per-patch files are written.""" + output_dir = tmp_path / "output" + + divide_transcripts( + transcripts_path=synthetic_transcripts, + output_dir=output_dir, + image_width_px=6000, + image_height_px=6000, + tile_width_um=1000.0, + overlap_um=50.0, + balanced=False, + pixel_size_um=PIXEL_SIZE, + max_workers=1, + ) + + # Grid metadata should exist + grid_json = output_dir / "patch_grid.json" + assert grid_json.exists() + + with open(grid_json) as f: + metadata = json.load(f) + + # Each patch should have a transcripts.parquet file + for patch in metadata["patches"]: + patch_parquet = output_dir / patch["patch_id"] / "transcripts.parquet" + assert patch_parquet.exists(), f"Missing parquet for {patch['patch_id']}" + + def test_divide_transcripts_coordinates_offset( + self, synthetic_transcripts: Path, tmp_path: Path + ): + """Verify coordinates are offset to patch-local space.""" + output_dir = tmp_path / "output" + + divide_transcripts( + transcripts_path=synthetic_transcripts, + output_dir=output_dir, + image_width_px=6000, + image_height_px=6000, + tile_width_um=1000.0, + overlap_um=50.0, + balanced=False, + pixel_size_um=PIXEL_SIZE, + max_workers=1, + ) + + with open(output_dir / "patch_grid.json") as f: + metadata = json.load(f) + + for patch_meta in metadata["patches"]: + patch_parquet = output_dir / patch_meta["patch_id"] / "transcripts.parquet" + if not patch_parquet.exists(): + continue + tbl = pq.read_table(str(patch_parquet)) + if tbl.num_rows == 0: + continue + + gb = patch_meta["global_bounds_um"] + patch_width = gb["x_max"] - gb["x_min"] + patch_height = gb["y_max"] - gb["y_min"] + + x_arr = tbl.column("x_location").to_numpy() + y_arr = tbl.column("y_location").to_numpy() + + # Local coords should be in [0, patch_width) x [0, patch_height) + assert float(np.min(x_arr)) >= -0.01, ( + f"Patch {patch_meta['patch_id']}: x_min={np.min(x_arr)} < 0" + ) + assert float(np.max(x_arr)) < patch_width + 0.01, ( + f"Patch {patch_meta['patch_id']}: x_max={np.max(x_arr)} >= {patch_width}" + ) + assert float(np.min(y_arr)) >= -0.01 + assert float(np.max(y_arr)) < patch_height + 0.01 + + def test_divide_transcripts_no_transcript_loss( + self, synthetic_transcripts: Path, tmp_path: Path + ): + """Verify all transcripts appear in at least one patch.""" + output_dir = tmp_path / "output" + + original = pq.read_table(str(synthetic_transcripts)) + original_ids = set(original.column("transcript_id").to_pylist()) + + divide_transcripts( + transcripts_path=synthetic_transcripts, + output_dir=output_dir, + image_width_px=6000, + image_height_px=6000, + tile_width_um=1000.0, + overlap_um=50.0, + balanced=False, + pixel_size_um=PIXEL_SIZE, + max_workers=1, + ) + + with open(output_dir / "patch_grid.json") as f: + metadata = json.load(f) + + found_ids: set[str] = set() + for patch_meta in metadata["patches"]: + patch_parquet = output_dir / patch_meta["patch_id"] / "transcripts.parquet" + if patch_parquet.exists(): + tbl = pq.read_table(str(patch_parquet)) + found_ids.update(tbl.column("transcript_id").to_pylist()) + + # Every original transcript must appear in at least one patch + missing = original_ids - found_ids + assert len(missing) == 0, f"{len(missing)} transcripts lost during division" + + +# --------------------------------------------------------------------------- +# Grid metadata JSON roundtrip +# --------------------------------------------------------------------------- + + +class TestGridMetadataJSON: + def test_grid_metadata_json_roundtrip(self, tmp_path: Path): + """Save + load grid metadata preserves all fields.""" + image_px = 1000 + extent = Bounds(0.0, image_px * PIXEL_SIZE, 0.0, image_px * PIXEL_SIZE) + patches, rows, cols, overlap_px = compute_tilewidth_uniform_grid( + image_height_px=image_px, + image_width_px=image_px, + tile_width_um=100.0, + overlap_um=50.0, + pixel_size_um=PIXEL_SIZE, + transcript_extent_um=extent, + ) + + path = tmp_path / "patch_grid.json" + save_grid_metadata( + patches=patches, + image_height_px=image_px, + image_width_px=image_px, + pixel_size_um=PIXEL_SIZE, + transcript_extent_um=extent, + grid_rows=rows, + grid_cols=cols, + overlap_um=50.0, + overlap_px=overlap_px, + grid_type="uniform", + output_path=path, + ) + + with open(path) as f: + data = json.load(f) + + assert data["version"] == "1.0" + assert data["grid_rows"] == rows + assert data["grid_cols"] == cols + assert data["overlap_um"] == 50.0 + assert data["overlap_px"] == overlap_px + assert data["grid_type"] == "uniform" + assert len(data["patches"]) == len(patches) + + for orig, loaded in zip(patches, data["patches"]): + assert loaded["patch_id"] == orig.patch_id + assert loaded["row"] == orig.row + assert loaded["col"] == orig.col + assert loaded["global_bounds_px"]["x_min"] == pytest.approx( + orig.global_bounds_px.x_min + ) + assert loaded["core_bounds_um"]["y_max"] == pytest.approx( + orig.core_bounds_um.y_max + ) + + +# --------------------------------------------------------------------------- +# Merge sparse tiles tests +# --------------------------------------------------------------------------- + + +def _make_2x2_grid(pixel_size: float = PIXEL_SIZE) -> tuple[list[PatchInfo], int, int]: + """Build a 2x2 uniform grid on a 1000x1000 pixel image. + + Returns: + Tuple of (patches, image_width_px, image_height_px). + """ + image_px = 1000 + overlap_px = int(math.ceil(50.0 / pixel_size)) + patches = _compute_uniform_grid( + image_height_px=image_px, + image_width_px=image_px, + grid_rows=2, + grid_cols=2, + overlap_px=overlap_px, + pixel_size_um=pixel_size, + ) + return patches, image_px, overlap_px + + +class TestMergeSparseTiles: + def test_no_merge_above_threshold(self): + """All tiles above threshold -- no merging happens.""" + patches, image_px, overlap_px = _make_2x2_grid() + image_um = image_px * PIXEL_SIZE + + rng = np.random.default_rng(42) + n = 4000 + x = rng.uniform(0, image_um, n).astype(np.float64) + y = rng.uniform(0, image_um, n).astype(np.float64) + + merged, merge_count = merge_sparse_tiles( + patches=patches, + x_coords_um=x, + y_coords_um=y, + overlap_px=overlap_px, + pixel_size_um=PIXEL_SIZE, + image_width_px=image_px, + image_height_px=image_px, + min_transcripts=100, + ) + + assert merge_count == 0 + assert len(merged) == len(patches) + merged_ids = {p.patch_id for p in merged} + original_ids = {p.patch_id for p in patches} + assert merged_ids == original_ids + + def test_merge_sparse_edge_tile(self): + """One corner tile has very few transcripts -- it gets merged into a neighbor.""" + patches, image_px, overlap_px = _make_2x2_grid() + + # Put 500 transcripts in each of 3 tiles, 5 in the last tile (row0_col0) + rng = np.random.default_rng(7) + # Find the core bounds of each patch to place transcripts correctly + patch_map = {p.patch_id: p for p in patches} + + sparse_id = patches[0].patch_id # first tile gets very few transcripts + xs, ys = [], [] + for p in patches: + cb = p.core_bounds_um + n = 5 if p.patch_id == sparse_id else 500 + xs.append(rng.uniform(cb.x_min + 0.1, cb.x_max - 0.1, n)) + ys.append(rng.uniform(cb.y_min + 0.1, cb.y_max - 0.1, n)) + + x = np.concatenate(xs) + y = np.concatenate(ys) + + merged, merge_count = merge_sparse_tiles( + patches=patches, + x_coords_um=x, + y_coords_um=y, + overlap_px=overlap_px, + pixel_size_um=PIXEL_SIZE, + image_width_px=image_px, + image_height_px=image_px, + min_transcripts=100, + ) + + assert merge_count == 1 + assert len(merged) == 3 + + # The sparse tile should no longer exist as a patch + merged_ids = {p.patch_id for p in merged} + assert sparse_id not in merged_ids + + # The absorbing neighbor's bounds should cover the sparse tile's area + sparse_global = patch_map[sparse_id].global_bounds_um + absorber = [p for p in merged if p.patch_id != sparse_id] + # At least one neighbor should now have bounds covering the sparse tile's origin + covers_sparse = any( + p.global_bounds_um.x_min <= sparse_global.x_min + 0.01 + and p.global_bounds_um.y_min <= sparse_global.y_min + 0.01 + for p in absorber + ) + assert covers_sparse, "No merged tile covers the sparse tile's region" + + def test_merge_preserves_all_transcripts(self, tmp_path: Path): + """After merging, divide_transcripts with merged grid loses no transcripts.""" + image_px = 1000 + image_um = image_px * PIXEL_SIZE + + # Create transcripts: sparse in one corner, dense elsewhere + rng = np.random.default_rng(33) + n_sparse = 10 + n_dense = 990 + + x_sparse = rng.uniform(0, image_um * 0.1, n_sparse).astype(np.float32) + y_sparse = rng.uniform(0, image_um * 0.1, n_sparse).astype(np.float32) + x_dense = rng.uniform(image_um * 0.3, image_um, n_dense).astype(np.float32) + y_dense = rng.uniform(image_um * 0.3, image_um, n_dense).astype(np.float32) + + n = n_sparse + n_dense + table = pa.table( + { + "transcript_id": pa.array( + [f"tx_{i}" for i in range(n)], type=pa.string() + ), + "cell_id": pa.array(["UNASSIGNED"] * n, type=pa.string()), + "overlaps_nucleus": pa.array([0] * n, type=pa.int32()), + "feature_name": pa.array( + [f"gene_{i % 50}" for i in range(n)], type=pa.string() + ), + "x_location": pa.array( + np.concatenate([x_sparse, x_dense]), type=pa.float32() + ), + "y_location": pa.array( + np.concatenate([y_sparse, y_dense]), type=pa.float32() + ), + "z_location": pa.array( + rng.uniform(0, 10, n).astype(np.float32), type=pa.float32() + ), + "qv": pa.array( + rng.uniform(20, 40, n).astype(np.float32), type=pa.float32() + ), + } + ) + parquet_path = tmp_path / "transcripts.parquet" + pq.write_table(table, str(parquet_path)) + + original_ids = set(table.column("transcript_id").to_pylist()) + output_dir = tmp_path / "output" + + divide_transcripts( + transcripts_path=parquet_path, + output_dir=output_dir, + image_width_px=image_px, + image_height_px=image_px, + tile_width_um=100.0, + overlap_um=10.0, + balanced=False, + pixel_size_um=PIXEL_SIZE, + max_workers=1, + min_transcripts=50, + ) + + with open(output_dir / "patch_grid.json") as f: + metadata = json.load(f) + + found_ids: set[str] = set() + for patch_meta in metadata["patches"]: + patch_parquet = output_dir / patch_meta["patch_id"] / "transcripts.parquet" + if patch_parquet.exists(): + tbl = pq.read_table(str(patch_parquet)) + found_ids.update(tbl.column("transcript_id").to_pylist()) + + missing = original_ids - found_ids + assert len(missing) == 0, ( + f"{len(missing)} transcripts lost after merge + divide" + ) + + def test_merge_disabled_with_zero_threshold(self): + """min_transcripts=0 disables merging regardless of transcript counts.""" + patches, image_px, overlap_px = _make_2x2_grid() + + # Put only 1 transcript per tile -- still no merge with threshold=0 + rng = np.random.default_rng(99) + xs, ys = [], [] + for p in patches: + cb = p.core_bounds_um + xs.append(rng.uniform(cb.x_min + 0.1, cb.x_max - 0.1, 1)) + ys.append(rng.uniform(cb.y_min + 0.1, cb.y_max - 0.1, 1)) + + x = np.concatenate(xs) + y = np.concatenate(ys) + + merged, merge_count = merge_sparse_tiles( + patches=patches, + x_coords_um=x, + y_coords_um=y, + overlap_px=overlap_px, + pixel_size_um=PIXEL_SIZE, + image_width_px=image_px, + image_height_px=image_px, + min_transcripts=0, + ) + + assert merge_count == 0 + assert len(merged) == len(patches) + + def test_count_transcripts_per_tile(self): + """Unit test for _count_transcripts_per_tile with known placement.""" + patches, image_px, _ = _make_2x2_grid() + + # Place 10 transcripts in each patch's core + rng = np.random.default_rng(11) + xs, ys = [], [] + expected_per_patch: dict[str, int] = {} + counts_list = [10, 20, 30, 40] + for p, n in zip(patches, counts_list): + cb = p.core_bounds_um + xs.append(rng.uniform(cb.x_min + 0.1, cb.x_max - 0.1, n)) + ys.append(rng.uniform(cb.y_min + 0.1, cb.y_max - 0.1, n)) + expected_per_patch[p.patch_id] = n + + x = np.concatenate(xs) + y = np.concatenate(ys) + + counts = _count_transcripts_per_tile(patches, x, y) + + for pid, expected in expected_per_patch.items(): + assert counts[pid] == expected, ( + f"Patch {pid}: expected {expected}, got {counts[pid]}" + ) + + def test_find_adjacent_patches(self): + """Each tile in a 2x2 grid has exactly 2 neighbors.""" + patches, _, _ = _make_2x2_grid() + adjacency = _find_adjacent_patches(patches) + + # 2x2 grid: each corner tile touches 2 others (horizontal + vertical) + for p in patches: + neighbors = adjacency[p.patch_id] + assert len(neighbors) == 2, ( + f"Patch {p.patch_id} has {len(neighbors)} neighbors, expected 2: {neighbors}" + ) diff --git a/tests/test_xenium_patch/test_stitch_transcripts.py b/tests/test_xenium_patch/test_stitch_transcripts.py new file mode 100644 index 00000000..5419c897 --- /dev/null +++ b/tests/test_xenium_patch/test_stitch_transcripts.py @@ -0,0 +1,865 @@ +"""Tests for stitch_transcripts.py — sopa-based stitching.""" + +import importlib.util +import json +import sys +from pathlib import Path + +import pyarrow as pa +import pyarrow.csv as pa_csv +import pytest +from shapely.geometry import Polygon, mapping + +# --------------------------------------------------------------------------- +# Import the standalone script from module resources +# --------------------------------------------------------------------------- + +_SCRIPT = ( + Path(__file__).resolve().parents[2] + / "modules/local/xenium_patch/stitch/resources/usr/bin/stitch_transcripts.py" +) +_spec = importlib.util.spec_from_file_location("stitch_transcripts", _SCRIPT) +_mod = importlib.util.module_from_spec(_spec) +sys.modules["stitch_transcripts"] = _mod +_spec.loader.exec_module(_mod) + +from stitch_transcripts import ( # noqa: E402 + Bounds, + PatchGridMetadata, + PatchInfo, + _normalize_geometry_collection, + read_geojson, + stitch_transcript_assignments, + transform_polygons, +) + +PIXEL_SIZE = 0.2125 + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _make_patch_info( + patch_id: str, + row: int, + col: int, + global_x: tuple[float, float], + global_y: tuple[float, float], + core_x: tuple[float, float], + core_y: tuple[float, float], +) -> PatchInfo: + """Create a PatchInfo with bounds in both pixel and micron space.""" + return PatchInfo( + patch_id=patch_id, + row=row, + col=col, + global_bounds_px=Bounds( + global_x[0] / PIXEL_SIZE, + global_x[1] / PIXEL_SIZE, + global_y[0] / PIXEL_SIZE, + global_y[1] / PIXEL_SIZE, + ), + global_bounds_um=Bounds(global_x[0], global_x[1], global_y[0], global_y[1]), + core_bounds_px=Bounds( + core_x[0] / PIXEL_SIZE, + core_x[1] / PIXEL_SIZE, + core_y[0] / PIXEL_SIZE, + core_y[1] / PIXEL_SIZE, + ), + core_bounds_um=Bounds(core_x[0], core_x[1], core_y[0], core_y[1]), + ) + + +def _make_metadata(patches: list[PatchInfo]) -> PatchGridMetadata: + """Create minimal PatchGridMetadata.""" + return PatchGridMetadata( + version="1.0", + bundle_path="", + image_height_px=10000, + image_width_px=10000, + pixel_size_um=PIXEL_SIZE, + transcript_extent_um=Bounds(0.0, 2125.0, 0.0, 2125.0), + grid_rows=1, + grid_cols=2, + overlap_um=50.0, + overlap_px=236, + patches=patches, + ) + + +def _write_grid_json(metadata: PatchGridMetadata, output_path: Path) -> None: + """Serialize PatchGridMetadata to JSON (matching the format load_grid_metadata expects).""" + + def bounds_dict(b: Bounds) -> dict: + return {"x_min": b.x_min, "x_max": b.x_max, "y_min": b.y_min, "y_max": b.y_max} + + data = { + "version": metadata.version, + "bundle_path": metadata.bundle_path, + "image_height_px": metadata.image_height_px, + "image_width_px": metadata.image_width_px, + "pixel_size_um": metadata.pixel_size_um, + "transcript_extent_um": bounds_dict(metadata.transcript_extent_um), + "grid_rows": metadata.grid_rows, + "grid_cols": metadata.grid_cols, + "overlap_um": metadata.overlap_um, + "overlap_px": metadata.overlap_px, + "grid_type": metadata.grid_type, + "patches": [ + { + "patch_id": p.patch_id, + "row": p.row, + "col": p.col, + "global_bounds_px": bounds_dict(p.global_bounds_px), + "global_bounds_um": bounds_dict(p.global_bounds_um), + "core_bounds_px": bounds_dict(p.core_bounds_px), + "core_bounds_um": bounds_dict(p.core_bounds_um), + } + for p in metadata.patches + ], + } + output_path.parent.mkdir(parents=True, exist_ok=True) + with open(output_path, "w") as f: + json.dump(data, f, indent=2) + + +def _write_patch_csv( + patch_dir: Path, rows: list[dict], filename: str = "segmentation.csv" +) -> None: + """Write a Baysor-style CSV.""" + if not rows: + return + cols = list(rows[0].keys()) + arrays = { + col: pa.array([str(r[col]) for r in rows], type=pa.string()) for col in cols + } + table = pa.table(arrays) + patch_dir.mkdir(parents=True, exist_ok=True) + pa_csv.write_csv(table, patch_dir / filename) + + +def _write_patch_geojson( + patch_dir: Path, + cell_polygons: dict[str, Polygon], + filename: str = "segmentation_polygons.json", +) -> None: + """Write a GeoJSON FeatureCollection with cell polygons in local coordinates.""" + features = [] + for cell_id, poly in cell_polygons.items(): + features.append( + { + "type": "Feature", + "id": cell_id, + "geometry": mapping(poly), + "properties": {"cell_id": cell_id}, + } + ) + geojson = {"type": "FeatureCollection", "features": features} + patch_dir.mkdir(parents=True, exist_ok=True) + with open(patch_dir / filename, "w") as f: + json.dump(geojson, f) + + +# --------------------------------------------------------------------------- +# Stitch tests +# --------------------------------------------------------------------------- + + +class TestStitchBasic: + def test_stitch_basic(self, tmp_path: Path): + """Create 2 patches with non-overlapping cells, verify merged output.""" + # Patch 0: core [0,500) x [0,1000), global [0,525) x [0,1000) + # Patch 1: core [500,1000) x [0,1000), global [475,1000) x [0,1000) + p0 = _make_patch_info( + "patch_0", + 0, + 0, + global_x=(0.0, 525.0), + global_y=(0.0, 1000.0), + core_x=(0.0, 500.0), + core_y=(0.0, 1000.0), + ) + p1 = _make_patch_info( + "patch_1", + 0, + 1, + global_x=(475.0, 1000.0), + global_y=(0.0, 1000.0), + core_x=(500.0, 1000.0), + core_y=(0.0, 1000.0), + ) + metadata = _make_metadata([p0, p1]) + + patches_dir = tmp_path / "patches" + _write_grid_json(metadata, patches_dir / "patch_grid.json") + + # Patch 0: cell at (100, 100) local -> (100, 100) global + _write_patch_csv( + patches_dir / "patch_0", + [ + { + "transcript_id": "tx_1", + "x": "100.0", + "y": "100.0", + "gene": "A", + "cell": "cell_1", + "is_noise": "0", + }, + { + "transcript_id": "tx_2", + "x": "200.0", + "y": "200.0", + "gene": "B", + "cell": "cell_1", + "is_noise": "0", + }, + ], + ) + _write_patch_geojson( + patches_dir / "patch_0", + {"cell_1": Polygon([(50, 50), (250, 50), (250, 250), (50, 250)])}, + ) + + # Patch 1: cell at (100, 100) local -> (575, 100) global + _write_patch_csv( + patches_dir / "patch_1", + [ + { + "transcript_id": "tx_3", + "x": "100.0", + "y": "100.0", + "gene": "C", + "cell": "cell_1", + "is_noise": "0", + }, + ], + ) + _write_patch_geojson( + patches_dir / "patch_1", + {"cell_1": Polygon([(50, 50), (200, 50), (200, 200), (50, 200)])}, + ) + + output_dir = tmp_path / "output" + stitch_transcript_assignments( + patches_dir=patches_dir, + output_dir=output_dir, + max_workers=1, + ) + + csv_out = output_dir / "xr-transcript-metadata.csv" + assert csv_out.exists() + + geo_out = output_dir / "xr-cell-polygons.geojson" + assert geo_out.exists() + + # Read CSV and verify transcripts present + merged = pa_csv.read_csv(csv_out) + assert merged.num_rows >= 3 + + def test_stitch_cell_id_sequential(self, tmp_path: Path): + """Verify global IDs are cell-1, cell-2, ...""" + p0 = _make_patch_info( + "patch_0", + 0, + 0, + global_x=(0.0, 525.0), + global_y=(0.0, 1000.0), + core_x=(0.0, 500.0), + core_y=(0.0, 1000.0), + ) + p1 = _make_patch_info( + "patch_1", + 0, + 1, + global_x=(475.0, 1000.0), + global_y=(0.0, 1000.0), + core_x=(500.0, 1000.0), + core_y=(0.0, 1000.0), + ) + metadata = _make_metadata([p0, p1]) + + patches_dir = tmp_path / "patches" + _write_grid_json(metadata, patches_dir / "patch_grid.json") + + _write_patch_csv( + patches_dir / "patch_0", + [ + { + "transcript_id": "tx_1", + "x": "100.0", + "y": "100.0", + "gene": "A", + "cell": "cell_1", + "is_noise": "0", + }, + ], + ) + _write_patch_geojson( + patches_dir / "patch_0", + {"cell_1": Polygon([(50, 50), (250, 50), (250, 250), (50, 250)])}, + ) + + _write_patch_csv( + patches_dir / "patch_1", + [ + { + "transcript_id": "tx_2", + "x": "100.0", + "y": "100.0", + "gene": "B", + "cell": "cell_1", + "is_noise": "0", + }, + ], + ) + _write_patch_geojson( + patches_dir / "patch_1", + {"cell_1": Polygon([(50, 50), (200, 50), (200, 200), (50, 200)])}, + ) + + output_dir = tmp_path / "output" + stitch_transcript_assignments( + patches_dir=patches_dir, + output_dir=output_dir, + max_workers=1, + ) + + geo_out = output_dir / "xr-cell-polygons.geojson" + with open(geo_out) as f: + geo = json.load(f) + + cell_ids = [feat["id"] for feat in geo["features"]] + for cid in cell_ids: + assert cid.startswith("cell-"), f"Cell ID {cid} not in cell-N format" + + # IDs should be sequential starting at 1 + numbers = sorted(int(cid.split("-")[1]) for cid in cell_ids) + assert numbers == list(range(1, len(cell_ids) + 1)) + + def test_stitch_transcript_dedup(self, tmp_path: Path): + """Same transcript in 2 patches: assigned wins over noise.""" + p0 = _make_patch_info( + "patch_0", + 0, + 0, + global_x=(0.0, 600.0), + global_y=(0.0, 1000.0), + core_x=(0.0, 500.0), + core_y=(0.0, 1000.0), + ) + p1 = _make_patch_info( + "patch_1", + 0, + 1, + global_x=(400.0, 1000.0), + global_y=(0.0, 1000.0), + core_x=(500.0, 1000.0), + core_y=(0.0, 1000.0), + ) + metadata = _make_metadata([p0, p1]) + + patches_dir = tmp_path / "patches" + _write_grid_json(metadata, patches_dir / "patch_grid.json") + + # tx_dup appears in both patches. In patch_0 it's assigned, in patch_1 it's noise. + _write_patch_csv( + patches_dir / "patch_0", + [ + { + "transcript_id": "tx_dup", + "x": "450.0", + "y": "100.0", + "gene": "A", + "cell": "cell_1", + "is_noise": "0", + }, + { + "transcript_id": "tx_only0", + "x": "100.0", + "y": "100.0", + "gene": "B", + "cell": "cell_1", + "is_noise": "0", + }, + ], + ) + _write_patch_geojson( + patches_dir / "patch_0", + {"cell_1": Polygon([(50, 50), (500, 50), (500, 250), (50, 250)])}, + ) + + _write_patch_csv( + patches_dir / "patch_1", + [ + { + "transcript_id": "tx_dup", + "x": "50.0", + "y": "100.0", + "gene": "A", + "cell": "", + "is_noise": "1", + }, + { + "transcript_id": "tx_only1", + "x": "200.0", + "y": "200.0", + "gene": "C", + "cell": "cell_1", + "is_noise": "0", + }, + ], + ) + _write_patch_geojson( + patches_dir / "patch_1", + {"cell_1": Polygon([(150, 50), (350, 50), (350, 350), (150, 350)])}, + ) + + output_dir = tmp_path / "output" + stitch_transcript_assignments( + patches_dir=patches_dir, + output_dir=output_dir, + max_workers=1, + ) + + csv_out = output_dir / "xr-transcript-metadata.csv" + merged = pa_csv.read_csv(csv_out) + tid_col = merged.column("transcript_id").to_pylist() + cell_col = merged.column("cell").to_pylist() + + # tx_dup should appear exactly once + dup_count = tid_col.count("tx_dup") + assert dup_count == 1, f"tx_dup appears {dup_count} times, expected 1" + + # The kept version should be assigned (non-empty cell) + dup_idx = tid_col.index("tx_dup") + assert cell_col[dup_idx] != "", "tx_dup should be assigned, not noise" + + def test_stitch_noise_spatial_reassignment(self, tmp_path: Path): + """Noise transcript inside a resolved cell polygon gets assigned.""" + p0 = _make_patch_info( + "patch_0", + 0, + 0, + global_x=(0.0, 600.0), + global_y=(0.0, 1000.0), + core_x=(0.0, 600.0), + core_y=(0.0, 1000.0), + ) + metadata = _make_metadata([p0]) + + patches_dir = tmp_path / "patches" + _write_grid_json(metadata, patches_dir / "patch_grid.json") + + # tx_noise is at (150, 150) local -> (150, 150) global, inside the cell polygon + _write_patch_csv( + patches_dir / "patch_0", + [ + { + "transcript_id": "tx_assigned", + "x": "100.0", + "y": "100.0", + "gene": "A", + "cell": "cell_1", + "is_noise": "0", + }, + { + "transcript_id": "tx_noise", + "x": "150.0", + "y": "150.0", + "gene": "B", + "cell": "", + "is_noise": "1", + }, + ], + ) + # Cell polygon covers (50,50) to (250,250) in local coords -> global same since origin is 0 + _write_patch_geojson( + patches_dir / "patch_0", + {"cell_1": Polygon([(50, 50), (250, 50), (250, 250), (50, 250)])}, + ) + + output_dir = tmp_path / "output" + stitch_transcript_assignments( + patches_dir=patches_dir, + output_dir=output_dir, + max_workers=1, + ) + + csv_out = output_dir / "xr-transcript-metadata.csv" + merged = pa_csv.read_csv(csv_out) + tid_col = merged.column("transcript_id").to_pylist() + cell_col = merged.column("cell").to_pylist() + + noise_idx = tid_col.index("tx_noise") + assert cell_col[noise_idx] != "", ( + "tx_noise should be spatially reassigned to a cell" + ) + + def test_stitch_geojson_not_found(self, tmp_path: Path): + """When GeoJSON doesn't exist, stitch should still work (transcript-only).""" + p0 = _make_patch_info( + "patch_0", + 0, + 0, + global_x=(0.0, 1000.0), + global_y=(0.0, 1000.0), + core_x=(0.0, 1000.0), + core_y=(0.0, 1000.0), + ) + metadata = _make_metadata([p0]) + + patches_dir = tmp_path / "patches" + _write_grid_json(metadata, patches_dir / "patch_grid.json") + + # Write CSV but no GeoJSON + _write_patch_csv( + patches_dir / "patch_0", + [ + { + "transcript_id": "tx_1", + "x": "100.0", + "y": "100.0", + "gene": "A", + "cell": "cell_1", + "is_noise": "0", + }, + ], + ) + + output_dir = tmp_path / "output" + # Should not raise + stitch_transcript_assignments( + patches_dir=patches_dir, + output_dir=output_dir, + max_workers=1, + ) + + # No geojson output (no polygons to write) + geo_out = output_dir / "xr-cell-polygons.geojson" + assert not geo_out.exists() + + +# --------------------------------------------------------------------------- +# Helper function tests +# --------------------------------------------------------------------------- + + +class TestReadGeoJSON: + def test_read_geojson_feature_collection(self, tmp_path: Path): + """Standard FeatureCollection is returned as-is.""" + geojson = { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "id": "cell_1", + "geometry": mapping(Polygon([(0, 0), (10, 0), (10, 10), (0, 10)])), + "properties": {"cell_id": "cell_1"}, + } + ], + } + path = tmp_path / "test.geojson" + with open(path, "w") as f: + json.dump(geojson, f) + + result = read_geojson(path) + assert result["type"] == "FeatureCollection" + assert len(result["features"]) == 1 + assert result["features"][0]["id"] == "cell_1" + + def test_read_geojson_geometry_collection(self, tmp_path: Path): + """proseg's GeometryCollection format is normalized to FeatureCollection.""" + geojson = { + "type": "GeometryCollection", + "geometries": [ + { + "type": "Polygon", + "coordinates": [[[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]]], + "cell": 1, + }, + { + "type": "Polygon", + "coordinates": [[[20, 20], [30, 20], [30, 30], [20, 30], [20, 20]]], + "cell": 2, + }, + ], + } + path = tmp_path / "proseg.geojson" + with open(path, "w") as f: + json.dump(geojson, f) + + result = read_geojson(path) + assert result["type"] == "FeatureCollection" + assert len(result["features"]) == 2 + assert result["features"][0]["id"] == "1" + assert result["features"][1]["id"] == "2" + # geometry should not contain the 'cell' key + assert "cell" not in result["features"][0]["geometry"] + + +class TestTransformPolygons: + def test_transform_polygons_offset(self): + """Verify coordinate shift by (offset_x, offset_y).""" + geojson = { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "id": "cell_1", + "geometry": mapping(Polygon([(0, 0), (10, 0), (10, 10), (0, 10)])), + "properties": {"cell_id": "cell_1"}, + } + ], + } + + shifted = transform_polygons(geojson, offset_x=100.0, offset_y=200.0) + + assert shifted["type"] == "FeatureCollection" + assert len(shifted["features"]) == 1 + + coords = shifted["features"][0]["geometry"]["coordinates"][0] + xs = [c[0] for c in coords] + ys = [c[1] for c in coords] + + assert min(xs) == pytest.approx(100.0) + assert max(xs) == pytest.approx(110.0) + assert min(ys) == pytest.approx(200.0) + assert max(ys) == pytest.approx(210.0) + + +class TestNormalizeGeometryCollection: + def test_empty_geometry_collection(self): + """Empty GeometryCollection returns empty FeatureCollection.""" + result = _normalize_geometry_collection( + {"type": "GeometryCollection", "geometries": []} + ) + assert result["type"] == "FeatureCollection" + assert result["features"] == [] + + def test_string_cell_id_passthrough(self): + """Non-integer cell key is passed through as string.""" + geojson = { + "type": "GeometryCollection", + "geometries": [ + { + "type": "Polygon", + "coordinates": [[[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]]], + "cell": "custom-id", + } + ], + } + result = _normalize_geometry_collection(geojson) + assert result["features"][0]["id"] == "custom-id" + + +# --------------------------------------------------------------------------- +# Baysor native format tests (empty cell column, mismatched IDs) +# --------------------------------------------------------------------------- + + +class TestBaysorNativeFormat: + def test_baysor_empty_cell_column(self, tmp_path: Path): + """Baysor native output: cell column is empty, GeoJSON has integer IDs. + + This is the core bug that spatial containment fixes. Previously, the + ID-matching approach would skip all polygons because no CSV cell values + matched the GeoJSON cell IDs. + """ + p0 = _make_patch_info( + "patch_0", + 0, + 0, + global_x=(0.0, 1000.0), + global_y=(0.0, 1000.0), + core_x=(0.0, 1000.0), + core_y=(0.0, 1000.0), + ) + metadata = _make_metadata([p0]) + + patches_dir = tmp_path / "patches" + _write_grid_json(metadata, patches_dir / "patch_grid.json") + + # Baysor CSV: cell column is EMPTY, cell_id has string labels + _write_patch_csv( + patches_dir / "patch_0", + [ + { + "transcript_id": "tx_1", + "cell_id": "higeahke-1", + "x": "100.0", + "y": "100.0", + "gene": "GeneA", + "cell": "", + "is_noise": "0", + }, + { + "transcript_id": "tx_2", + "cell_id": "higeahke-1", + "x": "150.0", + "y": "150.0", + "gene": "GeneB", + "cell": "", + "is_noise": "0", + }, + { + "transcript_id": "tx_3", + "cell_id": "", + "x": "800.0", + "y": "800.0", + "gene": "GeneC", + "cell": "", + "is_noise": "1", + }, + ], + ) + + # GeoJSON: GeometryCollection with integer cell keys (proseg format) + # Polygon covers (50,50)-(250,250), so tx_1 and tx_2 are inside, tx_3 is outside + geojson = { + "type": "GeometryCollection", + "geometries": [ + { + "type": "Polygon", + "coordinates": [ + [[50, 50], [250, 50], [250, 250], [50, 250], [50, 50]] + ], + "cell": 4986, + } + ], + } + patch_dir = patches_dir / "patch_0" + patch_dir.mkdir(parents=True, exist_ok=True) + with open(patch_dir / "segmentation_polygons.json", "w") as f: + json.dump(geojson, f) + + output_dir = tmp_path / "output" + stitch_transcript_assignments( + patches_dir=patches_dir, + output_dir=output_dir, + max_workers=1, + ) + + csv_out = output_dir / "xr-transcript-metadata.csv" + assert csv_out.exists(), "CSV output should be written" + + geo_out = output_dir / "xr-cell-polygons.geojson" + assert geo_out.exists(), "GeoJSON output should be written" + + merged = pa_csv.read_csv(csv_out) + tid_col = merged.column("transcript_id").to_pylist() + cell_col = merged.column("cell").to_pylist() + + # tx_1 and tx_2 should be assigned to a cell (spatially inside polygon) + for tx_id in ["tx_1", "tx_2"]: + idx = tid_col.index(tx_id) + assert cell_col[idx] != "", ( + f"{tx_id} should be assigned via spatial containment" + ) + assert cell_col[idx].startswith("cell-"), ( + f"{tx_id} should have global ID format" + ) + + # tx_3 should be noise (outside polygon) + tx3_idx = tid_col.index("tx_3") + assert cell_col[tx3_idx] == "", "tx_3 should remain noise (outside polygon)" + + def test_baysor_two_patches_empty_cell(self, tmp_path: Path): + """Two patches with Baysor native format: spatial assignment across patches.""" + p0 = _make_patch_info( + "patch_0", + 0, + 0, + global_x=(0.0, 525.0), + global_y=(0.0, 1000.0), + core_x=(0.0, 500.0), + core_y=(0.0, 1000.0), + ) + p1 = _make_patch_info( + "patch_1", + 0, + 1, + global_x=(475.0, 1000.0), + global_y=(0.0, 1000.0), + core_x=(500.0, 1000.0), + core_y=(0.0, 1000.0), + ) + metadata = _make_metadata([p0, p1]) + + patches_dir = tmp_path / "patches" + _write_grid_json(metadata, patches_dir / "patch_grid.json") + + # Patch 0: cell column empty, transcript at (100,100) + _write_patch_csv( + patches_dir / "patch_0", + [ + { + "transcript_id": "tx_1", + "cell_id": "abc-1", + "x": "100.0", + "y": "100.0", + "gene": "A", + "cell": "", + "is_noise": "0", + }, + ], + ) + # Polygon at (50,50)-(250,250) in local coords + _write_patch_geojson( + patches_dir / "patch_0", + {"anything": Polygon([(50, 50), (250, 50), (250, 250), (50, 250)])}, + ) + + # Patch 1: cell column empty, transcript at (100,100) local -> (575,100) global + _write_patch_csv( + patches_dir / "patch_1", + [ + { + "transcript_id": "tx_2", + "cell_id": "xyz-1", + "x": "100.0", + "y": "100.0", + "gene": "B", + "cell": "", + "is_noise": "0", + }, + ], + ) + _write_patch_geojson( + patches_dir / "patch_1", + {"whatever": Polygon([(50, 50), (200, 50), (200, 200), (50, 200)])}, + ) + + output_dir = tmp_path / "output" + stitch_transcript_assignments( + patches_dir=patches_dir, + output_dir=output_dir, + max_workers=1, + ) + + csv_out = output_dir / "xr-transcript-metadata.csv" + assert csv_out.exists() + + merged = pa_csv.read_csv(csv_out) + tid_col = merged.column("transcript_id").to_pylist() + cell_col = merged.column("cell").to_pylist() + + # Both transcripts should be assigned + for tx_id in ["tx_1", "tx_2"]: + idx = tid_col.index(tx_id) + assert cell_col[idx] != "", f"{tx_id} should be assigned" + assert cell_col[idx].startswith("cell-") + + # They should be in different cells + tx1_cell = cell_col[tid_col.index("tx_1")] + tx2_cell = cell_col[tid_col.index("tx_2")] + assert tx1_cell != tx2_cell, ( + "Transcripts in different patches should have different cells" + ) + + geo_out = output_dir / "xr-cell-polygons.geojson" + assert geo_out.exists() + with open(geo_out) as f: + geo = json.load(f) + assert len(geo["features"]) == 2 diff --git a/workflows/spatialaxe.nf b/workflows/spatialaxe.nf new file mode 100644 index 00000000..8e11f058 --- /dev/null +++ b/workflows/spatialaxe.nf @@ -0,0 +1,794 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// multiqc +include { MULTIQC } from '../modules/nf-core/multiqc/main' +include { MULTIQC as MULTIQC_PRE_XR_RUN } from '../modules/nf-core/multiqc/main' +include { MULTIQC as MULTIQC_POST_XR_RUN } from '../modules/nf-core/multiqc/main' +include { paramsSummaryMultiqc } from '../subworkflows/nf-core/utils_nfcore_pipeline' + +// nf-core functionality +include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' +include { methodsDescriptionText } from '../subworkflows/local/utils_nfcore_spatialaxe_pipeline' +include { paramsSummaryMap } from 'plugin/nf-schema' + +// nf-core modules +include { UNTAR } from '../modules/nf-core/untar/main' + +// coordinate-based segmentation subworklfows +include { SEGGER_CREATE_TRAIN_PREDICT } from '../subworkflows/local/segger_create_train_predict/main' +include { PROSEG_PRESET_PROSEG2BAYSOR } from '../subworkflows/local/proseg_preset_proseg2baysor/main' +include { PROSEG_PRESET_PROSEG2BAYSOR_TILED } from '../subworkflows/local/proseg_preset_proseg2baysor_tiled/main' +include { BAYSOR_GENERATE_PREVIEW } from '../subworkflows/local/baysor_generate_preview/main' +include { BAYSOR_RUN_TRANSCRIPTS_PARQUET } from '../subworkflows/local/baysor_run_transcripts_parquet/main' + +// image-based segmentation subworklfows +include { BAYSOR_RUN_PRIOR_SEGMENTATION_MASK } from '../subworkflows/local/baysor_run_prior_segmentation_mask/main' +include { CELLPOSE_RESOLIFT_MORPHOLOGY_OME_TIF } from '../subworkflows/local/cellpose_resolift_morphology_ome_tif/main' +include { CELLPOSE_BAYSOR_IMPORT_SEGMENTATION } from '../subworkflows/local/cellpose_baysor_import_segmentation/main' +include { STARDIST_RESOLIFT_MORPHOLOGY_OME_TIF } from '../subworkflows/local/stardist_resolift_morphology_ome_tif/main' +include { XENIUMRANGER_RESEGMENT_MORPHOLOGY_OME_TIF } from '../subworkflows/local/xeniumranger_resegment_morphology_ome_tif/main' + +// segmentation-free subworkflows +include { BAYSOR_GENERATE_SEGFREE } from '../subworkflows/local/baysor_generate_segfree/main' +include { FICTURE_PREPROCESS_MODEL } from '../subworkflows/local/ficture_preprocess_model/main' + +// xeniumranger subworkflows +include { XENIUMRANGER_RELABEL_RESEGMENT } from '../subworkflows/local/xeniumranger_relabel_resegment/main' +include { XENIUMRANGER_IMPORT_SEGMENTATION_REDEFINE_BUNDLE } from '../subworkflows/local/xeniumranger_import_segmentation_redefine_bundle/main' + +// spatialdata subworkflows +include { SPATIALDATA_WRITE_META_MERGE } from '../subworkflows/local/spatialdata_write_meta_merge/main' + +// qc layer subworkflows +include { OPT_FLIP_TRACK_STAT } from '../subworkflows/local/opt_flip_track_stat/main' + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + RUN MAIN WORKFLOW +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow SPATIALAXE { + take: + ch_samplesheet // channel: samplesheet read in from --input + alignment_csv + baysor_config + baysor_prior + baysor_scale + baysor_tiling + baysor_tiling_scale + buffer_samples + buffer_size + cell_segmentation_only + cellpose_downscale + cellpose_model + expansion_distance + features + gene_panel + gene_synonyms + max_x + max_y + method + min_qv + min_x + min_y + mode + multiqc_config + multiqc_logo + multiqc_methods_description + nucleus_segmentation_only + offtarget_probe_tracking + outdir + probes_fasta + qupath_polygons + reference_annotations + relabel_genes + run_qc + segger_model + segmentation_mask + sharpen_tiff + stardist_nuclei_model + tiling + xeniumranger_only + + main: + + /* + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SPATIALAXE - GENERATE INPUT CHANNELS + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + ch_versions = channel.empty() + + ch_input = channel.empty() + ch_config = channel.empty() + ch_features = channel.value([]) + ch_raw_bundle = channel.empty() + ch_gene_panel = channel.empty() + ch_qc_reports = channel.empty() + ch_bundle_path = channel.empty() + ch_preview_html = channel.empty() + ch_exp_metadata = channel.empty() + ch_gene_synonyms = channel.empty() + ch_multiqc_files = channel.empty() + ch_multiqc_report = channel.empty() + ch_qupath_polygons = channel.empty() + ch_morphology_image = channel.empty() + ch_redefined_bundle = channel.empty() + ch_coordinate_space = channel.empty() + ch_panel_probes_fasta = channel.empty() + ch_transcripts_file = channel.empty() + ch_reference_annotations = channel.empty() + ch_multiqc_pre_xr_report = channel.empty() + ch_multiqc_post_xr_report = channel.empty() + + + /* + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SPATIALAXE - DATA STAGING + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + if (workflow.profile.contains('test')) { + + // get sample, xenium bundle and image path + ch_input_untar = ch_samplesheet.map { meta, bundle, _image -> + return [meta, bundle] + } + + // get testdata + UNTAR(ch_input_untar) + + ch_untar_outs = UNTAR.out.untar.map { meta, bundle -> + return [meta, bundle.toString()] + } + + ch_samplesheet + .combine(ch_untar_outs, by: 0) + .map { meta, _url, image, test_bundle -> + return [meta, test_bundle, image] + } + .set { ch_input } + } + else { + // for all other profile runs + + // check if samples are buffered + if (buffer_samples) { + ch_input = ch_samplesheet.buffer(size: buffer_size).map + { buffered_sample -> + def (meta, bundle, tif) = buffered_sample[0] + tuple(meta, bundle, tif) + } + } + else { + ch_input = ch_samplesheet + } + } + + // validate xenium bundle POST-staging — works uniformly for production + // directories and for tarball inputs that the UNTAR step has just extracted + def bundle_required_files = [ + "cell_boundaries.csv.gz", + "cell_boundaries.parquet", + "cell_feature_matrix.h5", + "cell_feature_matrix.zarr.zip", + "cells.csv.gz", + "cells.parquet", + "cells.zarr.zip", + "experiment.xenium", + "gene_panel.json", + "metrics_summary.csv", + "morphology.ome.tif", + "morphology_focus/", + "nucleus_boundaries.csv.gz", + "nucleus_boundaries.parquet", + "transcripts.parquet", + "transcripts.zarr.zip", + ] + def bundle_optional_files = [ + "analysis.tar.gz", + "analysis.zarr.zip", + "analysis_summary.html", + ] + + // path to bundle input + ch_bundle_path = ch_input.map { meta, bundle, _image -> + + def bundle_path = file(bundle) + if( !bundle_path.exists() ) { + error("❌ Xenium bundle does not exist: ${bundle}") + } + + def missing_required = bundle_required_files.findAll { check -> !file("${bundle_path}/${check}").exists() } + if (missing_required) { + error("❌ Missing required file(s) in xenium bundle '${bundle}': ${missing_required}") + } + + def missing_optional = bundle_optional_files.findAll { check -> !file("${bundle_path}/${check}").exists() } + if (missing_optional) { + log.warn("⚠️ Missing optional file(s) in xenium bundle '${bundle}': ${missing_optional}") + } + + log.info("✅ Xenium bundle validated: ${bundle}") + return [meta, bundle] + } + + // get transcript.parquet from the xenium bundle + ch_transcripts_file = ch_input.map { meta, bundle, _image -> + def transcripts_parquet = file( + bundle.toString().replaceFirst(/\/$/, '') + "/transcripts.parquet", + checkIfExists: true + ) + return [meta, transcripts_parquet] + } + + // get morphology focus image from the xenium bundle (single 2D plane) + // supports all Xenium versions: + // v2/v3: morphology_focus/morphology_focus_0000.ome.tif + // v4+: morphology_focus/ch0000_dapi.ome.tif + // v1.x: morphology_focus.ome.tif (single file at bundle root) + // fallback: morphology.ome.tif (multi-Z stack, not ideal for Cellpose) + ch_morphology_image = ch_input.map { meta, bundle, image -> + def morphology_img + if (image) { + morphology_img = file(image) + } else { + def bundle_path = bundle.toString().replaceFirst(/\/$/, '') + def focus_v3 = file("${bundle_path}/morphology_focus/morphology_focus_0000.ome.tif") + def focus_v4 = file("${bundle_path}/morphology_focus/ch0000_dapi.ome.tif") + def focus_v1 = file("${bundle_path}/morphology_focus.ome.tif") + if (focus_v3.exists()) { + morphology_img = focus_v3 + } else if (focus_v4.exists()) { + morphology_img = focus_v4 + } else if (focus_v1.exists()) { + morphology_img = focus_v1 + } else { + morphology_img = file("${bundle_path}/morphology.ome.tif", checkIfExists: true) + } + } + return [meta, morphology_img] + } + + // get experiment metdata - experiment.xenium + ch_exp_metadata = ch_input.map { meta, bundle, _image -> + def exp_metadata = file( + bundle.toString().replaceFirst(/\/$/, '') + "/experiment.xenium", + checkIfExists: true + ) + return [meta, exp_metadata] + } + + // get baysor xenium config + ch_config = channel.fromPath( + "${projectDir}/assets/config/xenium.toml", + checkIfExists: true + ) + .flatten() + + // get segmentation mask if provided with --segmentation_mask for the baysor method + if (segmentation_mask) { + ch_segmentation_mask = channel.fromPath( + segmentation_mask, + checkIfExists: true + ) + .flatten() + } + + // get a list of features if provided with the --features for the ficture method + ch_features = features + ? channel.fromPath(features, checkIfExists: true).flatten() + : channel.value([]) + + // get custom cellpose model if provided with the --cellpose_model for the cellpose method + if (cellpose_model) { + cellpose_model = channel.fromPath( + cellpose_model, + checkIfExists: true + ) + .flatten() + } + + // get panel probes fasta for off-target-probe tracking + if (probes_fasta) { + ch_panel_probes_fasta = channel.fromPath( + probes_fasta, + checkIfExists: true + ) + .flatten() + } + + // get reference annotation files (gff,fa) for off-target-probe tracking + if (reference_annotations) { + ch_reference_annotations = channel.fromPath( + "${reference_annotations}/*.{fa,gff}".toString(), + checkIfExists: true + ) + .flatten() + } + + // get gene synonyms for off-target-probe tracking + if (gene_synonyms) { + ch_gene_synonyms = channel.fromPath( + gene_synonyms, + checkIfExists: true + ) + .flatten() + } + + // get qupath ploygons + if (qupath_polygons) { + ch_qupath_polygons = channel.fromPath( + "${qupath_polygons}/*.geojson", + checkIfExists: true + ) + .flatten() + } + + // get gene_panel.json if provided with --gene_panel, sets relabel_genes to true + def do_relabel = gene_panel ? true : relabel_genes + if (gene_panel) { + + def gene_panel_file = file(gene_panel, checkIfExists: true) + ch_gene_panel = ch_input.map { meta, _bundle, _image -> + return [meta, gene_panel_file] + } + } + else { + + // gene panel to use if only --relabel_genes is provided + ch_gene_panel = ch_input.map { meta, bundle, _image -> + def gene_panel_file = file( + bundle.toString().replaceFirst(/\/$/, '') + "/gene_panel.json", + checkIfExists: true + ) + return [meta, gene_panel_file] + } + } + + /* + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SPATIALAXE - RELABEL GENES + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + // run xr relabel if relabel_genes is true, check if gene_panel.json is provided + if (do_relabel) { + + XENIUMRANGER_RELABEL_RESEGMENT( + ch_bundle_path, + ch_gene_panel, + ) + ch_raw_bundle = XENIUMRANGER_RELABEL_RESEGMENT.out.redefined_bundle + } + else { + ch_raw_bundle = ch_bundle_path + } + + /* + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SPATIALAXE - DATA PREVIEW + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + // run baysor preview if `generate_preview ` is true + if (mode == 'preview') { + + BAYSOR_GENERATE_PREVIEW( + ch_transcripts_file, + ch_config, + ) + ch_preview_html = BAYSOR_GENERATE_PREVIEW.out.preview_html + } + + /* + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SPATIALAXE - XENIUMRANGER LAYER + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + // run only xeniumranger import segmentation with changes xr specific params + if (mode == 'image' && xeniumranger_only) { + + XENIUMRANGER_IMPORT_SEGMENTATION_REDEFINE_BUNDLE( + ch_bundle_path, + alignment_csv, + expansion_distance, + nucleus_segmentation_only, + qupath_polygons, + ) + ch_redefined_bundle = XENIUMRANGER_IMPORT_SEGMENTATION_REDEFINE_BUNDLE.out.redefined_bundle + ch_coordinate_space = XENIUMRANGER_IMPORT_SEGMENTATION_REDEFINE_BUNDLE.out.coordinate_space + } + + + /* + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SPATIALAXE - IMAGE-BASED SEGMENTATION LAYER + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + if (mode == 'image') { + + // trigger the default image-based workflow if no method is specified + if (!method) { + + CELLPOSE_BAYSOR_IMPORT_SEGMENTATION( + ch_morphology_image, + ch_bundle_path, + ch_transcripts_file, + ch_exp_metadata, + ch_config, + cell_segmentation_only, + cellpose_model, + max_x, + max_y, + min_qv, + min_x, + min_y, + nucleus_segmentation_only, + sharpen_tiff, + stardist_nuclei_model, + ) + ch_redefined_bundle = CELLPOSE_BAYSOR_IMPORT_SEGMENTATION.out.redefined_bundle + ch_coordinate_space = CELLPOSE_BAYSOR_IMPORT_SEGMENTATION.out.coordinate_space + } + + // run xeniumranger resegment with morphology_ome.tif + if (method == 'xeniumranger') { + + XENIUMRANGER_RESEGMENT_MORPHOLOGY_OME_TIF( + ch_bundle_path, + nucleus_segmentation_only, + ) + ch_redefined_bundle = XENIUMRANGER_RESEGMENT_MORPHOLOGY_OME_TIF.out.redefined_bundle + ch_coordinate_space = XENIUMRANGER_RESEGMENT_MORPHOLOGY_OME_TIF.out.coordinate_space + } + + // run baysor run with morphology_ome.tif + if (method == 'baysor') { + + if (segmentation_mask) { + BAYSOR_RUN_PRIOR_SEGMENTATION_MASK( + ch_bundle_path, + ch_transcripts_file, + ch_segmentation_mask, + ch_config, + max_x, + max_y, + min_qv, + min_x, + min_y, + ) + } + ch_redefined_bundle = BAYSOR_RUN_PRIOR_SEGMENTATION_MASK.out.redefined_bundle + ch_coordinate_space = BAYSOR_RUN_PRIOR_SEGMENTATION_MASK.out.coordinate_space + } + + // run cellpose on the morphology_ome.tif + if (method == 'cellpose') { + + CELLPOSE_RESOLIFT_MORPHOLOGY_OME_TIF( + ch_morphology_image, + ch_bundle_path, + cellpose_downscale, + cellpose_model, + nucleus_segmentation_only, + sharpen_tiff, + stardist_nuclei_model, + ) + ch_redefined_bundle = CELLPOSE_RESOLIFT_MORPHOLOGY_OME_TIF.out.redefined_bundle + ch_coordinate_space = CELLPOSE_RESOLIFT_MORPHOLOGY_OME_TIF.out.coordinate_space + } + + // run stardist on the morphology_ome.tif + if (method == 'stardist') { + + STARDIST_RESOLIFT_MORPHOLOGY_OME_TIF( + ch_morphology_image, + ch_bundle_path, + sharpen_tiff, + stardist_nuclei_model, + ) + ch_redefined_bundle = STARDIST_RESOLIFT_MORPHOLOGY_OME_TIF.out.redefined_bundle + ch_coordinate_space = STARDIST_RESOLIFT_MORPHOLOGY_OME_TIF.out.coordinate_space + } + } + + /* + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SPATIALAXE - TRANSCRIPT-BASED SEGMENTATION LAYER + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + if (mode == 'coordinate') { + + // run proseg with transcripts.parquet if method = proseg or is not provided (default workflow) + if (!method || method == 'proseg') { + + if (tiling) { + PROSEG_PRESET_PROSEG2BAYSOR_TILED( + ch_bundle_path, + ch_transcripts_file, + ) + ch_redefined_bundle = PROSEG_PRESET_PROSEG2BAYSOR_TILED.out.redefined_bundle + ch_coordinate_space = PROSEG_PRESET_PROSEG2BAYSOR_TILED.out.coordinate_space + } else { + PROSEG_PRESET_PROSEG2BAYSOR( + ch_bundle_path, + ch_transcripts_file, + ) + ch_redefined_bundle = PROSEG_PRESET_PROSEG2BAYSOR.out.redefined_bundle + ch_coordinate_space = PROSEG_PRESET_PROSEG2BAYSOR.out.coordinate_space + } + } + + // run segger with transcripts.parquet + if (method == 'segger') { + + SEGGER_CREATE_TRAIN_PREDICT( + ch_bundle_path, + ch_transcripts_file, + segger_model, + ) + ch_redefined_bundle = SEGGER_CREATE_TRAIN_PREDICT.out.redefined_bundle + ch_coordinate_space = SEGGER_CREATE_TRAIN_PREDICT.out.coordinate_space + } + + // run baysor with transcripts.parquet (unified tiled/non-tiled subworkflow) + if (method == 'baysor') { + + // Image-based prior (cellpose mask) requires non-tiled Baysor + if ( baysor_tiling && baysor_prior == 'cellpose' ) { + error "ERROR: baysor_prior='cellpose' (image-based) requires baysor_tiling=false. " + + "For tiled Baysor, use baysor_prior='cells' (column-based)." + } + + ch_prior_mask = channel.empty() + + BAYSOR_RUN_TRANSCRIPTS_PARQUET( + ch_bundle_path, + ch_transcripts_file, + ch_morphology_image, + ch_config, + ch_prior_mask, + baysor_config, + baysor_scale, + baysor_tiling, + baysor_tiling_scale, + max_x, + max_y, + min_qv, + min_x, + min_y, + ) + ch_redefined_bundle = BAYSOR_RUN_TRANSCRIPTS_PARQUET.out.redefined_bundle + ch_coordinate_space = BAYSOR_RUN_TRANSCRIPTS_PARQUET.out.coordinate_space + } + } + + + + /* + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SPATIALAXE - SPATIALDATA / METADATA LAYER + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + // run spatialdata modules to generate sd objects in image or coordinate mode + if (mode == 'image' || mode == 'coordinate') { + + SPATIALDATA_WRITE_META_MERGE( + ch_bundle_path, + ch_redefined_bundle, + ch_coordinate_space, + cell_segmentation_only, + mode, + nucleus_segmentation_only, + ) + } + + /* + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SPATIALAXE - QC LAYER + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + // check to run the qc layer + if (mode == 'qc' || run_qc) { + + if (offtarget_probe_tracking) { + + // run off-target probe tracking + OPT_FLIP_TRACK_STAT( + ch_panel_probes_fasta, + ch_reference_annotations, + ch_gene_synonyms, + ) + } + } + + + /* + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SPATIALAXE - SEGMENTATION-FREE LAYER + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + if (mode == 'segfree') { + + // trigger the default segfree workflow if no method or if the method is baysor + if (!method || method == 'baysor') { + + BAYSOR_GENERATE_SEGFREE( + ch_transcripts_file, + ch_config, + max_x, + max_y, + min_qv, + min_x, + min_y, + ) + } + + // run ficture with transcripts.parquet + if (method == 'ficture') { + + FICTURE_PREPROCESS_MODEL( + ch_transcripts_file, + ch_features, + features, + ) + } + } + + + + /* + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SPATIALAXE - COLLATE & SAVE SOFTWARE VERSIONS + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + // Collect versions published via topic channels (local modules) + ch_topic_versions = channel.topic('versions') + .map { process, tool, version -> + "\"${process}\":\n ${tool}: ${version}" + } + + softwareVersionsToYAML(ch_versions.mix(ch_topic_versions)) + .collectFile( + storeDir: "${outdir}/pipeline_info", + name: 'nf_core_' + 'spatialaxe_software_' + 'mqc_' + 'versions.yml', + sort: true, + newLine: true, + ) + .set { ch_collated_versions } + + /* + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SPATIALAXE - MultiQC + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + ch_multiqc_config = channel.fromPath( + "${projectDir}/assets/multiqc_config.yml", + checkIfExists: true + ) + + ch_multiqc_custom_config = multiqc_config + ? channel.fromPath(multiqc_config, checkIfExists: true) + : channel.empty() + + ch_multiqc_logo = multiqc_logo + ? channel.fromPath(multiqc_logo, checkIfExists: true) + : channel.empty() + + // Combine default and custom configs into a single list for the tuple-based MULTIQC input + ch_multiqc_configs = ch_multiqc_config.mix(ch_multiqc_custom_config).collect() + + 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 = multiqc_methods_description + ? file(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, + ) + ) + + if (mode == 'image' || mode == 'coordinate') { + + // get path to the raw bundle + ch_multiqc_files = ch_multiqc_files.mix( + ch_bundle_path.map { _meta, bundle -> file(bundle) }.collect().ifEmpty([]) + ) + + MULTIQC_PRE_XR_RUN ( + ch_multiqc_files.collect().map { mqc_files -> [mqc_files] } + .combine(ch_multiqc_configs.map { mqc_configs -> [mqc_configs] }) + .combine(ch_multiqc_logo.toList().map { mqc_logo -> [mqc_logo] }) + .map { files, configs, logo -> + [ [id: 'multiqc_pre_xr'], files, configs, logo ? logo[0] : [], [], [] ] + } + ) + ch_multiqc_pre_xr_report = MULTIQC_PRE_XR_RUN.out.report.map { _meta, report -> report }.toList() + + // get path to the redefined bundle + ch_multiqc_files = ch_multiqc_files.mix( + ch_redefined_bundle.map { _meta, bundle -> file(bundle) }.collect().ifEmpty([]) + ) + + MULTIQC_POST_XR_RUN ( + ch_multiqc_files.collect().map { mqc_files -> [mqc_files] } + .combine(ch_multiqc_configs.map { mqc_configs -> [mqc_configs] }) + .combine(ch_multiqc_logo.toList().map { mqc_logo -> [mqc_logo] }) + .map { files, configs, logo -> + [ [id: 'multiqc_post_xr'], files, configs, logo ? logo[0] : [], [], [] ] + } + ) + ch_multiqc_post_xr_report = MULTIQC_POST_XR_RUN.out.report.map { _meta, report -> report }.toList() + + } else { + + // get path to the raw bundle + ch_multiqc_files = ch_multiqc_files.mix( + ch_bundle_path.map { _meta, bundle -> file(bundle) }.collect().ifEmpty([]) + ) + + + // get the qc htmls if qc mode is run + if (mode == 'qc' || run_qc) { + + ch_multiqc_files = ch_multiqc_files.mix( + ch_qc_reports.map { _meta, qc_reports -> qc_reports }.collect().ifEmpty([]) + ) + + } + + + // get the preview html if preview mode is run + if (mode == 'preview') { + + ch_multiqc_files = ch_multiqc_files.mix( + ch_preview_html.map { _meta, preview_html -> preview_html }.collect().ifEmpty([]) + ) + + } + + + MULTIQC ( + ch_multiqc_files.collect().map { mqc_files -> [mqc_files] } + .combine(ch_multiqc_configs.map { mqc_configs -> [mqc_configs] }) + .combine(ch_multiqc_logo.toList().map { mqc_logo -> [mqc_logo] }) + .map { files, configs, logo -> + [ [id: 'multiqc'], files, configs, logo ? logo[0] : [], [], [] ] + } + ) + ch_multiqc_report = MULTIQC.out.report.map { _meta, report -> report }.toList() + + } + + emit: + multiqc_pre_xr_report = ch_multiqc_pre_xr_report // channel: /path/to/multiqc_report.html + multiqc_post_xr_report = ch_multiqc_post_xr_report // channel: /path/to/multiqc_report.html + multiqc_report = ch_multiqc_report // channel: /path/to/multiqc_report.html + versions = ch_versions // channel: [ path(versions.yml) ] +} diff --git a/workflows/spatialxe.nf b/workflows/spatialxe.nf deleted file mode 100644 index d9a6a306..00000000 --- a/workflows/spatialxe.nf +++ /dev/null @@ -1,101 +0,0 @@ -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ -include { FASTQC } from '../modules/nf-core/fastqc/main' -include { MULTIQC } from '../modules/nf-core/multiqc/main' -include { paramsSummaryMap } from 'plugin/nf-schema' -include { paramsSummaryMultiqc } from '../subworkflows/nf-core/utils_nfcore_pipeline' -include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' -include { methodsDescriptionText } from '../subworkflows/local/utils_nfcore_spatialxe_pipeline' - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - RUN MAIN WORKFLOW -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -workflow SPATIALXE { - - take: - ch_samplesheet // channel: samplesheet read in from --input - multiqc_config - multiqc_logo - multiqc_methods_description - outdir - - main: - - 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.map{ _meta, file -> file }) - - // - // Collate and save software 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')}" - } - - def ch_collated_versions = softwareVersionsToYAML(ch_versions.mix(topic_versions.versions_file)) - .mix(topic_versions_string) - .collectFile( - storeDir: "${outdir}/pipeline_info", - name: 'nf_core_' + 'spatialxe_software_' + 'mqc_' + 'versions.yml', - sort: true, - newLine: true - ) - - // - // MODULE: MultiQC - // - ch_multiqc_files = ch_multiqc_files.mix(ch_collated_versions) - 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.map { _meta, report -> [report] }.toList() // channel: /path/to/multiqc_report.html - versions = ch_versions // channel: [ path(versions.yml) ] -} - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - THE END -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/

    $&(WpvyYB^Gv_ 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 @Cfg;V;@-<8|9u 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�R!m8Zs>gqZwa1pU66;rL%^jc`c;#(TM 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�&0gCS>FY&=7#_{Hzi-T!-t@Kx-gw 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Μ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