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 .device
data data
data-keep data-*
secret.py
node_modules node_modules
static/calibration-visualiser.* static/calibration-visualiser.*

63
app.py
View File

@ -9,7 +9,17 @@ import uuid
from . import db, config, scanner, calibration from . import db, config, scanner, calibration
app = Flask(__name__) 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: def get_calibration(conn: sqlite3.Connection) -> db.Calibration:
@ -51,19 +61,45 @@ def object(id: int):
@app.route('/scan/<id>') @app.route('/scan/<id>')
def scan(id: int): def scan(id: int):
conn = db.get() conn = db.get()
print(session)
object = db.Object.get_from_id(id, conn) object = db.Object.get_from_id(id, conn)
return render_template('object.html', object=object) return render_template('object.html', object=object)
@app.route("/calibrate/") @app.route("/calibrate/")
def calibrate(): def calibrate():
print(session)
conn = db.get() conn = db.get()
if 'calibration_id' not in session: if 'calibration_id' not in session:
with conn: with conn:
calibration = db.Calibration.create(conn) calibration = db.Calibration.create(conn)
session['calibration_id'] = calibration.id 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>") @app.route("/api/preview/<id>")
@ -102,9 +138,19 @@ def scan_calibration():
return app.response_class(generate(), mimetype='text/plain') return app.response_class(generate(), mimetype='text/plain')
@app.route("/api/calibrate/<id>") @app.route('/api/use-last-calibration')
def run_calibration(id: int): def use_last_calibration():
conn = db.get() 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) calib = db.Calibration.get_from_id(id, conn)
if calib is None: if calib is None:
return 'oops', 404 return 'oops', 404
@ -133,13 +179,6 @@ def validate_calibration():
return redirect('/') 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>') @app.route('/static/<path:path>')
def send_static(path): def send_static(path):
return send_from_directory('static', path) return send_from_directory('static', path)

View File

@ -20,7 +20,7 @@ def print_error(msg: str):
def calibrate(input_dir: str): def calibrate(input_dir: str):
# Load all images # 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] images = [np.asarray(Image.open(os.path.join(input_dir, x))) for x in image_names]
# Camera parameters # Camera parameters

View File

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

49
db.py
View File

@ -1,12 +1,12 @@
#!/usr/bin/env python #!/usr/bin/env python
from enum import IntEnum from enum import IntEnum
from datetime import datetime
from flask import g from flask import g
import os import os
from os.path import join from os.path import join
import shutil import shutil
import sqlite3 import sqlite3
import sys
from typing import Optional from typing import Optional
if __name__ != '__main__': if __name__ != '__main__':
@ -18,7 +18,7 @@ else:
def get() -> sqlite3.Connection: def get() -> sqlite3.Connection:
if 'db' not in g: if 'db' not in g:
g.db = sqlite3.connect( g.db = sqlite3.connect(
os.environ.get('DATABASE_PATH', 'db.sqlite'), config.DATABASE_PATH,
detect_types=sqlite3.PARSE_DECLTYPES, detect_types=sqlite3.PARSE_DECLTYPES,
) )
g.db.row_factory = sqlite3.Row g.db.row_factory = sqlite3.Row
@ -69,11 +69,15 @@ class Calibration:
[int(CalibrationState.Empty)] [int(CalibrationState.Empty)]
) )
calibration = Calibration.from_row(response.fetchone()) calibration = Calibration.from_row(response.fetchone())
if calibration is None:
return None
os.makedirs(join(config.CALIBRATION_DIR, str(calibration.id))) os.makedirs(join(config.CALIBRATION_DIR, str(calibration.id)))
return calibration return calibration
@staticmethod @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) return Calibration(*row)
def save(self, db: sqlite3.Connection): def save(self, db: sqlite3.Connection):
@ -92,6 +96,15 @@ class Calibration:
) )
return Calibration.from_row(response.fetchone()) 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) Calibration.Dummy = Calibration(-1, CalibrationState.Empty)
@ -114,7 +127,9 @@ class Acquisition:
) )
@staticmethod @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) return Acquisition(*row)
@staticmethod @staticmethod
@ -150,7 +165,9 @@ class Object:
) )
@staticmethod @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) return Object(*row)
@staticmethod @staticmethod
@ -192,20 +209,25 @@ class Object:
def main(): def main():
if config.MODE != 'debug': # Move current data to backup dir
print('Can only reset db in debug mode') if os.path.isdir(config.DATA_DIR):
sys.exit(1) # 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( db = sqlite3.connect(
os.environ.get('DATABASE_PATH', 'db.sqlite'), config.DATABASE_PATH,
detect_types=sqlite3.PARSE_DECLTYPES, detect_types=sqlite3.PARSE_DECLTYPES,
) )
db.row_factory = sqlite3.Row db.row_factory = sqlite3.Row
init(db) init(db)
if os.path.isdir(config.DATA_DIR):
shutil.rmtree(config.DATA_DIR)
# Create a new object # Create a new object
with db: with db:
Object.create('Mon premier objet', db) Object.create('Mon premier objet', db)
@ -230,5 +252,4 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
if len(sys.argv) > 1 and sys.argv[1] == 'reset': main()
main()

View File

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

View File

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

View File

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

View File

@ -18,7 +18,7 @@
<a href="/calibrate/" class="button is-link">Étalonner le scanner</a> <a href="/calibrate/" class="button is-link">Étalonner le scanner</a>
</div> </div>
<div class="control"> <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> </div>
</div> </div>
@ -41,5 +41,10 @@
modal.classList.remove('is-active'); 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> </script>
{% endif %}{% endblock %} {% endif %}{% endblock %}

View File

@ -39,6 +39,13 @@ function getImageElementById(id: string): HTMLImageElement {
return element; 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. * The class that manages the interface for the calibration visualisation.
*/ */
@ -105,7 +112,7 @@ export class Engine {
/** Initialises the engine. */ /** Initialises the engine. */
static async create(domId: string) { static async create(domId: string) {
let calibrationId = parseInt(window.location.pathname.split('/')[2], 10); let calibrationId = window.CALIBRATION_ID;
let domElement = getElementById(domId); let domElement = getElementById(domId);
let engine = new Engine(); let engine = new Engine();