Skip to content
Closed
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
13 changes: 7 additions & 6 deletions ledsa/core/ConfigData.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import configparser as cp
import os.path
from datetime import datetime, timedelta

import numpy as np
Expand Down Expand Up @@ -271,10 +272,10 @@ def in_time_diff_to_img_time(self) -> None:
time = input('Please give the time shown on the clock in the time reference image in hh:mm:ss: ')
self['DEFAULT']['time_ref_img_time'] = str(time)
time = self['DEFAULT']['time_ref_img_time']
print(self['DEFAULT']['img_directory'] + self['DEFAULT']['time_img_id'])
tag = 'EXIF DateTimeOriginal'
exif_entry = get_exif_entry(self['DEFAULT']['img_directory'] + self['DEFAULT']['img_name_string'].format(
self['DEFAULT']['time_img_id']), tag)
print(os.path.join(self['DEFAULT']['img_directory'], self['DEFAULT']['time_img_id']))
tag = 'DateTimeOriginal'
exif_entry = get_exif_entry(os.path.join(self['DEFAULT']['img_directory'], self['DEFAULT']['img_name_string']
.format(self['DEFAULT']['time_img_id'])), tag)
date, time_meta = exif_entry.split(' ')
self['DEFAULT']['date'] = date
img_time = _get_datetime_from_str(date, time_meta)
Expand Down Expand Up @@ -351,8 +352,8 @@ def get_start_time(self) -> None:
Updates the 'DEFAULT' key with the 'start_time' computed.

