From 9be861b01cd980c2cf2749b97e8b572b639c8e9e Mon Sep 17 00:00:00 2001 From: Nicolas Bertrand Date: Tue, 8 Oct 2024 16:09:42 +0200 Subject: [PATCH 01/14] Add camera for image shoot --- scanner.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/scanner.py b/scanner.py index 3cbf673..142e9b4 100644 --- a/scanner.py +++ b/scanner.py @@ -3,6 +3,8 @@ import os from os.path import join import shutil import time +import gphoto2 as gp + from . import config # Delay between to captures @@ -11,16 +13,22 @@ DELAY = 0.5 def capture(output_path: str) -> bool: try: - with open('.device', 'r') as f: - device_id = int(f.read().rstrip()) + 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 =output_path + 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) + s = True except: - device_id = 0 - - cam = cv2.VideoCapture(device_id) - s, img = cam.read() - if s: - cv2.imwrite(output_path, img) - cam.release() + print(f'Somethings wrong on gphoto2') + s = False + finally: + camera.exit() return s @@ -33,9 +41,9 @@ def scan(output_dir: str): # Measure the time it takes to capture start = time.time() - # capture(img) + capture(img) # For debug purposes - shutil.copyfile(join('data-keep/small', led + '.jpg'), img) + #shutil.copyfile(join('data-keep/small', led + '.jpg'), img) delta = time.time() - start From e769611395c12241d12e7ad972a704d926606436 Mon Sep 17 00:00:00 2001 From: Nicolas Bertrand Date: Tue, 5 Nov 2024 09:51:26 +0100 Subject: [PATCH 02/14] Add Readme --- Readme.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 Readme.md diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..27f738a --- /dev/null +++ b/Readme.md @@ -0,0 +1,56 @@ +# NenuScanner + + +## Appareil photo + + +### Réglages EOS 7D + +Prise de photo: +``` + gphoto2 --capture-image-and-download +``` + +#### Format imge + +Télécharger RAW et JPEG + +``` + gphoto2 --set-config imageformat=10 + #Choice: 10 RAW + Small Fine JPEG +``` + + + +Liste des formats: +``` +gphoto2 --get-config imageformat +``` + +#### Capture target + +Stock sur la mémoire interne ou SD de l'apparail + +Sur la mémoire innterne, au download c'est toujours le mêm fichier (écrasement) + +``` +gphoto2 --get-config capturetarget 0 ↵ + +Label: Capture Target +Readonly: 0 +Type: RADIO +Current: Memory card +Choice: 0 Internal RAM +Choice: 1 Memory card +``` +En prod choix 0 : on ne conserve pas les photos sur l'appareil +Et test choix 1 : on conserve sur l'appareil + +Exemple choix 1: +``` +gphoto2 --set-config capturetarget=1 +``` + + + + From a01c73d228190cc4155faa782d3bad74f68a4705 Mon Sep 17 00:00:00 2001 From: Nicolas Bertrand Date: Tue, 5 Nov 2024 10:46:02 +0100 Subject: [PATCH 03/14] Updates on camera config --- Readme.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Readme.md b/Readme.md index 27f738a..308bd6c 100644 --- a/Readme.md +++ b/Readme.md @@ -52,5 +52,19 @@ gphoto2 --set-config capturetarget=1 ``` +# ISO + +ISO 100 + +``` +gphoto2 --set-config iso=1 +``` + +### réglages objectif + +| Ouverture | aperture | gphoto2 --set-config aperture=14 | Ouverture f/10 | +| temps de pose | shutterspeed | gphoto2 --set-config shutterspeed=35 | Vitesse 1/100 s | +| Picture Style | picturestyle | gphoto2 --set-config picturestyle=0 | Standard | +| Exposition | aeb | gphoto2 --set-config aeb=0 | 0 | From 7e3787053ab7a174cbc0cdf29415bd17dfd017ff Mon Sep 17 00:00:00 2001 From: Nicolas Bertrand Date: Thu, 7 Nov 2024 17:15:07 +0100 Subject: [PATCH 04/14] 1st Leds try --- requirements.txt | 6 ++++++ scanner.py | 13 ++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d007bb1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +Flask +pillow +opencv-python +gphoto2 +gpiozero +scipy diff --git a/scanner.py b/scanner.py index 142e9b4..c58175e 100644 --- a/scanner.py +++ b/scanner.py @@ -4,7 +4,7 @@ from os.path import join import shutil import time import gphoto2 as gp - +from gpiozero import PWMLED from . import config # Delay between to captures @@ -34,16 +34,19 @@ def capture(output_path: str) -> bool: def scan(output_dir: str): os.makedirs(output_dir, exist_ok=True) - for led in config.LEDS_UUIDS: - print(f'Turn on {led}') + gpio= PWMLED(17) + gpio.value = 0 + for count,led in enumerate(config.LEDS_UUIDS): + print(f'Turn on {count} {led}') img = join(output_dir, led + '.jpg') # Measure the time it takes to capture start = time.time() - + gpio.value = count/len(config.LEDS_UUIDS) capture(img) # For debug purposes #shutil.copyfile(join('data-keep/small', led + '.jpg'), img) + gpio.value = 0 delta = time.time() - start @@ -51,5 +54,5 @@ def scan(output_dir: str): if delta < DELAY: time.sleep(DELAY - delta) - print(f'Turn off {led}') + print(f'Turn off {count} {led}') yield led From a658be32160943b6008a9d211ec62c9e9a81e2f4 Mon Sep 17 00:00:00 2001 From: Nicolas Bertrand Date: Thu, 7 Nov 2024 17:16:07 +0100 Subject: [PATCH 05/14] correct requirements.txt --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index d007bb1..3718bca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ opencv-python gphoto2 gpiozero scipy +lgpio From 8cfcf1ec554e07cbbf0ff222ace32f4c8bd34345 Mon Sep 17 00:00:00 2001 From: Nicolas Bertrand Date: Tue, 12 Nov 2024 15:26:08 +0100 Subject: [PATCH 06/14] 1st leds conversion --- requirements.txt | 1 - scanner.py | 41 ++++++++++++++++++++++++++++++++--------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/requirements.txt b/requirements.txt index 3718bca..1dacac3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,5 @@ Flask pillow opencv-python gphoto2 -gpiozero scipy lgpio diff --git a/scanner.py b/scanner.py index c58175e..bba3da1 100644 --- a/scanner.py +++ b/scanner.py @@ -4,12 +4,14 @@ from os.path import join import shutil import time import gphoto2 as gp -from gpiozero import PWMLED +import gpiod from . import config # Delay between to captures DELAY = 0.5 +LED2_PIN = 17 +LED3_PIN = 18 def capture(output_path: str) -> bool: try: @@ -21,32 +23,46 @@ def capture(output_path: str) -> bool: target =output_path print('Copying image to', target) camera_file = camera.file_get( - file_path.folder, file_path.name, gp.GP_FILE_TYPE_NORMAL) + file_path.folder, file_path.name, gp.GP_FILE_TYPE_RAW) camera_file.save(target) + camera.exit() s = True except: print(f'Somethings wrong on gphoto2') s = False - finally: - camera.exit() + return s def scan(output_dir: str): os.makedirs(output_dir, exist_ok=True) - gpio= PWMLED(17) - gpio.value = 0 + chip = gpiod.Chip('gpiochip0') + + led2 = chip.get_line(LED2_PIN) + led2.request(consumer="LED", type=gpiod.LINE_REQ_DIR_OUT) + + led3 = chip.get_line(LED3_PIN) + led3.request(consumer="LED", type=gpiod.LINE_REQ_DIR_OUT) + + led3.set_value(0) + led3.set_value(0) + for count,led in enumerate(config.LEDS_UUIDS): print(f'Turn on {count} {led}') img = join(output_dir, led + '.jpg') # Measure the time it takes to capture start = time.time() - gpio.value = count/len(config.LEDS_UUIDS) + if count % 2 == 0: + led2.set_value(1) + else : + led3.set_value(1) + capture(img) # For debug purposes #shutil.copyfile(join('data-keep/small', led + '.jpg'), img) - gpio.value = 0 + led2.set_value(0) + led3.set_value(0) delta = time.time() - start @@ -54,5 +70,12 @@ def scan(output_dir: str): if delta < DELAY: time.sleep(DELAY - delta) - print(f'Turn off {count} {led}') + print(f'Turn off {count} {led}') + if count == len(config.LEDS_UUIDS) -1 : + led2.set_value(0) + led3.set_value(0) + led2.release() + led3.release() + yield led + From c02991eb44f0929a635d7d40dbc82c206ebb01a4 Mon Sep 17 00:00:00 2001 From: Nicolas Bertrand Date: Fri, 15 Nov 2024 11:15:51 +0100 Subject: [PATCH 07/14] Working with GPIO --- config.py | 32 +++++++++++---------- leds.py | 52 +++++++++++++++++++++++++++++++++++ scanner.py | 81 +++++++++++++++++++++++------------------------------- 3 files changed, 105 insertions(+), 60 deletions(-) create mode 100644 leds.py diff --git a/config.py b/config.py index 3947dac..6760501 100644 --- a/config.py +++ b/config.py @@ -8,17 +8,21 @@ DATABASE_PATH = join(DATA_DIR, 'db.sqlite') AUTO_USE_LAST_CALIBRATION = False -LEDS_UUIDS = [ - 'ac59350e-3787-46d2-88fa-743c1d34fe86', - '83ab3133-d4de-4a42-99a7-8f8a3f3732ba', - '577d6e72-f518-4d65-af28-f8faf1ca3f5d', - 'ec49a20c-bddd-4614-b828-fa45e01bfb19', - '5c249cce-d2b6-4b56-96c8-5caa43d8f040', - '22d783fb-ae55-4581-a3c6-e010d9d5e9de', - '12cb6a32-04a6-433b-8146-b753b8f1286d', - '461255a3-259a-4291-adc3-2fb736231a04', - '3896662f-9826-4445-ad70-86c2e6c143e7', - 'f87698ec-3cba-42fe-9a61-5ac9f77d767a', - '4c77a655-4b68-4557-a696-29345c6676a1', - 'b1cfe287-aa3b-445e-bcdc-75ae375efe43', -] +GPIO_CHIP = 'gpiochip0' + +# LEDS_UUIDS = [ +# 'ac59350e-3787-46d2-88fa-743c1d34fe86', +# '83ab3133-d4de-4a42-99a7-8f8a3f3732ba', +# '577d6e72-f518-4d65-af28-f8faf1ca3f5d', +# 'ec49a20c-bddd-4614-b828-fa45e01bfb19', +# '5c249cce-d2b6-4b56-96c8-5caa43d8f040', +# '22d783fb-ae55-4581-a3c6-e010d9d5e9de', +# '12cb6a32-04a6-433b-8146-b753b8f1286d', +# '461255a3-259a-4291-adc3-2fb736231a04', +# '3896662f-9826-4445-ad70-86c2e6c143e7', +# 'f87698ec-3cba-42fe-9a61-5ac9f77d767a', +# '4c77a655-4b68-4557-a696-29345c6676a1', +# 'b1cfe287-aa3b-445e-bcdc-75ae375efe43', +# ] + +LEDS_UUIDS = [17, 18, 22, 23, 24, 27] diff --git a/leds.py b/leds.py new file mode 100644 index 0000000..b8f47e5 --- /dev/null +++ b/leds.py @@ -0,0 +1,52 @@ +import gpiod + +from . import config + +class GpioLed: + chip = gpiod.Chip(config.GPIO_CHIP) + + def __init__(self, gpio_pin: int): + self.gpio_pin = gpio_pin + self.led = None + + def __enter__(self): + self.led = GpioLed.chip.get_line(self.gpio_pin) + self.led.request(consumer=str(self), type=gpiod.LINE_REQ_DIR_OUT) + self.off() + + def __exit__(self, *args): + self.off() + self.led.release() + self.led = None + + def on(self): + self.led.set_value(1) + + def off(self): + self.led.set_value(0) + + def __str__(self): + return f'LED{self.gpio_pin:02}' + + +class GpioLeds: + def __init__(self, gpio_pins: list[int]): + self.leds = [] + for pin in gpio_pins: + self.leds.append(GpioLed(pin)) + + def __enter__(self): + for led in self.leds: + led.__enter__() + + def __exit__(self, *args): + for led in self.leds: + led.__exit__(*args) + + def off(self): + for led in self.leds: + led.off() + + def on(self): + for led in self.leds: + led.on() diff --git a/scanner.py b/scanner.py index bba3da1..8094033 100644 --- a/scanner.py +++ b/scanner.py @@ -1,31 +1,30 @@ import cv2 import os from os.path import join +import subprocess import shutil import time import gphoto2 as gp import gpiod -from . import config +from . import config, leds # Delay between to captures DELAY = 0.5 -LED2_PIN = 17 -LED3_PIN = 18 - -def capture(output_path: str) -> bool: +def capture(camera, output_path: str) -> bool: try: - 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 =output_path + target = output_path + '.cr2' print('Copying image to', target) - camera_file = camera.file_get( - file_path.folder, file_path.name, gp.GP_FILE_TYPE_RAW) + + camera_file = camera.file_get(file_path.folder, file_path.name, gp.GP_FILE_TYPE_RAW) camera_file.save(target) - camera.exit() + + camera_file = camera.file_get(file_path.folder, file_path.name, gp.GP_FILE_TYPE_PREVIEW) + camera_file.save(output_path + '.jpg') + s = True except: print(f'Somethings wrong on gphoto2') @@ -35,47 +34,37 @@ def capture(output_path: str) -> bool: def scan(output_dir: str): + camera = gp.Camera() + camera.init() + os.makedirs(output_dir, exist_ok=True) - chip = gpiod.Chip('gpiochip0') + gpio_leds = leds.GpioLeds(config.LEDS_UUIDS) - led2 = chip.get_line(LED2_PIN) - led2.request(consumer="LED", type=gpiod.LINE_REQ_DIR_OUT) + with gpio_leds: + for count, led in enumerate(gpio_leds.leds): + print(f'Turn on {led}') + img = join(output_dir, f'{led}') - led3 = chip.get_line(LED3_PIN) - led3.request(consumer="LED", type=gpiod.LINE_REQ_DIR_OUT) + # Measure the time it takes to capture + start = time.time() - led3.set_value(0) - led3.set_value(0) + led.on() + capture(camera, img) + led.off() + + # For debug purposes + # shutil.copyfile(join('data-keep/small', led + '.jpg'), img) - for count,led in enumerate(config.LEDS_UUIDS): - print(f'Turn on {count} {led}') - img = join(output_dir, led + '.jpg') + delta = time.time() - start - # Measure the time it takes to capture - start = time.time() - if count % 2 == 0: - led2.set_value(1) - else : - led3.set_value(1) + # Wait for at least one second between each capture + if delta < DELAY: + time.sleep(DELAY - delta) - capture(img) - # For debug purposes - #shutil.copyfile(join('data-keep/small', led + '.jpg'), img) - led2.set_value(0) - led3.set_value(0) + print(f'Turn off {led}') + if count == len(config.LEDS_UUIDS) -1: + gpio_leds.off() + camera.exit() - delta = time.time() - start - - # Wait for at least one second between each capture - if delta < DELAY: - time.sleep(DELAY - delta) - - print(f'Turn off {count} {led}') - if count == len(config.LEDS_UUIDS) -1 : - led2.set_value(0) - led3.set_value(0) - led2.release() - led3.release() - - yield led + yield led From 6b4cb074eb2c847d621ee79eb859a77da7097ee4 Mon Sep 17 00:00:00 2001 From: Nicolas Bertrand Date: Fri, 15 Nov 2024 11:48:34 +0100 Subject: [PATCH 08/14] update Readme.md --- Readme.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 308bd6c..0d5b1ea 100644 --- a/Readme.md +++ b/Readme.md @@ -1,6 +1,19 @@ # NenuScanner +## GPIO + +### Cablage des LEDs existantes de la salle noire + +| GPIO | ID LED salle noire | +| GPIO 17 | LED 2 | +| GPIO 18 | LED 3 | +| GPIO 27 | LED 5 | +| GPIO 23 | LED 6 | +| GPIO 23 | LED 6 | + + + ## Appareil photo @@ -11,7 +24,7 @@ Prise de photo: gphoto2 --capture-image-and-download ``` -#### Format imge +#### Format image Télécharger RAW et JPEG From 0acb2f0a0ca2702f465a78130ba1644a84ccd549 Mon Sep 17 00:00:00 2001 From: Nicolas Bertrand Date: Fri, 15 Nov 2024 14:14:53 +0100 Subject: [PATCH 09/14] Working version with RAW and JPEG --- scanner.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/scanner.py b/scanner.py index 8094033..436e875 100644 --- a/scanner.py +++ b/scanner.py @@ -18,16 +18,20 @@ def capture(camera, output_path: str) -> bool: print('Camera file path: {0}/{1}'.format(file_path.folder, file_path.name)) target = output_path + '.cr2' print('Copying image to', target) - - camera_file = camera.file_get(file_path.folder, file_path.name, gp.GP_FILE_TYPE_RAW) - camera_file.save(target) - - camera_file = camera.file_get(file_path.folder, file_path.name, gp.GP_FILE_TYPE_PREVIEW) - camera_file.save(output_path + '.jpg') + print(file_path.folder, file_path.name[:-3] + 'JPG') + camera_file_jpg = camera.file_get(file_path.folder, file_path.name[:-3] + 'JPG', gp.GP_FILE_TYPE_NORMAL) + camera_file_jpg.save(output_path + '.jpg') + camera_file_raw = camera.file_get(file_path.folder, file_path.name, gp.GP_FILE_TYPE_RAW) + camera_file_raw.save(target) + + camera.exit() + camera.init() s = True - except: + + except Exception as e: print(f'Somethings wrong on gphoto2') + print(e) s = False return s From 3a63449c83ba0a220bbccc53886922be14a737ea Mon Sep 17 00:00:00 2001 From: Nicolas Bertrand Date: Fri, 15 Nov 2024 14:50:04 +0100 Subject: [PATCH 10/14] Add capture full on full off --- scanner.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scanner.py b/scanner.py index 436e875..0697de4 100644 --- a/scanner.py +++ b/scanner.py @@ -67,7 +67,16 @@ def scan(output_dir: str): print(f'Turn off {led}') if count == len(config.LEDS_UUIDS) -1: + + # capture with all leds ON OFF + gpio_leds.on() + img = join(output_dir, f'all_on') + capture(camera, img) + gpio_leds.off() + img = join(output_dir, f'all_off') + capture(camera, img) + camera.exit() yield led From 2e4238fc60206fdc035b41e3910404cebe5b0608 Mon Sep 17 00:00:00 2001 From: Thomas Forgione Date: Mon, 18 Nov 2024 09:49:22 +0100 Subject: [PATCH 11/14] Easy testing with or without dark room setup --- .gitignore | 3 ++ __init__.py | 2 +- camera.py | 58 ++++++++++++++++++++++++++++++++++++++ config.darkroom.py | 14 ++++++++++ config.local.py | 14 ++++++++++ config.py | 28 ------------------- leds.py | 60 +++++++++++++++++++++++++++++++++------ requirements.txt | 3 +- scanner.py | 65 ++++++++----------------------------------- templates/object.html | 2 +- templates/scan.html | 2 +- 11 files changed, 156 insertions(+), 95 deletions(-) create mode 100644 camera.py create mode 100644 config.darkroom.py create mode 100644 config.local.py delete mode 100644 config.py diff --git a/.gitignore b/.gitignore index 2a054fe..6517bf9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Config file +config.py + .device data data-* diff --git a/__init__.py b/__init__.py index 175437f..699b2cd 100644 --- a/__init__.py +++ b/__init__.py @@ -25,7 +25,7 @@ def inject(): conn = db.get() return { 'calibration': utils.get_calibration(conn), - 'leds': config.LEDS_UUIDS, + 'leds': config.GPIO_LEDS.leds, 'CalibrationState': db.CalibrationState, } diff --git a/camera.py b/camera.py new file mode 100644 index 0000000..bec4ce1 --- /dev/null +++ b/camera.py @@ -0,0 +1,58 @@ +import gphoto2 as gp +import shutil +from . import leds + + +class Camera: + def __init__(self): + self.inner = gp.Camera() + + def __enter__(self): + self.inner.init() + + def __exit__(self): + self.inner.exit() + + def capture(self, output_path: str) -> bool: + try: + with self: + file_path = self.inner.capture(gp.GP_CAPTURE_IMAGE) + preview = self.inner.file_get(file_path.folder, file_path.name[:-3] + '.JPG', gp.GP_FILE_TYPE_NORMAL) + raw = self.inner.file_get(file_path.folder, file_path.name, gp.GP_FILE_TYPE_RAW) + + preview.sve(output_path + '.jpg') + raw.save(output_path + '.cr2') + return True + + except Exception as e: + print('An error occured when capturing photo', e) + return False + + +class DummyCamera: + def __init__(self, leds: leds.DummyLeds): + self.leds = leds + + def __enter__(self): + pass + + def __exit__(self): + pass + + def capture(self, output_path: str) -> bool: + # Find which leds are turned on + found = None + all_on = False + for led in self.leds.leds: + if led.is_on: + if found is None: + found = led + else: + all_on = True + + if found: + shutil.copyfile('data-keep/small/' + str(found) + '.jpg', output_path + '.jpg') + elif all_on: + shutil.copyfile('data-keep/small/LED17.jpg', output_path + '.jpg') + else: + shutil.copyfile('data-keep/small/LED17.jpg', output_path + '.jpg') diff --git a/config.darkroom.py b/config.darkroom.py new file mode 100644 index 0000000..09ca3b1 --- /dev/null +++ b/config.darkroom.py @@ -0,0 +1,14 @@ +from os.path import join +from . import camera, leds + +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') + +AUTO_USE_LAST_CALIBRATION = True +GPIO_CHIP = 'gpiochip0' +LEDS_UUIDS = [17, 18, 22, 23, 24, 27] +GPIO_LEDS = leds.GpioLeds(LEDS_UUIDS) +CAMERA = camera.Camera() diff --git a/config.local.py b/config.local.py new file mode 100644 index 0000000..d0ed701 --- /dev/null +++ b/config.local.py @@ -0,0 +1,14 @@ +from os.path import join +from . import camera, leds + +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') + +AUTO_USE_LAST_CALIBRATION = False +GPIO_CHIP = None +LEDS_UUIDS = [17, 18, 22, 23, 24, 27] +GPIO_LEDS = leds.DummyLeds(LEDS_UUIDS) +CAMERA = camera.DummyCamera(GPIO_LEDS) diff --git a/config.py b/config.py deleted file mode 100644 index 6760501..0000000 --- a/config.py +++ /dev/null @@ -1,28 +0,0 @@ -from os.path import join - -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') - -AUTO_USE_LAST_CALIBRATION = False - -GPIO_CHIP = 'gpiochip0' - -# LEDS_UUIDS = [ -# 'ac59350e-3787-46d2-88fa-743c1d34fe86', -# '83ab3133-d4de-4a42-99a7-8f8a3f3732ba', -# '577d6e72-f518-4d65-af28-f8faf1ca3f5d', -# 'ec49a20c-bddd-4614-b828-fa45e01bfb19', -# '5c249cce-d2b6-4b56-96c8-5caa43d8f040', -# '22d783fb-ae55-4581-a3c6-e010d9d5e9de', -# '12cb6a32-04a6-433b-8146-b753b8f1286d', -# '461255a3-259a-4291-adc3-2fb736231a04', -# '3896662f-9826-4445-ad70-86c2e6c143e7', -# 'f87698ec-3cba-42fe-9a61-5ac9f77d767a', -# '4c77a655-4b68-4557-a696-29345c6676a1', -# 'b1cfe287-aa3b-445e-bcdc-75ae375efe43', -# ] - -LEDS_UUIDS = [17, 18, 22, 23, 24, 27] diff --git a/leds.py b/leds.py index b8f47e5..54ecd09 100644 --- a/leds.py +++ b/leds.py @@ -1,20 +1,17 @@ import gpiod -from . import config class GpioLed: - chip = gpiod.Chip(config.GPIO_CHIP) - def __init__(self, gpio_pin: int): self.gpio_pin = gpio_pin self.led = None - def __enter__(self): - self.led = GpioLed.chip.get_line(self.gpio_pin) + def enter(self, chip: gpiod.Chip): + self.led = chip.get_line(self.gpio_pin) self.led.request(consumer=str(self), type=gpiod.LINE_REQ_DIR_OUT) self.off() - def __exit__(self, *args): + def exit(self): self.off() self.led.release() self.led = None @@ -30,18 +27,63 @@ class GpioLed: class GpioLeds: - def __init__(self, gpio_pins: list[int]): + def __init__(self, chip: str, gpio_pins: list[int]): + self.chip = gpiod.Chip(chip) self.leds = [] for pin in gpio_pins: self.leds.append(GpioLed(pin)) def __enter__(self): for led in self.leds: - led.__enter__() + led.enter(self.chip) def __exit__(self, *args): for led in self.leds: - led.__exit__(*args) + led.exit() + + def off(self): + for led in self.leds: + led.off() + + def on(self): + for led in self.leds: + led.on() + + +class DummyLed: + def __init__(self, gpio_pin: int): + self.gpio_pin = gpio_pin + self.is_on = False + + def enter(self): + pass + + def exit(self): + pass + + def on(self): + self.is_on = True + + def off(self): + self.is_on = False + + def __str__(self): + return f'LED{self.gpio_pin:02}' + + +class DummyLeds: + def __init__(self, gpio_pins: list[int]): + self.leds = [] + for pin in gpio_pins: + self.leds.append(DummyLed(pin)) + + def __enter__(self): + for led in self.leds: + led.enter() + + def __exit__(self, *args): + for led in self.leds: + led.exit() def off(self): for led in self.leds: diff --git a/requirements.txt b/requirements.txt index 1dacac3..eb2089e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ Flask pillow -opencv-python gphoto2 scipy -lgpio +gpiod diff --git a/scanner.py b/scanner.py index 0697de4..5ecd274 100644 --- a/scanner.py +++ b/scanner.py @@ -1,51 +1,17 @@ -import cv2 import os from os.path import join -import subprocess -import shutil import time -import gphoto2 as gp -import gpiod -from . import config, leds +from . import config # Delay between to captures DELAY = 0.5 -def capture(camera, output_path: str) -> bool: - try: - 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 = output_path + '.cr2' - print('Copying image to', target) - print(file_path.folder, file_path.name[:-3] + 'JPG') - camera_file_jpg = camera.file_get(file_path.folder, file_path.name[:-3] + 'JPG', gp.GP_FILE_TYPE_NORMAL) - camera_file_jpg.save(output_path + '.jpg') - - camera_file_raw = camera.file_get(file_path.folder, file_path.name, gp.GP_FILE_TYPE_RAW) - camera_file_raw.save(target) - - camera.exit() - camera.init() - s = True - - except Exception as e: - print(f'Somethings wrong on gphoto2') - print(e) - s = False - - return s - def scan(output_dir: str): - camera = gp.Camera() - camera.init() - os.makedirs(output_dir, exist_ok=True) - gpio_leds = leds.GpioLeds(config.LEDS_UUIDS) - with gpio_leds: - for count, led in enumerate(gpio_leds.leds): + with config.GPIO_LEDS: + for count, led in enumerate(config.GPIO_LEDS.leds): print(f'Turn on {led}') img = join(output_dir, f'{led}') @@ -53,11 +19,8 @@ def scan(output_dir: str): start = time.time() led.on() - capture(camera, img) + config.CAMERA.capture(img) led.off() - - # For debug purposes - # shutil.copyfile(join('data-keep/small', led + '.jpg'), img) delta = time.time() - start @@ -65,19 +28,15 @@ def scan(output_dir: str): if delta < DELAY: time.sleep(DELAY - delta) - print(f'Turn off {led}') - if count == len(config.LEDS_UUIDS) -1: - + print(f'Turn off {led}') + if count == len(config.LEDS_UUIDS) - 1: # capture with all leds ON OFF - gpio_leds.on() - img = join(output_dir, f'all_on') - capture(camera, img) + config.GPIO_LEDS.on() + img = join(output_dir, 'all_on') + config.CAMERA.capture(img) - gpio_leds.off() - img = join(output_dir, f'all_off') - capture(camera, img) - - camera.exit() + config.GPIO_LEDS.off() + img = join(output_dir, 'all_off') + config.CAMERA.capture(img) yield led - diff --git a/templates/object.html b/templates/object.html index e3fc765..99e48fb 100644 --- a/templates/object.html +++ b/templates/object.html @@ -70,7 +70,7 @@
Voulez-vous vraiment supprimer l'objet ?
-
Supprimer cet objet diff --git a/templates/scan.html b/templates/scan.html index df23253..303d5ba 100644 --- a/templates/scan.html +++ b/templates/scan.html @@ -50,7 +50,7 @@
Voulez-vous vraiment supprimer l'acquisition ?
-
Supprimer cette acquisition From 8d13713c5ff2fe9bde0cb44e093a07b0fafaa6cd Mon Sep 17 00:00:00 2001 From: Thomas Forgione Date: Mon, 18 Nov 2024 15:38:09 +0100 Subject: [PATCH 12/14] Fix 404 on reuse last calibration --- routes/calibration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/calibration.py b/routes/calibration.py index e976a46..dab4736 100644 --- a/routes/calibration.py +++ b/routes/calibration.py @@ -125,4 +125,4 @@ def use_last(): conn = db.get() calib = db.Calibration.get_last(conn) session['calibration_id'] = calib.id - return redirect('/calibrate') + return redirect('/calibration/calibrate') From 62b6bb2417d27fef8ad5ddd1294bf4356ea088b0 Mon Sep 17 00:00:00 2001 From: Thomas Forgione Date: Mon, 18 Nov 2024 15:53:32 +0100 Subject: [PATCH 13/14] Fix bug and colors of text --- templates/index.html | 10 +++++----- templates/object.html | 28 +++++++++++++++------------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/templates/index.html b/templates/index.html index 8422c06..43d48fb 100644 --- a/templates/index.html +++ b/templates/index.html @@ -18,7 +18,7 @@
{% for project in projects %} - +
{{ project.name }}
@@ -35,7 +35,7 @@
@@ -83,23 +83,25 @@ {% endblock content %} -{% block extrajs %}{% if calibration.state < CalibrationState.IsValidated %} +{% block extrajs %} -{% endif %}{% endblock %} +{% endblock %} From 727ab37b0b688940e7300022e79b5dc81b3ea3ec Mon Sep 17 00:00:00 2001 From: Thomas Forgione Date: Tue, 19 Nov 2024 11:34:21 +0100 Subject: [PATCH 14/14] all_on and all_off --- __init__.py | 4 +-- camera.py | 45 +++++++++++++++++++------------ config.darkroom.py | 4 +-- config.local.py | 4 +-- leds.py | 29 ++++++++++++++++++-- routes/acquisition.py | 8 +++--- routes/calibration.py | 2 +- scanner.py | 61 +++++++++++++++++++++++++------------------ templates/scan.html | 16 ++++++++++++ 9 files changed, 116 insertions(+), 57 deletions(-) diff --git a/__init__.py b/__init__.py index 699b2cd..151441d 100644 --- a/__init__.py +++ b/__init__.py @@ -1,6 +1,6 @@ from flask import Flask, send_from_directory, session import os -from . import db, config, routes, utils +from . import db, config, routes, utils, leds app = Flask(__name__) @@ -25,7 +25,7 @@ def inject(): conn = db.get() return { 'calibration': utils.get_calibration(conn), - 'leds': config.GPIO_LEDS.leds, + 'leds': leds.get().leds, 'CalibrationState': db.CalibrationState, } diff --git a/camera.py b/camera.py index bec4ce1..f95434a 100644 --- a/camera.py +++ b/camera.py @@ -1,42 +1,47 @@ import gphoto2 as gp import shutil -from . import leds +from . import leds, config class Camera: + def capture(self, output_path: str) -> bool: + return False + + +class RealCamera(Camera): def __init__(self): self.inner = gp.Camera() def __enter__(self): self.inner.init() + return self - def __exit__(self): + def __exit__(self, *args): self.inner.exit() def capture(self, output_path: str) -> bool: try: - with self: - file_path = self.inner.capture(gp.GP_CAPTURE_IMAGE) - preview = self.inner.file_get(file_path.folder, file_path.name[:-3] + '.JPG', gp.GP_FILE_TYPE_NORMAL) - raw = self.inner.file_get(file_path.folder, file_path.name, gp.GP_FILE_TYPE_RAW) + file_path = self.inner.capture(gp.GP_CAPTURE_IMAGE) + preview = self.inner.file_get(file_path.folder, file_path.name[:-3] + '.JPG', gp.GP_FILE_TYPE_NORMAL) + raw = self.inner.file_get(file_path.folder, file_path.name, gp.GP_FILE_TYPE_RAW) - preview.sve(output_path + '.jpg') - raw.save(output_path + '.cr2') - return True + preview.sve(output_path + '.jpg') + raw.save(output_path + '.cr2') + return True except Exception as e: print('An error occured when capturing photo', e) return False -class DummyCamera: +class DummyCamera(Camera): def __init__(self, leds: leds.DummyLeds): self.leds = leds def __enter__(self): - pass + return self - def __exit__(self): + def __exit__(self, *args): pass def capture(self, output_path: str) -> bool: @@ -50,9 +55,17 @@ class DummyCamera: else: all_on = True - if found: + if all_on: + shutil.copyfile('data-keep/small/all_on.jpg', output_path + '.jpg') + elif found is not None: shutil.copyfile('data-keep/small/' + str(found) + '.jpg', output_path + '.jpg') - elif all_on: - shutil.copyfile('data-keep/small/LED17.jpg', output_path + '.jpg') else: - shutil.copyfile('data-keep/small/LED17.jpg', output_path + '.jpg') + print('ALL_OFF') + shutil.copyfile('data-keep/small/all_off.jpg', output_path + '.jpg') + + +camera = DummyCamera(leds.get()) if config.CAMERA == "dummy" else RealCamera() + + +def get(): + return camera diff --git a/config.darkroom.py b/config.darkroom.py index 09ca3b1..dae0118 100644 --- a/config.darkroom.py +++ b/config.darkroom.py @@ -1,5 +1,4 @@ from os.path import join -from . import camera, leds DATA_DIR = 'data' BACKUPS_DIR = 'data-backups' @@ -10,5 +9,4 @@ DATABASE_PATH = join(DATA_DIR, 'db.sqlite') AUTO_USE_LAST_CALIBRATION = True GPIO_CHIP = 'gpiochip0' LEDS_UUIDS = [17, 18, 22, 23, 24, 27] -GPIO_LEDS = leds.GpioLeds(LEDS_UUIDS) -CAMERA = camera.Camera() +CAMERA = 'real' diff --git a/config.local.py b/config.local.py index d0ed701..5442bf4 100644 --- a/config.local.py +++ b/config.local.py @@ -1,5 +1,4 @@ from os.path import join -from . import camera, leds DATA_DIR = 'data' BACKUPS_DIR = 'data-backups' @@ -10,5 +9,4 @@ DATABASE_PATH = join(DATA_DIR, 'db.sqlite') AUTO_USE_LAST_CALIBRATION = False GPIO_CHIP = None LEDS_UUIDS = [17, 18, 22, 23, 24, 27] -GPIO_LEDS = leds.DummyLeds(LEDS_UUIDS) -CAMERA = camera.DummyCamera(GPIO_LEDS) +CAMERA = 'dummy' diff --git a/leds.py b/leds.py index 54ecd09..f5eb9b0 100644 --- a/leds.py +++ b/leds.py @@ -1,5 +1,21 @@ import gpiod +from . import config + + +class Leds: + def __enter__(self): + pass + + def __exit__(self, *args): + pass + + def on(self): + pass + + def off(self): + pass + class GpioLed: def __init__(self, gpio_pin: int): @@ -26,7 +42,7 @@ class GpioLed: return f'LED{self.gpio_pin:02}' -class GpioLeds: +class GpioLeds(Leds): def __init__(self, chip: str, gpio_pins: list[int]): self.chip = gpiod.Chip(chip) self.leds = [] @@ -36,6 +52,7 @@ class GpioLeds: def __enter__(self): for led in self.leds: led.enter(self.chip) + return self def __exit__(self, *args): for led in self.leds: @@ -71,7 +88,7 @@ class DummyLed: return f'LED{self.gpio_pin:02}' -class DummyLeds: +class DummyLeds(Leds): def __init__(self, gpio_pins: list[int]): self.leds = [] for pin in gpio_pins: @@ -80,6 +97,7 @@ class DummyLeds: def __enter__(self): for led in self.leds: led.enter() + return self def __exit__(self, *args): for led in self.leds: @@ -92,3 +110,10 @@ class DummyLeds: def on(self): for led in self.leds: led.on() + + +_leds = GpioLeds(config.GPIO_CHIP, config.LEDS_UUIDS) if config.GPIO_CHIP is not None else DummyLeds(config.LEDS_UUIDS) + + +def get() -> Leds: + return _leds diff --git a/routes/acquisition.py b/routes/acquisition.py index fa26dd7..8896f0c 100644 --- a/routes/acquisition.py +++ b/routes/acquisition.py @@ -26,7 +26,7 @@ def scan_existing(id: int): conn = db.get() calibrated = session.get('calibration_id', None) is not None acquisition = db.Acquisition.get_from_id(id, conn) - object = acquisition.object(conn) + object = acquisition.object(conn) if acquisition is not None else None return render_template('scan.html', object=object, acquisition=acquisition, calibrated=calibrated) @@ -48,7 +48,7 @@ def run(object_id: int): def generate(): yield str(acquisition.id) - length = len(config.LEDS_UUIDS) + length = len(config.LEDS_UUIDS) + 2 # with all_on and all_off 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" @@ -74,7 +74,7 @@ def rescan(acquisition_id: int): object = acquisition.object(conn) def generate(): - length = len(config.LEDS_UUIDS) + length = len(config.LEDS_UUIDS) + 2 # with all_on and all_off 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" @@ -87,7 +87,7 @@ def validate(acquisition_id: int): acquisition = db.Acquisition.get_from_id(acquisition_id, conn) if acquisition is None: - raise f"Aucune acquisition d'id {acquisition_id}" + raise Exception(f"Aucune acquisition d'id {acquisition_id}") object = acquisition.object(conn) diff --git a/routes/calibration.py b/routes/calibration.py index dab4736..0f58911 100644 --- a/routes/calibration.py +++ b/routes/calibration.py @@ -57,7 +57,7 @@ def scan(): def generate(): length = len(config.LEDS_UUIDS) - for index, led_uuid in enumerate(scanner.scan(join(config.CALIBRATION_DIR, calibration_id))): + for index, led_uuid in enumerate(scanner.scan(join(config.CALIBRATION_DIR, calibration_id), False)): yield f"{led_uuid},{(index+1)/length}\n" with conn: diff --git a/scanner.py b/scanner.py index 5ecd274..f70bc32 100644 --- a/scanner.py +++ b/scanner.py @@ -1,42 +1,51 @@ import os from os.path import join import time -from . import config +from . import leds, camera # Delay between to captures DELAY = 0.5 -def scan(output_dir: str): +def delay_capture(cam, output_path): + # Measure the time it takes to capture + start = time.time() + output = cam.capture(output_path) + delta = time.time() - start + + # Wait for at least one second between each capture + if delta < DELAY: + time.sleep(DELAY - delta) + + return output + + +def scan(output_dir: str, on_and_off: bool = True): os.makedirs(output_dir, exist_ok=True) - with config.GPIO_LEDS: - for count, led in enumerate(config.GPIO_LEDS.leds): - print(f'Turn on {led}') - img = join(output_dir, f'{led}') + with leds.get() as gpio_leds: + for count, led in enumerate(gpio_leds.leds): + with camera.get() as cam: + print(f'Turn on {led}') + img = join(output_dir, f'{led}') - # Measure the time it takes to capture - start = time.time() + led.on() + delay_capture(cam, img) + led.off() - led.on() - config.CAMERA.capture(img) - led.off() + print(f'Turn off {led}') + yield str(led) - delta = time.time() - start - - # Wait for at least one second between each capture - if delta < DELAY: - time.sleep(DELAY - delta) - - print(f'Turn off {led}') - if count == len(config.LEDS_UUIDS) - 1: - # capture with all leds ON OFF - config.GPIO_LEDS.on() + # capture with all leds ON OFF + if on_and_off: + with camera.get() as cam: + gpio_leds.on() img = join(output_dir, 'all_on') - config.CAMERA.capture(img) + delay_capture(cam, img) + yield 'all_on' - config.GPIO_LEDS.off() + with camera.get() as cam: + gpio_leds.off() img = join(output_dir, 'all_off') - config.CAMERA.capture(img) - - yield led + delay_capture(cam, img) + yield 'all_off' diff --git a/templates/scan.html b/templates/scan.html index 303d5ba..27d70ea 100644 --- a/templates/scan.html +++ b/templates/scan.html @@ -102,6 +102,22 @@ cell.appendChild(img); grid.appendChild(cell); {% endfor %} + + cell = document.createElement('div'); + cell.classList.add('cell'); + img = document.createElement('img'); + img.classList.add('is-loading'); + img.src = '/data/objects/{{ object.id }}/' + acquisitionId + '/all_on.jpg?v=0'; + cell.appendChild(img); + grid.appendChild(cell); + + cell = document.createElement('div'); + cell.classList.add('cell'); + img = document.createElement('img'); + img.classList.add('is-loading'); + img.src = '/data/objects/{{ object.id }}/' + acquisitionId + '/all_off.jpg?v=0'; + cell.appendChild(img); + grid.appendChild(cell); } scanButton.addEventListener('click', async () => {