Skip to content

Commit 9c99d08

Browse files
authored
Merge pull request #126 from FireDynamics/develop_bugfixes
Develop bugfixes
2 parents 9c9980e + 325f040 commit 9c99d08

3 files changed

Lines changed: 51 additions & 13 deletions

File tree

ledsa/core/image_reading.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,25 @@ def read_channel_data_from_img(filename: str, channel: int) -> np.ndarray:
2626
channel_array = _read_channel_data_from_raw_file(filename, channel)
2727
return channel_array
2828

29+
def read_img_array_from_img(filename: str, channel: int) -> np.ndarray:
30+
"""
31+
Returns a 2D array of the image for all color channels.
32+
33+
34+
:param filename: The path of the image file to read.
35+
:type filename: str
36+
:param channel: The color channel used for calculating black level values.
37+
:type channel: int
38+
:return: A 2D array containing the processed image data for all color channels.
39+
:rtype: np.ndarray
40+
"""
41+
extension = os.path.splitext(filename)[-1]
42+
if extension in ['.JPG', '.JPEG', '.jpg', '.jpeg', '.PNG', '.png']:
43+
img_array = _read_grayscale_img_array_from_img_file(filename)
44+
elif extension in ['.CR2', '.CR3']:
45+
img_array, _ = _read_img_array_from_raw_file(filename, channel)
46+
return img_array
47+
2948

3049
def _read_channel_data_from_img_file(filename: str, channel: int) -> np.ndarray:
3150
"""
@@ -38,7 +57,7 @@ def _read_channel_data_from_img_file(filename: str, channel: int) -> np.ndarray:
3857
:return: A 2D numpy array containing the data of the specified color channel from the image.
3958
:rtype: np.ndarray
4059
"""
41-
img_array = read_img_array_from_img_file(filename)
60+
img_array = _read_img_array_from_img_file(filename)
4261
return img_array[:, :, channel]
4362

4463

@@ -54,15 +73,15 @@ def _read_channel_data_from_raw_file(filename: str, channel: int) -> np.ndarray:
5473
:return: A 2D numpy array representing the extracted channel, with all other channel values masked or set to zero.
5574
:rtype: np.ndarray
5675
"""
57-
img_array, filter_array = read_img_array_from_raw_file(filename, channel)
76+
img_array, filter_array = _read_img_array_from_raw_file(filename, channel)
5877
if channel == 0 or channel == 2:
5978
channel_array = np.where(filter_array == channel, img_array, 0)
6079
elif channel == 1:
6180
channel_array = np.where((filter_array == 1) | (filter_array == 3), img_array, 0)
6281
return channel_array
6382

6483

65-
def read_img_array_from_raw_file(filename: str, channel: int) -> np.ndarray:
84+
def _read_img_array_from_raw_file(filename: str, channel: int) -> np.ndarray:
6685
# TODO: channel is only relevant for black level, consider individually!
6786
with rawpy.imread(filename) as raw:
6887
data = raw.raw_image_visible.copy()
@@ -74,10 +93,16 @@ def read_img_array_from_raw_file(filename: str, channel: int) -> np.ndarray:
7493
img_array = np.clip(img_array, 0, white_level)
7594
return img_array, filter_array
7695

77-
def read_img_array_from_img_file(filename: str) -> np.ndarray:
96+
def _read_img_array_from_img_file(filename: str) -> np.ndarray:
7897
img_array = plt.imread(filename)
7998
return img_array
8099

100+
def _read_grayscale_img_array_from_img_file(filename: str) -> np.ndarray:
101+
img_array = plt.imread(filename)
102+
weights = np.array([0.2989, 0.5870, 0.1140])
103+
gray = np.dot(img_array[..., :3], weights).astype(np.uint8)
104+
return gray
105+
81106

82107
def get_exif_entry(filename: str, tag: str) -> str:
83108
"""

ledsa/data_extraction/DataExtractor.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44

55
import matplotlib.pyplot as plt
6+
from matplotlib.colors import LogNorm
67
import numpy as np
78
from tqdm import tqdm
89

