Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions conf/modules.config
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,15 @@ process {
ext.args = { "--diameter 9 --channel_axis 0 --save_flows" }
}

// ---------------------------- scportrait -------------------------------------

withName: SCPORTRAIT_SEGMENT {
publishDir = [
path: { "${params.outdir}/${params.mode}/scportrait" },
mode: params.publish_dir_mode,
]
}

// ---------------------------- opt -----------------------------------------

withName: OPT_FLIP {
Expand Down
36 changes: 36 additions & 0 deletions modules/local/scportrait/imagesegment/main.nf
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
process SCPORTRAIT_IMAGESEGMENT {
tag "$meta.id"
label 'process_high'
maxForks params.restrict_concurrency ? 1 : 0

container "docker.io/library/python:3.11-slim"

input:
tuple val(meta), path(image)

output:
tuple val(meta), path("${prefix}/nuclei_labels.tif"), emit: nuclei, optional: true
tuple val(meta), path("${prefix}/cells_labels.tif"), emit: cells, optional: true
path "versions.yml", emit: versions

when:
task.ext.when == null || task.ext.when

script:
prefix = task.ext.prefix ?: "${meta.id}"
def nucleusOnly = params.nucleus_segmentation_only
template('image_segmentation.py')

stub:
prefix = task.ext.prefix ?: "${meta.id}"
"""
mkdir -p ${prefix}
touch ${prefix}/nuclei_labels.tif
touch ${prefix}/cells_labels.tif

cat <<-END_VERSIONS > versions.yml
"${task.process}":
scportrait: stub
END_VERSIONS
"""
}
77 changes: 77 additions & 0 deletions modules/local/scportrait/imagesegment/meta.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json
# # TODO nf-core: Add a description of the module and list keywords
name: "scportrait_imagesegment"
description: write your description here
keywords:
- sort
- example
- genomics
tools:
## TODO nf-core: Add a description and other details for the software below
- "scportrait":
description: ""
homepage: ""
documentation: ""
tool_dev_url: ""
doi: ""
licence: null
identifier: null

input:
### TODO nf-core: Add a description of all of the variables used as input
- - meta:
type: map
description: |
Groovy Map containing sample information
e.g. `[ id:'sample1' ]`
- bam:
type: file
description: Sorted BAM/CRAM/SAM file
pattern: "*.{bam,cram,sam}"
ontologies:
- edam: "http://edamontology.org/format_2572" # BAM
- edam: "http://edamontology.org/format_2573" # CRAM
- edam: "http://edamontology.org/format_3462" # SAM

output:
### TODO nf-core: Add a description of all of the variables used as output
bam:
- - meta:
type: map
description: |
Groovy Map containing sample information
e.g. `[ id:'sample1' ]`
- "*.bam":
type: file
description: Sorted BAM/CRAM/SAM file
pattern: "*.{bam,cram,sam}"
ontologies:
- edam: "http://edamontology.org/format_2572" # BAM
- edam: "http://edamontology.org/format_2573" # CRAM
- edam: "http://edamontology.org/format_3462" # SAM
versions_scportrait:
- - "${task.process}":
type: string
description: The name of the process
- "scportrait":
type: string
description: The name of the tool
- "scportrait --version":
type: eval
description: The expression to obtain the version of the tool

topics:
versions:
- - ${task.process}:
type: string
description: The name of the process
- scportrait:
type: string
description: The name of the tool
- scportrait --version:
type: eval
description: The expression to obtain the version of the tool
authors:
- "@TPbiocode"
maintainers:
- "@TPbiocode"
149 changes: 149 additions & 0 deletions modules/local/scportrait/imagesegment/templates/image_segmentation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#!/usr/bin/env python3

from pathlib import Path
from importlib.metadata import PackageNotFoundError, version

import numpy as np
from tifffile import imread, imwrite

from scportrait.pipeline.project import Project
from scportrait.pipeline.segmentation.workflows import (
CytosolSegmentationCellpose,
DAPISegmentationCellpose,
)


def normalize_input_shape(image: np.ndarray) -> np.ndarray:
image = np.asarray(image)
image = np.squeeze(image)

if image.ndim == 2:
return image[np.newaxis, ...]

if image.ndim == 3:
if image.shape[0] <= 4:
return image
if image.shape[-1] <= 4:
return np.moveaxis(image, -1, 0)
raise ValueError(f"Unable to infer channel axis from image shape {image.shape}")

if image.ndim == 4:
image = image[0]
if image.shape[0] <= 4:
return image
if image.shape[-1] <= 4:
return np.moveaxis(image, -1, 0)
raise ValueError(f"Unable to infer channel axis from 4D image shape {image.shape}")

raise ValueError(f"Unsupported morphology image shape {image.shape}")


def extract_label(label_obj) -> np.ndarray:
if hasattr(label_obj, "scale0"):
label_obj = label_obj.scale0.image
elif hasattr(label_obj, "image"):
label_obj = label_obj.image

data = getattr(label_obj, "data", label_obj)
if hasattr(data, "compute"):
data = data.compute()

data = np.asarray(data)
data = np.squeeze(data)
return data.astype(np.uint32)


def run_segmentation(
image_path: str,
nucleus_only: bool,
nuclei_out: str,
cells_out: str,
project_dir: str,
) -> None:
image = normalize_input_shape(imread(image_path))

if nucleus_only:
segmentation_cls = DAPISegmentationCellpose
config = {
"DAPISegmentationCellpose": {
"input_channels": 1,
"output_masks": 1,
"cache": ".",
"chunk_size": 100,
"nucleus_segmentation": {"model": "nuclei"},
}
}
image = image[:1, :, :]
channel_names = ["nucleus"]
else:
segmentation_cls = CytosolSegmentationCellpose
if image.shape[0] == 1:
image = np.repeat(image, 2, axis=0)
else:
image = image[:2, :, :]

config = {
"CytosolSegmentationCellpose": {
"input_channels": 2,
"output_masks": 2,
"cache": ".",
"chunk_size": 100,
"nucleus_segmentation": {"model": "nuclei"},
"cytosol_segmentation": {"model": "cyto2"},
"match_masks": True,
"filter_masks_size": False,
}
}
channel_names = ["nucleus", "cytosol"]

project = Project(
project_location=project_dir,
config_path=config,
segmentation_f=segmentation_cls,
overwrite=True,
debug=False,
)
project.load_input_from_array(image, channel_names=channel_names)
project.segment()

if "seg_all_nucleus" in project.sdata:
imwrite(nuclei_out, extract_label(project.sdata["seg_all_nucleus"]))

if "seg_all_cytosol" in project.sdata:
imwrite(cells_out, extract_label(project.sdata["seg_all_cytosol"]))


def generate_versions_yml() -> None:
try:
scportrait_version = version("scportrait")
except PackageNotFoundError:
scportrait_version = "unknown"

with open("versions.yml", "w", encoding="utf-8") as handle:
handle.write('"${task.process}":\n')
handle.write(f" scportrait: {scportrait_version}\n")


def main() -> None:
image_path = "${image}"
prefix = "${prefix}"
project_dir = "scportrait_project"
nucleus_only = "${nucleusOnly}".lower() == "true"
nuclei_out = f"{prefix}/nuclei_labels.tif"
cells_out = f"{prefix}/cells_labels.tif"

Path(prefix).mkdir(parents=True, exist_ok=True)
Path(project_dir).mkdir(parents=True, exist_ok=True)

run_segmentation(
image_path=image_path,
nucleus_only=nucleus_only,
nuclei_out=nuclei_out,
cells_out=cells_out,
project_dir=project_dir,
)
generate_versions_yml()


if __name__ == "__main__":
main()
79 changes: 79 additions & 0 deletions modules/local/scportrait/imagesegment/tests/main.nf.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// TODO nf-core: Once you have added the required tests, please run the following command to build this file:
// nf-core modules test scportrait/imagesegment
nextflow_process {

name "Test Process SCPORTRAIT_IMAGESEGMENT"
script "../main.nf"
process "SCPORTRAIT_IMAGESEGMENT"

tag "modules"
tag "modules_"
tag "scportrait"
tag "scportrait/imagesegment"

// TODO nf-core: Change the test name preferably indicating the test-data and file-format used
test("sarscov2 - bam") {

// TODO nf-core: If you are created a test for a chained module
// (the module requires running more than one process to generate the required output)
// add the 'setup' method here.
// You can find more information about how to use a 'setup' method in the docs (https://nf-co.re/docs/contributing/modules#steps-for-creating-nf-test-for-chained-modules).

when {
process {
"""
// TODO nf-core: define inputs of the process here. Example:

input[0] = [
[ id:'test' ],
file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true),
]
"""
}
}

then {
assert process.success
assertAll(
{ assert snapshot(
process.out,
path(process.out.versions[0]).yaml
).match() }
//TODO nf-core: Add all required assertions to verify the test output.
// See https://nf-co.re/docs/contributing/tutorials/nf-test_assertions for more information and examples.
)
}

}

// TODO nf-core: Change the test name preferably indicating the test-data and file-format used but keep the " - stub" suffix.
test("sarscov2 - bam - stub") {

options "-stub"

when {
process {
"""
// TODO nf-core: define inputs of the process here. Example:

input[0] = [
[ id:'test' ],
file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true),
]
"""
}
}

then {
assert process.success
assertAll(
{ assert snapshot(
process.out,
path(process.out.versions[0]).yaml
).match() }
)
}

}

}
2 changes: 1 addition & 1 deletion nextflow.config
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ params {
format = 'xenium' // preset value set as `xenium`

// Segmentation methods
image_seg_methods = ["cellpose", "xeniumranger", "baysor"]
image_seg_methods = ["cellpose", "xeniumranger", "baysor", "scportrait"]
transcript_seg_methods = ["proseg", "segger", "baysor"]
segfree_methods = ["ficture", "baysor"]

Expand Down
4 changes: 2 additions & 2 deletions nextflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
},
"method": {
"type": "string",
"enum": ["cellpose", "xeniumranger", "baysor", "proseg", "segger", "ficture"],
"enum": ["cellpose", "xeniumranger", "baysor", "scportrait", "proseg", "segger", "ficture"],
"description": "Segmentation method to run."
},
"gene_panel": {
Expand Down Expand Up @@ -174,7 +174,7 @@
"type": "array",
"items": {
"type": "string",
"enum": ["cellpose", "xeniumranger", "baysor"]
"enum": ["cellpose", "xeniumranger", "baysor", "scportrait"]
},
"description": "List of image-based segmentation methods."
},
Expand Down
Empty file.
Loading