Skip to main content

Intelligent Bark Beetle Identifier (IBBI)

PyPI version Tests License: MIT Documentation Models on Hugging Face Benchmark Open In Colab

IBBI is a Python package that provides a simple, unified interface for detecting and identifying bark and ambrosia beetles (Curculionidae: Scolytinae and Platypodinae) in images with trained, benchmarked computer vision models.

It finds every specimen in an image, names it at the deepest taxonomic level it can trust (species, genus, tribe or subfamily) and says when it does not recognise a beetle, instead of forcing a species name. It also gives access to the Bark and Ambrosia Beetle Detection Benchmark and scores any model on it with the benchmark's own evaluator.

The package is designed to support entomological research and plant-health diagnostics by automating the laborious task of beetle identification, enabling high-throughput analysis for ecological studies, pest surveillance, port interceptions and biodiversity monitoring.

import ibbi

pipe = ibbi.create_pipeline()                 # arthropod detector + hierarchical classifier
result = pipe.predict("trap_sample.jpg")
for box, rec in zip(result["boxes"], result["classifications"]):
    print(box, rec["reported"])               # e.g. "Xyleborus volvulus" or "Euwallacea sp. (species undetermined)"

Motivation

The ability to accurately detect and identify bark and ambrosia beetles is critical for forest health and pest management: quarantine, eradication and interception decisions are made at the level of species. Traditional methods face significant challenges:

  • They are slow and time-consuming. Trap samples and interception lots can contain hundreds of specimens.
  • They require highly specialised expertise. Many congeners are near-identical, and the taxonomists who can separate them are few.
  • They create a bottleneck for large-scale research. Surveillance programmes generate far more material than can be identified by hand.

Automated identification helps only if it is honest about its limits. A model that always answers with one of the species it was trained on will confidently misname every species it has never seen, and in a regulatory setting a confident wrong name is worse than no name. IBBI therefore pairs detectors with a hierarchical classifier that abstains: it reports "Euwallacea sp." when it is sure of the genus but not the species, and "unrecognised" when the specimen does not look like any beetle it knows. Every model is benchmarked on species it has never seen, so users can see where the models can and cannot be trusted.

What's new in v0.3

  • New model set: a universal arthropod detector, hierarchical classifiers with per-level abstention (DINOv3 and BioCLIP 2), six retrained species detectors and four zero-shot detectors (Grounding DINO, OWLv2, YOLO-World, SAM 3).
  • A two-stage identification pipeline (ibbi.create_pipeline()).
  • One dataset: the Bark and Ambrosia Beetle Detection Benchmark v2.0.1, with crowd-aware evaluation through the benchmark's reference evaluator.
  • Every model benchmarked, licences stated per set of weights. See the changelog.

