Working on calibration

This commit is contained in:
Thomas Forgione 2024-07-10 16:26:59 +02:00
parent 8676b21969
commit 658fcd57c3
8 changed files with 125 additions and 35 deletions

31
app.py
View File

@ -30,14 +30,23 @@ def create_object():
def object(id: int): def object(id: int):
conn = db.get() conn = db.get()
object = db.Object.get_from_id(id, conn) object = db.Object.get_from_id(id, conn)
return render_template('object.html', object=object) calibration = object.calibration(conn)
return render_template('object.html', object=object, calibration=calibration)
@app.route("/calibrate/<id>") @app.route("/calibrate/<id>")
def calibrate(id: int): def calibrate(id: int):
conn = db.get() conn = db.get()
object = db.Object.get_from_id(id, conn) object = db.Object.get_from_id(id, conn)
return render_template('calibrate.html', object=object) if object.calibration_id is None:
with conn:
calibration = db.Calibration.create(conn)
object.calibration_id = calibration.id
object.save(conn)
else:
calibration = object.calibration(conn)
return render_template('calibrate.html', object=object, calibration=calibration, leds=config.LEDS_UUIDS)
@app.route("/api/preview/<id>") @app.route("/api/preview/<id>")
@ -54,22 +63,26 @@ def preview(id: int):
@app.route("/api/scan-for-calibration/<id>") @app.route("/api/scan-for-calibration/<id>")
def scan_calibration(id: int): def scan_calibration(id: int):
conn = db.get() conn = db.get()
db.Object.get_from_id(id, conn) calibration = db.Calibration.get_from_id(id, conn)
def generate(): def generate():
length = len(config.LEDS_UUIDS) length = len(config.LEDS_UUIDS)
for index, led_uuid in enumerate(scanner.scan(join(config.DATA_DIR, id, 'calibration'))): for index, led_uuid in enumerate(scanner.scan(join(config.CALIBRATION_DIR, id))):
yield f"{led_uuid},{(index+1)/length}\n" yield f"{led_uuid},{(index+1)/length}\n"
with conn:
calibration.state = db.CalibrationState.HasData
calibration.save(conn)
return app.response_class(generate(), mimetype='text/plain') return app.response_class(generate(), mimetype='text/plain')
@app.route("/api/calibrate/<id>") @app.route("/api/calibrate/<id>")
def run_calibration(id: int): def run_calibration(id: int):
conn = db.get() conn = db.get()
db.Object.get_from_id(id, conn) db.Calibration.get_from_id(id, conn)
calibration_json = calibration.calibrate(join(config.DATA_DIR, str(id), 'calibration')) calibration_json = calibration.calibrate(join(config.CALIBRATION_DIR, str(id)))
with open(join(config.DATA_DIR, str(id), 'calibration.json'), 'w') as f: with open(join(config.CALIBRATION_DIR, str(id), 'calibration.json'), 'w') as f:
json.dump(calibration_json, f, indent=4) json.dump(calibration_json, f, indent=4)
return 'ok' return 'ok'
@ -77,8 +90,8 @@ def run_calibration(id: int):
@app.route("/calibration/<id>") @app.route("/calibration/<id>")
def calibration_page(id: int): def calibration_page(id: int):
conn = db.get() conn = db.get()
object = db.Object.get_from_id(id, conn) calibration = db.Calibration.get_from_id(id, conn)
return render_template('calibration.html', object=object) return render_template('calibration.html', calibration=calibration)
@app.route('/static/<path:path>') @app.route('/static/<path:path>')

View File

