Skip to contents

About this tutorial

This tutorial describes how to use the aforoR package to process images, extract otolith contours, and calculate shape descriptors including Elliptic Fourier Descriptors (EFDs), Wavelets, and morphometrics variables.

1. Image preparation and Standardizing

Otolith images should be placed with the sulcus acusticus facing upward, the rostrum to the right, and the dorsal margin at the top (Tuset et al., 2008; Lombarte and Tuset, 2015), according to its natural position.

During image acquisition, modeling clay can be helpful for otoliths with pronounced convexity.

The package determine automatically the initial contour point, which represents the maximum distance from the otolith centroid to this point locating to the right. The rostrum is usually the reference point; however, in otoliths where the rostrum is not clearly defined, it is preferable to select another homologous point, as illustrated in the following example.

Automatic contour detection is highly sensitive to illumination conditions. To minimize variability and ensure reliable results, we recommend acquiring all images under consistent lighting and background conditions (preferably uniform black color).

2. Setting up images

The package works by processing all supported image files (.jpg, .jpeg, .png, .tif, .tiff) within a specified folder. While we use sample images for this tutorial, full datasets of Aphanopus carbo and A. intermedius (as used in other vignettes) can be downloaded from Zenodo:

[!NOTE] Zenodo Dataset: Aphanopus Otolith Images

2.1 Folder Organization Best Practices

To ensure mathematically and biologically sound analyses, it is highly recommended to organize your image dataset structurally before running the package. Place images of different species, populations, or stocks into separate, dedicated subfolders.

For example:

Project_Images/
├── Aphanopus_carbo_Azores/
│   ├── otolith1.jpg
│   └── otolith2.jpg
└── Aphanopus_intermedius_Madeira/
    ├── otolith3.jpg
    └── otolith4.jpg

This structure is particularly critical if you intend to use the Generalized Procrustes Analysis (GPA) alignment (procrustes = TRUE). GPA calculates a global “consensus shape” for all specimens in the folder. If you mix highly divergent species in the same folder, the algorithm will attempt to find a non-biological average between them, distorting your morphometric signals. By processing species/stocks homogeneously in their respective folders, Procrustes operates optimally.

Furthermore, keeping datasets physically segregated simplifies data management. Although aforoR will generate separate output tables (.csv) for each folder, you can easily bind them together in R downstream, automatically assigning species or stock labels based on the directory names.

2.2 Setting up the working directory

First, let’s look at where the example image is stored:

library(aforoR)

# Locate the example image
image_path <- system.file("extdata", "otolith.jpg", package = "aforoR")

# Fallback if the package is not installed but we are running vignettes locally
if (image_path == "") {
  image_path <- file.path("../inst/extdata", "otolith.jpg")
}

# Create a temporary directory for processing
work_dir <- file.path(tempdir(), "aforo_tutorial")
if (dir.exists(work_dir)) unlink(work_dir, recursive = TRUE)
dir.create(work_dir)

# Copy the example image to the working directory
file.copy(image_path, file.path(work_dir, "otolith.jpg"))
#> [1] TRUE

cat("Working directory:", work_dir, "\n")
#> Working directory: /tmp/RtmpfXBinh/aforo_tutorial
list.files(work_dir)
#> [1] "otolith.jpg"

3. Processing images

The main function process_images handles the entire workflow: 1. Preprocessing: Grayscale conversion, filtering, and binarization. 2. Contour Extraction: Identifies the otolith boundaires. 3. Feature Calculation: Computes distances, wavelets, and EFDs. 4. Morphometrics: Calculates linear measurements and relative indices.

You can specify the scale of your images using the pixels_per_mm argument to get measurements in millimeters.

# Run the processing pipeline
# We set pixels_per_mm = 100 as an example scale (100 pixels = 1 mm)
process_images(
  folder = work_dir,
  threshold = NULL, # Automatic thresholding (Otsu)
  wavelets = TRUE, # Calculate wavelets
  ef = TRUE, # Calculate Elliptic Fourier Descriptors
  pixels_per_mm = 100 # define scale
)
#> 
#> Phase 1: Extracting contours...
#>   |                                                |                                        |   0%  |                                                |========================================| 100%
#> 
#> Phase 2: Calculating descriptors and saving results...
#>   |                                                |                                        |   0%  |                                                |========================================| 100%

4. Examining Results

The function creates two subdirectories: Polar (Polar coordinates) and Cartesian (Perimeter coordinates).

4.1 Morphometric Measurements

A new file MorphometricsEN.csv is created containing geometric measurements.

morpho_file <- file.path(work_dir, "Polar", "MorphometricsEN.csv")
if (file.exists(morpho_file)) {
  morpho_data <- read.table(morpho_file, header = TRUE, sep = ";", dec = ".")
  knitr::kable(morpho_data, caption = "Morphometric Indices")
}
Morphometric Indices
Image Area Perimeter Length Width Feret_Max Feret_Min PCA_Angle Units
otolith.jpg 9.7628 12.89881 5.025411 2.72705 5.031948 2.726763 3.082376 mm

The measurements include:

  • Area: Area of the otolith (mm2mm^2).
  • Perimeter: Perimeter length (mmmm).
  • Length / Width: Major and minor axis dimensions of the PCA-aligned bounding box (mmmm).
  • Feret_Max: Maximum Feret diameter (longest distance between any two boundary points; recommended robust measure of length) (mmmm).
  • Feret_Min: Minimum Feret diameter (minimum distance between parallel tangent lines; recommended robust measure of width) (mmmm).
  • PCA_Angle: Orientation angle of the major axis of inertia in degrees (normalized to [0, 180)).

4.2 Wavelet Coefficients

The wavelet analysis results are saved in multiple CSV files corresponding to different scales.

wavelet_file <- file.path(work_dir, "Polar", "Wavelet_5EN.csv")
if (file.exists(wavelet_file)) {
  wave5 <- read.table(wavelet_file, header = FALSE, sep = ";", dec = ".")

  # Display first few columns
  knitr::kable(wave5[, 1:10], caption = "Wavelet Scale 5 (First 10 columns)")
}
Wavelet Scale 5 (First 10 columns)
V1 V2 V3 V4 V5 V6 V7 V8 V9 V10
otolith.jpg 0.0597147 0.0592524 0.0584312 0.0572526 0.055724 0.0538587 0.0516752 0.0491968 0.0464515

4.3 Visualizations

The package automatically generates diagnostic images to verify the contour extraction and analysis zones.

Zonal Analysis

This image shows the original binary otolith with the centroid (black dot), the starting point of the contour (red dot), and the angular sectors used for analysis (colored lines).

Zonal Analysis showing contour and sectors
Zonal Analysis showing contour and sectors

Wavelet Transform

This plot visualizes the wavelet coefficients at a specific scale (default: scale 5). It highlights how different parts of the contour contribute to the shape complexity.

Wavelet Transform (Scale 5)
Wavelet Transform (Scale 5)

(Images are saved in the Polar folder).