From 9781dea3165d7a9986401fca0fc841dc73b06202 Mon Sep 17 00:00:00 2001 From: Manuel Osburg <79084170+ManuelOsburg@users.noreply.github.com> Date: Mon, 26 Oct 2026 08:18:50 +0100 Subject: [PATCH] cast the image ID in ConfigData before formatting the image name The config documents a format spec such as img{:04d}.jpg for the zero padded IDs most cameras write, and init_functions and DataExtractor cast the ID to int before formatting. get_start_time and in_time_diff_to_img_time passed the ID as read from config.ini, a string, so with a format spec they failed with "ValueError: Unknown format code 'd' for object of type 'str'" - get_start_time whenever the start time is taken from the first image, which is the default. Cast the ID there as well. New acceptance suite 09 runs the workflow with zero padded test images and img_name_string = test_img_{:04d}.jpg, once with the start time from the first image and once with the clock offset from a time reference image given by its ID. Without the fix both stop with the ValueError above. --- ledsa/core/ConfigData.py | 5 +- .../AcceptanceTests/09_test_image_names.robot | 48 +++++++++++++++++++ .../AcceptanceTests/LedsaATestLibrary.py | 14 ++++++ 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 ledsa/tests/AcceptanceTests/09_test_image_names.robot diff --git a/ledsa/core/ConfigData.py b/ledsa/core/ConfigData.py index 75a3f7a..951a4f0 100644 --- a/ledsa/core/ConfigData.py +++ b/ledsa/core/ConfigData.py @@ -314,7 +314,8 @@ def in_time_diff_to_img_time(self) -> None: if has_id: generated_path = os.path.abspath(os.path.join( self['DEFAULT']['img_directory'], - self['DEFAULT']['img_name_string'].format(self['DEFAULT']['time_ref_img_id']) + # the ID is read as a string, a format spec like {:04d} needs it as a number + self['DEFAULT']['img_name_string'].format(int(self['DEFAULT']['time_ref_img_id'])) )) if has_file: @@ -422,7 +423,7 @@ def get_start_time(self) -> None: """ time_img = get_exif_datetime(os.path.join(self['DEFAULT']['img_directory'], self['DEFAULT']['img_name_string'].format( - self['DEFAULT']['first_img_experiment_id'])), + int(self['DEFAULT']['first_img_experiment_id']))), 'DateTimeOriginal') start_time = time_img - timedelta(seconds=self['DEFAULT'].getfloat('exif_time_infront_real_time')) time_format = '%H:%M:%S' if start_time.microsecond == 0 else '%H:%M:%S.%f' diff --git a/ledsa/tests/AcceptanceTests/09_test_image_names.robot b/ledsa/tests/AcceptanceTests/09_test_image_names.robot new file mode 100644 index 0000000..d49f292 --- /dev/null +++ b/ledsa/tests/AcceptanceTests/09_test_image_names.robot @@ -0,0 +1,48 @@ +*** Settings *** +Resource global_keywords.resource + +Force Tags image_names + +*** Test Cases *** +Zero Padded Image IDs With A Format Spec + [Documentation] Most cameras write zero padded image IDs (DSC_0001.CR3), for which the config + ... documents a format spec such as {:04d}. The whole workflow must accept it, including + ... the start time, which is read from the first image when it is not set. + Prepare Padded Image IDs padded_ids + Run Workflow And Check Results + +Time Reference Image With A Zero Padded ID + [Documentation] The camera clock offset can be computed from a time reference image given by its ID, + ... which is formatted with the same template. The first test image shows 12:00:00. + Prepare Padded Image IDs padded_time_reference + Set Config Option DEFAULT exif_time_infront_real_time None + Set Config Option DEFAULT time_ref_img_id 1 + Set Config Option DEFAULT time_ref_img_time 12:00:00 + Run Workflow And Check Results + +*** Keywords *** +Prepare Padded Image IDs + [Arguments] ${dir} + Create Directory ${WORKDIR}${/}${dir} + Change Directory ${WORKDIR}${/}${dir} + Create Test Data + Rename Test Images To Padded IDs + Create Config + Set Config Option DEFAULT img_name_string test_img_{:04d}.jpg + +Run Workflow And Check Results + Execute Ledsa -s1 + Execute Ledsa -s2 + Execute Ledsa --coordinates + Execute Ledsa -s3_fast + Analysed Image Names Should Be test_img_0001.jpg test_img_0002.jpg test_img_0003.jpg test_img_0004.jpg + Create And Fill Config Analysis linear + Execute Ledsa --analysis + Check Padded Results 2 + Check Padded Results 3 + Check Padded Results 4 + +Check Padded Results + [Arguments] ${image_id} + ${rmse} = Check Input Vs Computed Extinction Coefficients ${image_id} linear + Should Be True ${rmse} < 0.05 diff --git a/ledsa/tests/AcceptanceTests/LedsaATestLibrary.py b/ledsa/tests/AcceptanceTests/LedsaATestLibrary.py index 9146831..987c803 100644 --- a/ledsa/tests/AcceptanceTests/LedsaATestLibrary.py +++ b/ledsa/tests/AcceptanceTests/LedsaATestLibrary.py @@ -480,6 +480,20 @@ def get_analysis_image_id(self, img_name): return img_id raise AssertionError(f'{img_name} is not listed in {file_path}') + @keyword + def rename_test_images_to_padded_ids(self, num_digits=4): + """Rename test_data/test_img_.jpg to zero padded IDs (test_img_0001.jpg), as most cameras write them.""" + for path in glob.glob(os.path.join('test_data', 'test_img_*.jpg')): + img_id = int(os.path.basename(path)[len('test_img_'):-len('.jpg')]) + os.rename(path, os.path.join('test_data', f'test_img_{img_id:0{int(num_digits)}d}.jpg')) + + @keyword + def set_config_option(self, section, option, value): + """Set an option in the config.ini of the current directory.""" + conf = ConfigData() + conf[section][option] = str(value) + conf.save() + @keyword def analysed_image_names_should_be(self, *expected_img_names): """Fail unless exactly the given images are listed in 'image_infos_analysis.csv'."""