diff --git a/conf/modules.config b/conf/modules.config index 254a9d1a..d9228b4f 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -138,7 +138,25 @@ process { path: { "${params.outdir}/cellpose" }, mode: params.publish_dir_mode, ] - ext.args = { "--pretrained_model nuclei --diameter 9 --channel_axis 0 --save_flows" } + ext.args = { "--diameter 9 --channel_axis 0 --save_flows" } + } + + // with new version of cellpose you can do --output_name cell_masks + withName: CELLPOSE_CELLS { + publishDir = [ + path: { "${params.outdir}/cellpose_cells" }, + mode: params.publish_dir_mode, + ] + ext.args = { "--diameter 9 --channel_axis 0 --save_flows" } + } + + // with new version of cellpose you can do --output_name nucleus_masks + withName: CELLPOSE_NUCLEI { + publishDir = [ + path: { "${params.outdir}/cellpose_nuclei" }, + mode: params.publish_dir_mode, + ] + ext.args = { "--diameter 9 --channel_axis 0 --save_flows" } } } diff --git a/modules/local/spatialdata/write/main.nf b/modules/local/spatialdata/write/main.nf index 53d8cbb6..7ebdb98a 100644 --- a/modules/local/spatialdata/write/main.nf +++ b/modules/local/spatialdata/write/main.nf @@ -7,6 +7,7 @@ process SPATIALDATA_WRITE { input: tuple val(meta), path(bundle, stageAs: "*") val(outputfolder) + val(segmented_object) output: tuple val(meta), path("${outputfolder}"), emit: spatialdata diff --git a/modules/local/spatialdata/write/templates/write.py b/modules/local/spatialdata/write/templates/write.py index cd34f24f..908ede73 100755 --- a/modules/local/spatialdata/write/templates/write.py +++ b/modules/local/spatialdata/write/templates/write.py @@ -12,14 +12,34 @@ def main(): input_path = "${bundle}" output_path = "." outputfolder = "${outputfolder}" + segmented_object = "${segmented_object}" + + cells_boundaries=False + nucleus_boundaries=False + cells_labels=False + nucleus_labels=False + + if ( segmented_object == 'cells' ): + cells_boundaries=True + cells_labels=True + if ( segmented_object == 'nuclei' ): + nucleus_boundaries=True + nucleus_labels=True + if ( segmented_object == 'cells_and_nuclei' ): + cells_boundaries=True + nucleus_boundaries=True + cells_labels=True + nucleus_labels=True format = "${params.format}" - if ( format == "xenium" ): sd_xenium_obj = xenium( input_path, cells_as_circles=True, - nucleus_boundaries=True, + cells_boundaries=cells_boundaries, + nucleus_boundaries=nucleus_boundaries, + cells_labels=cells_labels, + nucleus_labels=nucleus_labels, transcripts=True, morphology_mip=True, morphology_focus=True, diff --git a/modules/nf-core/cellpose/main.nf b/modules/nf-core/cellpose/main.nf index 78dc797b..9baa2ae2 100644 --- a/modules/nf-core/cellpose/main.nf +++ b/modules/nf-core/cellpose/main.nf @@ -6,7 +6,8 @@ process CELLPOSE { input: tuple val(meta), path(image) - path(model) + val(model) + val(maskname) output: tuple val(meta), path("*masks.tif") , emit: mask @@ -33,6 +34,7 @@ process CELLPOSE { --save_tif \\ $model_command \\ $args + mv *masks.tif morphology.ome_${maskname}_masks.tif cat <<-END_VERSIONS > versions.yml "${task.process}": diff --git a/subworkflows/local/cellpose_resolift_morphology_ome_tif/main.nf b/subworkflows/local/cellpose_resolift_morphology_ome_tif/main.nf index dd889264..62fb908d 100644 --- a/subworkflows/local/cellpose_resolift_morphology_ome_tif/main.nf +++ b/subworkflows/local/cellpose_resolift_morphology_ome_tif/main.nf @@ -2,7 +2,8 @@ // Run cellpose on the morphology tiff // -include { CELLPOSE } from '../../../modules/nf-core/cellpose/main' +include { CELLPOSE as CELLPOSE_CELLS } from '../../../modules/nf-core/cellpose/main' +include { CELLPOSE as CELLPOSE_NUCLEI } from '../../../modules/nf-core/cellpose/main' include { RESOLIFT } from '../../../modules/local/resolift/main' include { XENIUMRANGER_IMPORT_SEGMENTATION } from '../../../modules/nf-core/xeniumranger/import-segmentation/main' @@ -26,24 +27,39 @@ workflow CELLPOSE_RESOLIFT_MORPHOLOGY_OME_TIF { ch_versions = ch_versions.mix( RESOLIFT.out.versions ) // run cellpose on the enhanced tiff - CELLPOSE ( RESOLIFT.out.enhanced_tiff, cellpose_model ) + CELLPOSE_CELLS ( RESOLIFT.out.enhanced_tiff, cellpose_model, 'cells' ) ch_versions = ch_versions.mix( CELLPOSE.out.versions ) + CELLPOSE_NUCLEI ( RESOLIFT.out.enhanced_tiff, 'nuclei', 'nuclei' ) + ch_versions = ch_versions.mix( CELLPOSE_NUCLEI.out.versions ) + } else { // run cellpose on the original tiff - CELLPOSE ( ch_morphology_image, cellpose_model ) - ch_versions = ch_versions.mix( CELLPOSE.out.versions ) + CELLPOSE_CELLS ( ch_morphology_image, cellpose_model, 'cells' ) + ch_versions = ch_versions.mix( CELLPOSE_CELLS.out.versions ) + + CELLPOSE_NUCLEI ( ch_morphology_image, 'nuclei', 'nuclei' ) + ch_versions = ch_versions.mix( CELLPOSE_NUCLEI.out.versions ) } // get cellpose segmentation results - cellpose_cells = CELLPOSE.out.cells.map { + cellpose_cells_cells = CELLPOSE_CELLS.out.cells.map { + _meta, cells -> return [ cells ] + } + cellpose_cells_mask = CELLPOSE_CELLS.out.mask.map { + _meta, mask -> return [ mask ] + } + _cellpose_cells_flows = CELLPOSE_CELLS.out.flows.map { + _meta, flows -> return [ flows ] + } + cellpose_nuclei_cells = CELLPOSE_NUCLEI.out.cells.map { _meta, cells -> return [ cells ] } - cellpose_mask = CELLPOSE.out.mask.map { + cellpose_nuclei_mask = CELLPOSE_NUCLEI.out.mask.map { _meta, mask -> return [ mask ] } - _cellpose_flows = CELLPOSE.out.flows.map { + _cellpose_nuclei_flows = CELLPOSE_NUCLEI.out.flows.map { _meta, flows -> return [ flows ] } @@ -53,7 +69,7 @@ workflow CELLPOSE_RESOLIFT_MORPHOLOGY_OME_TIF { XENIUMRANGER_IMPORT_SEGMENTATION ( ch_bundle_path, [], - cellpose_mask, + cellpose_nuclei_mask, [], [], [], @@ -66,8 +82,8 @@ workflow CELLPOSE_RESOLIFT_MORPHOLOGY_OME_TIF { XENIUMRANGER_IMPORT_SEGMENTATION ( ch_bundle_path, [], - cellpose_mask, - cellpose_cells, + cellpose_nuclei_mask, + cellpose_cells_mask, [], [], "" @@ -77,9 +93,12 @@ workflow CELLPOSE_RESOLIFT_MORPHOLOGY_OME_TIF { emit: - mask = CELLPOSE.out.mask // channel: [ val(meta), [ "*masks.tif" ] ] - flows = CELLPOSE.out.flows // channel: [ val(meta), [ "*flows.tif" ] ] - cells = CELLPOSE.out.cells // channel: [ val(meta), [ "*seg.npy" ] ] + cells_mask = CELLPOSE_CELLS.out.mask // channel: [ val(meta), [ "*masks.tif" ] ] + cells_flows = CELLPOSE_CELLS.out.flows // channel: [ val(meta), [ "*flows.tif" ] ] + cells_cells = CELLPOSE_CELLS.out.cells // channel: [ val(meta), [ "*seg.npy" ] ] + nuclei_mask = CELLPOSE_NUCLEI.out.mask // channel: [ val(meta), [ "*masks.tif" ] ] + nuclei_flows = CELLPOSE_NUCLEI.out.flows // channel: [ val(meta), [ "*flows.tif" ] ] + nuclei_cells = CELLPOSE_NUCLEI.out.cells // channel: [ val(meta), [ "*seg.npy" ] ] redefined_bundle = XENIUMRANGER_IMPORT_SEGMENTATION.out.bundle // channel: [ val(meta), ["redefined-xenium-bundle"] ] diff --git a/subworkflows/local/spatialdata_write_meta_merge/main.nf b/subworkflows/local/spatialdata_write_meta_merge/main.nf index 36635052..e32b26b2 100644 --- a/subworkflows/local/spatialdata_write_meta_merge/main.nf +++ b/subworkflows/local/spatialdata_write_meta_merge/main.nf @@ -10,8 +10,9 @@ include { SPATIALDATA_META } from '../../ workflow SPATIALDATA_WRITE_META_MERGE { take: - ch_bundle_path // channel: [ val(meta), [ "path-to-xenium-bundle" ] ] - ch_redefined_bundle // channel: [ val(meta), [ "redefined-xenium-bundle" ] ] + ch_bundle_path // channel: [ val(meta), [ "path-to-xenium-bundle" ] ] + ch_redefined_bundle // channel: [ val(meta), [ "redefined-xenium-bundle" ] ] + ch_segmented_object // can be either cells,nuclei,cells_and_nuclei main: @@ -20,7 +21,8 @@ workflow SPATIALDATA_WRITE_META_MERGE { // write spatialdata object from the raw xenium bundle SPATIALDATA_WRITE_RAW_BUNDLE ( ch_bundle_path, - 'spatialdata_raw' + 'spatialdata_raw', + ch_segmented_object ) ch_versions = ch_versions.mix ( SPATIALDATA_WRITE_RAW_BUNDLE.out.versions ) @@ -28,7 +30,8 @@ workflow SPATIALDATA_WRITE_META_MERGE { // write spatialdata object after running IMP_SEG SPATIALDATA_WRITE_REDEFINED_BUNDLE ( ch_redefined_bundle, - 'spatialdata_redefined' + 'spatialdata_redefined', + ch_segmented_object ) ch_versions = ch_versions.mix ( SPATIALDATA_WRITE_REDEFINED_BUNDLE.out.versions ) diff --git a/subworkflows/local/utils_nfcore_spatialxe_pipeline/main.nf b/subworkflows/local/utils_nfcore_spatialxe_pipeline/main.nf index e9207233..d04d0b53 100644 --- a/subworkflows/local/utils_nfcore_spatialxe_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_spatialxe_pipeline/main.nf @@ -195,13 +195,6 @@ def validateInputParameters() { log.warn "⚠️ Use --nucleus_segmentation_only to enable nucleus segmentation to redefine xenium bundle with import-segmentation module." } - if ( params.mode == 'image' && params.method == 'baysor' ) { - if ( !params.method_mask ) { - log.error "❌ Error: Missing path to segmentation mask. Image-based segmentation with the `baysor` method requires a segmentation mask with the `--segmentation_mask` option." - exit 1 - } - } - } // diff --git a/workflows/spatialxe.nf b/workflows/spatialxe.nf index 3bffb276..e6a86dbd 100644 --- a/workflows/spatialxe.nf +++ b/workflows/spatialxe.nf @@ -241,12 +241,21 @@ workflow SPATIALXE { // run baysor run with morphology_ome.tif if ( params.method == 'baysor' ) { - BAYSOR_RUN_PRIOR_SEGMENTATION_MASK ( - ch_bundle_path, - ch_transcripts_parquet, - ch_segmentation_mask, - ch_config - ) + if ( params.segmentation_mask ) { + BAYSOR_RUN_PRIOR_SEGMENTATION_MASK ( + ch_bundle_path, + ch_transcripts_parquet, + ch_segmentation_mask, + ch_config + ) + } else { + BAYSOR_RUN_PRIOR_SEGMENTATION_MASK ( + ch_bundle_path, + ch_transcripts_parquet, + [], + ch_config + ) + } ch_redefined_bundle = BAYSOR_RUN_PRIOR_SEGMENTATION_MASK.out.redefined_bundle } @@ -343,9 +352,12 @@ workflow SPATIALXE { // run spatialdata modules to generate sd objects in image or coordinate mode if ( params.mode == 'image' || params.mode == 'coordinate' ) { + ch_segmented_object = Channel.value('cells_and_nuclei') + SPATIALDATA_WRITE_META_MERGE ( ch_bundle_path, - ch_redefined_bundle + ch_redefined_bundle, + ch_segmented_object ) }