From 7eb06bbd57c5efda4c0617dc01a6e62ef96c2867 Mon Sep 17 00:00:00 2001 From: Nicolas Bertrand Date: Wed, 3 Sep 2025 22:19:03 +0200 Subject: [PATCH] WIP --- Readme.md | 36 +++++++++ capture-image.py | 51 +++++++++++++ src/nenuscanner/camera.py | 73 ++++++++++++++++++ src/nenuscanner/routes/__init__.py | 3 +- src/nenuscanner/routes/camera.py | 71 +++++++++++++++++ src/nenuscanner/templates/camera.html | 106 ++++++++++++++++++++++++++ 6 files changed, 339 insertions(+), 1 deletion(-) create mode 100644 capture-image.py create mode 100644 src/nenuscanner/routes/camera.py create mode 100644 src/nenuscanner/templates/camera.html diff --git a/Readme.md b/Readme.md index 6bacedc..64919cf 100644 --- a/Readme.md +++ b/Readme.md @@ -1,5 +1,41 @@ # NenuScanner +## Lancer l application flask depuis la racine + + +### Intall local de l applcation + +Avoir un venv + +puis + +``` +pip install -e . +``` + +dans src/nenuscanner + +cp config.local.py dans config.py + + +### Lancer l'application + +``` +flask --app . run --debug +``` + +resoudre dependance si besoins genre: + +``` +pip install gpiod==2.1.3 +``` + + + + + + + ## GPIO diff --git a/capture-image.py b/capture-image.py new file mode 100644 index 0000000..64348fa --- /dev/null +++ b/capture-image.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python + +# python-gphoto2 - Python interface to libgphoto2 +# http://github.com/jim-easterbrook/python-gphoto2 +# Copyright (C) 2015-22 Jim Easterbrook jim@jim-easterbrook.me.uk +# +# This file is part of python-gphoto2. +# +# python-gphoto2 is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# python-gphoto2 is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with python-gphoto2. If not, see +# . + +import logging +import locale +import os +import subprocess +import sys + +import gphoto2 as gp + +def main(): + locale.setlocale(locale.LC_ALL, '') + logging.basicConfig( + format='%(levelname)s: %(name)s: %(message)s', level=logging.WARNING) + callback_obj = gp.check_result(gp.use_python_logging()) + camera = gp.Camera() + camera.init() + print('Capturing image') + file_path = camera.capture(gp.GP_CAPTURE_IMAGE) + print('Camera file path: {0}/{1}'.format(file_path.folder, file_path.name)) + target = os.path.join('/tmp', file_path.name) + print('Copying image to', target) + camera_file = camera.file_get( + file_path.folder, file_path.name, gp.GP_FILE_TYPE_NORMAL) + camera_file.save(target) + subprocess.call(['xdg-open', target]) + camera.exit() + return 0 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/nenuscanner/camera.py b/src/nenuscanner/camera.py index 0e80ec6..414e163 100644 --- a/src/nenuscanner/camera.py +++ b/src/nenuscanner/camera.py @@ -2,12 +2,61 @@ import subprocess import gphoto2 as gp import shutil from . import leds, config +import subprocess +import json + + +def parse_config(lines): + config = {} + block = [] + for line in lines: + line = line.strip() + if not line: + continue + if line.startswith('/main/'): + # Nouveau bloc : traiter le précédent s'il existe + if block: + insert_block(config, block) + block = [] + block.append(line) + elif line == 'END': + block.append(line) + insert_block(config, block) + block = [] + else: + block.append(line) + return config + + +def insert_block(config, block): + path = block[0].strip('/').split('/') # ['main', 'actions', 'syncdatetimeutc'] + data = {} + for line in block[1:-1]: # Exclure le chemin et 'END' + if ':' in line: + key, value = line.split(':', 1) + key = key.strip() + value = value.strip() + if key.startswith('Choice'): + # Extrait les choix dans une liste + idx, label = value.split(' ', 1) + data.setdefault('Choices', []).append({'id': int(idx), 'label': label}) + else: + data[key] = value + + # Insère dans le dictionnaire hiérarchique + d = config + for part in path[:-1]: # Traverse les sections, ex: main -> actions + d = d.setdefault(part, {}) + d[path[-1]] = data # Attribue les données à la clé finale class Camera: def capture(self): return None + def config(self): + return None + class RealCamera(Camera): def __init__(self): @@ -35,6 +84,23 @@ class RealCamera(Camera): subprocess.run(['convert', output_file + '.jpg', '-resize', '10%', output_file + '.jpg']) raw.save(output_file + '.cr2') + def config(self): + res = subprocess.run(["gphoto2", "--list-all-config"], capture_output=True, encoding="utf-8") + + # print(res.stdout[:200]) + + configs = res.stdout.split("\n") + print(configs) + config_dict = parse_config(configs) + + # Sauvegarde en JSON + with open("configCamera.json", "w", encoding="utf-8") as f: + json.dump(config_dict, f, indent=2, ensure_ascii=False) + + def set_config(self, parameter, value): + subprocess.run(["gphoto2", "--set-config", f"{parameter}={value}"]) + return 0 + class DummyCamera(Camera): def __init__(self, leds: leds.DummyLeds): @@ -73,3 +139,10 @@ camera = DummyCamera(leds.get()) if config.CAMERA == "dummy" else RealCamera() def get(): return camera + + +def config(): + return camera.config() + +def set_config(parameter, value): + return camera.set_config(parameter, value) diff --git a/src/nenuscanner/routes/__init__.py b/src/nenuscanner/routes/__init__.py index 8038c73..3ef32e1 100644 --- a/src/nenuscanner/routes/__init__.py +++ b/src/nenuscanner/routes/__init__.py @@ -1,7 +1,7 @@ from flask import Blueprint, render_template from .. import db -from . import object, calibration, acquisition +from . import object, calibration, acquisition, camera blueprint = Blueprint('routes', __name__) @@ -20,3 +20,4 @@ def index(): blueprint.register_blueprint(object.blueprint, url_prefix='/object') blueprint.register_blueprint(calibration.blueprint, url_prefix='/calibration') blueprint.register_blueprint(acquisition.blueprint, url_prefix='/acquisition') +blueprint.register_blueprint(camera.blueprint, url_prefix='/camera') diff --git a/src/nenuscanner/routes/camera.py b/src/nenuscanner/routes/camera.py new file mode 100644 index 0000000..106e81f --- /dev/null +++ b/src/nenuscanner/routes/camera.py @@ -0,0 +1,71 @@ +from flask import Blueprint, render_template, request, redirect +import json + +from .. import camera as C + +blueprint = Blueprint('camera', __name__) + +# Routes for object management + + +@blueprint.route('/') +def get(): + """ + Returns the page showing camera configuration + """ + # Load configCamera.json + with open('configCamera.json', 'r') as f: + config = json.load(f) + + # Extract shutterspeed choices and current value + shutterspeed = config['main']['capturesettings']['shutterspeed'] + shutterspeed_choices = [ + {'value': c.get('id', idx), 'label': c['label']} + for idx, c in enumerate(shutterspeed['Choices']) + ] + shutterspeed_current = shutterspeed['Current'] + + # Extract aperture choices and current value + aperture = config['main']['capturesettings']['aperture'] + aperture_choices = [ + {'value': c.get('id', idx), 'label': c['label']} + for idx, c in enumerate(aperture['Choices']) + ] + aperture_current = aperture['Current'] + + return render_template( + 'camera.html', + shutterspeed_choices=shutterspeed_choices, + shutterspeed_current=shutterspeed_current, + aperture_choices=aperture_choices, + aperture_current=aperture_current + ) + + +@blueprint.route('/set', methods=['POST']) +def set_camera_settings(): + """ + Receives and processes new camera settings (shutterspeed, aperture) from the client. + """ + data = request.get_json() + shutterspeed = data.get('shutterspeed') + aperture = data.get('aperture') + + if shutterspeed is not None: + print(f"Received shutterspeed: {shutterspeed}") + C.set_config('shutterspeed', shutterspeed) + + if aperture is not None: + print(f"Received aperture: {aperture}") + C.set_config('aperture', aperture) + + return {'status': 'ok', 'shutterspeed': shutterspeed, 'aperture': aperture} + + + +# filepath: /home/nicolas/dev/git/git.polymny.net/source/nenuscanner/src/nenuscanner/routes/camera.py +from flask import send_file + +@blueprint.route('/feed.jpg') +def camera_feed(): + return send_file('static/feed.jpg', mimetype='image/jpeg') diff --git a/src/nenuscanner/templates/camera.html b/src/nenuscanner/templates/camera.html new file mode 100644 index 0000000..9e8a02c --- /dev/null +++ b/src/nenuscanner/templates/camera.html @@ -0,0 +1,106 @@ +{% extends "base.html" %} + +{% block content %} +
+
+

Configurer la camera

+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+
+ +
+
+
+ +
+
+ Camera Preview +
+ +
+
+
+
+
+{% endblock content %} + +{% block extrajs %} + +{% endblock extrajs %} \ No newline at end of file