diff --git a/app.py b/app.py new file mode 100755 index 0000000..bf30633 --- /dev/null +++ b/app.py @@ -0,0 +1,34 @@ +from flask import Flask, render_template, send_from_directory + +app = Flask(__name__) + + +@app.route("/") +def hello_world(): + return render_template('index.html') + + +@app.route("/calibration") +def calibration(): + return render_template('calibration.html') + + +@app.route("/api/calibration-data") +def calibration_data(): + # Return dummy output + return { + 'led_0040': [-5.55700564, 8.14046472, 10.54967666], + 'led_0090': [-7.06683588, 3.12804841, 6.68884105], + 'led_0070': [4.75589064, -4.53847239, 4.59339772], + 'led_0020': [-2.70015523, -9.65693344, 9.3807489], + 'led_0050': [3.26747465, 9.09127322, 8.52493055], + 'led_0010': [4.69548335, -7.92767838, 7.98930158], + 'led_0030': [-7.78342731, -3.88182141, 8.52941532], + 'led_0060': [9.8648123, 3.14267492, 10.09358765], + 'led_0080': [-3.3681401, -7.6538693, 6.36718535], + } + + +@app.route('/static/') +def send_static(path): + return send_from_directory('static', path) diff --git a/calibration.py b/calibration.py index 622ebad..b206b4a 100755 --- a/calibration.py +++ b/calibration.py @@ -18,7 +18,9 @@ def print_error(msg: str): def calibrate(input_dir: str): - images = [np.asarray(Image.open(os.path.join(input_dir, x))) for x in os.listdir(input_dir)] + # Load all images + image_names = os.listdir(input_dir) + images = [np.asarray(Image.open(os.path.join(input_dir, x))) for x in image_names] # Camera parameters nu, nv, nc = images[0].shape @@ -88,7 +90,8 @@ def calibrate(input_dir: str): # Calculate the positions of the light sources light_positions = utils.lines_intersections(sphere_centers, estimated_lights) - return light_positions + # Return value as dictionnary + return dict(zip(image_names, light_positions)) def main(): diff --git a/static/calibration-visualiser.js b/static/calibration-visualiser.js new file mode 100644 index 0000000..2b5ca73 --- /dev/null +++ b/static/calibration-visualiser.js @@ -0,0 +1,211 @@ +import * as THREE from 'three'; +import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; + +class Pyramid extends THREE.Object3D { + constructor() { + super(); + + let width = 1; + let height = 1; + let length = 2; + + let vertices = [ + new THREE.Vector3( 0, 0, 0), + new THREE.Vector3( width, height, length), + new THREE.Vector3(-width, height, length), + new THREE.Vector3(-width, -height, length), + new THREE.Vector3( width, -height, length), + ] + + // Faces + { + let material = new THREE.MeshPhongMaterial({color: 0xffffff}); + material.transparent = true; + material.opacity = 0.8; + + let geometry = new THREE.BufferGeometry(); + let faces = [ + // Sides of the pyramid + [0, 2, 1], + [0, 3, 2], + [0, 4, 3], + [0, 1, 4], + + // Base of the pyramid + [1, 2, 3], + [1, 3, 4], + ] + + let buffer = new Float32Array(3 * 3 * faces.length); + + for (let faceIndex in faces) { + let face = faces[faceIndex]; + buffer[faceIndex * 3 * 3 + 0] = vertices[face[0]].x; + buffer[faceIndex * 3 * 3 + 1] = vertices[face[0]].y; + buffer[faceIndex * 3 * 3 + 2] = vertices[face[0]].z; + + buffer[faceIndex * 3 * 3 + 3] = vertices[face[1]].x; + buffer[faceIndex * 3 * 3 + 4] = vertices[face[1]].y; + buffer[faceIndex * 3 * 3 + 5] = vertices[face[1]].z; + + buffer[faceIndex * 3 * 3 + 6] = vertices[face[2]].x; + buffer[faceIndex * 3 * 3 + 7] = vertices[face[2]].y; + buffer[faceIndex * 3 * 3 + 8] = vertices[face[2]].z; + } + + + geometry.setAttribute('position', new THREE.BufferAttribute(buffer, 3)); + const mesh = new THREE.Mesh(geometry, material); + this.add(mesh); + } + + // Lines + { + let material = new THREE.LineBasicMaterial({color: 0xff0000}); + let geometry = new THREE.BufferGeometry(); + + let width = 1; + let height = 1; + let length = 2; + + + + let lines = [ + [0, 1], + [0, 2], + [0, 3], + [0, 4], + [1, 2], + [2, 3], + [3, 4], + [4, 1], + ] + + let buffer = new Float32Array(2 * 3 * lines.length); + + for (let lineIndex in lines) { + let line = lines[lineIndex]; + buffer[lineIndex * 2 * 3 + 0] = vertices[line[0]].x; + buffer[lineIndex * 2 * 3 + 1] = vertices[line[0]].y; + buffer[lineIndex * 2 * 3 + 2] = vertices[line[0]].z; + + buffer[lineIndex * 2 * 3 + 3] = vertices[line[1]].x; + buffer[lineIndex * 2 * 3 + 4] = vertices[line[1]].y; + buffer[lineIndex * 2 * 3 + 5] = vertices[line[1]].z; + } + + + geometry.setAttribute('position', new THREE.BufferAttribute(buffer, 3)); + const mesh = new THREE.Line(geometry, material); + this.add(mesh); + } + } +} + +let renderer, scene, camera, controls, leds, cameraObject, domElement, raycaster, pointer, selectedObject; + +async function init(dataPath, domElementArg = document.body) { + let request = await fetch('/api/calibration-data'); + let data = await request.json(); + + domElement = domElementArg; + let w = domElement === document.body ? window.innerWidth : domElement.offsetWidth; + let h = domElement === document.body ? window.innerHeight : domElement.offsetHeight; + + raycaster = new THREE.Raycaster(); + + camera = new THREE.PerspectiveCamera(45, w / h, 0.001, 1000); + camera.position.set(0, 0, -50); + + scene = new THREE.Scene(); + + leds = new THREE.Object3D(); + + for (let key in data) { + let row = data[key]; + const geometry = new THREE.SphereGeometry(1, 32, 16); + const material = new THREE.MeshPhongMaterial({ color: 0xffff00 }); + let sphere = new THREE.Mesh(geometry, material); + sphere.position.x = row[0]; + sphere.position.y = row[1]; + sphere.position.z = row[2]; + sphere.name = key + leds.add(sphere); + } + + scene.add(leds); + + cameraObject = new Pyramid(); + scene.add(cameraObject); + + const axesHelper = new THREE.AxesHelper(10); + scene.add(axesHelper); + + const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5); + scene.add(directionalLight); + + const ambientLight = new THREE.AmbientLight(0xffffff, 0.25); + scene.add(ambientLight); + + renderer = new THREE.WebGLRenderer({antialias: true, alpha: true}); + renderer.setAnimationLoop(animate); + + selectedObject = document.getElementById('selected-object'); + + // Add listeners + controls = new OrbitControls(camera, renderer.domElement); + controls.update(); + + window.addEventListener('pointermove', onPointerMove); + window.addEventListener('resize', onWindowResize, false); + onWindowResize(); + + domElement.appendChild(renderer.domElement); +} + +function animate(time) { + controls.update(); + + + if (pointer !== undefined) { + raycaster.setFromCamera(pointer, camera); + const intersects = raycaster.intersectObjects(leds.children); + if (intersects.length > 0) { + for (let led of leds.children) { + if (led === intersects[0].object) { + led.material.color.setHex(0xff0000); + selectedObject.innerText = led.name; + } else { + led.material.color.setHex(0xffff00); + } + } + } + } + + renderer.render(scene, camera); +} + +function onWindowResize() { + let w = domElement === document.body ? window.innerWidth : domElement.offsetWidth; + let h = domElement === document.body ? window.innerHeight : domElement.offsetHeight; + + camera.aspect = w / h; + camera.updateProjectionMatrix(); + + renderer.setSize(w, h); +} + +function onPointerMove(event) { + // calculate pointer position in normalized device coordinates + // (-1 to +1) for both components + + if (pointer === undefined) { + pointer = new THREE.Vector2(); + } + let w = domElement === document.body ? window.innerWidth : domElement.offsetWidth; + let h = domElement === document.body ? window.innerHeight : domElement.offsetHeight; + pointer.x = (event.offsetX / w) * 2 - 1; + pointer.y = - (event.offsetY / h) * 2 + 1; +} + +export { init }; diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..00cf49a --- /dev/null +++ b/templates/base.html @@ -0,0 +1,37 @@ + + + + + + NenuScanner + + {% block extracss %} + + {% endblock extracss %} + + + + {% block content %}{% endblock content %} + {% block extrajs %}{% endblock extrajs %} + + diff --git a/templates/calibration.html b/templates/calibration.html new file mode 100644 index 0000000..dfd40e9 --- /dev/null +++ b/templates/calibration.html @@ -0,0 +1,37 @@ +{% extends "base.html" %} + +{% block content %} +
+
+

Visualisation de l'étalonnage

+
+
+ Objet sélectionné : aucun +
+
+
+
+{% endblock content %} + +{% block extracss %} + +{% endblock extracss %} + +{% block extrajs %} + + +{% endblock extrajs %} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..8abec17 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} + +{% block content %} +
+
+

Bienvenue sur NenuScanner

+

+ Cliquez ici pour visualiser l'étalonnage! +

+
+
+{% endblock content %}