Working on acquisition

This commit is contained in:
Thomas Forgione 2024-07-17 16:51:56 +02:00
parent 2691df360f
commit 586c585980
6 changed files with 285 additions and 33 deletions

92
app.py
View File

@ -6,6 +6,7 @@ import os
from os.path import join
import sqlite3
import uuid
from typing import Optional
from . import db, config, scanner, calibration
app = Flask(__name__)
@ -33,7 +34,7 @@ def get_calibration(conn: sqlite3.Connection) -> db.Calibration:
@app.context_processor
def inject_stage_and_region():
conn = db.get()
return dict(calibration=get_calibration(conn), CalibrationState=db.CalibrationState)
return dict(calibration=get_calibration(conn), leds=config.LEDS_UUIDS, CalibrationState=db.CalibrationState)
@app.before_request
@ -63,15 +64,33 @@ def create_object():
@app.route('/object/<id>')
def object(id: int):
conn = db.get()
object = db.Object.get_from_id(id, conn)
object = db.Object.get_from_id(id, conn).full(conn)
return render_template('object.html', object=object)
@app.route('/scan/<id>')
def scan(id: int):
conn = db.get()
calibration_id = session.get('calibration_id', None)
object = db.Object.get_from_id(id, conn)
return render_template('object.html', object=object)
if calibration_id is None:
raise RuntimeError("Impossible de faire l'acquisition sans étalonnage")
return render_template('scan.html', object=object)
@app.route('/scan-acquisition/<id>')
def scan_existing(id: int):
conn = db.get()
calibration_id = session.get('calibration_id', None)
acquisition = db.Acquisition.get_from_id(id, conn)
object = acquisition.object(conn)
if calibration_id is None:
raise RuntimeError("Impossible de faire l'acquisition sans étalonnage")
return render_template('scan.html', object=object, acquisition=acquisition)
@app.route("/calibrate/")
@ -85,7 +104,7 @@ def calibrate():
calibration = db.Calibration.get_from_id(session['calibration_id'], conn)
if calibration.state in [db.CalibrationState.Empty, db.CalibrationState.HasData]:
return render_template('calibrate.html', leds=config.LEDS_UUIDS)
return render_template('calibrate.html')
else:
return render_template('calibration.html', calibration=calibration)
@ -146,6 +165,71 @@ def scan_calibration():
return app.response_class(generate(), mimetype='text/plain')
@app.route("/api/scan-for-object/<object_id>")
def scan_object(object_id: int):
conn = db.get()
calibration_id = session.get('calibration_id', None)
if calibration_id is None:
raise RuntimeError("Impossible de faire l'acquisition sans étalonnage")
object = db.Object.get_from_id(object_id, conn)
if object is None:
raise RuntimeError(f"Aucun objet d'id {object_id}")
with conn:
acquisition = object.add_acquisition(calibration_id, conn)
def generate():
yield str(acquisition.id)
length = len(config.LEDS_UUIDS)
for index, led_uuid in enumerate(scanner.scan(join(config.OBJECT_DIR, str(object.id), str(acquisition.id)))):
yield f"{led_uuid},{(index+1)/length}\n"
return app.response_class(generate(), mimetype='text/plain')
@app.route("/api/scan-for-acquisition/<acquisition_id>")
def scan_acquisition(acquisition_id: int):
conn = db.get()
calibration_id = session.get('calibration_id', None)
if calibration_id is None:
raise RuntimeError("Impossible de faire l'acquisition sans étalonnage")
acquisition = db.Acquisition.get_from_id(acquisition_id, conn)
if acquisition is None:
raise RuntimeError(f"Aucun acquisition d'id {acquisition_id}")
object = acquisition.object(conn)
def generate():
length = len(config.LEDS_UUIDS)
for index, led_uuid in enumerate(scanner.scan(join(config.OBJECT_DIR, str(object.id), str(acquisition.id)))):
yield f"{led_uuid},{(index+1)/length}\n"
return app.response_class(generate(), mimetype='text/plain')
@app.route("/validate-acquisition/<acquisition_id>")
def validate_acquisition(acquisition_id: int):
conn = db.get()
acquisition = db.Acquisition.get_from_id(acquisition_id, conn)
if acquisition is None:
raise f"Aucune acquisition d'id {acquisition_id}"
object = acquisition.object(conn)
acquisition.validated = True
with conn:
acquisition.save(conn)
return redirect(f'/object/{object.id}')
@app.route('/api/use-last-calibration')
def use_last_calibration():
conn = db.get()

77
db.py
View File