Key Features

  • Model Access:
    Access every model with a single function call, ibbi.create_model(). The following types of models are available:

    • Two-Stage Identification (recommended): ibbi.create_pipeline() chains the arthropod detector and a hierarchical classifier. The detector finds every specimen, each one is cropped, and the classifier names it at subfamily, tribe, genus and species, with a calibrated probability and a known / unsure decision per level. This is the most accurate way to identify the 65 trained species and the only one that can say "I don't know" for the rest.

    • Arthropod Detection: Detect any arthropod in an image, whatever its species. Two models trained on the same 307,421 images from 14 sources (lab photographs and scans, light traps, sticky cards and pitfall trays, camera traps, field photographs): a fast YOLO11x (the default) and a larger, more accurate Co-DINO with an EVA-02-L backbone. They locate specimens but do not name them.

    • Hierarchical Classification with Abstention: Name a cropped specimen at four taxonomic levels at once. The classifiers (fine-tuned DINOv3 ViT-L and BioCLIP 2 ViT-L) use a tree-factorised head, so the probabilities of the levels are always consistent, a temperature per level for calibration, and a novelty score per level to decide whether the specimen belongs to a taxon they know. They report the deepest level they trust.

    • One-Step Species Detection: Locate and name beetles of the 65 trainable species of the benchmark in a single step. Six architectures (YOLOv8x, YOLOv9e, YOLOv10x, YOLO11x, YOLO12x, RT-DETR-X), trained on the benchmark's train split. Fast and simple, but they always answer with one of their 65 species.

    • Zero-Shot Detection: Detect objects from a text prompt (e.g. "beetle", "a photo of an insect") without any training on beetle data: Grounding DINO, OWLv2, YOLO-World and SAM 3. Default prompts, image tiling and operating confidences were chosen for arthropods on a held-out validation sample.

    • Feature Extraction: Extract embeddings from the fine-tuned classifier backbones (extract_features) for clustering, retrieval or similarity analysis.

  • Benchmark Data:
    ibbi.get_dataset() loads any split of the Bark and Ambrosia Beetle Detection Benchmark v2.0.1 (14,491 images, 175 species) at a pinned revision, downloading only the split you ask for.

  • Model Evaluation:
    ibbi.Evaluator() scores any model on the benchmark. Three families of metrics are available:

    • Benchmark Metrics: evaluator.benchmark() runs the model on the test splits and scores it with the benchmark's own crowd-aware reference evaluator (vendored unchanged), so results are directly comparable to published numbers.

      • Species-level models (species detectors, pipelines): the COCO suite (AP over IoU 0.50:0.95, AP50, AR@100), a decomposition into detection recall (share of specimens found), species accuracy given detection (share of found specimens correctly named), species recall (found and correctly named) and genus accuracy given detection, class-agnostic recall on unseen species, calibration and a novelty AUROC.
      • Class-agnostic detectors (arthropod and zero-shot detectors): class-agnostic AP / AR, plus recall, precision and false alarms per image at the model's operating confidence.
      • Crowd regions (real specimens that are annotated but not scored) are ignored, as the benchmark requires.
    • Hierarchical Classification Metrics: evaluator.hierarchical_classification() classifies every ground-truth specimen crop and reports, per taxonomic level: accuracy and expected calibration error on known taxa; novelty AUROC and FPR at 95% TPR (how well the novelty score separates taxa the classifier knows from taxa it does not); the reported depth against the ideal depth and the over-commit rate (naming a taxon below the deepest level that could be right, e.g. naming a species for a beetle whose genus was never seen); and the same broken down by taxonomic distance band of the unseen species (near_genus, mid_tribe, far_tribe).

    • Embedding & Clustering Metrics: evaluator.embeddings() extracts one embedding per specimen, reduces it with Uniform Manifold Approximation and Projection (UMAP) and clusters it with HDBSCAN, then calculates:

      • Intrinsic metrics of cluster quality: Silhouette Score, Davies-Bouldin Index and Calinski-Harabasz Index.
      • Extrinsic metrics against the true species labels: Adjusted Rand Index (ARI), Normalized Mutual Information (NMI) and Cluster Purity.
      • Mantel correlation between embedding distances of species and their taxonomic distance (0 same species, 1 same genus, 2 same tribe, 3 same subfamily, 4 otherwise): does the embedding space reflect the classification?
  • Model Explainability:
    Gain insight into why a model makes a prediction with integrated explainability methods. The ibbi.Explainer() wrapper works with every model:

    • SHapley Additive exPlanations (SHAP): a game-theory-based approach that attributes a prediction to the parts of an image. explainer.with_shap() uses SHAP's PartitionExplainer with a blurring masker and a background set sampled from the benchmark (ibbi.get_shap_background_dataset()).
    • Local Interpretable Model-agnostic Explanations (LIME): explains one prediction by fitting an interpretable model on perturbed superpixels. explainer.with_lime() is a quicker, more intuitive view of which regions of a single image drove the decision.

Table of Contents


Workflow: How the Models Were Built

The trained models in ibbi are the result of a data collection, annotation, training and benchmarking pipeline by the Forest Entomology Lab at the University of Florida.

