Species explorer
Search any moth / butterfly name (scientific or common). Results come live from the GBIF Backbone Taxonomy and iNaturalist. Only exact name matches are shown, so the images belong to that species.
name = input("species name: ") country = None # region filter (ISO-2) sp = search_species(name, country=country)
Moth AI: Uncertainty-Aware Moth Identification
Development & evaluation of an uncertainty-aware moth identification system with taxonomic integration via the GBIF Species API and the iNaturalist API.
Pipeline: a 3-pass vision ensemble ranks the top-5 species → predictive entropy & ensemble agreement quantify uncertainty → every candidate name is reconciled against the GBIF backbone → research-grade iNaturalist observations provide visual evidence.
import requests from moth_ai import EnsembleIdentifier, uncertainty GBIF = "https://api.gbif.org/v1" INAT = "https://api.inaturalist.org/v1" model = EnsembleIdentifier(backbone="gemini-3-flash", passes=3) TAU_CONF, TAU_ENTROPY = 0.60, 0.50 # abstention thresholds
1. Load a specimen image
Upload a photo (optionally with its known species for evaluation), or load one of the iNaturalist research-grade reference specimens.
img = load_image(...)
result = model.identify(img, top_k=5)Compare species
Pick two similar moths to see their photos, GBIF occurrence maps and key distinguishing features side by side.
a, b = None, None compare(a, b, country=None)
Evaluation & calibration
Every identification with a known true_label is a test case. We report top-k accuracy, Expected Calibration Error (ECE, 10 bins), Brier score, and the selective-prediction trade-off between coverage and accuracy.
test_set = load_labelled_images("./test/") # or the iNaturalist reference set for img, y in test_set: model.identify(img, true_label=y)
metrics = evaluate(db.labelled())
print(metrics)n_samples : 0 top1_accuracy : nan top1_pre_rerank : nan (re-ranked 0, fixed 0, broke 0) top5_accuracy : nan genus_accuracy : nan ECE : nan brier : nan abstention_rate : nan acc | abstained : nan acc | accepted : nan expert_labels : 0 (abstention justified: nan)
plt.bar(bins, acc_before, alpha=.5); plt.bar(bins, acc_after) plt.plot([0,1],[0,1],"--"); plt.title("Reliability: before vs after re-ranking")
ECE before rerank: nan → after: nan (Δ nan) Brier before rerank: nan → after: nan re-ranked samples : 0 / 0
tau = 0.60 # abstain if p(top1) < tau plot_coverage_accuracy(metrics, tau)
export_report(metrics, charts=True) # whole evaluation run as one notebook
results_df[["true_label", "top1", "p", "top1_correct", "top5_correct", "abstained"]]