backyard-birder / app.py
profplate's picture
Create app.py
5023401 verified
import gradio as gr
from transformers import pipeline
import numpy as np
from datetime import datetime
# Load the audio classification model
classifier = pipeline(
"audio-classification",
model="dima806/bird_sounds_classification",
device=-1,
)
# Bird information database (15 species with descriptions)
BIRD_INFO = {
"Great Tinamou": {
"habitat": "Tropical and subtropical lowland forests from southern Mexico to northern South America.",
"song": "A series of tremulous, haunting whistles that echo through the forest - one of the most recognizable sounds of the neotropical lowlands.",
"range": "Southern Mexico through Central America to northern South America, including Brazil and Peru.",
"fun_fact": "Despite being a ground-dwelling bird, the Great Tinamou roosts in trees at night. Its eggs are among the most beautiful in the bird world - glossy turquoise-blue.",
},
"Plain Chachalaca": {
"habitat": "Brushy woodland edges, thickets, and riparian areas. The only chachalaca regularly found in the United States (southern Texas).",
"song": "A loud, raucous CHA-cha-LAC repeated in chorus by groups - unmistakable once you've heard it. Often called at dawn.",
"range": "Southern Texas through Mexico to Costa Rica.",
"fun_fact": "Plain Chachalacas are one of the few species in this model's list that can actually be seen in the US - in the Rio Grande Valley of Texas.",
},
"Crested Guan": {
"habitat": "Mountain forests and cloud forests, typically at elevations of 500-2,500 meters.",
"song": "A variety of honking and trumpeting calls, especially loud during breeding season.",
"range": "Southern Mexico through Central America to western South America.",
"fun_fact": "Crested Guans are important seed dispersers for many tropical tree species. They swallow fruits whole and spread seeds through the forest.",
},
"Andean Guan": {
"habitat": "Cloud forests and humid montane forests of the Andes, typically between 1,500 and 3,500 meters elevation.",
"song": "Deep honking calls that carry through mountain valleys, often given in the early morning.",
"range": "Andes from Venezuela south through Colombia, Ecuador, Peru, and Bolivia.",
"fun_fact": "The Andean Guan is a canopy specialist that rarely descends to the ground, moving through the treetops to feed on fruit and leaves.",
},
"Little Tinamou": {
"habitat": "Dense undergrowth of tropical forests. Extremely secretive and almost never seen despite being common.",
"song": "A long, tremulous whistle that rises and falls - one of the most frequently heard but least seen birds in its range.",
"range": "Southern Mexico through Central America to South America, as far south as Brazil.",
"fun_fact": "Little Tinamous are heard far more often than seen. They freeze when threatened and rely on their camouflage, only flushing at the last moment.",
},
"Solitary Tinamou": {
"habitat": "Interior of humid tropical forests, usually on the forest floor.",
"song": "A mournful, descending whistle that sounds almost electronic - quite eerie when heard in the deep forest.",
"range": "Central America through northern South America.",
"fun_fact": "True to its name, the Solitary Tinamou is almost always found alone. Males incubate the eggs and raise the chicks by themselves.",
},
"Highland Tinamou": {
"habitat": "Cloud forests and montane forests, from 1,200 to 3,000 meters elevation.",
"song": "A clear, descending series of whistles. Sometimes described as sounding like someone playing a slow scale on a flute.",
"range": "Mountains of Costa Rica and Panama through the Andes to Bolivia.",
"fun_fact": "Highland Tinamous have been recorded at higher elevations than almost any other tinamou species.",
},
"Grey-headed Chachalaca": {
"habitat": "Dry forests, forest edges, and agricultural areas with scattered trees.",
"song": "Loud, harsh calls similar to other chachalacas, often given by groups in noisy choruses at dawn and dusk.",
"range": "Honduras through Central America to northern Colombia.",
"fun_fact": "Chachalacas get their name from the sound of their call - cha-cha-lac-a - repeated over and over.",
},
"Band-tailed Guan": {
"habitat": "Humid mountain forests and cloud forests.",
"song": "A series of deep, resonant honking sounds, especially vocal during the breeding season.",
"range": "Andes from Colombia and Venezuela south to Bolivia.",
"fun_fact": "Band-tailed Guans travel in small family groups and are surprisingly acrobatic for their size, leaping between branches to reach fruit.",
},
"Black-capped Tinamou": {
"habitat": "Forests from lowlands to lower montane elevations.",
"song": "A bubbling, accelerating series of whistles - sounds like a bouncing ball slowing to a stop, but in reverse.",
"range": "Central Peru to Bolivia, in the eastern Andean slopes.",
"fun_fact": "Like other tinamous, the Black-capped Tinamou can fly but strongly prefers to walk. When it does fly, it's in short, explosive bursts.",
},
"Spotted Nothura": {
"habitat": "Grasslands and open areas, including agricultural fields and pastures.",
"song": "A series of sharp, staccato whistles, often given from the ground in open grassland.",
"range": "Central South America - Brazil, Paraguay, Argentina, Uruguay.",
"fun_fact": "Nothuras are grassland tinamous - unlike their forest-dwelling relatives, they live in open habitats and look somewhat like partridges.",
},
"Red-winged Tinamou": {
"habitat": "Grasslands, scrublands, and agricultural areas in southern South America.",
"song": "A melodious, flute-like whistle that rises and falls, often heard at dawn and dusk across the pampas.",
"range": "Southern Brazil, Paraguay, Uruguay, Argentina.",
"fun_fact": "Named for the rufous-red color visible on its wings in flight - one of the few times you'll see this secretive bird in the open.",
},
"Australian Brushturkey": {
"habitat": "Rainforests, scrublands, and suburban gardens in eastern Australia.",
"song": "Deep booming calls during breeding. Otherwise relatively quiet compared to other species in this list.",
"range": "Eastern Australia, from Cape York to southern New South Wales.",
"fun_fact": "Males build enormous mound nests (up to 4 meters wide) out of decomposing vegetation. The heat from decomposition incubates the eggs - no body heat required.",
},
"Dusky Megapode": {
"habitat": "Tropical forests on islands in the western Pacific, often near volcanic areas.",
"song": "Loud wailing calls, especially at night. Some species in this family are among the noisiest birds in their habitat.",
"range": "Islands of Indonesia and Papua New Guinea.",
"fun_fact": "Some megapodes use volcanic heat to incubate their eggs, burying them in volcanically warmed soil - the only birds known to use geothermal energy for reproduction.",
},
"Tataupa Tinamou": {
"habitat": "Forest edges, secondary growth, and dense undergrowth in tropical and subtropical regions.",
"song": "A rich, mellow series of whistles. Has been described as one of the most beautiful bird songs in South America.",
"range": "Eastern South America from Brazil to Argentina.",
"fun_fact": "The Tataupa Tinamou is more tolerant of disturbed habitats than many of its relatives, which helps it survive in areas affected by deforestation.",
},
}
ALL_SPECIES = sorted(set(classifier.model.config.id2label.values()))
# Sighting log (in-memory)
sighting_log = []
def classify_bird(audio):
if audio is None:
return "Please upload or record an audio file.", gr.update(), gr.update()
sr, y = audio
if y.dtype == np.int16:
y = y.astype(np.float32) / 32768.0
elif y.dtype == np.int32:
y = y.astype(np.float32) / 2147483648.0
elif y.dtype != np.float32:
y = y.astype(np.float32)
if len(y.shape) > 1:
y = y[:, 0]
if sr != 16000:
duration = len(y) / sr
new_length = int(duration * 16000)
y = np.interp(
np.linspace(0, len(y) - 1, new_length),
np.arange(len(y)),
y,
)
sr = 16000
results = classifier({"sampling_rate": sr, "raw": y}, top_k=3)
lines = []
top_species = results[0]["label"]
top_score = results[0]["score"]
if top_score < 0.40:
lines.append("Not confident - this may not be a recognizable bird song,")
lines.append("or the species may not be in this model's training data.\n")
for i, pred in enumerate(results, 1):
score = pred["score"]
label = pred["label"]
# Color-coded confidence
if score >= 0.70:
indicator = "HIGH"
elif score >= 0.40:
indicator = "MED"
else:
indicator = "LOW"
bar_length = int(score * 20)
bar = "#" * bar_length + "." * (20 - bar_length)
lines.append(f"[{indicator}] {i}. {label}")
lines.append(f" {bar} {score:.1%}\n")
return "\n".join(lines), gr.update(value=top_species), gr.update()
def get_bird_info(species):
if species in BIRD_INFO:
info = BIRD_INFO[species]
text = f"## {species}\n\n"
text += f"**Habitat:** {info['habitat']}\n\n"
text += f"**Song:** {info['song']}\n\n"
text += f"**Range:** {info['range']}\n\n"
text += f"**Fun fact:** {info['fun_fact']}"
return text
else:
return (
f"## {species}\n\n"
f"No detailed description available for this species yet. "
f"I've written descriptions for 15 of the 50 species in this model. "
f"Try searching [Xeno-Canto](https://xeno-canto.org/) or the "
f"[Cornell Lab](https://www.allaboutbirds.org/) for more information."
)
def add_sighting(species, location, notes):
if not species:
return format_log()
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
entry = {
"species": species,
"date": timestamp,
"location": location or "Not specified",
"notes": notes or "",
}
sighting_log.append(entry)
return format_log()
def format_log():
if not sighting_log:
return "No sightings logged yet. Identify a bird and add it to your log!"
lines = [f"### Sighting Log ({len(sighting_log)} entries)\n"]
for i, entry in enumerate(sighting_log, 1):
lines.append(f"**{i}. {entry['species']}**")
lines.append(f" {entry['date']} | {entry['location']}")
if entry["notes"]:
lines.append(f" Notes: {entry['notes']}")
lines.append("")
lines.append("---")
lines.append(
"*This log is stored in memory and will reset when the Space restarts. "
"Copy your log if you want to save it!*"
)
return "\n".join(lines)
# Build the interface with tabs
with gr.Blocks(theme=gr.themes.Soft(), title="The Backyard Birder") as demo:
gr.Markdown(
"""
# The Backyard Birder
A multi-feature birding assistant. Identify birds from audio recordings,
learn about the species, and keep a sighting log.
**Model:** `dima806/bird_sounds_classification` - 50 species (Tinamous, Guans, Chachalacas, and relatives).
These are neotropical birds, not typical North American backyard species. The tool demonstrates how
audio classification pipelines work; with a different model (like BirdNET), it could identify local birds too.
---
"""
)
# State for passing species between tabs
current_species = gr.State("")
with gr.Tabs():
# Tab 1: Identify
with gr.Tab("Identify"):
gr.Markdown("Upload a bird recording to identify the species. Best results with clean recordings of 3+ seconds.")
with gr.Row():
with gr.Column():
audio_input = gr.Audio(
label="Upload or Record Audio",
type="numpy",
)
classify_btn = gr.Button("Identify Bird", variant="primary")
with gr.Column():
classification_output = gr.Textbox(
label="Top 3 Predictions",
lines=10,
interactive=False,
)
species_display = gr.Textbox(
label="Top prediction",
visible=False,
)
# Tab 2: Learn
with gr.Tab("Learn"):
gr.Markdown("Select a species to learn about it. Descriptions available for 15 of the 50 species.")
species_dropdown = gr.Dropdown(
choices=ALL_SPECIES,
label="Select a Species",
value="Great Tinamou",
)
bird_info_output = gr.Markdown(
value=get_bird_info("Great Tinamou"),
)
# Tab 3: Log
with gr.Tab("Log Sightings"):
gr.Markdown("Keep track of what you hear. Add species to your sighting log with location and notes.")
with gr.Row():
with gr.Column():
log_species = gr.Dropdown(
choices=ALL_SPECIES,
label="Species",
allow_custom_value=True,
)
log_location = gr.Textbox(
label="Location",
placeholder="e.g., Backyard, Local park, Trail near school...",
)
log_notes = gr.Textbox(
label="Notes",
placeholder="e.g., Heard at dawn, two birds calling back and forth...",
lines=2,
)
log_btn = gr.Button("Add to Log", variant="primary")
with gr.Column():
log_output = gr.Markdown(value=format_log())
# Wire up events
classify_btn.click(
fn=classify_bird,
inputs=[audio_input],
outputs=[classification_output, species_dropdown, species_display],
)
species_dropdown.change(
fn=get_bird_info,
inputs=[species_dropdown],
outputs=[bird_info_output],
)
log_btn.click(
fn=add_sighting,
inputs=[log_species, log_location, log_notes],
outputs=[log_output],
)
gr.Markdown(
"""
---
*Riley's Space 3 - AI + Research Level 2*
**How this works:** The Identify tab uses an audio classification model to predict which species
is singing. The Learn tab shows hand-written descriptions for 15 species. The Log tab lets you
track your sightings during this session. This is a multi-model pipeline: audio classification
feeds species information, and errors in identification propagate to wrong descriptions.
"""
)
demo.launch()