πŸ”¬ Gray Leaf Spot Colony Segmentation β€” Demo Pipeline

Downloads License DOI

End-to-end analysis pipeline for gray leaf spot (Magnaporthe and related fungal) colony morphometry on 90 mm petri-dish images, powered by a lightweight SmallUNet trained with area-consistency loss (w=0.7).

β–Ά Try the live demo β€” upload images, run inference, see overlays & 16 growth charts in your browser.


Model

Weights: rotsl/grayleafspot-segmentation/best_area_w_0.7.pt

Property Value
Architecture SmallUNet (custom lightweight U-Net)
Parameters ~250 K
Base channels 16 β†’ 32 β†’ 64 β†’ 128 β†’ 256 (bottleneck)
Input 256 Γ— 256 RGB
Output 1-channel sigmoid mask
Training loss BCE + area-consistency loss (weight = 0.7)
Dish detection OpenCV HoughCircles on Gaussian-blurred grayscale
CPU compatible βœ… Pure PyTorch β€” no custom CUDA kernels

SmallUNet Architecture

Input (3 Γ— 256 Γ— 256)
  β”‚
  β”œβ”€ enc1: ConvBlock(3 β†’ 16)              ─── skip s1
  β”œβ”€ enc2: MaxPool2d β†’ ConvBlock(16 β†’ 32)  ─── skip s2
  β”œβ”€ enc3: MaxPool2d β†’ ConvBlock(32 β†’ 64)  ─── skip s3
  β”œβ”€ enc4: MaxPool2d β†’ ConvBlock(64 β†’ 128) ─── skip s4
  β”‚
  β”œβ”€ bottleneck: MaxPool2d β†’ ConvBlock(128 β†’ 256)
  β”‚
  β”œβ”€ up4: Upsample + cat(s4) β†’ ConvBlock(384 β†’ 128)
  β”œβ”€ up3: Upsample + cat(s3) β†’ ConvBlock(192 β†’ 64)
  β”œβ”€ up2: Upsample + cat(s2) β†’ ConvBlock(96 β†’ 32)
  β”œβ”€ up1: Upsample + cat(s1) β†’ ConvBlock(48 β†’ 16)
  β”‚
  └─ head: Conv2d(16 β†’ 1) β†’ Sigmoid

Each ConvBlock = Conv3Γ—3 (no bias) β†’ ReLU β†’ Conv3Γ—3 (no bias) β†’ ReLU. DownBlock = MaxPool2d(2) β†’ ConvBlock. UpBlock = Bilinear upsample(Γ—2, align_corners=False) β†’ cat([skip, x]) β†’ ConvBlock.

Area-Consistency Weights

The model repo contains variants trained with different area-consistency loss weights. Higher weights enforce stronger agreement between predicted mask area and ground-truth polygon area:

Weight file Loss weight Description
best_area_w_0.1.pt 0.1 Light area regularisation
best_area_w_0.3.pt 0.3 Moderate area regularisation
best_area_w_0.5.pt 0.5 Balanced BCE + area
best_area_w_0.7.pt 0.7 Strong area consistency (used by demo)
grayleafspot.pt β€” Main smp.Unet (ResNet-34) model (24.4M params)

Pipeline Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Gradio Space (rotsl/grayleafspot-segmentation-demo)             β”‚
β”‚                                                                  β”‚
β”‚  Upload images                                                   β”‚
β”‚  β”œβ”€ Fast mode: SmallUNet β†’ mask β†’ overlay (per image)            β”‚
β”‚  └─ Full pipeline (per image):                                   β”‚
β”‚       1. OpenCV HoughCircles β†’ dish detection β†’ px_to_mm         β”‚
β”‚       2. SmallUNet β†’ colony mask (threshold configurable)        β”‚
β”‚       3. Crack detection (adaptive thresholding + morphology)    β”‚
β”‚       4. Hyphae detection (Frangi + Meijering + hybrid skeleton) β”‚
β”‚       5. Morphometrics β€” all in mm/mmΒ² via per-image calibration β”‚
β”‚       6. 6 overlay panels per image                              β”‚
β”‚       7. 16 growth charts (β‰₯2 images)                            β”‚
β”‚       8. Export: analysis_full.csv / .json / .zip                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Visualisation Outputs

6 Overlay Panels Per Image

Panel Colour Shows
Raw + Dish Green circle, red contour Detected dish boundary + colony outline
Colony Mask White on black Binary segmentation mask
Colony Overlay Red 50% blend Colony area highlighted on raw image
Cracks Yellow Detected cracks inside colony (dilated for visibility)
Hyphae Cyan Hyphae skeleton (Frangi + Meijering hybrid filter)
All Combined Red + yellow + cyan Colony + cracks + hyphae together

