diff --git a/index.html b/index.html
index 3f050322..c5b4d467 100644
--- a/index.html
+++ b/index.html
@@ -85,7 +85,7 @@
Quick access settings panel
- Nocmig mode
- Audio filters
- - Context-aware mode
+ - Context-aware mode (Chirpity model only)
- Fullscreen mode for the spectrogram
- Which detection list to use
- And the confidence threshold
@@ -1060,7 +1060,7 @@ Set Location
blur_on
swap_horiz
+ class="chirpity-only material-symbols-outlined btn btn-outline-secondary p-1 pt-2 text-warning">swap_horiz
fullscreen
{
tf.env().set("TOPK_LAST_DIM_CPU_HANDOFF_SIZE_THRESHOLD", 0);
}
tf.enableProdMode();
+ //tf.enableDebugMode();
if (DEBUG) {
console.log(tf.env());
console.log(tf.env().getFlags());
@@ -292,10 +293,10 @@ class Model {
const finalPrediction = newPrediction || prediction;
const { indices, values } = tf.topk(finalPrediction, 5, true)
// For reasons I don't understand, the Promise.all approach is flakey: on occasion, not all predictions are returned
- // const [topIndices, topValues] = await Promise.all([indices.array(), values.array()]).catch(err => console.log('Data transfer error:',err));
- const topIndices = await indices.array();
+ const [topIndices, topValues] = await Promise.all([indices.array(), values.array()]).catch(err => console.log('Data transfer error:',err));
+//const topIndices = await indices.array();
indices.dispose();
- const topValues = await values.array();
+//const topValues = await values.array();
values.dispose();
finalPrediction.dispose();
@@ -333,7 +334,7 @@ class Model {
return tf.tidy(() => {
const sigMax = tf.max(tensor, 1, true);
const sigMin = tf.min(tensor, 1, true);
- const normalized = tensor.sub(sigMin).div(sigMax.sub(sigMin)).mul(tf.scalar(2)).sub(tf.scalar(1));
+ const normalized = tensor.sub(sigMin).divNoNan(sigMax.sub(sigMin)).mul(tf.scalar(2)).sub(tf.scalar(1));
return normalized;
})
}
@@ -346,7 +347,7 @@ class Model {
const sigMin = tf.min(signal);
const range = sigMax.sub(sigMin);
//return signal.sub(sigMin).div(range).mul(tf.scalar(8192.0, 'float32')).sub(tf.scalar(4095, 'float32'))
- return signal.sub(sigMin).div(range).mul(tf.scalar(2)).sub(tf.scalar(1))
+ return signal.sub(sigMin).divNoNan(range).mul(tf.scalar(2)).sub(tf.scalar(1))
})
};
async predictChunk(audioBuffer, start, fileStart, file, threshold, confidence) {
diff --git a/js/ui.js b/js/ui.js
index 59c5b34a..51c03944 100644
--- a/js/ui.js
+++ b/js/ui.js
@@ -1355,7 +1355,16 @@ function updatePrefs() {
}
}
-
+function fillDefaults(config, defaultConfig) {
+ Object.keys(defaultConfig).forEach(key => {
+ if (!(key in config)) {
+ config[key] = defaultConfig[key];
+ } else if (typeof config[key] === 'object' && typeof defaultConfig[key] === 'object') {
+ // Recursively fill in defaults for nested objects
+ fillDefaults(config[key], defaultConfig[key]);
+ }
+ });
+}
///////////////////////// Window Handlers ////////////////////////////
let appPath, tempPath;
window.onload = async () => {
@@ -1369,7 +1378,7 @@ window.onload = async () => {
UUID: uuidv4(),
locale: 'en_uk',
colormap: 'inferno',
- timeOfDay: false,
+ timeOfDay: true,
list: 'nocturnal',
local: true,
speciesThreshold: 0.03,
@@ -1404,11 +1413,8 @@ window.onload = async () => {
}
//fill in defaults - after updates add new items
- Object.keys(defaultConfig).forEach(key => {
- if (!(key in config)) {
- config[key] = defaultConfig[key];
- }
- });
+ fillDefaults(config, defaultConfig);
+
// Update model if old models in config
if (!['chirpity', 'v3', 'v4', 'birdnet'].includes(config.model)) {
config.model = config.model === 'v2.4' ? 'birdnet' : 'chirpity';
@@ -4053,11 +4059,11 @@ DOM.gain.addEventListener('input', () => {
document.addEventListener('click', function (e) {
- const target = e.target.closest('[id]').id;
+ const target = e.target.closest('[id]')?.id;
contextMenu.classList.add("d-none");
hideConfidenceSlider();
config.debug && console.log('clicked', target);
- target !== 'result1' && track('UI', 'Click', target);
+ target && target !== 'result1' && track('UI', 'Click', target);
})
diff --git a/js/worker.js b/js/worker.js
index cd775a33..63a034d8 100644
--- a/js/worker.js
+++ b/js/worker.js
@@ -818,7 +818,7 @@ const prepSummaryStatement = (included) => {
filesBeingProcessed = [...FILE_QUEUE];
- for (let i = 0; i < NUM_WORKERS; i++) {
+ for (let i = 0; i < filesBeingProcessed.length; i++) {
processNextFile({ start: start, end: end, worker: i });
}
}
@@ -1314,14 +1314,13 @@ const prepSummaryStatement = (included) => {
return
}
const offlineCtx = await setupCtx(chunk, metadata[file].header);
+ let worker;
if (offlineCtx) {
offlineCtx.startRendering().then((resampled) => {
const myArray = resampled.getChannelData(0);
- if (++workerInstance >= NUM_WORKERS) {
- workerInstance = 0;
- }
- let worker = workerInstance;
+ workerInstance = ++workerInstance >= NUM_WORKERS ? 0 : workerInstance;
+ worker = workerInstance;
feedChunksToModel(myArray, chunkStart, file, end, worker);
chunkStart += WINDOW_SIZE * BATCH_SIZE * sampleRate;
// Now the async stuff is done ==>
@@ -1336,12 +1335,9 @@ const prepSummaryStatement = (included) => {
});
} else {
console.log('Short chunk', chunk.length, 'skipping')
- if (worker === undefined) {
- if (++workerInstance >= NUM_WORKERS) {
- workerInstance = 0;
- }
- worker = workerInstance;
- }
+ workerInstance = ++workerInstance >= NUM_WORKERS ? 0 : workerInstance;
+ worker = workerInstance;
+
// Create array with 0's (short segment of silence that will trigger the finalChunk flag
const myArray = new Float32Array(Array.from({length: chunkLength}).fill(0));
feedChunksToModel(myArray, chunkStart, file, end);
@@ -1350,6 +1346,7 @@ const prepSummaryStatement = (included) => {
})
readStream.on('end', function () {
readStream.close();
+ console.log('All chunks sent for ', file)
})
readStream.on('error', err => {
console.log(`readstream error: ${err}, start: ${start}, , end: ${end}, duration: ${metadata[file].duration}`);
@@ -1438,7 +1435,6 @@ const prepSummaryStatement = (included) => {
};
predictWorkers[worker].isAvailable = false;
predictWorkers[worker].postMessage(objData, [channelData.buffer]);
-
}
async function doPrediction({
@@ -1794,7 +1790,9 @@ const prepSummaryStatement = (included) => {
worker: i
})
worker.onmessage = async (e) => {
- await parseMessage(e)
+ await parseMessage(e).catch(error => {
+ console.warn("Parse message error", error, 'e was', e)
+ })
}
worker.onerror = (e) => {
console.warn(`Worker ${i} is suffering, shutting it down. THe error was:`, e)
@@ -1966,7 +1964,7 @@ const prepSummaryStatement = (included) => {
}
// Remove the trailing comma and space
insertQuery = insertQuery.slice(0, -2);
- DEBUG && console.log(insertQuery);
+ //DEBUG && console.log(insertQuery);
// Make sure we have some values to INSERT
insertQuery.endsWith(')') && await db.runAsync(insertQuery);
return fileID
@@ -1974,10 +1972,10 @@ const prepSummaryStatement = (included) => {
const parsePredictions = async (response) => {
let file = response.file;
- const included = await getIncludedIDs(file);
+ const included = await getIncludedIDs(file).catch(error => console.log('Error getting included IDs', error));
const latestResult = response.result, db = STATE.db;
DEBUG && console.log('worker being used:', response.worker);
- if (! STATE.selection) await generateInsertQuery(latestResult, file);
+ if (! STATE.selection) await generateInsertQuery(latestResult, file).catch(error => console.log('Error generating insert query', error));
let [keysArray, speciesIDBatch, confidenceBatch] = latestResult;
for (let i = 0; i < keysArray.length; i++) {
let updateUI = false;
@@ -2003,7 +2001,7 @@ const prepSummaryStatement = (included) => {
confidenceRequired = STATE.detect.confidence;
}
if (confidence >= confidenceRequired) {
- const { cname } = await memoryDB.getAsync(`SELECT cname FROM species WHERE id = ${speciesID}`);
+ const { cname } = await memoryDB.getAsync(`SELECT cname FROM species WHERE id = ${speciesID}`).catch(error => console.log('Error getting species name', error));
const result = {
timestamp: timestamp,
position: key,
@@ -2041,6 +2039,7 @@ const prepSummaryStatement = (included) => {
});
}
})
+ .catch(error => console.log('Error generating new result', error))
}
updateFilesBeingProcessed(response.file)
console.log(`File ${file} processed after ${(new Date() - predictionStart) / 1000} seconds: ${filesBeingProcessed.length} files to go`);
@@ -2075,9 +2074,10 @@ const prepSummaryStatement = (included) => {
case "prediction": {
if ( !aborted) {
predictWorkers[response.worker].isAvailable = true;
- let worker = await parsePredictions(response);
- DEBUG && console.log('predictions left for', response.file, predictionsReceived[response.file] - predictionsRequested[response.file])
- if (predictionsReceived[response.file] === predictionsRequested[response.file]) {
+ let worker = await parsePredictions(response).catch(error => console.log('Error parsing predictions', error));
+ console.log('predictions left for', response.file, predictionsReceived[response.file] - predictionsRequested[response.file])
+ const remaining = predictionsReceived[response.file] - predictionsRequested[response.file]
+ if (remaining === 0) {
const limit = 10;
clearCache(CACHE_LOCATION, limit);
if (filesBeingProcessed.length) {
@@ -2271,7 +2271,7 @@ const prepSummaryStatement = (included) => {
const params = getSummaryParams(included);
const summary = await STATE.GET_SUMMARY_SQL.allAsync(...params);
- DEBUG && console.log("Get Summary took", (Date.now() - t0) / 1000, "seconds");
+ //DEBUG && console.log("Get Summary took", (Date.now() - t0) / 1000, "seconds");
const event = interim ? 'update-summary' : 'summary-complate';
UI.postMessage({
event: event,
@@ -3038,7 +3038,7 @@ const prepSummaryStatement = (included) => {
await setIncludedIDs(lat,lon,week);
hitOrMiss = 'miss';
}
- DEBUG && console.log(`Cache ${hitOrMiss}: setting the ${STATE.list} list took ${Date.now() -t0}ms`)
+ //DEBUG && console.log(`Cache ${hitOrMiss}: setting the ${STATE.list} list took ${Date.now() -t0}ms`)
return STATE.included[STATE.model][STATE.list];
}
}
diff --git a/package.json b/package.json
index 1a991194..2daeaa87 100644
--- a/package.json
+++ b/package.json
@@ -203,7 +203,7 @@
"ffmpeg-static-electron": "^2.0.3",
"fluent-ffmpeg": "^2.1.2",
"lodash.merge": "^4.6.2",
- "sqlite3": "^5.1.6",
+ "sqlite3": "^5.1.7",
"suncalc": "^1.9.0",
"utimes": "^5.1.1",
"uuid": "^8.3.2",