@ -1,4 +1,7 @@
from os.path import join
DATA_DIR = 'data' DATA_DIR = 'data'
CALIBRATION_DIR = join(DATA_DIR, 'calibration')
LEDS_UUIDS = [ LEDS_UUIDS = [
'ac59350e-3787-46d2-88fa-743c1d34fe86', 'ac59350e-3787-46d2-88fa-743c1d34fe86',

72
db.py
View File

@ -1,5 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
from enum import IntEnum
from flask import g from flask import g
import os import os
import sqlite3 import sqlite3
@ -13,6 +14,8 @@ def get() -> sqlite3.Connection:
detect_types=sqlite3.PARSE_DECLTYPES, detect_types=sqlite3.PARSE_DECLTYPES,
) )
g.db.row_factory = sqlite3.Row g.db.row_factory = sqlite3.Row
cur = g.db.cursor()
cur.execute('PRAGMA foreign_keys = on')
return g.db return g.db
@ -43,34 +46,75 @@ if __name__ == '__main__':
init(db) init(db)
class CalibrationState(IntEnum):
Empty = 0
HasData = 1
IsValidated = 2
class Calibration:
@staticmethod
def select_args() -> str:
return 'id, state'
def __init__(self, calibration_id: int, state: int):
self.id = calibration_id
self.state = CalibrationState(state)
@staticmethod
def create(db: sqlite3.Connection) -> 'Calibration':
cur = db.cursor()
response = cur.execute(
'INSERT INTO calibration(state) VALUES (?) RETURNING ' + Calibration.select_args() + ';',
[int(CalibrationState.Empty)]
)
return Calibration.from_row(response.fetchone())
@staticmethod
def from_row(row: sqlite3.Row) -> 'Calibration':
return Calibration(*row)
def save(self, db: sqlite3.Connection):
cur = db.cursor()
cur.execute(
'UPDATE calibration SET state = ? WHERE id = ?',
[int(self.state), self.id]
)
@staticmethod
def get_from_id(calibration_id: int, db: sqlite3.Connection) -> Optional['Calibration']:
cur = db.cursor()
response = cur.execute(
'SELECT ' + Calibration.select_args() + ' FROM calibration WHERE id = ?;',
[calibration_id]
)
return Calibration.from_row(response.fetchone())
class Object: class Object:
@staticmethod @staticmethod
def select_args() -> str: def select_args() -> str:
return 'id, name, calibrated' return 'id, name, calibration_id'
def __init__(self, object_id: int, name: str, calibrated: int): def __init__(self, object_id: int, name: str, calibration: int):
self.id = object_id self.id = object_id
self.name = name self.name = name
self.calibration_id = calibration
# 0 means no data available
# 1 means data available but calibration not done
# 2 means calibration done
self.calibrated = calibrated
@staticmethod @staticmethod
def create(name: str, db: sqlite3.Connection) -> 'Object': def create(name: str, db: sqlite3.Connection) -> 'Object':
cur = db.cursor() cur = db.cursor()
response = cur.execute( response = cur.execute(
'INSERT INTO object(name, calibrated) VALUES (?, ?) RETURNING ' + Object.select_args() + ';', 'INSERT INTO object(name, calibration_id) VALUES (?, ?) RETURNING ' + Object.select_args() + ';',
[name, 0] [name, None]
) )
return Object.from_row(response.fetchone()) return Object.from_row(response.fetchone())
def save(self, db: sqlite3.Connection): def save(self, db: sqlite3.Connection):
cur = db.cursor() cur = db.cursor()
cur.execute( cur.execute(
'UPDATE object SET name = ? WHERE id = ?', 'UPDATE object SET name = ?, calibration_id = ? WHERE id = ?',
[self.name, self.id] [self.name, self.calibration_id, self.id]
) )
@staticmethod @staticmethod
@ -94,3 +138,9 @@ class Object:
[] []
) )
return list(map(Object.from_row, response.fetchall())) return list(map(Object.from_row, response.fetchall()))
def calibration(self, db: sqlite3.Connection) -> Optional[Calibration]:
if self.calibration_id is None:
return None
return Calibration.get_from_id(self.calibration_id, db)

View File

