Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
116 commits
Select commit Hold shift + click to select a range
7b386ef
Changes to route parameters optionality, and default values to accoun…
MLumme May 20, 2025
900479c
Fixed an incorrectly set default value.
MLumme May 20, 2025
3e6f9d4
Increased possible sound chunk overlap.
MLumme Jun 17, 2025
a52877c
Merge branch 'main' into development
aorin Oct 21, 2025
2e48d1d
move data files to models folder, allow scripts package to be install…
aorin Oct 21, 2025
ca83899
first version of the app
aorin Oct 22, 2025
18b75b7
update desktop app development insturctions
aorin Oct 22, 2025
4454453
use .h5 format instead of the .keras format of the model since it wor…
aorin Oct 22, 2025
83082bf
use absolute paths for the model files in the app
aorin Oct 22, 2025
0c90943
add spec file to git
aorin Oct 22, 2025
3c12438
make audio player animation smoother
aorin Oct 22, 2025
dc17aa1
changing styles so that they would look better with the dark mode
aorin Oct 22, 2025
42b7170
don't display console with the desktop app, only include specific mod…
aorin Oct 24, 2025
aa64a1d
use QMediaPlayer to play audio
aorin Oct 27, 2025
ff35050
audio can be played by clicking the waveform graph
aorin Oct 27, 2025
5cf25ae
fine-tune icons
aorin Oct 28, 2025
ed76aa4
use less stylesheets and more native styles
aorin Oct 29, 2025
58e97ef
set minimum height to inputs
aorin Oct 29, 2025
bd8a8ea
set analyze button transparent when disabled
aorin Oct 29, 2025
e53dd0c
play icon changes to pause icon when playing audio
aorin Oct 29, 2025
15bb286
display splash screen while the app is initializing
aorin Oct 29, 2025
3254255
analyze button can be clicked while the audio is still loading
aorin Oct 29, 2025
bad7393
refactoring
aorin Oct 29, 2025
c7b9d53
use splash screen paintEvent instead of threading to ensure that the …
aorin Oct 30, 2025
74d320e
rename result file names and columns
aorin Oct 31, 2025
365e906
add loading spinner before the status text
aorin Oct 31, 2025
11c57a9
show a text when analyzing multiple files completes
aorin Oct 31, 2025
f483174
add placeholder app icons
aorin Oct 31, 2025
432f31e
fix icon paths
aorin Oct 31, 2025
04886ee
show correct taskbar icon on windows
aorin Oct 31, 2025
989110f
fix import error, load audio with sample rate 24000
aorin Nov 5, 2025
c055992
add model config tab for adding and removing models
aorin Nov 10, 2025
6cc1c7c
fix window size increasing too much when switching between tabs
aorin Nov 10, 2025
81860af
fine-tuning
aorin Nov 10, 2025
3c2811c
show alert if there's no models
aorin Nov 10, 2025
700f822
fine-tuning file select
aorin Nov 10, 2025
2efa642
detector can be cancelled
aorin Nov 11, 2025
17b31ce
try to fix app sometimes freezing when starting up by importing heavy…
aorin Nov 11, 2025
3ebcec8
remove unused imports
aorin Nov 11, 2025
a0d77ff
reduce the size of the desktop app by removing unnecessary dependenci…
aorin Nov 13, 2025
528d32d
switch back using tensorflow-cpu instead of tflite-runtime since it d…
aorin Nov 14, 2025
84fc182
try to optimize analyze function
aorin Nov 17, 2025
e5f364a
run the model on different process
aorin Nov 19, 2025
931e68c
include analyze_process to package
aorin Nov 24, 2025
e46cd81
add github action for building windows installer
aorin Nov 26, 2025
9d658b6
fix github folder location
aorin Nov 26, 2025
e454a95
fix workflow file format
aorin Nov 26, 2025
2dfab20
workflow gets models from another repo
aorin Nov 26, 2025
434a91f
test workflow
aorin Nov 26, 2025
d322fbf
try to fix windows workflow
aorin Nov 26, 2025
4bcf871
try to fix windows workflow
aorin Nov 26, 2025
0fe150f
try to debug windows workflow
aorin Nov 26, 2025
265c0f5
update installforge config
aorin Nov 26, 2025
8c0cb0d
try to fix installforge build
aorin Nov 26, 2025
3a66575
default models can't be removed, when importing a model only path to …
aorin Nov 28, 2025
e877eb5
update windows installer
aorin Nov 28, 2025
3d18fc1
fix data repository name and windows paths
aorin Nov 28, 2025
4fec5c8
remove unnecessary step
aorin Nov 28, 2025
5d18083
app doesn't need to be initialized in another thread anymore
aorin Dec 1, 2025
ad776d4
don't build windows installer everytime
aorin Dec 1, 2025
431df10
refactor process worker, add more status texts
aorin Dec 1, 2025
101d801
show info bar at the bottom with version number and link to instructi…
aorin Dec 1, 2025
45456d1
fix output file name
aorin Dec 1, 2025
5b135d4
fix wrong version in windows version info
aorin Dec 1, 2025
03b58e2
add a workflow for building macos disk image
aorin Dec 2, 2025
a484d81
use different tensorflow package for mac
aorin Dec 2, 2025
994be24
add execution rights
aorin Dec 2, 2025
14cce08
don't store custom models to the installation directory
aorin Dec 3, 2025
ae5bf07
fix build dmg script
aorin Dec 4, 2025
61facd5
update pyside6
aorin Dec 5, 2025
0557993
fine-tuning
aorin Dec 5, 2025
c3a3962
improve waveform by loading long recordings in chunks and improve its…
aorin Dec 5, 2025
302e385
set output folder the same as input folder as a default
aorin Dec 8, 2025
768baa1
add download results button to single file tab
aorin Dec 8, 2025
4bd6279
include finbif name and link to licenses in the info bar
aorin Dec 10, 2025
7dc50a8
use custom styles for the main button
aorin Dec 10, 2025
be0bd91
style fine-tuning
aorin Dec 10, 2025
39e6b75
waveform is always centered
aorin Dec 11, 2025
c12defc
Added access_token authentication and enabled sdm-usage with treshold…
MLumme Dec 11, 2025
a23ee5e
Fixed merge conflicts with the new tabletop app changes.
MLumme Dec 12, 2025
10e0b99
add desktop app licenses
aorin Dec 12, 2025
a52b54c
Merge branch 'development' of github.com:luomus/bird-identification i…
aorin Dec 12, 2025
de80039
get app version number from git tag, combine windows and macos instal…
aorin Dec 18, 2025
a22c545
fix mac requirement
aorin Dec 18, 2025
479ee6d
fix previous commit
aorin Dec 18, 2025
f18d182
fix matrix format
aorin Dec 18, 2025
b8ab75c
try to fix version issue in the workflow
aorin Dec 18, 2025
0355376
try to fix version issue in the workflow
aorin Dec 18, 2025
565fc80
try to fix version issue in the workflow
aorin Dec 18, 2025
5425a73
try to fix version issue in the workflow
aorin Dec 18, 2025
20a9385
fix create-dmg missing from macos workflow
aorin Dec 18, 2025
4ce1ddd
Added an example config for running the web-api uvicorn.
MLumme Jan 12, 2026
e41278e
Added the actual uvicorn config to the file.
MLumme Jan 12, 2026
5fa3642
Security key changed from query parameter to header
MLumme Jan 14, 2026
7a1c6d6
Fixed typo in import
MLumme Jan 14, 2026
8909747
add support for a bat model
aorin Feb 9, 2026
79bc507
Merge branch 'development' of github.com:luomus/bird-identification i…
aorin Feb 9, 2026
aa8cf6e
add some typings
aorin Feb 9, 2026
aed5bd7
audio player shows error alert when it can't play audio
aorin Feb 10, 2026
ef3785d
remove unused import
aorin Feb 10, 2026
c8d13b2
improvements to expection handling
aorin Feb 10, 2026
1b4faa9
small refactoring
aorin Feb 12, 2026
e9812f1
add custom model path for linux
aorin Feb 12, 2026
c4f72da
Fxied incorrect classfier parameter call.
MLumme Mar 3, 2026
1932aef
change app name to Sirkku and add an icon
aorin Mar 11, 2026
d934a70
Merge branch 'development' of github.com:luomus/bird-identification i…
aorin Mar 11, 2026
eda353c
include architecture to package names
aorin Mar 13, 2026
e01ba8c
merge main
aorin Mar 13, 2026
353e4fa
add tests
aorin Mar 13, 2026
55e176a
rename test file
aorin Mar 13, 2026
add98be
update windows icon
aorin Mar 25, 2026
071294f
update icons
aorin Mar 26, 2026
5ba421c
fix build_dmg script icon path
aorin Mar 26, 2026
91e7699
icon changes
aorin Mar 31, 2026
d908fd8
update icons
aorin Apr 7, 2026
f9532cd
refactor by splitting scripts folder to api, cli and shared folders
aorin Apr 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
106 changes: 106 additions & 0 deletions .github/workflows/build-installers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: Build Installers

