Skip to content

Commit

Permalink
add annotation page
Browse files Browse the repository at this point in the history
  • Loading branch information
Nitnelav committed Dec 12, 2024
1 parent ad83f9c commit 18af6a3
Show file tree
Hide file tree
Showing 5 changed files with 334 additions and 0 deletions.
36 changes: 36 additions & 0 deletions services/ansible_openvpn/docker/dashboard/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,35 @@
)


@app.get("/api/random-sample/")
async def random_sample(request: Request):
post_data = json.loads(
templates.get_template("trigger_random.json").render())
response = client.search(**post_data)
result = response["hits"]["hits"][0]
result_index = result["_index"]
result_id = result["_id"]
result_source = result["_source"]
result_source["trigger_index"] = result_index
result_source["trigger_id"] = result_id
return result_source


@app.post("/api/update-sample/")
async def update_sample(request: Request):
post_data = await request.json()
print(post_data)
res = client.update(
index=post_data["trigger_index"],
id=post_data["trigger_id"],
doc={
"annotation": post_data["annotation"]
}
)
print(res)
return "success"


@app.get("/api/list-samples/{start_epoch_millis}/{end_epoch_millis}/")
async def get_list_samples(request: Request, start_epoch_millis: int,
end_epoch_millis: int, hwa: str = ""):
Expand Down Expand Up @@ -144,6 +173,13 @@ async def recordings(request: Request):
return templates.TemplateResponse("recordings.html",
context={"request": request, "hwa_list": hwa_list})

@app.get('/annotate', response_class=HTMLResponse)
async def annotate(request: Request):
return templates.TemplateResponse("annotate.html",
context={"request": request})





@app.get('/devices', response_class=HTMLResponse)
Expand Down
22 changes: 22 additions & 0 deletions services/ansible_openvpn/docker/dashboard/app/static/annotate.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