@ -1,7 +1,14 @@
DROP TABLE IF EXISTS object; DROP TABLE IF EXISTS object;
DROP TABLE IF EXISTS calibration;
CREATE TABLE calibration (
id INTEGER PRIMARY KEY AUTOINCREMENT,
state INTEGER NOT NULL
);
CREATE TABLE object ( CREATE TABLE object (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, name TEXT NOT NULL,
calibrated INT NOT NULL calibration_id INTEGER,
CONSTRAINT fk_calibration FOREIGN KEY(calibration_id) REFERENCES calibration(id)
); );

View File

@ -30,7 +30,7 @@
</div> </div>
</div> </div>
<progress id="progress-bar" class="progress is-link" style="display: none;" value="0" max="1000"></progress> <progress id="progress-bar" class="progress is-link" style="display: none;" value="0" max="1000"></progress>
<div id="calibrate" style="display: none;"> <div id="calibrate" {% if calibration.state == 0 %}style="display: none;"{% endif %}>
<p>Si les données d'étalonnage conviennent, appuyez sur le bouton ci-dessous pour procéder à l'étalonnage du scanner</p> <p>Si les données d'étalonnage conviennent, appuyez sur le bouton ci-dessous pour procéder à l'étalonnage du scanner</p>
<button id="calibrate-button" class="button is-link">Étalonner le scanner</button> <button id="calibrate-button" class="button is-link">Étalonner le scanner</button>
<p id="calibration-info"></p> <p id="calibration-info"></p>
@ -41,7 +41,7 @@
{% block extrajs %} {% block extrajs %}
<script> <script>
let scanIndex = 0; let scanIndex = 1;
let progress = 0; let progress = 0;
let previewButton = document.getElementById('preview-button'); let previewButton = document.getElementById('preview-button');
let scanButton = document.getElementById('scan-button'); let scanButton = document.getElementById('scan-button');
@ -74,6 +74,20 @@
buttons.forEach(x => x.removeAttribute('disabled')); buttons.forEach(x => x.removeAttribute('disabled'));
}); });
// If we already have calibration images, we show them right now
if ({% if calibration.state > 0 %}true{% else %}false{% endif %}) {
let cell, img;
{% for led in leds %}
cell = document.createElement('div');
cell.classList.add('cell');
img = document.createElement('img');
img.classList.add('is-loading');
img.src = '/data/calibration/{{ calibration.id }}/{{ led }}.jpg?v=0';
cell.appendChild(img);
grid.appendChild(cell);
{% endfor %}
}
scanButton.addEventListener('click', async () => { scanButton.addEventListener('click', async () => {
scanIndex++; scanIndex++;
progress = 0; progress = 0;
@ -84,7 +98,7 @@
progressBar.style.display = "block"; progressBar.style.display = "block";
grid.innerHTML = ''; grid.innerHTML = '';
let response = await fetch('/api/scan-for-calibration/{{ object.id }}'); let response = await fetch('/api/scan-for-calibration/{{ calibration.id }}');
let reader = response.body.pipeThrough(new TextDecoderStream()).getReader(); let reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
while (true) { while (true) {
@ -103,7 +117,7 @@
cell.classList.add('cell'); cell.classList.add('cell');
let img = document.createElement('img'); let img = document.createElement('img');
img.classList.add('is-loading'); img.classList.add('is-loading');
img.src = '/data/{{ object.id }}/calibration/' + uuid + '.jpg?v=' + scanIndex; img.src = '/data/calibration/{{ calibration.id }}/' + uuid + '.jpg?v=' + scanIndex;
cell.appendChild(img); cell.appendChild(img);
grid.appendChild(cell); grid.appendChild(cell);
} }
@ -113,7 +127,7 @@
calibrateButton.classList.add('is-loading'); calibrateButton.classList.add('is-loading');
await fetch('/api/calibrate/{{ object.id }}'); await fetch('/api/calibrate/{{ object.id }}');
window.location.href = '/calibration/{{ object.id }}'; window.location.href = '/calibration/{{ calibration.id }}';
}); });

