This commit is contained in:
Thomas Forgione 2024-07-15 11:06:37 +02:00
parent b1ddbe23ee
commit f6f34d6260
10 changed files with 134 additions and 48 deletions

4
.gitignore vendored
View File

@ -1,7 +1,7 @@
db.sqlite
.device
data
data-keep
data-*
secret.py
node_modules
static/calibration-visualiser.*

63
app.py
View File

@ -9,7 +9,17 @@ import uuid
from . import db, config, scanner, calibration
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(20).hex()
# Manage secret key
try:
from . import secret
app.config['SECRET_KEY'] = secret.SECRET_KEY
except ImportError:
# Secret key file does not exist, create it
secret = os.urandom(50).hex()
with open('secret.py', 'w') as f:
f.write(f'SECRET_KEY = "{secret}"')
app.config['SECRET_KEY'] = secret
def get_calibration(conn: sqlite3.Connection) -> db.Calibration:
@ -51,19 +61,45 @@ def object(id: int):
@app.route('/scan/<id>')
def scan(id: int):
conn = db.get()
print(session)
object = db.Object.get_from_id(id, conn)
return render_template('object.html', object=object)
@app.route("/calibrate/")
def calibrate():
print(session)
conn = db.get()
if 'calibration_id' not in session:
with conn:
calibration = db.Calibration.create(conn)
session['calibration_id'] = calibration.id
return render_template('calibrate.html', leds=config.LEDS_UUIDS)
else:
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)
else:
return render_template('calibration.html', calibration=calibration)
@app.route("/new-calibration")
def new_calibration():
conn = db.get()
with conn:
calibration = db.Calibration.create(conn)
session['calibration_id'] = calibration.id
return redirect('/calibrate')
@app.route("/cancel-calibration")
def cancel_calibration():
conn = db.get()
calibration = db.Calibration.get_from_id(session['calibration_id'], conn)
calibration.state = db.CalibrationState.HasData
with conn:
calibration.save(conn)
return redirect('/calibrate')
@app.route("/api/preview/<id>")
@ -102,9 +138,19 @@ def scan_calibration():
return app.response_class(generate(), mimetype='text/plain')
@app.route("/api/calibrate/<id>")
def run_calibration(id: int):
@app.route('/api/use-last-calibration')
def use_last_calibration():
conn = db.get()
calibration = db.Calibration.get_last(conn)
session['calibration_id'] = calibration.id
print(session);
return 'ok'
@app.route("/api/calibrate")
def run_calibration():
conn = db.get()
id = session['calibration_id']
calib = db.Calibration.get_from_id(id, conn)
if calib is None:
return 'oops', 404
@ -133,13 +179,6 @@ def validate_calibration():
return redirect('/')
@app.route("/calibration/<id>")
def calibration_page(id: int):
conn = db.get()
calibration = db.Calibration.get_from_id(id, conn)
return render_template('calibration.html', calibration=calibration)
@app.route('/static/<path:path>')
def send_static(path):
return send_from_directory('static', path)

View File

@ -20,7 +20,7 @@ def print_error(msg: str):
def calibrate(input_dir: str):
# Load all images
image_names = sorted(os.listdir(input_dir))
image_names = list(filter(lambda x: x != 'calibration.json', sorted(os.listdir(input_dir))))
images = [np.asarray(Image.open(os.path.join(input_dir, x))) for x in image_names]
# Camera parameters

View File