@ -128,18 +128,20 @@ Calibration.Dummy = Calibration(-1, CalibrationState.Empty, None)
class Acquisition:
@staticmethod
def select_args() -> str:
return 'id, calibration_id, object_id'
return 'id, calibration_id, object_id, date, validated'
def __init__(self, acquisition_id: int, calibration_id: int, object_id: int):
self.id = object_id
def __init__(self, acquisition_id: int, calibration_id: int, object_id: int, date: int, validated: int):
self.id = acquisition_id
self.calibration_id = calibration_id
self.object_id = object_id
self.date = datetime.fromtimestamp(date)
self.validated = validated != 0
def save(self, db: sqlite3.Connection):
cur = db.cursor()
cur.execute(
'UPDATE acquisition SET calibration_id = ?, object_id = ? WHERE id = ?',
[self.calibration_id, self.object_id, self.id]
'UPDATE acquisition SET calibration_id = ?, object_id = ?, date = ?, validated = ? WHERE id = ?',
[self.calibration_id, self.object_id, self.date.timestamp(), 1 if self.validated else 0, self.id]
)
@staticmethod
@ -163,6 +165,19 @@ class Acquisition:
return Calibration.get_from_id(self.calibration_id, db)
def object(self, db: sqlite3.Connection) -> 'Object':
return Object.get_from_id(self.object_id, db)
def get_pretty_date(self) -> str:
return dateutils.format(self.date)
class FullObject:
def __init__(self, object_id: int, name: str, acquisitions: list[Acquisition]):
self.id = object_id
self.name = name
self.acquisitions = acquisitions
class Object:
@staticmethod
@ -218,11 +233,21 @@ class Object:
def add_acquisition(self, calibration_id: int, db: sqlite3.Connection) -> Acquisition:
cur = db.cursor()
response = cur.execute(
'INSERT INTO acquisition(calibration_id, object_id) VALUES (?, ?) RETURNING ' + Acquisition.select_args() + ';',
[calibration_id, self.id]
'INSERT INTO acquisition(calibration_id, object_id, date, validated) VALUES (?, ?, ?, ?) RETURNING ' + Acquisition.select_args() + ';',
[calibration_id, self.id, datetime.now().timestamp(), 0]
)
return Acquisition.from_row(response.fetchone())
def full(self, db: sqlite3.Connection) -> FullObject:
cur = db.cursor()
response = cur.execute(
'SELECT ' + Acquisition.select_args() + ' FROM acquisition WHERE object_id = ? ORDER BY date DESC;',
[self.id]
)
acquisitions = list(map(lambda x: Acquisition.from_row(x), response.fetchall()))
print(acquisitions)
return FullObject(self.id, self.name, acquisitions)
def main():
# Move current data to backup dir
@ -245,26 +270,26 @@ def main():
init(db)
# Create a new object
with db:
Object.create('Mon premier objet', db)
# calibration = Calibration.create(db)
# object.add_acquisition(calibration.id, db)
# object.add_acquisition(calibration.id, db)
# object.add_acquisition(calibration.id, db)
# calibration = Calibration.create(db)
# object.add_acquisition(calibration.id, db)
# object.add_acquisition(calibration.id, db)
# object.add_acquisition(calibration.id, db)
# with db:
# Object.create('Mon premier objet', db)
# # calibration = Calibration.create(db)
# # object.add_acquisition(calibration.id, db)
# # object.add_acquisition(calibration.id, db)
# # object.add_acquisition(calibration.id, db)
# # calibration = Calibration.create(db)
# # object.add_acquisition(calibration.id, db)
# # object.add_acquisition(calibration.id, db)
# # object.add_acquisition(calibration.id, db)
Object.create('Mon deuxième objet', db)
# calibration = Calibration.create(db)
# object.add_acquisition(calibration.id, db)
# object.add_acquisition(calibration.id, db)
# object.add_acquisition(calibration.id, db)
# calibration = Calibration.create(db)
# object.add_acquisition(calibration.id, db)
# object.add_acquisition(calibration.id, db)
# object.add_acquisition(calibration.id, db)
# Object.create('Mon deuxième objet', db)
# # calibration = Calibration.create(db)
# # object.add_acquisition(calibration.id, db)
# # object.add_acquisition(calibration.id, db)
# # object.add_acquisition(calibration.id, db)
# # calibration = Calibration.create(db)
# # object.add_acquisition(calibration.id, db)
# # object.add_acquisition(calibration.id, db)
# # object.add_acquisition(calibration.id, db)
if __name__ == '__main__':

View File

@ -12,6 +12,8 @@ CREATE TABLE acquisition (
id INTEGER PRIMARY KEY AUTOINCREMENT,
calibration_id INTEGER NOT NULL,
object_id INTEGER NOT NULL,
date INTEGER NOT NULL,
validated INT NOT NULL,
CONSTRAINT fk_calibration FOREIGN KEY(calibration_id) REFERENCES calibration(id),
CONSTRAINT fk_object FOREIGN KEY(object_id) REFERENCES object(id)
);

View File

