520 lines
15 KiB
HTML
520 lines
15 KiB
HTML
<!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>
|
|
|
|
<!-- this canvas is not displayed and only used to generate the photo -->
|
|
<canvas id="canvas-temp"></canvas>
|
|
|
|
<div class="container">
|
|
<!-- 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>
|
|
<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>
|
|
<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>
|
|
</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>
|
|
|
|
|
|
<button class="btn btn-primary float-end" onclick="toggleFullscreen();" id="fullscreen-button">toggle Fullscreen video</button>
|
|
<div class="row">
|
|
<button class="btn btn-primary float-end" onclick="toggle_cam();">Toggle camera</button>
|
|
</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">next picture</button>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
<script>
|
|
|
|
|
|
|
|
var nematode = {
|
|
"type" : "",
|
|
"pictures" : [],
|
|
};
|
|
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 nematodes = [];
|
|
|
|
function submit_nematode(){
|
|
nematodes.push(nematode);
|
|
nematode = {
|
|
"type" : "",
|
|
"pictures" : [],
|
|
};
|
|
update_nematode_table();
|
|
update_results_table();
|
|
|
|
}
|
|
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();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
// init video switching get IDs of video inputs
|
|
|
|
var deviceIDs = [];
|
|
|
|
navigator.mediaDevices
|
|
.enumerateDevices()
|
|
.then((devices) => {
|
|
devices.forEach((device) => {
|
|
if (device.kind=="videoinput"){
|
|
deviceIDs.push(device.deviceId);
|
|
}
|
|
console.log(`${device.kind}: ${device.label} id = ${device.deviceId}`);
|
|
});
|
|
})
|
|
.catch((err) => {
|
|
console.error(`${err.name}: ${err.message}`);
|
|
});
|
|
|
|
// toggle between cameras
|
|
|
|
var device_index = 0;
|
|
|
|
function toggle_cam(){
|
|
device_index += 1
|
|
if (device_index>=deviceIDs.length){
|
|
device_index=0
|
|
};
|
|
|
|
if (navigator.mediaDevices.getUserMedia) {
|
|
navigator.mediaDevices.getUserMedia({ video: {
|
|
deviceId: deviceIDs[device_index],
|
|
}, })
|
|
.then(function (stream) {
|
|
video.srcObject = stream;
|
|
})
|
|
.catch(function (error) {
|
|
console.log("Something went wrong!");
|
|
});
|
|
}
|
|
};
|
|
|
|
// 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({ video: {width: { ideal: 99999} , height: { ideal: 99999 }}, audio: false })
|
|
.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;
|
|
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.
|
|
window.addEventListener("load", startup, false);
|
|
//})();
|
|
|
|
|
|
</script>
|
|
</body>
|
|
</html>
|