@ -1,9 +1,11 @@
from os.path import join
MODE = 'debug'
DATA_DIR = 'data'
BACKUPS_DIR = 'data-backups'
CALIBRATION_DIR = join(DATA_DIR, 'calibrations')
OBJECT_DIR = join(DATA_DIR, 'objects')
DATABASE_PATH = join(DATA_DIR, 'db.sqlite')
SECRET_KEY = 'tobedefined'
LEDS_UUIDS = [
'ac59350e-3787-46d2-88fa-743c1d34fe86',

49
db.py
View File

@ -1,12 +1,12 @@
#!/usr/bin/env python
from enum import IntEnum
from datetime import datetime
from flask import g
import os
from os.path import join
import shutil
import sqlite3
import sys
from typing import Optional
if __name__ != '__main__':
@ -18,7 +18,7 @@ else:
def get() -> sqlite3.Connection:
if 'db' not in g:
g.db = sqlite3.connect(
os.environ.get('DATABASE_PATH', 'db.sqlite'),
config.DATABASE_PATH,
detect_types=sqlite3.PARSE_DECLTYPES,
)
g.db.row_factory = sqlite3.Row
@ -69,11 +69,15 @@ class Calibration:
[int(CalibrationState.Empty)]
)
calibration = Calibration.from_row(response.fetchone())
if calibration is None:
return None
os.makedirs(join(config.CALIBRATION_DIR, str(calibration.id)))
return calibration
@staticmethod
def from_row(row: sqlite3.Row) -> 'Calibration':
def from_row(row: Optional['sqlite3.Row']) -> Optional['Calibration']:
if row is None:
return None
return Calibration(*row)
def save(self, db: sqlite3.Connection):
@ -92,6 +96,15 @@ class Calibration:
)
return Calibration.from_row(response.fetchone())
@staticmethod
def get_last(db: sqlite3.Connection) -> Optional['Calibration']:
cur = db.cursor()
response = cur.execute(
'SELECT ' + Calibration.select_args() + ' FROM calibration WHERE state = 3 ORDER BY id DESC LIMIT 1;',
[]
)
return Calibration.from_row(response.fetchone())
Calibration.Dummy = Calibration(-1, CalibrationState.Empty)
@ -114,7 +127,9 @@ class Acquisition:
)
@staticmethod
def from_row(row: sqlite3.Row) -> 'Acquisition':
def from_row(row: Optional[sqlite3.Row]) -> Optional['Acquisition']:
if row is None:
return None
return Acquisition(*row)
@staticmethod
@ -150,7 +165,9 @@ class Object:
)
@staticmethod
def from_row(row: sqlite3.Row) -> 'Object':
def from_row(row: Optional[sqlite3.Row]) -> Optional['Object']:
if row is None:
return None
return Object(*row)
@staticmethod
@ -192,20 +209,25 @@ class Object:
def main():
if config.MODE != 'debug':
print('Can only reset db in debug mode')
sys.exit(1)
# Move current data to backup dir
if os.path.isdir(config.DATA_DIR):
# Ensure backup dir exists
os.makedirs(config.BACKUPS_DIR, exist_ok=True)
now = datetime.now()
dest = join(config.BACKUPS_DIR, f'{now.year}-{now.month:02}-{now.day:02}--{now.hour:02}-{now.minute:02}-{now.second:02}')
shutil.move(config.DATA_DIR, dest)
# Create new empty data dir
os.makedirs(config.DATA_DIR, exist_ok=True)
db = sqlite3.connect(
os.environ.get('DATABASE_PATH', 'db.sqlite'),
config.DATABASE_PATH,
detect_types=sqlite3.PARSE_DECLTYPES,
)
db.row_factory = sqlite3.Row
init(db)
if os.path.isdir(config.DATA_DIR):
shutil.rmtree(config.DATA_DIR)
# Create a new object
with db:
Object.create('Mon premier objet', db)
@ -230,5 +252,4 @@ def main():
if __name__ == '__main__':
if len(sys.argv) > 1 and sys.argv[1] == 'reset':
main()
main()

View File