@ -33,7 +33,6 @@
<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>
<button id="calibrate-button" class="button is-link">Étalonner le scanner</button>
<p id="calibration-info"></p>
</div>
</div>
</section>
@ -53,7 +52,6 @@
let errorContent = document.getElementById('error-content');
let calibrateDiv = document.getElementById('calibrate');
let calibrateButton = document.getElementById('calibrate-button');
let calibrationInfo = document.getElementById('calibration-info');
previewButton.addEventListener('click', async () => {
buttons.forEach(x => x.setAttribute('disabled', 'disabled'));

View File

@ -2,8 +2,27 @@
{% block content %}
<section class="section">
<div class="container">
<div class="container content">
<h1 class="title">{{ object.name }}</h1>
{% if object.acquisitions %}
<div class="fixed-grid has-6-cols">
<div class="grid">
{% for acquisition in object.acquisitions %}
<a class="cell" href="/scan-acquisition/{{ acquisition.id }}">
<div class="has-text-centered p-3" style="border-radius: 15px; border-width: 1px; border-color: {% if acquisition.validated %}green{% else %}red{% endif %}; border-style: solid;">
<div>
<img src="/data/objects/{{ object.id }}/{{ acquisition.id }}/{{ leds[0] }}.jpg">
</div>
<p>
{{ acquisition.get_pretty_date() }}
</p>
</div>
</a>
{% endfor %}
</div>
</div>
{% endif %}
{% if calibration.state == CalibrationState.IsValidated %}
<a href="/scan/{{ object.id }}" class="button is-link">Faire un scan</a>
{% else %}

124
templates/scan.html Normal file
View File

@ -0,0 +1,124 @@
{% extends "base.html" %}
{% block content %}
<section class="section">
<div class="container">
<h1 class="title">Faire une acquisition</h1>
<div class="mb-2">
<p>Placez l'objet devant le scanner puis appuyez sur le bouton pour lancer l'acquisition.</p>
</div>
<div class="field is-grouped is-grouped-multiline">
<div class="control">
<button {% if acquisition and acquisition.validated %}disabled{% endif %} id="scan-button" class="button is-link">Lancer l'acquisition</button>
</div>
</div>
<article id="error-container" class="message is-danger" style="display: none;">
<div id="error-content" class="message-body">
</div>
</article>
<div class="fixed-grid has-8-cols">
<div id="grid" class="grid">
</div>
</div>
<progress id="progress-bar" class="progress is-link" style="display: none;" value="0" max="1000"></progress>
<div id="calibrate" {% if not acquisition %}style="display: none;"{% endif %}>
<p>Si les données acquises conviennent, appuyez sur le bouton ci-dessous pour valider l'acquisition.</p>
{% if acquisition %}
{% if acquisition.validated %}
<button disabled id="calibrate-button" class="button is-link">Cette acquisition a déjà été validée</button>
{% else %}
<a href="/validate-acquisition/{{ acquisition.id }}" id="calibrate-button" class="button is-link">Valider l'acquisition</a>
{% endif %}
{% else %}
<a id="calibrate-button" class="button is-link">Valider l'acquisition</a>
{% endif %}
</div>
</div>
</section>
{% endblock content %}
{% block extrajs %}
<script>
let acquisitionId = {% if acquisition %}{{ acquisition.id }}{% else %}null{% endif %};
let scanIndex = 1;
let progress = 0;
let scanButton = document.getElementById('scan-button');
let progressBar = document.getElementById('progress-bar');
let grid = document.getElementById('grid');
let buttons = [scanButton];
let errorContainer = document.getElementById('error-container');
let errorContent = document.getElementById('error-content');
let calibrateDiv = document.getElementById('calibrate');
let calibrateButton = document.getElementById('calibrate-button');
// If we already have calibration images, we show them right now
if (acquisitionId !== null) {
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/objects/{{ object.id }}/' + acquisitionId + '/{{ led }}.jpg?v=0';
cell.appendChild(img);
grid.appendChild(cell);
{% endfor %}
}
scanButton.addEventListener('click', async () => {
scanIndex++;
progress = 0;
progressBar.value = 0;
buttons.forEach(x => x.setAttribute('disabled', 'disabled'));
scanButton.classList.add('is-loading');
progressBar.style.display = "block";
grid.innerHTML = '';
let response;
if (acquisitionId === null) {
response = await fetch('/api/scan-for-object/{{ object.id }}');
} else {
response = await fetch('/api/scan-for-acquisition/' + acquisitionId);
}
let reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
while (true) {
let { value, done } = await reader.read();
if (done) {
buttons.forEach(x => x.removeAttribute('disabled'));
scanButton.classList.remove('is-loading');
calibrateDiv.style.display = "block";
break;
}
if (acquisitionId === null) {
acquisitionId = parseInt(value, 10);
calibrateButton.setAttribute('href', '/validate-acquisition/' + acquisitionId);
continue;
}
let [ uuid, ratio ] = value.trim().split(',');
progress = Math.ceil(1000 * parseFloat(ratio));
let cell = document.createElement('div');
cell.classList.add('cell');
let img = document.createElement('img');
img.classList.add('is-loading');
img.src = '/data/objects/{{ object.id }}/' + acquisitionId + '/' + uuid + '.jpg?v=' + scanIndex;
cell.appendChild(img);
grid.appendChild(cell);
}
});
function refreshProgressBar() {
if (progress !== progressBar.value) {
progressBar.value = Math.min(progressBar.value + 5, progress);
}
requestAnimationFrame(refreshProgressBar);
}
refreshProgressBar();
</script>
{% endblock extrajs %}