Skip to content
Merged
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## v0.2.0 - 2025-09-12

Add segmentation report.

## v0.1.0 - 2025-08-29

Initial release of WEHI-SODA-Hub/spatialproteomics, created with the [nf-core](https://nf-co.re/) template.
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,39 @@ are run separately, and then consolidated into whole cells with nuclei with full
shape and intensity measurements per compartment. The output GeoJSON files can
be viewed in QuPath.

<details>
<summary>Click to view Mermaid diagram</summary>
```mermaid
flowchart TD
A("COMET TIFF") --> B["Extract markers"]
B --> C["Background
subtraction"]
C --> D{"Segmentation
method"} & O["Backsub TIFF"]
N("COMET/MIBI TIFF") --> D
D -- Cellpose (COMET only) --> E["sopa convert"]
E --> F["sopa patchify"]
F --> G["cellpose
(nuclear)"]
F --> H["cellpose
(whole-cell)"]
G --> I["sopa resolve"]
H --> I
I --> J["parquet to tiff"]
J --> K["Cell measurement"]
D -- Mesmer (COMET/MIBI) --> L["mesmer
(nuclear)"]
D -- Mesmer (COMET/MIBI) --> M["mesmer
(whole-cell)"]
L --> K
M --> K
K --> P("GeoJSON")
K --> Q["segmentation
report"]
Q --> R("html file"]
```
</details>

![spatialproteomics workflow](assets/spatialproteomics_workflow.png)

The pipeline uses the following tools:
Expand All @@ -39,6 +72,8 @@ The pipeline uses the following tools:
QuPath API to calculate compartment measurements and intensities.
- [sopa](https://github.com/gustaveroussy/sopa) -- we use the sopa CLI tool to
patchify images and perform cellpose segmentation.
- [spatialVis](https://github.com/WEHI-SODA-Hub/spatialVis) -- R package for spatial
analyses, used to generate plots for the segmentation report.

Please see the [docs for more detailed information on pipeline usage and output](docs/README.md)

Expand Down
Binary file modified assets/spatialproteomics_workflow.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions docs/output.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ The pipeline is built using [Nextflow](https://www.nextflow.io/) and processes d
segmentations, optionally containing measurements and intensity values per
cell, compatible with QuPath.

#### Segmentation report

- `segmentationreport/sample/sample.html` -- html file for visualising report.
- `segmentationreport/sample/sample_report_template_files` -- files necessary to display report.

#### Data provenance

- `pipeline_info/`
Expand Down
6 changes: 6 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,12 @@ The following Mesmer parameters can be set:
| pixel_size_microns | Pixel size in microns, use 0.28 for COMET and 0.390625 for MIBI |
| estimate_cell_boundary_dist | Where no matching membrane ROI exists, expand the nucleus by this many pixels |

### Report parameters

| Parameter Name | Description |
| --------------- | ----------------------------------- |
| generate_report | Generate segmentation report for QC |

### Updating the pipeline

When you run the above command, Nextflow automatically pulls the pipeline code from GitHub and stores it as a cached version. When running the pipeline after this, it will always use the cached version if available - even if the pipeline has been updated since. To make sure that you're running the latest version of the pipeline, make sure that you regularly update the cached version of the pipeline:
Expand Down
11 changes: 11 additions & 0 deletions modules/local/segmentationreport/environment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
channels:
- conda-forge
- bioconda
- defaults
dependencies:
- conda-forge::r-tidyverse=2.0.0
- conda-forge::r-quarto=1.4.4
- conda-forge::r-magick=2.8.5
- conda-forge::r-devtools=2.4.5
- bioconda::bioconductor-spatialexperiment=1.16.0
- bioconda::bioconductor-spasim=1.8.0
87 changes: 87 additions & 0 deletions modules/local/segmentationreport/main.nf
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
process SEGMENTATIONREPORT {
tag "$meta.id"
label 'process_high'

conda "${moduleDir}/environment.yml"
container 'ghcr.io/wehi-soda-hub/spatialvis:0.1.4'

input:
tuple val(meta),
path(annotations),
val(run_mesmer),
val(run_cellpose),
val(nuclear_channel),
val(membrane_channels)

output:
tuple val(meta), path("*/*.html"), path("*/*_files/*"), emit: report
tuple val(meta), path("*/*.rds"), emit: rds, optional: true
path "versions.yml" , emit: versions

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

script:
def prefix = task.ext.prefix ?: "${meta.id}"
def args = task.ext.args ?: ''
"""
Rscript -e "spatialVis::copy_report_template(
template_name = 'segmentation_report_template.qmd',
output_dir = '.',
overwrite = TRUE
)"
quarto render segmentation_report_template.qmd \\
--to html \\
--no-cache \\
--output ${prefix}.html \\
${args} \\
-P sample_name:${meta.id} \\
-P geojson_file:${annotations} \\
-P nuclear_channel:${nuclear_channel} \\
-P membrane_channels:"${membrane_channels}" \\
-P run_cellpose:${run_cellpose} \\
-P cellpose_diameter:${params.cellpose_diameter} \\
-P cellpose_min_area:${params.cellpose_min_area} \\
-P cellpose_flow_threshold:${params.cellpose_flow_threshold} \\
-P cellpose_cellprob_threshold:${params.cellpose_cellprob_threshold} \\
-P cellpose_model_type:${params.cellpose_model_type} \\
-P cellpose_pretrained_model:${params.cellpose_pretrained_model} \\
-P run_mesmer:${run_mesmer} \\
-P mesmer_segmentation_level:${params.mesmer_segmentation_level} \\
-P mesmer_maxima_threshold:${params.mesmer_maxima_threshold} \\
-P mesmer_interior_threshold:${params.mesmer_interior_threshold} \\
-P mesmer_maxima_smooth:${params.mesmer_maxima_smooth} \\
-P mesmer_min_nuclei_area:${params.mesmer_min_nuclei_area} \\
-P mesmer_remove_border_cells:${params.mesmer_remove_border_cells} \\
-P mesmer_pixel_expansion:${params.mesmer_pixel_expansion} \\
-P mesmer_padding:${params.mesmer_padding}

mkdir -p ${prefix}
mv ${prefix}.html ${prefix}
if [[ -f ${prefix}.rds ]]; then
mv ${prefix}.rds ${prefix}
fi
cp -r segmentation_report_template_files ${prefix}

cat <<-END_VERSIONS > versions.yml
"${task.process}":
r-base: \$(Rscript -e "cat(as.character(getRversion()))")
spatialVis: \$(Rscript -e "cat(as.character(packageVersion('spatialVis')))")
END_VERSIONS
"""

stub:
def prefix = task.ext.prefix ?: "${meta.id}"
"""
mkdir -p ${prefix}/segmentation_report_template_files
touch ${prefix}/${prefix}.html
touch ${prefix}/${prefix}.rds
touch ${prefix}/segmentation_report_template_files/foo.txt

cat <<-END_VERSIONS > versions.yml
"${task.process}":
r-base: \$(Rscript -e "cat(as.character(getRversion()))")
spatialVis: \$(Rscript -e "cat(as.character(packageVersion('spatialVis')))")
END_VERSIONS
"""
}
61 changes: 61 additions & 0 deletions modules/local/segmentationreport/tests/main.nf.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
nextflow_process {

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

tag "modules"
tag "modules_nfcore"
tag "segmentationreport"

test("segmentationreport - basic functionality") {

when {
process {
"""
input[0] = [
[ id:'test' ], // meta map
file('/vast/scratch/users/cmero.ma/results/spatialproteomics_test/cellmeasurement/test.geojson'),
true,
false
]
"""
}
}

then {
assertAll(
{ assert process.success },
{ assert snapshot(process.out).match() }
)
}

}

test("segmentationreport - stub") {

options "-stub"

when {
process {
"""
input[0] = [
[ id:'stub_test' ], // meta map
file('/vast/scratch/users/cmero.ma/results/spatialproteomics_test/cellmeasurement/test.geojson'),
true,
false
]
"""
}
}

then {
assertAll(
{ assert process.success },
{ assert snapshot(process.out).match() }
)
}

}

}
150 changes: 150 additions & 0 deletions modules/local/segmentationreport/tests/main.nf.test.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
{
"segmentationreport - basic functionality": {
"content": [
{
"0": [
[
{
"id": "test"
},
"test.html:md5,978a4490880fb404e965aea8838d8f37",
[
[
"channel_intensities-1.png:md5,6a0c625d84e7e87344c68e83bd508505",
"compartment_measurements-1.png:md5,67e32361a18ca731b2b69e77c3ef3133",
"unnamed-chunk-1-1.png:md5,1bc975856040cdf40996f4a9ecee2af7"
],
[
[
"bootstrap-bb462d781dde1847d9e3ccf7736099dd.min.css:md5,bb462d781dde1847d9e3ccf7736099dd",
"bootstrap-icons.css:md5,2318f137d201fbf51601fa0faba6bef7",
"bootstrap-icons.woff:md5,1a51dfbe5467607389d603c02a77cd10",
"bootstrap.min.js:md5,e2b09c06f0e714b6144a6788a28e3950"
],
[
"clipboard.min.js:md5,15f52a1ee547f2bdd46e56747332ca2d"
],
[
"anchor.min.js:md5,91bcf6cd1fd88fec3f109811e5fc3546",
"popper.min.js:md5,3a4a291ccf3f1ea0f82b641542fd3827",
"quarto-syntax-highlighting-c8ad9e5dbd60b7b70b38521ab19b7da4.css:md5,c8ad9e5dbd60b7b70b38521ab19b7da4",
"quarto.js:md5,6f4245742deb3cc92337dbe2701ccfe1",
[
"tabsets.js:md5,e8d6a8862b33ccc461f8fd532e05b365"
],
"tippy.css:md5,ebd6f8ce46a677e1a4f5f8a8317109a9",
"tippy.umd.min.js:md5,d828775275749701b48bd9d958814111"
]
]
]
]
],
"1": [

],
"2": [
"versions.yml:md5,a36295eacb48d679e04b9d2c71e4f0eb"
],
"rds": [

],
"report": [
[
{
"id": "test"
},
"test.html:md5,978a4490880fb404e965aea8838d8f37",
[
[
"channel_intensities-1.png:md5,6a0c625d84e7e87344c68e83bd508505",
"compartment_measurements-1.png:md5,67e32361a18ca731b2b69e77c3ef3133",
"unnamed-chunk-1-1.png:md5,1bc975856040cdf40996f4a9ecee2af7"
],
[
[
"bootstrap-bb462d781dde1847d9e3ccf7736099dd.min.css:md5,bb462d781dde1847d9e3ccf7736099dd",
"bootstrap-icons.css:md5,2318f137d201fbf51601fa0faba6bef7",
"bootstrap-icons.woff:md5,1a51dfbe5467607389d603c02a77cd10",
"bootstrap.min.js:md5,e2b09c06f0e714b6144a6788a28e3950"
],
[
"clipboard.min.js:md5,15f52a1ee547f2bdd46e56747332ca2d"
],
[
"anchor.min.js:md5,91bcf6cd1fd88fec3f109811e5fc3546",
"popper.min.js:md5,3a4a291ccf3f1ea0f82b641542fd3827",
"quarto-syntax-highlighting-c8ad9e5dbd60b7b70b38521ab19b7da4.css:md5,c8ad9e5dbd60b7b70b38521ab19b7da4",
"quarto.js:md5,6f4245742deb3cc92337dbe2701ccfe1",
[
"tabsets.js:md5,e8d6a8862b33ccc461f8fd532e05b365"
],
"tippy.css:md5,ebd6f8ce46a677e1a4f5f8a8317109a9",
"tippy.umd.min.js:md5,d828775275749701b48bd9d958814111"
]
]
]
]
],
"versions": [
"versions.yml:md5,a36295eacb48d679e04b9d2c71e4f0eb"
]
}
],
"meta": {
"nf-test": "0.9.2",
"nextflow": "25.04.4"
},
"timestamp": "2025-09-01T13:49:50.280243443"
},
"segmentationreport - stub": {
"content": [
{
"0": [
[
{
"id": "stub_test"
},
"stub_test.html:md5,d41d8cd98f00b204e9800998ecf8427e",
"foo.txt:md5,d41d8cd98f00b204e9800998ecf8427e"
]
],
"1": [
[
{
"id": "stub_test"
},
"stub_test.rds:md5,d41d8cd98f00b204e9800998ecf8427e"
]
],
"2": [
"versions.yml:md5,a36295eacb48d679e04b9d2c71e4f0eb"
],
"rds": [
[
{
"id": "stub_test"
},
"stub_test.rds:md5,d41d8cd98f00b204e9800998ecf8427e"
]
],
"report": [
[
{
"id": "stub_test"
},
"stub_test.html:md5,d41d8cd98f00b204e9800998ecf8427e",
"foo.txt:md5,d41d8cd98f00b204e9800998ecf8427e"
]
],
"versions": [
"versions.yml:md5,a36295eacb48d679e04b9d2c71e4f0eb"
]
}
],
"meta": {
"nf-test": "0.9.2",
"nextflow": "25.04.4"
},
"timestamp": "2025-09-01T13:12:50.675902625"
}
}
Loading