"""
exif_entry = get_exif_entry(self['DEFAULT']['img_directory'] + self['DEFAULT']['img_name_string'].format(
self['DEFAULT']['first_img_experiment_id']), 'EXIF DateTimeOriginal')
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)
start_time = time_img - timedelta(seconds=self['DEFAULT'].getint('exif_time_infront_real_time'))
Expand Down
12 changes: 7 additions & 5 deletions ledsa/core/image_reading.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os

import exifread
import exiv2
import numpy as np
import rawpy
from matplotlib import pyplot as plt
Expand All @@ -22,7 +22,7 @@ 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']:
elif extension in ['.CR2', '.CR3']:
channel_array = _read_channel_data_from_raw_file(filename, channel)
return channel_array

Expand Down Expand Up @@ -91,10 +91,12 @@ def get_exif_entry(filename: str, tag: str) -> str:
:rtype: str
:raises KeyError: If the EXIF tag is not found in the image metadata.
"""
with open(filename, 'rb') as f:
exif = exifread.process_file(f, details=False, stop_tag=tag)
img = exiv2.ImageFactory.open(filename)
img.readMetadata()
exiv_data = img.exifData()
full_tag = f'Exif.Photo.{tag}'
try:
return exif[tag].values
return exiv_data[full_tag].print()
except KeyError:
print("No EXIF metadata found")
exit(1)
4 changes: 2 additions & 2 deletions ledsa/data_extraction/DataExtractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,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(config['ref_img_id']))
in_file_path = os.path.join(config['img_directory'], config['img_name_string'].format(int(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 @@ -121,7 +121,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(config['ref_img_id']))
in_file_path = os.path.join(config['img_directory'], config['img_name_string'].format(int(config['ref_img_id'])))
data = ledsa.core.image_reading.read_channel_data_from_img(in_file_path, channel=0)
search_area_radius = int(config['search_area_radius'])
plt.figure(dpi=1200)
Expand Down
6 changes: 3 additions & 3 deletions ledsa/data_extraction/init_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ def _calc_experiment_and_real_time(build_type: str, config: ConfigData, tag: str
:return: Tuple containing experiment time and real time.
:rtype: tuple
"""
exif_entry = get_exif_entry(config['DEFAULT']['img_directory'] +
config['DEFAULT']['img_name_string'].format(int(img_number)), tag)
exif_entry = get_exif_entry(os.path.join(config['DEFAULT']['img_directory'],
config['DEFAULT']['img_name_string'].format(int(img_number))), tag)
date, time_meta = exif_entry.split(' ')
date_time_img = _get_datetime_from_str(date, time_meta)

Expand Down Expand Up @@ -193,7 +193,7 @@ def _build_img_data_string(build_type: str, config: ConfigData) -> str:
img_increment = config.getint(build_type, 'num_skip_imgs') + 1 if build_type == 'analyse_photo' else 1
img_id_list = _find_img_number_list(first_img_id, last_img_id, img_increment)
for img_id in img_id_list:
tag = 'EXIF DateTimeOriginal'
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)) +
',' + time.strftime('%H:%M:%S') + ',' + str(experiment_time) + '\n')
Expand Down
2 changes: 1 addition & 1 deletion ledsa/data_extraction/step_3_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ def _log_warnings(img_filename, channel, led_data, cx, cy, size_of_search_area,
:type conf: ConfigData
"""
res = ' '.join(np.array_str(led_data.fit_results.x).split()).replace('[ ', '[').replace(' ]', ']').replace(' ', ',')
img_file_path = conf['DEFAULT']['img_directory'] + img_filename
img_file_path = os.path.join(conf['DEFAULT']['img_directory'], img_filename)

log = f'Irregularities while fitting:\n {img_file_path} {led_data.led_id} {led_data.led_array} {res} ' \
f'{led_data.fit_results.success} {led_data.fit_results.fun} {led_data.fit_results.nfev} ' \
Expand Down
2 changes: 1 addition & 1 deletion ledsa/tests/AcceptanceTests/05_test_analysis.robot
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,4 @@ Check Results

Rmse Should Be Small
[Arguments] ${rmse}
Should Be True ${rmse} < 0.05
Should Be True ${rmse} < 0.15
22 changes: 14 additions & 8 deletions ledsa/tests/AcceptanceTests/LedsaATestLibrary.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import matplotlib.pyplot as plt
import numpy as np
from numpy.random import normal
import piexif
import exiv2
from PIL import Image
from robot.api.deco import keyword, library
from robot.libraries.BuiltIn import BuiltIn
Expand Down Expand Up @@ -166,14 +166,20 @@ def create_test_image(image_id, experiment):
num_of_leds = len(experiment.leds)
transmissions = experiment.calc_all_led_transmissions()
img_array = create_img_array(num_of_leds, transmissions)

img_array = np.clip(img_array, 0, 255).astype(np.uint8)
img = Image.fromarray(img_array, 'RGB')
exif_ifd = {
piexif.ExifIFD.DateTimeOriginal: f'2021:01:01 12:00:{0 + image_id:01d}'
}
exif_dict = {'Exif': exif_ifd}
exif_bytes = piexif.dump(exif_dict)
img.save(os.path.join('test_data', f'test_img_{image_id + 1}.jpg'), exif=exif_bytes)

# Save image without EXIF data
out = os.path.join("test_data", f"test_img_{image_id + 1}.jpg")
img.save(out)

# Add EXIF data to the image afterward
img2 = exiv2.ImageFactory.open(out)
img2.readMetadata()
ex = img2.exifData()
ex["Exif.Photo.DateTimeOriginal"] = f"2021:01:01 12:00:{image_id:02d}"
img2.setExifData(ex)
img2.writeMetadata()


def create_img_array(num_of_leds, transmissions):
Expand Down
65 changes: 40 additions & 25 deletions ledsa/tools/exposure_checker.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"source": [
"import os\n",
"from ledsa.core.image_reading import read_channel_data_from_img, get_exif_entry"
],
"id": "41c98f47ba1f266f"
"id": "41c98f47ba1f266f",
"outputs": [],
"execution_count": null
},
{
"metadata": {},
Expand All @@ -36,17 +36,21 @@
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"source": [
"# Configure the image path and range\n",
"image_dir = \"/path/to/your/images\" # Update this to your image directory\n",
"image_name_string = \"IMG_{:04d}.JPG\" # F-string template for image names\n",
"image_range = range(1, 10) # Range of image numbers to process\n",
"channel = 0 # Color channel to analyze (0=Red, 1=Green, 2=Blue)\n",
"saturation = 255"
"\n",
"# Set saturation limit for image (usually 2**8-1 for JPG and 2**14-1 for RAW files)\n",
"if image_name_string.split('.')[-1] in ['JPG', 'JPEG', 'jpg', 'jpeg']:\n",
" saturation = 2**8-1\n",
"elif image_name_string.split('.')[-1] in ['CR2', 'CR3']:\n",
" saturation = 2**14-1"
],
"id": "aa799b526e7eb076"
"id": "aa799b526e7eb076",
"outputs": [],
"execution_count": null
},
{
"metadata": {},
Expand All @@ -57,8 +61,6 @@
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"source": [
"# Process each image in the range\n",
"for img_id in image_range:\n",
Expand All @@ -73,24 +75,37 @@
" print(f\"Processing image: {image_filename}\")\n",
" \n",
" # Read the image data for the specified channel\n",
" try:\n",
" exposure_time = get_exif_entry(image_filename, 'ExposureTime')\n",
" channel_array_0 = read_channel_data_from_img(image_filename, 0)\n",
" channel_array_1 = read_channel_data_from_img(image_filename, 1)\n",
" channel_array_2 = read_channel_data_from_img(image_filename, 2)\n",
" \n",
" print(f\"Exposure Time: {exposure_time}\")\n",
" print(f\"Max CH0: {channel_array_0.max()}, Sat CH0: {channel_array_0.max()/saturation*100} %\")\n",
" print(f\"Max CH1: {channel_array_1.max()}, Sat CH1: {channel_array_1.max()/saturation*100} %\")\n",
" print(f\"Max CH2: {channel_array_2.max()}, Sat CH2: {channel_array_2.max()/saturation*100} %\")\n",
" print(\"-----------------\")\n",
"\n",
" \n",
" exposure_time = get_exif_entry(image_filename, 'ExposureTime')\n",
" channel_array_0 = read_channel_data_from_img(image_filename, 0)\n",
" channel_array_1 = read_channel_data_from_img(image_filename, 1)\n",
" channel_array_2 = read_channel_data_from_img(image_filename, 2)\n",
"\n",
" \n",
"\n"
" print(f\"Exposure Time: {exposure_time}\")\n",
" print(f\"Max CH0: {channel_array_0.max()}, Sat CH0: {channel_array_0.max()/saturation*100} %\")\n",
" print(f\"Max CH1: {channel_array_1.max()}, Sat CH1: {channel_array_1.max()/saturation*100} %\")\n",
" print(f\"Max CH2: {channel_array_2.max()}, Sat CH2: {channel_array_2.max()/saturation*100} %\")\n",
" print(\"-----------------\")"
],
"id": "344c5f7ce039571b"
"id": "344c5f7ce039571b",
"outputs": [],
"execution_count": null
},
{
"metadata": {},
"cell_type": "code",
"source": "",
"id": "d59edd59c70e781f",
"outputs": [],
"execution_count": null
},
{
"metadata": {},
"cell_type": "code",
"source": "",
"id": "a323ef7ab9b6805a",
"outputs": [],
"execution_count": null
}
],
"metadata": {},
Expand Down
7 changes: 3 additions & 4 deletions ledsa/tools/photo_renamer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
from os import path
import csv
import pandas as pd
import exifread

from ledsa.core.image_reading import get_exif_entry

def set_working_dir():
"""
Expand Down Expand Up @@ -49,11 +49,10 @@ def get_files():
with open(image, 'rb') as image_file:
tag_datetime = 'DateTimeOriginal'
tag_subsectime = 'SubSecTimeDigitized'
exif = exifread.process_file(image_file, details=False)
capture_date = exif[f"EXIF {tag_datetime}"].values
capture_date = get_exif_entry(image, tag_datetime)
try:
# Try parsing with subsecond precision
subsec_time = exif[f"EXIF {tag_subsectime}"].values
subsec_time = get_exif_entry(image, tag_subsectime)
datetime_object = datetime.strptime(capture_date + "." + subsec_time, '%Y:%m:%d %H:%M:%S.%f')
except:
# Fall back to second precision
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ dependencies = [
"newrawpy >= 0.18.1; platform_machine == 'arm64'",
"rawpy >= 0.18.1; platform_machine != 'arm64'",
"tqdm >=4.66.2",
"exifread >= 3.0.0",
"exiv2 >= 0.18.0",
"piexif >= 1.1.3",
"robotframework >= 6.1.1",
"pillow >= 10.2.0",
Expand Down
Loading