Analysis of Canopy Openness Index from Fisheye Images

Tools to estimate the Canopy Openness Index (COI) from hemispherical (fisheye) canopy photographs. The package imports canopy images, crops them to the circular area delimited by the fisheye lens, binarizes them to separate sky from vegetation pixels, and computes the index as the proportion of sky pixels in the cropped image. Canopy openness is a measure related to canopy closure, canopy cover and understorey illumination, as discussed in Jennings, Brown and Sheil (1999) "Assessing forest canopies and understorey illumination: canopy closure, canopy cover and other measures" . Example canopy images are included so users can test the workflow.


coiR

Edson Silva-Júnior 2026-09-29

coiR

Package to calculate Canopy Openness Index (COI) from canopy images

Installing package

require("devtools")

devtools::install_github("Edbbioeco/coiR")

Loading package

coiR package is downloaded now, so we can library it. Additionally, we library the following packages:

  • terra: import images files as rasters;

  • purrr: create loops for multiple operations;

  • ggplot2: create elegant graphs for imported images;

  • tidyterra: visualize RGB raster

library(coiR)

library(terra)

library(purrr)

library(ggplot2)

library(tidyterra)

Data

Importing

Now, we need to import our data Images may be shotten photos, as .png, .jpg or .jpeg files. first, we import package exemple data, using coiR::coir_data() function, and import them using terra::rast() function for every image, throught a loop with purrr::map() function. Our images (images) are setted as a list class object.

images <- purrr::map(coiR::data_coir(),
                     ~.x |> terra::rast())

images
[[1]]
class       : SpatRaster
size        : 800, 800, 4  (nrow, ncol, nlyr)
resolution  : 1, 1  (x, y)
extent      : 0, 800, 0, 800  (xmin, xmax, ymin, ymax)
coord. ref. : 
source      : imagem1.png
names       : imagem1_1, imagem1_2, imagem1_3, imagem1_4

[[2]]
class       : SpatRaster
size        : 800, 800, 4  (nrow, ncol, nlyr)
resolution  : 1, 1  (x, y)
extent      : 0, 800, 0, 800  (xmin, xmax, ymin, ymax)
coord. ref. : 
source      : imagem2.png
names       : imagem2_1, imagem2_2, imagem2_3, imagem2_4

[[3]]
class       : SpatRaster
size        : 800, 800, 4  (nrow, ncol, nlyr)
resolution  : 1, 1  (x, y)
extent      : 0, 800, 0, 800  (xmin, xmax, ymin, ymax)
coord. ref. : 
source      : imagem3.png
names       : imagem3_1, imagem3_2, imagem3_3, imagem3_4

[[4]]
class       : SpatRaster
size        : 800, 800, 4  (nrow, ncol, nlyr)
resolution  : 1, 1  (x, y)
extent      : 0, 800, 0, 800  (xmin, xmax, ymin, ymax)
coord. ref. : 
source      : imagem4.png
names       : imagem4_1, imagem4_2, imagem4_3, imagem4_4

Visualizing

Next, lets visualize every image, using a purrr::map() loop, through ggplot and tidyterra::geom_spatraster_rgb() function.

purrr::map(images, 
           purrr::in_parallel(
             
             ~ggplot() + 
               tidyterra::geom_spatraster_rgb(data = .x) + 
               theme_void()
             
             ),
           .progress = TRUE)
[[1]]

[[2]]

[[3]]

[[4]]

Canopy Openness Index

Isolating a single image

For our exemples, lets work in two ways: run for a single and run for multiple images. Lets set first image as a single image.

single_image <- images[[1]]

Crop image

Usely, canopy images are shotten photos, square images. For our analysis, we need to crop images into circles. We use coiR::coir_crop() |function.

single_image |> 
  coiR::coir_crop()

class       : SpatRaster
size        : 800, 800, 4  (nrow, ncol, nlyr)
resolution  : 1, 1  (x, y)
extent      : 0, 800, 0, 800  (xmin, xmax, ymin, ymax)
coord. ref. : 
source(s)   : memory
varname     : imagem1
names       : imagem1_1, imagem1_2, imagem1_3, imagem1_4
min values  :         4,        25,        33,        29
max values  :       243,       241,       246,       255

And we also can analyse multiple images from an one shot, using purrr::map() loop.

purrr::map(images, coiR::coir_crop)

[[1]]
class       : SpatRaster
size        : 800, 800, 4  (nrow, ncol, nlyr)
resolution  : 1, 1  (x, y)
extent      : 0, 800, 0, 800  (xmin, xmax, ymin, ymax)
coord. ref. : 
source(s)   : memory
varname     : imagem1
names       : imagem1_1, imagem1_2, imagem1_3, imagem1_4
min values  :         4,        25,        33,        29
max values  :       243,       241,       246,       255

[[2]]
class       : SpatRaster
size        : 800, 800, 4  (nrow, ncol, nlyr)
resolution  : 1, 1  (x, y)
extent      : 0, 800, 0, 800  (xmin, xmax, ymin, ymax)
coord. ref. : 
source(s)   : memory
varname     : imagem2
names       : imagem2_1, imagem2_2, imagem2_3, imagem2_4
min values  :         2,        21,        21,        29
max values  :       255,       255,       255,       255