16 Growth Charts (when β‰₯2 images)

All spatial metrics are in mm (or mmΒ²) via per-image px_to_mm calibration from dish detection, so images of different resolutions are correctly comparable.

Category Charts Units
Colony geometry Colony Area, Colony Diameter, Colony Perimeter mmΒ², mm, mm
Shape descriptors Eccentricity, Edge Roughness (P/Ο€d), Colony Centre Offset unitless, unitless, mm
Texture Colony Texture Entropy, Colony Texture Std Dev unitless, unitless
Cracks Crack Area, Crack Coverage, Number of Cracks mmΒ², %, count
Hyphae Hyphae Length β€” Frangi, Meijering, Hybrid mm, mm, mm
Growth rates Relative Growth Rate (RGR), Absolute Growth Rate ln mmΒ²/day, mmΒ²/day

Charts are only generated when β‰₯2 valid data points exist for that metric. All charts are included as PNGs in the download zip.


Usage via HF API (Programmatic Access)

Run the full pipeline remotely via the Gradio Client without installing anything locally.

Install

pip install gradio_client

Quick Start β€” Upload + Run Pipeline

from gradio_client import Client, handle_file

client = Client("rotsl/grayleafspot-segmentation-demo")

# Step 1: Upload images
result = client.predict(
    files=[
        handle_file("plate_d01.jpg"),
        handle_file("plate_d03.jpg"),
        handle_file("plate_d05.jpg"),
    ],
    api_name="/on_upload",
)

# Step 2: Run the full analysis pipeline
analysis = client.predict(
    en="GLS_Exp01",               # experiment name
    ed="2026-04-01",              # experiment start date
    un="YourName",                # user name
    pc=1,                         # plates count
    thresh=0.5,                   # mask confidence threshold
    full_pipeline=True,           # enable full morphometrics
    api_name="/on_run",
)

status_msg    = analysis[0]
overlays      = analysis[1]   # list of {image: filepath, caption: str}
charts        = analysis[2]   # list of {image: filepath, caption: str}
results_table = analysis[3]   # {"headers": [...], "data": [[...], ...]}
zip_path      = analysis[4]   # local path to downloaded analysis_full.zip

print(status_msg)
print(f"Overlays: {len(overlays)} panels")
print(f"Charts:   {len(charts)}")
print(f"Download: {zip_path}")

Export Metadata Only (no inference)

meta = client.predict(
    en="GLS_Exp01",
    ed="2026-04-01",
    un="YourName",
    pc=1,
    api_name="/on_export",
)
# meta[0] = status message
# meta[1] = metadata dataframe
# meta[2] = path to image_metadata.zip

Available API Endpoints

Endpoint Description Key Parameters
/on_upload Upload images β†’ gallery files: list of filepaths
/on_sel Select image in gallery ed: experiment date
/on_save Save per-image date/reminder nd: date, nr: reminder, ed: exp date
/on_export Export metadata CSV/JSON/ICS en, ed, un, pc
/on_run Run full pipeline (segmentation + morphometrics + 16 charts) en, ed, un, pc, thresh, full_pipeline

Batch Processing Script

"""Process a folder of petri dish images via the HF Space API."""
from pathlib import Path
from gradio_client import Client, handle_file

IMAGE_DIR = Path("./my_experiment")
EXPERIMENT = "GLS_Exp01"
START_DATE = "2026-04-01"

client = Client("rotsl/grayleafspot-segmentation-demo")

# Collect all images
images = sorted(
    p for p in IMAGE_DIR.rglob("*")
    if p.suffix.lower() in {".jpg", ".jpeg", ".png", ".tif", ".bmp", ".webp"}
)
print(f"Found {len(images)} images")

# Upload
client.predict(
    files=[handle_file(str(p)) for p in images],
    api_name="/on_upload",
)

# Run pipeline
status, overlays, charts, table, zip_path = client.predict(
    en=EXPERIMENT,
    ed=START_DATE,
    un="BatchUser",
    pc=1,
    thresh=0.5,
    full_pipeline=True,
    api_name="/on_run",
)

print(status)
print(f"Results zip: {zip_path}")

# Access results as a DataFrame
import pandas as pd
df = pd.DataFrame(table["data"], columns=table["headers"])
print(df[["image_path", "area_mm2", "diameter_mm", "crack_coverage_pct"]].to_string())

Output Columns

Metadata

Column Description
image_path Image filename
experiment_name Experiment identifier
experiment_date Start date (YYYY-MM-DD)
image_date Auto-detected capture date
day_code d01, d02, …
user_name Researcher
plates_count Number of plates

Calibration

Column Unit Description
dish_detected bool Whether dish was found
dish_radius_px px Dish radius in pixels
px_to_mm mm/px Per-image scale factor from dish detection
calibration_diameter_mm mm Should be β‰ˆ 90.0
calibration_error_pct % Target < 2%