View File

@ -3,7 +3,7 @@
{% block content %} {% block content %}
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<h1 class="title is-2">Visualisation de l'étalonnage de {{ object.name }}</h1> <h1 class="title is-2">Visualisation de l'étalonnage {{ calibration.id }}</h1>
<div class="columns"> <div class="columns">
<div class="column"> <div class="column">
<h2 class="title is-3">Positions 3D des LEDs</h2> <h2 class="title is-3">Positions 3D des LEDs</h2>

View File

@ -6,17 +6,17 @@
<h1 class="title">{{ object.name }}</h1> <h1 class="title">{{ object.name }}</h1>
<div class="field is-grouped is-grouped-multiline"> <div class="field is-grouped is-grouped-multiline">
<div class="control"> <div class="control">
{% if object.calibrated == 0 %} {% if calibration is none or calibration.state == 0 %}
<span class="tags has-addons"> <span class="tags has-addons">
<span class="tag is-dark">étalonnage</span> <span class="tag is-dark">étalonnage</span>
<span class="tag is-danger">données manquantes</span> <span class="tag is-danger">aucune donnée</span>
</span> </span>
{% elif object.calibrated == 1 %} {% elif calibration.state == 1 %}
<span class="tags has-addons"> <span class="tags has-addons">
<span class="tag is-dark">étalonnage</span> <span class="tag is-dark">étalonnage</span>
<span class="tag is-warning">pas fait</span> <span class="tag is-warning">pas fait</span>
</span> </span>
{% elif object.calibrated == 2 %} {% elif calibration.state == 2 %}
<a href="/calibration/{{ object.id }}" class="tags has-addons"> <a href="/calibration/{{ object.id }}" class="tags has-addons">
<span class="tag is-dark">étalonnage</span> <span class="tag is-dark">étalonnage</span>
<span class="tag is-success">fait</span> <span class="tag is-success">fait</span>
@ -24,8 +24,11 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% if object.calibrated == 0 %} {% if calibration is none or calibration.state == 0 %}
<a href="/calibrate/{{ object.id }}" class="button is-link">Étalonner le scanner</a> <div class="field is-grouped">
<a href="/calibrate/{{ object.id }}" class="button is-link">Étalonner le scanner</a>
<a href="#" title="Non implémenté" disabled="disabled" class="button is-link">Utiliser le dernier étalonnage connu</a>
</div>
{% endif %} {% endif %}
</div> </div>
</section> </section>

View File

@ -113,7 +113,7 @@ export class Engine {
engine.domElement = domElement; engine.domElement = domElement;
engine.initHtml(); engine.initHtml();
let request = await fetch('/data/' + objectId + '/calibration.json'); let request = await fetch('/data/calibration/' + objectId + '/calibration.json');
let calibration = await request.json(); let calibration = await request.json();
engine.initScene(calibration); engine.initScene(calibration);
engine.initListeners(); engine.initListeners();
@ -296,7 +296,7 @@ export class Engine {
showImage(led: Led): void { showImage(led: Led): void {
if (led.on) { if (led.on) {
this.selectedObject.innerText = led.name.split('.')[0] + ' (' + (<number> this.leds.currentLedIndex + 1) + '/' + this.leds.children.length + ')'; this.selectedObject.innerText = led.name.split('.')[0] + ' (' + (<number> this.leds.currentLedIndex + 1) + '/' + this.leds.children.length + ')';
this.ledView.src = '/data/' + this.objectId + '/calibration/' + led.name; this.ledView.src = '/data/calibration/' + this.objectId + '/' + led.name;
this.ledView.style.display = 'block'; this.ledView.style.display = 'block';
} else { } else {
this.selectedObject.innerText = 'aucune'; this.selectedObject.innerText = 'aucune';