Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ledsa/core/ConfigData.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ def get_start_time(self) -> None:
Updates the 'DEFAULT' key with the 'start_time' computed.

"""
exif_entry = get_exif_entry(os.path.join(self['DEFAULT']['img_directory'] + self['DEFAULT']['img_name_string'].format(
exif_entry = get_exif_entry(os.path.join(self['DEFAULT']['img_directory'], self['DEFAULT']['img_name_string'].format(
self['DEFAULT']['first_img_experiment_id'])), 'DateTimeOriginal')
date, time_meta = exif_entry.split(' ')
time_img = _get_datetime_from_str(date, time_meta)
Expand Down
20 changes: 20 additions & 0 deletions ledsa/core/image_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,26 @@
from ledsa.core.file_handling import read_table


def format_img_name(img_name_string: str, img_id) -> str:
"""
Build an image file name from the config template and an image ID.

Plain ``{}`` placeholders keep the ID as given, so zero-padded IDs such as
'0001' (e.g. DSC_0001.NEF) are preserved. Numeric format specifications
such as ``{:04d}`` require an integer, for which the ID is cast.

:param img_name_string: Name template from the config, e.g. 'DSC_{}.CR3'.
:type img_name_string: str
:param img_id: The image ID, as string or integer.
:return: The formatted image file name.
:rtype: str
"""
try:
return img_name_string.format(img_id)
except ValueError:
return img_name_string.format(int(img_id))


def get_img_name(img_id: str) -> str:
"""
Retrieves the image path corresponding to a given image ID.
Expand Down
8 changes: 6 additions & 2 deletions ledsa/core/image_reading.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ def read_channel_data_from_img(filename: str, channel: int) -> np.ndarray:
extension = os.path.splitext(filename)[-1]
if extension in ['.JPG', '.JPEG', '.jpg', '.jpeg', '.PNG', '.png']:
channel_array = _read_channel_data_from_img_file(filename, channel)
elif extension in ['.CR2', '.CR3']:
elif extension in ['.CR2', '.CR3', '.NEF', '.ARW', '.DNG']:
channel_array = _read_channel_data_from_raw_file(filename, channel)
else:
raise ValueError(f"Unsupported image format '{extension}' of file {filename}.")
return channel_array

def read_img_array_from_img(filename: str, channel: int) -> np.ndarray:
Expand All @@ -41,8 +43,10 @@ def read_img_array_from_img(filename: str, channel: int) -> np.ndarray:
extension = os.path.splitext(filename)[-1]
if extension in ['.JPG', '.JPEG', '.jpg', '.jpeg', '.PNG', '.png']:
img_array = _read_grayscale_img_array_from_img_file(filename)
elif extension in ['.CR2', '.CR3']:
elif extension in ['.CR2', '.CR3', '.NEF', '.ARW', '.DNG']:
img_array, _ = _read_img_array_from_raw_file(filename, channel)
else:
raise ValueError(f"Unsupported image format '{extension}' of file {filename}.")
return img_array


Expand Down
4 changes: 2 additions & 2 deletions ledsa/data_extraction/DataExtractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def find_search_areas(self) -> None:
Identify all LEDs in the reference image and define the areas where LEDs will be searched in the experiment images.
"""
config = self.config['find_search_areas']
in_file_path = os.path.join(config['img_directory'], config['img_name_string'].format(int(config['ref_img_id'])))
in_file_path = os.path.join(config['img_directory'], ledsa.core.image_handling.format_img_name(config['img_name_string'], config['ref_img_id']))
channel = config['channel']
search_area_radius = int(config['search_area_radius'])
max_num_leds = int(config['max_num_leds'])
Expand Down Expand Up @@ -126,7 +126,7 @@ def plot_search_areas(self, reorder_leds=False) -> None:
if self.search_areas is None:
self.load_search_areas()

in_file_path = os.path.join(config['img_directory'], config['img_name_string'].format(int(config['ref_img_id'])))
in_file_path = os.path.join(config['img_directory'], ledsa.core.image_handling.format_img_name(config['img_name_string'], config['ref_img_id']))
# TODO this currently only works for RAW files but should work for JPG files as well
data = ledsa.core.image_reading.read_img_array_from_img(in_file_path, channel=0)
search_area_radius = int(config['search_area_radius'])
Expand Down
5 changes: 3 additions & 2 deletions ledsa/data_extraction/init_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import List

from ledsa.core.ConfigData import ConfigData
from ledsa.core.image_handling import format_img_name
from ledsa.core.image_reading import get_exif_entry


Expand Down Expand Up @@ -114,7 +115,7 @@ def _calc_experiment_and_real_time(build_type: str, config: ConfigData, tag: str
:rtype: tuple
"""
exif_entry = get_exif_entry(os.path.join(config['DEFAULT']['img_directory'],
config['DEFAULT']['img_name_string'].format(int(img_number))), tag)
format_img_name(config['DEFAULT']['img_name_string'], img_number)), tag)
date, time_meta = exif_entry.split(' ')
date_time_img = _get_datetime_from_str(date, time_meta)

Expand Down Expand Up @@ -201,7 +202,7 @@ def _build_img_data_string(build_type: str, config: ConfigData) -> str:
for img_id in img_id_list:
tag = 'DateTimeOriginal'
experiment_time, time = _calc_experiment_and_real_time(build_type, config, tag, img_id)
img_data += (str(img_idx) + ',' + config[build_type]['img_name_string'].format(int(img_id)) +
img_data += (str(img_idx) + ',' + format_img_name(config[build_type]['img_name_string'], img_id) +
',' + time.strftime('%H:%M:%S') + ',' + str(experiment_time) + '\n')
img_idx += 1
return img_data
Expand Down
Empty file.
41 changes: 41 additions & 0 deletions ledsa/tests/UnitTests/test_raw_format_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import numpy as np
import pytest

import ledsa.core.image_reading as image_reading
from ledsa.core.image_handling import format_img_name


class TestRawExtensionRouting:
@pytest.fixture
def raw_reader_stub(self, monkeypatch):
calls = []

def stub(filename, channel):
calls.append(filename)
return np.zeros((4, 4))

monkeypatch.setattr(image_reading, '_read_channel_data_from_raw_file', stub)
return calls

@pytest.mark.parametrize('extension', ['.CR2', '.CR3', '.NEF', '.ARW', '.DNG'])
def test_raw_formats_are_routed_to_raw_reader(self, raw_reader_stub, extension):
image_reading.read_channel_data_from_img(f'img_0001{extension}', channel=0)
assert raw_reader_stub == [f'img_0001{extension}']

def test_unsupported_format_raises(self):
with pytest.raises(ValueError, match='Unsupported image format'):
image_reading.read_channel_data_from_img('img_0001.xyz', channel=0)


class TestFormatImgName:
def test_plain_placeholder_preserves_leading_zeros(self):
assert format_img_name('DSC_{}.NEF', '0001') == 'DSC_0001.NEF'

def test_plain_placeholder_with_int_id(self):
assert format_img_name('test_img_{}.jpg', 7) == 'test_img_7.jpg'

def test_numeric_format_spec_with_string_id(self):
assert format_img_name('IMG_{:04d}.CR2', '7') == 'IMG_0007.CR2'

def test_numeric_format_spec_with_int_id(self):
assert format_img_name('IMG_{:04d}.CR2', 7) == 'IMG_0007.CR2'