flowchart TB
    subgraph data["1. Data"]
        G["Bark and Ambrosia Gallery<br/>+ iNaturalist photographs"] --> A["Boxes: zero-shot proposals,<br/>human-verified"]
        A --> T["Species labels by<br/>expert taxonomists"]
        T --> B[("Benchmark v2.0.1<br/>14,491 images, 175 species<br/>specimen-disjoint splits")]
        C[("Arthropod detection corpus<br/>307,421 images, 14 sources")]
    end
    subgraph train["2. Training"]
        C --> D["Arthropod detector<br/>YOLO11x, 1 class"]
        B -->|"train split, 65 species"| S["Species detectors<br/>6 architectures x 3 seeds"]
        B -->|"train crops, 65 species"| H["Hierarchical classifiers<br/>DINOv3-L, BioCLIP 2-L"]
        C -->|"non-beetle crops<br/>(outlier exposure)"| H
        Z["Released foundation models"] --> ZS["Zero-shot detectors<br/>prompts and tiling chosen"]
    end
    subgraph sel["3. Selection (validation data only)"]
        D --> D2["epoch with best recall<br/>at 0.2 false alarms / image"]
        S --> S2["best seed per architecture"]
        H --> H2["calibration + novelty<br/>thresholds per level"]
        ZS --> Z2["operating confidence"]
    end
    D2 & S2 & H2 & Z2 --> E["4. Benchmark: iid_test, inat_test, semantic_ood"]
    E --> HF["5. Hugging Face Hub: IBBI-bio<br/>weights + model cards"]
  1. Data Collection and Curation: Images come mostly from the Bark and Ambrosia Gallery (lab photographs of identified specimens) plus iNaturalist field photographs. A zero-shot detector proposes boxes, which are verified by humans; species labels come from expert taxonomists. The result is the Bark and Ambrosia Beetle Detection Benchmark v2.0.1, whose splits are disjoint by specimen, and the separate IBBI arthropod detection corpus of 14 public and in-house datasets.

  2. Model-Specific Training:

    • Arthropod detector: trained on the arthropod detection corpus (239,477 training images, square-root balancing over sources) to find any arthropod in any setting.
    • Species detectors: trained in a supervised manner on the benchmark's train split (6,096 images, 53,040 specimens, 65 species) from the Ultralytics COCO checkpoints; three seeds per architecture.
    • Hierarchical classifiers: trained on specimen crops of the 65 species with a hierarchical loss, a class-balanced sampler and non-beetle arthropod crops as outlier examples (outlier exposure). The DINOv3 model also uses synthetic outliers and a hierarchical contrastive loss.

    Note - No additional training is performed for the zero-shot detectors: they use the weights released by their authors with prompts and tiling chosen for arthropods.

  3. Selection on Validation Data: every choice (epoch, seed, configuration, calibration temperatures, novelty thresholds, prompts, operating confidences) was made on validation data, never on the benchmark test splits (the one exception, the species detectors' seed selection, is disclosed in docs/benchmark.md). No real unknown beetles were used to train the classifiers or to set any threshold.

  4. Evaluation: every model was run on the benchmark's three test splits: held-out specimens of the trained species (iid_test), field photographs (inat_test) and 110 species never seen in training (semantic_ood). A summary of the results can be viewed with ibbi.list_models() or in the model summary table; the full results are in docs/benchmark.md. Evaluation can be re-run with ibbi.Evaluator().

  5. Deployment: the trained weights are hosted in the IBBI-bio Hugging Face Hub community, each with a model card (training details, benchmark numbers, licence), and are downloaded on first use.


Package API and Usage

The ibbi package is designed to be simple and intuitive. The diagram summarises the main functions, classes and methods with their inputs and outputs.

flowchart LR
    IMG[/"image: path, URL,<br/>PIL image or numpy array"/]
    subgraph models["Models"]
        CM["ibbi.create_model(name)"] --> M["model"]
        CP["ibbi.create_pipeline()"] --> P["pipeline"]
    end
    IMG --> M & P
    M -->|".predict()"| O1["boxes, scores, labels<br/>or a hierarchical record"]
    M -->|".predict_proba()"| O2["class scores [N, classes]"]
    M -->|".extract_features()"| O3["embedding [1, D]"]
    P -->|".predict()"| O4["boxes + hierarchical record<br/>per specimen"]
    subgraph data["Data"]
        GD["ibbi.get_dataset(split)"] --> DS["BenchmarkDataset"]
    end
    subgraph eval["Evaluation"]
        EV["ibbi.Evaluator(model)"]
        EV -->|".benchmark()"| R1["reference-evaluator metrics"]
        EV -->|".hierarchical_classification()"| R2["per-level accuracy, novelty, depth"]
        EV -->|".embeddings(dataset)"| R3["clustering + Mantel metrics"]
    end
    M & P --> EV
    DS --> EV
    subgraph xai["Explainability"]
        EX["ibbi.Explainer(model)"]
        EX -->|".with_lime(image)"| L["LIME explanation"] --> PL["ibbi.plot_lime_explanation()"]
        EX -->|".with_shap(images, background)"| SV["SHAP values"] --> PS["ibbi.plot_shap_explanation()"]
    end
    M --> EX
Function / class Input Output
ibbi.list_models(as_df=False) – table of every model with its benchmark numbers
ibbi.create_model(name, **kwargs) model name or alias (device=, operating_point=, prompts=, ...) a model
ibbi.create_pipeline(detector, classifier, det_conf, operating_point) model names (defaults: arthropod detector + DINOv3 classifier) an IdentificationPipeline
model.predict(image or list) image(s) detectors: {"boxes", "scores", "labels"}; classifiers: hierarchical record(s)
model.predict_proba(images) list of images [N, n_classes] scores
model.extract_features(image) image embedding tensor
ibbi.get_dataset(split) "train", "iid_test", "inat_test", "semantic_ood" a BenchmarkDataset
ibbi.Evaluator(model) a model or pipeline .benchmark(), .hierarchical_classification(), .embeddings(dataset)
ibbi.Explainer(model) a model .with_lime(image), .with_shap(images, background)
ibbi.get_taxonomy() – the 175 species with subfamily, tribe, genus and benchmark role

Installation

This package requires PyTorch. For compatibility with your hardware (e.g. a CUDA-enabled GPU), install PyTorch before installing ibbi.

1. Install PyTorch

Follow the official instructions at pytorch.org to install the correct version for your system.

2. Install IBBI

Once PyTorch is installed, install the package from PyPI:

pip install ibbi

Or install the latest development version directly from GitHub:

pip install git+https://github.com/ChristopherMarais/IBBI.git

IBBI needs Python ≥ 3.11. A full walk-through with conda and pixi is in the usage guide.

Notes

  • IBBI requires ultralytics>=8.3.139,<8.4. Ultralytics 8.4 changes the predictions of the YOLOv10 and RT-DETR checkpoints, which were trained and benchmarked with 8.3; the package warns if 8.4 is installed.
  • SAM 3 is gated on the Hugging Face Hub: accept its licence at https://huggingface.co/facebook/sam3 and run hf auth login before using sam3_zero_shot_detector.
  • YOLO-World downloads its text encoder (CLIP) through Ultralytics on first use.
  • The Co-DINO detector is large (347 M parameters, images up to 2048 × 1280 px): use a GPU. ibbi runs it on plain PyTorch, without MMDetection or MMCV.
  • Weights and dataset files are cached in ~/.cache/ibbi (override with IBBI_CACHE_DIR; ibbi.clean_cache() deletes it). Set IBBI_MODELS_DIR to a folder with one sub-folder per model repository to load weights offline.

Hardware Requirements

  • Speed: on a CUDA GPU the pipeline decodes, letterboxes and crops on the GPU and classifies the crops of several images together: about 33 images per second on an RTX PRO 6000 (12.6-megapixel photographs), 4.3× the reference path. Pass lists of images to predict to benefit; fast=False gives the reference path of the published benchmark.

  • Disk Space: about 0.1 GB per detector, 1.2 GB per classifier and 1–3.5 GB per zero-shot model. The full benchmark needs about 27 GB; ibbi.get_dataset() downloads only the split you ask for (iid_test and inat_test are small, semantic_ood is the largest).

  • CPU & RAM: inference on a CPU works but is slow, especially for the ViT-L classifiers and the zero-shot models, which tile large images. 16 GB of RAM is enough for inference; evaluation streams images from disk.

  • GPU (Recommended): a CUDA-enabled GPU with at least 8 GB of memory (e.g. NVIDIA T4, RTX 3060 or better). For reference, a full benchmark run on one RTX PRO 6000 takes about 15–20 minutes for a species detector or a classifier, about 20 minutes for a pipeline and 1–4 hours for a zero-shot detector.


Quick Start

Using ibbi is straightforward. Load a model and immediately use it for inference.

import ibbi

# --- List Available Models ---
ibbi.list_models()

# --- Two-stage identification (recommended) ---
pipe = ibbi.create_pipeline()
res = pipe.predict("path/to/your/image.jpg")
res["labels"]                  # reported identification per specimen

# --- One-step species detection ---
detector = ibbi.create_model("species_detector")
results = detector.predict("path/to/your/image.jpg")

# --- Zero-shot detection ---
zero_shot = ibbi.create_model("zero_shot_detector")
results = zero_shot.predict("path/to/your/image.jpg", text_prompt="beetle")

For more detailed demonstrations, see the example notebooks in the notebooks/ folder: quick start, getting started, inference, evaluation, explainability and scoring your own predictions.


Available Models

To see the available models and their benchmark numbers directly from Python, run:

ibbi.list_models()               # prints the table
df = ibbi.list_models(as_df=True)

Use any name below with ibbi.create_model(model_name).

Model Summary Table

The most detailed version of the table, with the headline benchmark metrics, is ibbi_model_summary.csv; every model is described in docs/models.md.

Model Name Task Architecture Parameters Input Weights Paper Licence of the weights
yolov8x_species_detector Species detection (65 species) YOLOv8x 68.2 M 640 px ibbi_yolov8x_species_detector arXiv AGPL-3.0
yolov9e_species_detector Species detection (65 species) YOLOv9e 58.2 M 640 px ibbi_yolov9e_species_detector arXiv AGPL-3.0
yolov10x_species_detector Species detection (65 species) YOLOv10x 31.8 M 640 px ibbi_yolov10x_species_detector arXiv AGPL-3.0
yolo11x_species_detector Species detection (65 species) YOLO11x 56.9 M 640 px ibbi_yolo11x_species_detector arXiv AGPL-3.0
yolo12x_species_detector Species detection (65 species) YOLO12x 59.2 M 640 px ibbi_yolo12x_species_detector arXiv AGPL-3.0
rtdetrx_species_detector Species detection (65 species) RT-DETR-X 67.4 M 640 px ibbi_rtdetrx_species_detector arXiv AGPL-3.0
yolo11x_arthropod_detector Arthropod detection (single class) YOLO11x 56.9 M 1024 px ibbi_yolo11x_arthropod_detector arXiv AGPL-3.0
codino_arthropod_detector Arthropod detection (single class) Co-DINO, EVA-02 ViT-L/16 347 M ≤ 2048 × 1280 px ibbi_codino_arthropod_detector arXiv CC BY-NC 4.0
dinov3_hierarchical_classifier Hierarchical classification with abstention, embeddings DINOv3 ViT-L/16 303 M 336 px ibbi_dinov3l_hierarchical_classifier arXiv DINOv3 License
bioclip2_hierarchical_classifier Hierarchical classification with abstention, embeddings BioCLIP 2 ViT-L/14 304 M 224 px ibbi_bioclip2_hierarchical_classifier arXiv MIT
grounding_dino_zero_shot_detector Zero-shot detection (text prompts) Grounding DINO-B 232 M 1024 px tiles + whole image IDEA-Research/grounding-dino-base arXiv Apache-2.0 (upstream)
owlv2_zero_shot_detector Zero-shot detection (text prompts) OWLv2-L 438 M 1024 px tiles + whole image google/owlv2-large-patch14-ensemble arXiv Apache-2.0 (upstream)
yoloworld_zero_shot_detector Zero-shot detection (text prompts) YOLO-World v2-X 224 M 1024 px tiles + whole image yolov8x-worldv2 arXiv AGPL-3.0 (upstream)
sam3_zero_shot_detector Zero-shot detection (text prompts) SAM 3 840 M 1024 px tiles + whole image facebook/sam3 (gated) arXiv SAM License (upstream)

Aliases: arthropod_detector / beetle_detector → yolo11x_arthropod_detector; species_detector → yolo12x_species_detector (best validation fitness of the six); hierarchical_classifier / species_classifier / feature_extractor → dinov3_hierarchical_classifier; zero_shot_detector → grounding_dino_zero_shot_detector (best zero-shot model on the arthropod-corpus validation sample).

Which model should I use?

Goal Use
Identify beetles in images that may contain species outside the 65 ibbi.create_pipeline()
Identify specimens that are already cropped hierarchical_classifier
Count or locate specimens, whatever their species arthropod_detector
The highest detection recall across imaging settings (field photos, traps, unseen species), with a GPU codino_arthropod_detector, alone or in ibbi.create_pipeline(detector="codino_arthropod_detector")
The fastest one-step detection when all specimens belong to the 65 species a species detector
Detect something other than arthropods, or with your own wording a zero-shot detector with text_prompt=
Embeddings for clustering or retrieval feature_extractor

The v0.2 models (*_bb_detect_model, *_bb_multi_class_detect_model, grounding_dino_detect_model, yoloworldv2_bb_detect_model and the *_features_model extractors) were removed in v0.3; their repositories remain on the Hub for older versions of the package.


Benchmark Results

Every model was run through ibbi.Evaluator on the benchmark v2.0.1 with its default settings (nothing tuned on the benchmark test splits). The full tables, protocol and caveats are in docs/benchmark.md, and the scripts are in benchmarks/.

  • ID = in-distribution (iid_test: held-out specimens of the 65 trained species).
  • Unseen species = semantic_ood: 110 species never seen in training.
Model Headline (benchmark v2.0.1)
yolov8x_species_detector iid AP 0.497, detection recall 0.929, species accuracy given detection 0.586; unseen-species detection recall (AR@100) 0.732
yolov9e_species_detector iid AP 0.519, detection recall 0.929, species accuracy given detection 0.613; unseen-species detection recall (AR@100) 0.735
yolov10x_species_detector iid AP 0.561, detection recall 0.932, species accuracy given detection 0.604; unseen-species detection recall (AR@100) 0.724
yolo11x_species_detector iid AP 0.485, detection recall 0.929, species accuracy given detection 0.575; unseen-species detection recall (AR@100) 0.727
yolo12x_species_detector iid AP 0.490, detection recall 0.931, species accuracy given detection 0.577; unseen-species detection recall (AR@100) 0.723
rtdetrx_species_detector iid AP 0.535, detection recall 0.937, species accuracy given detection 0.647; unseen-species detection recall (AR@100) 0.739
pipeline: arthropod detector + DINOv3 classifier iid AP 0.718, detection recall 0.932, species accuracy given detection 0.858; unseen-species detection recall (AR@100) 0.762
pipeline: arthropod detector + BioCLIP 2 classifier iid AP 0.691, detection recall 0.932, species accuracy given detection 0.828; unseen-species detection recall (AR@100) 0.762
pipeline: Co-DINO detector + DINOv3 classifier iid AP 0.729, detection recall 0.934, species accuracy given detection 0.850; unseen-species detection recall (AR@100) 0.778
yolo11x_arthropod_detector class-agnostic AP: iid 0.744, iNat 0.932, unseen species 0.632
codino_arthropod_detector class-agnostic AP: iid 0.669, iNat 0.952, unseen species 0.683
grounding_dino_zero_shot_detector class-agnostic AP: iid 0.294, iNat 0.595, unseen species 0.243
owlv2_zero_shot_detector class-agnostic AP: iid 0.365, iNat 0.531, unseen species 0.207
yoloworld_zero_shot_detector class-agnostic AP: iid 0.114, iNat 0.482, unseen species 0.252
sam3_zero_shot_detector class-agnostic AP: iid 0.218, iNat 0.362, unseen species 0.202
dinov3_hierarchical_classifier iid species / genus accuracy 0.835 / 0.891; unseen species named at an impossible depth 0.497; genus novelty AUROC 0.561
bioclip2_hierarchical_classifier iid species / genus accuracy 0.814 / 0.871; unseen species named at an impossible depth 0.471; genus novelty AUROC 0.625

Advanced Usage

For more detailed examples, see the notebooks in the notebooks/ folder. The documentation site at gcmarais.com/IBBI has the full usage guide, the model descriptions and the API reference.

Inference

Use inference to locate and identify bark and ambrosia beetles in an image.

# --- Create models ---
detector = ibbi.create_model("arthropod_detector")
species_detector = ibbi.create_model("species_detector")
zero_shot = ibbi.create_model("zero_shot_detector")
pipe = ibbi.create_pipeline(det_conf=detector.operating_conf)

# --- Perform inference ---
boxes = detector.predict("path/to/your/image.jpg", conf=detector.operating_conf)
species = species_detector.predict("path/to/your/image.jpg")
prompted = zero_shot.predict("path/to/your/image.jpg", text_prompt="beetle . insect")
identified = pipe.predict("path/to/your/image.jpg")

Create a model with ibbi.create_model(), using a name from ibbi.list_models() or an alias. Images can be file paths, URLs, PIL images or RGB numpy arrays, alone or in a list (a list returns a list of results).

The detectors' predict() returns a dictionary with the following keys:

  • boxes: bounding boxes as [x1, y1, x2, y2] in pixels.
  • scores: the confidence of each box.
  • labels: the class of each box: a species name (species detectors), "arthropod" (arthropod detector) or the matching text prompt (zero-shot detectors).

The pipeline's predict() adds det_scores (detector confidence), labels (the reported identification at the deepest trusted level), species (the classifier's best species) and classifications (the full hierarchical record of each specimen).

