---
title: "biopixR - Introduction"
author: "Tim Brauckhoff"
date: "`r Sys.Date()`"
output:
  html_document:
    toc: yes
    number_sections: yes
  pdf_document:
    toc: yes
vignette: >
  %\VignetteIndexEntry{biopixR - Introduction} 
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

<style>
body {
text-align: justify}
</style>

```{r setup, include=FALSE}
knitr::opts_chunk$set(
  echo = TRUE,
  warning = FALSE,
  message = FALSE,
  cache = FALSE,
  comment = NA,
  verbose = TRUE,
  fig.width = 5,
  fig.height = 5,
  dev = "jpeg",
  dev.args = list(quality = 25)
)
original_options <- options()
options(digits = 3)
```

# Getting started

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.

## Detecting objects in an image

```{r beads}
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.

```{r class}
class(beads)
```

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.

```{r objectDetection}
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:

```{r visualization_1}
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.

```{r visualization_2}
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.

```{r visualization_3}
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.

## Filter microbeads according to size and proximity

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.

```{r, error=TRUE}
#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.

```{r}
res_sizeFilter <- sizeFilter(
  centers = res_objectDetection$centers,
  coordinates = res_objectDetection$coordinates,
  lowerlimit = 0,
  upperlimit = 150
)
```

**visualization sizeFilter**:

```{r}
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.

```{r}
res_proximityFilter <-
  proximityFilter(
    centers = res_sizeFilter$centers,
    coordinates = res_objectDetection$coordinates,
    radius = "auto"
  )
```

**visualization proximityFilter**:

```{r}
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.

## Displaying results

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.

```{r}
result <-
  resultAnalytics(
    img = beads,
    coordinates = res_proximityFilter$coordinates,
    unfiltered = res_objectDetection$coordinates
  )
result$detailed
```

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.

```{r}
result$summary
```

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.

## Individual usage of the filter functions

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.

```{r}
result_proximityFilter <-
  resultAnalytics(
    img = beads,
    coordinates = res_objectDetection$coordinates
  )

result_proximityFilter$detailed
```

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.

```{r}
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"
)
```


```{r}
result_sizeFilter <-
  resultAnalytics(
    img = beads,
    coordinates = ind_sizeFilter$coordinates,
    unfiltered = res_objectDetection$coordinates
  )

result_sizeFilter$detailed
```

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. 

```{r}
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.

```{r}
result_proximityFilter <-
  resultAnalytics(
    img = beads,
    coordinates = ind_proximityFilter$coordinates,
    unfiltered = res_objectDetection$coordinates
  )

result_proximityFilter$detailed
```

## Relative distance between microparticles

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
((x~1~, y~1~)) and ((x~2~, y~2~)) is:

\[ \text{Distance} = \sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2} \]

```{r}
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.

```{r}
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)
```

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.

```{r}
for (a in 1:num_points) {
  result$detailed$relative_distance[a] <-
    mean(na.omit(relative_distances[a, ]))
}

result$detailed
```

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. 

```{r}
result$summary$mean_distance <-
  mean(result$detailed$relative_distance)
result$summary
```

*will be part of the `resultAnalytics` function as of version 0.2.0*

# Case study: microbeads in droplets

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.

```{r fig.show='hold', out.width="49%"}
plot(droplets)
plot(droplet_beads)
```

## Dealing with discontinous edges

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.

```{r fig.show='hold', out.width="49%"}
# 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)
```

```{r fig.align='center'}
# 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.

```{r fig.show='hold', out.width="49%"}
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.

### Animated visualization of the `fillLineGaps` algorithm

```{r fig.align='center'}
first_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"
)
```


## Characterizing partitions

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.

```{r}
# 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.)

```{r fig.align='center'}
# 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.

```{r fig.align='center', out.width="100%"}
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.

```{r}
# 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
```

### Analysis of the partition size

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.

```{r}
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.

```{r}
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.

## Displaying result

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.

```{r}
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))
)
```

```{r fig.show='hold', out.width="49%"}
plot(droplets)
plot(closed_gaps)
```

```{r echo=FALSE}
options(original_options)
```

