Gray leaf spot (GLS) is a fungal disease of maize (Zea mays) caused by Cercospora zeae-maydis and Cercospora zeicola. In laboratory settings, fungal isolates are grown on petri dishes and photographed at successive time points to characterise colony expansion, morphology, and structural organisation. Manual measurement of these traits is time-consuming and operator-dependent.
grayleafspotr provides a fully automated pipeline for
quantitative phenotyping of GLS colonies from time-lapse plate
photographs. The package uses a bundled SmallUNet deep-learning
segmentation model to identify colonies in each image, then extracts
morphometric and texture features that describe colony growth, shape,
and internal organisation over time. The resulting tidy data frame can
be explored with included template ggplot2 visualisations
or passed to any downstream R analysis.
Python dependencies are managed automatically through
basilisk; no manual Python environment setup is
required.
Given a folder of plate photographs, grayleafspotr:
best_area_w_0.7.pt).grayleafspot_run — containing a data frame indexed by
filename and imaging day.ggplot2 objects ready for further
customisation.| Requirement | Detail |
|---|---|
| Image formats | JPEG, PNG, BMP, TIFF, WEBP |
| Naming convention | Encode the day with a d\d+ token:
*_d04_*.jpg for day 4 |
| Plate diameter | Standard 90 mm (adjustable via plate_diameter_mm) |
| Folder structure | One folder per experiment; one image per plate per time point |
Example filenames and the day values they produce:
20260210_P001_06-FEB_WT_PCBM_SUB_d04_TOP.jpg → day 4
20260212_P001_06-FEB_WT_PCBM_SUB_d06_TOP.jpg → day 6
20260216_P001_06-FEB_WT_PCBM_SUB_d10_TOP.jpg → day 10
Plate images (folder)
│
▼
grayleafspot_analyze() / grayleafspot_run()
│
├─ Dish geometry detection (classical Hough transform)
├─ SmallUNet segmentation (best_area_w_0.7.pt)
└─ Feature extraction (area, shape, texture, cracks, radial)
│
▼
grayleafspot_run S3 object
│
├─ $results per-image feature data frame
└─ $run run manifest (paths, timestamp, engine)
│
▼
Template ggplot2 plots / tidy data / custom analysis
Install from Bioconductor:
if (!requireNamespace("BiocManager", quietly = TRUE))
install.packages("BiocManager")
BiocManager::install("grayleafspotr")Python dependencies (NumPy, OpenCV, PyTorch, scikit-image, etc.) are
installed automatically by basilisk the first time the
analysis pipeline is invoked. This one-time setup may take a few
minutes; subsequent calls use the cached environment instantly.
The package ships three pre-computed example results so that all plotting and data-wrangling functions can be explored without running the full pipeline.
example_run$results[, c("filename", "day", "area_mm2", "diameter_mm",
"circularity", "crack_coverage_pct", "qc_status")]## # A tibble: 2 × 7
## filename day area_mm2 diameter_mm circularity crack_coverage_pct qc_status
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <chr>
## 1 demo_day1… 1 12 3.9 0.8 0 pass
## 2 demo_day2… 2 18 4.78 0.77 2.5 pass
The three plate photographs used to generate this example are also
included in the package and are accessible via
system.file():
image_dir <- system.file("extdata", "testdata", "06FEB", package = "grayleafspotr")
list.files(image_dir)## [1] "20260210_P001_06-FEB_WT_PCBM_SUB_d04_TOP.jpg"
## [2] "20260212_P001_06-FEB_WT_PCBM_SUB_d06_TOP.jpg"
## [3] "20260216_P001_06-FEB_WT_PCBM_SUB_d10_TOP.jpg"
Every plotting function accepts a grayleafspot_run
object and returns a ggplot2 object that can be customised
with standard ggplot2 calls.
Colony equivalent diameter (mm) plotted against imaging day.
Daily growth increment (mm/day) alongside a measure of colony edge irregularity.
Proportion of the colony mask classified as cracked tissue, alongside the total crack count — a proxy for structural stress.
Texture entropy and the centre-to-edge entropy gradient reflect internal colony heterogeneity.
Eccentricity (0 = circular, 1 = elongated) plotted against crack coverage to reveal coupling between morphology and structural remodelling.
Pearson correlations between all numeric features.
Convert the run object to a plain data frame for any downstream workflow:
## # A tibble: 2 × 34
## id filename day area_mm2 radius_mm diameter_mm perimeter_mm circularity
## <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 demo-1 demo_day… 1 12 1.95 3.9 8 0.8
## 2 demo-2 demo_day… 2 18 2.39 4.78 9.1 0.77
## # ℹ 26 more variables: eccentricity <dbl>, edge_roughness <dbl>,
## # contrast <dbl>, correlation <dbl>, energy <dbl>, homogeneity <dbl>,
## # entropy <dbl>, center_edge_delta <dbl>, density_index <dbl>, core <dbl>,
## # middle <dbl>, outer <dbl>, crack_count <dbl>, crack_length_mm <dbl>,
## # crack_coverage_pct <dbl>, proportional_crack_coverage_pct <dbl>,
## # radial_velocity_mm_per_day <dbl>, area_growth_rate_mm2_per_day <dbl>,
## # relative_growth_rate_per_day <dbl>, radial_acceleration <dbl>, …
Place all plate photographs for one experiment in a single directory.
The filename must contain a d\d+ token that encodes the
imaging day:
my_experiment/
├── isolate_A_d03_rep1.jpg
├── isolate_A_d05_rep1.jpg
├── isolate_A_d07_rep1.jpg
└── ...
grayleafspot_run() is the recommended entry point for
most users. It returns the raw JSON payload as a named list.
grayleafspot_analyze()grayleafspot_analyze() returns a
grayleafspot_run S3 object with direct access to the
template plots:
run <- grayleafspot_analyze(
input_dir = "my_experiment",
output_dir = "outputs",
run_name = "isolate_A"
)
plot_colony_expansion(run)Why are these chunks not evaluated? They require actual plate images on disk and invoke the Python pipeline, which cannot run during package build. The executable examples in sections 3–5 above use the bundled pre-computed results and demonstrate the same data structures and plotting functions.
Normal users do not need to configure Python. Developers maintaining
a local virtual environment (e.g. rvenv_arm_311) can bypass
basilisk by setting:
# ~/.Rprofile — developer use only
Sys.setenv(GRAYLEAFSPOTR_PYTHON = "/path/to/rvenv_arm_311/bin/python")When GRAYLEAFSPOTR_PYTHON is set, the pipeline uses that
interpreter directly instead of the basilisk-managed environment.
## R version 4.6.0 (2026-04-24)
## Platform: x86_64-pc-linux-gnu
## Running under: Ubuntu 24.04.4 LTS
##
## Matrix products: default
## BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
## LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so; LAPACK version 3.12.0
##
## locale:
## [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
## [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
## [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
## [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
## [9] LC_ADDRESS=C LC_TELEPHONE=C
## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
##
## time zone: Etc/UTC
## tzcode source: system (glibc)
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] grayleafspotr_0.99.2 rmarkdown_2.31
##
## loaded via a namespace (and not attached):
## [1] generics_0.1.4 sass_0.4.10 utf8_1.2.6 lattice_0.22-9
## [5] hms_1.1.4 digest_0.6.39 magrittr_2.0.5 evaluate_1.0.5
## [9] grid_4.6.0 RColorBrewer_1.1-3 fastmap_1.2.0 jsonlite_2.0.0
## [13] Matrix_1.7-5 scales_1.4.0 jquerylib_0.1.4 cli_3.6.6
## [17] rlang_1.2.0 crayon_1.5.3 bit64_4.8.2 withr_3.0.2
## [21] cachem_1.1.0 yaml_2.3.12 otel_0.2.0 tools_4.6.0
## [25] dir.expiry_1.21.0 parallel_4.6.0 tzdb_0.5.0 dplyr_1.2.1
## [29] ggplot2_4.0.3 filelock_1.0.3 basilisk_1.25.0 reticulate_1.46.0
## [33] buildtools_1.0.0 vctrs_0.7.3 R6_2.6.1 png_0.1-9
## [37] lifecycle_1.0.5 bit_4.6.0 vroom_1.7.1 pkgconfig_2.0.3
## [41] pillar_1.11.1 bslib_0.11.0 gtable_0.3.6 glue_1.8.1
## [45] Rcpp_1.1.1-1.1 xfun_0.58 tibble_3.3.1 tidyselect_1.2.1
## [49] sys_3.4.3 knitr_1.51 farver_2.1.2 htmltools_0.5.9
## [53] labeling_0.4.3 maketools_1.3.2 readr_2.2.0 compiler_4.6.0
## [57] S7_0.2.2