on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'

jobs:
build:
runs-on: ${{ matrix.os }}

strategy:
matrix:
include:
- os: windows-latest
requirements_tf: requirements_tf.txt
architecture: windows-x64
- os: macos-latest
requirements_tf: requirements_tf_mac.txt
architecture: macos-arm64

steps:
- name: Checkout repo
uses: actions/checkout@v6

- name: Write release version
shell: bash
run: |
VERSION="${GITHUB_REF_NAME#v}"
echo "VERSION=$VERSION" >> "$GITHUB_ENV"

- name: Get data from private repo
uses: actions/checkout@v6
with:
repository: luomus/bird-identification-app-data
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
path: private-data

- name: Move data to correct places (Windows)
if: startsWith(matrix.os, 'windows-')
run: |
Remove-Item -Recurse -Force app\models
Move-Item private-data\default-models app\models
Expand-Archive -Path private-data\InstallForge.zip -DestinationPath app

- name: Move data to correct places (macOS)
if: startsWith(matrix.os, 'macos-')
run: |
rm -rf app/models
mv private-data/default-models app/models

- name: Install create-dmg (macOS)
if: startsWith(matrix.os, 'macos-')
run: |
brew install create-dmg

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.9'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install ..
pip install -r requirements.txt
pip install -r ${{ matrix.requirements_tf }}
pip install pyinstaller
working-directory: ./app

