diff --git a/modules/nf-core/xeniumranger/.gitignore b/modules/nf-core/xeniumranger/.gitignore deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/modules/nf-core/xeniumranger/Dockerfile b/modules/nf-core/xeniumranger/Dockerfile index daa3c965c5e0..1bcc0aad53a6 100644 --- a/modules/nf-core/xeniumranger/Dockerfile +++ b/modules/nf-core/xeniumranger/Dockerfile @@ -10,20 +10,20 @@ RUN apt-get update --allow-releaseinfo-change \ # copy over xeniumranger # the latest version of the xeniumranger tool has been downloaded from https://www.10xgenomics.com/support/software/xenium-ranger/downloads -COPY xeniumranger-3.0.1.tar.gz /xeniumranger-3.0.1.tar.gz +COPY xeniumranger-4.0.0.tar.gz /xeniumranger-4.0.0.tar.gz # install xenium ranger -RUN tar -xzvf /xeniumranger-3.0.1.tar.gz && \ - rm /xeniumranger-3.0.1.tar.gz +RUN tar -xzvf /xeniumranger-4.0.0.tar.gz && \ + rm /xeniumranger-4.0.0.tar.gz # Set environment variables -# ENV PATH="xeniumranger-xenium3.0/bin:$PATH" +# ENV PATH="xeniumranger-xenium4.0/bin:$PATH" # multistage to reduce image size FROM ubuntu:20.04 # Set environment variables -ENV PATH="/xeniumranger-xenium3.0/bin:$PATH" +ENV PATH="/xeniumranger-xenium4.0/bin:$PATH" # copy over xenium from builder -COPY --from=builder /xeniumranger-xenium3.0 /xeniumranger-xenium3.0/ +COPY --from=builder /xeniumranger-xenium4.0 /xeniumranger-xenium4.0/ diff --git a/modules/nf-core/xeniumranger/README.md b/modules/nf-core/xeniumranger/README.md index 9bbe56b01c29..fbfdb0173f24 100644 --- a/modules/nf-core/xeniumranger/README.md +++ b/modules/nf-core/xeniumranger/README.md @@ -5,7 +5,7 @@ Xenium Ranger is a commercial tool from 10X Genomics. The container provided for 1. Navigate to the appropriate download page. - [Xenium Ranger](https://www.10xgenomics.com/support/software/xenium-ranger/downloads): download the tar ball of the desired Xenium Ranger version with `curl` or `wget`. Place this file in the same folder where the Dockerfile lies. 2. Edit the Dockerfile. Update the Xenium Ranger versions in this line: - (current version for xenium ranger is fixated at [3.0.1](https://www.10xgenomics.com/support/software/xenium-ranger/downloads) in the dockerfile) + (current version for xenium ranger is fixated at [4.0.0](https://www.10xgenomics.com/support/software/xenium-ranger/downloads) in the dockerfile) 3. Create and test the container: diff --git a/modules/nf-core/xeniumranger/import-segmentation/main.nf b/modules/nf-core/xeniumranger/import-segmentation/main.nf index 50b17272a16c..264b8a72ef63 100644 --- a/modules/nf-core/xeniumranger/import-segmentation/main.nf +++ b/modules/nf-core/xeniumranger/import-segmentation/main.nf @@ -2,80 +2,67 @@ process XENIUMRANGER_IMPORT_SEGMENTATION { tag "$meta.id" label 'process_high' - container "nf-core/xeniumranger:3.0.1" + container "nf-core/xeniumranger:4.0" input: - tuple val(meta), path(xenium_bundle) - val(expansion_distance) - path(coordinate_transform) - path(nuclei) - path(cells) - path(transcript_assignment) - path(viz_polygons) + 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("**/outs/**"), emit: outs - path "versions.yml", emit: versions + 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." + error "XENIUMRANGER_IMPORT_SEGMENTATION module does not support Conda. Please use Docker / Singularity / Podman instead." } - def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" - // image based segmentation options - def expansion_distance = expansion_distance ? "--expansion-distance=\"${expansion_distance}\"": "" // expansion distance (default - 5, range - 0 - 100) - def coordinate_transform = coordinate_transform ? "--coordinate-transform=\"${coordinate_transform}\"": "" + prefix = task.ext.prefix ?: "${meta.id}" - def nuclei_detection = nuclei ? "--nuclei=\"${nuclei}\"": "" - def cells = cells ? "--cells=\"${cells}\"": "" + // 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." + } - // transcript based segmentation - def transcript_assignment = transcript_assignment ? "--transcript-assignment=\"${transcript_assignment}\"": "" - def viz_polygons = viz_polygons ? "--viz-polygons=\"${viz_polygons}\"":"" + 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}\"" + } - // shared argument - def units = coordinate_transform ? "--units=microns": "--units=pixels" + def args = assembled_args ? assembled_args.join(" \\\n ") : "" """ xeniumranger import-segmentation \\ - --id="${prefix}" \\ + --id="XENIUMRANGER_IMPORT_SEGMENTATION" \\ --xenium-bundle="${xenium_bundle}" \\ --localcores=${task.cpus} \\ --localmem=${task.memory.toGiga()} \\ - ${coordinate_transform} \\ - ${nuclei_detection} \\ - ${cells} \\ - ${expansion_distance} \\ - ${transcript_assignment} \\ - ${viz_polygons} \\ - ${units} \\ ${args} - cat <<-END_VERSIONS > versions.yml - "${task.process}": - xeniumranger: \$(xeniumranger -V | sed -e "s/xeniumranger-/- /g") - END_VERSIONS + rm -rf "${prefix}" + mv XENIUMRANGER_IMPORT_SEGMENTATION/outs "${prefix}" """ stub: - // 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." - } - def prefix = task.ext.prefix ?: "${meta.id}" + prefix = task.ext.prefix ?: "${meta.id}" """ - mkdir -p "${prefix}/outs/" - touch "${prefix}/outs/fake_file.txt" - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - xeniumranger: \$(xeniumranger -V | sed -e "s/xeniumranger-/- /g") - END_VERSIONS + 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 index 5aefa1c61087..e222df582a05 100644 --- a/modules/nf-core/xeniumranger/import-segmentation/meta.yml +++ b/modules/nf-core/xeniumranger/import-segmentation/meta.yml @@ -1,9 +1,14 @@ name: xeniumranger_import_segmentation -description: The xeniumranger import-segmentation module allows you to specify 2D - nuclei and/or cell segmentation results for assigning transcripts to cells and recalculate - all Xenium Onboard Analysis (XOA) outputs that depend on segmentation. Segmentation - results can be generated by community-developed tools or prior Xenium segmentation - result. +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 @@ -28,66 +33,109 @@ input: type: map description: | Groovy Map containing run information - e.g. [id:'xenium_bundle_path'] + e.g. [ id:'xenium_sample' ] - xenium_bundle: type: directory - description: Path to the xenium output bundle generated by the Xenium Onboard - Analysis pipeline - - expansion_distance: - type: integer - description: Nuclei boundary expansion distance in µm. Only for use when nucleus - segmentation provided as input. Default-5 (accepted range 0 - 100) - - coordinate_transform: - type: file - description: Image alignment file containing similarity transform matrix e.g., - the _imagealignment.csv file exported from Xenium Explorer - ontologies: [] - - nuclei: - type: file - description: | - Label mask (TIFF or NPY), polygons of nucleus segmentations (GeoJSON FeatureCollection), or Xenium Onboard Analysis cells.zarr.zip (the nucleus masks as input). - --nuclei will use nucleusGeometry polygon if it exists in the GeoJSON (i.e., for QuPath-like GeoJSON files), - or geometry if it does not. Error if --transcript-assignment argument is used. - ontologies: [] - - cells: - type: file - description: | - Label mask (TIFF or NPY), polygons of cell segmentations (GeoJSON FeatureCollection), or Xenium Onboard Analysis cells.zarr.zip (the cell masks as input). - Features with a non-cell objectType will be ignored. Error if --transcript-assignment argument is used. - In Xenium Ranger v2.0, --nuclei no longer needs to be used with --cells. - ontologies: [] - - transcript_assignment: - type: file - description: | - Transcript CSV with cell assignment from Baysor v0.6. Error if --cells or --nuclei arguments are used. - ontologies: [] - - viz_polygons: - type: file - description: | - Cell boundary polygons (GeoJSON) for visualization from Baysor v0.6. Required if --transcript-assignment argument used. Error if --cells or --nuclei arguments used. - ontologies: [] + 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: file - description: Files containing the outputs of Cell Ranger, see official 10X - Genomics documentation for a complete list - pattern: "${meta.id}/outs/*" - ontologies: [] - - "**/outs/**": - type: file - description: Files containing the outputs of xenium ranger, see official 10X - Genomics documentation for a complete list of outputs - pattern: "${meta.id}/outs/*" - ontologies: [] + 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: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" - ontologies: - - edam: http://edamontology.org/format_3750 # YAML + - - ${task.process}: + type: string + description: The process the versions were collected from + - 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 index 6e2826eba480..3a8616c1c470 100644 --- a/modules/nf-core/xeniumranger/import-segmentation/tests/main.nf.test +++ b/modules/nf-core/xeniumranger/import-segmentation/tests/main.nf.test @@ -3,7 +3,6 @@ nextflow_process { name "Test Process XENIUMRANGER_IMPORT_SEGMENTATION" script "../main.nf" process "XENIUMRANGER_IMPORT_SEGMENTATION" - config "./nextflow.config" tag "modules" tag "modules_nfcore" @@ -11,296 +10,70 @@ nextflow_process { 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/spatialxe/Xenium_Prime_Mouse_Ileum_tiny_outs.zip', checkIfExists: true)] - """ - } - } - } - test("xeniumranger import-segmentation nuclei npy") { - when { + run("UNZIP") { + script "modules/nf-core/unzip/main.nf" 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] = [] + input[0] = [[], file('https://raw.githubusercontent.com/nf-core/test-datasets/spatialxe/Xenium_V1_Protein_Human_Kidney_tiny_outs.zip', checkIfExists: true)] """ } } - 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") { + test("xeniumranger import-segmentation nuclei") { 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" + input[0] = UNZIP.out.unzipped_archive + .map { _, bundle -> + [ + [id: "test_xeniumranger_import-segmentation"], + bundle, + [], + [], + bundle + "/cells.zarr.zip", + [], + [], + [] + ] + } """ } } - 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() }, + { assert process.out.outs != null }, + { assert file(process.out.outs.get(0).get(1)).isDirectory() }, + { assert file(process.out.outs.get(0).get(1)).list().size() > 0 }, + { assert snapshot(process.out.versions_xeniumranger).match()}, ) } } 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] = [] + input[0] = UNZIP.out.unzipped_archive + .map { _, bundle -> + [ + [id: "test_xeniumranger_import-segmentation"], + bundle, + [], + [], + bundle + "/cells.zarr.zip", + [], + [] + ] + } """ } } 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 index 1c312ae0f3ba..b0f079b96e1e 100644 --- a/modules/nf-core/xeniumranger/import-segmentation/tests/main.nf.test.snap +++ b/modules/nf-core/xeniumranger/import-segmentation/tests/main.nf.test.snap @@ -1,72 +1,19 @@ { - "xeniumranger import-segmentation": { + "xeniumranger import-segmentation nuclei": { "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" + [ + "XENIUMRANGER_IMPORT_SEGMENTATION", + "xeniumranger", + "4.0.1.1" + ] ] ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-10-29T23:22:58.158857" + "timestamp": "2025-12-10T19:58:12.844137213" }, "xeniumranger import-segmentation stub": { "content": [ @@ -76,52 +23,41 @@ { "id": "test_xeniumranger_import-segmentation" }, - "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + [ + "experiment.xenium:md5,d41d8cd98f00b204e9800998ecf8427e" + ] ] ], "1": [ - "versions.yml:md5,d76e870d71abf94ed9ae972a08b83f63" + [ + "XENIUMRANGER_IMPORT_SEGMENTATION", + "xeniumranger", + "4.0.1.1" + ] ], "outs": [ [ { "id": "test_xeniumranger_import-segmentation" }, - "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + [ + "experiment.xenium:md5,d41d8cd98f00b204e9800998ecf8427e" + ] ] ], - "versions": [ - "versions.yml:md5,d76e870d71abf94ed9ae972a08b83f63" + "versions_xeniumranger": [ + [ + "XENIUMRANGER_IMPORT_SEGMENTATION", + "xeniumranger", + "4.0.1.1" + ] ] } ], "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" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-10-29T23:11:37.18721" + "timestamp": "2025-12-10T19:58:19.883560548" } } \ 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 deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/modules/nf-core/xeniumranger/relabel/main.nf b/modules/nf-core/xeniumranger/relabel/main.nf index b06fe9ea13eb..bf04a9713ec6 100644 --- a/modules/nf-core/xeniumranger/relabel/main.nf +++ b/modules/nf-core/xeniumranger/relabel/main.nf @@ -2,55 +2,46 @@ process XENIUMRANGER_RELABEL { tag "$meta.id" label 'process_high' - container "nf-core/xeniumranger:3.0.1" + container "nf-core/xeniumranger:4.0" input: - tuple val(meta), path(xenium_bundle) - path(gene_panel) + tuple val(meta), path(xenium_bundle, stageAs: "bundle/"), path(panel) output: - tuple val(meta), path("**/outs/**"), emit: outs - path "versions.yml", emit: versions + 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." } - def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" + + prefix = task.ext.prefix ?: "${meta.id}" + def args = task.ext.args ?: "" """ xeniumranger relabel \\ - --id="${prefix}" \\ + --id="XENIUMRANGER_RELABEL" \\ --xenium-bundle="${xenium_bundle}" \\ - --panel="${gene_panel}" \\ + --panel="${panel}" \\ --localcores=${task.cpus} \\ --localmem=${task.memory.toGiga()} \\ ${args} - cat <<-END_VERSIONS > versions.yml - "${task.process}": - xeniumranger: \$(xeniumranger -V | sed -e "s/xeniumranger-/- /g") - END_VERSIONS + rm -rf "${prefix}" + mv XENIUMRANGER_RELABEL/outs "${prefix}" """ stub: - // 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." - } - def prefix = task.ext.prefix ?: "${meta.id}" - """ - mkdir -p "${prefix}/outs/" - touch "${prefix}/outs/fake_file.txt" + prefix = task.ext.prefix ?: "${meta.id}" - cat <<-END_VERSIONS > versions.yml - "${task.process}": - xeniumranger: \$(xeniumranger -V | sed -e "s/xeniumranger-/- /g") - END_VERSIONS + """ + 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 index 1d4d263bf214..8b61e0bb8f02 100644 --- a/modules/nf-core/xeniumranger/relabel/meta.yml +++ b/modules/nf-core/xeniumranger/relabel/meta.yml @@ -1,6 +1,6 @@ name: xeniumranger_relabel -description: The xeniumranger relabel module allows you to change the gene labels - applied to decoded transcripts. +description: The xeniumranger relabel module allows you to change the gene + labels applied to decoded transcripts. keywords: - spatial - relabel @@ -15,44 +15,58 @@ tools: 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 + - "10x Genomics EULA" identifier: "" input: - - meta: type: map description: | Groovy Map containing run information - e.g. [id:'xenium_bundle_path'] + e.g. [ id:'xenium_bundle_path' ] - xenium_bundle: type: directory - description: Path to the xenium output bundle generated by the Xenium Onboard - Analysis pipeline - - gene_panel: - type: file - description: Gene panel JSON file to use for relabeling decoded transcripts - ontologies: [] + 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: file - description: Files containing the outputs of Cell Ranger, see official 10X - Genomics documentation for a complete list - pattern: "${meta.id}/outs/*" - ontologies: [] - - "**/outs/**": - type: file - description: Files containing the outputs of xenium ranger, see official 10X - Genomics documentation for a complete list of outputs - pattern: "${meta.id}/outs/*" - ontologies: [] + 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: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" - ontologies: - - edam: http://edamontology.org/format_3750 # YAML + - - "${task.process}": + type: string + description: The process the versions were collected from + - 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 index 9db6e96a62f1..6518e8b8214d 100644 --- a/modules/nf-core/xeniumranger/relabel/tests/main.nf.test +++ b/modules/nf-core/xeniumranger/relabel/tests/main.nf.test @@ -3,7 +3,6 @@ nextflow_process { name "Test Process XENIUMRANGER_RELABEL" script "../main.nf" process "XENIUMRANGER_RELABEL" - config "./nextflow.config" tag "modules" tag "modules_nfcore" @@ -11,25 +10,31 @@ nextflow_process { 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/spatialxe/Xenium_Prime_Mouse_Ileum_tiny_outs.zip', checkIfExists: true)] - """ - } + + run("UNZIP") { + script "modules/nf-core/unzip/main.nf" + process { + """ + input[0] = [[], file('https://raw.githubusercontent.com/nf-core/test-datasets/spatialxe/Xenium_V1_Protein_Human_Kidney_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" + input[0] = UNZIP.out.unzipped_archive + .map { _, bundle -> + [ + [id: "test_xeniumranger_relabel"], + bundle, + bundle + "/gene_panel.json" + ] + } """ } } @@ -37,37 +42,10 @@ nextflow_process { 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() }, - ) + { assert file(process.out.outs.get(0).get(1)).isDirectory() }, + { assert file(process.out.outs.get(0).get(1)).list().size() > 0 }, + { assert snapshot(process.out.versions_xeniumranger).match()}, + ) } } @@ -76,10 +54,14 @@ nextflow_process { 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" + input[0] = UNZIP.out.unzipped_archive + .map { _, bundle -> + [ + [id: "test_xeniumranger_relabel"], + bundle, + bundle + "/gene_panel.json" + ] + } """ } } diff --git a/modules/nf-core/xeniumranger/relabel/tests/main.nf.test.snap b/modules/nf-core/xeniumranger/relabel/tests/main.nf.test.snap index 7c70cfc28faa..63af4ff31a16 100644 --- a/modules/nf-core/xeniumranger/relabel/tests/main.nf.test.snap +++ b/modules/nf-core/xeniumranger/relabel/tests/main.nf.test.snap @@ -2,33 +2,18 @@ "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" + [ + "XENIUMRANGER_RELABEL", + "xeniumranger", + "4.0.1.1" + ] ] ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-10-29T21:06:09.082129" + "timestamp": "2025-12-10T20:00:59.571728235" }, "xeniumranger relabel stub": { "content": [ @@ -38,29 +23,41 @@ { "id": "test_xeniumranger_relabel" }, - "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + [ + "experiment.xenium:md5,d41d8cd98f00b204e9800998ecf8427e" + ] ] ], "1": [ - "versions.yml:md5,ab2584177544560d5a9e9c36f7d24354" + [ + "XENIUMRANGER_RELABEL", + "xeniumranger", + "4.0.1.1" + ] ], "outs": [ [ { "id": "test_xeniumranger_relabel" }, - "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + [ + "experiment.xenium:md5,d41d8cd98f00b204e9800998ecf8427e" + ] ] ], - "versions": [ - "versions.yml:md5,ab2584177544560d5a9e9c36f7d24354" + "versions_xeniumranger": [ + [ + "XENIUMRANGER_RELABEL", + "xeniumranger", + "4.0.1.1" + ] ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-10-22T15:22:34.353444" + "timestamp": "2025-12-10T20:01:06.673047685" } } \ 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 deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/modules/nf-core/xeniumranger/rename/main.nf b/modules/nf-core/xeniumranger/rename/main.nf index d273caf3276c..dc653daed371 100644 --- a/modules/nf-core/xeniumranger/rename/main.nf +++ b/modules/nf-core/xeniumranger/rename/main.nf @@ -2,31 +2,33 @@ process XENIUMRANGER_RENAME { tag "$meta.id" label 'process_high' - container "nf-core/xeniumranger:3.0.1" + container "nf-core/xeniumranger:4.0" input: - tuple val(meta), path(xenium_bundle) - val(region_name) - val(cassette_name) + tuple val(meta), path(xenium_bundle, stageAs: "bundle/"), val(region_name), val(cassette_name) output: - tuple val(meta), path("**/outs/**"), emit: outs - path "versions.yml", emit: versions + 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_RENAME 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}" """ + rm -rf "${prefix}" + xeniumranger rename \\ - --id="${prefix}" \\ + --id="XENIUMRANGER_RENAME" \\ --xenium-bundle="${xenium_bundle}" \\ --region-name="${region_name}" \\ --cassette-name="${cassette_name}" \\ @@ -34,25 +36,15 @@ process XENIUMRANGER_RENAME { --localmem=${task.memory.toGiga()} \\ ${args} - cat <<-END_VERSIONS > versions.yml - "${task.process}": - xeniumranger: \$(xeniumranger -V | sed -e "s/xeniumranger-/- /g") - END_VERSIONS + mv XENIUMRANGER_RENAME/outs "${prefix}" """ stub: - // Exit if running this module with -profile conda / -profile mamba - if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { - error "XENIUMRANGER_RENAME module does not support Conda. Please use Docker / Singularity / Podman instead." - } - def prefix = task.ext.prefix ?: "${meta.id}" - """ - mkdir -p "${prefix}/outs/" - touch "${prefix}/outs/fake_file.txt" - cat <<-END_VERSIONS > versions.yml - "${task.process}": - xeniumranger: \$(xeniumranger -V | sed -e "s/xeniumranger-/- /g") - END_VERSIONS + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p "${prefix}" + touch "${prefix}/experiment.xenium" """ } diff --git a/modules/nf-core/xeniumranger/rename/meta.yml b/modules/nf-core/xeniumranger/rename/meta.yml index 30b79d922fe5..b3880f4ec268 100644 --- a/modules/nf-core/xeniumranger/rename/meta.yml +++ b/modules/nf-core/xeniumranger/rename/meta.yml @@ -1,7 +1,7 @@ name: xeniumranger_rename -description: The xeniumranger rename module allows you to change the sample region_name - and cassette_name throughout all the Xenium Onboard Analysis output files that contain - this information. +description: The xeniumranger rename module allows you to change the sample + region_name and cassette_name throughout all the Xenium Onboard Analysis + output files that contain this information. keywords: - spatial - rename @@ -24,36 +24,48 @@ input: description: Groovy Map containing sample information e.g. [ id:'test' ] - xenium_bundle: type: directory - description: Path to the xenium output bundle generated by the Xenium Onboard - Analysis pipeline - - region_name: - type: string - description: New region name - - cassette_name: - type: string - description: New cassette name + description: Path to the xenium output bundle generated by the Xenium + Onboard Analysis pipeline + - region_name: + type: string + description: New region name + - cassette_name: + type: string + description: New cassette name output: outs: - - meta: - type: file - description: Files containing the outputs of Cell Ranger, see official 10X - Genomics documentation for a complete list - pattern: "${meta.id}/outs/*" - ontologies: [] - - "**/outs/**": - type: file - description: Files containing the outputs of xenium ranger, see official 10X - Genomics documentation for a complete list of outputs - pattern: "${meta.id}/outs/*" - ontologies: [] + 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: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" - ontologies: - - edam: http://edamontology.org/format_3750 # YAML + - - "${task.process}": + type: string + description: The process the versions were collected from + - 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/rename/tests/main.nf.test b/modules/nf-core/xeniumranger/rename/tests/main.nf.test index 36bf00d95d30..07b1eaeb8b20 100644 --- a/modules/nf-core/xeniumranger/rename/tests/main.nf.test +++ b/modules/nf-core/xeniumranger/rename/tests/main.nf.test @@ -3,7 +3,6 @@ nextflow_process { name "Test Process XENIUMRANGER_RENAME" script "../main.nf" process "XENIUMRANGER_RENAME" - config "./nextflow.config" tag "modules" tag "modules_nfcore" @@ -16,7 +15,7 @@ nextflow_process { script "modules/nf-core/unzip/main.nf" process { """ - input[0] = [[], file('https://raw.githubusercontent.com/nf-core/test-datasets/spatialxe/Xenium_Prime_Mouse_Ileum_tiny_outs.zip', checkIfExists: true)] + input[0] = [[], file('https://raw.githubusercontent.com/nf-core/test-datasets/spatialxe/Xenium_V1_Protein_Human_Kidney_tiny_outs.zip', checkIfExists: true)] """ } } @@ -26,11 +25,15 @@ nextflow_process { when { process { """ - input[0] = Channel.of([ + input[0] = UNZIP.out.unzipped_archive + .map { _, bundle -> + [ [id: "test_xeniumranger_rename"], - ]).combine(UNZIP.out.unzipped_archive.map { it[1] }) - input[1] = "test_region" - input[2] = "test_cassette" + bundle, + "test_region", + "test_cassette" + ] + } """ } } @@ -38,18 +41,16 @@ nextflow_process { 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_summary.html', - 'experiment.xenium', - ]} - ).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 == 'experiment.xenium' }).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)).isDirectory() }, + { assert file(process.out.outs.get(0).get(1) + "/analysis_summary.html").exists() }, + { assert file(process.out.outs.get(0).get(1) + "/cells.csv.gz").exists() }, + { assert file(process.out.outs.get(0).get(1) + "/cells.parquet").exists() }, + { assert file(process.out.outs.get(0).get(1) + "/cells.zarr.zip").exists() }, + { assert file(process.out.outs.get(0).get(1) + "/transcripts.parquet").exists() }, + { assert file(process.out.outs.get(0).get(1) + "/transcripts.zarr.zip").exists() }, + { assert file(process.out.outs.get(0).get(1) + "/analysis.zarr.zip").exists() }, + { assert file(process.out.outs.get(0).get(1) + "/cell_feature_matrix.zarr.zip").exists() }, + { assert snapshot(process.out.versions_xeniumranger).match()}, ) } } @@ -59,11 +60,15 @@ nextflow_process { when { process { """ - input[0] = Channel.of([ + input[0] = UNZIP.out.unzipped_archive + .map { meta, bundle -> + [ [id: "test_xeniumranger_rename"], - ]).combine(UNZIP.out.unzipped_archive.map { it[1] }) - input[1] = "test_region" - input[2] = "test_cassette" + bundle, + "test_region", + "test_cassette" + ] + } """ } } diff --git a/modules/nf-core/xeniumranger/rename/tests/main.nf.test.snap b/modules/nf-core/xeniumranger/rename/tests/main.nf.test.snap index ea57ab7e0fd6..5127d1cc25ff 100644 --- a/modules/nf-core/xeniumranger/rename/tests/main.nf.test.snap +++ b/modules/nf-core/xeniumranger/rename/tests/main.nf.test.snap @@ -1,4 +1,20 @@ { + "xeniumranger rename": { + "content": [ + [ + [ + "XENIUMRANGER_RENAME", + "xeniumranger", + "4.0.1.1" + ] + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-12-10T20:01:21.033622706" + }, "xeniumranger rename stub": { "content": [ { @@ -7,59 +23,41 @@ { "id": "test_xeniumranger_rename" }, - "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + [ + "experiment.xenium:md5,d41d8cd98f00b204e9800998ecf8427e" + ] ] ], "1": [ - "versions.yml:md5,823917e7bc8f27cf314ef477fe7369eb" + [ + "XENIUMRANGER_RENAME", + "xeniumranger", + "4.0.1.1" + ] ], "outs": [ [ { "id": "test_xeniumranger_rename" }, - "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + [ + "experiment.xenium:md5,d41d8cd98f00b204e9800998ecf8427e" + ] ] ], - "versions": [ - "versions.yml:md5,823917e7bc8f27cf314ef477fe7369eb" + "versions_xeniumranger": [ + [ + "XENIUMRANGER_RENAME", + "xeniumranger", + "4.0.1.1" + ] ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" - }, - "timestamp": "2024-10-30T23:10:43.918492" - }, - "xeniumranger rename": { - "content": [ - [ - "versions.yml:md5,823917e7bc8f27cf314ef477fe7369eb" - ], - [ - "analysis.zarr.zip:md5,1ffb1b86586fe6c80ce1676b79137785", - "cell_boundaries.csv.gz:md5,8b4f2aa455a6fb14b2669a42db32ea7e", - "cell_boundaries.parquet:md5,e55d6a7fbec336103994baad8c8e4a9a", - "cell_feature_matrix.h5:md5,96cb400f1b1dd6f8796daea0ad5c74e6", - "cell_feature_matrix.zarr.zip:md5,36f45a290cf4ee1232f2d1cd0fdbd820", - "cells.csv.gz:md5,3cef2d7cc8cfba1d47bdb7c65c3d5d5f", - "cells.parquet:md5,e1450c7eca3d7ce0d4911c95042b1303", - "cells.zarr.zip:md5,556e47d5b14150239b10b2f801defa2b", - "gene_panel.json:md5,8890dd5fd90706e751554ac3fdfdedde", - "metrics_summary.csv:md5,54ad3944eb3ba6a4d7bda01bc2a6bb1c", - "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,203cb05ee7689805cc505ebda9557551", - "transcripts.zarr.zip:md5,807e63a2ef8340e085cd899507f45395" - ] - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-10-30T23:10:34.499597" + "timestamp": "2025-12-10T20:01:28.086802143" } -} \ No newline at end of file +} diff --git a/modules/nf-core/xeniumranger/rename/tests/nextflow.config b/modules/nf-core/xeniumranger/rename/tests/nextflow.config deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/modules/nf-core/xeniumranger/resegment/main.nf b/modules/nf-core/xeniumranger/resegment/main.nf index 5d28fa698a76..d52eba0e9e83 100644 --- a/modules/nf-core/xeniumranger/resegment/main.nf +++ b/modules/nf-core/xeniumranger/resegment/main.nf @@ -2,69 +2,44 @@ process XENIUMRANGER_RESEGMENT { tag "$meta.id" label 'process_high' - container "nf-core/xeniumranger:3.0.1" + container "nf-core/xeniumranger:4.0" input: - tuple val(meta), path(xenium_bundle) - val(expansion_distance) - val(dapi_filter) - val(boundary_stain) - val(interior_stain) + tuple val(meta), path(xenium_bundle, stageAs: "bundle/") output: - tuple val(meta), path("**/outs/**"), emit: outs - path "versions.yml", emit: versions + 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." } - def args = task.ext.args ?: "" - def prefix = task.ext.prefix ?: "${meta.id}" - - def expansion_distance = expansion_distance ? "--expansion-distance=\"${expansion_distance}\"": "" - def dapi_filter = dapi_filter ? "--dapi-filter=\"${dapi_filter}\"": "" - // Do not use boundary stain in analysis, but keep default interior stain and DAPI - def boundary_stain = boundary_stain ? "--boundary-stain=disable": "" - // Do not use interior stain in analysis, but keep default boundary stain and DAPI - def interior_stain = interior_stain ? "--interior-stain=disable": "" + prefix = task.ext.prefix ?: "${meta.id}" + def args = task.ext.args ?: "" """ xeniumranger resegment \\ - --id="${prefix}" \\ + --id="XENIUMRANGER_RESEGMENT" \\ --xenium-bundle="${xenium_bundle}" \\ - ${expansion_distance} \\ - ${dapi_filter} \\ - ${boundary_stain} \\ - ${interior_stain} \\ --localcores=${task.cpus} \\ --localmem=${task.memory.toGiga()} \\ ${args} - cat <<-END_VERSIONS > versions.yml - "${task.process}": - xeniumranger: \$(xeniumranger -V | sed -e "s/xeniumranger-/- /g") - END_VERSIONS + rm -rf "${prefix}" + mv XENIUMRANGER_RESEGMENT/outs "${prefix}" """ stub: - // 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." - } - def prefix = task.ext.prefix ?: "${meta.id}" + prefix = task.ext.prefix ?: "${meta.id}" """ - mkdir -p "${prefix}/outs/" - touch "${prefix}/outs/fake_file.txt" - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - xeniumranger: \$(xeniumranger -V | sed -e "s/xeniumranger-/- /g") - END_VERSIONS + 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 index 6666134916a8..687c67231830 100644 --- a/modules/nf-core/xeniumranger/resegment/meta.yml +++ b/modules/nf-core/xeniumranger/resegment/meta.yml @@ -1,7 +1,7 @@ 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. +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 @@ -26,47 +26,44 @@ input: e.g. [ id:'xenium_experiment' ] - xenium_bundle: type: directory - description: Path to the xenium output bundle generated by the Xenium Onboard - Analysis pipeline - - expansion_distance: - type: integer - description: Nuclei boundary expansion distance in µm. Only for use when nucleus - segmentation provided as input. Default-5 (accepted range 0 - 100) - - dapi_filter: - type: integer - description: Minimum intensity in photoelectrons to filter nuclei default-100 - range of values is 0 to 99th percentile of image stack or 1000, whichever is - larger - - boundary_stain: - type: string - description: Specify the name of the boundary stain to use or disable possible - options are default-ATP1A1/CD45/E-Cadherin or disable - - interior_stain: - type: string - description: Specify the name of the interior stain to use or disable possible - options are default-18S or disable + description: Path to the xenium output bundle generated by the Xenium + Onboard Analysis pipeline output: outs: - - meta: - type: file - description: Files containing the outputs of Cell Ranger, see official 10X - Genomics documentation for a complete list - pattern: "${meta.id}/outs/*" - ontologies: [] - - "**/outs/**": - type: file - description: Files containing the outputs of xenium ranger, see official 10X - Genomics documentation for a complete list of outputs - pattern: "${meta.id}/outs/*" - ontologies: [] + 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: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" - ontologies: - - edam: http://edamontology.org/format_3750 # YAML + - - "${task.process}": + type: string + description: The process the versions were collected from + - 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 index 85fc63e580c2..c24d897d72e2 100644 --- a/modules/nf-core/xeniumranger/resegment/tests/main.nf.test +++ b/modules/nf-core/xeniumranger/resegment/tests/main.nf.test @@ -3,7 +3,6 @@ nextflow_process { name "Test Process XENIUMRANGER_RESEGMENT" script "../main.nf" process "XENIUMRANGER_RESEGMENT" - config "./nextflow.config" tag "modules" tag "modules_nfcore" @@ -12,42 +11,45 @@ nextflow_process { tag "unzip" setup { - run("UNZIP") { - script "modules/nf-core/unzip/main.nf" - process { - """ - input[0] = [[], file('https://raw.githubusercontent.com/nf-core/test-datasets/spatialxe/Xenium_Prime_Mouse_Ileum_tiny_outs.zip', checkIfExists: true)] - """ - } + run("UNZIP") { + script "modules/nf-core/unzip/main.nf" + process { + """ + input[0] = [[], file('https://raw.githubusercontent.com/nf-core/test-datasets/spatialxe/Xenium_V1_Protein_Human_Kidney_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] = [] + input[0] = UNZIP.out.unzipped_archive.map { + _, bundle -> + [ + [id: "test_xeniumranger_resegment"], + bundle + ] + } """ } } then { + // xeniumranger doesn't generate deterministic outputs, so we can't use snapshot 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() } + { assert file(process.out.outs.get(0).get(1)).isDirectory() }, + { assert file(process.out.outs.get(0).get(1) + "/analysis_summary.html").exists() }, + { assert file(process.out.outs.get(0).get(1) + "/cells.csv.gz").exists() }, + { assert file(process.out.outs.get(0).get(1) + "/cells.parquet").exists() }, + { assert file(process.out.outs.get(0).get(1) + "/cells.zarr.zip").exists() }, + { assert file(process.out.outs.get(0).get(1) + "/transcripts.parquet").exists() }, + { assert file(process.out.outs.get(0).get(1) + "/transcripts.zarr.zip").exists() }, + { assert file(process.out.outs.get(0).get(1) + "/analysis.zarr.zip").exists() }, + { assert file(process.out.outs.get(0).get(1) + "/cell_feature_matrix.zarr.zip").exists()}, + { assert snapshot(process.out.versions_xeniumranger).match()}, ) } } @@ -57,13 +59,13 @@ nextflow_process { 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] = [] + input[0] = UNZIP.out.unzipped_archive.map { + _, bundle -> + [ + [id: "test_xeniumranger_resegment"], + bundle + ] + } """ } } diff --git a/modules/nf-core/xeniumranger/resegment/tests/main.nf.test.snap b/modules/nf-core/xeniumranger/resegment/tests/main.nf.test.snap index 16f94c14ebf2..cbeaa885f6dd 100644 --- a/modules/nf-core/xeniumranger/resegment/tests/main.nf.test.snap +++ b/modules/nf-core/xeniumranger/resegment/tests/main.nf.test.snap @@ -1,4 +1,20 @@ { + "xeniumranger resegment": { + "content": [ + [ + [ + "XENIUMRANGER_RESEGMENT", + "xeniumranger", + "4.0.1.1" + ] + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-12-10T21:21:39.73207635" + }, "xeniumranger resegment stub": { "content": [ { @@ -7,29 +23,41 @@ { "id": "test_xeniumranger_resegment" }, - "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + [ + "experiment.xenium:md5,d41d8cd98f00b204e9800998ecf8427e" + ] ] ], "1": [ - "versions.yml:md5,4671141281357e0ce26d9cb35fed23a8" + [ + "XENIUMRANGER_RESEGMENT", + "xeniumranger", + "4.0.1.1" + ] ], "outs": [ [ { "id": "test_xeniumranger_resegment" }, - "fake_file.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + [ + "experiment.xenium:md5,d41d8cd98f00b204e9800998ecf8427e" + ] ] ], - "versions": [ - "versions.yml:md5,4671141281357e0ce26d9cb35fed23a8" + "versions_xeniumranger": [ + [ + "XENIUMRANGER_RESEGMENT", + "xeniumranger", + "4.0.1.1" + ] ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-10-30T23:22:35.438329" + "timestamp": "2025-12-10T21:21:47.102042217" } -} \ 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 deleted file mode 100644 index e69de29bb2d1..000000000000