[[3]]
class       : SpatRaster
size        : 800, 800, 4  (nrow, ncol, nlyr)
resolution  : 1, 1  (x, y)
extent      : 0, 800, 0, 800  (xmin, xmax, ymin, ymax)
coord. ref. : 
source(s)   : memory
varname     : imagem3
names       : imagem3_1, imagem3_2, imagem3_3, imagem3_4
min values  :         3,        19,         7,        29
max values  :       255,       255,       255,       255

[[4]]
class       : SpatRaster
size        : 800, 800, 4  (nrow, ncol, nlyr)
resolution  : 1, 1  (x, y)
extent      : 0, 800, 0, 800  (xmin, xmax, ymin, ymax)
coord. ref. : 
source(s)   : memory
varname     : imagem4
names       : imagem4_1, imagem4_2, imagem4_3, imagem4_4
min values  :         1,        13,         1,        30
max values  :       255,       255,       255,       255

Binarize images

Our next step is to binarize our images. We use coiR::coir_binarize() function. To avoid replot canopy image, we use plot = FALSE argument at coiR::coir_crop() function. To facilite our analysis, lets use pipe (|>) to conect functions output.

single_image |> 
  coiR::coir_crop(plot = FALSE) |> 
  coiR::coir_binarize()

class       : SpatRaster
size        : 800, 800, 1  (nrow, ncol, nlyr)
resolution  : 1, 1  (x, y)
extent      : 0, 800, 0, 800  (xmin, xmax, ymin, ymax)
coord. ref. : 
source(s)   : memory
name        : variavel
min value   :        0
max value   :        1

As previously made, we can binarize multiple images, making a function in our purrr::map() loop.

purrr::map(images, 
           purrr::in_parallel(
             
             ~coiR::coir_crop(data = .x,
                              plot = FALSE) |>
               coiR::coir_binarize()
             
             ),
           .progress = TRUE)

[[1]]
class       : SpatRaster
size        : 800, 800, 1  (nrow, ncol, nlyr)
resolution  : 1, 1  (x, y)
extent      : 0, 800, 0, 800  (xmin, xmax, ymin, ymax)
coord. ref. : 
source(s)   : memory
name        : variavel
min value   :        0
max value   :        1

[[2]]
class       : SpatRaster
size        : 800, 800, 1  (nrow, ncol, nlyr)
resolution  : 1, 1  (x, y)
extent      : 0, 800, 0, 800  (xmin, xmax, ymin, ymax)
coord. ref. : 
source(s)   : memory
name        : variavel
min value   :        0
max value   :        1

[[3]]
class       : SpatRaster
size        : 800, 800, 1  (nrow, ncol, nlyr)
resolution  : 1, 1  (x, y)
extent      : 0, 800, 0, 800  (xmin, xmax, ymin, ymax)
coord. ref. : 
source(s)   : memory
name        : variavel
min value   :        0
max value   :        1

[[4]]
class       : SpatRaster
size        : 800, 800, 1  (nrow, ncol, nlyr)
resolution  : 1, 1  (x, y)
extent      : 0, 800, 0, 800  (xmin, xmax, ymin, ymax)
coord. ref. : 
source(s)   : memory
name        : variavel
min value   :        0
max value   :        1

Index

Finally, we get COI index. We use coiR::coir_index() for get that value. To avoid replot cropped and binarized images, we set plot = FALSE for both coiR::coir_crop() and coiR::coir_binarize() functions.

single_image |> 
  coiR::coir_crop(plot = FALSE) |> 
  coiR::coir_binarize(plot = FALSE) |> 
  coiR::coir_index()
[1] 0.5

As previously made, we can also do for multiple images, making a function in purrr::map_dbl() function.

purrr::map_dbl(images, 
               purrr::in_parallel(
                 
      ~coiR::coir_crop(data = .x,
                       plot = FALSE) |>
        coiR::coir_binarize(plot = FALSE) |> 
        coiR::coir_index()
             
             ),
      .progress = TRUE)
[1] 0.50 0.52 0.28 0.29

Finally, we make a data rame with those values, using purrr::imap_dfr(), to get image names.

df_index <- purrr::imap_dfr(
  images, 
  purrr::in_parallel(
    
    \(imagem, nome){
      
      index <- imagem |> 
        coiR::coir_crop(plot = FALSE) |>
        coiR::coir_binarize(plot = FALSE) |> 
        coiR::coir_index(round = 3)
      
      tibble::tibble(id = nome,
                     Index = index)
      
      }
    
    ),
  .progress = TRUE)

df_index
# A tibble: 4 × 2
     id Index
  <int> <dbl>
1     1 0.501
2     2 0.518
3     3 0.281
4     4 0.29 

Reference manual

It appears you don't have a PDF plugin for this browser. You can click here to download the reference manual.

install.packages("coiR")

0.1.0 by Edson Nilton de Moura Silva-Júnior, 6 hours ago


https://github.com/Edbbioeco/coiR


Report a bug at https://github.com/Edbbioeco/coiR/issues


Browse source code at https://github.com/cran/coiR


Authors: Edson Nilton de Moura Silva-Júnior [aut, cre]


Documentation:   PDF Manual  


MIT + file LICENSE license


Imports ggplot2, dplyr, terra, tidyterra, rlang

Suggests testthat, purrr


See at CRAN