Every model has a sensible default confidence (conf=0.25 for the detectors). The arthropod and zero-shot detectors also have an operating_conf, the confidence that gives at most about 0.2 false alarms per image on their validation sample; use it when false alarms matter more than missed specimens.

Input Image Arthropod Detection
conf 0.70
Species Detection
YOLO12x, conf 0.25
Two-Stage Identification
detector conf 0.70, gallery operating point
Zero-Shot Detection
Grounding DINO, conf 0.60
Trained species: Platypus koryoensis (benchmark iid_test, held-out specimens)
Input image Arthropod detection Species detection Two-stage identification Zero-shot detection
Unseen species: Platypus cylindrus (benchmark semantic_ood; the correct answer is Platypus sp.)
Input image Arthropod detection Species detection Two-stage identification Zero-shot detection

What the examples show. On the trained species, the arthropod detector finds all 11 specimens and both the species detector and the pipeline name every one Platypus koryoensis (Grounding DINO, at its strict operating confidence, finds 5). On the unseen species, the species detector names 9 of the 10 specimens Euplatypus compositus: a confident answer in the wrong genus, the only kind of answer it can give. The pipeline backs off to the correct tribe (Platypodini) for 2 specimens but still names a wrong Euplatypus species for the other 8. Over-commitment on unseen species of trained genera is the main open problem of the classifiers (about 58% of near_genus specimens on the benchmark; see benchmark results).

