diff --git a/docs/images/spatialxe-metromap.png b/docs/images/spatialxe-metromap.png index b17bb1b7..2b10c41c 100644 Binary files a/docs/images/spatialxe-metromap.png and b/docs/images/spatialxe-metromap.png differ diff --git a/modules/local/baysor/preprocess/main.nf b/modules/local/baysor/preprocess/main.nf new file mode 100644 index 00000000..1a240af7 --- /dev/null +++ b/modules/local/baysor/preprocess/main.nf @@ -0,0 +1,37 @@ +process BAYSOR_PREPROCESS_TRANSCRIPTS { + tag "$meta.id" + label 'process_low' + + container "ghcr.io/scverse/spatialdata:spatialdata0.3.0_spatialdata-io0.1.7_spatialdata-plot0.2.9" + + input: + tuple val(meta), path(transcripts) + val(min_qv) + val(max_x) + val(min_x) + val(max_y) + val(min_y) + + output: + tuple val(meta), path("*.parquet"), emit: transcripts_parquet + path("versions.yml") , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "PREPROCESS_TRANSCRIPTS module does not support Conda. Please use Docker / Singularity / Podman instead." + } + + template 'preprocess_transcripts.py' + + stub: + """ + touch ${transcripts}.parquet + cat <<-END_VERSIONS > versions.yml + "${task.process}": + baysor_preprocess_transcripts: "1.0.0" + END_VERSIONS + """ +} diff --git a/modules/local/baysor/preprocess/meta.yml b/modules/local/baysor/preprocess/meta.yml new file mode 100644 index 00000000..a7774a3b --- /dev/null +++ b/modules/local/baysor/preprocess/meta.yml @@ -0,0 +1,70 @@ +name: "baysor_preprocess" +description: Filter transcript.parquet fiel based on the specified thresholds +keywords: + - baysor + - transcripts + - filter_transcripts +tools: + - "baysor": + description: "Baysor is a tool that segments cells using spatial gene expression maps. Optionally, segmentation masks can be given as additional input." + homepage: "https://kharchenkolab.github.io/Baysor/dev/" + documentation: "https://kharchenkolab.github.io/Baysor/dev/" + tool_dev_url: "https://github.com/kharchenkolab/Baysor" + doi: "https://doi.org/10.1038/s41587-021-01044-w" + licence: ["MIT license"] + identifier: + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - transcripts: + type: file + description: transcripts.parquet file from the xenium bundle + pattern: "*.parquet" + + - min_qv: + type: float + description: minimum Q-Score to pass filtering (default - 20.0) + - max_x: + type: float + description: Only keep transcripts whose x-coordinate is less than specified limit + if no limit is specified, the default value will retain all + transcripts since Xenium slide is <24000 microns in x and y (default - 24000.0) + - min_x: + type: float + description: only keep transcripts whose x-coordinate is greater than specified limit + if no limit is specified, the default minimum value will be 0.0 + - max_y: + type: float + description: only keep transcripts whose y-coordinate is less than specified limit + if no limit is specified, the default value will retain all + transcripts since Xenium slide is <24000 microns in x and y (default - 24000.0) + - min_y: + type: float + description: only keep transcripts whose y-coordinate is greater than specified limit + if no limit is specified, the default minimum value will be 0.0 + +output: + - - transcripts_parquet: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "*.parquet": + type: file + description: filtered transcripts.parquet + pattern: "filtered_transcripts.parquet" + + - - versions: + type: file + description: File containing software versions + pattern: "versions.yml" + +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/modules/local/baysor/preprocess/templates/preprocess_transcripts.py b/modules/local/baysor/preprocess/templates/preprocess_transcripts.py new file mode 100644 index 00000000..9820e16d --- /dev/null +++ b/modules/local/baysor/preprocess/templates/preprocess_transcripts.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 + +import pandas as pd + + +def filter_transcripts ( + transcripts: str, + min_qv: float = 20.0, + min_x: float = 0.0, + max_x: float = 24000.0, + min_y: float = 0.0, + max_y: float = 24000.0 +) -> None: + """ + Filter transcripts based on the specified thresholds + + Args: + transcripts - path to transcripts parquet + ----------------------------------- filters -------------------------------------------- + min_qv - minimum Q-Score to pass filtering (default: 20.0) + min_x - only keep transcripts whose x-coordinate is greater than specified limit + if no limit is specified, the default minimum value will be 0.0 + max_x - only keep transcripts whose x-coordinate is less than specified limit + if no limit is specified, the default value will retain all + transcripts since Xenium slide is <24000 microns in x and y (default: 24000.0) + min_y - only keep transcripts whose y-coordinate is greater than specified limit + if no limit is specified, the default minimum value will be 0.0 + max_y - only keep transcripts whose y-coordinate is less than specified limit + if no limit is specified, the default value will retain all + transcripts since Xenium slide is <24000 microns in x and y (default: 24000.0) + """ + df = pd.read_parquet(transcripts, engine = 'pyarrow') + + # filter transcripts df with thresholds, ignore negative controls + filtered_df = df[(df["qv"] >= min_qv) & + (df["x_location"] >= min_x) & + (df["x_location"] <= max_x) & + (df["y_location"] >= min_y) & + (df["y_location"] <= max_y) & + (~df["feature_name"].str.startswith("NegControlProbe_")) & + (~df["feature_name"].str.startswith("antisense_")) & + (~df["feature_name"].str.startswith("NegControlCodeword_")) & + (~df["feature_name"].str.startswith("BLANK_"))] + + # change cell_id of cell-free transcripts from -1 to 0 + neg_cell_row = filtered_df["cell_id"] == -1 + filtered_df.loc[neg_cell_row,"cell_id"] = 0 + + # Output filtered transcripts to parquet + filtered_df.to_parquet( + '_'.join(["X"+str(min_x)+"-"+str(max_x), "Y"+str(min_y)+"-"+str(max_y), "filtered_transcripts.parquet"]), + index=False + ) + + return None + + +def generate_version_yml() -> None: + with open("versions.yml", "w") as yml: + yml.write('"${task.process}":\\n') + yml.write("Baysor-Preprocess Transcripts: 1.0.0'\\n") + + return None + + +if __name__ == "__main__": + + transcripts: str = "${transcripts}" + + filter_transcripts ( + transcripts=transcripts, + ) + + generate_version_yml() diff --git a/modules/local/spatialconverter/parquet_to_csv/templates/parquet_to_csv.py b/modules/local/spatialconverter/parquet_to_csv/templates/parquet_to_csv.py index 3d4acbb1..f13ba23d 100755 --- a/modules/local/spatialconverter/parquet_to_csv/templates/parquet_to_csv.py +++ b/modules/local/spatialconverter/parquet_to_csv/templates/parquet_to_csv.py @@ -32,7 +32,7 @@ def convert_parquet ( extension=extension ) - #Output version information + #Output versions.yml with open("versions.yml", "w") as f: f.write('"${task.process}":\\n') - f.write(f'spatialconverter: "v0.0.1"\\n') + f.write('spatialconverter: "v0.0.1"\\n') diff --git a/modules/local/spatialdata/write/templates/write.py b/modules/local/spatialdata/write/templates/write.py index 908ede73..edc10017 100755 --- a/modules/local/spatialdata/write/templates/write.py +++ b/modules/local/spatialdata/write/templates/write.py @@ -14,6 +14,7 @@ def main(): outputfolder = "${outputfolder}" segmented_object = "${segmented_object}" + cells_as_circles=True cells_boundaries=False nucleus_boundaries=False cells_labels=False @@ -22,20 +23,22 @@ def main(): if ( segmented_object == 'cells' ): cells_boundaries=True cells_labels=True - if ( segmented_object == 'nuclei' ): + elif ( segmented_object == 'nuclei' ): nucleus_boundaries=True nucleus_labels=True - if ( segmented_object == 'cells_and_nuclei' ): + elif ( segmented_object == 'cells_and_nuclei' ): cells_boundaries=True nucleus_boundaries=True cells_labels=True nucleus_labels=True + else: + cells_as_circles=False format = "${params.format}" if ( format == "xenium" ): sd_xenium_obj = xenium( input_path, - cells_as_circles=True, + cells_as_circles=cells_as_circles, cells_boundaries=cells_boundaries, nucleus_boundaries=nucleus_boundaries, cells_labels=cells_labels, diff --git a/modules/local/utility/segger2xr/main.nf b/modules/local/utility/segger2xr/main.nf new file mode 100644 index 00000000..cba9173d --- /dev/null +++ b/modules/local/utility/segger2xr/main.nf @@ -0,0 +1,32 @@ +process SEGGER2XR { + tag "$meta.id" + label 'process_low' + + container "ghcr.io/scverse/spatialdata:spatialdata0.3.0_spatialdata-io0.1.7_spatialdata-plot0.2.9" + + input: + tuple val(meta), path(transcripts) + + output: + tuple val(meta), path("transcripts.parquet"), emit: transcripts_parquet + path("versions.yml") , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "SEGGER2XR module does not support Conda. Please use Docker / Singularity / Podman instead." + } + + template 'segger2xr.py' + + stub: + """ + touch ${transcripts}.parquet + cat <<-END_VERSIONS > versions.yml + "${task.process}": + segger2xr: "${task.version}" + END_VERSIONS + """ +} diff --git a/modules/local/utility/segger2xr/templates/segger2xr.py b/modules/local/utility/segger2xr/templates/segger2xr.py new file mode 100644 index 00000000..cffd9f07 --- /dev/null +++ b/modules/local/utility/segger2xr/templates/segger2xr.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 + +import pandas as pd +from pathlib import Path +from typing import List + +# Expected columns in transcripts.parquet +REQUIRED_COLUMNS: List[str] = [ + "transcript_id", + "cell_id", + "overlaps_nucleus", + "feature_name", + "x_location", + "y_location", + "z_location", + "qv", + "segger_id" +] + +def refine_transcripts(parquet_path: str) -> pd.DataFrame: + """ + Replace the cell_id column with segger_id + """ + parquet_file = Path(parquet_path) + if not parquet_file.exists(): + raise FileNotFoundError(f"File not found: {parquet_path}") + + # Read parquet file + df = pd.read_parquet(parquet_file, engine="pyarrow") + + # Validate required columns + missing_cols = [col for col in REQUIRED_COLUMNS if col not in df.columns] + if missing_cols: + raise ValueError(f"Missing required columns: {missing_cols}") + + # get 'cell_id' column index + cell_id_index = df.columns.get_loc("cell_id") + + # Drop 'cell_id' and insert 'segger_id' at the same position + df = df.drop(columns=["cell_id"]) + segger_series = df.pop("segger_id") + df.insert(cell_id_index, "cell_id", segger_series) + + return df + + +def main(input_file: str) -> None: + transcripts = refine_transcripts(input_file) + transcripts.to_parquet("transcripts.parquet", engine="pyarrow") + + +if __name__ == "__main__": + + transcripts = "${transcripts}" + + main(input_file=transcripts) + + #Output versions.yml + with open("versions.yml", "w") as f: + f.write('"${task.process}":\\n') + f.write('segger2xr: "v0.0.1"\\n') diff --git a/modules/local/utility/split_transcripts/main.nf b/modules/local/utility/split_transcripts/main.nf new file mode 100644 index 00000000..f24773d4 --- /dev/null +++ b/modules/local/utility/split_transcripts/main.nf @@ -0,0 +1,34 @@ +process SPLIT_TRANSCRIPTS { + tag "$meta.id" + label 'process_low' + + container "ghcr.io/scverse/spatialdata:spatialdata0.3.0_spatialdata-io0.1.7_spatialdata-plot0.2.9" + + input: + tuple val(meta), path(transcripts) + val(x_bins) + val(y_bins) + + output: + tuple val(meta), path("splits.csv"), emit: splits_csv + path("versions.yml") , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "SPLIT_TRANSCRIPTS module does not support Conda. Please use Docker / Singularity / Podman instead." + } + + template 'split_transcripts.py' + + stub: + """ + touch ${transcripts}.parquet + cat <<-END_VERSIONS > versions.yml + "${task.process}": + baysor_split_parquet: "1.0.0" + END_VERSIONS + """ +} diff --git a/modules/local/utility/split_transcripts/meta.yml b/modules/local/utility/split_transcripts/meta.yml new file mode 100644 index 00000000..8317c052 --- /dev/null +++ b/modules/local/utility/split_transcripts/meta.yml @@ -0,0 +1,54 @@ +name: "split_transcripts" +description: Split transcripts along x & y axes +keywords: + - baysor + - transcripts + - split_transcripts +tools: + - "baysor": + description: "Utility package to split transcripts for Baysor. Baysor is a tool that segments cells using spatial gene expression maps. Optionally, segmentation masks can be given as additional input." + homepage: "https://kharchenkolab.github.io/Baysor/dev/" + documentation: "https://kharchenkolab.github.io/Baysor/dev/" + tool_dev_url: "https://github.com/kharchenkolab/Baysor" + doi: "https://doi.org/10.1038/s41587-021-01044-w" + licence: ["MIT license"] + identifier: + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - transcripts: + type: file + description: transcripts.parquet file from the xenium bundle + pattern: "*.parquet" + - x_bins: + type: integer + description: number of slices along the x axis (default - 10) + - y_bins: + type: integer + description: number of slices along the y axis (default - 10) + +output: + - - splits_csv: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "*.csv": + type: file + description: filtered transcripts.parquet + pattern: "splits.csv" + + - - versions: + type: file + description: File containing software versions + pattern: "versions.yml" + +authors: + - "@khersameesh24" +maintainers: + - "@khersameesh24" diff --git a/modules/local/utility/split_transcripts/templates/split_transcripts.py b/modules/local/utility/split_transcripts/templates/split_transcripts.py new file mode 100644 index 00000000..9392b16f --- /dev/null +++ b/modules/local/utility/split_transcripts/templates/split_transcripts.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 + +import pandas as pd +from typing import List + + +def compute_quantile_ranges(df: pd.DataFrame, col: str, n_bins: int) -> List: + """ + Compute the bin edges for `df[col]` such that each of the n_bins + has ~equal count of points. Returns a list of (min, max) tuples. + """ + _, bins = pd.qcut(df[col], q=n_bins, retbins=True, duplicates='drop') + + ranges = [(bins[i], bins[i+1]) for i in range(len(bins)-1)] + + return ranges + + +def make_tiles(df: pd.DataFrame, x_bins: int, y_bins: int) -> pd.DataFrame: + """ + Produce a DataFrame with one row per tile: + tile_id, x_min, x_max, y_min, y_max + """ + x_ranges = compute_quantile_ranges(df, 'x_location', x_bins) + y_ranges = compute_quantile_ranges(df, 'y_location', y_bins) + + tiles = [] + for ix, (x_min, x_max) in enumerate(x_ranges, start=1): + for iy, (y_min, y_max) in enumerate(y_ranges, start=1): + tiles.append({ + 'tile_id': f'{ix}_{iy}', + 'x_min': x_min, + 'x_max': x_max, + 'y_min': y_min, + 'y_max': y_max + }) + + return pd.DataFrame(tiles) + + +def generate_version_yml() -> None: + with open("versions.yml", "w") as yml: + yml.write('"${task.process}":\\n') + yml.write("Baysor-Split Transcripts: 1.0.0'\\n") + + return None + + +def main( + transcripts: str, + x_bins: int = 10, + y_bins: int = 10 +) -> None: + """ + Generate splits + """ + # read parquet file + df = pd.read_parquet(transcripts, engine='fastparquet') + + # compute tiles + tiles_df = make_tiles(df, x_bins, y_bins) + + # save parquet file + tiles_df.to_csv("splits.csv", index=False) + + # generate version yml + generate_version_yml() + + return None + + +if __name__ == "__main__": + + transcripts: str = "${transcripts}" + x_bins: int = "${x_bins}" + y_bins: int = "${y_bins}" + + main(transcripts=transcripts, x_bins=x_bins, y_bins=y_bins) diff --git a/modules/nf-core/cellpose/cellpose.diff b/modules/nf-core/cellpose/cellpose.diff index 913da4e5..eecf7e9b 100644 --- a/modules/nf-core/cellpose/cellpose.diff +++ b/modules/nf-core/cellpose/cellpose.diff @@ -3,7 +3,14 @@ Changes in component 'nf-core/cellpose' Changes in 'cellpose/main.nf': --- modules/nf-core/cellpose/main.nf +++ modules/nf-core/cellpose/main.nf -@@ -11,6 +11,7 @@ +@@ -6,11 +6,13 @@ + + input: + tuple val(meta), path(image) +- path(model) ++ val(model) ++ val(maskname) + output: tuple val(meta), path("*masks.tif") , emit: mask tuple val(meta), path("*flows.tif") , emit: flows, optional: true @@ -11,8 +18,16 @@ Changes in 'cellpose/main.nf': path "versions.yml" , emit: versions when: +@@ -32,6 +34,7 @@ + --save_tif \\ + $model_command \\ + $args ++ mv *masks.tif morphology.ome_${maskname}_masks.tif + + cat <<-END_VERSIONS > versions.yml + "${task.process}": -'modules/nf-core/cellpose/tests/main.nf.test.snap' is unchanged 'modules/nf-core/cellpose/tests/main.nf.test' is unchanged +'modules/nf-core/cellpose/tests/main.nf.test.snap' is unchanged 'modules/nf-core/cellpose/tests/nextflow_wflows.config' is unchanged ************************************************************ diff --git a/nextflow.config b/nextflow.config index 173c9b74..dee7c335 100644 --- a/nextflow.config +++ b/nextflow.config @@ -22,7 +22,8 @@ params { // execution specific sharpen_tiff = false // wether to sharpen the morphology-focus tiff - nucleus_segmentation_only = false // to only run nucleus segmentation while running XR_IMP-SEG + nucleus_segmentation_only = false // to only run nucleus segmentation while running segmentation methods & XR_IMP-SEG + cell_segmentation_only = true // to only run cell segmentation while running segmentation methods & XR_IMP-SEG // Xeniumranger specific xeniumranger_only = false // to generate redefined bundle with just changing the xr specific params @@ -50,6 +51,16 @@ params { features = null // Baysor specific + filter_transcripts = false + min_qv = 20 + max_x = 24000.0 + min_x = 0.0 + max_y = 24000.0 + min_y = 0.0 + + // utility modules + csplit_x_bins = 2 // number of tiles along the x axis (total number of bins is product of x_bins * y_bins) + csplit_y_bins = 2 // number of tiles along the y axis // MultiQC options multiqc_config = null diff --git a/nextflow_schema.json b/nextflow_schema.json index aa9088be..7b6e2b0a 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -95,6 +95,10 @@ "type": "boolean", "description": "Whether to run vanilla xeniumranger workflow." }, + "cell_segmentation_only": { + "type": "boolean", + "description": "Whether to only run nucleus segmentation." + }, "nucleus_segmentation_only": { "type": "boolean", "description": "Whether to only run nucleus segmentation." @@ -171,6 +175,39 @@ "features": { "type": "string", "description": "List of features to be passed to the ficture method. (eg: TP53,OCIAD1,BCAS3,SOX)" + }, + "filter_transcripts": { + "type": "boolean", + "description": "Whether to filter the transcripts.parquet file before running Baysor segmentation.", + "default": false + }, + "min_qv": { + "type": "number", + "description": "minimum Q-Score to pass filtering (default: 20.0)" + }, + "min_x": { + "type": "number", + "description": "only keep transcripts whose x-coordinate is greater than specified limit, if no limit is specified, the default minimum value will be 0.0" + }, + "max_x": { + "type": "number", + "description": "only keep transcripts whose x-coordinate is less than specified limit, if no limit is specified, the default value will retain all transcripts since Xenium slide is <24000 microns in x and y (default: 24000.0)" + }, + "min_y": { + "type": "number", + "description": "only keep transcripts whose y-coordinate is greater than specified limit, if no limit is specified, the default minimum value will be 0.0" + }, + "max_y": { + "type": "number", + "description": "only keep transcripts whose y-coordinate is less than specified limit, if no limit is specified, the default value will retain all transcripts since Xenium slide is <24000 microns in x and y (default: 24000.0)" + }, + "csplit_x_bins": { + "type": "integer", + "description": "number of slices along the x axis (default - 10)" + }, + "csplit_y_bins": { + "type": "integer", + "description": "number of slices along the y axis (default - 10)" } } }, diff --git a/subworkflows/local/baysor_run_prior_segmentation_mask/main.nf b/subworkflows/local/baysor_run_prior_segmentation_mask/main.nf index 40a4924e..ce2609a4 100644 --- a/subworkflows/local/baysor_run_prior_segmentation_mask/main.nf +++ b/subworkflows/local/baysor_run_prior_segmentation_mask/main.nf @@ -2,7 +2,8 @@ // Run baysor run & import-segmentation // -include { BAYSOR_RUN as BAYSOR_RUN_IMAGE } from '../../../modules/local/baysor/run/main' +include { BAYSOR_PREPROCESS_TRANSCRIPTS } from '../../../modules/local/baysor/preprocess/main' +include { BAYSOR_RUN } from '../../../modules/local/baysor/run/main' include { XENIUMRANGER_IMPORT_SEGMENTATION } from '../../../modules/nf-core/xeniumranger/import-segmentation/main' @@ -19,28 +20,49 @@ workflow BAYSOR_RUN_PRIOR_SEGMENTATION_MASK { ch_versions = Channel.empty() + ch_transcripts = Channel.empty() + ch_segmentation = Channel.empty() ch_polygons2d = Channel.empty() ch_htmls = Channel.empty() ch_redefined_bundle = Channel.empty() + // filter transcripts.parquet based on thresholds + if ( params.filter_transcripts ) { + + BAYSOR_PREPROCESS_TRANSCRIPTS ( + ch_transcripts_parquet, + params.min_qv, + params.max_x, + params.min_x, + params.max_y, + params.min_y + ) + ch_versions = ch_versions.mix ( BAYSOR_PREPROCESS_TRANSCRIPTS.out.versions ) + + ch_transcripts = BAYSOR_PREPROCESS_TRANSCRIPTS.out.transcripts_parquet + + } else { + + ch_transcripts = ch_transcripts_parquet + } // run baysor with morphology.tiff - BAYSOR_RUN_IMAGE ( - ch_transcripts_parquet, + BAYSOR_RUN ( + ch_transcripts, ch_segmentation_mask, ch_config, 30 ) - ch_versions = ch_versions.mix( BAYSOR_RUN_IMAGE.out.versions ) + ch_versions = ch_versions.mix( BAYSOR_RUN.out.versions ) - ch_segmentation = BAYSOR_RUN_IMAGE.out.segmentation + ch_segmentation = BAYSOR_RUN.out.segmentation ch_just_segmentation = ch_segmentation.map { _meta, segmentation -> return [ segmentation ] } - ch_polygons2d = BAYSOR_RUN_IMAGE.out.polygons2d - ch_htmls = BAYSOR_RUN_IMAGE.out.htmls + ch_polygons2d = BAYSOR_RUN.out.polygons2d + ch_htmls = BAYSOR_RUN.out.htmls // run xeniumranger import-segmentation XENIUMRANGER_IMPORT_SEGMENTATION ( diff --git a/subworkflows/local/baysor_run_transcripts_parquet/main.nf b/subworkflows/local/baysor_run_transcripts_parquet/main.nf index 7077eede..e7999d6a 100644 --- a/subworkflows/local/baysor_run_transcripts_parquet/main.nf +++ b/subworkflows/local/baysor_run_transcripts_parquet/main.nf @@ -2,8 +2,10 @@ // Run baysor run and import-segmentation // -include { BAYSOR_RUN as BAYSOR_RUN_TRANSCRIPTS } from '../../../modules/local/baysor/run/main' -include { XENIUMRANGER_IMPORT_SEGMENTATION } from '../../../modules/nf-core/xeniumranger/import-segmentation/main' +// include { SPLIT_TRANSCRIPTS } from '../../../modules/local/utility/split_transcripts/main' +include { BAYSOR_PREPROCESS_TRANSCRIPTS } from '../../../modules/local/baysor/preprocess/main' +include { BAYSOR_RUN } from '../../../modules/local/baysor/run/main' +include { XENIUMRANGER_IMPORT_SEGMENTATION } from '../../../modules/nf-core/xeniumranger/import-segmentation/main' workflow BAYSOR_RUN_TRANSCRIPTS_PARQUET { @@ -18,27 +20,79 @@ workflow BAYSOR_RUN_TRANSCRIPTS_PARQUET { ch_versions = Channel.empty() + ch_transcripts = Channel.empty() + // ch_splits_csv = Channel.empty() + ch_segmentation = Channel.empty() ch_polygons2d = Channel.empty() ch_htmls = Channel.empty() ch_redefined_bundle = Channel.empty() - // run baysor with transcripts.csv - BAYSOR_RUN_TRANSCRIPTS ( - ch_transcripts_parquet, + + // generate splits + // SPLIT_TRANSCRIPTS ( + // ch_transcripts_parquet, + // params.x_bins, + // params.y_bins + // ) + // ch_versions = ch_versions.mix ( SPLIT_TRANSCRIPTS.out.versions ) + + // ch_splits_csv = SPLIT_TRANSCRIPTS.out.splits_csv + + + // Set splits.csv into tuple queue channel + // Channel + // ch_splits_csv + // .flatMap { meta, splits_file -> + // splits_file.splitCsv(header: true).collect { row -> + // tuple(meta, row.tile_id, row.x_min, row.x_max, row.y_min, row.y_max) + // } + // } + // .set { ch_splits } // channel: [ val(tile_id), val(x_min), val(x_max), val(y_min), val(y_max) ] + + + //Add in sample path for each split value + // transcripts_input = ch_transcripts_parquet.combine(ch_splits, by: 0) + + + // filter transcripts.parquet based on thresholds + if ( params.filter_transcripts ) { + + BAYSOR_PREPROCESS_TRANSCRIPTS ( + ch_transcripts_parquet, + params.min_qv, + params.max_x, + params.min_x, + params.max_y, + params.min_y + ) + ch_versions = ch_versions.mix ( BAYSOR_PREPROCESS_TRANSCRIPTS.out.versions ) + + ch_transcripts = BAYSOR_PREPROCESS_TRANSCRIPTS.out.transcripts_parquet + + } else { + + ch_transcripts = ch_transcripts_parquet + } + + + // run baysor with the filtered transcripts.parquet + BAYSOR_RUN ( + ch_transcripts, [], ch_config, 30 ) - ch_versions = ch_versions.mix ( BAYSOR_RUN_TRANSCRIPTS.out.versions ) + ch_versions = ch_versions.mix ( BAYSOR_RUN.out.versions ) - ch_segmentation = BAYSOR_RUN_TRANSCRIPTS.out.segmentation - ch_jus_segmentation = ch_segmentation.map { + ch_segmentation = BAYSOR_RUN.out.segmentation + ch_segmentation_csv = ch_segmentation.map { _meta, segmentation -> return [ segmentation ] } - ch_polygons2d = BAYSOR_RUN_TRANSCRIPTS.out.polygons2d - ch_htmls = BAYSOR_RUN_TRANSCRIPTS.out.htmls + ch_polygons2d = BAYSOR_RUN.out.polygons2d + ch_htmls = BAYSOR_RUN.out.htmls + // run xeniumranger import-segmentation XENIUMRANGER_IMPORT_SEGMENTATION ( @@ -46,7 +100,7 @@ workflow BAYSOR_RUN_TRANSCRIPTS_PARQUET { [], [], [], - ch_jus_segmentation, + ch_segmentation_csv, ch_polygons2d, "microns" ) diff --git a/subworkflows/local/cellpose_baysor_import_segmentation/main.nf b/subworkflows/local/cellpose_baysor_import_segmentation/main.nf index 22f556b9..0ade1a9d 100644 --- a/subworkflows/local/cellpose_baysor_import_segmentation/main.nf +++ b/subworkflows/local/cellpose_baysor_import_segmentation/main.nf @@ -2,8 +2,11 @@ // Run the cellpose, baysor and import-segmentation flow // -include { CELLPOSE } from '../../../modules/nf-core/cellpose/main' +include { RESOLIFT } from '../../../modules/local/resolift/main' +include { CELLPOSE as CELLPOSE_CELLS } from '../../../modules/nf-core/cellpose/main' +include { CELLPOSE as CELLPOSE_NUCLEI } from '../../../modules/nf-core/cellpose/main' include { BAYSOR_RUN } from '../../../modules/local/baysor/run/main' +include { BAYSOR_PREPROCESS_TRANSCRIPTS } from '../../../modules/local/baysor/preprocess/main' include { XENIUMRANGER_IMPORT_SEGMENTATION } from '../../../modules/nf-core/xeniumranger/import-segmentation/main' workflow CELLPOSE_BAYSOR_IMPORT_SEGMENTATION { @@ -17,27 +20,115 @@ workflow CELLPOSE_BAYSOR_IMPORT_SEGMENTATION { main: - ch_versions = Channel.empty() + ch_versions = Channel.empty() + ch_image = Channel.empty() + ch_polygons = Channel.empty() + ch_segmentation = Channel.empty() + ch_filtered_transcripts = Channel.empty() + ch_cellpose_cells_mask = Channel.empty() + ch_cellpose_nuclei_mask = Channel.empty() + ch_cellpose_cells_cells = Channel.empty() + ch_cellpose_nuclei_cells = Channel.empty() + ch_cellpose_cells_flows = Channel.empty() + ch_cellpose_nuclei_flows = Channel.empty() - // run cellpose to generate segmentation mask - CELLPOSE ( ch_morphology_image, []) - ch_versions = ch_versions.mix ( CELLPOSE.out.versions ) + cellpose_model = params.cellpose_model ? (Channel.fromPath(params.cellpose_model, checkIfExists: true)) : [] + // sharpen morphology tiff if param - sharpen_tiff is true + if ( params.sharpen_tiff ) { + + RESOLIFT ( ch_morphology_image ) + ch_versions = ch_versions.mix( RESOLIFT.out.versions ) + + ch_image = RESOLIFT.out.enhanced_tiff + + } else { + + ch_image = ch_morphology_image + + } + + // run cellpose on the enhanced tiff + if ( params.cell_segmentation_only ) { + + CELLPOSE_CELLS ( ch_image, cellpose_model, 'cells' ) + ch_versions = ch_versions.mix( CELLPOSE_CELLS.out.versions ) + + ch_cellpose_cells_cells = CELLPOSE_CELLS.out.cells.map { + _meta, cells -> return [ cells ] + } + ch_cellpose_cells_mask = CELLPOSE_CELLS.out.mask.map { + _meta, mask -> return [ mask ] + } + ch_cellpose_cells_flows = CELLPOSE_CELLS.out.flows.map { + _meta, flows -> return [ flows ] + } + + } + + if ( params.nucleus_segmentation_only ) { + + CELLPOSE_NUCLEI ( ch_image, 'nuclei', 'nuclei' ) + ch_versions = ch_versions.mix( CELLPOSE_NUCLEI.out.versions ) + + ch_cellpose_nuclei_cells = CELLPOSE_NUCLEI.out.cells.map { + _meta, cells -> return [ cells ] + } + ch_cellpose_nuclei_mask = CELLPOSE_NUCLEI.out.mask.map { + _meta, mask -> return [ mask ] + } + ch_cellpose_nuclei_flows = CELLPOSE_NUCLEI.out.flows.map { + _meta, flows -> return [ flows ] + } + + } + + + // filter transcripts.parquet based on thresholds + if ( params.filter_transcripts ) { + + BAYSOR_PREPROCESS_TRANSCRIPTS ( + ch_transcripts_parquet, + params.min_qv, + params.max_x, + params.min_x, + params.max_y, + params.min_y + ) + ch_versions = ch_versions.mix ( BAYSOR_PREPROCESS_TRANSCRIPTS.out.versions ) + + ch_filtered_transcripts = BAYSOR_PREPROCESS_TRANSCRIPTS.out.transcripts_parquet - // run baysor with the segmentation mask from cellpose - ch_mask = CELLPOSE.out.mask.map { - _meta, seg_mask -> [ seg_mask ] } - BAYSOR_RUN ( ch_transcripts_parquet, ch_mask, ch_config, 30 ) - ch_versions = ch_versions.mix ( BAYSOR_RUN.out.versions ) + // run baysor with cellpose results + if ( params.nucleus_segmentation_only ) { + + // run baysor with nuclei mask + BAYSOR_RUN ( ch_filtered_transcripts, ch_cellpose_nuclei_mask, ch_config, 30 ) + ch_versions = ch_versions.mix ( BAYSOR_RUN.out.versions ) + + } else if ( params.cell_segmentation_only ) { + + // run baysor with cell mask + BAYSOR_RUN ( ch_filtered_transcripts, ch_cellpose_cells_mask, ch_config, 30 ) + ch_versions = ch_versions.mix ( BAYSOR_RUN.out.versions ) - // run import segmentation with outputs from baysor + } else { + + // run baysor with cell mask + BAYSOR_RUN ( ch_filtered_transcripts, [], ch_config, 30 ) + ch_versions = ch_versions.mix ( BAYSOR_RUN.out.versions ) + + } + + // run import-segmentation with baysor outs ch_segmentation = BAYSOR_RUN.out.segmentation.map { _meta, segmentation -> return [ segmentation ] } ch_polygons = BAYSOR_RUN.out.polygons2d + XENIUMRANGER_IMPORT_SEGMENTATION ( ch_bundle_path, [], @@ -51,6 +142,16 @@ workflow CELLPOSE_BAYSOR_IMPORT_SEGMENTATION { emit: + cells_mask = ch_cellpose_cells_mask // channel: [ val(meta), [ "*masks.tif" ] ] + cells_flows = ch_cellpose_cells_flows // channel: [ val(meta), [ "*flows.tif" ] ] + cells_cells = ch_cellpose_cells_cells // channel: [ val(meta), [ "*seg.npy" ] ] + nuclei_mask = ch_cellpose_nuclei_mask // channel: [ val(meta), [ "*masks.tif" ] ] + nuclei_flows = ch_cellpose_nuclei_flows // channel: [ val(meta), [ "*flows.tif" ] ] + nuclei_cells = ch_cellpose_nuclei_cells // channel: [ val(meta), [ "*seg.npy" ] ] + + segmentation_mask = ch_segmentation // channel: [ val(meta), [ *segmentation.csv ] ] + polygons = ch_polygons // channel: [ val(meta), [ *polygons.json ] ] + redefined_bundle = XENIUMRANGER_IMPORT_SEGMENTATION.out.bundle // channel: [ val(meta), ["redefined-xenium-bundle"] ] versions = ch_versions // channel: [ versions.yml ] diff --git a/subworkflows/local/cellpose_resolift_morphology_ome_tif/main.nf b/subworkflows/local/cellpose_resolift_morphology_ome_tif/main.nf index 62fb908d..d8203039 100644 --- a/subworkflows/local/cellpose_resolift_morphology_ome_tif/main.nf +++ b/subworkflows/local/cellpose_resolift_morphology_ome_tif/main.nf @@ -2,9 +2,9 @@ // Run cellpose on the morphology tiff // +include { RESOLIFT } from '../../../modules/local/resolift/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' workflow CELLPOSE_RESOLIFT_MORPHOLOGY_OME_TIF { @@ -16,7 +16,14 @@ workflow CELLPOSE_RESOLIFT_MORPHOLOGY_OME_TIF { main: - ch_versions = Channel.empty() + ch_versions = Channel.empty() + ch_image = Channel.empty() + ch_cellpose_cells_mask = Channel.empty() + ch_cellpose_nuclei_mask = Channel.empty() + ch_cellpose_cells_cells = Channel.empty() + ch_cellpose_nuclei_cells = Channel.empty() + ch_cellpose_cells_flows = Channel.empty() + ch_cellpose_nuclei_flows = Channel.empty() cellpose_model = params.cellpose_model ? (Channel.fromPath(params.cellpose_model, checkIfExists: true)) : [] @@ -26,40 +33,42 @@ workflow CELLPOSE_RESOLIFT_MORPHOLOGY_OME_TIF { RESOLIFT ( ch_morphology_image ) ch_versions = ch_versions.mix( RESOLIFT.out.versions ) - // run cellpose on the enhanced tiff - 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 ) + ch_image = RESOLIFT.out.enhanced_tiff } else { - // run cellpose on the original tiff - CELLPOSE_CELLS ( ch_morphology_image, cellpose_model, 'cells' ) - ch_versions = ch_versions.mix( CELLPOSE_CELLS.out.versions ) + ch_image = ch_morphology_image - CELLPOSE_NUCLEI ( ch_morphology_image, 'nuclei', 'nuclei' ) - ch_versions = ch_versions.mix( CELLPOSE_NUCLEI.out.versions ) } - // get cellpose segmentation results - 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 ] + // run cellpose on morphology tiff + if ( !params.nucleus_segmentation_only ) { + + CELLPOSE_CELLS ( ch_image, cellpose_model, 'cells' ) + ch_versions = ch_versions.mix( CELLPOSE_CELLS.out.versions ) + + ch_cellpose_cells_cells = CELLPOSE_CELLS.out.cells.map { + _meta, cells -> return [ cells ] + } + ch_cellpose_cells_mask = CELLPOSE_CELLS.out.mask.map { + _meta, mask -> return [ mask ] + } + ch_cellpose_cells_flows = CELLPOSE_CELLS.out.flows.map { + _meta, flows -> return [ flows ] + } + } - cellpose_nuclei_cells = CELLPOSE_NUCLEI.out.cells.map { + + CELLPOSE_NUCLEI ( ch_image, 'nuclei', 'nuclei' ) + ch_versions = ch_versions.mix( CELLPOSE_NUCLEI.out.versions ) + + ch_cellpose_nuclei_cells = CELLPOSE_NUCLEI.out.cells.map { _meta, cells -> return [ cells ] } - cellpose_nuclei_mask = CELLPOSE_NUCLEI.out.mask.map { + ch_cellpose_nuclei_mask = CELLPOSE_NUCLEI.out.mask.map { _meta, mask -> return [ mask ] } - _cellpose_nuclei_flows = CELLPOSE_NUCLEI.out.flows.map { + ch_cellpose_nuclei_flows = CELLPOSE_NUCLEI.out.flows.map { _meta, flows -> return [ flows ] } @@ -69,7 +78,7 @@ workflow CELLPOSE_RESOLIFT_MORPHOLOGY_OME_TIF { XENIUMRANGER_IMPORT_SEGMENTATION ( ch_bundle_path, [], - cellpose_nuclei_mask, + ch_cellpose_nuclei_mask, [], [], [], @@ -82,8 +91,8 @@ workflow CELLPOSE_RESOLIFT_MORPHOLOGY_OME_TIF { XENIUMRANGER_IMPORT_SEGMENTATION ( ch_bundle_path, [], - cellpose_nuclei_mask, - cellpose_cells_mask, + ch_cellpose_nuclei_mask, + ch_cellpose_cells_mask, [], [], "" @@ -93,12 +102,12 @@ workflow CELLPOSE_RESOLIFT_MORPHOLOGY_OME_TIF { emit: - 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" ] ] + cells_mask = ch_cellpose_cells_mask // channel: [ val(meta), [ "*masks.tif" ] ] + cells_flows = ch_cellpose_cells_flows // channel: [ val(meta), [ "*flows.tif" ] ] + cells_cells = ch_cellpose_cells_cells // channel: [ val(meta), [ "*seg.npy" ] ] + nuclei_mask = ch_cellpose_nuclei_mask // channel: [ val(meta), [ "*masks.tif" ] ] + nuclei_flows = ch_cellpose_nuclei_flows // channel: [ val(meta), [ "*flows.tif" ] ] + nuclei_cells = ch_cellpose_nuclei_cells // channel: [ val(meta), [ "*seg.npy" ] ] redefined_bundle = XENIUMRANGER_IMPORT_SEGMENTATION.out.bundle // channel: [ val(meta), ["redefined-xenium-bundle"] ] diff --git a/subworkflows/local/segger_create_train_predict/main.nf b/subworkflows/local/segger_create_train_predict/main.nf index 99f07c12..ad46ef81 100644 --- a/subworkflows/local/segger_create_train_predict/main.nf +++ b/subworkflows/local/segger_create_train_predict/main.nf @@ -2,21 +2,25 @@ // Run segger create_dataset, train and predict modules & parquet_to_csv // -include { SEGGER_TRAIN } from '../../../modules/local/segger/train/main' -include { SEGGER_PREDICT } from '../../../modules/local/segger/predict/main' -include { SEGGER_CREATE_DATASET } from '../../../modules/local/segger/create_dataset/main' -include { PARQUET_TO_CSV } from '../../../modules/local/spatialconverter/parquet_to_csv/main' +include { SEGGER2XR } from '../../../modules/local/utility/segger2xr/main' +include { SEGGER_TRAIN } from '../../../modules/local/segger/train/main' +include { SEGGER_PREDICT } from '../../../modules/local/segger/predict/main' +include { SEGGER_CREATE_DATASET } from '../../../modules/local/segger/create_dataset/main' +include { XENIUMRANGER_IMPORT_SEGMENTATION } from '../../../modules/nf-core/xeniumranger/import-segmentation/main' workflow SEGGER_CREATE_TRAIN_PREDICT { take: - ch_basedir // channel: [ val(meta), [ "basedir" ] ] - ch_transcripts_parquet // channel: [ val(meta), [bundle + "/transcripts.parquet"]] + ch_basedir // channel: [ val(meta), [ "basedir" ] ] + ch_transcripts_parquet // channel: [ val(meta), [bundle + "/transcripts.parquet"]] + ch_bundle // channel: [ val(meta), ["path-to-xenium-bundle"] ] main: - ch_versions = Channel.empty() + ch_versions = Channel.empty() + ch_redefined_bundle = Channel.empty() + ch_segger_transcripts = Channel.empty() // create dataset SEGGER_CREATE_DATASET ( ch_basedir ) @@ -33,19 +37,54 @@ workflow SEGGER_CREATE_TRAIN_PREDICT { ch_just_transcripts_parquet = ch_transcripts_parquet.map { _meta, transcripts -> return [ transcripts ] } - SEGGER_PREDICT ( SEGGER_CREATE_DATASET.out.datasetdir, ch_just_trained_models, ch_just_transcripts_parquet ) + SEGGER_PREDICT ( + SEGGER_CREATE_DATASET.out.datasetdir, + ch_just_trained_models, + ch_just_transcripts_parquet + ) ch_versions = ch_versions.mix ( SEGGER_PREDICT.out.versions ) - // convert parquet to csv - PARQUET_TO_CSV( SEGGER_PREDICT.out.transcripts ) - ch_versions = ch_versions.mix( PARQUET_TO_CSV.out.versions ) + // convert parquet to XR compatible form + SEGGER2XR ( SEGGER_PREDICT.out.transcripts ) + ch_versions = ch_versions.mix( SEGGER2XR.out.versions ) + + ch_segger_transcripts = SEGGER2XR.out.transcripts_parquet.map { + _meta, transcripts -> return [ transcripts ] + } + + // replace transcripts.parquet in xenium bundle + ch_updated_bundle = ch_bundle.map { fobj -> + if (fobj.name == 'transcripts.parquet') { + ch_segger_transcripts.val + } else { + fobj + } + } + + + // run xeniumranger import-segmentation + XENIUMRANGER_IMPORT_SEGMENTATION ( + ch_updated_bundle, + [], + [], + [], + [], + [], + "pixel" + ) + ch_redefined_bundle = XENIUMRANGER_IMPORT_SEGMENTATION.out.bundle + + ch_versions = ch_versions.mix ( XENIUMRANGER_IMPORT_SEGMENTATION.out.versions ) + emit: - datasetdir = SEGGER_CREATE_DATASET.out.datasetdir // channel: [ val(meta), [ datasetdir ] ] - trained_models = SEGGER_TRAIN.out.trained_models // channel: [ val(meta), [ trained_models ] ] - benchmarks = SEGGER_PREDICT.out.benchmarks // channel: [ val(meta), [ benchmarks ] ] - ch_transcripts = PARQUET_TO_CSV.out.transcripts_csv // channel: [ val(meta), [ transcripts ] ] + datasetdir = SEGGER_CREATE_DATASET.out.datasetdir // channel: [ val(meta), [ datasetdir ] ] + trained_models = SEGGER_TRAIN.out.trained_models // channel: [ val(meta), [ trained_models ] ] + benchmarks = SEGGER_PREDICT.out.benchmarks // channel: [ val(meta), [ benchmarks ] ] + segger_transcripts = ch_segger_transcripts // channel: [ [ transcripts.parquet ] ] + + redefined_bundle = ch_redefined_bundle // channel: [ val(meta), ["redefined-xenium-bundle"] ] - versions = ch_versions // channel: [ versions.yml ] + versions = ch_versions // channel: [ versions.yml ] } diff --git a/subworkflows/local/spatialdata_write_meta_merge/main.nf b/subworkflows/local/spatialdata_write_meta_merge/main.nf index e32b26b2..8daebfe2 100644 --- a/subworkflows/local/spatialdata_write_meta_merge/main.nf +++ b/subworkflows/local/spatialdata_write_meta_merge/main.nf @@ -12,11 +12,45 @@ 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_segmented_object // can be either cells,nuclei,cells_and_nuclei main: ch_versions = Channel.empty() + ch_segmented_object = Channel.empty() + + // check segmentation - only nuclei, cells or both cells & nuclei + if ( params.mode == 'image') { + + if ( params.nucleus_segmentation_only && params.cell_segmentation_only ) { + + ch_segmented_object = Channel.value('cells_and_nuclei') + + } + + else if ( params.nucleus_segmentation_only ) { + + ch_segmented_object = Channel.value('nuclei') + + } + + else if ( params.cell_segmentation_only ) { + + ch_segmented_object = Channel.value('cells') + + } else { + + ch_segmented_object = Channel.value([]) + + } + } + + // set all boundaries as false - default + if ( params.mode == 'coordinate') { + + ch_segmented_object = Channel.value([]) + + } + // write spatialdata object from the raw xenium bundle SPATIALDATA_WRITE_RAW_BUNDLE ( diff --git a/subworkflows/local/utils_nfcore_spatialxe_pipeline/main.nf b/subworkflows/local/utils_nfcore_spatialxe_pipeline/main.nf index d04d0b53..7e696a89 100644 --- a/subworkflows/local/utils_nfcore_spatialxe_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_spatialxe_pipeline/main.nf @@ -195,6 +195,12 @@ def validateInputParameters() { log.warn "⚠️ Use --nucleus_segmentation_only to enable nucleus segmentation to redefine xenium bundle with import-segmentation module." } + // check if segmentation mask is provided in image mode and baysor method + if ( params.mode == 'image' && params.method == 'baysor' ) + if (!params.segmentation_mask ) { + log.error "❌ Error: Missing segmentation mask with `--segmentation_mask` when pipeline is run in ${params.mode} and with the ${params.method}." + } + } // diff --git a/workflows/spatialxe.nf b/workflows/spatialxe.nf index e6a86dbd..e7e11069 100644 --- a/workflows/spatialxe.nf +++ b/workflows/spatialxe.nf @@ -153,6 +153,14 @@ workflow SPATIALXE { ) } + // get custom cellpose model if provided with the --cellpose_model for the cellpose method + if ( params.cellpose_model ) { + ch_features = Channel.fromPath ( + params.cellpose_model, + checkIfExists: true + ) + } + // get gene_panel.json if provided with --gene_panel, sets relabel_genes to true if (( params.gene_panel )) { @@ -352,17 +360,13 @@ 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_segmented_object + ch_redefined_bundle ) } - /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SPATIALXE - QC LAYER