@@ -26,7 +27,9 @@ class DataExtractor:
2627
:vartype channels: Tuple
2728
:ivar fit_leds: Whether to fit LEDs or not.
2829
:vartype fit_leds: bool
29-
:ivar search_areas: 2D numpy array with dimension (# of LEDs) x (LED_id, x, y).
30+
:ivar fit_leds: Whether to fit LEDs or not.
31+
:vartype threshold: float, optional
32+
:ivar threshold: The threshold value used for LED detection.
3033
:vartype search_areas: numpy.ndarray, optional
3134
:ivar line_indices: 2D list with dimension (# of LED arrays) x (# of LEDs per array) or None.
3235
:vartype line_indices: list[list[int]], optional
@@ -46,6 +49,7 @@ def __init__(self, channels=(0), load_config_file=True, build_experiment_infos=T
4649
self.config = ConfigData(load_config_file=load_config_file)
4750
self.channels = list(channels)
4851
self.fit_leds = fit_leds
52+
self.threshold = None
4953

5054
# 2D numpy array with dimension (# of LEDs) x (LED_id, x, y)
5155
self.search_areas = None
@@ -95,12 +99,13 @@ def find_search_areas(self) -> None:
9599
max_num_leds = int(config['max_num_leds'])
96100
pixel_value_percentile = float(config['pixel_value_percentile'])
97101
if channel == 'all':
98-
data, _ = ledsa.core.image_reading.read_img_array_from_raw_file(in_file_path, channel=0) # TODO: Channel to be removed here!
102+
# TODO this currently only works for RAW files but should work for JPG files as well
103+
data = ledsa.core.image_reading.read_img_array_from_img(in_file_path, channel=0) # TODO: Channel to be removed here!
99104
else:
100105
channel = int(channel)
101106
data = ledsa.core.image_reading.read_channel_data_from_img(in_file_path, channel=channel)
102107

103-
self.search_areas = ledsa.data_extraction.step_1_functions.find_search_areas(data, search_area_radius=search_area_radius, max_n_leds=max_num_leds, pixel_value_percentile=pixel_value_percentile)
108+
self.search_areas, self.threshold = ledsa.data_extraction.step_1_functions.find_search_areas(data, search_area_radius=search_area_radius, max_n_leds=max_num_leds, pixel_value_percentile=pixel_value_percentile)
104109
self.write_search_areas()
105110
self.plot_search_areas()
106111
ledsa.core.file_handling.remove_flag('reorder_leds')
@@ -122,12 +127,14 @@ def plot_search_areas(self, reorder_leds=False) -> None:
122127
self.load_search_areas()
123128

124129
in_file_path = os.path.join(config['img_directory'], config['img_name_string'].format(int(config['ref_img_id'])))
125-
data = ledsa.core.image_reading.read_channel_data_from_img(in_file_path, channel=0)
130+
# TODO this currently only works for RAW files but should work for JPG files as well
131+
data = ledsa.core.image_reading.read_img_array_from_img(in_file_path, channel=0)
126132
search_area_radius = int(config['search_area_radius'])
127133
plt.figure(dpi=1200)
128134
ax = plt.gca()
129135
ledsa.data_extraction.step_1_functions.add_search_areas_to_plot(self.search_areas, search_area_radius, ax)
130-
plt.imshow(data, cmap='Greys')
136+
plt.imshow(data, norm=LogNorm(vmin=self.threshold, vmax=data.max()), cmap='Grays')
137+
131138
plt.xlim(self.search_areas[:, 2].min() - 5 * search_area_radius, self.search_areas[:, 2].max() + 5 * search_area_radius)
132139
plt.ylim(self.search_areas[:, 1].max() + 5 * search_area_radius, self.search_areas[:, 1].min() - 5 * search_area_radius)
133140
plt.colorbar()

ledsa/data_extraction/step_1_functions.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
from typing import Any
2+
13
import numpy as np
24
from matplotlib import pyplot as plt
35
import cv2
6+
from numpy import ndarray, dtype, floating
47

58

6-
def find_search_areas(image: np.ndarray, search_area_radius, pixel_value_percentile=99.875, max_n_leds=1300) -> np.ndarray:
9+
def find_search_areas(image: np.ndarray, search_area_radius, pixel_value_percentile=99.875, max_n_leds=1300) -> tuple[
10+
ndarray[Any, dtype[Any]], floating[Any]]:
711
"""
812
Identifies and extracts locations of LEDs in an image.
913
@@ -15,8 +19,10 @@ def find_search_areas(image: np.ndarray, search_area_radius, pixel_value_percent
1519
:type pixel_value_percentile: float
1620
:param max_n_leds: The maximum number of LED locations to identify in the image.
1721
:type max_n_leds: int
18-
:return: A numpy array of identified LED locations, each represented as (LED ID, y-coordinate, x-coordinate).
19-
:rtype: np.ndarray
22+
:return: A tuple containing:
23+
- A numpy array of identified LED locations, each represented as (LED ID, y-coordinate, x-coordinate).
24+
- The threshold value used for LED detection.
25+
:rtype: tuple[np.ndarray, float]
2026
"""
2127
(_, max_pixel_value, _, max_pixel_loc) = cv2.minMaxLoc(image)
2228
threshold = np.percentile(image, pixel_value_percentile)
@@ -34,7 +40,7 @@ def find_search_areas(image: np.ndarray, search_area_radius, pixel_value_percent
3440
led_id += 1
3541
print('\n')
3642
print(f"Found {led_id} LEDS")
37-
return np.array(search_areas_list)
43+
return np.array(search_areas_list), threshold
3844

3945

4046
def add_search_areas_to_plot(search_areas: np.ndarray, search_area_radius: int, ax: plt.axes) -> None:

0 commit comments

Comments
 (0)