Images: Bark and Ambrosia Beetle Detection Benchmark v2.0.1, iid_test/c6c569ff-6a4b-4f0d-b298-8da5e4b9f2b2.jpg (Averie M. Kulbeda) and semantic_ood/1de1a58e-41c8-4b52-8647-31284228858f.jpg (Isabelle C. Stratton), University of Florida Forest Entomology Lab, CC BY-NC 4.0. Produced with ibbi v0.3 default models.


The hierarchical record

The hierarchical classifiers (alone or inside the pipeline) return one record per specimen:

clf = ibbi.create_model("hierarchical_classifier")
rec = clf.predict("path/to/crop.jpg")                       # one specimen per image
recs = clf.predict("photo.jpg", boxes=[[10, 20, 300, 400]])  # or boxes of a larger image
>>> res = ibbi.create_pipeline(det_conf=0.70).predict(image)
>>> res["classifications"][0]           # values rounded; "entropy" and the third top3 entry omitted
{
  "subfamily": {"taxon": "Platypodinae", "prob": 1.000, "score": 0.401, "threshold": 0.012, "known": True,
                "top3": [["Platypodinae", 1.000], ["Scolytinae", 0.000]]},
  "tribe":     {"taxon": "Platypodini", "prob": 1.000, "score": 0.917, "threshold": 0.051, "known": True, ...},
  "genus":     {"taxon": "Platypus", "prob": 0.991, "score": 0.595, "threshold": 0.051, "known": True,
                "top3": [["Platypus", 0.991], ["Treptoplatypus", 0.004], ["Crossotarsus", 0.002]]},
  "species":   {"taxon": "Platypus koryoensis", "prob": 0.991, "score": 0.748, "threshold": 0.054, "known": True,
                "top3": [["Platypus koryoensis", 0.991], ["Treptoplatypus solidus", 0.004], ["Crossotarsus kuntzeni", 0.002]]},
  "depth_by_op": {"0.9": 4, "0.95": 4, "0.99": 4, "gallery": 4},
  "depth": 4,
  "reported": "Platypus koryoensis"
}
  • reported: the human-readable answer at the deepest trusted level, e.g. "Xyleborus volvulus", "Euwallacea sp. (species undetermined)", "Xyleborini (genus undetermined)" or "unrecognised".
  • depth: 0 unrecognised, 1 subfamily, 2 tribe, 3 genus, 4 species.
  • One entry per level (subfamily, tribe, genus, species) with:
    • taxon: the predicted taxon; prob: its calibrated probability (consistent across levels: P(subfamily) ≥ P(tribe) ≥ P(genus) ≥ P(species) along the predicted lineage); top3: the three most likely taxa.
    • score: the novelty score, the percentile of the specimen's familiarity among known validation beetles (higher = more familiar); threshold: the operating point's threshold; known: score >= threshold.
  • depth_by_op: the depth the specimen would get under every operating point.

