diff --git a/README.md b/README.md index 88f3247..7ad9a61 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,9 @@ ## Introduction -**nf-core/spatialxe** is a bioinformatics best-practice processing and quality control pipeline for Xenium data. **The pipeline is currently under developement and no completed yet!** +**nf-core/spatialxe** is a bioinformatics best-practice processing and quality control pipeline for Xenium data. **The pipeline is currently under developement and not completed yet!**. The current plan for the pipeline implementation is shown in the metromap below. Please note that the pipeline steps and methods might change as we move forward in the development cycle. + +![nf-core/spatialxe-metromap](docs/images/spatialxe-metromap.png) The pipeline is built using [Nextflow](https://www.nextflow.io), a workflow tool to run tasks across multiple compute infrastructures in a very portable manner. It uses Docker/Singularity containers making installation trivial and results highly reproducible. The [Nextflow DSL2](https://www.nextflow.io/docs/latest/dsl2.html) implementation of this pipeline uses one container per process which makes it much easier to maintain and update software dependencies. Where possible, these processes have been submitted to and installed from [nf-core/modules](https://github.com/nf-core/modules) in order to make them available to all nf-core pipelines, and to everyone within the Nextflow community! @@ -22,8 +24,6 @@ On release, automated continuous integration tests run the pipeline on a full-si ## Pipeline summary - - ## Quick Start 1. Install [`Nextflow`](https://www.nextflow.io/docs/latest/getstarted.html#installation) (`>=22.10.1`) diff --git a/docs/images/mqc_fastqc_adapter.png b/docs/images/mqc_fastqc_adapter.png deleted file mode 100755 index 361d0e4..0000000 Binary files a/docs/images/mqc_fastqc_adapter.png and /dev/null differ diff --git a/docs/images/mqc_fastqc_counts.png b/docs/images/mqc_fastqc_counts.png deleted file mode 100755 index cb39ebb..0000000 Binary files a/docs/images/mqc_fastqc_counts.png and /dev/null differ diff --git a/docs/images/mqc_fastqc_quality.png b/docs/images/mqc_fastqc_quality.png deleted file mode 100755 index a4b89bf..0000000 Binary files a/docs/images/mqc_fastqc_quality.png and /dev/null differ diff --git a/docs/images/spatialxe-metromap.png b/docs/images/spatialxe-metromap.png new file mode 100644 index 0000000..c48c05e Binary files /dev/null and b/docs/images/spatialxe-metromap.png differ diff --git a/docs/images/spatialxe-metromap.svg b/docs/images/spatialxe-metromap.svg new file mode 100644 index 0000000..c4f2ab6 --- /dev/null +++ b/docs/images/spatialxe-metromap.svg @@ -0,0 +1,4 @@ + + + +
Xenium bundle
gene panel
relabel
import-segmentation
Bundle redefinition Xenium Ranger
segger
xeniumranger resegment
Segmentation refinement
Coordinates/ mask
FICTURE
Proseg
Segmentation-free approach
BOMS
Cellpose
Image-based segmentation approach
Baysor
tiff
csv
JSON
morphology
transcripts
Xenium bundle (redefined)
spatialxe QC
spatialxe meta
SpatialData Domain
html
QC reports
JSON
Metadata
Coordinates/ mask
RO-crate output
SpatialData integration
Approach: image-based (Cellpose, BOMS)
Approach: image-based and segmentation-free (Baysor)
Approach: segmentation-free (Proseg, FICTURE)
Optional step
Outputs
Xenium onboard analysis (XOA)
Inputs
\ No newline at end of file diff --git a/modules.json b/modules.json index a8ab935..5654423 100644 --- a/modules.json +++ b/modules.json @@ -5,6 +5,11 @@ "https://github.com/nf-core/modules.git": { "modules": { "nf-core": { + "cellpose": { + "branch": "master", + "git_sha": "666652151335353eef2fcd58880bcef5bc2928e1", + "installed_by": ["modules"] + }, "custom/dumpsoftwareversions": { "branch": "master", "git_sha": "c8e35eb2055c099720a75538d1b8adb3fb5a464c", @@ -24,6 +29,11 @@ "branch": "master", "git_sha": "b5e1891a88491d8731b5e68e22bd907726caec4a", "installed_by": ["modules"] + }, + "xeniumranger/resegment": { + "branch": "master", + "git_sha": "b5e1891a88491d8731b5e68e22bd907726caec4a", + "installed_by": ["modules"] } } } diff --git a/modules/local/proseg/main.nf b/modules/local/proseg/main.nf new file mode 100644 index 0000000..d67abdd --- /dev/null +++ b/modules/local/proseg/main.nf @@ -0,0 +1,82 @@ +process PROSEG { + tag "$meta.id" + label 'process_high' + + container "nf-core/proseg:1.1.8" + + input: + tuple val(meta), path(transcripts) + + output: + tuple val(meta), path("cell-polygons.geojson.gz"), emit: cell_polygons_2d + path("expected-counts.csv.gz"), emit: expected_counts + path("cell-metadata.csv.gz"), emit: cell_metadata + path("transcript-metadata.csv.gz"), emit: transcript_metadata + path("gene-metadata.csv.gz"), emit: gene_metadata + path("rates.csv.gz"), emit: rates + path("cell-polygons-layers.geojson.gz"), emit: cell_polygons_layers + path("cell-hulls.geojson.gz"), emit: cell_hulls + 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 "PROSEG module does not support Conda. Please use Docker / Singularity / Podman instead." + } + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def platform = preset ? "${params.preset}" : "" + + // check for preset values + if (!(platform in ['xenium', 'cosmx', 'merscope'])) { + error "${platform} is an invalid platform (preset) type. Please specify xenium, cosmx, or merscope" + } + + """ + proseg \\ + --${preset} \\ + ${transcripts} \\ + --nthreads ${task.cpus} \\ + --output-expected-counts expected-counts.csv.gz \\ + --output-cell-metadata cell-metadata.csv.gz \\ + --output-transcript-metadata transcript-metadata.csv.gz \\ + --output-gene-metadata gene-metadata.csv.gz \\ + --output-rates rates.csv.gz \\ + --output-cell-polygons cell-polygons.geojson.gz \\ + --output-cell-polygon-layers cell-polygons-layers.geojson.gz \\ + --output-cell-hulls cell-hulls.geojson.gz \\ + ${args} + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + proseg: \$(proseg --version | sed 's/proseg //') + END_VERSIONS + """ + + 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." + } + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + touch expected-counts.csv.gz + touch cell-metadata.csv.gz + touch transcript-metadata.csv.gz + touch gene-metadata.csv.gz + touch rates.csv.gz + touch cell-polygons.geojson.gz + touch cell-polygons-layers.geojson.gz + touch cell-hulls.geojson.gz + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + proseg: \$(proseg --version | sed 's/proseg //') + END_VERSIONS + """ +} diff --git a/modules/local/proseg/meta.yml b/modules/local/proseg/meta.yml new file mode 100644 index 0000000..524b5ca --- /dev/null +++ b/modules/local/proseg/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/preprocess/main.nf b/modules/local/proseg/preprocess/main.nf new file mode 100644 index 0000000..dd979c4 --- /dev/null +++ b/modules/local/proseg/preprocess/main.nf @@ -0,0 +1,53 @@ +process PROSEG2BAYSOR { + tag "$meta.id" + label 'process_high' + + container "nf-core/proseg:1.1.8" + + input: + path(transcript_metadata) + path(cell_polygons) + + output: + path("xr-transcript-metadata.csv"), emit: xr_metadata + path("xr-cell-polygons.geojson"), emit: xr_polygons + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "PROSEG2BAYSOR (preprocess) module does not support Conda. Please use Docker / Singularity / Podman instead." + } + + """ + proseg-to-baysor \ + ${transcript_metadata} \ + ${cell_polygons} \ + --output-transcript-metadata xr-transcript-metadata.csv \ + --output-cell-polygons xr-cell-polygons.geojson + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + proseg: \$(proseg --version | sed 's/proseg //') + END_VERSIONS + + """ + + 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." + } + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + touch xr-transcript-metadata.csv + touch xr-cell-polygons.geojson + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + proseg: \$(proseg --version | sed 's/proseg //') + END_VERSIONS + """ +} + diff --git a/modules/local/proseg/tests/main.nf.test b/modules/local/proseg/tests/main.nf.test new file mode 100644 index 0000000..9b8f8c3 --- /dev/null +++ b/modules/local/proseg/tests/main.nf.test @@ -0,0 +1,77 @@ +nextflow_process { + + name "Test Process PROSEG" + script "../main.nf" + process "PROSEG" + + tag "modules" + tag "modules_nfcore" + tag "proseg" + tag "segmentation" + tag "cell_segmentation" + + + 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("proseg - transcripts.csv") { + + when { + process { + """ + input[0] = Channel.of([ + [id: "test_run_proseg"], + ]).combine(UNZIP.out.unzipped_archive.map { it[1] } + "/transcripts.csv") + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() }, + { assert file(process.out.expected_counts.get(0).get(1).find { file(it).name == 'expected-counts.csv.gz' }).exists() }, + { assert file(process.out.cell_metadata.get(0).get(1).find { file(it).name == 'cell-metadata.csv.gz' }).exists() }, + { assert file(process.out.transcript_metadata.get(0).get(1).find { file(it).name == 'transcript-metadata.csv.gz' }).exists() }, + { assert file(process.out.gene_metadata.get(0).get(1).find { file(it).name == 'gene-metadata.csv.gz' }).exists() }, + { assert file(process.out.rates.get(0).get(1).find { file(it).name == 'rates.csv.gz' }).exists() }, + { assert file(process.out.cell_polygons.get(0).get(1).find { file(it).name == 'cell-polygons.geojson.gz' }).exists() }, + { assert file(process.out.cell_polygons_layers.get(0).get(1).find { file(it).name == 'cell-polygons-layers.geojson.gz' }).exists() }, + { assert file(process.out.cell-hulls.get(0).get(1).find { file(it).name == 'cell-hulls.geojson.gz' }).exists() }, + ) + } + + } + + test("proseg stub") { + + options "-stub" + + when { + process { + """ + input[0] = Channel.of([ + [id: "test_run_proseg"], + ]).combine(UNZIP.out.unzipped_archive.map { it[1] } + "/transcripts.csv") + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/local/proseg/tests/tags.yml b/modules/local/proseg/tests/tags.yml new file mode 100644 index 0000000..8fec433 --- /dev/null +++ b/modules/local/proseg/tests/tags.yml @@ -0,0 +1,2 @@ +proseg: + - "modules/nf-core/proseg/**" diff --git a/modules/nf-core/cellpose/main.nf b/modules/nf-core/cellpose/main.nf new file mode 100644 index 0000000..f100904 --- /dev/null +++ b/modules/nf-core/cellpose/main.nf @@ -0,0 +1,58 @@ +process CELLPOSE { + tag "$meta.id" + label 'process_medium' + + container "docker.io/biocontainers/cellpose:3.0.1_cv1" + + input: + tuple val(meta), path(image) + path(model) + + output: + tuple val(meta), path("*masks.tif") , emit: mask + tuple val(meta), path("*flows.tif") , emit: flows, optional: true + 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 "I did not manage to create a cellpose module in Conda that works in all OSes. Please use Docker / Singularity / Podman instead." + } + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def model_command = model ? "--pretrained_model $model" : "" + def VERSION = '3.0.1' + """ + cellpose \\ + --image_path $image \\ + --save_tif \\ + $model_command \\ + $args + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + cellpose: $VERSION + END_VERSIONS + """ + stub: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "I did not manage to create a cellpose module in Conda that works in all OSes. Please use Docker / Singularity / Podman instead." + } + def prefix = task.ext.prefix ?: "${meta.id}" + def VERSION = "3.0.1" // WARN: Version information not provided by tool on CLI. Please update this string when bumping container versions. + def name = image.name + def base = name.lastIndexOf('.') != -1 ? name[0..name.lastIndexOf('.') - 1] : name + """ + touch ${base}_cp_masks.tif + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + cellpose: $VERSION + END_VERSIONS + """ + +} diff --git a/modules/nf-core/cellpose/meta.yml b/modules/nf-core/cellpose/meta.yml new file mode 100644 index 0000000..5397944 --- /dev/null +++ b/modules/nf-core/cellpose/meta.yml @@ -0,0 +1,63 @@ +name: "cellpose" +description: cellpose segments cells in images +keywords: + - segmentation + - image + - cellpose +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 for ready for segmentation + pattern: "*.{tif,tiff}" + - - model: + type: file + description: Optional input file. Cellpose 2 model trained by user using human-in-the-loop + approach. +output: + - mask: + - meta: + type: map + description: | + Groovy Map containing sample information + [sample id] + - "*masks.tif": + type: file + description: labelled mask output from cellpose in tif format + pattern: "*.{tif, tiff}" + - flows: + - meta: + type: map + description: | + Groovy Map containing sample information + [sample id] + - "*flows.tif": + type: file + description: cell flow output from cellpose + pattern: "*.{tif}" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@josenimo" + - "@FloWuenne" +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 0000000..6a7688b --- /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 0000000..03b4dbf --- /dev/null +++ b/modules/nf-core/cellpose/tests/main.nf.test.snap @@ -0,0 +1,87 @@ +{ + "flows": { + "content": [ + [ + [ + { + "id": "test" + }, + "cycif_tonsil_registered.ome_flows.tif:md5,de79a792d4bebd2f9753ceb47a0de5f7" + ] + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-03-18T14:22:16.855256249" + }, + "versions": { + "content": [ + [ + "versions.yml:md5,ce42208b574084f390cf58b4c19b5717" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-03-18T14:22:16.875087557" + }, + "cellpose - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "cycif_tonsil_registered.ome_cp_masks.tif:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + + ], + "2": [ + "versions.yml:md5,ce42208b574084f390cf58b4c19b5717" + ], + "flows": [ + + ], + "mask": [ + [ + { + "id": "test" + }, + "cycif_tonsil_registered.ome_cp_masks.tif:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,ce42208b574084f390cf58b4c19b5717" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-03-18T14:22:39.339792992" + }, + "mask": { + "content": [ + [ + [ + { + "id": "test" + }, + "cycif_tonsil_registered.ome_cp_masks.tif:md5,001ad312413f18bc2615741bd3ad12cf" + ] + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-03-18T14:22:16.8369758" + } +} \ 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 0000000..773c97b --- /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/cellpose/tests/tags.yml b/modules/nf-core/cellpose/tests/tags.yml new file mode 100644 index 0000000..1280d1f --- /dev/null +++ b/modules/nf-core/cellpose/tests/tags.yml @@ -0,0 +1,2 @@ +cellpose: + - "modules/nf-core/cellpose/**" diff --git a/modules/nf-core/xeniumranger/resegment/main.nf b/modules/nf-core/xeniumranger/resegment/main.nf new file mode 100644 index 0000000..5d28fa6 --- /dev/null +++ b/modules/nf-core/xeniumranger/resegment/main.nf @@ -0,0 +1,70 @@ +process XENIUMRANGER_RESEGMENT { + tag "$meta.id" + label 'process_high' + + container "nf-core/xeniumranger:3.0.1" + + input: + tuple val(meta), path(xenium_bundle) + val(expansion_distance) + val(dapi_filter) + val(boundary_stain) + val(interior_stain) + + output: + tuple val(meta), path("**/outs/**"), emit: outs + 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 "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": "" + + """ + xeniumranger resegment \\ + --id="${prefix}" \\ + --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 + """ + + 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}" + """ + 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 + """ +} diff --git a/modules/nf-core/xeniumranger/resegment/meta.yml b/modules/nf-core/xeniumranger/resegment/meta.yml new file mode 100644 index 0000000..af0e888 --- /dev/null +++ b/modules/nf-core/xeniumranger/resegment/meta.yml @@ -0,0 +1,58 @@ +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 + - - 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 +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/*" + - "**/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/*" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" 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 0000000..861c241 --- /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/spatialxe/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 0000000..16f94c1 --- /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 0000000..e69de29 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 0000000..99f47c8 --- /dev/null +++ b/modules/nf-core/xeniumranger/resegment/tests/tags.yml @@ -0,0 +1,2 @@ +xeniumranger/resegment: + - "modules/nf-core/xeniumranger/resegment/**"