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 selection list
|
||||||
SPECIES_LIST="./config/species_list.txt"
|
SPECIES_LIST="./config/species_list.txt"
|
||||||
# Minimal confidence threshold
|
# Minimal confidence threshold
|
||||||
CONFIDENCE=0.25
|
CONFIDENCE=0.1
|
||||||
# Recording duration (in seconds)
|
# Recording duration (in seconds)
|
||||||
RECORDING_DURATION=15
|
RECORDING_DURATION=15
|
||||||
# Chunk folder location
|
# Chunk folder location
|
||||||
|
|
|
@ -28,9 +28,9 @@ mem() {
|
||||||
string=$2
|
string=$2
|
||||||
substring=$1
|
substring=$1
|
||||||
if [[ "$string" == *"$substring"* ]]; then
|
if [[ "$string" == *"$substring"* ]]; then
|
||||||
echo "true"
|
true
|
||||||
else
|
else
|
||||||
echo "false"
|
false
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ junk() {
|
||||||
# Get all empty treatment directories
|
# Get all empty treatment directories
|
||||||
junk="$junk $(find ${CHUNK_FOLDER}/out -type d -empty)"
|
junk="$junk $(find ${CHUNK_FOLDER}/out -type d -empty)"
|
||||||
# Get all empty record directories
|
# 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
|
if [[ ! -z ${treatement_folder} ]]; then
|
||||||
for folder in $treatement_folder; do
|
for folder in $treatement_folder; do
|
||||||
echo $folder
|
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
|
DEVICE=$1
|
||||||
DURATION=$2
|
DURATION=$2
|
||||||
debug "Recording from $DEVICE for $DURATION seconds"
|
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"
|
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
|
common_name TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
/** Locality table */
|
/** Location table */
|
||||||
CREATE TABLE IF NOT EXISTS locality (
|
CREATE TABLE IF NOT EXISTS location (
|
||||||
locality_id INTEGER PRIMARY KEY,
|
location_id INTEGER PRIMARY KEY,
|
||||||
name TEXT NOT NULL,
|
|
||||||
latitude REAL NOT NULL,
|
latitude REAL NOT NULL,
|
||||||
longitude REAL NOT NULL
|
longitude REAL NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
/** Observation table */
|
/** Observation table */
|
||||||
CREATE TABLE IF NOT EXISTS observation (
|
CREATE TABLE IF NOT EXISTS observation (
|
||||||
observation_id INTEGER PRIMARY KEY,
|
`observation_id` INTEGER PRIMARY KEY,
|
||||||
taxon_id INTEGER NOT NULL,
|
`audio_file` TEXT NOT NULL,
|
||||||
locality_id INTEGER NOT NULL,
|
`start` REAL NOT NULL,
|
||||||
date TEXT NOT NULL,
|
`end` REAL NOT NULL,
|
||||||
time TEXT NOT NULL,
|
`taxon_id` INTEGER NOT NULL,
|
||||||
notes TEXT,
|
`location_id` INTEGER NOT NULL,
|
||||||
confidence REAL NOT NULL,
|
`date` TEXT NOT NULL,
|
||||||
|
`notes` TEXT,
|
||||||
|
`confidence` REAL NOT NULL,
|
||||||
FOREIGN KEY(taxon_id) REFERENCES taxon(taxon_id),
|
FOREIGN KEY(taxon_id) REFERENCES taxon(taxon_id),
|
||||||
FOREIGN KEY(locality_id) REFERENCES locality(locality_id)
|
FOREIGN KEY(location_id) REFERENCES location(location_id)
|
||||||
);
|
);
|
|
@ -43,12 +43,12 @@ install_birdnetstream() {
|
||||||
workdir=$(pwd)
|
workdir=$(pwd)
|
||||||
if [ -d "$workdir/BirdNET-stream" ]; then
|
if [ -d "$workdir/BirdNET-stream" ]; then
|
||||||
debug "BirdNET-stream is already installed"
|
debug "BirdNET-stream is already installed"
|
||||||
return
|
else
|
||||||
fi
|
|
||||||
# Clone BirdNET-stream
|
# Clone BirdNET-stream
|
||||||
debug "Cloning BirdNET-stream from $REPOSITORY"
|
debug "Cloning BirdNET-stream from $REPOSITORY"
|
||||||
git clone --recurse-submodules $REPOSITORY
|
git clone --recurse-submodules $REPOSITORY
|
||||||
# Install BirdNET-stream
|
# Install BirdNET-stream
|
||||||
|
fi
|
||||||
cd BirdNET-stream
|
cd BirdNET-stream
|
||||||
debug "Creating python3 virtual environment '$PYTHON_VENV'"
|
debug "Creating python3 virtual environment '$PYTHON_VENV'"
|
||||||
python3 -m venv $PYTHON_VENV
|
python3 -m venv $PYTHON_VENV
|
||||||
|
|
|
@ -10,3 +10,5 @@ import './styles/app.css';
|
||||||
|
|
||||||
// start the Stimulus application
|
// start the Stimulus application
|
||||||
import './bootstrap';
|
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);
|
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
|
||||||
z-index: 1;
|
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;
|
root /var/www/html;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
return 302 https://$host$request_uri;
|
return 302 https://$server_name$request_uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
server {
|
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