The reported depth is the deepest level such that every level above it is known. Operating points trade abstention on known species against over-commitment on unknown ones:

Operating point Meaning
gallery (default) accept 99% of known validation beetles at subfamily, 95% at tribe, genus and species
0.90, 0.95, 0.99 accept that share of known validation beetles at every level
clf = ibbi.create_model("hierarchical_classifier", operating_point="0.99")   # or predict(..., operating_point="0.99")

Feature Extraction

The classifiers' fine-tuned backbones produce embeddings that are useful for downstream tasks like clustering, retrieval or similarity analysis.

# --- Create a model ---
feature_extractor = ibbi.create_model("feature_extractor")      # DINOv3 hierarchical classifier

# --- Extract features ---
embedding = feature_extractor.extract_features("path/to/your/crop.jpg")

extract_features() returns a tensor of shape (1, embedding_dimension) (2,048 for the DINOv3 classifier). Every model has extract_features(), but only the classifiers' embeddings were trained to separate species; their taxonomic structure is measured by the Mantel correlation in docs/benchmark.md.


Model Evaluation

Evaluate a model on the benchmark with the ibbi.Evaluator() wrapper.

# --- Create a model and an evaluator ---
model = ibbi.create_model("species_detector")
evaluator = ibbi.Evaluator(model)

