Analyze objects

1 Directory

setwd("E:/Desktop/UFSC/cursos/pliman_tut/imgs")

2 Working with polygons

A ‘polygon’ is a plane figure that is described by a finite number of straight line segments connected to form a closed polygonal chain (Singer, 1993)1.

We can then conclude that image objects can be expressed as polygons with n vertices. pliman has a set of functions(draw_*()) useful for drawing common shapes like circles, squares, triangles, rectangles and n- tagons . Another group of poly_*() functions can be used to analyze polygons. Let’s start with a simple example related to the area and perimeter of a square.

The legacy packages maptools, rgdal, and rgeos, underpinning the sp package,
which was just loaded, will retire in October 2023.
Please refer to R-spatial evolution reports for details, especially
https://r-spatial.org/r/2023/05/15/evolution4.html.
It may be desirable to make the sf package available;
package maintainers should consider adding sf to Suggests:.
The sp package is now running under evolution status 2
     (status 2 uses the sf package in place of rgdal)
|==========================================================|
| Tools for Plant Image Analysis (pliman 2.0.0)            |
| Author: Tiago Olivoto                                    |
| Type 'citation('pliman')' to know how to cite pliman     |
| Type 'vignette('pliman_start')' for a short tutorial     |
| Visit 'http://bit.ly/pkg_pliman' for a complete tutorial |
|==========================================================|
square <- draw_square(side = 2)

poly_area(square)
[1] 4
[1] 6

Now, let’s see what happens when we start with a hexagon and increase the number of sides up to 1000.

shapes <- 
  list(side6 = draw_n_tagon(6, plot = FALSE),
       side12 = draw_n_tagon(8, plot = FALSE),
       side24 = draw_n_tagon(12, plot = FALSE),
       side100 = draw_n_tagon(50, plot = FALSE),
       side500 = draw_n_tagon(100, plot = FALSE),
       side100 = draw_n_tagon(1000, plot = FALSE))

plot_polygon(shapes, merge = FALSE, aspect_ratio = 1)

poly_area(shapes)
[1] 2.598076 2.828427 3.000000 3.133331 3.139526 3.141572
   side6   side12   side24  side100  side500  side100 
5.000000 5.357568 5.694019 6.153471 6.219330 6.276892 

Note that when \(n \to \infty\), the sum of the sides becomes the circumference of the circle, given by \(2 \pi r\), and the area becomes \(\pi r^2\). This is fun, but pliman is primarily designed for analyzing plant image analysis. So why use polygons? Let’s see how we can use these functions to get applicable information.

leaves <- image_import("ref_leaves.jpg", plot = TRUE)

# getting the outline of objects
cont <- 
  object_contour(leaves,
                 index = "B-R",
                 watershed = FALSE)

# plotting the polygon
plot_polygon(cont)

Nice! We can use the contours of any object to get useful information related to its shape. To reduce the amount of output, I will only use five samples: 1, 2, 12, and 23.

cont2 <- cont[c("5", "20", "27")]
plot_polygon(cont2)

In the current version of pliman, you can calculate the following measurements. For more details, see Chen & Wang (2005)2, Claude (2008)3, and Montero et al. (2009)4.

2.1 Area

The area of a shape is calculated using Shoelace’s formula (Lee and Lim, 2017)5, as follows

\[ A=\frac{1}{2}\left |\sum_{i=1}^{n}\left(x_{i} y_{i+1}-x_{i+1}y_{i}\right)\right| \]

poly_area(cont)
 [1] 18352.5 36372.0 37283.0 16610.5  5077.0 30713.5 19633.0  6659.0  9035.0
[10]  3083.5 12284.5  8285.0  1518.5  1878.0  1271.5  9692.0  3306.5  8433.5
[19]  2952.0 16263.0 12589.0  1865.5  1726.0

2.2 Perimeter

The perimeter is calculated as the sum of the Euclidean distance between all points on a shape. Distances can be obtained with poly_distpts().

        1         2         3         4         5         6         7        10 
 615.2569 1125.0874  797.8082  552.3574  386.2914  897.2102  604.4701  412.7056 
       11        14        15        16        17        18        19        20 
 453.4163  284.3797  502.7716  469.5584  160.4386  285.4508  156.2965  412.1026 
       21        22        23        24        25        26        27 
 256.8650  365.7279  301.3087  538.4701  479.2742  211.8944  548.2548 
# perimeter of a circle with radius 2
circle <- draw_circle(radius = 2, plot = FALSE)
poly_perimeter(circle)
[1] 12.56635
# check the result
2*pi*2
[1] 12.56637

2.3 Radius

The radius of a pixel on the object’s contour is calculated as its distance from the object’s centroid(also called ‘center of mass’). These distances can be obtained with poly_centdist().

dist <- poly_centdist(cont)

# stats for radius
mean_list(dist)
        1         2         3         4         5         6         7        10 
 81.50472 107.15072 111.67757  75.92128  48.48228 112.97916  82.68263  53.51156 
       11        14        15        16        17        18        19        20 
 56.72946  36.54740  64.67529  61.75205  22.48669  33.83886  21.25721  57.39115 
       21        22        23        24        25        26        27 
 34.56566  52.56715  38.06452  73.40734  65.81308  28.06548  67.26094 
min_list(dist)
        1         2         3         4         5         6         7        10 
56.365931 62.460726 79.800528 55.452805 21.232635 48.004837 53.638969 25.979106 
       11        14        15        16        17        18        19        20 
34.823777 15.440760 43.867424 29.407891 15.553085  5.815242 11.841794 42.995237 
       21        22        23        24        25        26        27 
23.217347 44.807718 13.925495 59.848727 47.836671 16.415385  3.438458 
max_list(dist)
        1         2         3         4         5         6         7        10 
126.82821 152.42898 166.37215 115.03892  84.48600 192.29568 127.78141  91.08601 
       11        14        15        16        17        18        19        20 
 88.38574  62.81868  95.99267 106.86452  33.31437  62.59528  33.27942  85.25620 
       21        22        23        24        25        26        27 
 54.26327  64.41345  67.61794 104.72813  98.71630  47.72001 134.03105 
sd_list(dist)
        1         2         3         4         5         6         7        10 
17.425062 18.421426 24.407052 14.837653 18.090150 41.426392 21.177024 19.809781 
       11        14        15        16        17        18        19        20 
13.522420 13.821949 12.949648 23.157277  5.360341 15.964780  6.161850 12.086296 
       21        22        23        24        25        26        27 
 8.886554  5.709767 15.304076  9.980561 12.689383  8.956172 38.480834 
# average radius of above circle
poly_centdist(circle) |> mean_list()
[1] 1.999998

2.4 Length and width

The length and width of an object are calculated with poly_lw(), as the difference between the maximum and minimum of the x and y coordinates after the object has been aligned with poly_align().

poly_lw(cont)
         length     width
 [1,] 238.54100 125.05197
 [2,] 280.55220 237.69204
 [3,] 315.47889 166.64659
 [4,] 209.33606 123.39163
 [5,] 168.01252  54.02002
 [6,] 375.58661 131.13737
 [7,] 249.45384 116.64014
 [8,] 179.84217  62.94321
 [9,] 152.34622 114.49558
[10,] 124.87025  39.06435
[11,] 166.54470 126.32871
[12,] 208.31970  68.91857
[13,]  66.15298  32.52419
[14,] 123.16969  30.24387
[15,]  66.09709  28.40237
[16,] 164.06833  90.09544
[17,] 102.73052  50.34951
[18,] 100.45299  98.53530
[19,] 133.47743  37.65555
[20,] 180.69778 135.88876
[21,] 176.71212 111.21299
[22,]  89.00219  36.02323
[23,] 266.25888  10.45654

2.5 Circularity, eccentricity, diameter, and elongation

Circularity(Montero et al. 2009)6 is also called shape compactness, or measure of roundness of an object. It is given by \(C = P^2 / A\), where \(P\) is the perimeter and \(A\) is the area of the object.

        1         2         3         4         5         6         7        10 
 20.62613  34.80209  17.07207  18.36782  29.39158  26.20952  18.61071  25.57831 
       11        14        15        16        17        18        19        20 
 22.75444  26.22728  20.57710  26.61257  16.95130  43.38773  19.21241  17.52255 
       21        22        23        24        25        26        27 
 19.95452  15.86019  30.75437  17.82882  18.24638  24.06822 174.15027 

As the above measurement depends on the scale, normalized roundness can be used. In this case, a perfect circle is assumed to have a circularity equal to 1. This measure is invariant under translation, rotation and scale transformations, given \(Cn = P^2 / 4 \pi A\)

        1         2         3         4         5         6         7        10 
0.6092451 0.3610809 0.7360778 0.6841513 0.4275500 0.4794582 0.6752224 0.4912902 
       11        14        15        16        17        18        19        20 
0.5522602 0.4791335 0.6106970 0.4721968 0.7413221 0.2896296 0.6540756 0.7171542 
       21        22        23        24        25        26        27 
0.6297505 0.7923216 0.4086044 0.7048347 0.6887047 0.5221148 0.0721582 
# normalized circularity for different shapes
draw_square(plot =FALSE) |> poly_circularity_norm()
[1] 1.396263
[1] 0.9999967

poly_circularity_haralick() calculates the Circularity of Haralick, CH (Haralick, 1974)7. The method is based on calculating all Euclidean distances from the object’s centroid to each contour pixel. With this set of distances, the mean(\(m\)) and the standard deviation(\(s\)) are calculated. These statistical parameters are used in a ratio that calculates CH as $CH = m/ sd $.

       1        2        3        4        5        6        7       10 
4.677442 5.816635 4.575627 5.116798 2.680038 2.727227 3.904355 2.701270 
      11       14       15       16       17       18       19       20 
4.195215 2.644156 4.994367 2.666637 4.195010 2.119594 3.449810 4.748449 
      21       22       23       24       25       26       27 
3.889658 9.206531 2.487214 7.355031 5.186468 3.133647 1.747908 

poly_convexity() Calculates the convexity of a shape using a ratio of the perimeter of the convex hull to the perimeter of the polygon.

        1         2         3         4         5         6         7        10 
0.8967601 0.6876980 0.9192591 0.9132441 0.8298061 0.8645018 0.8757106 0.9226473 
       11        14        15        16        17        18        19        20 
0.8410503 0.9023328 0.8468413 0.9133223 0.9154601 0.6425806 0.9193294 0.9192070 
       21        22        23        24        25        26        27 
0.8967441 0.8459495 0.9009212 0.9076033 0.8508448 0.7493671 0.7361697 

poly_eccentricity() Calculates the eccentricity of a shape using the ratio of the eigenvalues(coordinate inertia axes).

             [,1]
 [1,] 0.346560699
 [2,] 0.844858938
 [3,] 0.401595451
 [4,] 0.431419527
 [5,] 0.132562754
 [6,] 0.156699400
 [7,] 0.322626716
 [8,] 0.149181272
 [9,] 0.543013592
[10,] 0.142622157
[11,] 0.608127299
[12,] 0.127298521
[13,] 0.379315768
[14,] 0.069374529
[15,] 0.275223477
[16,] 0.414141956
[17,] 0.313023030
[18,] 0.960354914
[19,] 0.109866568
[20,] 0.648373662
[21,] 0.456904377
[22,] 0.190833679
[23,] 0.002065428

