Last updated: 2025-11-26
Checks: 7 0
Knit directory:
2025_cytoconnect_spatial_workshop/
This reproducible R Markdown analysis was created with workflowr (version 1.7.2). The Checks tab describes the reproducibility checks that were applied when the results were created. The Past versions tab lists the development history.
Great! Since the R Markdown file has been committed to the Git repository, you know the exact version of the code that produced these results.
Great job! The global environment was empty. Objects defined in the global environment can affect the analysis in your R Markdown file in unknown ways. For reproduciblity it’s best to always run the code in an empty environment.
The command set.seed(20251002) was run prior to running
the code in the R Markdown file. Setting a seed ensures that any results
that rely on randomness, e.g. subsampling or permutations, are
reproducible.
Great job! Recording the operating system, R version, and package versions is critical for reproducibility.
Nice! There were no cached chunks for this analysis, so you can be confident that you successfully produced the results during this run.
Great job! Using relative paths to the files within your workflowr project makes it easier to run your code on other machines.
Great! You are using Git for version control. Tracking code development and connecting the code version to the results is critical for reproducibility.
The results in this page were generated with repository version a88eee1. See the Past versions tab to see a history of the changes made to the R Markdown and HTML files.
Note that you need to be careful to ensure that all relevant files for
the analysis have been committed to Git prior to generating the results
(you can use wflow_publish or
wflow_git_commit). workflowr only checks the R Markdown
file, but you know if there are other scripts or data files that it
depends on. Below is the status of the Git repository when the results
were generated:
Ignored files:
Ignored: .DS_Store
Ignored: data/.DS_Store
Ignored: data/imc/
Ignored: data/visium/
Unstaged changes:
Modified: analysis/imc_02.Rmd
Note that any generated files, e.g. HTML, png, CSS, etc., are not included in this status report because it is ok for generated content to have uncommitted changes.
These are the previous versions of the repository in which changes were
made to the R Markdown (analysis/imc_01.Rmd) and HTML
(docs/imc_01.html) files. If you’ve configured a remote Git
repository (see ?wflow_git_remote), click on the hyperlinks
in the table below to view the files as they were in that past version.
| File | Version | Author | Date | Message |
|---|---|---|---|---|
| Rmd | a88eee1 | Givanna Putri | 2025-11-26 | wflow_publish("analysis/imc_01.Rmd") |
In this part of IMC analysis, we will focus on cell segmentation using popular tools QuPath and ImageJ with the Mesmer plugin. Accurate cell segmentation is crucial for downstream analysis, as it allows us to quantify marker expressions at the single-cell level.
To be able to follow this tutorial, please ensure you have the following software installed:
For this tutorial, we will be using the 35-marker IMC panel on FFPE
human intestines. The paper describing the data:
https://doi.org/10.1002/cyto.a.24847. The panel is designed
to delineate various immune cell subsets and HIV RNA.
The data can be downloaded either from the Google drive link in the setup page or from Zenodo.
The dataset is provided as single channel tiff files for each marker. Hence when you download the data, you will get a folder structure like this:
imc
└──tif_files
└── originalimages
├── aSMA.tif
├── Axl.tif
├── CCR6.tif
└── ...
As previously mentioned in the introduction, we generally use membrane/cytoplasm and nuclei markers for segmentation. For this dataset, we will use 3 membrane markers: CD45, E-cadherin, and NaKATPase. For nuclei, we will use DNA1.
We will first prepare the merged membrane marker using ImageJ.
First, open CD45.tif, Ecadherin.tif,
NaKATPase.tif in ImageJ. One window per image.
Go to Process → Image Calculator to add the channels in order.

Select image 1 as the CD45 and Ecadherin as image 2. You will then get a new window called Result of CD45 as a result.

Save it as CD45_Ecadherin.tif somewhere in your
computer.

Repeat the process but select CD45_Ecadherin.tifas image
1 and NAKatPase as image 2.