# --- Benchmark metrics (reference evaluator) on iid_test, inat_test and semantic_ood ---
results = evaluator.benchmark()
results["headline"]                                        # flat dict of the key metrics
results["per_split"]["iid_test"]["decomposition"]          # detection recall, species accuracy given detection, ...

# --- A quick check on a few images (not comparable to published numbers) ---
evaluator.benchmark(splits=["iid_test"], max_images=50)

# --- Hierarchical classification metrics ---
clf_eval = ibbi.Evaluator(ibbi.create_model("hierarchical_classifier"))
h = clf_eval.hierarchical_classification()
h["per_split"]["iid_test"]["acc_species"]
h["per_split"]["semantic_ood"]["by_band"]                  # behaviour on unseen species by taxonomic distance
h["novelty"]["genus"]["auroc"]

# --- Embedding & clustering metrics ---
data = ibbi.get_dataset("iid_test")
emb_eval = ibbi.Evaluator(ibbi.create_model("feature_extractor"))
emb = emb_eval.embeddings(data.select(range(100)))
emb["mantel_correlation"]                                  # embedding distance vs taxonomic distance

Note on run time and memory

benchmark() and hierarchical_classification() stream images from disk, so memory use stays flat, but a full run covers more than 8,000 images (most of them in semantic_ood): use a GPU, or pass splits= and max_images= for a quick look. embeddings() keeps every embedding in memory; use a subset (data.select(range(n))) on small machines.

To score predictions made outside ibbi, pass COCO-format results to ibbi.evaluate.benchmark.evaluate_predictions() (species-level) or evaluate_class_agnostic() (see the scoring notebook).


Model Explainability

Understand why a model made a prediction by highlighting the image regions that were most influential.

# --- Create a model and an explainer ---
model = ibbi.create_model("hierarchical_classifier")
explainer = ibbi.Explainer(model)

# --- Explain with LIME ---
explanation, image = explainer.with_lime(crop, image_size=(336, 336), num_samples=1000, top_labels=3)
ibbi.plot_lime_explanation(explanation, image, top_k=1)

# --- Explain with SHAP (needs a background set) ---
background = ibbi.get_shap_background_dataset(image_size=(336, 336))
shap_values = explainer.with_shap([{"image": crop}], background, num_explain_samples=1, image_size=(336, 336))
ibbi.plot_shap_explanation(shap_values[0], model, top_k=3)

Classifiers are explained through their calibrated species probabilities, detectors through their highest confidence per class, and zero-shot detectors through their score per prompt (text_prompt= sets the prompts).

LIME (green: supports the species, red: against it) SHAP (red: supports the species, blue: against it)
LIME explanation SHAP explanation

The Dataset

IBBI uses one dataset: the Bark and Ambrosia Beetle Detection Benchmark v2.0.1 (Hugging Face, Zenodo DOI 10.5281/zenodo.22695714): 14,491 images, 175 species, COCO format, splits disjoint by specimen.

