Add spectro webpage (not the audio of the server yet) & dataminer script
This commit is contained in:
parent
0399fa085e
commit
6b37e1cfc5
|
@ -7,7 +7,7 @@ LOCATION="Maison ORTION - Saint-Gervais-en-Belin"
|
|||
# Species selection list
|
||||
SPECIES_LIST="./config/species_list.txt"
|
||||
# Minimal confidence threshold
|
||||
CONFIDENCE=0.25
|
||||
CONFIDENCE=0.1
|
||||
# Recording duration (in seconds)
|
||||
RECORDING_DURATION=15
|
||||
# Chunk folder location
|
||||
|
|
|
@ -28,9 +28,9 @@ mem() {
|
|||
string=$2
|
||||
substring=$1
|
||||
if [[ "$string" == *"$substring"* ]]; then
|
||||
echo "true"
|
||||
true
|
||||
else
|
||||
echo "false"
|
||||
false
|
||||
fi
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ junk() {
|
|||
# Get all empty treatment directories
|
||||
junk="$junk $(find ${CHUNK_FOLDER}/out -type d -empty)"
|
||||
# Get all empty record directories
|
||||
treatement_folder=$(find "${CHUNK_FOLDER}/out" -type d ! -empty)
|
||||
treatement_folder=$(find "${CHUNK_FOLDER}/out/*" -type d ! -empty)
|
||||
if [[ ! -z ${treatement_folder} ]]; then
|
||||
for folder in $treatement_folder; do
|
||||
echo $folder
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
#! /usr/bin/env bash
|
||||
# Extract observations from a model output folder
|
||||
#
|
||||
|
||||
DEBUG=${DEBUG:-0}
|
||||
|
||||
set -e
|
||||
|
||||
debug() {
|
||||
if [ $DEBUG -eq 1 ]; then
|
||||
echo "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
# Load bash library to deal with BirdNET-stream database
|
||||
source ./daemon/database/scripts/database.sh
|
||||
|
||||
# Load config
|
||||
source ./config/analyzer.conf
|
||||
# Check config
|
||||
if [[ -z ${CHUNK_FOLDER} ]]; then
|
||||
echo "CHUNK_FOLDER is not set"
|
||||
exit 1
|
||||
else
|
||||
if [[ ! -d ${CHUNK_FOLDER}/out ]]; then
|
||||
echo "CHUNK_FOLDER does not exist: ${CHUNK_FOLDER}/out"
|
||||
echo "Cannot extract observations."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z ${LATITUDE} ]]; then
|
||||
echo "LATITUDE is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z ${LONGITUDE} ]]; then
|
||||
echo "LONGITUDE is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
model_outputs() {
|
||||
find -name "model.out.csv" -type f ! -empty
|
||||
}
|
||||
|
||||
source_wav() {
|
||||
model_output_path=$1
|
||||
model_output_dir=$(dirname $model_output_path)
|
||||
source_wav=$(basename $model_output_dir | rev | cut --complement -d"." -f1 | rev)
|
||||
echo $source_wav
|
||||
}
|
||||
|
||||
record_datetime() {
|
||||
source_wav=$1
|
||||
source_base=$(basename $source_wav .wav)
|
||||
record_date=$(echo $source_base | cut -d"_" -f2)
|
||||
record_time=$(echo $source_base | cut -d"_" -f3)
|
||||
YYYY=$(echo $record_date | cut -c 1-4)
|
||||
MM=$(echo $record_date | cut -c 5-6)
|
||||
DD=$(echo $record_date | cut -c 7-8)
|
||||
HH=$(echo $record_time | cut -c 1-2)
|
||||
MM=$(echo $record_time | cut -c 3-4)
|
||||
SS=$(echo $record_time | cut -c 5-6)
|
||||
SSS="000"
|
||||
date="$YYYY-$MM-$DD $HH:$MM:$SS.$SSS"
|
||||
echo $date
|
||||
}
|
||||
|
||||
save_observations() {
|
||||
model_output_path=$1
|
||||
source_audio=$(source_wav $model_output_path)
|
||||
debug "Audio source: $source_audio"
|
||||
observations=$(cat $model_output_path | tail -n +2)
|
||||
IFS=$'\n'
|
||||
for observation in $observations; do
|
||||
if [[ -z "$observation" ]]; then
|
||||
continue
|
||||
fi
|
||||
# debug "Observation: $observation"
|
||||
start=$(echo "$observation" | cut -d"," -f1)
|
||||
end=$(echo "$observation" | cut -d"," -f2)
|
||||
scientific_name=$(echo "$observation" | cut -d"," -f3)
|
||||
common_name=$(echo "$observation" | cut -d"," -f4)
|
||||
confidence=$(echo "$observation" | cut -d"," -f5)
|
||||
debug "Observation: $scientific_name ($common_name) from $start to $end with confidence $confidence"
|
||||
taxon_id=$(get_taxon_id "$scientific_name")
|
||||
if [[ -z $taxon_id ]]; then
|
||||
debug "Taxon not found: $scientific_name"
|
||||
debug "Inserting taxon..."
|
||||
insert_taxon "$scientific_name" "$common_name"
|
||||
taxon_id=$(get_taxon_id "$scientific_name")
|
||||
fi
|
||||
location_id=$(get_location_id "$LATITUDE" "$LONGITUDE")
|
||||
if [[ -z $location_id ]]; then
|
||||
debug "Location not found: $LATITUDE, $LONGITUDE"
|
||||
debug "Inserting location..."
|
||||
insert_location "$LATITUDE" "$LONGITUDE"
|
||||
location_id=$(get_location_id "$LATITUDE" "$LONGITUDE")
|
||||
fi
|
||||
datetime=$(record_datetime $source_audio)
|
||||
if [[ $(observation_exists "$source_audio" "$start" "$end" "$taxon_id" "$location_id") = "true" ]]; then
|
||||
debug "Observation already exists: $source_audio, $start, $end, $taxon_id, $location_id"
|
||||
exit 1
|
||||
else
|
||||
debug "Inserting observation: $source_audio, $start, $end, $taxon_id, $location_id"
|
||||
insert_observation "$source_audio" "$start" "$end" "$taxon_id" "$location_id" "$confidence" "$datetime"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
main() {
|
||||
# Remove all junk observations
|
||||
./daemon/birdnet_clean.sh
|
||||
# Get model outputs
|
||||
for model_output in $(model_outputs); do
|
||||
save_observations $model_output
|
||||
done
|
||||
}
|
||||
|
||||
main
|
|
@ -32,7 +32,7 @@ record() {
|
|||
DEVICE=$1
|
||||
DURATION=$2
|
||||
debug "Recording from $DEVICE for $DURATION seconds"
|
||||
echo "" | ffmpeg -nostdin -f pulse -i ${DEVICE} -t ${DURATION} -vn -acodec pcm_s16le -ac 1 -ar 48000 file:${CHUNK_FOLDER}/in/birdnet_$(date "+%Y%m%d_%H%M%S").wav
|
||||
ffmpeg -nostdin -f pulse -i ${DEVICE} -t ${DURATION} -vn -acodec pcm_s16le -ac 1 -ar 48000 file:${CHUNK_FOLDER}/in/birdnet_$(date "+%Y%m%d_%H%M%S").wav
|
||||
}
|
||||
|
||||
config_filepath="./config/analyzer.conf"
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
#! /usr/bin/env bash
|
||||
# SQLite library to deal with BirdNET-stream database
|
||||
|
||||
set -e
|
||||
|
||||
source ./config/analyzer.conf
|
||||
|
||||
# Create database in case it was not created yet
|
||||
./daemon/database/scripts/create.sh
|
||||
|
||||
DATABASE=${DATABASE:-"./var/db.sqlite"}
|
||||
|
||||
get_location_id() {
|
||||
sqlite3 $DATABASE "SELECT location_id FROM location WHERE latitude=$1 AND longitude=$2"
|
||||
}
|
||||
|
||||
get_taxon_id() {
|
||||
sqlite3 $DATABASE "SELECT taxon_id FROM taxon WHERE scientific_name='$1'"
|
||||
}
|
||||
|
||||
insert_taxon() {
|
||||
sqlite3 $DATABASE "INSERT INTO taxon (scientific_name, common_name) VALUES ('$1', '$2')"
|
||||
}
|
||||
|
||||
insert_location() {
|
||||
sqlite3 $DATABASE "INSERT INTO location (latitude, longitude) VALUES ($1, $2)"
|
||||
}
|
||||
|
||||
insert_observation() {
|
||||
sqlite3 $DATABASE "INSERT INTO observation (audio_file, start, end, taxon_id, location_id, confidence, date) VALUES ('$1', '$2', '$3', '$4', '$5', '$6', '$7')"
|
||||
}
|
||||
|
||||
# Check if the observation already exists in the database
|
||||
observation_exists() {
|
||||
sqlite3 $DATABASE "SELECT EXISTS(SELECT observation_id FROM observation WHERE audio_file='$1' AND start='$2' AND end='$3' AND taxon_id='$4' AND location_id='$5')"
|
||||
}
|
|
@ -7,23 +7,24 @@ CREATE TABLE IF NOT EXISTS taxon (
|
|||
common_name TEXT NOT NULL
|
||||
);
|
||||
|
||||
/** Locality table */
|
||||
CREATE TABLE IF NOT EXISTS locality (
|
||||
locality_id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
/** Location table */
|
||||
CREATE TABLE IF NOT EXISTS location (
|
||||
location_id INTEGER PRIMARY KEY,
|
||||
latitude REAL NOT NULL,
|
||||
longitude REAL NOT NULL
|
||||
);
|
||||
|
||||
/** Observation table */
|
||||
CREATE TABLE IF NOT EXISTS observation (
|
||||
observation_id INTEGER PRIMARY KEY,
|
||||
taxon_id INTEGER NOT NULL,
|
||||
locality_id INTEGER NOT NULL,
|
||||
date TEXT NOT NULL,
|
||||
time TEXT NOT NULL,
|
||||
notes TEXT,
|
||||
confidence REAL NOT NULL,
|
||||
`observation_id` INTEGER PRIMARY KEY,
|
||||
`audio_file` TEXT NOT NULL,
|
||||
`start` REAL NOT NULL,
|
||||
`end` REAL NOT NULL,
|
||||
`taxon_id` INTEGER NOT NULL,
|
||||
`location_id` INTEGER NOT NULL,
|
||||
`date` TEXT NOT NULL,
|
||||
`notes` TEXT,
|
||||
`confidence` REAL NOT NULL,
|
||||
FOREIGN KEY(taxon_id) REFERENCES taxon(taxon_id),
|
||||
FOREIGN KEY(locality_id) REFERENCES locality(locality_id)
|
||||
FOREIGN KEY(location_id) REFERENCES location(location_id)
|
||||
);
|
10
install.sh
10
install.sh
|
@ -43,12 +43,12 @@ install_birdnetstream() {
|
|||
workdir=$(pwd)
|
||||
if [ -d "$workdir/BirdNET-stream" ]; then
|
||||
debug "BirdNET-stream is already installed"
|
||||
return
|
||||
else
|
||||
# Clone BirdNET-stream
|
||||
debug "Cloning BirdNET-stream from $REPOSITORY"
|
||||
git clone --recurse-submodules $REPOSITORY
|
||||
# Install BirdNET-stream
|
||||
fi
|
||||
# Clone BirdNET-stream
|
||||
debug "Cloning BirdNET-stream from $REPOSITORY"
|
||||
git clone --recurse-submodules $REPOSITORY
|
||||
# Install BirdNET-stream
|
||||
cd BirdNET-stream
|
||||
debug "Creating python3 virtual environment '$PYTHON_VENV'"
|
||||
python3 -m venv $PYTHON_VENV
|
||||
|
|
|
@ -10,3 +10,5 @@ import './styles/app.css';
|
|||
|
||||
// start the Stimulus application
|
||||
import './bootstrap';
|
||||
|
||||
import './utils/spectro';
|
|
@ -81,3 +81,9 @@ nav ul li a:hover {
|
|||
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* Credits to:
|
||||
* https://codepen.io/jakealbaugh/pen/jvQweW
|
||||
*/
|
||||
|
||||
// UPDATE: there is a problem in chrome with starting audio context
|
||||
// before a user gesture. This fixes it.
|
||||
var started = false;
|
||||
var spectro_button = document.getElementById('spectro-button');
|
||||
spectro_button.addEventListener('click', () => {
|
||||
if (started) return;
|
||||
started = true;
|
||||
console.log("starting spectro");
|
||||
initialize();
|
||||
})
|
||||
|
||||
function initialize() {
|
||||
const CVS = document.getElementById('spectro-canvas');
|
||||
const CTX = CVS.getContext('2d');
|
||||
const W = CVS.width = window.innerWidth;
|
||||
const H = CVS.height = window.innerHeight;
|
||||
|
||||
const ACTX = new AudioContext();
|
||||
const ANALYSER = ACTX.createAnalyser();
|
||||
|
||||
ANALYSER.fftSize = 4096;
|
||||
|
||||
navigator.mediaDevices
|
||||
.getUserMedia({ audio: true })
|
||||
.then(process);
|
||||
|
||||
function process(stream) {
|
||||
const SOURCE = ACTX.createMediaStreamSource(stream);
|
||||
SOURCE.connect(ANALYSER);
|
||||
const DATA = new Uint8Array(ANALYSER.frequencyBinCount);
|
||||
const LEN = DATA.length;
|
||||
const h = H / LEN;
|
||||
const x = W - 1;
|
||||
CTX.fillStyle = 'hsl(280, 100%, 10%)';
|
||||
CTX.fillRect(0, 0, W, H);
|
||||
|
||||
loop();
|
||||
|
||||
function loop() {
|
||||
window.requestAnimationFrame(loop);
|
||||
let imgData = CTX.getImageData(1, 0, W - 1, H);
|
||||
CTX.fillRect(0, 0, W, H);
|
||||
CTX.putImageData(imgData, 0, 0);
|
||||
ANALYSER.getByteFrequencyData(DATA);
|
||||
for (let i = 0; i < LEN; i++) {
|
||||
let rat = DATA[i] / 255;
|
||||
let hue = Math.round((rat * 120) + 280 % 360);
|
||||
let sat = '100%';
|
||||
let lit = 10 + (70 * rat) + '%';
|
||||
CTX.beginPath();
|
||||
CTX.strokeStyle = `hsl(${hue}, ${sat}, ${lit})`;
|
||||
CTX.moveTo(x, H - (i * h));
|
||||
CTX.lineTo(x, H - (i * h + h));
|
||||
CTX.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,9 +4,8 @@ server {
|
|||
root /var/www/html;
|
||||
|
||||
location / {
|
||||
return 302 https://$host$request_uri;
|
||||
return 302 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
server {
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
// src/Controller/AboutController.php
|
||||
namespace App\Controller;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
|
||||
class SpectroController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @Route("/spectro", name="spectro")
|
||||
*/
|
||||
public function about()
|
||||
{
|
||||
return $this->render('spectro/index.html.twig', [
|
||||
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{% extends "base.html.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{{ "Spectrogram" | trans }}</h2>
|
||||
<div>
|
||||
<button id="spectro-button">{{ "Launch Live Spectrogram" | trans }}</button>
|
||||
<canvas id="spectro-canvas"></canvas>
|
||||
</div>
|
||||
{% endblock %}
|
Loading…
Reference in New Issue