You will get a new window called Result of CD45_Ecad.tif
window.

Add a small amount of gaussian filter to blur them out so they don’t look like 3 separate channels. Go to Process → Filters → Gaussian Blur with small sigma (1–2 px).

You can tick the “Preview” checkbox to see what the resulting image will look like after applying certain sigma values. The lower the sigma value, the less blurring there will be.

Then save the image as membrane_channel_combined.tif
Before sending the tif file to mesmer for segmentation, we need to first create a stack tif file containing the DNA1 tif file and the merged membrane channel tif file we created in the previous section.
First open the DNA1 image and the merged membrane channel tif file in that order. The order is important because Mesmer require the nuclei stain to be the very 1st layer, followed by the membrane layer. Opening the DNA1 first will allow us to specify the DNA channel as as the very 1st layer, followed by the membrane channel.

Next, go to Image → stacks → Image to Stack.

You should get a stacked image. Make sure stack 1 of 2 is DNA1 and
stack 2 is the membrane channel. You can flick through the stack using
the left and right arrow key. If the stacks are ordered correctly, you
should see 1/2 next to the DNA1 and 2/2 next to
membrane_channel_combined.


Save the image, use any file name.
Now we are ready to send our file to DeepCell server to be segmented.
With the stack image open, select the image, then go to Plugin → DeepCell Kiosk → Submit Active Image.

You can leave the config as it is and just click ok.


On the menu bar you should see a progress bar showing the status.

When it is done you should get the segmentation mask.

Before we can import the segmentation to Qupath for closer inspection, we have to first generate the label overlay. Go to Plugin → DeepCell Kiosk → Create label overlay.

Once it is done you will get the cell outlines like so.

You can then export it by going to Image → Overlay → To ROI manager.

Select more at bottom right of the window then save.

It should export the ROI as a zip file.
Before we can view all the channels in QuPath, we need to first convert all the single channel tif files (1 channel = 1 tif file) into one stack image and save it.
We can do this using ImageJ.
Go to File → Import → Image Sequence. Click browser to select the folder containing all the tif files and click ok.

When done, you should have an image with 37 channels. You can switch between different channels using the left and right arrow keys. You can also zoom in and out of the image using the up and down arrow keys respectively.


We have to then convert it to hyperstack. Go to Image → Hyperstacks → Stack to Hyperstack.
Set the channels to how many markers you have (37 in this dataset) and slice to 1. Leave the order as it is as that’s how qupath likes it.

Then save as tiff file.
Create a qupath project by clicking on the
create project... button on the right.

Pick where you want to store the files associated with the project.

You will then be asked to add images to the QuPath project. Click
Choose files and select the directory where you store the
tif stack tif file we created in the previous section. Then click
import.

Qupath will ask you what type of image this is. Its guesses are generally pretty accurate.

By default, it will show all the channels and there will be overlaps in colours.
To pick which channel to show, go to View → Brightness/Contrast. Untick all channels, then tick whichever you want to show. In example below, I picked DNA1 (red) and CD45 (turquoise).

When you view a stack image (DNA1 and CD45 in the example above), each channel is shown with its own intensity range. The range is usually from the minimum pixel value to the maximum pixel value.
If a channel has very bright signal (like DNA1 in our example above), it can visually dominate the display. We can reduce its apparent brightness by changing how QuPath maps pixel intensities to display brightness.
You can either move the max slider to the right, which tells qupath any value larger than this threshold will be “whiter”. Or move the min slider to the right, which tells qupath to exaggerate the contrast.

This doesn’t change your data — it just changes how it’s visualized.
You can zoom in and out of the image by scrolling on the image up or down. The scale of the zoom is showed on the bottom left of the image.
Zoom out:

Zoom in:

Go to Extensions → ImageJ → Import ImageJ ROIs.

Select the zip file we exported from ImageJ and click ok. You should now get the segmentation masks overlaid over the image.

