From 077996a1e4fefcd9e8c3853139e5099b63ab0d65 Mon Sep 17 00:00:00 2001 From: Mattk70 Date: Wed, 22 Nov 2023 20:20:29 +0000 Subject: [PATCH] Summary update every record renamed 'prediction-ongoing' 'new-result' renamed 'prediction-done' 'summary-complete' refactored onpredictiondone into 2 functions: onResultsComplete onSummaryComplete --- js/ui.js | 171 ++++++++++++++++++++------------------------------- js/worker.js | 60 +++++++++--------- 2 files changed, 99 insertions(+), 132 deletions(-) diff --git a/js/ui.js b/js/ui.js index cb0ee5c6..102818d6 100644 --- a/js/ui.js +++ b/js/ui.js @@ -1020,6 +1020,7 @@ exploreLink.addEventListener('click', async () => { adjustSpecDims(true) worker.postMessage({ action: 'update-state', globalOffset: 0, filteredOffset: {}}); worker.postMessage({ action: 'filter', species: undefined, range: STATE.explore.range, updateSummary: true }); + resetResults(true, true) }); const datasetLink = document.getElementById('dataset'); @@ -1474,19 +1475,13 @@ const setUpWorkerMessaging = () => { const args = e.data; const event = args.event; switch (event) { - case 'chart-data': - onChartData(args); - break; + case 'chart-data': onChartData(args); break; case 'diskDB-has-records': chartsLink.classList.remove('disabled'); exploreLink.classList.remove('disabled'); break; - case 'file-location-id': - onFileLocationID(args); - break; - case 'files': - onOpenFiles(args); - break; + case 'file-location-id': onFileLocationID(args); break; + case 'files': onOpenFiles(args); break; case 'generate-alert': if (args.updateFilenamePanel) { renderFilenamePanel(); @@ -1502,7 +1497,7 @@ const setUpWorkerMessaging = () => { worker.postMessage({ action: 'filter', species: isSpeciesViewFiltered(true), - active: getActiveRowID(), + active: args.active, updateSummary: true }); // no re-prepare resetResults(true, false) @@ -1512,61 +1507,38 @@ const setUpWorkerMessaging = () => { } break; case 'results-complete': - const table = document.getElementById('resultTableBody') - table.replaceWith(resultsBuffer); - //document.getElementById('resultsDiv').scrollTo({ top: 0, left: 0, behavior: "smooth" }); + onResultsComplete(args); hideLoadingSpinner(); break; case 'location-list': LOCATIONS = args.locations; locationID = args.currentLocation; break; - case 'model-ready': - onModelReady(args); - break; + case 'model-ready': onModelReady(args); break; case 'mode-changed': STATE.mode = args.mode; // Update the current file name in the UI renderFilenamePanel(); - console.log('Mode changed to: ' + args.mode); - break; - case 'prediction-done': - onPredictionDone(args); - break; - case 'prediction-ongoing': - renderResult(args); - break; - case 'progress': - onProgress(args); - break; - case 'seen-species-list': - generateBirdList('seenSpecies', args.list); - break; - case 'show-spinner': - showLoadingSpinner(500); - break; - case 'spawning': - displayWarmUpMessage(); - break; - case 'total-records': - updatePagination(args.total, args.offset); + config.debug && console.log('Mode changed to: ' + args.mode); break; + case 'summary-complate': onSummaryComplete(args); break; + case 'new-result': renderResult(args); break; + case 'progress': onProgress(args); break; + case 'seen-species-list': generateBirdList('seenSpecies', args.list); break; + case 'show-spinner': showLoadingSpinner(500); break; + case 'spawning': displayWarmUpMessage(); break; + case 'total-records': updatePagination(args.total, args.offset); break; case 'unsaved-records': window.electron.unsavedRecords(true); document.getElementById('unsaved-icon').classList.remove('d-none'); - case 'update-audio-duration': - DIAGNOSTICS['Audio Duration'] ? - DIAGNOSTICS['Audio Duration'] += args.value : - DIAGNOSTICS['Audio Duration'] = args.value; - break; - case 'update-summary': - updateSummary(args); break; - case 'worker-loaded-audio': - onWorkerLoadedAudio(args); + case 'update-audio-duration': + DIAGNOSTICS['Audio Duration'] ??= 0; + DIAGNOSTICS['Audio Duration'] += args.value; break; - default: - alert(`Unrecognised message from worker:${args.event}`) + case 'update-summary': updateSummary(args); break; + case 'worker-loaded-audio': onWorkerLoadedAudio(args); break; + default: alert(`Unrecognised message from worker:${args.event}`) } }) }) @@ -2604,60 +2576,20 @@ const updateSummary = ({ summary = [], filterSpecies = '' }) => { if (currentFilter) currentFilter.focus(); } -async function onPredictionDone({ - filterSpecies = undefined, - batchInProgress = false, - audacityLabels = {}, - file = undefined, - summary = [], - active = undefined, - offset = 0, - action = undefined -}) { - // Reset daylight banner - shownDaylightBanner = false; - AUDACITY_LABELS = audacityLabels; - enableMenuItem(['save2db', 'export2audio']); - // Defer further processing until batch complete - if (batchInProgress) { - progressDiv.classList.remove('d-none'); - return; - } else { - PREDICTING = false; - } - if (resultsBuffer) { - const results = document.getElementById('resultTableBody'); - results.replaceWith(resultsBuffer); - // resultsBuffer = undefined; - } - - updateSummary({ summary: summary, filterSpecies: filterSpecies }); - - - if (action !== 'filter') { - if (! isEmptyObject(AUDACITY_LABELS)) { - enableMenuItem(['saveLabels', 'saveCSV']); - $('.download').removeClass('disabled'); - } else { - disableMenuItem(['saveLabels', 'saveCSV']); - } - if (currentFile) enableMenuItem(['analyse']) - - // DIAGNOSTICS: - t1_analysis = Date.now(); - const analysisTime = ((t1_analysis - t0_analysis) / 1000).toFixed(2); - DIAGNOSTICS['Analysis Duration'] = analysisTime + ' seconds'; - DIAGNOSTICS['Analysis Rate'] = (DIAGNOSTICS['Audio Duration'] / analysisTime).toFixed(0) + 'x faster than real time performance.'; - } - - +/* +onResultsComplete is called when the last result is sent +*/ +function onResultsComplete({active = undefined} = {}){ + let table = document.getElementById('resultTableBody'); + table.replaceWith(resultsBuffer); + table = document.getElementById('resultTableBody'); + PREDICTING = false; // Set active Row - const resultTable = document.getElementById('resultTableBody'); if (active) { // Refresh node and scroll to active row: - activeRow = resultTable.rows[active]; + activeRow = table.rows[active]; if (! activeRow) { // because: after an edit the active row may not exist - const rows = resultTable.querySelectorAll('tr.daytime, tr.nighttime') + const rows = table.querySelectorAll('tr.daytime, tr.nighttime') if (rows.length) { activeRow = rows[rows.length - 1]; } @@ -2665,19 +2597,51 @@ async function onPredictionDone({ activeRow.classList.add('table-active'); } } - else if (STATE.mode === 'analyse') { - activeRow = resultTable.querySelector('.table-active'); + else { // if (STATE.mode === 'analyse') { + activeRow = table.querySelector('.table-active'); if (!activeRow) { // Select the first row - activeRow = resultTable.querySelector('tr:first-child'); + activeRow = table.querySelector('tr:first-child'); + activeRow.classList.add('table-active'); + document.getElementById('resultsDiv').scrollTo({ top: 0, left: 0, behavior: "smooth" }); } } + if (!batchInProgress && activeRow) { activeRow.focus(); activeRow.click(); } + // DIAGNOSTICS: + t1_analysis = Date.now(); + const analysisTime = ((t1_analysis - t0_analysis) / 1000).toFixed(2); + DIAGNOSTICS['Analysis Duration'] = analysisTime + ' seconds'; + DIAGNOSTICS['Analysis Rate'] = (DIAGNOSTICS['Audio Duration'] / analysisTime).toFixed(0) + 'x faster than real time performance.'; +} + + + +/* +onSummaryComplete is called when getSummary finishes. +*/ +function onSummaryComplete({ + filterSpecies = undefined, + audacityLabels = {}, + summary = [], + active = undefined, +}) { + updateSummary({ summary: summary, filterSpecies: filterSpecies }); + // Why do we do audacity labels here? + AUDACITY_LABELS = audacityLabels; + if (! isEmptyObject(AUDACITY_LABELS)) { + enableMenuItem(['saveLabels', 'saveCSV', 'save2db', 'export2audio']); + $('.download').removeClass('disabled'); + } else { + disableMenuItem(['saveLabels', 'saveCSV']); + } + if (currentFile) enableMenuItem(['analyse']) } + const pagination = document.querySelectorAll('.pagination'); pagination.forEach(item => { item.addEventListener('click', (e) => { @@ -3912,7 +3876,8 @@ const insertManualRecord = (cname, start, end, comment, count, label, action, ba label: label, DBaction: action, batch: batch, - confidence: confidence + confidence: confidence, + active: activeRow.rowIndex - 1 }) } diff --git a/js/worker.js b/js/worker.js index cd10b21e..7c06faea 100644 --- a/js/worker.js +++ b/js/worker.js @@ -20,7 +20,7 @@ let workerInstance = 0; let TEMP, appPath, CACHE_LOCATION, BATCH_SIZE, LABELS, BACKEND, batchChunksToSend = {}; let SEEN_LIST_UPDATE = false // Prevents list updates from every worker on every change -const DEBUG = true; +const DEBUG = false; const DATASET = true; const adding_chirpity_additions = true; @@ -73,15 +73,15 @@ const createDB = async (file) => { UNIQUE (dateTime, fileID, speciesID), CONSTRAINT fk_files FOREIGN KEY (fileID) REFERENCES files(id) ON DELETE CASCADE, - FOREIGN KEY (speciesID) REFERENCES species(id))`); + FOREIGN KEY (speciesID) REFERENCES species(id))`); await db.runAsync(`CREATE TABLE duration( day INTEGER, duration INTEGER, fileID INTEGER, UNIQUE (day, fileID), CONSTRAINT fk_files FOREIGN KEY (fileID) REFERENCES files(id) ON DELETE CASCADE)`); - await db.runAsync('CREATE INDEX idx_datetime ON records(dateTime)'); - await db.runAsync('CREATE INDEX idx_species ON records(speciesID)'); - await db.runAsync('CREATE INDEX idx_files ON records(fileID)'); + // await db.runAsync('CREATE INDEX idx_datetime ON records(dateTime)'); + // await db.runAsync('CREATE INDEX idx_species ON records(speciesID)'); + // await db.runAsync('CREATE INDEX idx_files ON records(fileID)'); if (archiveMode) { for (let i = 0; i < LABELS.length; i++) { const [sname, cname] = LABELS[i].replaceAll("'", "''").split('_'); @@ -134,7 +134,8 @@ async function loadDB(path) { // const datetime = diskDB.runAsync('CREATE INDEX IF NOT EXISTS idx_datetime ON records (dateTime)'); //const covering_total = diskDB.runAsync('CREATE INDEX IF NOT EXISTS idx_covering_total ON records (confidence, isDaylight)'); // const species = diskDB.runAsync('CREATE INDEX IF NOT EXISTS idx_species ON records (speciesID)'); - //await Promise.all([datetime, species, files, covering_total]); + // const files = diskDB.runAsync('CREATE INDEX IF NOT EXISTS idx_files ON records (fileID)'); + // await Promise.all([species, files]); console.log("Opened and cleaned disk db " + file) } return true @@ -290,11 +291,7 @@ async function handleMessage(e) { case 'insert-manual-record': const count = await onInsertManualRecord(args); // if (STATE.mode !== 'explore' && !args.batch) { - UI.postMessage({ - event: 'generate-alert', - // message: `${count} ${args.cname} record has been saved to the archive.`, - filter: true - }) + // } break; case 'load-model': @@ -611,7 +608,7 @@ async function onAnalyse({ }) { // Now we've asked for a new analysis, clear the aborted flag aborted = false; - + predictionStart = new Date(); // Set the appropraite selection range if this is a selection analysis STATE.update({ selection: end ? getSelectionRange(filesInScope[0], start, end) : undefined }); @@ -1305,7 +1302,6 @@ async function doPrediction({ end = metadata[file].duration, worker = undefined }) { - predictionStart = new Date(); await getPredictBuffers({ file: file, start: start, end: end, worker: undefined }); UI.postMessage({ event: 'update-audio-duration', value: metadata[file].duration }); } @@ -1724,7 +1720,7 @@ async function batchInsertRecords(cname, label, files, originalCname) { } -const onInsertManualRecord = async ({ cname, start, end, comment, count, file, label, batch, originalCname, confidence }) => { +const onInsertManualRecord = async ({ cname, start, end, comment, count, file, label, batch, originalCname, confidence, active }) => { if (batch) return batchInsertRecords(cname, label, file, originalCname) start = parseFloat(start), end = parseFloat(end); const startMilliseconds = Math.round(start * 1000); @@ -1764,7 +1760,12 @@ const onInsertManualRecord = async ({ cname, start, end, comment, count, file, l } // let test = await db.allAsync('SELECT * from records where datetime = ?', dateTime) // console.log('After insert: ',JSON.stringify(test)); - return response.changes + UI.postMessage({ + event: 'generate-alert', + // message: `${count} ${args.cname} record has been saved to the archive.`, + filter: true, + active: active + }) } const parsePredictions = async (response) => { @@ -1831,7 +1832,7 @@ const parsePredictions = async (response) => { if (!row) { const result = `No predictions found in ${file}`; UI.postMessage({ - event: 'prediction-ongoing', + event: 'new-result', file: file, result: result, index: index, @@ -1841,10 +1842,10 @@ const parsePredictions = async (response) => { }) } updateFilesBeingProcessed(response.file) - console.log(`Prediction done ${filesBeingProcessed.length} files to go`); - console.log('Analysis took ' + (new Date() - predictionStart) / 1000 + ' seconds.'); + console.log(`File ${file} processed after ${(new Date() - predictionStart) / 1000} seconds: ${filesBeingProcessed.length} files to go`); } - !STATE.selection && (!DATASET || STATE.increment() === 0) && getSummary({ interim: true }); + //!STATE.selection && (!DATASET || STATE.increment() === 0) && + getSummary({ interim: true }); return response.worker } @@ -1883,9 +1884,9 @@ async function parseMessage(e) { const limit = 10; clearCache(CACHE_LOCATION, limit); if (filesBeingProcessed.length) { - UI.postMessage({ - event: 'prediction-done', batchInProgress: true, - }) + // UI.postMessage({ + // event: 'prediction-done', batchInProgress: true, + // }) processNextFile({ worker: worker }); } else if (!STATE.selection) { getSummary(); @@ -1958,13 +1959,12 @@ async function processNextFile({ const result = `No detections in ${file}. It has no period within it where predictions would be given`; index++; UI.postMessage({ - event: 'prediction-ongoing', file: file, result: result, index: index + event: 'new-result', file: file, result: result, index: index }); console.log('Recursion: start = end') await processNextFile(arguments[0]); } else { - console.log('Total predictions received:', sumObjectValues(predictionsReceived)) if (!sumObjectValues(predictionsReceived)) { UI.postMessage({ event: 'progress', @@ -2091,7 +2091,7 @@ const getSummary = async ({ const summary = await STATE.GET_SUMMARY_SQL.allAsync(...params); DEBUG && console.log("Get Summary took", (Date.now() - t0) / 1000, " seconds"); - const event = interim ? 'update-summary' : 'prediction-done'; + const event = interim ? 'update-summary' : 'summary-complate'; UI.postMessage({ event: event, summary: summary, @@ -2123,7 +2123,8 @@ const getResults = async ({ limit = STATE.limit, offset = undefined, topRankin = STATE.topRankin, - exportTo = undefined + exportTo = undefined, + active = undefined } = {}) => { let confidence = STATE.detect.confidence; if (offset === undefined) { // Get offset state @@ -2191,7 +2192,7 @@ const getResults = async ({ } } } - STATE.selection || UI.postMessage({event: 'results-complete'}); + STATE.selection || UI.postMessage({event: 'results-complete', active: active}); }; // Function to format the CSV export @@ -2272,10 +2273,11 @@ const sendResult = (index, result, fromDBQuery) => { cname: result.cname, score: Number(result.score) / 100 }; - AUDACITY[file] ? AUDACITY[file].push(audacity) : AUDACITY[file] = [audacity]; + AUDACITY[file] ??= []; + AUDACITY[file].push(audacity); } UI.postMessage({ - event: 'prediction-ongoing', + event: 'new-result', file: file, result: result, index: index,