- name: Set correct version numbers
shell: bash
run: |
python replace_version.py "${VERSION}" "${{ matrix.architecture }}"
working-directory: ./app

- name: Build executable with PyInstaller
run: |
pyinstaller app.spec --noconfirm
working-directory: ./app

- name: Build Windows installer with InstallForge
if: startsWith(matrix.os, 'windows-')
run: |
.\ifbuildx86.exe -i ..\..\app.ifp
working-directory: ./app/InstallForge/bin

- name: Build macOS Disk Image with create-dmg
if: startsWith(matrix.os, 'macos-')
run: |
./build_dmg.sh
working-directory: ./app

- name: Upload installer (Windows)
if: startsWith(matrix.os, 'windows-')
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.architecture }}-installer
path: .\app\dist\sirkku-*-setup.exe

- name: Upload installer (macOS)
if: startsWith(matrix.os, 'macos-')
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.architecture }}-installer
path: ./app/dist/sirkku-*.dmg
26 changes: 21 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,21 @@
input/*
!input/readme.txt

models/model_v3.5.keras
models/model*.keras

models/*
!models/readme.txt
!models/Pred_adjustment
!models/classes.csv
!models/license_birdnet.txt
!models/BirdNET_GLOBAL_6K_V2.4_Model_FP32.tflite

app/models/*
!app/models/.gitkeep
!app/models/default
app/models/default/*
!app/models/default/.gitkeep
app/custom_models/

app/InstallForge/

# Byte-compiled / optimized / DLL files
__pycache__/
Expand Down Expand Up @@ -33,12 +45,13 @@ share/python-wheels/
.installed.cfg
*.egg
MANIFEST
api/config/*_local.json

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
#*.spec

# Installer logs
pip-log.txt
Expand Down Expand Up @@ -167,4 +180,7 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.idea/

run/*
api/config/uvicorn_start
18 changes: 0 additions & 18 deletions Dockerfile

This file was deleted.

123 changes: 17 additions & 106 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@

This repository provides tools for automated bird species detection from audio recordings and for generating quality control reports. It supports local processing and API-based workflows and is built on an AI model developed at the University of Jyväskylä as part of the [Muuttolintujen kevät -project](https://www.jyu.fi/en/research/muuttolintujen-kevat).

## Project Structure

- `shared/` — Shared code: classifier and functions
- `api/` — HTTP API (FastAPI)
- `cli/` — Command-line scripts for local analysis and report generation
- `app/` — Desktop application

## Features

- Analyzes audio recordings (WAV, MP3, FLAC) to detect bird species, either locally or via API
- Files are divided into smaller chunks based on chunk_size parameter, because the model has a limit on the input size. There chunks are then divided into segments of clip_dur seconds (currently fixed to 3 seconds), and overlap parameter defines how much of each segment is overlapped with the next segment.
- Files are divided into smaller chunks based on chunk_size parameter, because the model has a limit on the input size. There chunks are then divided into segments of clip_dur seconds (currently fixed to 3 seconds), and overlap parameter defines how much of each segment is overlapped with the next segment.
- Uses species distribution and temporal modeling to improve detection accuracy
- Handles batch processing of multiple audio files
- Generates reports with species statistics and sample audio clips to help verifying the results
- Desktop application in development
- Desktop application for users who prefer a graphical interface

## Setup

Expand All @@ -26,115 +33,19 @@ This repository provides tools for automated bird species detection from audio r
- `git clone`
- `cd bird-identification`
- Place models to the `/models` folder: BirdNET and Muuttolintujen kevät
- `docker compose up --build; docker compose down;`
- Access the running docker container:
- `docker exec -ti bird-identification bash`
- Run the scripts, see below
- `docker compose up --build # start both cli and api`
- `docker compose up cli --build # start only cli`
- `docker compose up api --build # start only api`
- Desktop app has another setup, see [app/README.md](app/README.md)

### Running unit tests

Test can be run from the host machine using Docker Compose:
From the host machine:

- `docker compose run --rm test`

Test can also be run from within the container:

- `docker exec -ti bird-identification bash`
- `pytest /app/tests -v`

## Usage

### Identifying species locally

This analyzes audio files and generates tabular text files containing the identifications, one file for each audio file.

- Place audio files to a folder under `/input`, for example `/input/my_backyard_2025-01`.
- Place `metadata.yaml` file in the same folder. This contains information that is shared by all the files. Example format:

```yaml
lat: 60.123
lon: 24.123
day_of_year: 152 # Note: this will be overridden if audio file names include a date
```

- Run the script with `python main.py --dir <subfolder>`
- Optional parameters:
- `--thr`: Detection threshold as a decimal number between 0<>1, default 0.5
- `--noise`: Include noise in the output, default False
- `--sdm`: Use species distribution model to adjust confidence values, default False
- `--skip`: Skip audio files that already have a corresponding result file, default False
- `--overlap`: Overlap of segments to be analyzed in seconds, default 1.
- `--chunk_size`: Audio files are cut into chunks for analysis. This defines the size in seconds, default 600.

#### Note

- Expects that
- Audio filenames are in format `[part1].[extension]`
- Extension is `wav`, `mp3` or `flac`, case-insensitive
- If classification stops with message "Killed", try restarting the Docker container. It's unclear what causes this issue.
- The model and/or classifier has limitations:
- Segments can't be too long. 10 minutes seem to work fine, 30 minutes are too long.
- Overlap can't be too high. 1 second works fine, 2 seconds doesn't. Longer overlap leads to "Killed" message.

### Generating validation report

This reads tabular files containing species identifications, and generates an HTML report with example audio files for validation, and statistics and charts of the species.

- First do species identification, see above. You can also use BirdNET to do the identifications (use csv export format.)
- Validation report generation expects that:
- Data files are in the same directory as the audio files and in format `[part1].[part2].results.csv`
- Data files have columns: `Start (s), End (s), Scientific name, Common name, Confidence, [Optional columns]`
- Run the script with `python main_report.py --dir <subfolder>`
- Optional parameters:
- `--thr`: Detection threshold as a decimal number between 0<>1, default 0.5
- `--padding`: Padding in seconds for example audio files, default 1.
- `--examples`: Number of example audio files to pick for each species, minimum 5, default 5.

### Identifying species using API

Submit data to endpoint `/classify`.

A bare minimum call with mandatory `latitude` and `longitude` parameters looks like this:

```bash
curl -X POST "http://localhost:8000/classify?latitude=60.1699&longitude=24.9384" \
-H "accept: application/json" \
-H "Content-Type: multipart/form-data" \
-F "file=@<path_to_audio_file>"
```

Call with all parameters:

```bash
curl -X POST "http://localhost:8000/classify?latitude=60.1699&longitude=24.9384&threshold=0.5&include_sdm=True&include_noise=True&day_of_year=1&chunk_size=500&overlap=1" \
-H "accept: application/json" \
-H "Content-Type: multipart/form-data" \
-F "file=@<path_to_audio_file>"
```

#### Note

- Day of year can be set as a parameter, but if not, today's date is used.

## Todo

- Analysis
- Include inference details in the analysis result file, or at least identify the inference file?
- Refactor to handle settings in a centralized way, so that adding new parameters is easier
- Add clip_dur as a parameter
- Include both sdm and non-sdm predictions in the output
- Add taxon MX codes to the output
- Check why comparison-audio files are sometimes split into 5, 6 or 7 segments
- Report
- If data from one day only, don't create date histogram
- Include inference metadata into the report, so that it can be shared independently. But what to do if there are multiple inference files?
- Species commonness: how many % of observations from that area (+- 100 km) and time (+-10 days) are this species
- Normalize x-axis for all temporal charts. Get first and last time from the original data when it's loaded?
- Histograms are not made for species with only few detections. However, <img> tag is generated for these on the result service. Would be elegant not to have broken image links, though they are not visible for users.
- Misc
- Organizing the repos: continue with this repo, include baim features. Then rethink whether this tool and analysis (Bart) tool should be bundled together. And how to manage web interface vs. desktop app.
- Error handling when functions return None
- More unit testing
- Handle file paths in a more consistent ways (directory path, file name, datetime from filename)


- For command line tool, see [cli/README.md](cli/README.md)
- For API, see [api/README.md](api/README.md)
- For desktop app, see [app/README.md](app/README.md)
24 changes: 24 additions & 0 deletions api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM python:3.9-slim

WORKDIR /app

# Install TensorFlow separately with increased timeout
# The model requires TensorFlow 2.14.0
COPY api/requirements-tf.txt .
RUN pip install --no-cache-dir --timeout=1000 -r requirements-tf.txt

RUN apt-get update
RUN apt-get install libexpat1 -y
RUN apt-get install curl -y

COPY api/requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

# Copy shared package and api package
COPY shared/ /app/shared/
COPY api/ /app/api/

EXPOSE 8000

CMD ["uvicorn", "api.api:app", "--host", "0.0.0.0", "--port", "8000"]
25 changes: 25 additions & 0 deletions api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Identifying species using API

Submit data to endpoint `/classify`.

A bare minimum call with mandatory `latitude` and `longitude` parameters looks like this:

```bash
curl -X POST "http://localhost:8000/classify?latitude=60.1699&longitude=24.9384" \
-H "accept: application/json" \
-H "Content-Type: multipart/form-data" \
-F "file=@<path_to_audio_file>"
```

Call with all parameters:

```bash
curl -X POST "http://localhost:8000/classify?latitude=60.1699&longitude=24.9384&threshold=0.5&include_sdm=True&include_noise=True&day_of_year=1&chunk_size=500&overlap=1" \
-H "accept: application/json" \
-H "Content-Type: multipart/form-data" \
-F "file=@<path_to_audio_file>"
```

#### Note

- Day of year can be set as a parameter, but if not, today's date is used.
File renamed without changes.
Loading