@ -22,30 +22,30 @@
</div>
<div id="navbarBasicExample" class="navbar-menu">
<div class="navbar-end">
<a id="calibration-tag-0" class="calibration-tag navbar-item" href="#" {% if calibration.state != 0 %}style="display: none;"{% endif %}>
<a id="calibration-tag-0" class="calibration-tag navbar-item" href="/calibrate/" {% if calibration.state != 0 %}style="display: none;"{% endif %}>
<span id="calibration-tag-0" class="tags has-addons">
<span class="tag is-dark">étalonnage</span>
<span class="tag is-danger">aucune donnée</span>
</span>
</a>
<a id="calibration-tag-1" class="calibration-tag navbar-item" href="#" {% if calibration.state != 1 %}style="display: none;"{% endif %}>
<a id="calibration-tag-1" class="calibration-tag navbar-item" href="/calibrate/" {% if calibration.state != 1 %}style="display: none;"{% endif %}>
<span class="tags has-addons" >
<span class="tag is-dark">étalonnage</span>
<span class="tag is-warning">non calculé</span>
</span>
</a>
<a id="calibration-tag-2" class="calibration-tag navbar-item" href="#" {% if calibration.state != 2 %}style="display: none;"{% endif %}>
<a id="calibration-tag-2" class="calibration-tag navbar-item" href="/calibrate/" {% if calibration.state != 2 %}style="display: none;"{% endif %}>
<span class="tags has-addons">
<span class="tag is-dark">étalonnage</span>
<span class="tag is-warning">non validé</span>
</span>
</a>
<span id="calibration-tag-3" class="calibration-tag navbar-item" href="#" {% if calibration.state != 3 %}style="display: none;"{% endif %}>
<span, class="tags has-addons">
<a id="calibration-tag-3" class="calibration-tag navbar-item" href="/calibrate" {% if calibration.state != 3 %}style="display: none;"{% endif %}>
<span class="tags has-addons">
<span class="tag is-dark">étalonnage</span>
<span class="tag is-success">validé</span>
</span>
</span>
</a>
</div>
</div>
</div>

View File

@ -131,8 +131,8 @@
calibrateButton.addEventListener('click', async () => {
calibrateButton.classList.add('is-loading');
await fetch('/api/calibrate/{{ calibration.id }}');
window.location.href = '/calibration/{{ calibration.id }}';
await fetch('/api/calibrate');
window.location.reload();
});

View File

@ -18,7 +18,7 @@
<div class="column">
<h2 class="title is-3">Vue de la LED selectionnée</h2>
<div>
<img id="led-view">
<img id="led-view" style="width: 100%">
</div>
</div>
</div>
@ -41,13 +41,24 @@
<section>
<div class="container">
<div class="field is-grouped is-grouped-right">
<div class="control">
<a href="/calibrate" class="button">Retourner à la page d'acquisition</a>
</div>
<div class="control">
<a href="/validate-calibration" class="button is-link">Valider l'étalonnage</a>
</div>
<div class="field is-horizontal is-grouped is-grouped-right">
{% if calibration.state == CalibrationState.IsValidated %}
<div class="control">
<button disabled class="button">
Cet étalonnage a déjà été validé
</button>
</div>
<div class="control">
<a href="/new-calibration" class="button is-link">Créer un nouvel étalonnage</a>
</div>
{% else %}
<div class="control">
<a href="/cancel-calibration" class="button">Retourner à la page d'acquisition</a>
</div>
<div class="control">
<a href="/validate-calibration" class="button is-link">Valider l'étalonnage</a>
</div>
{% endif %}
</div>
</div>
</section>
@ -77,5 +88,6 @@
{% endblock extracss %}
{% block extrajs %}
<script>window.CALIBRATION_ID = {{ calibration.id }};</script>
<script src="/static/calibration-visualiser.js"></script>
{% endblock extrajs %}

View File

@ -18,7 +18,7 @@
<a href="/calibrate/" class="button is-link">Étalonner le scanner</a>
</div>
<div class="control">
<a href="/calibrate/" class="button is-link">Réutiliser le dernier étalonnage</a>
<button id="use-last-calibration-button" href="/use-last-calibration/" class="button is-link">Réutiliser le dernier étalonnage</button>
</div>
</div>
</div>
@ -41,5 +41,10 @@
modal.classList.remove('is-active');
});
});
document.getElementById('use-last-calibration-button').addEventListener('click', async () => {
let resp = await fetch('/api/use-last-calibration');
await resp.text();
window.location.href = '/scan/{{ object.id }}';
});
</script>
{% endif %}{% endblock %}

View File

@ -39,6 +39,13 @@ function getImageElementById(id: string): HTMLImageElement {
return element;
}
declare global {
interface Window {
/** This global variable must be set before including this script in the HTML page. */
CALIBRATION_ID: number;
}
}
/**
* The class that manages the interface for the calibration visualisation.
*/
@ -105,7 +112,7 @@ export class Engine {
/** Initialises the engine. */
static async create(domId: string) {
let calibrationId = parseInt(window.location.pathname.split('/')[2], 10);
let calibrationId = window.CALIBRATION_ID;
let domElement = getElementById(domId);
let engine = new Engine();