R语言:将带有规则点的图像转换为数据框

R language: Converting images with regular dots to data frame

我有很多紫外线图像 (*.png),每个图像有 96 个 "wells" 显示为圆圈。这些孔以 8 x 12 的方式排列。请看下面的例子。

在每张图片中,96 个圆圈中的一些点亮了(对紫外线有反应),而另一些则没有。我想给每个圆圈一个数字,然后将它们标识为 "lighted up" 或 "not lighted up"(具有预定义的截止值)。

您认为在 R 中实现此目的的最简单方法是什么? 我一直在玩这个包 imager 但没有成功。

请注意:并非我所有的图像都具有相同的放大倍率(即,圆圈在文件中的大小并不总是相同,但在一个文件中,它们的大小大致相同)。

source("https://bioconductor.org/biocLite.R")
biocLite("EBImage")
library(EBImage)

fn = YOURFILE
img <- readImage(fn)

# remove outer frame
border <- 5 # 5px
dims <- dim(img)
img <- img[border:(dims[1]-border), border:(dims[2]-border),1:dims[3]]

# some despectling
img <- medianFilter(img,size=10) # blur image 
# display(img) # check if blurring is fine

# get plate mask.
highpass_rect <- .02 # if images are darker, lower this
rect <- bwlabel(dilate(img>highpass_rect, makeBrush(size=3)))
rect <- rect > 0 # remove background
# display(rect) # check if plate is recognized correctly

highpass_light <- .4 # again, darker images need lower values here for the light objects
# get single light objects
img <- bwlabel(img > highpass_light)
# display(img) # # check if all lights are displayed

# get dimensions of rectangle
rect_mid <- round(dim(rect@.Data[,,1]) / 2)
x_range <- c(min(which(rect@.Data[,rect_mid[2],1])), max(which(rect@.Data[,rect_mid[2],1])))
y_range <- c(min(which(rect@.Data[rect_mid[1],,1])), max(which(rect@.Data[rect_mid[1],,1])))

# now substract border of plate
# measured from the image you provided. 'should' scale to other images as well
# x: left 69px, right: 74px, ROI: 1085
x_range[1] <- x_range[1] + round((diff(x_range)) * (74/(1085+74+69)))
x_range[2] <- x_range[2] - round((diff(x_range)) * (69/(1085+74+69)))

# y: top 17px, bottom: 12px, ROI: 722
y_range[1] <- y_range[1] + round((diff(y_range)) * (17/(722+17+12)))
y_range[2] <- y_range[2] - round((diff(y_range)) * (12/(722+17+12)))

# get pixel ranges for the 12x8 cells
# we will use this as indexes in df
x_cuts <- c(rep(NA,x_range[1]), cut(x_range[1]:x_range[2],12,dig.lab = 0,include.lowest = T,labels=F))
y_cuts <- c(rep(NA,y_range[1]), cut(y_range[1]:y_range[2],8,dig.lab = 0,include.lowest = T,labels=F))

# create 12X8 matrix
df <- matrix(rep(0,12*8),nrow=8,dimnames = list(levels(cut(y_range,8,dig.lab = 0)),
                                                levels(cut(x_range,12,dig.lab = 0,include.lowest = T))))
# now go through lighted objects
for (i in 1:(dim(table(img))-1)) { 
  # img == i is light nr i
  # get position of object
  pos <- which(img@.Data[,,1] == i, arr.ind = T)

  # add up enlighted pixels in df
  for (row in 1:nrow(pos)) {
    df[y_cuts[pos[row,2]], x_cuts[pos[row,1]]] <- df[y_cuts[pos[row,2]], x_cuts[pos[row,1]]] + 1
  }
}

print(df)

>                  [143,235] (235,326] (326,417] (417,508] (508,599] (599,690] (690,781] (781,872] (872,963] (963,1.05e+03] (1.05e+03,1.14e+03] (1.14e+03,1.24e+03]
> (90,1.8e+02]           1116         0         0         0      1974         0         0         0         0              0                   0                   0
> (1.8e+02,2.7e+02]         0      2528         0         0         0      2528         0         0         0              0                1938                 845
> (2.7e+02,3.6e+02]         0      2479      2518      2430         0         0         0         0         0           2477                2409                   0
> (3.6e+02,4.5e+02]      1731      2339         0      2601         0      2775         0         0         0              0                   0                   0
> (4.5e+02,5.4e+02]         0         0         0      2549         0      1033         0         0         0              0                   0                   0
> (5.4e+02,6.3e+02]      2370      2449      2570         0         0         0         0      2555         0              0                   0                   0
> (6.3e+02,7.3e+02]         0         0         0         0         0         0         0         0         0              0                1836                2348
> (7.3e+02,8.2e+02]         0      1447      1760         0         0         0         0         0         0              0                2303                   0

