From 8bc52eae04f7a02d86ff42523a6e3d1ef4b67837 Mon Sep 17 00:00:00 2001 From: ABelliqueux Date: Sun, 2 Feb 2025 15:45:05 +0100 Subject: [PATCH] DSLR capture, initialize working --- config.toml | 2 +- dslr_helper.py | 148 +++++++++++++++++++++++++++++++++++++++++++++++ frame_opencv.py | 95 ++++++++++-------------------- requirements.txt | 1 + 4 files changed, 180 insertions(+), 66 deletions(-) create mode 100644 dslr_helper.py diff --git a/config.toml b/config.toml index b81b0f1..2e34522 100644 --- a/config.toml +++ b/config.toml @@ -1,6 +1,6 @@ [DEFAULT] # Camera type - can be : showmewebcam, picam, webcam, dslr -cam_type = 'picam' +cam_type = 'dslr' use_date_for_folder = false file_extension = 'jpg' jpg_quality = 88 diff --git a/dslr_helper.py b/dslr_helper.py new file mode 100644 index 0000000..b6238b9 --- /dev/null +++ b/dslr_helper.py @@ -0,0 +1,148 @@ +#!/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 + +camera_current_settings = { + 'capturemode' : 3, # use IR remote + 'imagesize' : 2, # use size S (1936x1296) + 'whitebalance' : 1, # Natural light + 'capturetarget' : 0, # Internal memory + 'nocfcardrelease' : 0, # Allow capture without sd card + 'recordingmedia' : 1, # Write to RAM +} + +def initialize_camera(): + camera = gp.check_result(gp.gp_camera_new()) + try: + gp.check_result(gp.gp_camera_init(camera)) + # get configuration tree + current_camera_config = gp.check_result(gp.gp_camera_get_config(camera)) + except: + camera.exit() + camera = None + current_camera_config = None + return camera, current_camera_config + + +def apply_gphoto_setting(config, setting, new_value): + # find the $setting config item + try: + cur_setting = gp.check_result(gp.gp_widget_get_child_by_name(config, setting)) + # find corresponding choice + cur_setting_choice = gp.check_result(gp.gp_widget_get_choice(cur_setting, new_value)) + # set config value + gp.check_result(gp.gp_widget_set_value(cur_setting, cur_setting_choice)) + except: + print("Configuration error while setting {} to {}".format(setting, new_value)) + + +def apply_dslr_settings(camera_settings): + camera, config = initialize_camera() + # iterate over the settings dictionary + for setting in camera_settings: + print(setting) + print(camera_settings[setting]) + apply_gphoto_setting(config, setting, camera_settings[setting]) + # validate config + status = gp.check_result(gp.gp_camera_set_config(camera, config)) + # close camera + camera.exit() + return status + + +def check_status_value(config, value, optimal_value=None): + cur_check = gp.check_result(gp.gp_widget_get_child_by_name(config, value)) + cur_check_value = gp.check_result(gp.gp_widget_get_value(cur_check)) + if optimal_value is not None: + cur_check_choice = gp.check_result(gp.gp_widget_get_choice(cur_check, optimal_value[value])) + return [cur_check_value, cur_check_choice] + else: + return cur_check_value + + +# ~ def check_status(camera, config): + # ~ for value in camera_status: + # ~ cur_check_value, cur_check_choice = check_status_value(config, value, camera_status) + # ~ if cur_check_value == cur_check_choice: + # ~ return True + # ~ else: + # ~ # Some values are not optimal + # ~ return False + + +def find_file_ext(gp_name:str, full_path:str): + # extract dir path + dirname = os.path.dirname(full_path) + # extract filename from path + new_name = os.path.basename(full_path) + # if the path does'nt contain file name, return camera's FS filename + if not full_path.endswith(('.jpg', '.JPG', '.raw')): + return gp_name + suffix = gp_name.split('.')[-1] + prefix = new_name.split('.')[:-1] + prefix.insert(len(prefix), suffix) + print(suffix) + print(prefix) + return os.path.join(dirname, '.'.join(prefix)) + + +def capture_and_download(target:str='/tmp'): + camera, current_camera_config = initialize_camera() + # Check battery level + battery_level = int(check_status_value(current_camera_config, 'batterylevel')[:-1]) + if battery_level < 10: + print("Battery level is too low, shutter disabled.") + return False + file_path = camera.capture(gp.GP_CAPTURE_IMAGE) + print('Camera file path: {0}/{1}'.format(file_path.folder, file_path.name)) + print(target) + target = find_file_ext(file_path.name, target) + print('Copying image to', target) + try: + camera_file = camera.file_get( + file_path.folder, file_path.name, gp.GP_FILE_TYPE_NORMAL) + except: + print("Camera error. Check Battery and try restarting the camera.") + return False + try: + camera_file.save(target) + except: + print('File access error.') + return False + camera.exit() + return True + +def main(): + apply_dslr_settings(camera_current_settings) + +if __name__ == "__main__": + logging.basicConfig( + format='%(levelname)s: %(name)s: %(message)s', level=logging.ERROR) + callback_obj = gp.check_result(gp.use_python_logging()) + sys.exit(main()) diff --git a/frame_opencv.py b/frame_opencv.py index 8934c98..b8678eb 100644 --- a/frame_opencv.py +++ b/frame_opencv.py @@ -16,7 +16,8 @@ import numpy as np import serialutils # DSLR -import gphoto2 as gp +# ~ import gphoto2 as gp +import dslr_helper # Run from SSH if not os.getenv('DISPLAY'): @@ -98,53 +99,18 @@ elif project_settings['cam_type'] == "picam": 'anti_flicker': dict(min=0, max=2, default=1, value=1), } elif project_settings['cam_type'] == "dslr": - camera_current_settings = {} + # Sane values for Nikon D40X + camera_current_settings = { + 'capturemode' : 3, # use IR remote + 'imagesize' : 2, # use size S (1936x1296) + 'whitebalance' : 1, # Natural light + 'capturetarget' : 0, # Internal memory + 'nocfcardrelease' : 0, # Allow capture without sd card + 'recordingmedia' : 1, # Write to RAM + } else: camera_current_settings = {} - -def apply_gphoto_setting(config, setting, new_value): - cur_setting = gp.check_result(gp.gp_widget_get_child_by_name(config, setting)) - # find corresponding choice - cur_setting_choice = gp.check_result(gp.gp_widget_get_choice(cur_setting, new_value)) - # set config value - gp.check_result(gp.gp_widget_set_value(cur_setting, cur_setting_choice)) - - -def apply_dslr_settings(camera, config): - # iterate over the settings dictionary - for setting in camera_settings: - # find the capture mode config item - # ~ cur_setting = gp.check_result(gp.gp_widget_get_child_by_name(config, setting)) - # ~ # find corresponding choice - # ~ cur_setting_choice = gp.check_result(gp.gp_widget_get_choice(cur_setting, camera_settings[setting])) - # ~ # set config value - # ~ gp.check_result(gp.gp_widget_set_value(cur_setting, cur_setting_choice)) - apply_gphoto_setting(config, setting, camera_settings[setting]) - # validate config - gp.check_result(gp.gp_camera_set_config(camera, config)) - - -def check_status_value( config, value, optimal_value=None): - cur_check = gp.check_result(gp.gp_widget_get_child_by_name(config, value)) - cur_check_value = gp.check_result(gp.gp_widget_get_value(cur_check)) - if optimal_value is not None: - cur_check_choice = gp.check_result(gp.gp_widget_get_choice(cur_check, optimal_value[value])) - return [cur_check_value, cur_check_choice] - else: - return cur_check_value - - -def check_status(camera, config): - for value in camera_status: - cur_check_value, cur_check_choice = check_status_value(config, value, camera_status) - if cur_check_value == cur_check_choice: - return True - else: - # Some values are not optimal - return False - - def apply_cam_setting(cam_settings:dict, to_set:list=None): # TODO: Refactor so that we can call with whatever camera type and setting, and have the work of determining how to apply it done here # This version should probably be kept and renamed construct_v4l2_cmd() @@ -556,16 +522,8 @@ def main(args): }) # ~ cam.stop() elif project_settings['cam_type'] == "dslr": - camera = gp.check_result(gp.gp_camera_new()) - try: - gp.check_result(gp.gp_camera_init(camera)) - # get configuration tree - current_camera_config = gp.check_result(gp.gp_camera_get_config(camera)) - apply_dslr_settings(camera, current_camera_config) - if check_status(camera, current_camera_config) is False: - print(_("Warning: Some settings are not set to the recommended value!")) - except: - camera = False + print(_("Applying DSLR settings...")) + dslr_helper.apply_dslr_settings(camera_current_settings) else: print(_("No camera type was defined in config.toml")) @@ -576,6 +534,9 @@ def main(args): camera_current_settings = apply_cam_setting(camera_current_settings) time.sleep(.5) + if project_settings['cam_type'] == "dslr": + onionskin = False + loop_delta = 0 while True: start = timer() @@ -774,18 +735,14 @@ def main(args): img_name = return_next_frame_number(get_last_frame(savepath)) img_path = os.path.join(savepath, img_name) if project_settings['cam_type'] == 'dslr': - # Get file from DSLR camera - new_frame_path = camera.capture(gp.GP_CAPTURE_IMAGE) - new_frame = camera.file_get( - new_frame_path.folder, new_frame_path.name, gp.GP_FILE_TYPE_NORMAL) - print(_("Saving {}{}").format(new_frame_path.folder, new_frame_path.name)) - new_frame.save(img_path) + # Get file from DSLR + capture_ok = dslr_helper.capture_and_download(img_path) else: # Cam is either webcam, showmewebcam or picam if project_settings['file_extension'] == 'jpg': - cv2.imwrite(img_path, og_frame, [int(cv2.IMWRITE_JPEG_QUALITY), project_settings['jpg_quality']]) + capture_ok = cv2.imwrite(img_path, og_frame, [int(cv2.IMWRITE_JPEG_QUALITY), project_settings['jpg_quality']]) else: - cv2.imwrite(img_path, og_frame) + capture_ok = cv2.imwrite(img_path, og_frame) print(_("File {} written.").format(img_path)) # Special case when we've no frame yet @@ -793,7 +750,13 @@ def main(args): img_list[index] = img_name else: index += 1 - frame = get_onionskin_frame(savepath, index) + # Display a message if capture was not successfull + if not capture_ok: + frame = generate_text_image(_("Error during capture."), + project_settings['screen_w'], project_settings['screen_h'] + ) + else: + frame = get_onionskin_frame(savepath, index) # Quit app elif k%256 == 27: # ESC pressed @@ -830,8 +793,10 @@ def main(args): ffmpeg_process.terminate() if project_settings['cam_type'] == "showmewebcam": cam.release() - else: + elif project_settings['cam_type'] == "picam": cam.close() + elif project_settings['cam_type'] == "dslr": + pass cv2.destroyAllWindows() cv2.namedWindow("StopiCV", cv2.WINDOW_GUI_NORMAL) diff --git a/requirements.txt b/requirements.txt index ec9f528..cc33666 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ numpy pyserial pillow opencv-python +gphoto2