knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
library(ARCOS)
library(ggplot2)

# Custom functions
myPlotBinTS <- function(objTS, 
                        xlim = c(0,65), 
                        ylim = c(65,0),
                        ncol = 4,
                        plotType = c("raster", "point"),
                        pointSize = 1) {

  locFrame = attr(objTS, "colFrame")
  locPos = attr(objTS, "colPos")
  locM = attr(objTS, "colMeas")

  locP = ggplot(objTS,
                aes(x = get(locPos[2]),
                    y = get(locPos[1])))

  if (plotType == "point") {
    locP = locP +
      geom_point(aes(color = as.factor(get(locM))), size = pointSize) +
      scale_color_manual(values = c("grey80",
                                    "grey20"))
  } else if (plotType == "raster") {
    locP = locP +
      geom_raster(aes(fill = as.factor(get(locM)))) +
      scale_fill_manual(values = c("grey80",
                                   "grey20"))
  }

  locP = locP +
    facet_wrap(locFrame, ncol = ncol) +
    coord_fixed(ratio=1) +
    scale_x_continuous(limits = xlim) +
    scale_y_continuous(trans = "reverse", limits = ylim) +
    theme_void() +
    theme(text = element_text(size = 20),
          legend.position = "none")

  return(locP)
}

myPlotRasterColl <- function(objColl, 
                             xlim = c(0,65), 
                             ylim = c(65,0),
                             ncol = 4) {
  ggplot(objColl,
         aes(x = x,
             y = y)) +
    geom_raster(aes(fill = as.factor(collid))) +
    scale_fill_discrete(name = "ID coll.") +
    facet_wrap(~ frame, ncol = ncol) +
    coord_fixed(ratio=1) +
    scale_x_continuous(limits = xlim) +
    scale_y_continuous(trans = "reverse", limits = ylim) +
    theme_void() +
    theme(text = element_text(size = 20),
          legend.position = "none")
}

Introduction

Here we demonstrate tracking of collective events from a sequence of binary images. Each pixel in the image is treated as a separate object, to which an objID number is assigned. The objID identifier is the same for objects with the same X/Y position in subsequent frames.

Pixel values, the measurement, assume values 0 or 1, which correspond to an inactive or active object, respectively. Objects with value 1 are active and our goal is to detect and track a collective activation of all such objects in all frames of the time sequence.

Growing circle

Here we detect and track a concentrically growing cluster of active objects directly from a sequence of 12 binary PNG images at 64-by-64 pixel resolution.

Read data from images

We use the loadDataFromImages function to read data from PNG images. The function returns an arcosTS object, which is a data.table with additional attributes that specify position, measurement, and object ID columns.

Since images are binary, we set the thres parameter to -1 to load all data, zeroes and ones, without thresholding.

dtIm = ARCOS::loadDataFromImages(file.path(system.file("testImagePatterns/concentricGrowth",
                                                       package="ARCOS"), 
                                           "png_64"),
                                 "*.png", 
                                 thres = -1)

The final long-format input data with active/inactive objects. Columns:

knitr::kable(head(dtIm))

Time sequence

Here we plot the time sequence with a growing concentric circle. Each frame (time point) consists of a matrix of 64x64 objects, where each object can assume a value of 0 (inactive) or 1 (active). In this example objects do not change their positions or identities across frames. In general, both positions and object identifiers may change between frames.

myPlotBinTS(dtIm, plotType = "raster")

Detection and tracking

We use the ARCOS::trackColl function to identify collective events in frames and to link them between frames.

dtColl = ARCOS::trackColl(dtIm[m>0])

The result from tracking of collective events is a long-format data.table with columns:

knitr::kable(head(dtColl))

Visualisation

Here we display frames only with collective events. The algorithm identified a single event in 10 subsequent frames:

myPlotRasterColl(dtColl)

Moving circle

Detect and track a moving cluster of active objects directly from a sequence of 16 binary PNG images at 64-by-64 pixel resolution.

