The biopixR package includes an image of microbeads as an example to demonstrate its analytical and processing abilities for biological imagery. This sample image displays the package’s features, enabling users to experiment with image analysis and manipulation within the contexts of biotechnology and life sciences. Researchers and practitioners can utilize this illustration to comprehend the application of biopixR to their individual imaging requirements, whether pertaining to cell biology, microscopy, or any other biological imaging applications.
library(biopixR)
plot(beads)
As the biopixR package primarily uses the image processing packages imager and magick, the examples are stored in the specific imager class: cimg.
class(beads)
[1] "cimg" "imager_array" "numeric"
The objective of this task is to extract important information from
an image consisting of microbeads. As a preliminary step, it is
essential to distinguish between individual microbeads and acquire their
corresponding coordinates or positions. The objectDetection
function uses edge detection and labeling techniques to gather elaborate
information about the microbeads, allowing for the identification and
differentiation of individual particles according to their unique edges.
This procedure assists in deriving precise coordinates for every
microbead in the image, which sets the groundwork for subsequent
analysis and characterization of the microbeads within the biopixR
package.
res_objectDetection <-
objectDetection(beads, method = 'edge', alpha = 1, sigma = 0)
This function generates a list of objects. Let’s examine the specific outcomes and explore methods for visualizing them, starting with the center coordinates of the microbeads:
plot(beads)
with(
res_objectDetection$centers,
points(
res_objectDetection$centers$mx,
res_objectDetection$centers$my,
col = factor(res_objectDetection$centers$value),
pch = 19
)
)
Upon closer examination, it is evident that each individual microbead is identified accurately by a singular point at its center, and their distinctiveness is conveyed through varying colors, aligning with our intended objective. However, the identification of clotted microbeads, referred to as doublets or multiplets, deviates from the expected pattern. Notably, not every visually distinguishable microbead is marked with a distinct color. The behavior observed, where doublets are identified as a single entity, is due to their edges disappearing along the contact surface. The same principle applies to multiplets, where the consecutive edges of clotted beads cause them to be treated as a unified, larger object.
Let’s examine the next output from objectDetection
. This
function captures the coordinates of labeled regions, providing precise
details about the position of each microbead. By leveraging another
function within the package, changePixelColor
, we can
selectively color-specific coordinates in a cimg. Thus, we can apply
this function to tint all the extracted coordinates in the microbead
image and assess whether the outcome aligns with our expectations.
changePixelColor(
beads,
res_objectDetection$coordinates,
color = factor(res_objectDetection$coordinates$value),
visualize = TRUE
)
The visual depiction shows that all relevant coordinates were
successfully retrieved, with each variant (single microbeads, doublets,
and multiplets) colored accordingly. As previously stated, these clotted
microbeads should be excluded from further consideration. The difference
in size serves as a critical factor for efficient sorting and subsequent
analysis. Therefore, the selected parameter for addressing these
microbeads will be their size. The next section will provide a detailed
explanation of the sizeFilter
application process.
Before delving into the available filter functions in the package,
let us first examine the internal visualization feature of the
objectDetection
function. The edges identified by the
edgeDetection
function are visually emphasized with color,
simplifying the adjustment of the threshold parameter (alpha) in the
objectDetection
function. In addition, the identified
centers are represented as green circles. This visualization is
particularly useful in determining the smoothing factor (sigma).
Sometimes, smoothing is necessary to improve the recognition of complete
objects and prevent the marking of fragmented edges.
res_objectDetection$marked_objects |> plot()
Nonetheless, a crucial differentiation occurs in obtaining the
highlighted microbeads as a cimg, which opens up possibilities for the
creation of an interactive tool using tcltk. This step facilitates the
development of an interactive interface, empowering users to dynamically
explore the adjustment of various variables and observe the
corresponding shifts in detected microbeads. The interactive interface
is presented through the interactive_objectDetection
function within the biopixR package.
As previously stated, it is crucial to remove doublets and multiplets
before performing the analysis. This objective will be addressed in this
section using the sizeFilter
. The filter is applied to the
image using previously obtained coordinates and centers, with specified
lower and upper limits. If more objects are identified, automated limit
calculation becomes available based on the interquartile range (IQR) of
the size distribution. To simplify limit selection in cases of
insufficient detected objects, the function will issue a warning and
generate a size distribution.
#res_sizeFilter <- sizeFilter(
# centers = res_objectDetection$centers,
# coordinates = res_objectDetection$coordinates,
# lowerlimit = "auto",
# upperlimit = "auto"
#)
As shown by the size distribution, there two larger objects (doublet - size: >150 px; multiplet - size: >400 px). Therefore, the limits will be set accordingly. In some cases, it can be difficult to achieve continuous edges around multiplets, which can lead to the detection of multiple small objects that correspond to the edges of the multiplet. To address this issue, it is possible to set a lower limit to exclude results that may be affected by this phenomenon.
res_sizeFilter <- sizeFilter(
centers = res_objectDetection$centers,
coordinates = res_objectDetection$coordinates,
lowerlimit = 0,
upperlimit = 150
)
visualization sizeFilter:
changePixelColor(
beads,
res_sizeFilter$coordinates,
color = "darkgreen",
visualize = TRUE
)
The goal of excluding clotted microbeads from the analysis has been achieved successfully. As shown in the image above, the resulting data set now only includes individual microbeads.
When microbeads are in close proximity, they can induce fluorescence
in each other. This phenomenon can lead to misleading signals and
contribute to false positives during analysis. To prevent distorted
results, the proximityFilter
is used in subsequent steps.
This function inspects each gathered center and surveys a defined radius
for positive pixels. If another positive pixel from a different object
is detected within this range, both are discarded because of their
proximity. The radius can be selected manually or determined
automatically. In the automatic calculation, the size of the remaining
microbeads is determined in the first step. The radius is then
calculated using the following formula, assuming a circular object:
\[ \text{radius} = \sqrt{\frac{A}{\pi}} \]
The function specifies that the scanned area from the center of the microbead is twice the radius, ensuring that the minimum distance to another microbead is half a microbead (only if radius = ‘auto’). Note that the coordinates obtained from the objectDetection function should be used as they are not filtered and therefore include all coordinates. This ensures the accurate exclusion of microbeads that are in close proximity to doublets or multiplets.
res_proximityFilter <-
proximityFilter(
centers = res_sizeFilter$centers,
coordinates = res_objectDetection$coordinates,
radius = "auto"
)
visualization proximityFilter:
changePixelColor(
beads,
res_proximityFilter$coordinates,
color = "darkgreen",
visualize = TRUE
)
text(
res_proximityFilter$centers$mx,
res_proximityFilter$centers$my,
res_proximityFilter$centers$value,
col = "grey"
)
The chapter’s objective has been accomplished, as demonstrated by the
most recent plot. The sizeFilter
successfully eliminated
the doublet and multiplet from the dataset. Furthermore, microbeads that
lacked at least half the size of a microbead between them were removed
with the aid of the proximityFilter
. To demonstrate this
result, the changePixelColor
function was once again
utilized, coloring every remaining pixel. Consequently, the microbeads
that remain are highlighted in dark green, indicating their successful
passage through the filtering process.
To conclude this chapter, we need to extract meaningful information
from the filtered data set. One of the most fundamental results to be
displayed after applying a filter is undoubtedly the number of remaining
and discarded objects. As the size of the objects has already been
calculated in both algorithms, this information should also be included
in the display. Moreover, the intensity of the signal is a crucial
parameter for both microbeads and any fluorescent image. Finally, it may
be of interest to calculate the area density, which represents the
percentage of detected pixels (microbeads) relative to the pixel area of
the entire image. To extract this information, the
resultAnalytics
function from the biopixR package is
utilized. This function requires the data frame of the remaining
coordinates, the individual size, and the original image as inputs.
Starting from version 0.2.2, the resultAnalytics
function
can utilize parallel processing with foreach and doParallel, reducing
the required time by approximately two-thirds.
result <-
resultAnalytics(
img = beads,
coordinates = res_proximityFilter$coordinates,
unfiltered = res_objectDetection$coordinates
)
result$detailed
objectnumber size intensity sd_intensity x y
1 3 98 0.593 0.183 9.23 38.0
2 4 96 0.628 0.183 53.27 39.6
3 5 97 0.591 0.177 108.66 43.9
4 6 423 0.682 0.170 98.16 64.6
5 7 84 0.606 0.177 35.39 97.6
6 8 100 0.531 0.167 58.69 101.2
While it’s possible to showcase a detailed version of results featuring individual microbeads with their cluster number, size, intensity, and coordinates, this presentation method can become quite overwhelming, especially when dealing with larger images containing numerous objects. Consequently, the image results are summarized in a single row, emphasizing the key parameters described earlier.
result$summary
number_of_objects mean_size sd_size mean_intensity sd_intensity
1 6 150 134 0.632 0.181
estimated_rejected coverage
1 3 0.0556
A final note regarding the estimated number of rejected objects: As previously mentioned, the detection of multiplets may have limitations. When only the edges are identified, the number of positive pixels is reduced, resulting in an inaccurate estimation of the discarded microbeads. This calculation is obtained by dividing the number of detected pixels by the average size while subtracting the count of centers that have successfully undergone the filter process.
The results generated by the objectDetection
function
can be quickly displayed using the resultAnalytics
function. Therefore, let’s first examine the unfiltered results
available from the image.
result_proximityFilter <-
resultAnalytics(
img = beads,
coordinates = res_objectDetection$coordinates
)
result_proximityFilter$detailed
objectnumber size intensity sd_intensity x y
1 1 93 0.577 0.177 63.97 8.68
2 2 96 0.630 0.189 69.01 20.24
3 3 98 0.593 0.183 9.23 37.97
4 4 96 0.628 0.183 53.27 39.61
5 5 97 0.591 0.177 108.66 43.86
6 6 423 0.682 0.170 98.16 64.61
7 7 84 0.606 0.177 35.39 97.61
8 8 100 0.531 0.167 58.69 101.17
9 9 190 0.637 0.173 22.72 121.02
10 10 94 0.567 0.169 39.50 125.00
To increase versatility, the filter functions can be used
individually, without depending on each other. The following section
examines the results of applying each filter separately. We will begin
with the sizeFilter
. Once again, the output from the
objectDetection
function is used as input.
ind_sizeFilter <- sizeFilter(
centers = res_objectDetection$centers,
coordinates = res_objectDetection$coordinates,
lowerlimit = 50,
upperlimit = 150
)
changePixelColor(
beads,
ind_sizeFilter$coordinates,
color = "darkgreen",
visualize = TRUE
)
text(
ind_sizeFilter$centers$mx,
ind_sizeFilter$centers$my,
ind_sizeFilter$centers$value,
col = "grey"
)
result_sizeFilter <-
resultAnalytics(
img = beads,
coordinates = ind_sizeFilter$coordinates,
unfiltered = res_objectDetection$coordinates
)
result_sizeFilter$detailed
objectnumber size intensity sd_intensity x y
1 1 93 0.577 0.177 63.97 8.68
2 2 96 0.630 0.189 69.01 20.24
3 3 98 0.593 0.183 9.23 37.97
4 4 96 0.628 0.183 53.27 39.61
5 5 97 0.591 0.177 108.66 43.86
6 7 84 0.606 0.177 35.39 97.61
7 8 100 0.531 0.167 58.69 101.17
8 10 94 0.567 0.169 39.50 125.00
As demonstrated in the previous section, the sizeFilter
function successfully removes multiplets and doublets. The resulting
output can then be used directly in the resultAnalytics
function to extract the most important information. The following
section will present the individual use of the
proximityFilter
. The input remains the same as before.
ind_proximityFilter <-
proximityFilter(
centers = res_objectDetection$centers,
coordinates = res_objectDetection$coordinates,
radius = "auto"
)
changePixelColor(
beads,
ind_proximityFilter$coordinates,
color = "darkgreen",
visualize = TRUE
)
text(
ind_proximityFilter$centers$mx,
ind_proximityFilter$centers$my,
ind_proximityFilter$centers$value,
col = "grey"
)
As expected, the proximityFilter
excluded microbeads
that were close to each other. In this situation, a doublet is in close
proximity to a single microbead, so both the doublet and its neighboring
microbead are rejected by the filter.
result_proximityFilter <-
resultAnalytics(
img = beads,
coordinates = ind_proximityFilter$coordinates,
unfiltered = res_objectDetection$coordinates
)
result_proximityFilter$detailed
objectnumber size intensity sd_intensity x y
1 3 98 0.593 0.183 9.23 38.0
2 4 96 0.628 0.183 53.27 39.6
3 5 97 0.591 0.177 108.66 43.9
4 6 423 0.682 0.170 98.16 64.6
5 7 84 0.606 0.177 35.39 97.6
6 8 100 0.531 0.167 58.69 101.2
Another important aspect to consider in bioimaging is the relative distance between individual objects. By examining the spatial relationships between the identified objects, researchers can gain valuable insights into their organization and distribution within the image. This information is critical for understanding biological phenomena such as spatial patterns, cell clustering, or the arrangement of microstructures. In the following sections, we present a method that explores the Euclidean distance between objects to enable users to investigate and quantify spatial relationships within their bioimages.
The relative distance between multiple objects typically refers to the distances between each pair of objects in relation to each other. The method to calculate relative distance depends on the dimensionality of your objects (e.g., 1D, 2D, 3D) and the specific metric you want to use (e.g., Euclidean distance, Manhattan distance). Here, I’ll provide a general approach for 2D objects using the Euclidean distance. Assuming you have a set of points in a 2D space (e.g., x, y coordinates), you can calculate the Euclidean distance between each pair of points. The formula for Euclidean distance between two points ((x1, y1)) and ((x2, y2)) is:
\[ \text{Distance} = \sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2} \]
euclidean_distance <- function(point1, point2) {
sqrt((point2$mx - point1$mx)^2 + (point2$my - point1$my)^2)
}
Now, let’s apply this function on the extracted centers. The centers serve as the initial reference points for measuring the distances between objects. Initially, we create an empty matrix to store the results. Next, we develop a nested for loop to systematically compare each center with every other center, except for comparing a center to itself, resulting in a missing value. This calculation results in a distance matrix.
num_points <- nrow(res_proximityFilter$centers)
relative_distances <-
matrix(NA, nrow = num_points, ncol = num_points)
for (i in 1:num_points) {
for (j in 1:num_points) {
if (i == j) {
next
} else {
relative_distances[i, j] <-
euclidean_distance(
res_proximityFilter$centers[i, ],
res_proximityFilter$centers[j, ]
)
}
}
}
as.matrix(relative_distances)
[,1] [,2] [,3] [,4] [,5] [,6]
[1,] NA 44.1 99.6 92.8 65.1 80.3
[2,] 44.1 NA 55.6 51.4 60.7 61.8
[3,] 99.6 55.6 NA 23.3 90.9 76.0
[4,] 92.8 51.4 23.3 NA 70.9 53.8
[5,] 65.1 60.7 90.9 70.9 NA 23.6
[6,] 80.3 61.8 76.0 53.8 23.6 NA
After removing missing values, the average relative distance for each center is calculated, which provides individual insight into its distance from the other microbeads. The resulting values are then added as a new column to the previously presented detailed results, providing further information about the distinct properties of each microbead.
for (a in 1:num_points) {
result$detailed$relative_distance[a] <-
mean(na.omit(relative_distances[a, ]))
}
result$detailed
objectnumber size intensity sd_intensity x y relative_distance
1 3 98 0.593 0.183 9.23 38.0 76.4
2 4 96 0.628 0.183 53.27 39.6 54.7
3 5 97 0.591 0.177 108.66 43.9 69.1
4 6 423 0.682 0.170 98.16 64.6 58.4
5 7 84 0.606 0.177 35.39 97.6 62.2
6 8 100 0.531 0.167 58.69 101.2 59.1
As mentioned previously, the detailed table results for a larger image would unavoidably become overly extensive. The same issue is applicable to the matrix. Therefore, to overcome this issue, instead of relying solely on the detailed table results, the mean relative distance is calculated and included in the summary table.
result$summary$mean_distance <-
mean(result$detailed$relative_distance)
result$summary
number_of_objects mean_size sd_size mean_intensity sd_intensity
1 6 150 134 0.632 0.181
estimated_rejected coverage mean_distance
1 3 0.0556 63.3
will be part of the resultAnalytics
function as of
version 0.2.0
To further illustrate the package’s capabilities, the following section presents a case study mainly focused on addressing discontinuous edges in image analysis. The study showcases the integration of crucial data from two images to determine the quantities of droplets and microbeads. Additionally, the analysis aims to investigate the frequency of events in which a single microbead joins a droplet, as opposed to situations in which multiple microbeads are present in a single droplet. The study employs an algorithm that focuses on filling gaps along discontinuous edges. This is achieved through a combination of detecting line ends and interpolating pixels. By using this comprehensive method, the study provides valuable perspectives on the distribution between droplets and microbeads in the provided image. These findings demonstrate the flexibility of the package to handle complex image analysis scenarios.
The following images serve as test subjects for the upcoming study. The first image displays a brightfield view showing droplets, some of which contain microbeads. The second image on the left displays the fluorescent channel, exhibiting only the microbeads.
plot(droplets)
plot(droplet_beads)
In typical fashion for image analysis, this study starts by applying
a threshold to the bright-field image. Subsequently, the resulting image
uncovers a distinct challenge: the edges of individual partitions are
not continuous. In order to differentiate individual partitions and
evaluate whether they contain microbeads, it is crucial to bridge these
gaps. Fortunately, the package offers a specialized function,
fillLineGaps
, to address this issue in image analysis. This
algorithm identifies line endpoints and connects them to the nearest
neighboring edge that is not their own. Additionally, the
objectDetection
function can eliminate specific objects,
such as microbeads. This step is crucial for avoiding the unwanted
outcome of line ends becoming connected to microbeads. The code chunks
below, derived from the fillLineGaps
function, visually
demonstrating the elimination of microbeads and the detection of line
ends by highlighting them with the changePixelColor
function.
# preprocessing: threshold, negate and mirroring
thresh <- threshold(droplets, "13%")
thresh_cimg <- as.cimg(thresh)
thresh_magick <- cimg2magick(thresh_cimg)
neg_thresh <- image_negate(thresh_magick)
neg_thresh_cimg <- magick2cimg(neg_thresh)
neg_thresh_m <- mirror(neg_thresh_cimg, axis = "x")
# first remove microbeads from droplet image (important for the linking of
# discontinuous edges, as otherwise they may connect with the microbeads)
# removes objects to prevent reconnecting with labeled regions that
# are not lines/edges
beads_to_del <- droplet_beads
bead_coords <-
objectDetection(beads_to_del, alpha = 1, sigma = 0.1)
# transform binary image to array to modify individual values
thresh_array <- as.array(neg_thresh_m)
for (i in seq_len(nrow(bead_coords$coordinates))) {
thresh_array[
bead_coords$coordinates[i, 1],
bead_coords$coordinates[i, 2], 1, 1
] <- 0
}
# removed microbeads from droplets and retransformation to cimg
thresh_clean_cimg <- as.cimg(thresh_array)
# displaying problem of discontinous edges
plot(thresh_cimg)
# displaying removed microbeads
plot(thresh_clean_cimg)
# same orientation for 'cimg' and 'magick-image'
thresh_clean_m <- mirror(thresh_clean_cimg, axis = "x")
thresh_clean_magick <- cimg2magick(thresh_clean_m)
# getting coordinates of all line ends
mo1_lineends <- image_morphology(
thresh_clean_magick,
"HitAndMiss", "LineEnds"
)
# transform extracted coordinates into data frame
lineends_cimg <- magick2cimg(mo1_lineends)
end_points <- which(lineends_cimg == TRUE, arr.ind = TRUE)
end_points_df <- as.data.frame(end_points)
colnames(end_points_df) <- c("x", "y", "dim3", "dim4")
# highlighted line ends
vis_lineend <-
changePixelColor(thresh_clean_cimg,
end_points_df,
color = "green",
visualize = TRUE
)
After reviewing part of the fillLineGaps
function, let
us apply it to the example images provided. The first three parameters
have already been discussed, which entails converting to binary using
thresholding and eliminating identified objects. The alpha and sigma
parameters, denoting the threshold and smoothing factor, are derived
from the cannyEdge
function in the imager package. Moving
on to the next parameter, the radius determines the maximum pixel range
around each line end that ought to be scanned for another edge. The
iterations parameter specifies the number of times the algorithm will be
applied to the given image. The function incorporates an internal
visualization that highlights the pixels added by the algorithm to fill
the line gaps. In the following images the visualization and the result
of the function are displayed.
closed_gaps <- fillLineGaps(droplets,
droplet_beads,
threshold = "13%",
alpha = 1,
sigma = 0.1,
radius = 5,
iterations = 3,
visualize = TRUE
)
closed_gaps |> plot()
As intended, the contours show reduced fragmentation and improved continuity, enabling further analysis to extract meaningful information from the image. While this accomplishment is remarkable, it is important to acknowledge the algorithm’s limitations. The algorithm performs admirably in situations where relatively straight lines are fragmented, but challenges arise in more complicated situations. One issue arises from diagonal line endings where, for lines with a one-pixel width, each pixel is treated as a separate cluster. As a result, the direct neighbor meets the reconnection requirements. To address this problem, diagonal line endings will not reconnect with their cluster or the first direct neighbor’s cluster. A different issue arises when there are multiple edges in the scan area. In these instances, the endpoint will reconnect with all of them, potentially generating small new partitions and a clotted-like structure. Despite this, the algorithm successfully manages to close gaps in the majority of cases, rendering it satisfactory for our specific example.
fillLineGaps
algorithmfirst_img <- vis_lineend
second_img <- closed_gaps
first_m <- mirror(first_img, axis = "x")
second_m <- mirror(second_img, axis = "x")
first_magick <- cimg2magick(first_m)
second_magick <- cimg2magick(second_m)
img <- c(first_magick, second_magick)
image_animate(image_scale(img, "500x635"),
fps = 1,
dispose = "previous"
)
Having resolved the initial difficulty of the discontinuous edges, the focus returns to the characterization of the partitions. Let’s examine whether we can now distinguish individual partitions after reducing the discontinuity. This is achieved by labeling the ‘closed_gaps’ image. However, we should abstain from visualizing the image at this point, as it would display a multitude of colors since every pixel should be labeled.
# label resulting image with filled gaps
lab_partitions <- label(closed_gaps)
df_lab_part <- as.data.frame(lab_partitions) |>
subset(value > 0)
In the following section, the labeled edges will be discarded to
isolate individual partitions. This will be achieved by only taking into
account coordinates that are equal to zero. The end result will be
displayed with the use of the changePixelColor
function,
which colors every pixel except for the contours. (It’s worth noting
that the top-left partition is excluded for unknown reasons.)
# removing the edges as labeled regions
x <- list()
y <- list()
value <- list()
for (g in seq_len(nrow(df_lab_part))) {
# droplets_array <- as.array(droplets)
if (closed_gaps[df_lab_part$x[g], df_lab_part$y[g], 1, 1] == 0) {
x[g] <- df_lab_part$x[g]
y[g] <- df_lab_part$y[g]
value[g] <- df_lab_part$value[g]
}
}
# data frame of labeled partitions (without contours)
clean_lab_df <- data.frame(
x = unlist(x),
y = unlist(y),
value = unlist(value)
)
changePixelColor(closed_gaps,
clean_lab_df,
color = "purple",
visualize = TRUE
)
Let’s summarize the clusters to validate their recognition as distinct partitions. This is done by grouping the data by cluster and calculating the mean coordinates using the data.table package. The resulting labeling of each partition contains its cluster number in the center, and as desired, each partition is distinguished by a unique cluster number.
library(data.table)
# summarizing data frame by cluster (value)
DT_droplet <- data.table(clean_lab_df)
grouped_droplets <-
DT_droplet[, .(mx = mean(x), my = mean(y)), by = value]
plot(closed_gaps)
text(
grouped_droplets$mx,
grouped_droplets$my,
grouped_droplets$value,
col = "gray",
cex = 0.5
)
Once the partitions have been identified, the main goal is to determine the quantity of microbeads in each one. This can be achieved without analyzing every single pixel by using the center coordinates of the microbeads. Each partition is analyzed to confirm which contains coordinates that match a microbead center. The result is shown in a brief table containing the cluster number in the first row and the corresponding number of microbeads in each partition in the second row.
# checking in which partition the center coordinates of the microbeads are present
bead_partition <- list()
for (c in seq_len(nrow(bead_coords$centers))) {
partition_pos <- which(
clean_lab_df$x == round(bead_coords$centers$mx[c]) &
clean_lab_df$y == round(bead_coords$centers$my[c])
)
bead_partition[c] <- clean_lab_df$value[partition_pos]
}
res_part <- table(as.character(bead_partition))
res_part
148 175 187 75
2 1 1 1
This section analyzes partition sizes, which is critical due to the
potential creation of small inclusions by the fillLineGaps
algorithm, leading to ‘false’ partitions. The analysis involves counting
the pixels within each partition and generating a histogram, which
reveals a positively skewed distribution
(e1071::skewness(size_df$size)
= 3.63). As a result, the
majority of partitions appear to be small, with sizes less than 100
pixels.
size_list <- list()
for (d in unique(clean_lab_df$value)) {
pxl_number <- which(clean_lab_df$value == d)
size <- length(pxl_number)
size_list[d] <- size
}
size_df <- data.frame(
partition = unique(clean_lab_df$value),
size = unlist(size_list)
)
hist(
size_df$size,
main = "Histogramm of partition size",
xlab = "size",
# labels = TRUE,
breaks = "Scott",
ylim = c(0, 120),
xlim = c(0, 1400)
)
To reduce errors caused by incorrectly created partitions, any partitions with a size of one pixel will be discarded. Although this may result in the removal of some correctly reconnected small partitions, it is expected to improve accuracy by eliminating certain falsely generated partitions.
small <- which(size_df$size == 1)
number_small <- length(small)
The data frame will be adjusted in the next section by subtracting the count of small partitions.
As previously mentioned, the former table may be overly extensive and difficult to extract information from at first glance. Therefore, a brief data frame is produced, presenting only the most essential information collected from the two images. Moreover, for reference, the initial image of droplets that contain the microbeads is presented below.
data.frame(
partitions = nrow(grouped_droplets) - number_small,
empty_partitions = nrow(grouped_droplets) - length(unique(bead_partition)) - number_small,
bead_partitions = length(unique(bead_partition)),
single_bead = length(which(res_part == 1)),
muliple_beads = length(which(res_part > 1))
)
partitions empty_partitions bead_partitions single_bead muliple_beads
1 125 121 4 3 1
plot(droplets)
plot(closed_gaps)