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()