dtIm = ARCOS::loadDataFromImages(file.path(system.file("testImagePatterns/movingCircle",
                                                       package="ARCOS"), 
                                           "png_64"),
                                 "*.png", 
                                 thres = -1)
myPlotBinTS(dtIm, plotType = "raster")
dtColl = ARCOS::trackColl(dtIm[m>0])
myPlotRasterColl(dtColl)

Split circle

Detect and track a cluster of active objects that splits into two parts. Analysis directly from a sequence of 15 binary PNG images at 64-by-64 pixel resolution.

The analysis should consider everything as a single cluster even after the split.

dtIm = ARCOS::loadDataFromImages(file.path(system.file("testImagePatterns/splitCircle",
                                                       package="ARCOS"), 
                                           "png_64"),
                                 "*.png", 
                                 thres = -1)
myPlotBinTS(dtIm, plotType = "raster", ncol = 5)
dtColl = ARCOS::trackColl(dtIm[m>0])
myPlotRasterColl(dtColl, ncol = 5)

Moving multiple circles

Detect and track multiple moving clusters of active objects directly from a sequence of 9 binary PNG images at 64-by-64 pixel resolution.

dtIm = ARCOS::loadDataFromImages(file.path(system.file("testImagePatterns/movingMulti",
                                                       package="ARCOS"), 
                                           "png_64"),
                                 "*.png", 
                                 thres = -1)
myPlotBinTS(dtIm, plotType = "raster", ncol = 3)
dtColl = ARCOS::trackColl(dtIm[m>0])
myPlotRasterColl(dtColl, ncol = 3)

Multiple events

Detect and track multiple activation sites from a sequence of 20 binary PNG images at 32-by-32 pixel resolution. Sprites created with Piskel web-app. Small random Gaussian noise added to X/Y positions.

dtIm = ARCOS::loadDataFromImages(file.path(system.file("testImagePatterns/4events",
                                                       package="ARCOS"), 
                                           "png_32"),
                                 "*.png", 
                                 thres = -1)

# add Gaussian noise to X/Y
# Change the seed to explore other configurations
set.seed(7)

dtIm[,
     `:=`(x = x + rnorm(.N, 0, .1),
          y = y + rnorm(.N, 0, .1))]
myPlotBinTS(dtIm,
            xlim = c(0,25), ylim = c(25,0), 
            plotType = "point", 
            ncol = 5, pointSize = .25)
dtColl = ARCOS::trackColl(dtIm[m > 0], eps = 2.5)

# Create convex hulls around collective events fro visualisation
dtCollCH = dtColl[,
                  .SD[grDevices::chull(x, y)],
                  by = .(frame,
                         collid)]
# Colour palette from ggthemes::tableau_color_pal(palette = "Tableau 10")(10)
# to avoid dependency on ggthemes.
myColorPal = c("#4E79A7", "#F28E2B", "#E15759",
               "#76B7B2", "#59A14F", "#EDC948",
               "#B07AA1", "#FF9DA7", "#9C755F",
               "#BAB0AC")

p2 = ggplot(dtIm,
            aes(x = x,
                y = y)) +
  geom_point(aes(color = as.factor(m)), size = .25) +
  scale_color_manual(values = c("0" = "grey80",
                                "1" = "grey20")) +
  ggnewscale::new_scale_color() +
  geom_point(data = dtColl,
             aes(color = as.factor(collid)), size = .1) +
  geom_polygon(data = dtCollCH,
               aes(color = as.factor(collid)),
               fill = NA, 
               size = 0.5) +
  facet_wrap(~ frame, ncol = 5) +
  coord_fixed(ratio=1) +
  scale_x_continuous(limits = c(0, 25)) +
  scale_y_continuous(trans = "reverse", limits = c(25, 0)) +
  theme_void() +
  theme(text = element_text(size = 20),
        legend.position = "none")

p2


dmattek/ARCOS documentation built on Dec. 5, 2024, 11:02 p.m.