这可能有点晚了,但这个问题解决了我经常遇到的问题。也许其他人可能会对更通用的解决方案感兴趣。这是一个建议的解决方案,它使用 EBImage 包的更多功能并与用户交互。它可以应用于任何几何形状的多孔培养皿。

# EBImage needs to be available
  if (!require(EBImage)) {
    source("https://bioconductor.org/biocLite.R")
    biocLite("EBImage")
    library(EBImage)
  }

# Read the image and examine it
  fn <- file.choose() # select saved image
  img <- readImage(fn)
  img <- channel(img, "gray") # gray scale for simplicity
  plot(img)

# Define the geometry of the plate
  nx <- 12 # number of columns
  ny <- 8 # number of rows
  nwells <- seq_len(nx*ny) # index of each well in plate

# Use locator() to interactively define the upper left "corner"
# of the top left and bottom right well with a red mark to confirm
  p <- locator(2, type = "p", col = 2)

# Calculate distance between wells and create coordinates in xx, yy
  dx <- abs(diff(p$x))/(nx - 1)
  dy <- abs(diff(p$y))/(ny - 1)
  ix <- (nwells - 1) %% nx  # index for rows
  iy <- (nwells - 1) %/% nx # index for columns
  xx <- ix*dx + p$x[1] # the first point must be the upper left well
  yy <- iy*dy + p$y[1]

# Confirm the calculated coordinates with red dots
  points(xx, yy, pch = 16, col = 2)

现在将创建一个对象掩码以与 computeFeatures 系列函数一起使用。使用以下代码分步创建掩码。

# First a pure black image
  mask <- Image(0, dim = dim(img)) # black image same size as image

# Add white rectangles allowing 5% space between rectangles
  for (x in xx)
    for (y in yy) 
      mask[x:(x + 0.95*dx), y:(y + 0.95*dy)] <- 1

# Convert image to an 'object mask' 
  mask <- bwlabel(mask)

# One could draw circular masks with drawCircle() but this is very,
# very inefficient and doesn't improve the quality of the data
#
# mask <- Image(0, dim = dim(img)) # black image same size as image
# for (x in xx)
#   for (y in yy) 
#     mask <- drawCircle(mask, x + 0.5*dx, y + 0.5*dy,
#       radius = 0.95*dx/2, col = 1, fill = TRUE)

# Show mask on top of original image
  plot(paintObjects(mask, img, col = "white"))

# Use the computeFeatures family of functions to measure the mean intensity
  M <- computeFeatures.basic(mask, img) # a matrix of values is returned
  options(digits = 4)
  head(M)
>   b.mean   b.sd   b.mad  b.q001  b.q005  b.q05 b.q095 b.q099
> 1 0.2800 0.2114 0.18605 0.04706 0.05882 0.2118 0.7451 0.8959
> 2 0.1778 0.0981 0.09303 0.05490 0.06275 0.1569 0.3882 0.4745
> 3 0.1817 0.1028 0.10465 0.05098 0.05882 0.1569 0.4039 0.4824
> 4 0.1854 0.1029 0.10465 0.05490 0.06275 0.1608 0.3961 0.4880
> 5 0.3300 0.2425 0.23256 0.05490 0.06667 0.2510 0.7882 0.9451
> 6 0.1967 0.1076 0.11047 0.05490 0.06275 0.1765 0.4157 0.5216

# Extract the mean intensity and examine with a simple barplot
  val <- M[,"b.mean"]
  barplot(val)

# Combination of median and mad provides a fair estimate of the cutoff
  bgnd <- median(val) + mad(val)
  abline(h = bgnd, col = 2)

# Collect values in labeled data.frame and score the values
  df <- data.frame(row = factor(LETTERS[iy+1]), column = factor(ix+1),
    val = val, positive = val > bgnd)
  head(df)
>   row column    val positive
> 1   A      1 0.2800     TRUE
> 2   A      2 0.1778    FALSE
> 3   A      3 0.1817    FALSE
> 4   A      4 0.1854    FALSE
> 5   A      5 0.3300     TRUE
> 6   A      6 0.1967    FALSE

# And for visual confirmation...or to check the background value
  plot(img)
  text(xx[df$positive], yy[df$positive], "+", col = 2, cex = 2)