Colony Morphometry

Column Unit Description
area_mm2 mmΒ² Colony area
diameter_mm mm Equivalent circular diameter
perimeter_mm mm Colony perimeter
eccentricity – 0 = circle, 1 = line
edge_roughness – Perimeter / equivalent circle perimeter
centre_delta_mm mm Colony centre to dish centre

Texture

Column Description
entropy Shannon entropy (local rank filter)
texture_std Pixel intensity standard deviation

Cracks

Column Unit Description
crack_px px Total crack pixels
crack_area_mm2 mmΒ² Total crack area
crack_coverage_pct % Crack / colony area Γ— 100
crack_count – Distinct crack count

Hyphae

Column Unit Description
hyph_frangi_mm mm Frangi vesselness skeleton length
hyph_meijering_mm mm Meijering neuriteness skeleton length
hyph_hybrid_mm mm Union of both

Time-Series

Column Unit Description
days_since_start days From first image
rgr_per_day day⁻¹ (ln Aβ‚‚ βˆ’ ln A₁) / Ξ”days
relative_growth_per_day mmΒ²/day (Aβ‚‚ βˆ’ A₁) / Ξ”days

R Studio Integration

library(readr)
library(dplyr)
library(ggplot2)

df <- read_csv("analysis_full.csv")

# Growth curve
df %>%
  filter(is.na(error) | error == "") %>%
  ggplot(aes(x = days_since_start, y = area_mm2, color = experiment_name)) +
  geom_line() + geom_point() +
  labs(x = "Days", y = "Colony Area (mmΒ²)", title = "Gray Leaf Spot Growth") +
  theme_minimal()

# Morphology summary
df %>%
  filter(is.na(error) | error == "") %>%
  group_by(experiment_name) %>%
  summarise(
    n = n(),
    mean_area = mean(area_mm2, na.rm = TRUE),
    mean_roughness = mean(edge_roughness, na.rm = TRUE),
    mean_crack_pct = mean(crack_coverage_pct, na.rm = TRUE),
    total_hyphae = sum(hyph_hybrid_mm, na.rm = TRUE)
  )

# RGR
df %>%
  filter(!is.na(rgr_per_day) & rgr_per_day != "") %>%
  mutate(rgr_per_day = as.numeric(rgr_per_day)) %>%
  ggplot(aes(x = days_since_start, y = rgr_per_day)) +
  geom_col(fill = "steelblue") +
  facet_wrap(~ experiment_name) +
  labs(x = "Days", y = "RGR (day⁻¹)") +
  theme_minimal()
library(jsonlite)
df <- fromJSON("analysis_full.json")

Technical Notes

Per-Image Pixel-to-mm Calibration

Each image gets its own px_to_mm conversion factor derived from dish detection. The pipeline detects the 90 mm petri dish via HoughCircles and computes:

px_to_mm = 90.0 / (2 Γ— dish_radius_px)

This means images of different resolutions (e.g. phone camera vs DSLR vs microscope) are correctly converted to physical mm units independently. If dish detection fails for an image, px_to_mm defaults to 1.0 and dish_detected is set to False.

Segmentation

  1. Resize full image to 256 Γ— 256 β†’ SmallUNet β†’ sigmoid probability map
  2. Threshold at user-configurable confidence level (default 0.5)
  3. Resize mask back to original resolution (nearest-neighbour)

Crack Detection

  • Local adaptive thresholding (Gaussian, block_size=51) inside colony mask
  • Filter by elongation: aspect ratio > 2.5 or eccentricity > 0.85
  • Interior erosion (disk radius 5) to remove edge artefacts

Hyphae Detection

  • Frangi filter: multi-scale vesselness (Οƒ = 1–4)
  • Meijering filter: neuriteness (Οƒ = 1–4)
  • Hybrid: union of both skeletonised responses
  • Analysis region extends 20 px beyond colony boundary

Troubleshooting

Issue Fix
Model download fails Check internet; for gated repos set HF_TOKEN
Dish not detected Full rim must be visible; avoid heavy shadows
Colony not detected Verify image has visible colony contrast against agar
px_to_mm = 1.0 Dish detection failed β€” check dish_detected column
Charts missing Need β‰₯2 images with valid data for that metric

Citation

@misc{rohan_r_2026,
    author       = { rohan r },
    title        = { grayleafspot-segmentation-demo (Revision d2b8555) },
    year         = 2026,
    url          = { https://huggingface.co/rotsl/grayleafspot-segmentation-demo },
    doi          = { 10.57967/hf/8569 },
    publisher    = { Hugging Face }
}

License

Apache License 2.0

Downloads last month
5
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support