.a-button {
background-color: #008CBA;
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
}
.a-button:hover {
cursor: pointer;
}
.a-button:disabled {
background-color: #e7e7e7 !important;
color: black !important;
} /* Gray */
.a-button-default {background-color: #008CBA;} /* Blue */
.a-button-warning {background-color: #ffa500;} /* Orange */
.a-button-success {background-color: #04AA6D;} /* Green */
.a-button-error {background-color: #f44336;} /* Red */
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import WaveSurfer from '/static/wavesurfer.esm.js'
import Spectrogram from '/static/spectrogram.esm.js'

var privateKey = null;

var updating = false;
var currentAudioAttributes = null;

async function do_decrypt(jsonContent) {
const el = document.getElementById("error_panel");
try {
const encrypted = atob(jsonContent.encrypted_audio);
if(privateKey == null) {
// convert a Forge certificate from PEM
const pem = await $('input[name="privkey"]')[0].files[0].text();
const pki = forge.pki;
privateKey = pki.decryptRsaPrivateKey(pem, $('input[name="pwd"]')[0].value);
}
if (privateKey == null) {
el.style.visibility = "visible";
el.innerHTML = "Invalid decryption key or password";
return;
} else {
el.style.visibility = "hidden";
const keyLength = privateKey.n.bitLength() / 8;
const decrypted = privateKey.decrypt(encrypted.substring(0, keyLength ), 'RSA-OAEP');
const aes_key = decrypted.substring(0, 16);
const iv = decrypted.substring(16, 32);
const decipher = forge.cipher.createDecipher('AES-CBC', aes_key);
decipher.start({iv: iv});
decipher.update(forge.util.createBuffer(encrypted.substring(keyLength)));
const result = decipher.finish(); // check 'result' for true/false
// outputs decrypted hex
// Create regex patterns for replacing unwanted characters in file name
let format = "raw";
// look for magic word in file header
if(decipher.output.data.substring(0,4) == "fLaC") {
format = "flac";
} else if(decipher.output.data.substring(0,3) == "Ogg") {
format = "ogg";
}
return {"data": decipher.output.data, "jsonContent": jsonContent, "format" : format}
}
} catch (e) {
el.style.visibility = "visible";
el.innerHTML = "No private key file submitted "+e;
return;
}
}

async function do_decrypt_and_play(jsonContent) {
const decrypted_data = await do_decrypt(jsonContent);
var len = decrypted_data.data.length;
var buf = new ArrayBuffer(len);
var view = new Uint8Array(buf);
for (var i = 0; i < len; i++) {
view[i] = decrypted_data.data.charCodeAt(i) & 0xff;
}
let b = new Blob([view], { type : "audio/"+decrypted_data.format });
ws.loadBlob(b);
}

function next_and_play() {
updating = true;
updateButtons()
$.ajax({
type: "GET",
url: "api/random-sample",
success: function(jsonContent) {
updating = false;
updateButtons();
currentAudioAttributes = Object.assign({}, jsonContent)
delete currentAudioAttributes.encrypted_audio
do_decrypt_and_play(jsonContent);
},
error: function(xhr, status, error) {
console.log("Error: " + error)
updating = false;
updateButtons()
},
contentType : 'application/json',
});
}

// Create an instance of WaveSurfer
const ws = WaveSurfer.create({
container: '#waveform',
waveColor: 'rgb(200, 0, 200)',
progressColor: 'rgb(100, 0, 100)',
normalize: true,
mediaControls:true,
height: 0,
})

// Initialize the Spectrogram plugin
ws.registerPlugin(
Spectrogram.create({
labels: true,
labelsColor: "#7c9cb6",
height: 200,
splitChannels: true,
maxFrequency: 8000
}),
)

// Play on click
ws.once('interaction', () => {
ws.play()
})

// Create Web Audio context
const audioContext = new AudioContext()

var gainNode = null

// Connect the audio to the equalizer
ws.media.addEventListener(
'canplay',
() => {
// Create a MediaElementSourceNode from the audio element
const mediaNode = audioContext.createMediaElementSource(ws.media)

gainNode = audioContext.createGain();
gainNode.gain.value = 100 * ws.media.volume;
mediaNode.connect(gainNode);
// Connect the filters to the audio output
gainNode.connect(audioContext.destination)
},
{ once: true },
)

ws.media.onvolumechange = function() {
if(gainNode != null) {
gainNode.gain.value = 100 * ws.media.volume;
}
}


function do_update_sample() {
if(updating) {
return;
}
updating = true;
updateButtons();

$.ajax({
type: "POST",
url: "api/update-sample",
data: JSON.stringify(currentAudioAttributes),
contentType : 'application/json',
success: next_and_play,
error: function(xhr, status, error) {
console.log("Error: " + error)
updating = false;
updateButtons()
},
});
}

function updateButtons() {
if (updating) {
$(".fa-refresh").show();
$(".a-button").prop("disabled",true);
// $("#train_button > button").addClass("a-button-disable").prop("disabled",true);;
} else {
$(".fa-refresh").hide();
$(".a-button").prop("disabled",false);
// $("#train_button > button").removeClass("a-button-disable").prop("disabled",false);;
}
}

function train_click() {
console.log("train_click")
currentAudioAttributes["annotation"] = "train"
do_update_sample()
}
function not_train_click() {
console.log("not_train_click")
currentAudioAttributes["annotation"] = "not_train"
do_update_sample()
}
function not_sure_click() {
console.log("not_sure_click")
currentAudioAttributes["annotation"] = "not_sure"
do_update_sample()
}

document.getElementById('next_button').addEventListener('click', next_and_play)

document.getElementById('not_train_button').addEventListener('click', not_train_click)
document.getElementById('not_sure_button').addEventListener('click', not_sure_click)
document.getElementById('train_button').addEventListener('click', train_click)
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Annotate</title>

<script type="text/javascript" src="static/forge.min.js"></script>
<script type="text/javascript" src="static/jquery.min.js"></script>
<script type="text/javascript" src="static/moment.min.js"></script>
<script type="text/javascript" src="static/download.min.js"></script>
<script type="text/javascript" src="static/daterangepicker.min.js"></script>
<script type="text/javascript" src="static/handsontable.full.js"></script>
<link type="text/css" href="static/handsontable.full.css" rel="stylesheet" media="screen">
<link rel="stylesheet" type="text/css" href="static/daterangepicker.css"/>
<link rel="stylesheet" type="text/css" href="static/forms.css"/>
<link rel="stylesheet" type="text/css" href="static/status.css"/>
<link rel="stylesheet" type="text/css" href="static/annotate.css"/>
<!-- Add icon library -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"">
</head>
<body>
<div class="info top_menu">
<ul class="ul_top_menu">
<li class="li_top_menu"><a href="/" target="_self">Map</a></li>
<li class="li_top_menu"><a href="/recordings" target="_self" style="outline: none;">Audio records</a></li>
<li class="li_top_menu"><a href="/devices" target="_self" style="outline: none;">Connected devices</a></li>
<li class="li_top_menu"><a href="https://nsraw.noise-planet.org" target="_blank">Backup data</a></li>
</ul>
</div>
<div class="form-style-7">
<h1>Annotate</h1>
<div id="error_panel" class="msg_error" style="visibility: hidden;">
Invalid decryption key or password
</div>
<ul>
<li>
<label for="privkey">Private key and password</label>
<input type="file" name="privkey" accept=".priv,.pem">
<input type="password" name="pwd" placeholder="Key File password"/>
<span>Encryption private key and password, not transferred</span>
</li>
<li>
<button class="a-button a-button-default" id="next_button">
<i class="fa fa-refresh fa-spin" style="display: none;"> </i> Next
</button>
</li>
<li>
<div id="waveform"></div>
</li>
<li id="train_buttons">

<button class="a-button a-button-error" id="not_train_button" disabled>
<i class="fa fa-xmark"> </i> Not a train
</button>
<button class="a-button a-button-warning" id="not_sure_button" disabled>
<i class="fa fa-question"></i> Not sure
</button>
<button class="a-button a-button-success" id="train_button" disabled>
<i class="fa fa-check"></i> Train !
</button>
</li>
</ul>
</div>
<script type="module" src="static/read_trigger_annotate.js" data-type="module" defer></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"index": "sensor_yamnet_*",
"size" : 1,
"_source": true,
"query": {
"function_score": {
"query": {
"range": {
"date": {
"gte": "2024-06-01",
"lte": "2024-11-31"
}
}
},
"random_score": {}
}
}
}

0 comments on commit 18af6a3

Please sign in to comment.