open-organism-observer/observe_nema.html

768 lines
24 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
<html>
<head>
<link href="bootstrap-5.0.2-dist/css/bootstrap.min.css" rel="stylesheet" ></link>
<script src="bootstrap-5.0.2-dist/js/bootstrap.bundle.min.js" ></script>
<meta charset='utf-8'>
<style>
#output{
position: relative;
width: 100%;
}
#video_container{
position: relative;
}
#video {
position:absolute;
z-index: 15;
width: 100%;
}
#canvas-draw{
position:absolute;
z-index:10;
width: 100%;
}
#photo {
position:absolute;
z-index:5;
width: 100%;
}
#canvas-temp{
display: none;
}
#tables {
width: 100%;
}
#control-elements {
height:30em;
}
</style>
</head>
<body>
2025-02-25 18:11:42 +01:00
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarTogglerDemo03" aria-controls="navbarTogglerDemo03" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand" href="#">Open Microscopy App</a>
<div class="collapse navbar-collapse" id="navbarTogglerDemo03">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarScrollingDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Settings
</a>
<ul class="dropdown-menu" aria-labelledby="navbarScrollingDropdown">
<li><a class="dropdown-item" href="/setup_microscope.html">Setup Camera</a></li>
<li><a class="dropdown-item" href="/sample_prep_protocol.html">Setup Preparation Protocol</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">Defaults</a></li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/sample_intake.html">add sample</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarScrollingDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Observe
</a>
<ul class="dropdown-menu" aria-labelledby="navbarScrollingDropdown">
<li><a class="dropdown-item" href="/observe_nema.html">Nematodes</a></li>
<li><a class="dropdown-item" href="/observe_main.html">Filamentous and Protozoa</a></li>
<li><a class="dropdown-item" href="/observe_bact.html">Bacteria</a></li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link" href="/create_report.html">generate report</a>
</li>
</ul>
</div>
</div>
</nav>
<!-- this canvas is not displayed and only used to generate the photo -->
<canvas id="canvas-temp"></canvas>
<div class="container">
<div class="row">
<div class="col-3">
<label class = "form-label" for="camera_chooser">choose the camera setup</label>
<select class="form-select" aria-label="Default select example"
name="camera_chooser" id = "camera_chooser"></select>
</div>
<div class="col-3">
<label class = "form-label" for="protocol_chooser">choose the preparation protocol</label>
<select class="form-select" aria-label="Default select example"
name="protocol_chooser" id = "protocol_chooser"></select>
</div>
<div class="col-3">
<label class = "form-label" for="dilution">change dilution</label>
<input class="form-control" name="dilution" id = "dilution"></input>
</div>
<div class="col-3">
<label class = "form-label" for="sample_chooser">choose the sample</label>
<select class="form-select" aria-label="Default select example"
name="sample_chooser" id = "sample_chooser"></select>
</div>
</div>
<!-- the output div is used to display the video, the photo and the canvas where you can draw in (and measure) your organisms. -->
<div id="output" class="row">
<div id = "video_container" class="col-9">
<img id="photo" alt="The screen capture will appear in this box." ></img>
<canvas id="canvas-draw"></canvas>
<video autoplay="true" id="video"></video>
</div>
<div id = "control-elements" class="col-3 ">
<button class="btn btn-primary float-end" onclick="takePicture();" id="start-button">Take photo</button>
<form id="organism_type" class="form-check" onsubmit="return false;">
<input type="radio" class="btn-check" name="choice" value="Fungal_Feeding" id="org-type-Fungal_Feeding" checked></input>
2025-01-29 15:45:00 +01:00
<label class="btn btn-outline-success" for="org-type-Fungal_Feeding">Fungal Feeding</label>
<input type="radio" class="btn-check" name="choice" value="Bacterial_Feeding" id="org-type-Bacterial_Feeding"></input>
2025-01-29 15:45:00 +01:00
<label class="btn btn-outline-success" for="org-type-Bacterial_Feeding">Bacterial Feeding</label>
<input type="radio" class="btn-check" name="choice" value="Predatory" id="org-type-Predatory"></input>
<label class="btn btn-outline-success" for="org-type-Predatory">Predatory</label>
2025-01-29 15:45:00 +01:00
<input type="radio" class="btn-check" name="choice" value="Root_Feeding" id="org-type-Root_Feeding"></input>
<label class="btn btn-outline-success" for="org-type-Root_Feeding">Root Feeding</label>
</form>
<div id="organism" class="row align-items-end">
<div class="col" name="Notes" >
<label class = "form-label" for="Notes">Notes</label>
<input class="form-control" type="textfield" name="Notes" value="" id="nemaNotes"> </input>
</div>
</div>
<div id="organisms-table">
<h2> nematode </h2>
<table id= "org-table" style="width:100%">
<thead>
<tr>
<th>
pictures
</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<button class="btn btn-primary float-end" onclick="add_picture();">add picture</button>
<button class="btn btn-primary float-end" onclick="submit_nematode();">submit Nematode</button>
</div>
<div class="col-9">
<div class="row">
<button class="btn btn-primary float-end" onclick="toggleFullscreen();" id="fullscreen-button">
toggle Fullscreen video
</button>
</div>
</div>
</div>
<div id="tables">
<div>
<h2> nematodes </h2>
<table id= "Results" style="width:100%">
<thead>
<tr>
<th>
number
</th>
<th>
type
</th>
<th>
picture(s)
</th>
<th style="width:15%">
<button class="btn btn-primary float-end" id="Result_submit">submit result</button>
</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
<script>
var microscope_setups = [];
var microscope_setup;
var default_setup_id;
var prep_protocols = {};
var prep_protocol;
var default_protocol_id;
var sample;
var samples = {};
var dilution_chooser = document.querySelector("#dilution")
var db;
let openRequest = indexedDB.open("my_db");
openRequest.onerror = function() {
console.error("Error", openRequest.error);
};
openRequest.onsuccess = function() {
db = openRequest.result;
get_default_camera_setup();
get_default_prep_setup();
get_all_samples();
// continue working with database using db object
};
function get_default_camera_setup(){
const request = db.transaction('defaults')
.objectStore('defaults')
.get(0);
request.onsuccess = ()=> {
default_setup_id = request.result.microscope_setup;
get_all_video_prefs();
// width of FoV -- will be supplied by FLASK microscope setup.
}
request.onerror = (err)=> {
console.error(`Error to get all setups: ${err}`)
}
}
function get_all_video_prefs(){
const request = db.transaction('microscope_setup')
.objectStore('microscope_setup')
.openCursor();
request.onsuccess = ()=> {
let cursor = event.target.result;
if (cursor) {
// Access the current record
make_camera_chooser(cursor.value, cursor.key);
// Move to the next record
cursor.continue();
}
else{
startup();
}
}
request.onerror = (err)=> {
console.error(`Error to get all setups: ${err}`)
}
}
var camera_chooser = document.querySelector("#camera_chooser");
function make_camera_chooser(setup, key){
let cameraID = document.createElement("option");
let text = document.createTextNode(setup.name);
cameraID.value = key;
microscope_setups[key] = setup;
if (key == default_setup_id){
cameraID.defaultSelected = true;
text.textContent += " (default)"
microscope_setup = setup;
}
cameraID.appendChild(text);
camera_chooser.appendChild(cameraID);
}
camera_chooser.addEventListener("change", change_cam);
function change_cam(){
microscope_setup = microscope_setups[this.value];
startup();
};
function get_default_prep_setup(){
const request = db.transaction('defaults')
.objectStore('defaults')
.get(1);
request.onsuccess = ()=> {
default_protocol_id = request.result.prep_setup;
get_all_prep_prefs();
// width of FoV -- will be supplied by FLASK microscope setup.
}
request.onerror = (err)=> {
console.error(`Error to get all setups: ${err}`)
}
}
function get_all_prep_prefs(){
const request = db.transaction('prep_protocol')
.objectStore('prep_protocol')
.openCursor();
request.onsuccess = ()=> {
let cursor = event.target.result;
if (cursor) {
// Access the current record
make_protocol_chooser(cursor.value, cursor.key);
// Move to the next record
cursor.continue();
}
}
request.onerror = (err)=> {
console.error(`Error to get all setups: ${err}`)
}
}
var protocol_chooser = document.querySelector("#protocol_chooser");
function make_protocol_chooser(protocol, key){
let protocolID = document.createElement("option");
let text = document.createTextNode(protocol.name);
protocolID.value = key;
prep_protocols[key] = protocol;
if (key == default_protocol_id){
protocolID.defaultSelected = true;
text.textContent += " (default)";
dilution_chooser.value = protocol.Main_Dilution;
}
protocolID.appendChild(text);
protocol_chooser.appendChild(protocolID);
}
protocol_chooser.addEventListener("change", change_protocol);
function change_protocol(){
protocol = prep_protocols[this.value];
dilution_chooser.value = prep_protocol.Main_Dilution;
};
function get_all_samples(){
const request = db.transaction('sample')
.objectStore('sample')
.openCursor();
request.onsuccess = ()=> {
let cursor = event.target.result;
if (cursor) {
// Access the current record
make_sample_chooser(cursor.value, cursor.key);
sample = cursor.key;
// Move to the next record
cursor.continue();
}
}
request.onerror = (err)=> {
console.error(`Error to get all setups: ${err}`)
}
}
var sample_chooser = document.querySelector("#sample_chooser");
function make_sample_chooser(sample, key){
let sampleID = document.createElement("option");
let text = document.createTextNode(sample.Name);
sampleID.value = key;
samples[key] = sample;
sampleID.appendChild(text);
sample_chooser.appendChild(sampleID);
}
sample_chooser.addEventListener("change", change_sample);
function change_sample(){
sample = samples[this.value];
};
var nematode = {
"type" : "",
"pictures" : [],
};
var nematodes = [];
function submit_nematode(){
if (nematode.type == "") {
nematode.type = organism_type.choice.value;
}
nematodes.push(nematode);
nematode = {
"type" : "",
"pictures" : [],
};
update_nematode_table();
update_results_table();
}
function add_picture(){
let photo = document.getElementById("photo");
if (nematode.type == "") {
nematode.type = organism_type.choice.value;
}
nematode.pictures.push({
"img" : photo.src,
"path" : lines[0],
"notes" : nemaNotes.value
});
lines = [];
video.style.zIndex = 15;
clearPhoto();
nemaNotes.value = "";
update_nematode_table();
}
var nematode_table = document.querySelector("#org-table");
// update organism table
function update_nematode_table(){
while (nematode_table.children[1].hasChildNodes()) {
nematode_table.children[1].removeChild(nematode_table.children[1].lastChild);
}
nematode.pictures.forEach((o, k) => render_nematode_table(o,k));
}
// remove organism from organsisms and update table and canvas
function remove_picture(index){
nematode.pictures.splice(index, 1);
lines=[];
update_nematode_table();
}
// make a new organism table
function render_nematode_table(image, index){
let newRow = nematode_table.children[1].insertRow();
let picture = newRow.insertCell(0);
let notes = newRow.insertCell(1);
let button = newRow.insertCell(2);
picture.innerHTML = "<img class='img-thumbnail'src='" + image.img + "'/>";
picture.style.width = "2em";
notes.innerHTML = image.notes;
button.innerHTML = '<button class="btn btn-primary float-end" onclick=remove_picture(' + index + ')>remove</button>';
}
var results_table = document.querySelector("#Results");
function update_results_table(){
while (results_table.children[1].hasChildNodes()) {
results_table.children[1].removeChild(results_table.children[1].lastChild);
}
nematodes.forEach((o, k) => render_results_table(o,k));
}
function render_results_table(nematode, index){
let newRow = results_table.children[1].insertRow();
let nematode_number = newRow.insertCell(0);
let type = newRow.insertCell(1);
let pictures = newRow.insertCell(2);
let button = newRow.insertCell(3);
nematode_number.innerHTML = index+1;
let pics = "";
nematode.pictures.forEach((image, i) => {
pics += "<img class='img-thumbnail' style='height:5em' src='" + image.img + "'/>";
});
type.innerHTML = nematode.type;
pictures.innerHTML = pics;
button.innerHTML = "<button class='btn btn-primary float-end' onclick=remove_nematode(" + index + ")>remove</button>";
}
function remove_nematode(index){
nematodes.splice(index, 1);
lines=[];
update_results_table();
}
function toggleFullscreen() {
let elem = document.querySelector("#output");
canvas_draw.style.width = video.offsetWidth
if (!document.fullscreenElement) {
elem.requestFullscreen().catch((err) => {
alert(
`Error attempting to enable fullscreen mode: ${err.message} (${err.name})`,
);
});
} else {
document.exitFullscreen();
}
}
submit_results = document.querySelector("#Result_submit")
submit_results.addEventListener("click",send_result_to_backend);
function send_result_to_backend(){
let to_send = {
"nematodes" : nematodes,
"sampleID" : sample_chooser.value,
"prepID" : protocol_chooser.value,
"setupID": camera_chooser.value,
"dilution" : dilution_chooser.value,
"datetime" : new Date,
}
let transaction = db.transaction(["nematode_scan"], "readwrite");
let objectStore = transaction.objectStore("nematode_scan");
let add_request = objectStore.add(to_send); // (3)
add_request.onsuccess = (event) => {
console.log("All done!");
};
add_request.onerror = (event) => {
console.log("something went wrong");
// Don't forget to handle errors!
};
}
var video = document.querySelector("#video");
var out = document.querySelector("#output");
// width of FoV -- will be supplied by FLASK microscope setup.
var real_width_FoV = 255;
2025-01-29 15:45:58 +01:00
// init drawing
var canvas_draw = document.querySelector("#canvas-draw");
var ctx = canvas_draw.getContext('2d');
var pos = { x: 0, y: 0 };
// store lines for measurement
var lines = [];
var line = [];
// calculate length of line - helper function
function get_length(line){
var dist = 0.0;
if (line){
for (let j = 0; j < line.length-1; ++j) {
dist += Math.sqrt((line[j].x - line[j+1].x) ** 2 + (line[j].y - line[j+1].y) ** 2);
}
}
return dist/canvas_draw.width
}
// events to set the position of the drawing cursor
canvas_draw.addEventListener('mousedown', setPosition);
canvas_draw.addEventListener('mouseenter', setPosition);
// set new position from mouse event
// scale canvas coords
function setPosition(e) {
var rect = canvas_draw.getBoundingClientRect();
scaleX = canvas_draw.width / rect.width, // relationship bitmap vs. element for x
scaleY = canvas_draw.height / rect.height;
pos.x = (e.clientX - rect.x) * scaleX;
pos.y = (e.clientY - rect.y) * scaleY;
}
// the draw function to draw onto canvas
canvas_draw.addEventListener('mousemove', draw);
function draw(e) {
// mouse left button must be pressed and video must be hidden / a photo must have been taken.
if (e.buttons !== 1) {
// if the length of the line is 0 there is nothing to do and nothing to calculate.
if (line.length > 1) {
console.log(organism_type.choice.value);
let xs=[];
let ys=[];
// create a bounding box line for the Nematode.
x_max = Math.max(... line.map(xs=>xs.x));
y_max = Math.max(... line.map(ys=>ys.y));
x_min = Math.min(... line.map(xs=>xs.x));
y_min = Math.min(... line.map(ys=>ys.y));
line = []
let bounding_box_size=20;
let delta_x = (x_max-x_min)*bounding_box_size/100;
let delta_y = (y_max-y_min)*bounding_box_size/100;
line.push({"x":x_max+delta_x,"y":y_max+delta_y});
line.push({"x":x_max+delta_x,"y":y_min-delta_y});
line.push({"x":x_min-delta_x,"y":y_min-delta_y});
line.push({"x":x_min-delta_x,"y":y_max+delta_y});
line.push({"x":x_max+delta_x,"y":y_max+delta_y});
// clear line array
lines.push(line);
draw_path(line);
line = [];
}
return;
}
// draw on canvas.
if (e.buttons == 1){
ctx.beginPath(); // begin
ctx.lineWidth = 0.5;
ctx.lineCap = 'round';
ctx.strokeStyle = 'black';
// update line
// first time push the first position
if (line.length==0){
line.push({x:pos.x, y:pos.y});
}
ctx.moveTo(pos.x, pos.y); // from
setPosition(e);
// everytime push the last position
line.push({x:pos.x, y:pos.y});
ctx.lineTo(pos.x, pos.y); // to
ctx.stroke(); // draw it!
}
}
function draw_path(path){
// draw a path on the canvas.
ctx.beginPath(); // begin
ctx.lineWidth = 0.5;
ctx.lineCap = 'round';
ctx.strokeStyle = 'black';
for (var i = 0; i < path.length-1; i++) {
ctx.moveTo(path[i].x, path[i].y);
ctx.lineTo(path[i+1].x, path[i+1].y);
ctx.stroke();
}
}
function update_canvas_draw(organisms){
lines=[];
canvas_draw.getContext('2d').clearRect(0, 0, canvas_draw.width, canvas_draw.height);
for (var i = 0; i < organisms.length; i++){
if (organisms[i].path_length){
draw_path(organisms[i].path_length);
}
if (organisms[i].path_width){
draw_path(organisms[i].path_width);
}
}
}
let streaming = false;
function startup() {
output = document.getElementById("output");
video = document.getElementById("video");
canvas = document.getElementById("canvas-temp");
photo = document.getElementById("photo");
startButton = document.getElementById("start-button");
navigator.mediaDevices
.getUserMedia(microscope_setup.video_prefs)
.then((stream) => {
video.srcObject = stream;
video.play();
})
.catch((err) => {
console.error(`An error occurred: ${err}`);
});
video.addEventListener(
"canplay",
(ev) => {
if (!streaming) {
video_aspect = video.videoWidth / video.videoHeight;
video.style.aspectRatio = video_aspect;
photo.style.aspectRatio = video_aspect;
canvas.style.aspectRatio = video_aspect;
canvas_draw.style.aspectRatio = video_aspect;
canvas_draw.height = canvas_draw.height*5;
2025-01-17 03:52:45 +01:00
canvas_draw.width = canvas_draw.height*video_aspect;
streaming = true;
}
},
false,
);
startButton.addEventListener(
"click",
(ev) => {
takePicture();
ev.preventDefault();
},
false,
);
clearPhoto();
}
// Fill the photo with an indication that none has been
// captured.
function clearPhoto() {
const context = canvas.getContext("2d");
context.fillRect(0, 0, canvas.width, canvas.height);
const data = canvas.toDataURL("image/png");
photo.setAttribute("src", data);
}
// Capture a photo by fetching the current contents of the video
// and drawing it into a canvas, then converting that to a PNG
// format data URL. By drawing it on an offscreen canvas and then
// drawing that to the screen, we can change its size and/or apply
// other changes before drawing it.
function takePicture() {
canvas_draw.getContext('2d').clearRect(0, 0, canvas_draw.width, canvas_draw.height);
const context = canvas.getContext("2d");
if (video.videoWidth && video.videoHeight) {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
video.style.zIndex = 1;
photo.style.zIndex = 5;
canvas.style.zIndex= 10;
const data = canvas.toDataURL("image/png");
photo.setAttribute("src", data);
} else {
clearPhoto();
}
}
// Set up our event listener to run the startup process
// once loading is complete.
//})();
</script>
</body>
</html>