The count_touching_cells function uses morphological analysis of nuclear and membrane segmentation maps to find touching cells of paired phenotypes. It reports the number of touching cells found and, optionally, writes image files showing the touching cells.

count_touching_cells uses the results of inForm cell segmentation to determine which cells are touching. It uses both nuclear and membrane segmentation to determine the extent of each cell.

Count touching cells with existing phenotypes

count_touching_cells processes a single field and multiple pairs of phenotypes. The specification of pairs and phenotypes is flexible to accommodate any requirement. The simplest case uses the phenotype names from inForm to select cells. For this case, only the pairs argument is needed.

For example, this code finds and visualizes touches between CK+ and CD8+ cells, and also between CK+ and CD68+ cells:


cell_seg_path <- sample_cell_seg_path()
pairs <- list(
  c('CK+', 'CD8+'),
  c('CK+', 'CD68+')
colors <- list('CK+'='cyan', 'CD8+'='yellow', 'CD68+'='magenta')
count_touching_cells(cell_seg_path, pairs, colors)

## # A tibble: 2 x 9
##   slide_id  source   phenotype1 phenotype2 total1 total2 p1_touch_p2 p2_touch_p1
##   <chr>     <chr>    <chr>      <chr>       <dbl>  <dbl>       <dbl>       <dbl>
## 1 Set4_1-6~ Set4_1-~ CK+        CD8+         2257    228         148          95
## 2 Set4_1-6~ Set4_1-~ CK+        CD68+        2257    417         250         163
## # ... with 1 more variable: touch_pairs <dbl>

Count touching cells with new phenotypes

For more flexibility, create new compound phenotypes using the phenotype_rules argument. For example, this code repeats the previous analysis limiting it to tumor cells with PDL1 above a threshold. Note that phenotype_rules only needs to include definitions for phenotypes which don’t match the names in pairs.

See the tutorial Selecting cells within a cell segmentation table for more details on selecting pairs.
pairs <- list(
  c('CK+ PDL1+', 'CD8+'),
  c('CK+ PDL1+', 'CD68+')

phenotype_rules <- list(
  'CK+ PDL1+'=list('CK+', ~`Entire Cell PDL1 (Opal 520) Mean`>3)
colors <- list('CK+ PDL1+'='cyan', 'CD8+'='yellow', 'CD68+'='magenta')
count_touching_cells(cell_seg_path, pairs, colors, phenotype_rules)

Count touching cells for multiple fields

Using purrr::map, you can find touching cells for all cell seg data files in a single directory.

# Directory containing data files
base_path <- '/path/to/data'

# A subdirectory for the results
output_base <- file.path(base_path, 'touches')

# All cell seg data files in base_path
files <- list_cell_seg_files(base_path)

# Count and visualize touching cells
touch_counts <- purrr::map_df(files, function(path) {
  cat('Processing', path, '\n')
  count_touching_cells(path, pairs, colors, phenotype_rules,

The result of the above is a tibble which may be written to a CSV file:

touches_path <- file.path(output_base, 'TouchCounts.csv')
readr::write_csv(touch_counts, touches_path)
The tutorial Aggregating touch counts in the phenoptrExamples package demonstrates aggregation across multiple fields from multiple samples.