poly_elongation() Calculates the elongation of an object as 1 - width / length

            [,1]
 [1,] 0.47576322
 [2,] 0.15277071
 [3,] 0.47176628
 [4,] 0.41055721
 [5,] 0.67847621
 [6,] 0.65084654
 [7,] 0.53241794
 [8,] 0.65000859
 [9,] 0.24845148
[10,] 0.68716047
[11,] 0.24147263
[12,] 0.66916922
[13,] 0.50834888
[14,] 0.75445361
[15,] 0.57029316
[16,] 0.45086636
[17,] 0.50988754
[18,] 0.01909042
[19,] 0.71788823
[20,] 0.24797772
[21,] 0.37065445
[22,] 0.59525454
[23,] 0.96072791

poly_caliper() Calculates the gauge(also called Feret’s diameter).

        1         2         3         4         5         6         7        10 
235.68835 284.39585 314.63630 205.34118 160.11246 372.48490 249.28097 179.69418 
       11        14        15        16        17        18        19        20 
154.98387 124.78782 167.69317 207.57649  66.18912 123.32883  66.09841 164.24677 
       21        22        23        24        25        26        27 
102.78619 128.03515 133.37541 185.60711 177.11578  89.04493 266.18790 

Users can use the poly_measures() function to calculate most object measurements in a single call.

measures <- poly_measures(cont) |> round_cols()
t(measures)
                            1        2        3        4       5        6
id                       1.00     2.00     3.00     4.00    5.00     6.00
x                      473.08   809.08   244.59   612.07  375.16   104.34
y                      144.40   168.44   194.72   152.34  155.65   440.98
area                 18352.50 36372.00 37283.00 16610.50 5077.00 30713.50
area_ch              19538.50 45891.00 37696.50 17423.00 5670.50 32710.50
perimeter              615.26  1125.09   797.81   552.36  386.29   897.21
radius_mean             81.50   107.15   111.68    75.92   48.48   112.98
radius_min              56.37    62.46    79.80    55.45   21.23    48.00
radius_max             126.83   152.43   166.37   115.04   84.49   192.30
radius_sd               17.43    18.42    24.41    14.84   18.09    41.43
radius_ratio             2.25     2.44     2.08     2.07    3.98     4.01
diam_mean              163.01   214.30   223.36   151.84   96.96   225.96
diam_min               112.73   124.92   159.60   110.91   42.47    96.01
diam_max               253.66   304.86   332.74   230.08  168.97   384.59
caliper                238.54   280.55   315.48   209.34  168.01   375.59
length                 238.54   280.55   315.48   209.34  168.01   375.59
width                  125.05   237.69   166.65   123.39   54.02   131.14
solidity                 0.94     0.79     0.99     0.95    0.90     0.94
convexity                0.90     0.69     0.92     0.91    0.83     0.86
elongation               0.48     0.15     0.47     0.41    0.68     0.65
circularity             20.63    34.80    17.07    18.37   29.39    26.21
circularity_haralick     4.68     5.82     4.58     5.12    2.68     2.73
circularity_norm         0.61     0.36     0.74     0.68    0.43     0.48
eccentricity             0.35     0.84     0.40     0.43    0.13     0.16
pcv                      1.26     2.84     0.43     0.94    1.64     0.82
                            7      10       11      14       15      16      17
id                       7.00    8.00     9.00   10.00    11.00   12.00   13.00
x                      929.08  341.49   529.46  632.11   773.02  446.42  568.82
y                      375.55  375.53   335.35  405.31   430.98  518.10  474.33
area                 19633.00 6659.00  9035.00 3083.50 12284.50 8285.00 1518.50
area_ch              20110.50 7117.00 10330.00 3218.50 13343.50 8811.00 1553.00
perimeter              604.47  412.71   453.42  284.38   502.77  469.56  160.44
radius_mean             82.68   53.51    56.73   36.55    64.68   61.75   22.49
radius_min              53.64   25.98    34.82   15.44    43.87   29.41   15.55
radius_max             127.78   91.09    88.39   62.82    95.99  106.86   33.31
radius_sd               21.18   19.81    13.52   13.82    12.95   23.16    5.36
radius_ratio             2.38    3.51     2.54    4.07     2.19    3.63    2.14
diam_mean              165.37  107.02   113.46   73.09   129.35  123.50   44.97
diam_min               107.28   51.96    69.65   30.88    87.73   58.82   31.11
diam_max               255.56  182.17   176.77  125.64   191.99  213.73   66.63
caliper                249.45  179.84   152.35  124.87   166.54  208.32   66.15
length                 249.45  179.84   152.35  124.87   166.54  208.32   66.15
width                  116.64   62.94   114.50   39.06   126.33   68.92   32.52
solidity                 0.98    0.94     0.87    0.96     0.92    0.94    0.98
convexity                0.88    0.92     0.84    0.90     0.85    0.91    0.92
elongation               0.53    0.65     0.25    0.69     0.24    0.67    0.51
circularity             18.61   25.58    22.75   26.23    20.58   26.61   16.95
circularity_haralick     3.90    2.70     4.20    2.64     4.99    2.67    4.20
circularity_norm         0.68    0.49     0.55    0.48     0.61    0.47    0.74
eccentricity             0.32    0.15     0.54    0.14     0.61    0.13    0.38
pcv                      0.85    1.15     1.53    1.75     1.36    1.07    3.13
                          18      19      20      21      22      23       24
id                     14.00   15.00   16.00   17.00   18.00   19.00    20.00
x                     305.39  633.31  858.65  751.61  536.84  654.67   199.12
y                     534.70  510.59  557.43  555.64  588.83  630.47   665.41
area                 1878.00 1271.50 9692.00 3306.50 8433.50 2952.00 16263.00
area_ch              2332.00 1315.00 9969.00 3531.50 8529.50 3202.00 17237.00
perimeter             285.45  156.30  412.10  256.87  365.73  301.31   538.47
radius_mean            33.84   21.26   57.39   34.57   52.57   38.06    73.41
radius_min              5.82   11.84   43.00   23.22   44.81   13.93    59.85
radius_max             62.60   33.28   85.26   54.26   64.41   67.62   104.73
radius_sd              15.96    6.16   12.09    8.89    5.71   15.30     9.98
radius_ratio           10.76    2.81    1.98    2.34    1.44    4.86     1.75
diam_mean              67.68   42.51  114.78   69.13  105.13   76.13   146.81
diam_min               11.63   23.68   85.99   46.43   89.62   27.85   119.70
diam_max              125.19   66.56  170.51  108.53  128.83  135.24   209.46
caliper               123.17   66.10  164.07  102.73  100.45  133.48   180.70
length                123.17   66.10  164.07  102.73  100.45  133.48   180.70
width                  30.24   28.40   90.10   50.35   98.54   37.66   135.89
solidity                0.81    0.97    0.97    0.94    0.99    0.92     0.94
convexity               0.64    0.92    0.92    0.90    0.85    0.90     0.91
elongation              0.75    0.57    0.45    0.51    0.02    0.72     0.25
circularity            43.39   19.21   17.52   19.95   15.86   30.75    17.83
circularity_haralick    2.12    3.45    4.75    3.89    9.21    2.49     7.36
circularity_norm        0.29    0.65    0.72    0.63    0.79    0.41     0.70
eccentricity            0.07    0.28    0.41    0.31    0.96    0.11     0.65
pcv                     2.24    3.12    1.12    1.99    1.10    1.71     1.20
                           25      26      27
id                      21.00   22.00   23.00
x                      767.38  313.05  428.12
y                      675.03  656.60  696.30
area                 12589.00 1865.50 1726.00
area_ch              13171.50 2076.00 2079.00
perimeter              479.27  211.89  548.25
radius_mean             65.81   28.07   67.26
radius_min              47.84   16.42    3.44
radius_max              98.72   47.72  134.03
radius_sd               12.69    8.96   38.48
radius_ratio             2.06    2.91   38.98
diam_mean              131.63   56.13  134.52
diam_min                95.67   32.83    6.88
diam_max               197.43   95.44  268.06
caliper                176.71   89.00  266.26
length                 176.71   89.00  266.26
width                  111.21   36.02   10.46
solidity                 0.96    0.90    0.83
convexity                0.85    0.75    0.74
elongation               0.37    0.60    0.96
circularity             18.25   24.07  174.15
circularity_haralick     5.19    3.13    1.75
circularity_norm         0.69    0.52    0.07
eccentricity             0.46    0.19    0.00
pcv                      1.03    2.45    0.68

If the image resolution is known, the measurements can be corrected with get_measures(). Image resolution can be obtained using a known distance in the image. In the example, the white square has a side of 5 cm. So using dpi() the resolution can be obtained. In this case, the dpi is ~50.

color_measures <- get_measures(measures, dpi = 50)
t(color_measures)
                             1         2         3         4         5
id                     1.00000   2.00000   3.00000   4.00000   5.00000
x                    473.08000 809.08000 244.59000 612.07000 375.16000
y                    144.40000 168.44000 194.72000 152.34000 155.65000
area                  47.36120  93.86304  96.21400  42.86572  13.10191
area_ch               50.42183 118.42815  97.28110  44.96249  14.63352
perimeter             31.25521  57.15457  40.52875  28.05989  19.62353
radius_mean            4.14020   5.44322   5.67334   3.85674   2.46278
radius_min             2.86360   3.17297   4.05384   2.81686   1.07848
radius_max             6.44296   7.74344   8.45160   5.84403   4.29209
radius_sd              0.88544   0.93574   1.24003   0.75387   0.91897
radius_ratio           0.11430   0.12395   0.10566   0.10516   0.20218
diam_mean              8.28091  10.88644  11.34669   7.71347   4.92557
diam_min               5.72668   6.34594   8.10768   5.63423   2.15748
diam_max              12.88593  15.48689  16.90319  11.68806   8.58368
caliper               12.11783  14.25194  16.02638  10.63447   8.53491
length                12.11783  14.25194  16.02638  10.63447   8.53491
width                  6.35254  12.07465   8.46582   6.26821   2.74422
solidity               0.04775   0.04013   0.05029   0.04826   0.04572
convexity              0.90000   0.69000   0.92000   0.91000   0.83000
elongation             0.48000   0.15000   0.47000   0.41000   0.68000
circularity           20.63000  34.80000  17.07000  18.37000  29.39000
circularity_haralick   4.68000   5.82000   4.58000   5.12000   2.68000
circularity_norm       0.61000   0.36000   0.74000   0.68000   0.43000
eccentricity           0.35000   0.84000   0.40000   0.43000   0.13000
pcv                    1.26000   2.84000   0.43000   0.94000   1.64000
pcv                    1.26000   2.84000   0.43000   0.94000   1.64000
                             6         7        10        11        14