Split Images Scored specimens Species Purpose
train 6,096 53,040 65 training
iid_test 620 650 (+4,367 crowd) 65 held-out specimens of the trained species
inat_test 74 80 (+8 crowd) 8 field photographs of trained species
semantic_ood 7,701 46,924 110 species never seen in training, banded by taxonomic distance
test = ibbi.get_dataset("iid_test")
item = test[0]
item["image"]                     # PIL image
item["objects"]["bbox"]           # [[x, y, w, h], ...] in pixels (COCO)
item["objects"]["category"]       # species names
item["objects"]["iscrowd"]        # 1 = real specimen that is not scored
ibbi.get_taxonomy()               # all 175 species with their lineage, role and distance band
  • Crowd regions (iscrowd=1) are real specimens that are not scored; ibbi.Evaluator ignores them, as the benchmark requires.
  • Distance bands of semantic_ood: near_genus (a trained genus), mid_tribe (a trained tribe, new genus) and far_tribe (a new tribe).
  • The package pins the dataset to the v2.0.1 revision so results are reproducible.
  • The older datasets (ibbi_test_data, ibbi_ood_data, ibbi_shap_dataset) are deprecated: get_ood_dataset() now returns semantic_ood with a deprecation warning, and the SHAP background is sampled from train.

Licences

  • Code of the ibbi package: MIT (LICENSE.md).
  • Model weights carry their own licences, set by the software and base models they derive from:
    • Ultralytics-trained detectors (species detectors, YOLO11x arthropod detector): AGPL-3.0.
    • Co-DINO arthropod detector: CC BY-NC 4.0. Its initial checkpoint was pretrained on Objects365, which is for academic use only; the model code is derived from Co-DETR and EVA-02 (MIT) and MMDetection (Apache-2.0).
    • DINOv3 hierarchical classifier: DINOv3 License (Meta). It is a derivative of DINOv3 and is redistributed under that licence, whose text ships with the weights; publications using it must acknowledge DINOv3.
    • BioCLIP 2 hierarchical classifier: MIT.
    • Zero-shot models are downloaded from their authors under their own licences.
  • Training data terms. About 60% of the benchmark's training images are CC BY-NC, and the arthropod corpus (and the classifiers' non-beetle examples) include iNaturalist 2017 (non-commercial research and education only) and IP102 (academic use only). Whether weights inherit the terms of their training data is unsettled; the IBBI weights are released for non-commercial research use, and commercial users should check each model card.
  • Benchmark: annotations CC BY 4.0, images under mixed Creative Commons licences (see image_licences.csv in the dataset).

ibbi imports Ultralytics, which is AGPL-3.0; software that distributes ibbi together with Ultralytics must comply with the AGPL.


How to Contribute

Contributions are welcome! If you would like to improve IBBI, please see the Contribution Guide. Questions and bug reports go to the issue tracker.

Citation

If you use IBBI, please cite the benchmark and the package:

@dataset{marais_bark_ambrosia_benchmark_2026,
  title     = {Bark and Ambrosia Beetle Detection Benchmark},
  author    = {Marais, G. Christopher and Schuster, Layla A. and Johnson, Andrew J. and Kuo, Eric and Dias, Raquel and Hulcr, Jiri},
  year      = {2026},
  publisher = {Zenodo},
  version   = {2.0.1},
  doi       = {10.5281/zenodo.22695714}
}

@software{marais_ibbi,
  title   = {IBBI: Intelligent Bark Beetle Identifier},
  author  = {Marais, G. Christopher and Kuo, Eric and Hulcr, Jiri and Dias, Raquel},
  url     = {https://github.com/ChristopherMarais/IBBI},
  license = {MIT}
}

Please also cite the base models you use (see the Paper column above); the DINOv3 License requires acknowledging DINOv3.

License

The code of this project is licensed under the MIT License; see LICENSE.md. Model weights carry their own licences (see Licences).

Metadata

Release files for ibbi 0.3.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ibbi 0.3.2
File Size Uploaded
ibbi-0.3.2.tar.gz 118.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ibbi 0.3.2
File Interpreter ABI Platform
ibbi-0.3.2-py3-none-any.whl Python 3 none any Details

Total release size: 236.0 kB

Release files / ibbi-0.3.2.tar.gz

Download URL ibbi-0.3.2.tar.gz
Size 118.1 kB
Tags Source
SHA-256 checksum
How to use checksums
00c8c305ffcce368075a24040c91fd870544c9a16944c02011e1a58ed5b22b65
BLAKE2b-256 checksum
How to use checksums
88acd64fad43d50ccfbc1de1e01fcd269e419da7d89dcbbac40a428e1408db91
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.

Transparency log

Release files / ibbi-0.3.2-py3-none-any.whl

Download URL ibbi-0.3.2-py3-none-any.whl
Size 118.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4f9fe371346ff45fbe6955701817477ff0b0df1dc5d84057aad5cafefad788ee
BLAKE2b-256 checksum
How to use checksums
86d8c5cb64e4e95d39f7e64f13c16467ed5ab1b5bb84554ed354cb8fce37e213
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.

Transparency log
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page