We can now scrutinise this segmentation result. Toggle few channels and see how it looks. Zoom in and out the tissue.
If you find a weird or just plain wrong segmentation, like below where we have cells missing DNA (red) - double clicking on a cell should focus on it. You can delete it by just pressing the delete key.

Or if you get an overzealous one like the one below, you can select it by double clicking and drag the nodes around to reshape it.

Qupath should automatically merge the nodes or create new ones as you drag them around.

To do any downstream analyses using R, we need to export the segmentation masks out into a csv file that we can read in using R.
To do this, we need to first get Qupath to measure the intensity of each channel.
First, go to Annotations tab on the menu panel on the left and click select all.

This should select all segmentation masks, like so.

Go to Analyze → Calculate Features → Add intensity features.

Tick all the channels one after another. Scroll down and select mean.

This tells Qupath to export out the mean intensity for each mask. There are other options, median, SD, min & max. If you want you can select more than one options. Then hit run.
It will then ask you to what to process, select “Selected objects” then click ok.

When it finishes, the panel at bottom left should be populated with intensity measurements for all channels.

When exporting masks, QuPath needs to know what masks to export. To tell it this, we need to assign an annotation to the masks.
Go to Annotations tab on the left panel, click “Select all” button on the Annotation list panel (like before). It shall highlight all the masks. Then pick “Other” on the class list panel and click “Set select…” button in the panel.

You should then see in the bottom left panel the classification row will say “other”.

You can also of course, annotate your cells by flicking through various channels, selecting individual masks and annotate them. BUT! This is best done using more traditional high dimensional approach.
However if you prefer to do it using Qupath..
On the class list panel, click on the + button to create a new label. Say CD4 T cell.

Select a mask that you think represents CD4 T cell. Then select the CD4 T cell class in the class list, then click set selected.

Then panel on the bottom left, should show CD4 T cell in the
classification row.

We can repeat for different class, say CD8 T cell.

Now we can finally export the masks.
Click “Select all” in the annotation list panel on the left, just like before. Go to Measure → Export measurements.

Select the tif file on the available panel on the left and click >> to transfer it to the right panel. Set the output file to whatever the filename you want to export it to.

Select “Annotations” as “Export type”, and csv as “Separator”. Click the populate button.

Tick everything you want to export.

Then click export and save it to
data/imc/measurements.csv folder you created in the setup
step.
You should now have a csv file like this that you can now export to R.

sessionInfo()
R version 4.5.1 (2025-06-13)
Platform: aarch64-apple-darwin20
Running under: macOS Sequoia 15.5
Matrix products: default
BLAS: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRblas.0.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.1
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
time zone: Australia/Melbourne
tzcode source: internal
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] workflowr_1.7.2
loaded via a namespace (and not attached):
[1] vctrs_0.6.5 httr_1.4.7 cli_3.6.5 knitr_1.50
[5] rlang_1.1.6 xfun_0.53 stringi_1.8.7 processx_3.8.6
[9] promises_1.3.3 jsonlite_2.0.0 glue_1.8.0 rprojroot_2.1.1
[13] git2r_0.36.2 htmltools_0.5.8.1 httpuv_1.6.16 ps_1.9.1
[17] sass_0.4.10 rmarkdown_2.29 jquerylib_0.1.4 tibble_3.3.0
[21] evaluate_1.0.5 fastmap_1.2.0 yaml_2.3.10 lifecycle_1.0.4
[25] whisker_0.4.1 stringr_1.5.2 compiler_4.5.1 fs_1.6.6
[29] pkgconfig_2.0.3 Rcpp_1.1.0 rstudioapi_0.17.1 later_1.4.4
[33] digest_0.6.37 R6_2.6.1 pillar_1.11.0 callr_3.7.6
[37] magrittr_2.0.4 bslib_0.9.0 tools_4.5.1 cachem_1.1.0
[41] getPass_0.2-4