id                     6.00000   7.00000   8.00000   9.00000  10.00000
x                    104.34000 929.08000 341.49000 529.46000 632.11000
y                    440.98000 375.55000 375.53000 335.35000 405.31000
area                  79.26049  50.66571  17.18448  23.31608   7.95740
area_ch               84.41402  51.89796  18.36641  26.65801   8.30579
perimeter             45.57827  30.70708  20.96567  23.03374  14.44650
radius_mean            5.73938   4.20014   2.71831   2.88188   1.85674
radius_min             2.43840   2.72491   1.31978   1.76886   0.78435
radius_max             9.76884   6.49122   4.62737   4.49021   3.19126
radius_sd              2.10464   1.07594   1.00635   0.68682   0.70206
radius_ratio           0.20371   0.12090   0.17831   0.12903   0.20676
diam_mean             11.47877   8.40080   5.43662   5.76377   3.71297
diam_min               4.87731   5.44982   2.63957   3.53822   1.56870
diam_max              19.53717  12.98245   9.25424   8.97992   6.38251
caliper               19.07997  12.67206   9.13587   7.73938   6.34340
length                19.07997  12.67206   9.13587   7.73938   6.34340
width                  6.66191   5.92531   3.19735   5.81660   1.98425
solidity               0.04775   0.04978   0.04775   0.04420   0.04877
convexity              0.86000   0.88000   0.92000   0.84000   0.90000
elongation             0.65000   0.53000   0.65000   0.25000   0.69000
circularity           26.21000  18.61000  25.58000  22.75000  26.23000
circularity_haralick   2.73000   3.90000   2.70000   4.20000   2.64000
circularity_norm       0.48000   0.68000   0.49000   0.55000   0.48000
eccentricity           0.16000   0.32000   0.15000   0.54000   0.14000
pcv                    0.82000   0.85000   1.15000   1.53000   1.75000
pcv                    0.82000   0.85000   1.15000   1.53000   1.75000
                            15        16        17        18        19
id                    11.00000  12.00000  13.00000  14.00000  15.00000
x                    773.02000 446.42000 568.82000 305.39000 633.31000
y                    430.98000 518.10000 474.33000 534.70000 510.59000
area                  31.70187  21.38060   3.91870   4.84644   3.28128
area_ch               34.43477  22.73802   4.00773   6.01805   3.39354
perimeter             25.54072  23.85365   8.15035  14.50086   7.94004
radius_mean            3.28574   3.13690   1.14249   1.71907   1.08001
radius_min             2.22860   1.49403   0.78994   0.29566   0.60147
radius_max             4.87629   5.42849   1.69215   3.18008   1.69062
radius_sd              0.65786   1.17653   0.27229   0.81077   0.31293
radius_ratio           0.11125   0.18440   0.10871   0.54661   0.14275
diam_mean              6.57098   6.27380   2.28448   3.43814   2.15951
diam_min               4.45668   2.98806   1.58039   0.59080   1.20294
diam_max               9.75309  10.85748   3.38480   6.35965   3.38125
caliper                8.46023  10.58266   3.36042   6.25704   3.35788
length                 8.46023  10.58266   3.36042   6.25704   3.35788
width                  6.41756   3.50114   1.65202   1.53619   1.44272
solidity               0.04674   0.04775   0.04978   0.04115   0.04928
convexity              0.85000   0.91000   0.92000   0.64000   0.92000
elongation             0.24000   0.67000   0.51000   0.75000   0.57000
circularity           20.58000  26.61000  16.95000  43.39000  19.21000
circularity_haralick   4.99000   2.67000   4.20000   2.12000   3.45000
circularity_norm       0.61000   0.47000   0.74000   0.29000   0.65000
eccentricity           0.61000   0.13000   0.38000   0.07000   0.28000
pcv                    1.36000   1.07000   3.13000   2.24000   3.12000
pcv                    1.36000   1.07000   3.13000   2.24000   3.12000
                            20        21        22        23        24
id                    16.00000  17.00000  18.00000  19.00000  20.00000
x                    858.65000 751.61000 536.84000 654.67000 199.12000
y                    557.43000 555.64000 588.83000 630.47000 665.41000
area                  25.01156   8.53289  21.76383   7.61805  41.96895
area_ch               25.72640   9.11353  22.01157   8.26321  44.48249
perimeter             20.93468  13.04900  18.57908  15.30655  27.35428
radius_mean            2.91541   1.75616   2.67056   1.93345   3.72923
radius_min             2.18440   1.17958   2.27635   0.70764   3.04038
radius_max             4.33121   2.75641   3.27203   3.43510   5.32028
radius_sd              0.61417   0.45161   0.29007   0.77724   0.50698
radius_ratio           0.10058   0.11887   0.07315   0.24689   0.08890
diam_mean              5.83082   3.51180   5.34060   3.86740   7.45795
diam_min               4.36829   2.35864   4.55270   1.41478   6.08076
diam_max               8.66191   5.51332   6.54456   6.87019  10.64057
caliper                8.33476   5.21868   5.10286   6.78078   9.17956
length                 8.33476   5.21868   5.10286   6.78078   9.17956
width                  4.57708   2.55778   5.00583   1.91313   6.90321
solidity               0.04928   0.04775   0.05029   0.04674   0.04775
convexity              0.92000   0.90000   0.85000   0.90000   0.91000
elongation             0.45000   0.51000   0.02000   0.72000   0.25000
circularity           17.52000  19.95000  15.86000  30.75000  17.83000
circularity_haralick   4.75000   3.89000   9.21000   2.49000   7.36000
circularity_norm       0.72000   0.63000   0.79000   0.41000   0.70000
eccentricity           0.41000   0.31000   0.96000   0.11000   0.65000
pcv                    1.12000   1.99000   1.10000   1.71000   1.20000
pcv                    1.12000   1.99000   1.10000   1.71000   1.20000
                            25        26        27
id                    21.00000  22.00000  23.00000
x                    767.38000 313.05000 428.12000
y                    675.03000 656.60000 696.30000
area                  32.48768   4.81418   4.45418
area_ch               33.99090   5.35741   5.36515
perimeter             24.34692  10.76401  27.85110
radius_mean            3.34315   1.42596   3.41681
radius_min             2.43027   0.83414   0.17475
radius_max             5.01498   2.42418   6.80872
radius_sd              0.64465   0.45517   1.95478
radius_ratio           0.10465   0.14783   1.98018
diam_mean              6.68680   2.85140   6.83362
diam_min               4.86004   1.66776   0.34950
diam_max              10.02944   4.84835  13.61745
caliper                8.97687   4.52120  13.52601
length                 8.97687   4.52120  13.52601
width                  5.64947   1.82982   0.53137
solidity               0.04877   0.04572   0.04216
convexity              0.85000   0.75000   0.74000
elongation             0.37000   0.60000   0.96000
circularity           18.25000  24.07000 174.15000
circularity_haralick   5.19000   3.13000   1.75000
circularity_norm       0.69000   0.52000   0.07000
eccentricity           0.46000   0.19000   0.00000
pcv                    1.03000   2.45000   0.68000
pcv                    1.03000   2.45000   0.68000

Some useful functions can be used to manipulate coordinates. In the following example I will show some features implemented in pliman. Just for simplicity, I’ll use only one object.

o2 <- cont[[5]]
plot_polygon(o2)

2.6 Rotate polygons

poly_rotate() can be used to rotate the polygon coordinates by an angle (0-360 degrees) in the trigonometric (anti-clockwise) direction.

rot <- poly_rotate(o2, angle = 45)

2.7 Invert polygons

poly_flip_x() and poly_flip_y() can be used to flip shapes along the x and y axis, respectively.

flipped <- 
  list(fx = poly_flip_x(o2), 
       fy = poly_flip_y(o2))

plot_polygon(flipped, merge = FALSE)

2.8 Perimeter sampling

poly_sample() samples n coordinates between existing points, and poly_sample_prop() samples a proportion of coordinates between existing ones.

# sample 50 coordinates
poly_sample(o2, n=10) |> plot_polygon()

# sample 10% of coordinates
poly_sample_prop(o2, prop = 0.1) |> plot_polygon()

2.9 smoothing

poly_smooth() smooths the contour of a polygon by combining prop coordinate point samples and interpolating them using vertices vertices(default is 1000) .

smoothed <-
  list( original = o2,
        s1 = poly_smooth(o2, prop = 0.2, plot = FALSE),
        s2 = poly_smooth(o2, prop = 0.1, plot = FALSE),
        s1 = poly_smooth(o2, prop = 0.04, plot = FALSE)
  )

plot_polygon(smoothed, merge = FALSE, ncol = 2, aspect_ratio = 1)

2.10 Noises

poly_jitter() adds a small amount of noise to a set of coordinates. See base::jitter() for more details.

set.seed(1)
c1 <- draw_circle(n = 200, plot = FALSE)
c2 <- draw_circle(n = 200, plot = FALSE) |>
  poly_jitter(noise_x = 100,
              noise_y = 100,
              plot = FALSE)

plot_polygon(list(c1, c2), merge = FALSE)

3 Analyzing objects

The functions we’ve seen can be used to obtain measurements of objects. However, for image analysis it is necessary to combine different functions (mainly object_contour() and poly_measures()). Also, almost always, several images need to be analyzed and repeating this process each time would be tedious and inefficient. To address these needs, users can use the analyze_objects() function. Let’s start with a simple example, using the object_300dpi.png image available on GitHub page. To facilitate importing images from this folder, an image_pliman() helper function is used.

library(pliman)
library(tidyverse)
library(metan)

img <- image_pliman("objects_300dpi.jpg", plot = TRUE)

The image above was produced with Microsoft PowerPoint. It has a known resolution of 300 dpi(dots per inch) and displays four objects

  • Larger square: 10 x 10 cm (100 cm\(^2\))
  • Smaller square: 5 x 5 cm(25 cm\(^2\))
  • Rectangle: 4 x 2 cm(8 cm\(^2\))
  • Circle: 3 cm in diameter(~7.07 cm\(^2\))

To count the objects in the image we use analyze_objects() and inform the image(the only required argument). By default, the NB index is used for object segmentation.

img_res <- analyze_objects(img, marker = "id")

3.1 Adjusting object measurements

The results were stored in img_res. Since there is no scale declared in the example above, we have no idea about the actual area of objects in cm\(^2\), only in pixels. In this case, we use get_measures() to adjust pixel measurements to metric units.

There are two main ways to adjust object measurements (from pixels to cm, for example). The first is to declare the known area, perimeter or radius of a given object. The measure for the other objects will then be calculated by a simple rule of three. The second is by declaring a known image resolution in dpi(dots per inch). In this case, perimeter, area, and radius will be adjusted by the dpi informed.

3.1.1 Declaring a known value

Since we know the area of the larger square (object 1), let’s adjust the area of the other objects in the image using this.

get_measures(img_res ,
             id = 1,
             area ~ 100) |> 
  t()
-----------------------------------------
measures corrected with:
object id: 1
area     : 100
-----------------------------------------
Total    : 40.009 
Average  : 13.336 
-----------------------------------------
                              2          3          4
id                      2.00000    3.00000    4.00000
x                    1736.51589 1736.57088 1736.96695
y                     451.99673 1295.35408  938.00393
area                   25.04122    7.01935    7.94891
area_ch                24.95759    7.02186    7.90671
perimeter              19.98294   10.09779   11.91303
radius_mean             2.86590    1.49087    1.66756
radius_min              2.49152    1.47805    0.98799
radius_max              3.53105    1.50513    2.22447
radius_sd               0.31408    0.00379    0.42266
diam_mean               5.73181    2.98173    3.33512
diam_min                4.98303    2.95611    1.97598
diam_max                7.06211    3.01026    4.44894
major_axis              2.04045    1.05585    1.48148
minor_axis              2.03680    1.05256    0.87434
caliper                 7.01731    2.99894    4.43833
length                  6.51989    2.99478    3.99360
width                   6.50883    2.99566    1.98719
radius_ratio            1.41723    1.01832    2.25152
theta                  -1.18050   -1.35228    0.00105
eccentricity            0.05980    0.07886    0.80727
form_factor             0.78804    0.86507    0.70384
narrow_factor           1.07629    1.00139    1.11136
asp_ratio               1.00170    0.99971    2.00967
rectangularity          1.69468    1.27809    0.99838
pd_ratio                2.84766    3.36712    2.68412
plw_ratio               1.53376    1.68565    1.99188
solidity                1.00335    0.99964    1.00534
convexity               0.74873    0.90505    0.83591
elongation              0.00170   -0.00029    0.50241
circularity            15.94643   14.52634   17.85407
circularity_haralick    9.12467  393.24974    3.94539
circularity_norm        0.78537    0.86045    0.69937
coverage                0.10684    0.02995    0.03392

3.1.2 Declaring the image resolution

If the image resolution is known, all measurements will be adjusted accordingly. Let’s see a numerical example with pixels_to_cm(). This function converts the number of pixels(* px *) into cm, considering the image resolution in dpi, as follows: \(cm = px \times(2.54 / dpi)\). As we know the number of pixels of the larger square, its perimeter in cm is given by

# number of pixels for the perimeter of the largest square

ls_px <- img_res$results$perimeter[1]
pixels_to_cm(px = ls_px , dpi = 300)
[1] 39.9265

The perimeter of object 1 adjusted by image resolution is very close to the known value (40 cm). Below, the values of all measures are adjusted by declaring the dpi argument in get_measures().

img_res_cor <- get_measures(img_res, dpi = 300)

t(img_res_cor)
                             1          2          3          4
id                     1.00000    2.00000    3.00000    4.00000
x                    667.99511 1736.51589 1736.57088 1736.96695
y                    796.99281  451.99673 1295.35408  938.00393
area                  99.81199   24.99414    7.00615    7.93396
area_ch               99.68629   24.91067    7.00866    7.89185
perimeter             39.92650   19.96415   10.08830   11.90183
radius_mean            5.72789    2.86321    1.48946    1.66599
radius_min             4.98370    2.48917    1.47666    0.98706
radius_max             7.05698    3.52773    1.50371    2.22238
radius_sd              0.62876    0.31379    0.00379    0.42226
diam_mean             11.45577    5.72641    2.97893    3.33198
diam_min               9.96740    4.97834    2.95333    1.97412
diam_max              14.11395    7.05547    3.00743    4.44476
major_axis             4.07720    2.03853    1.05486    1.48009
minor_axis             4.07190    2.03488    1.05157    0.87352
caliper               14.03942    7.01071    2.99612    4.43416
length                11.12135    6.51376    2.99197    3.98985
width                 11.10659    6.50271    2.99284    1.98532
radius_ratio           1.41601    1.41723    1.01832    2.25152
theta                  0.12122   -1.18050   -1.35228    0.00105
eccentricity           0.05093    0.05980    0.07886    0.80727
form_factor            0.78681    0.78804    0.86507    0.70384
narrow_factor          1.26238    1.07629    1.00139    1.11136
asp_ratio              1.00133    1.00170    0.99971    2.00967
rectangularity         1.23753    1.69468    1.27809    0.99838
pd_ratio               2.84388    2.84766    3.36712    2.68412
plw_ratio              1.79623    1.53376    1.68565    1.99188
solidity               1.00126    1.00335    0.99964    1.00534
convexity              0.75029    0.74873    0.90505    0.83591
elongation             0.00133    0.00170   -0.00029    0.50241
circularity           15.97128   15.94643   14.52634   17.85407
circularity_haralick   9.10975    9.12467  393.24974    3.94539
circularity_norm       0.78548    0.78537    0.86045    0.69937
coverage               0.42666    0.10684    0.02995    0.03392

3.1.3 Understanding measurements

object_contour(img) %>% # get the contour of objects
  poly_mass() %>% # computes the center of mass and minimum and maximum radii
  plot_mass() # plot the measurements

  • Larger square:
  • The minimum diameter(a = 9.97) can be considered as the side of the square
  • The maximum diameter, given by \(a \sqrt {2}\) can be considered the diagonal of the square (\(9.97 \sqrt {2} = 14,099 \approx 14,105\)
t(img_res_cor)
                             1          2          3          4
id                     1.00000    2.00000    3.00000    4.00000
x                    667.99511 1736.51589 1736.57088 1736.96695
y                    796.99281  451.99673 1295.35408  938.00393
area                  99.81199   24.99414    7.00615    7.93396
area_ch               99.68629   24.91067    7.00866    7.89185
perimeter             39.92650   19.96415   10.08830   11.90183
radius_mean            5.72789    2.86321    1.48946    1.66599
radius_min             4.98370    2.48917    1.47666    0.98706
radius_max             7.05698    3.52773    1.50371    2.22238
radius_sd              0.62876    0.31379    0.00379    0.42226
diam_mean             11.45577    5.72641    2.97893    3.33198
diam_min               9.96740    4.97834    2.95333    1.97412
diam_max              14.11395    7.05547    3.00743    4.44476
major_axis             4.07720    2.03853    1.05486    1.48009
minor_axis             4.07190    2.03488    1.05157    0.87352
caliper               14.03942    7.01071    2.99612    4.43416
length                11.12135    6.51376    2.99197    3.98985
width                 11.10659    6.50271    2.99284    1.98532
radius_ratio           1.41601    1.41723    1.01832    2.25152
theta                  0.12122   -1.18050   -1.35228    0.00105
eccentricity           0.05093    0.05980    0.07886    0.80727
form_factor            0.78681    0.78804    0.86507    0.70384
narrow_factor          1.26238    1.07629    1.00139    1.11136
asp_ratio              1.00133    1.00170    0.99971    2.00967
rectangularity         1.23753    1.69468    1.27809    0.99838
pd_ratio               2.84388    2.84766    3.36712    2.68412
plw_ratio              1.79623    1.53376    1.68565    1.99188
solidity               1.00126    1.00335    0.99964    1.00534
convexity              0.75029    0.74873    0.90505    0.83591
elongation             0.00133    0.00170   -0.00029    0.50241
circularity           15.97128   15.94643   14.52634   17.85407
circularity_haralick   9.10975    9.12467  393.24974    3.94539
circularity_norm       0.78548    0.78537    0.86045    0.69937
coverage               0.42666    0.10684    0.02995    0.03392

3.2 Texture features

The function computes 13 Haralick texture features for each object based on a gray-level co-occurrence matrix (Haralick et al. 1979)8. Haralick features depend on the configuration of the parameters har_nbins and har_scales. har_nbins controls the number of bins used to compute the Haralick matrix. A smaller har_nbins can give more accurate estimates of the correlation because the number of events per bin is higher. While a higher value will give more sensitivity. har_scales controls the number of scales used to compute the Haralick features. Since Haralick features compute the correlation of intensities of neighboring pixels, it is possible to identify textures with different scales, e.g., a texture that is repeated every two pixels or 10 pixels. By default, the Haralick features are computed with the R band. To change this default, use the argument har_band. For example, har_band = 2 will compute the features with the green band.

The following measures are returned (for more details, see this post)

  • asm: The angular second-moment feature.
  • con: The contrast feature
  • cor: Correlation measures the linear dependency of gray levels of neighboring pixels.
  • var: The variance of gray levels pixels.
  • idm: The Inverse Difference Moment (IDM), i.e., the local homogeneity.
  • sav: The Sum Average.
  • sva: The Sum Variance.
  • sen: Sum Entropy.
  • dva: Difference Variance.
  • den: Difference Entropy
  • f12: Difference Variance.
  • f13: The angular second-moment feature.

3.3 Single image processing

The analyze_objects() function calculates a range of measurements that can be used to study the shape and texture of objects, such as leaves. In the following example, I show how to plot the length and width of each leaf in the following image.

leaves <- image_import("folhas.jpg", plot = TRUE)

leaves_meas <-
  analyze_objects(leaves,
                  watershed = FALSE)

leaves_cor <- get_measures(leaves_meas, dpi = 300)

t(leaves_cor)
                             1         2
id                     1.00000   2.00000
x                    527.55799 232.98225
y                    298.91031 824.80531
area                   5.86235   3.87949
area_ch                5.94565   4.22712
perimeter             12.16180   9.94191
radius_mean            1.59090   1.14499
radius_min             0.73539   0.83769
radius_max             2.68488   1.67672
radius_sd              0.58172   0.21951
diam_mean              3.18180   2.28999
diam_min               1.47078   1.67538
diam_max               5.36976   3.35344
major_axis             1.58955   0.97653
minor_axis             0.58515   0.63683
caliper                5.23440   3.20489
length                 5.24101   3.20957
width                  1.60227   1.88314
radius_ratio           3.65097   2.00160
theta                 -1.04997  -0.04881
eccentricity           0.92978   0.75810
form_factor            0.49807   0.49322
narrow_factor          0.99874   0.99854
asp_ratio              3.27100   1.70436
rectangularity         1.43244   1.55796
pd_ratio               2.32344   3.10211
plw_ratio              1.77719   1.95219
solidity               0.98599   0.91776
convexity              0.91242   0.79138
elongation             0.69428   0.41327
circularity           25.23036  25.47800
circularity_haralick   2.73482   5.21617
circularity_norm       0.49436   0.48864
coverage               0.10392   0.06877
# plot width and length
plot_measures(leaves_cor , measure = "width")
plot_measures(leaves_cor , measure = "length", vjust = 50)

Here, we will count the grains in the grains.jpg image. This image has a cyan background and contains 90 soybeans that touch each other. The analyze_objects() function segments the image using the normalized blue index by default, as follows \(NB =(B /(R + G + B))\), where R, G and B are the red, green and blue bands. Note that if the image is contained in the default directory, it is not necessary to import it. Just enter the image name in quotes in the img argument(e.g., img = "grains").

In this example, objects are counted and segmented objects are colored with random colors using the show_segmentation = TRUE argument. Users can set show_contour = FALSE to remove the contour line and identify the objects (in this example, the grains) using the marker = "id" arguments. The background color can also be changed with col_background.

count <-
  analyze_objects(img = "grains",
                  show_segmentation = TRUE,
                  show_contour = FALSE,
                  marker = "id")

count$statistics
       stat        value
1         n 9.000000e+01
2  min_area 4.440000e+02
3 mean_area 6.731000e+02
4  max_area 8.400000e+02
5   sd_area 7.179435e+01
6  sum_area 6.057900e+04
7  coverage 5.196747e-02
# Get the measurements of the object
get_measures(count) |> 
  head() |> 
  t()
                             1         2         3         4         5
id                     1.00000   2.00000   3.00000   4.00000   5.00000
x                    350.85885 823.97070 810.25934 709.00419 806.08333
y                    410.19954 263.76794 197.83538 261.79469 341.01925
area                 840.00000 822.00000 735.00000 761.00000 755.00000
area_ch              809.00000 806.50000 711.00000 733.00000 724.00000
perimeter            104.49747 108.49747  98.49747 100.25483  99.42641
radius_mean           15.89263  15.72760  14.84635  15.11219  15.04126
radius_min            14.82535  13.73620  14.14058  12.88636  13.64198
radius_max            16.89040  17.80771  15.38458  17.04844  16.73018
radius_sd              0.47873   1.20792   0.31891   1.18211   0.81518
diam_mean             31.78526  31.45520  29.69270  30.22438  30.08252
diam_min              29.65071  27.47239  28.28116  25.77273  27.28396
diam_max              33.78080  35.61543  30.76917  34.09688  33.46037
major_axis            11.43632  11.90210  10.56633  11.46245  11.14716
minor_axis            11.04595  10.35090  10.43395   9.91827  10.13100
caliper               33.52611  35.46830  30.59412  33.83785  33.12099
length                33.49452  35.36845  30.47650  33.81768  33.01793
width                 32.03030  29.77299  30.04568  27.70773  29.39465
radius_ratio           1.13929   1.29641   1.08797   1.32298   1.22638
theta                  1.22449  -0.30022  -0.84102  -0.29657   0.58308
eccentricity           0.25904   0.49363   0.15780   0.50128   0.41714
form_factor            0.96667   0.87749   0.95202   0.95145   0.95974
narrow_factor          1.00094   1.00282   1.00386   1.00060   1.00312
asp_ratio              1.04571   1.18794   1.01434   1.22051   1.12326
rectangularity         1.27719   1.28105   1.24583   1.23129   1.28550
pd_ratio               3.11690   3.05900   3.21949   2.96280   3.00192
plw_ratio              1.59478   1.66557   1.62746   1.62949   1.59305
solidity               1.03832   1.01922   1.03376   1.03820   1.04282
convexity              0.94526   0.91214   0.90327   0.88220   0.86156
elongation             0.04372   0.15820   0.01414   0.18067   0.10974
circularity           12.99967  14.32081  13.19966  13.20766  13.09352
circularity_haralick  33.19729  13.02043  46.55411  12.78406  18.45156
circularity_norm       0.91316   0.82572   0.89568   0.89518   0.90254
coverage               0.00072   0.00071   0.00063   0.00065   0.00065
                             6
id                     6.00000
x                    817.90089
y                    713.82865
area                 739.00000
area_ch              713.00000
perimeter             98.84062
radius_mean           14.87775
radius_min            13.79271
radius_max            16.25471
radius_sd              0.61950
diam_mean             29.75550
diam_min              27.58541
diam_max              32.50943
major_axis            10.89320
minor_axis            10.15209
caliper               31.89044
length                31.76844
width                 29.09858
radius_ratio           1.17850
theta                 -1.12183
eccentricity           0.36255
form_factor            0.95057
narrow_factor          1.00384
asp_ratio              1.09175
rectangularity         1.25090
pd_ratio               3.09938
plw_ratio              1.62388
solidity               1.03647
convexity              0.88516
elongation             0.08404
circularity           13.21985
circularity_haralick  24.01584
circularity_norm       0.89333
coverage               0.00063

In the following example, we will select objects with an area above the average of all objects using lower_size = 675.

#

analyze_objects("grains",
                marker = "id",
                topn_upper = 5)

Users can also use the topn _* arguments to select the n objects based on the smallest or largest areas. Let’s see how to select the 5 grains with the smallest area, showing the original grains on a blue background. We will also use the my_index argument to choose a custom index to segment the image. Just for comparison, we will explicitly set the normalized blue index by calling my_index = "B /(R + G + B)".

analyze_objects("grains",
                marker = "id",
                topn_lower = 5,
                col_background = "black",
                index = "B /(R + G + B)") # normalized blue(NB)

3.4 Batch processing

In image analysis, it is often necessary to process more than one image. For example, in plant breeding, the number of grains per plant(eg wheat) is often used in the indirect selection of high-yielding plants. In pliman, batch processing can be done when the user declares the pattern argument.

To speed up processing time, especially for large numbers of images, the parallel = TRUE argument can be used. In this case, images are processed asynchronously (in parallel) in separate R sessions running in the background on the same machine. The number of sections is set to 50% of available cores. This number can be explicitly controlled with the workers argument.

system.time(
  list_res <- analyze_objects(pattern = "img_sb",
                              verbose = FALSE,
                              plot =  FALSE,
                              dir_original = "soja")
)
  usuário   sistema decorrido 
     2.05      0.17      2.54 
# parallel processing
# 6 multiple sections (50% of my pc's cores)
system.time(
  list_res <-
    analyze_objects(pattern = "img_sb",
                    verbose = FALSE,
                    plot =  FALSE,
                    dir_original = "soja",
                    parallel = TRUE)
)
  usuário   sistema decorrido 
     0.12      0.00     17.77 

4 Object coordinates

Users can get the coordinates for all desired objects with object_coord(). When the id argument is set to NULL (default), a bounding rectangle is drawn including all objects. Use id = "all" to get the coordinates of all objects in the image or use a numeric vector to indicate the objects to calculate the coordinates. Note that the watershed = FALSE argument is used to avoid unique objects being split up into multiple objects by the watershed segmentation algorithm.

leaves <- image_import("folhas.jpg", plot = TRUE)

# get the id of each object
object_id(leaves, watershed = FALSE)

# Get the coordinates of a bounding rectangle around all objects
object_coord(leaves, watershed = FALSE)

     xleft xright ybottom ytop
[1,]    39    701      48  936
# Get coordinates for all objects
object_coord(leaves ,
             id = "all",
             watershed = FALSE)

     xleft xright ybottom ytop
[1,]   378    701      48  602
[2,]    39    424     709  936
# Get the coordinates of objects 1 and 2
# 20 border pixels
object_coord(leaves ,
             id = 1,
             edge = 20,
             watershed = FALSE)

     xleft xright ybottom ytop
[1,]   360    719      30  620

5 Isolating objects

Knowing the coordinates of each object, it is possible to isolate it. The object_isolate() function is used for this. In the following example, I will isolate object 1 and set a 10-pixel border around the object.

id1 <-
  object_isolate(leaves ,
                 watershed = FALSE,
                 id = 1,
                 edge = 10)
plot(id1)

6 Including objects in a list

object_split() can be used to split up a series of objects contained in a single image into a list, where each element is one object. By default, the background is removed and shown in white.

list <- object_split(leaves, watershed = FALSE)
==============================
Summary of the procedure
==============================
Number of objects: 2 
Average area     : 67935 
Minimum area     : 54096 
Maximum area     : 81774 
Objects created  : 2 
==============================

str(list)
List of 2
 $ 1:Formal class 'Image' [package "EBImage"] with 2 slots
  .. ..@ .Data    : num [1:326, 1:557, 1:3] 0.427 0.427 0.427 0.431 0.416 ...
  .. ..@ colormode: int 2
  .. ..$ dim: int [1:3] 326 557 3
 $ 2:Formal class 'Image' [package "EBImage"] with 2 slots
  .. ..@ .Data    : num [1:386, 1:230, 1:3] 0.463 0.467 0.478 0.459 0.451 ...
  .. ..@ colormode: int 2
  .. ..$ dim: int [1:3] 386 230 3

7 RGB values for each object

To get the RGB intensity of each object in the image, we use the object_rgb = TRUE argument in the analyze_objects() function. In the following example, we will use the R, G and B bands and their normalized values. The pliman_indexes() function returns the indexes available in the package. To compute a specific index, simply enter a formula containing the values of R, G, or B (e.g. object_index = "B/G+R").

img <- image_import("flax.jpg", plot = TRUE)

(indx <- pliman_indexes())
 [1] "R"     "G"     "B"     "NR"    "NG"    "NB"    "GB"    "RB"    "GR"   
[10] "BI"    "BIM"   "SCI"   "GLI"   "HI"    "NGRDI" "NDGBI" "NDRBI" "I"    
[19] "S"     "VARI"  "HUE"   "HUE2"  "BGI"   "L"     "GRAY"  "GLAI"  "CI"   
[28] "SHP"   "RI"    "G-B"   "G-R"   "R-G"   "R-B"   "B-R"   "B-G"   "DGCI" 
[37] "GRAY2" "L*"    "a"     "b*"    "L*-a"  "L*-b"  "b*-a" 
flax_leaves <-
  analyze_objects(img ,
                  index = "B",
                  watershed = FALSE,
                  filter = 2,
                  object_index = pliman_indexes(), 
                  marker = "id",
                  marker_col = "black",
                  col_background = "white",
                  pixel_level_index = TRUE,
                  show_contour = FALSE)

# PCA with the indices
ind <- summary_index(flax_leaves, type ="var")
Warning: Columns GB, RB, HI, SHP, RI with infinite/NA values removed.
Warning in sqrt(eigenvalue): NaNs produzidos

The R index provided the greatest contribution to the variation of PC1. The biplot containing the indices (variables) and the grains (individuals) can be seen below.

get_biplot(ind$pca_res, show = "var")

get_biplot(ind$pca_res, show = "ind")

Now, let’s plot the DGCI (Dark Green Color Index) on each object. The DGCI is based on the HSB (Hue, Saturation, and Brightness) space color and has been used as an indicator of the darkness of the green color 9

plot(img)
plot_measures(flax_leaves, measure = "DGCI")

It seems that leaves with an average DGCI value of less than 0.4 can be considered “Yellowish” leaves. Users can then work with this feature and adapt it to their case.

report <-
  summary_index(flax_leaves ,
                index = "DGCI",
                cut_point = 0.45,
                plot = FALSE)
Warning: Columns GB, RB, HI, SHP, RI with infinite/NA values removed.
Warning in sqrt(eigenvalue): NaNs produzidos
ids <- report$ids
report$between_id
    n nsel      prop mean_index_sel mean_index_nsel
1 156   34 0.2179487      0.3605177       0.6151214
report$within_id[report$within_id$id %in% ids,]
     id         x         y n_less n_greater less_ratio greater_ratio
33   33  411.2955  193.0278    567       194      0.745         0.255
46   46 1210.7760  262.1628   1350       121      0.918         0.082
54   54  968.9355  277.1675    588       152      0.795         0.205
82   82  212.8252  403.4821    826        94      0.898         0.102
84   86 1015.7916  439.0643    682       140      0.830         0.170
86   88 1356.2915  444.4434   1011       163      0.861         0.139
91   93 1410.8280  449.3729   1230        22      0.982         0.018
93   95 1211.4498  470.4446    868       214      0.802         0.198
97   99  702.7143  527.2268    872        82      0.914         0.086
102 104  811.2008  554.1447   1156        37      0.969         0.031
105 107 1478.2658  536.0117    571        87      0.868         0.132
107 109  527.7322  575.4593   1380       142      0.907         0.093
108 110 1214.9970  600.8662    866         2      0.998         0.002
109 111 1018.1722  593.1940    981       239      0.804         0.196
110 112 1453.2494  603.1382    856       673      0.560         0.440
114 116 1390.3908  647.5456   1192       225      0.841         0.159
116 119 1319.1235  663.9558    626         3      0.995         0.005
120 123  388.1048  685.2422    863       432      0.666         0.334
122 125  438.4210  713.7525    724       228      0.761         0.239
123 126 1102.7345  701.4443    794       595      0.572         0.428
124 127  225.4042  695.3136    871       205      0.809         0.191
129 132  893.3411  706.0923    545        55      0.908         0.092
132 136 1224.0432  778.8951    626       128      0.830         0.170
134 137  719.3392  796.4026    710        15      0.979         0.021
137 140  770.8562  807.8198   1332        89      0.937         0.063
144 147 1106.1427  869.9452   1300         6      0.995         0.005
146 149  396.4064  904.0924   1277       267      0.827         0.173
148 151  147.2097  870.4569   1270       278      0.820         0.180
151 155  896.3308  918.5319    936       424      0.688         0.312
155 159  220.3641  988.6054    800        18      0.978         0.022
157  84  702.7616 1030.7817   1018        NA         NA            NA
158  85  865.5592 1035.2224    895        NA         NA            NA
159 117  965.9459 1050.1934    756        NA         NA            NA
160 152  483.7303 1054.4670   1004        NA         NA            NA

In the following graph, I plot the distribution of the RGB values “Yellowish” and “Greenish” leaves R, G, and B values.

# distribution of RGB values
library(tidyverse)
rgbs <-
  flax_leaves$object_rgb |>
  select(id, h, s, b) |> 
  mutate(type = ifelse(id %in% ids, "Yellowish", "Greenish")) |>
  select(-id) |>
  pivot_longer(-type)

ggplot(rgbs, aes(x = value)) +
  geom_density(aes(fill = name), alpha = 0.5) +
  facet_grid(type ~ name)
Warning: Removed 74 rows containing non-finite values (`stat_density()`).

Now, using the ids of each grain, I plot the values only in the “yello” leaves.

# plot 
plot(img)
plot_measures(flax_leaves ,
              id = ids,
              measure = "DGCI",
              col = "red")
plot_measures(flax_leaves ,
              id = flax_leaves$results$id[!flax_leaves$results$id %in% ids],
              measure = "DGCI",
              col = "white")

8 Leaf area

8.1 Known resolution

leaves <- image_import("ref_leaves.jpg", plot = TRUE)

af <-
  analyze_objects(leaves,
                  index = "B-G",
                  watershed = FALSE,
                  col_background = "black",
                  marker = "id")

# using images with known resolution
af_cor <- get_measures(af, dpi = 45.8)
plot_measures(af_cor ,
              measure = "area",
              vjust = -20,
              col = " cyan ")

# declaring the measure of a known object
af_cor2 <- 
  get_measures(af,
               id = 18,
               measure = area ~ 25)
-----------------------------------------
measures corrected with:
object id: 18
area     : 25
-----------------------------------------
Total    : 796.959 
Average  : 36.225 
-----------------------------------------
plot_measures(af_cor2 ,
              measure = "area",
              vjust = -35,
              col = "salmon")

8.2 Reference object (dev version)

8.2.1 Single images

The reference argument can now be used to correct the object measures even when images with different shooting distances are used. This differs from the previous example (declaring the object with the known area) in a subtle, but crucial aspect: when reference is informed, batch processing can be used! In this example, the leaf area of the ref_leaves image is quantified and corrected considering a 5 x 5 (25 cm\(^2\)) red square as the reference object. When reference = TRUE is informed in analyze_objects() the function will perform a two-step process of object segmentation; so, the time processing is a bit slower.

The first step consists in segmenting the foreground (leaves and reference object) from the background. To do that, an image index is used and can be declared in the back_fore_index. The default (back_fore_index = "R/(G/B)") is optimized to segment white backgrounds from green leaves and a blue reference object. Let’s see how this index performs in this example.

img <- image_import("ref_leaves.jpg", plot = TRUE)

ind <- image_index(img, index = "R/(G/B)", plot =  FALSE)[[1]]
Index 'R/(G/B)' is not available. Trying to compute your own index.
bin <- image_binary(img, index = "R/(G/B)", plot =  FALSE)[[1]]
Index 'R/(G/B)' is not available. Trying to compute your own index.
image_combine(ind, bin)

This index definitively is not the better option in this case. Will some other available index be better?

image_index(img, index = "all")
Warning: The number of layers is too large and plots may not fit well to the
plot area. Consider reducing the number of indexes used.

The B-G index seems to be a good candidate.

ind <- image_index(img, index = "B-G", plot =  FALSE)[[1]]
seg1 <- image_segment(img, index = "B-G", plot =  FALSE)
image_combine(ind, seg1)

Good job! now, we have the background removed. The next step is to segment the objects and the reference template. We basically need to repeat the previous step isolating the reference.

# see the better index
image_index(seg1, index = "all")
Warning: The number of layers is too large and plots may not fit well to the
plot area. Consider reducing the number of indexes used.

The R-G is a good candidate. So, I’ll perform the second segmentation using this index.

seg2 <- 
  image_binary(seg1,
               index = "R-G")

# number of pixels in the reference object
length(which(seg2$`R-G` != 1))
[1] 8108

Now that we know the indexes to be used for each segmentation, we can use the function analyze_objects to get the corrected measures based on the reference object.

res1 <- 
  analyze_objects(img, 
                  reference = TRUE,
                  reference_area = 25,
                  back_fore_index = "B-G",
                  fore_ref_index = "R-G",
                  watershed = FALSE,
                  marker = "area")

# plot the measures corrected by the image resolution
plot_measures(af_cor ,
              measure = "area",
              vjust = -20,
              col = " cyan ")

8.2.2 Multiple images

If users need to analyze multiple images from the same sample, the images must share the same filename prefix, which is defined as the part of the filename that precedes the first hyphen (-) or underscore (_). Then, when using get_measures(), measurements from leaf images called, for example, F1-1.jpeg, F1_2.jpeg and F1-3.jpeg will be combined into a single image (F1), displayed in the merge object. This is useful, for example, for analyzing large sheets that need to be split into multiple images or multiple sheets belonging to the same sample that cannot be scanned into a single image.

In the following example, 36 images will be analyzed. These images contain flax leaves from 12 evaluation dates, with three replications10. Note that to ensure that all images are processed, all images must share a common pattern, in this case ("A"). Here, I will use the pattern = "A" to indicate that all images with this pattern name should be merged.

res <-
  analyze_objects(pattern = "A",
                  dir_original = "linhaca",
                  reference = TRUE,
                  reference_area = 20,
                  watershed = FALSE,
                  filter = 2,
                  parallel = TRUE)
Processing 36 images in multiple sessions (6). Please, wait.
--------------------------------------------
     Image Objects
   A1_28_1      18
   A1_28_2      29
   A1_28_3      28
  A10_90_1     149
  A10_90_2     116
  A10_90_3      77
  A11_98_1     133
  A11_98_2     154
  A11_98_3     119
 A12_105_1     111
 A12_105_2     127
 A12_105_3     155
   A2_32_1      24
   A2_32_2      25
   A2_32_3      31
   A3_42_1      39
   A3_42_2      47
   A3_42_3      54
   A4_46_1      57
   A4_46_2      38
   A4_46_3      56
   A5_55_1      53
   A5_55_2      74
   A5_55_3      63
   A6_63_1      68
   A6_63_2     105
   A6_63_3      83
   A7_70_1      98
   A7_70_2     111
   A7_70_3     112
   A8_76_1     113
   A8_76_2     116
   A8_76_3     113
   A9_83_1     119
   A9_83_2      89
   A9_83_3     100
--------------------------------------------
Done!
Elapsed time: 00:00:40
merged <- get_measures(res)

Note that the merged is a list with three objects:

  • results: a data frame that contains the measurements of each individual object (in this case, an individual leaf) of each analyzed image.
merged$results |> head(n = 5) |> t()
                     1          2          3          4          5         
img                  "A1_28_1"  "A1_28_1"  "A1_28_1"  "A1_28_1"  "A1_28_1" 
id                   "1"        "2"        "3"        "4"        "5"       
x                    "372.0789" "421.4407" "162.9520" "126.3978" "266.4682"
y                    "315.9907" "319.3856" "349.6462" "386.7082" "399.5777"
area                 "0.11514"  "0.15914"  "0.15721"  "0.19485"  "0.20121" 
area_ch              "0.15056"  "0.19042"  "0.15098"  "0.19291"  "0.22515" 
perimeter            "2.09472"  "2.11421"  "1.70686"  "2.29741"  "2.13036" 
radius_mean          "0.24918"  "0.26147"  "0.23023"  "0.30151"  "0.26289" 
radius_min           "0.00981"  "0.06450"  "0.11653"  "0.09891"  "0.11571" 
radius_max           "0.47779"  "0.45243"  "0.36772"  "0.51707"  "0.42928" 
radius_sd            "0.12621"  "0.11123"  "0.07650"  "0.13080"  "0.09084" 
diam_mean            "0.49836"  "0.52294"  "0.46046"  "0.60303"  "0.52578" 
diam_min             "0.01962"  "0.12899"  "0.23307"  "0.19781"  "0.23143" 
diam_max             "0.95558"  "0.90487"  "0.73543"  "1.03413"  "0.85855" 
major_axis           "0.26979"  "0.26867"  "0.21949"  "0.31601"  "0.24862" 
minor_axis           "0.07137"  "0.09192"  "0.10304"  "0.08948"  "0.12441" 
caliper              "0.91076"  "0.87337"  "0.72478"  "1.02769"  "0.80493" 
length               "0.90912"  "0.87174"  "0.72291"  "1.02496"  "0.80212" 
width                "0.23326"  "0.30972"  "0.28295"  "0.23797"  "0.42684" 
radius_ratio         "48.71382" " 7.01490" " 3.15544" " 5.22783" " 3.70981"
theta                "-1.46559" " 1.32218" " 0.08942" "-0.58010" "-1.17190"
eccentricity         "0.96438"  "0.93965"  "0.88296"  "0.95907"  "0.86580" 
form_factor          "0.32974"  "0.44741"  "0.67808"  "0.46390"  "0.55713" 
narrow_factor        "1.00180"  "1.00187"  "1.00259"  "1.00267"  "1.00351" 
asp_ratio            "3.89739"  "2.81464"  "2.55492"  "4.30702"  "1.87920" 
rectangularity       "1.84187"  "1.69655"  "1.30113"  "1.25182"  "1.70157" 
pd_ratio             "2.29997"  "2.42074"  "2.35500"  "2.23550"  "2.64662" 
plw_ratio            "1.83363"  "1.78949"  "1.69693"  "1.81911"  "1.73347" 
solidity             "0.76471"  "0.83576"  "1.04125"  "1.01004"  "0.89367" 
convexity            "0.55229"  "0.55353"  "0.91615"  "0.91475"  "0.74206" 
elongation           "0.74342"  "0.64472"  "0.60860"  "0.76782"  "0.46786" 
circularity          "38.10998" "28.08723" "18.53230" "27.08857" "22.55541"
circularity_haralick "1.97429"  "2.35064"  "3.00944"  "2.30524"  "2.89416" 
circularity_norm     "0.28456"  "0.40228"  "0.62197"  "0.42601"  "0.51422" 
coverage             "0.00113"  "0.00156"  "0.00154"  "0.00191"  "0.00197" 
  • summary : a data frame that contains the summary of the results, containing the number of objects in each image (n) the sum, mean and standard deviation of the area of each image, as well as the average value for all others measurements (perimeter, radius, etc.)
merged$summary |> head(n = 5) |> t()
                     1            2            3            4            
img                  "imgA1_28_1" "imgA1_28_2" "imgA1_28_3" "imgA10_90_1"
n                    " 18"        " 29"        " 28"        "149"        
area_sum             "  2.56594"  "  6.29361"  "  7.04808"  "171.90417"  
area_mean            "0.14255"    "0.21702"    "0.25172"    "1.15372"    
area_sd              "0.05759"    "0.07495"    "0.09524"    "0.43971"    
area_ch              "0.16279"    "0.21887"    "0.27358"    "1.21400"    
perimeter            "2.00362"    "2.71748"    "2.71511"    "6.54996"    
radius_mean          "0.24832"    "0.34275"    "0.33846"    "0.81144"    
radius_min           "0.07567"    "0.07876"    "0.10713"    "0.18661"    
radius_max           "0.43423"    "0.62526"    "0.60196"    "1.50957"    
radius_sd            "0.10638"    "0.16549"    "0.14840"    "0.40285"    
diam_mean            "0.49665"    "0.68550"    "0.67693"    "1.62288"    
diam_min             "0.15134"    "0.15753"    "0.21426"    "0.37322"    
diam_max             "0.86847"    "1.25052"    "1.20392"    "3.01914"    
major_axis           "0.25413"    "0.37116"    "0.35314"    "0.88572"    
minor_axis           "0.08556"    "0.08191"    "0.10610"    "0.18347"    
caliper              "0.83830"    "1.23288"    "1.17672"    "2.97301"    
length               "0.83666"    "1.23244"    "1.17617"    "2.97448"    
width                "0.26194"    "0.22838"    "0.30106"    "0.51589"    
radius_ratio         " 8.74014"   "10.30621"   " 5.72830"   " 9.70617"   
theta                " 0.02449"   "-0.10613"   " 0.26184"   " 0.07055"   
eccentricity         "0.92820"    "0.97374"    "0.95030"    "0.97749"    
form_factor          "0.46118"    "0.36111"    "0.43781"    "0.33784"    
narrow_factor        "1.00176"    "1.00039"    "1.00069"    "0.99980"    
asp_ratio            "3.88030"    "5.64527"    "4.24631"    "6.23272"    
rectangularity       "1.51394"    "1.32901"    "1.39147"    "1.33923"    
pd_ratio             "2.36860"    "2.20665"    "2.29786"    "2.19253"    
plw_ratio            "1.81024"    "1.86072"    "1.83281"    "1.87284"    
solidity             "0.95496"    "0.98661"    "0.98786"    "1.01775"    
convexity            "0.77426"    "0.77286"    "0.84992"    "0.85640"    
elongation           "0.69142"    "0.81273"    "0.74698"    "0.82959"    
circularity          "30.16456"   "36.25009"   "29.78587"   "38.46601"   
circularity_haralick "2.37631"    "2.08159"    "2.31270"    "2.01769"    
circularity_norm     "0.41143"    "0.32685"    "0.40763"    "0.30386"    
coverage             "0.00140"    "0.00128"    "0.00141"    "0.00076"    
                     5            
img                  "imgA10_90_2"
n                    "116"        
area_sum             "119.06277"  
area_mean            "1.02640"    
area_sd              "0.33712"    
area_ch              "1.13381"    
perimeter            "6.38542"    
radius_mean          "0.77889"    
radius_min           "0.16397"    
radius_max           "1.46491"    
radius_sd            "0.39273"    
diam_mean            "1.55778"    
diam_min             "0.32795"    
diam_max             "2.92982"    
major_axis           "0.85230"    
minor_axis           "0.17488"    
caliper              "2.87974"    
length               "2.87874"    
width                "0.50404"    
radius_ratio         "10.20943"   
theta                " 0.03805"   
eccentricity         "0.97582"    
form_factor          "0.33066"    
narrow_factor        "1.00079"    
asp_ratio            "6.24416"    
rectangularity       "1.39066"    
pd_ratio             "2.20709"    
plw_ratio            "1.88076"    
solidity             "0.99459"    
convexity            "0.85418"    
elongation           "0.82530"    
circularity          "40.13084"   
circularity_haralick "2.00092"    
circularity_norm     "0.29394"    
coverage             "0.00069"    

The area_sum of img A1_28_1 is the sum of the 18 leaves.

sum(merged$results$area[1:18])
[1] 2.56595
  • merge: a data frame that contains the results merged by image prefix (In this case, A1 to A12).
merged$merge |> head(n = 5) |> t()
                     1           2           3           4          
img                  "imgA1"     "imgA10"    "imgA11"    "imgA12"   
n                    " 75"       "342"       "406"       "393"      
area_sum             " 15.90763" "364.00764" "478.02200" "368.89681"
area_mean            "0.20376"   "1.04290"   "1.20252"   "0.95738"  
area_sd              "0.07593"   "0.29574"   "0.44633"   "0.33975"  
area_ch              "0.21841"   "1.09549"   "1.25403"   "0.98737"  
perimeter            "2.47874"   "6.57315"   "6.76831"   "6.16784"  
radius_mean          "0.30984"   "0.80629"   "0.83100"   "0.76035"  
radius_min           "0.08719"   "0.16293"   "0.18562"   "0.15426"  
radius_max           "0.55382"   "1.52077"   "1.55450"   "1.42725"  
radius_sd            "0.14009"   "0.41090"   "0.41521"   "0.38635"  
diam_mean            "0.61969"   "1.61258"   "1.66199"   "1.52070"  
diam_min             "0.17437"   "0.32586"   "0.37125"   "0.30851"  
diam_max             "1.10764"   "3.04155"   "3.10900"   "2.85450"  
major_axis           "0.32614"   "0.88784"   "0.90842"   "0.83691"  
minor_axis           "0.09119"   "0.16641"   "0.18464"   "0.15716"  
caliper              "1.08263"   "3.00044"   "3.06851"   "2.81646"  
length               "1.08175"   "3.00048"   "3.06880"   "2.81631"  
width                "0.26380"   "0.46698"   "0.51461"   "0.43114"  
radius_ratio         " 8.25822"  "10.63699"  "10.35437"  " 9.96570" 
theta                "0.06007"   "0.02661"   "0.10848"   "0.19753"  
eccentricity         "0.95075"   "0.98051"   "0.97600"   "0.97927"  
form_factor          "0.42003"   "0.30940"   "0.32598"   "0.32386"  
narrow_factor        "1.00095"   "1.00023"   "1.00011"   "1.00023"  
asp_ratio            "4.59062"   "6.92547"   "6.39061"   "6.73768"  
rectangularity       "1.41147"   "1.33254"   "1.33934"   "1.28724"  
pd_ratio             "2.29103"   "2.18462"   "2.20346"   "2.19193"  
plw_ratio            "1.83459"   "1.89170"   "1.88509"   "1.89288"  
solidity             "0.97648"   "1.00830"   "1.00417"   "1.00802"  
convexity            "0.79901"   "0.85295"   "0.84057"   "0.83455"  
elongation           "0.75038"   "0.84453"   "0.83136"   "0.84134"  
circularity          "32.06684"  "42.44614"  "40.09633"  "40.85288" 
circularity_haralick "2.25686"   "1.97161"   "2.01276"   "1.98964"  
circularity_norm     "0.38197"   "0.27674"   "0.29551"   "0.29044"  
coverage             "0.00136"   "0.00070"   "0.00081"   "0.00075"  
                     5          
img                  "imgA2"    
n                    " 80"      
area_sum             " 21.44658"
area_mean            "0.26849"  
area_sd              "0.12016"  
area_ch              "0.30438"  
perimeter            "2.83415"  
radius_mean          "0.35092"  
radius_min           "0.09725"  
radius_max           "0.62590"  
radius_sd            "0.15732"  
diam_mean            "0.70185"  
diam_min             "0.19451"  
diam_max             "1.25180"  
major_axis           "0.36743"  
minor_axis           "0.10928"  
caliper              "1.22042"  
length               "1.21789"  
width                "0.32062"  
radius_ratio         " 9.67376" 
theta                "0.08814"  
eccentricity         "0.94696"  
form_factor          "0.43987"  
narrow_factor        "1.00180"  
asp_ratio            "4.17961"  
rectangularity       "1.43860"  
pd_ratio             "2.30524"  
plw_ratio            "1.82475"  
solidity             "0.96833"  
convexity            "0.83413"  
elongation           "0.73372"  
circularity          "30.65038" 
circularity_haralick "2.29773"  
circularity_norm     "0.40617"  
coverage             "0.00170"  

Below, I will fit a non-linear model (Logistic) to model the leaf area evolution across the crop cycle.

Code
library(tidyverse)

df_plot <- 
  merged$summary |> 
  separate_col(img, 
               into = c("avaliacao", "das", "bloco")) |> 
  mutate(das = as.numeric(das))

# plot 

my_theme <- 
  theme_bw(base_size = 14) +
  theme(panel.grid.major = element_blank())

# leaf area
formula <- y ~ b1/(1 + exp(b2 - b3 * x))

ggplot(df_plot, aes(das, area_sum)) + 
  geom_smooth(method = "nls",
              method.args = list(formula = formula,
                                 start = c(b1 = 248,
                                           b2 = 6,
                                           b3 = 0.07)),
              se = FALSE,
              color = "red") +
  stat_summary(fun.data = mean_se,
               geom = "errorbar",
               width = 0.5) +
  stat_summary(fun = mean,
               geom = "point",
               col = "blue",
               size = 3) +
  scale_x_continuous(breaks = unique(df_plot$das)) +
  scale_y_continuous(breaks = seq(0, 150, by = 25)) +
  labs(x = "Days after sowing",
       y = expression(Área~foliar~média~(cm^2~planta^{-1}))) +
  my_theme
# number of leaves
formula <- y ~ b1/(1 + exp(b2 - b3 * x))

ggplot(df_plot, aes(das, n)) + 
  geom_smooth(method = "nls",
              method.args = list(formula = formula,
                                 start = c(b1 = 188,
                                           b2 = 3,
                                           b3 = 0.05)),
              se = FALSE,
              color = "red") +
  stat_summary(fun.data = mean_se,
               geom = "errorbar",
               width = 0.5) +
  stat_summary(fun = mean,
               geom = "point",
               col = "blue",
               size = 3) +
  scale_x_continuous(breaks = unique(df_plot$das)) +
  scale_y_continuous(breaks = seq(0, 150, by = 25)) +
  labs(x = "Days after sowing",
       y = "Number of leaves per plant") +
  my_theme

8.3 Filling ‘holes’

An important aspect to consider in leaf area measures is when leaves present ‘holes’. This can occur, for example, by the attack of pests. In this case, the area would have to be considered, because it was there! The image bellow is used as an example.

holes <- image_import("holes.jpg", plot = TRUE)

In this case, the missing area will not be computed using the default settings of analyze_objects(). To include this area as the leaf area, we can use the argument fill_hull(). Note that this will only work for missing areas within a closed object. If the missing area includes the original leaf contour, there is no (yet available) way to reconstruct the leaf perimeter.

af <-
  analyze_objects(holes,
                  watershed = FALSE,
                  col_background = "white",
                  marker = "area",
                  marker_col = "red",
                  marker_size = 3,
                  plot =  FALSE,
                  save_image = TRUE,
                  dir_processed = tempdir(),
                  contour_size = 5)

# fill the missing area
af2 <-
  analyze_objects(holes,
                  fill_hull = TRUE, # fill ' holes '
                  watershed = FALSE,
                  col_background = "white",
                  marker = "area",
                  marker_col = "red",
                  marker_size = 3,
                  plot =  FALSE,
                  save_image = TRUE,
                  prefix = "proc2_",
                  dir_processed = tempdir(),
                  contour_size = 5)

imgs <- image_import(pattern = "proc", path = tempdir())
image_combine(imgs)

We can simply use the ratio between proc_img and proc_img2 to compute the injured area in this leaflet.

# percent of the injured area
100 - 86432 / 99186 * 100
[1] 12.85867

8.4 Compound leaves

A simple leaf blade is undivided. The blade of a compound leaf is divided into several leaflets. In the following examples, I will show how to analyze simple and compound leaves with analyze_objects(), mainly if the goal is to obtain the measures for each leaf (e.g., mean area), where the number of objects (leaves) will influence the results.

The following images by Daniel Saueressig were obtained from the Sistema de Identificação Dendrológica Online - Floresta Ombrófila Mista11 and show examples of simple and compound leaves.

imgs <- 
  image_import(c("simple.jpg", "compound.jpg")) |> 
  image_horizontal()
image_combine(imgs)

Analyzing non-touching simple leaves is fairly simple. We already did that. The squares in the background have 4 cm\(^2\). With this information, it is possible to obtain the image resolution with dpi(simple), which will be useful to adjust the measures. In this case, the estimated dpi is 48.65.

simple <- imgs$simple.jpg
sarea <- analyze_objects(simple, marker = "id")

Note that with the default settings, the simple leaf was partitioned into small, segmented leaves. This can be solved by either using object_size = "large" or watershed = FALSE, to omit the watershed segmentation algorithm. The last is used here.

sarea <- 
  analyze_objects(simple,
                  watershed = FALSE,
                  marker = "id",
                  show_chull = TRUE,
                  contour_size = 6)

sarea_cor <- get_measures(sarea, dpi = 48.65)
sarea_cor |> t()
                             1         2
id                     1.00000   2.00000
x                    182.30988 177.83018
y                     67.65594 183.76066
area                  20.00228  41.39746
area_ch               31.85154  56.24925
perimeter             31.52950  41.28656
radius_mean            3.03137   4.28452
radius_min             1.09831   1.06972
radius_max             5.71519   7.95304
radius_sd              1.21685   1.80229
diam_mean              6.06273   8.56904
diam_min               2.19662   2.13943
diam_max              11.43038  15.90607
major_axis             3.05069   4.36690
minor_axis             1.16636   1.59089
caliper               11.06845  14.95531
length                11.06847  15.47434
width                  4.69925   6.04070
radius_ratio           5.20362   7.43472
theta                 -0.00016  -0.07118
eccentricity           0.92403   0.93128
form_factor            0.25285   0.30519
narrow_factor          1.00000   0.96646
asp_ratio              2.35537   2.56168
rectangularity         2.60038   2.25801
pd_ratio               2.84859   2.76066
plw_ratio              1.99962   1.91896
solidity               0.62798   0.73596
convexity              0.77953   0.67022
elongation             0.57544   0.60963
circularity           49.69980  41.17595
circularity_haralick   2.49116   2.37726
circularity_norm       0.24365   0.29814
coverage               0.08788   0.18188

For compound leaves, if the watershed segmentation is used, leaflets will probably be considered as different leaves, as can be seen below.

compound <- imgs$compound.jpg
carea <- 
  analyze_objects(compound,
                  show_segmentation = TRUE,
                  show_contour = FALSE,
                  marker = "id")

Therefore, using watershed = FALSE will solve this problem, since all leaflets connected by at least one pixel will be considered part of the same leaf.

carea <- 
  analyze_objects(compound,
                  watershed = FALSE,
                  show_segmentation = TRUE,
                  show_contour = FALSE,
                  show_chull = TRUE,
                  marker = "id")

carea_cor <- get_measures(carea, dpi = 49.5)
carea_cor |> t()
                             3         4
id                     3.00000   4.00000
x                     89.22055 242.57443
y                    111.31761 112.69170
area                  18.84728  15.83509
area_ch               43.16207  33.09070
perimeter             54.54305  48.70669
radius_mean            2.54616   2.24631
radius_min             0.05956   0.02471
radius_max             5.93998   4.94236
radius_sd              1.26133   1.14489
diam_mean              5.09232   4.49263
diam_min               0.11913   0.04943
diam_max              11.87996   9.88471
major_axis             2.20953   2.05408
minor_axis             1.78609   1.46145
caliper                9.27193   7.37643
length                 9.33971   8.10641
width                  7.14337   6.44205
radius_ratio          99.72535 199.98693
theta                 -1.23490   1.29832
eccentricity           0.58869   0.70270
form_factor            0.07961   0.08388
narrow_factor          0.99274   0.90995
asp_ratio              1.30747   1.25836
rectangularity         3.53988   3.29786
pd_ratio               5.88260   6.60302
plw_ratio              3.30903   3.34789
solidity               0.43666   0.47854
convexity              0.38233   0.41166
elongation             0.23516   0.20531
circularity          157.84475 149.81551
circularity_haralick   2.01864   1.96203
circularity_norm       0.07452   0.07817
coverage               0.08572   0.07202

9 Shapefiles

Orthomosaic kindly provided by Prof. Dr. Ivan Ricardo Carvalho (UNIJUÍ)

shp <- image_import("mosaic.jpg")
aligned <- image_rotate(shp, angle = 82.591)

cropped <- image_crop(aligned,
                      width = 1900:2920,
                      height = 1350:1625,
                      plot = TRUE)

# or using the mapview option
# cropped <- image_prepare_mv(shp)
anal <- 
  analyze_objects_shp(cropped,
                      prepare = FALSE,
                      ncol = 13,
                      index = "HUE",
                      object_index = "DGCI",
                      plot = FALSE,
                      watershed = FALSE)
plot(anal$final_image_masked)
plot(anal$shapefiles)
plot_measures(anal, measure = "coverage", vjust = -160, col = "black")
plot_measures(anal, measure = "DGCI", vjust = -185, col = "red")

10 Fourier analyses

10.1 Functions

The functions were adapted from Claude (2088)12

10.2 Elliptical fourier

library(pliman)
imgs <- image_import(pattern = "img", path = "fourier")
names(imgs) <- paste0("ind", 1:9)

conts <- 
  object_contour(imgs,
                 index = "B",
                 watershed = FALSE,
                 plot =  FALSE)


ef <- efourier(conts, nharm = 30)
efi1 <- efourier_inv(ef[[8]], nharm = 5)
efi2 <- efourier_inv(ef[[8]], nharm = 10)
efi3 <- efourier_inv(ef[[8]], nharm = 30)

plot(imgs[[8]])
plot_contour(efi1)
plot_contour(efi2, col = "red")
plot_contour(efi3, col = "blue")

10.3 Rotation and size

library(pliman)
img <- image_import("changes.png", path = "fourier")
cont <- 
  object_contour(img,
                 index = "B",
                 watershed = FALSE,
                 plot =  FALSE)


ef <- 
  efourier(cont) |> 
  efourier_norm() |> 
  efourier_coefs()
ef
  object A1          A2           A3          A4          A5          A6
1      1  1 -0.02075229 -0.001967149 -0.02243785 -0.01051923 -0.02631046
2      2 -1  0.05347429  0.005280597  0.03800024  0.01186258  0.05128006
3      3  1 -0.01316395 -0.002955584 -0.01750128 -0.01291722 -0.02049034
             A7           A8           A9          A10            B1         B2
1  0.0015605953 -0.006927166 -0.011259484  0.004915726 -3.044942e-17 -0.3949627
2 -0.0005919858  0.014879933  0.009298835 -0.010101305 -2.785724e-16  0.3912420
3  0.0010814994 -0.003907685 -0.010955053  0.004655006  7.638715e-18 -0.3969607
             B3         B4           B5          B6           B7          B8
1 -0.0003923146 -0.1202646 -0.002084410 -0.09945037 -0.002193451 -0.03342269
2  0.0146176740  0.1166273 -0.001846976  0.08874169  0.002321198  0.03056498
3  0.0030213897 -0.1212294 -0.001632096 -0.09895883 -0.001303100 -0.03134100
             B9         B10            C1          C2          C3          C4
1 -0.0002261568  0.01457887  0.000000e+00  0.10442995  0.03222546 -0.06960008
2 -0.0050153344 -0.01110277 -3.482156e-16 -0.09969635 -0.07453366  0.06915561
3 -0.0006675290  0.01620912  3.819357e-18  0.10565086  0.02465100 -0.06874620
           C5          C6           C7          C8           C9          C10
1  0.01617662  0.04154609  0.009682157 -0.04718118 -0.001724962  0.007288867
2 -0.02598072 -0.03852236 -0.019599632  0.04202645  0.006442364 -0.006935145
3  0.01291151  0.04239523  0.006671048 -0.04783110 -0.001079465  0.006609363
          D1          D2         D3           D4          D5           D6
1 -0.9523335 -0.01969877  0.3351972 -0.001506831  0.04915802 -0.003672522
2  0.9531687  0.04780208 -0.3290529 -0.006601458 -0.04505256  0.016706985
3 -0.9502921 -0.01514573  0.3383713 -0.001711505  0.05055679 -0.001177859
           D7          D8           D9          D10
1  0.03955211  0.01431461 -0.010822192 0.0011125890
2 -0.03592838 -0.02672881  0.008299971 0.0010049357
3  0.03811810  0.01118330 -0.011920117 0.0006020825

10.4 Morphological diversity

conts <- 
  object_contour(imgs,
                 index = "B",
                 watershed = FALSE,
                 plot =  FALSE) |> 
  efourier() |> 
  efourier_norm() |> 
  efourier_coefs()


coefs <- 
  conts |> 
  dplyr::select(-object) |> 
  dplyr::mutate(specie = c(rep(paste0("specie", 1:3), each = 3)), .before = 1)


library(factoextra)
Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa

Attaching package: 'factoextra'
The following object is masked from 'package:metan':

    get_dist
Warning: package 'FactoMineR' was built under R version 4.2.3
pca <- PCA(coefs, quali.sup = 1, graph = FALSE)
fviz_pca_ind(pca, habillage = 1, repel = TRUE)

11 Distance to an ideotype

#| out-width: "100%"
img <- image_import("distance.png", path = "fourier")
cont <- 
  object_contour(img,
                 index = "B",
                 watershed = FALSE,
                 plot =  TRUE)

efs <- 
  cont |> 
  efourier() |> 
  efourier_norm() |> 
  efourier_coefs() |> 
  dplyr::select(-object)


desc_ide <- as.matrix(efs[1, ])
desc_objs <- as.matrix(efs[-1, ])

# compute the differences between each object and the ideotype
gen_ide <- sweep(desc_objs, 2, desc_ide, "-")

distances <- 
  apply(gen_ide, 1, function(x){sqrt(sum(x^2))}) %>% 
  sort(decreasing = FALSE)
distances
          2           6           3           5           4 
0.002371749 0.128478398 2.331976897 2.430066643 2.458400351