ledsa 0.9.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ledsa might be problematic. Click here for more details.

Files changed (45) hide show
  1. ledsa/__init__.py +0 -0
  2. ledsa/__main__.py +48 -0
  3. ledsa/analysis/ConfigDataAnalysis.py +163 -0
  4. ledsa/analysis/Experiment.py +313 -0
  5. ledsa/analysis/ExperimentData.py +118 -0
  6. ledsa/analysis/ExtinctionCoefficients.py +216 -0
  7. ledsa/analysis/ExtinctionCoefficientsLinear.py +147 -0
  8. ledsa/analysis/ExtinctionCoefficientsNonLinear.py +136 -0
  9. ledsa/analysis/__init__.py +0 -0
  10. ledsa/analysis/data_preparation.py +28 -0
  11. ledsa/core/ConfigData.py +385 -0
  12. ledsa/core/__init__.py +0 -0
  13. ledsa/core/file_handling.py +400 -0
  14. ledsa/core/image_handling.py +39 -0
  15. ledsa/core/image_reading.py +100 -0
  16. ledsa/core/parser_arguments_declaration.py +109 -0
  17. ledsa/core/parser_arguments_run.py +244 -0
  18. ledsa/data_extraction/DataExtractor.py +266 -0
  19. ledsa/data_extraction/LEDAnalysisData.py +85 -0
  20. ledsa/data_extraction/__init__.py +0 -0
  21. ledsa/data_extraction/init_functions.py +228 -0
  22. ledsa/data_extraction/model.py +103 -0
  23. ledsa/data_extraction/step_1_functions.py +58 -0
  24. ledsa/data_extraction/step_2_functions.py +428 -0
  25. ledsa/data_extraction/step_3_functions.py +312 -0
  26. ledsa/demo/__init__.py +0 -0
  27. ledsa/demo/demo_run.py +29 -0
  28. ledsa/demo/demo_setup.py +252 -0
  29. ledsa/ledpositions/__init__.py +0 -0
  30. ledsa/ledpositions/coordinates.py +222 -0
  31. ledsa/postprocessing/__init__.py +0 -0
  32. ledsa/postprocessing/helper_functions.py +3 -0
  33. ledsa/postprocessing/simulation.py +410 -0
  34. ledsa/tests/AcceptanceTests/LedsaATestLibrary.py +226 -0
  35. ledsa/tests/AcceptanceTests/TestExperiment.py +148 -0
  36. ledsa/tests/AcceptanceTests/__init__.py +0 -0
  37. ledsa/tests/AcceptanceTests/__main__.py +14 -0
  38. ledsa/tests/__init__.py +0 -0
  39. ledsa/tests/__main__.py +0 -0
  40. ledsa/tools/__init__.py +0 -0
  41. ledsa/tools/photo_renamer.py +160 -0
  42. ledsa-0.9.2.dist-info/METADATA +174 -0
  43. ledsa-0.9.2.dist-info/RECORD +45 -0
  44. ledsa-0.9.2.dist-info/WHEEL +5 -0
  45. ledsa-0.9.2.dist-info/top_level.txt +1 -0
ledsa/__init__.py ADDED
File without changes
ledsa/__main__.py ADDED
@@ -0,0 +1,48 @@
1
+ # this file is executed if the package is run via python -m ledsa [arguments]
2
+ import argparse
3
+ import sys
4
+ from typing import List
5
+
6
+ from ledsa.core.parser_arguments_declaration import (add_parser_arguments_tools, add_parser_arguments_data_extraction,
7
+ add_parser_arguments_testing, add_parser_arguments_demo, add_parser_argument_analysis)
8
+ from ledsa.core.parser_arguments_run import run_tools_arguments, run_data_extraction_arguments, run_testing_arguments, run_demo_arguments, \
9
+ run_analysis_arguments, run_analysis_arguments_with_extinction_coefficient
10
+
11
+
12
+ def main(argv: List[str]) -> None:
13
+ """
14
+ Main function to execute the LEDSA package with command line arguments.
15
+
16
+ :param argv: Command line arguments.
17
+ :type argv: list[str]
18
+ """
19
+ parser = argparse.ArgumentParser(description=
20
+ 'Allows the analysis of light dampening of LEDs behind a smoke screen.')
21
+ add_parser_arguments_tools(parser)
22
+ add_parser_arguments_data_extraction(parser)
23
+ add_parser_argument_analysis(parser)
24
+ add_parser_arguments_demo(parser)
25
+ add_parser_arguments_testing(parser)
26
+
27
+ args = parser.parse_args(argv)
28
+
29
+ print('ledsa runs with the following arguments:')
30
+ print(args)
31
+
32
+ if len(argv) == 0:
33
+ print('Please run with an argument. --help for possible arguments.')
34
+ exit(0)
35
+
36
+ if args.demo:
37
+ run_demo_arguments(args, parser)
38
+ else:
39
+ run_tools_arguments(args)
40
+ run_data_extraction_arguments(args)
41
+ run_analysis_arguments(args)
42
+ run_analysis_arguments_with_extinction_coefficient(args)
43
+ run_testing_arguments(args)
44
+
45
+
46
+
47
+ if __name__ == "__main__":
48
+ main(sys.argv[1:])
@@ -0,0 +1,163 @@
1
+ import configparser as cp
2
+
3
+
4
+ class ConfigDataAnalysis(cp.ConfigParser):
5
+ """
6
+ Class responsible for handling the configuration data related to LEDSA's data analysis.
7
+
8
+ """
9
+ def __init__(self, load_config_file=True, camera_position=None, num_layers=20, domain_bounds=None,
10
+ led_array_indices=None, num_ref_images=10, camera_channels=0, num_cores=1,
11
+ reference_property='sum_col_val',
12
+ average_images=False, solver='linear', weighting_preference=-6e-3, weighting_curvature=1e-6,
13
+ num_iterations=200, lambda_reg=1e-3):
14
+ """
15
+ :param load_config_file: Determines whether to load the config file on initialization. Defaults to True.
16
+ :type load_config_file: bool
17
+ :param camera_position: Global X, Y, Z position of the camera. Defaults to None.
18
+ :type camera_position: list[float] or None
19
+ :param num_layers: Number of horizontal smoke layers. Defaults to 20.
20
+ :type num_layers: int
21
+ :param domain_bounds: Lower and upper bounds of the computational domain. Defaults to None.
22
+ :type domain_bounds: list[float] or None
23
+ :param led_array_indices: Indices of the LED arrays for which the extinction coefficients should be computed. Defaults to None.
24
+ :type led_array_indices: list[int] or None
25
+ :param num_ref_images: Number of images used to compute normalize LED intensities. Defaults to 10.
26
+ :type num_ref_images: int
27
+ :param camera_channels: Camera channels to be considered in the analysis. Defaults to 0.
28
+ :type camera_channels: List[int]
29
+ :param num_cores: Number of CPU cores for (multicore) processing. If greater than 1, multicore processing is applied. Defaults to 1.
30
+ :type num_cores: int
31
+ :param reference_property: Property used for reference in LEDSA. Defaults to 'sum_col_val'.
32
+ :type reference_property: str
33
+ :param average_images: Determines if intensities are computed as an average from two consecutive images. Defaults to False.
34
+ :type average_images: bool
35
+ :param solver: Method used to compute extinction coefficients - can be 'linear' or 'nonlinear'. Defaults to 'linear'.
36
+ :type solver: str
37
+ :param weighting_preference: Weighting factor for the preference to push the nonlinear solver to high or low values for the extinction coeffiientes. Defaults to -6e-3.
38
+ :type weighting_preference: float
39
+ :param weighting_curvature: Weighting factor for the smoothness of the solution. Defaults to 1e-6.
40
+ :type weighting_curvature: float
41
+ :param num_iterations: Maximum number of iterations for the nonlinear solver. Defaults to 200.
42
+ :type num_iterations: int
43
+ :param lambda_reg: Regularization parameter for the linear solver. Defaults to 1e-3.
44
+ :type lambda_reg: float
45
+ """
46
+ cp.ConfigParser.__init__(self, allow_no_value=True)
47
+ if load_config_file:
48
+ self.load()
49
+ else:
50
+ self['DEFAULT'] = {}
51
+ self.set('DEFAULT', '# Variables used in multiple parts of LEDSA')
52
+ self.set('DEFAULT', ' # Number of CPUs, multicore processing is applied if > 1')
53
+ self['DEFAULT'][' num_cores'] = str(num_cores)
54
+ self['DEFAULT'][' reference_property'] = str(reference_property)
55
+ self.set('DEFAULT', ' # Number images used to compute normalize LED intensities')
56
+ self['DEFAULT'][' num_ref_images'] = str(num_ref_images)
57
+ self['DEFAULT'][' camera_channels'] = str(camera_channels)
58
+ self.set('DEFAULT', ' # Intensities are computed as average from two consecutive images if set to True ')
59
+ self['DEFAULT'][' average_images'] = str(average_images)
60
+ self.set('DEFAULT', ' # Extinction coefficients can be computed by linear or numeric solver ')
61
+ self['DEFAULT'][' solver'] = str(solver)
62
+ self.set('DEFAULT', ' # Options for nonlinear solver (ignored when linear solver is used) ')
63
+ self['DEFAULT'][' weighting_preference'] = str(weighting_preference)
64
+ self['DEFAULT'][' weighting_curvature'] = str(weighting_curvature)
65
+ self['DEFAULT'][' num_iterations'] = str(num_iterations)
66
+ self.set('DEFAULT', ' # Options for linear solver (ignored when nonlinear solver is used) ')
67
+ self['DEFAULT'][' lambda_reg'] = str(lambda_reg)
68
+
69
+ self['experiment_geometry'] = {}
70
+ self.set('experiment_geometry', '# Global X Y Z position of the camera ')
71
+ self['experiment_geometry'][' camera_position'] = str(camera_position)
72
+
73
+ self['model_parameters'] = {}
74
+ self.set('model_parameters', '# Parameters regarding the discretization of the spatial domain ')
75
+ self.set('model_parameters', ' # LED arrays for that the extinction coefficients should be computed ')
76
+ self['model_parameters'][' led_array_indices'] = str(led_array_indices)
77
+ self.set('model_parameters', ' # Number of horizontal smoke layers ')
78
+ self['model_parameters'][' num_layers'] = str(num_layers)
79
+ self.set('model_parameters', ' # Lower and upper bounds of the computational domain ')
80
+ self['model_parameters'][' domain_bounds'] = str(domain_bounds)
81
+
82
+ with open('config_analysis.ini', 'w') as configfile:
83
+ self.write(configfile)
84
+ print('config_analysis.ini created')
85
+
86
+ def load(self) -> None:
87
+ """
88
+ Loads the configuration data from 'config_analysis.ini' file.
89
+
90
+ Raises:
91
+ FileNotFoundError: If 'config_analysis.ini' is not found in the working directory.
92
+ """
93
+ try:
94
+ self.read_file(open('config_analysis.ini'))
95
+ except FileNotFoundError:
96
+ print(
97
+ 'config_analysis.ini not found in working directory! Please create it with argument "--config_analysis".')
98
+ exit(1)
99
+ print('config_analysis.ini loaded!')
100
+
101
+ def save(self) -> None:
102
+ """
103
+ Saves the current configuration to 'config_analysis.ini' file.
104
+ """
105
+ with open('config_analysis.ini', 'w') as configfile:
106
+ self.write(configfile)
107
+ print('config_analysis.ini saved')
108
+
109
+ def get_list_of_values(self, section:str, option:str, dtype=int) -> None:
110
+ """
111
+ Returns a list of values of a specified dtype from a given section and option.
112
+
113
+ :param section: Section in the configuration file.
114
+ :type section: str
115
+ :param option: Option under the specified section.
116
+ :type option: str
117
+ :param dtype: Data type of the values to be returned. Defaults to int.
118
+ :type dtype: type
119
+ :return: List of values or None if the option's value is 'None'.
120
+ :rtype: list or None
121
+ """
122
+ if self[section][option] == 'None':
123
+ return None
124
+ values = [dtype(i) for i in self[section][option].split()]
125
+ return values
126
+
127
+ def in_camera_channels(self) -> None:
128
+ """
129
+ Prompts the user to input the camera channels to analyse and updates the configuration.
130
+ """
131
+ self['DEFAULT']['camera_channels'] = input('Please give the camera channels that should be considered in the '
132
+ 'analysis: ')
133
+
134
+ def in_camera_position(self) -> None:
135
+ """
136
+ Prompts the user to input the camera's global X, Y, Z coordinates and updates the configuration.
137
+ """
138
+ self['experiment_geometry']['camera_position'] = input('Please give the global X Y Z [m] coordinates of the '
139
+ 'camera : ')
140
+
141
+ def in_num_layers(self) -> None:
142
+ """
143
+ Prompts the user to input the number of layers for spatial domain discretization and updates the configuration.
144
+ """
145
+ self['model_parameters']['num_layers'] = input('Please give number of layers the spatial domain should '
146
+ 'be discretized to : ')
147
+
148
+ def in_domain_bounds(self) -> None:
149
+ """
150
+ Prompts the user to input the lower and upper height of the spatial domain and updates the configuration.
151
+ """
152
+ self['model_parameters']['domain_bounds'] = input('Please give lower and upper height [m] of the spatial '
153
+ 'domain : ')
154
+
155
+ def in_led_array_indices(self) -> None:
156
+ """
157
+ Prompts the user to input the indices of (merged) LED Arrays for computation and updates the configuration.
158
+ """
159
+ self['model_parameters']['led_array_indices'] = input('Please give indices of (merged) LED Arrays to compute: ')
160
+
161
+
162
+ if __name__ == 'main':
163
+ ConfigDataAnalysis()
@@ -0,0 +1,313 @@
1
+ import os
2
+ from dataclasses import dataclass, field
3
+ from pathlib import Path
4
+ from typing import List
5
+
6
+ import numpy as np
7
+
8
+
9
+ @dataclass
10
+ class Layer:
11
+ """
12
+ Represents a spatial layer with a bottom and top border.
13
+
14
+ :ivar bottom_border: The z-coordinate of the bottom border of the layer
15
+ :vartype bottom_border: float
16
+ :ivar top_border: The z-coordinate of the top border of the layer
17
+ :vartype top_border: float
18
+ """
19
+ bottom_border: float
20
+ top_border: float
21
+
22
+ def __contains__(self, obj):
23
+ """
24
+ Check if a given object's z-coordinate falls within this layer.
25
+
26
+ :param obj: The object to check
27
+ :type obj: object with attribute 'pos_z'
28
+ :return: True if object is contained within the layer, False otherwise
29
+ :rtype: bool
30
+ """
31
+ if self.bottom_border <= obj.pos_z < self.top_border:
32
+ return True
33
+ return False
34
+
35
+
36
+ @dataclass
37
+ class Layers:
38
+ """
39
+ Represents a collection of spatial layers.
40
+
41
+ :ivar amount: The number of layers
42
+ :vartype amount: int
43
+ :ivar bottom_border: The z-coordinate of the bottom-most border of the layers
44
+ :vartype bottom_border: float
45
+ :ivar top_border: The z-coordinate of the top-most border of the layers
46
+ :vartype top_border: float
47
+ """
48
+ amount: int
49
+ bottom_border: float
50
+ top_border: float
51
+ layers: [Layer] = field(init=False)
52
+ borders: np.ndarray = field(init=False)
53
+
54
+ def __post_init__(self):
55
+ self.layers = []
56
+ self.borders = np.linspace(self.bottom_border, self.top_border, self.amount + 1)
57
+ for i in range(self.amount):
58
+ self.layers.append(Layer(self.borders[i], self.borders[i + 1]))
59
+
60
+ def __getitem__(self, layer):
61
+ return self.layers[layer]
62
+
63
+ def __str__(self):
64
+ return f'num_of_layers: {self.amount}, bottom_border: {self.bottom_border}, top_border: {self.top_border}\n'
65
+
66
+ def __repr__(self):
67
+ return f'Layers(amount={self.amount}, bottom_border={self.bottom_border}, top_border={self.top_border})'
68
+
69
+
70
+ @dataclass
71
+ class Camera:
72
+ """
73
+ Contains a camera's position in 3D space.
74
+
75
+ :ivar pos_x: The x-coordinate of the camera
76
+ :vartype pos_x: float
77
+ :ivar pos_y: The y-coordinate of the camera
78
+ :vartype pos_y: float
79
+ :ivar pos_z: The z-coordinate of the camera
80
+ :vartype pos_z: float
81
+ """
82
+ pos_x: float
83
+ pos_y: float
84
+ pos_z: float
85
+
86
+ def __str__(self):
87
+ return f'Camera: ({self.pos_x}, {self.pos_y}, {self.pos_z})\n'
88
+
89
+ def __repr__(self):
90
+ return f'Camera(pos_x={self.pos_x}, pos_y={self.pos_y}, pos_z={self.pos_z})'
91
+
92
+
93
+ @dataclass
94
+ class LED:
95
+ """
96
+ Represents a LED light source in 3D space.
97
+
98
+ :ivar id: The identifier for the LED
99
+ :vartype id: int
100
+ :ivar pos_x: The x-coordinate of the LED
101
+ :vartype pos_x: float
102
+ :ivar pos_y: The y-coordinate of the LED
103
+ :vartype pos_y: float
104
+ :ivar pos_z: The z-coordinate of the LED
105
+ :vartype pos_z: float
106
+ """
107
+ id: int
108
+ pos_x: float
109
+ pos_y: float
110
+ pos_z: float
111
+
112
+
113
+ class Experiment:
114
+ """
115
+ Represents an experimental setup involving layers, an LED array, and a camera.
116
+
117
+ :ivar layers: The spatial layers involved in the experiment.
118
+ :vartype layers: Layers
119
+ :ivar led_array: The identifier for the LED array.
120
+ :vartype led_array: int
121
+ :ivar camera: The camera involved in the experiment.
122
+ :vartype camera: Camera
123
+ :ivar leds: List of LED light sources.
124
+ :vartype leds: List[LED]
125
+ :ivar led_number: Number of LEDs on LED array.
126
+ :vartype led_number: int
127
+ :ivar path: File path for experiment data.
128
+ :vartype path: Path
129
+ :ivar channel: The camera channel to be analysed.
130
+ :vartype channel: int
131
+ :ivar merge_led_arrays: Whether to merge LED arrays.
132
+ :vartype merge_led_arrays: bool
133
+ """
134
+ def __init__(self, layers: Layers, led_array: int, camera: Camera, path=Path('.'), channel=0,
135
+ merge_led_arrays=False):
136
+ """
137
+ :param layers: The spatial layers involved in the experiment.
138
+ :type layers: Layers
139
+ :param led_array: The identifier for the LED array.
140
+ :type led_array: int
141
+ :param camera: The camera involved in the experiment.
142
+ :type camera: Camera
143
+ :param path: The file path for experiment data, defaults to current directory.
144
+ :type path: Path, optional
145
+ :param channel: The camera channel to be analysed, defaults to 0.
146
+ :type channel: int, optional
147
+ :param merge_led_arrays: Whether to merge LED arrays, defaults to False.
148
+ :type merge_led_arrays: bool, optional
149
+ """
150
+ self.num_leds = None
151
+ self.layers = layers
152
+ self.led_array = led_array
153
+ self.camera = camera
154
+ self.leds = []
155
+ self.path = path
156
+ self.channel = channel
157
+ self.merge_led_arrays = merge_led_arrays
158
+
159
+ try:
160
+ self.set_leds()
161
+ except IOError as err:
162
+ print(err)
163
+
164
+ def __str__(self):
165
+ out = f'channel: {self.channel}, led_array: {self.led_array}\n' + \
166
+ str(self.layers) + \
167
+ str(self.camera)
168
+ return out
169
+
170
+ def calc_traversed_dist_per_layer(self, led: LED) -> np.ndarray:
171
+ """
172
+ Calculate the distance traversed by light from an LED through each layer to the camera.
173
+
174
+ :param led: The LED light source
175
+ :type led: LED
176
+ :return: Array of distances traversed in each layer
177
+ :rtype: np.ndarray
178
+ """
179
+ horizontal_dist = np.sqrt((self.camera.pos_x - led.pos_x) ** 2 + (self.camera.pos_y - led.pos_y) ** 2)
180
+ alpha = np.arctan((led.pos_z - self.camera.pos_z) / horizontal_dist)
181
+ if alpha == 0:
182
+ distance_per_layer = self.calc_traversed_dist_in_plane(led)
183
+ else:
184
+ distance_per_layer = self.calc_traversed_dist_per_layer_with_nonzero_alpha(alpha, led)
185
+
186
+ if not self.distance_calculation_is_consistent(distance_per_layer, led):
187
+ distance_per_layer = None
188
+ return distance_per_layer
189
+
190
+ def calc_traversed_dist_in_plane(self, led: LED) -> np.ndarray:
191
+ """
192
+ Calculate the distance traversed by light from an LED in a plane to the camera.
193
+
194
+ :param led: The LED light source
195
+ :type led: LED
196
+ :return: Array of distances traversed in each layer within the plane
197
+ :rtype: np.ndarray
198
+ """
199
+ distance_per_layer = np.zeros(self.layers.amount)
200
+ horizontal_dist = np.sqrt((self.camera.pos_x - led.pos_x) ** 2 + (self.camera.pos_y - led.pos_y) ** 2)
201
+ for layer in range(self.layers.amount):
202
+ layer_bottom = self.layers.borders[layer]
203
+ layer_top = self.layers.borders[layer + 1]
204
+ if layer_bottom <= self.camera.pos_z < layer_top:
205
+ distance_per_layer[layer] = horizontal_dist
206
+ return distance_per_layer
207
+
208
+ def calc_traversed_dist_per_layer_with_nonzero_alpha(self, alpha: float, led: LED) -> np.ndarray:
209
+ """
210
+ Calculate the distance traversed by light from an LED through each layer to the camera,
211
+ taking into account a non-zero angle of incidence.
212
+
213
+ :param alpha: The angle of incidence
214
+ :type alpha: float
215
+ :param led: The LED light source
216
+ :type led: LED
217
+ :return: Array of distances traversed in each layer considering the angle
218
+ :rtype: np.ndarray
219
+ """
220
+ distance_per_layer = np.zeros(self.layers.amount)
221
+ for layer in range(self.layers.amount):
222
+ layer_bottom = self.layers.borders[layer]
223
+ layer_top = self.layers.borders[layer + 1]
224
+ th = self.calc_traversed_height_in_layer(led.pos_z, layer_bottom, layer_top)
225
+ distance_per_layer[layer] = np.abs(th / np.sin(alpha))
226
+ return distance_per_layer
227
+
228
+ def calc_traversed_height_in_layer(self, led_height: float, layer_bot: float, layer_top: float) -> float:
229
+ """
230
+ Calculate the vertical distance (height) traversed by light from an LED within a layer.
231
+
232
+ :param led_height: The z-coordinate of the LED
233
+ :type led_height: float
234
+ :param layer_bot: The z-coordinate of the bottom of the layer
235
+ :type layer_bot: float
236
+ :param layer_top: The z-coordinate of the top of the layer
237
+ :type layer_top: float
238
+ :return: The vertical distance traversed within the layer
239
+ :rtype: float
240
+ """
241
+ top_end = max(self.camera.pos_z, led_height)
242
+ bot_end = min(self.camera.pos_z, led_height)
243
+ bot = max(bot_end, layer_bot)
244
+ top = min(top_end, layer_top)
245
+ h = top - bot
246
+ if h < 0:
247
+ h = 0
248
+ return h
249
+
250
+ def distance_calculation_is_consistent(self, distance_per_layer: np.ndarray, led: LED, silent=True) -> bool:
251
+ """
252
+ Check if the computed distance values for each layer are consistent with the Euclidean distance.
253
+
254
+ :param distance_per_layer: Array of distances traversed in each layer
255
+ :type distance_per_layer: np.ndarray
256
+ :param led: The LED light source
257
+ :type led: LED
258
+ :param silent: If set to False, prints out debugging information, defaults to True
259
+ :type silent: bool, optional
260
+ :return: True if computations are consistent, False otherwise
261
+ :rtype: bool
262
+ """
263
+ if np.abs(np.sum(distance_per_layer) - np.sqrt((self.camera.pos_x - led.pos_x) ** 2 +
264
+ (self.camera.pos_y - led.pos_y) ** 2 +
265
+ (self.camera.pos_z - led.pos_z) ** 2)) > 1e-6:
266
+ if not silent:
267
+ print("error in distance computation, camera_x: {}, camera_y: {} camera_z: {}, led_x: {}, led_y: {}, "
268
+ "led_z: {}".format(
269
+ self.camera.pos_x, self.camera.pos_y, self.camera.pos_z, led.pos_x, led.pos_y, led.pos_z))
270
+ return False
271
+ return True
272
+
273
+ def set_leds(self) -> None:
274
+ """
275
+ Initialize the LED instances involved in the experiment based on loaded data.
276
+
277
+ """
278
+ ids = self.get_led_ids()
279
+ x, y, z = self.get_led_positions(ids)
280
+ for i in range(len(ids)):
281
+ self.leds.append(LED(ids[i], x[i], y[i], z[i]))
282
+ self.num_leds = len(ids)
283
+
284
+ def get_led_ids(self) -> np.ndarray:
285
+ """
286
+ Retrieve the IDs for the LEDs involved in the experiment.
287
+
288
+ :return: Array of LED IDs
289
+ :rtype: np.ndarray
290
+ """
291
+ file_name_extension = '_merge' if self.merge_led_arrays != 'None' else ''
292
+ file_name = f'led_array_indices_{self.led_array:03d}{file_name_extension}.csv'
293
+ file_path = os.path.join(self.path, 'analysis', file_name)
294
+
295
+ led_array_indices = np.loadtxt(file_path, dtype=int)
296
+ return led_array_indices
297
+
298
+ def get_led_positions(self, ids: np.ndarray) -> List[np.ndarray]:
299
+ """
300
+ Retrieve the 3D positions for a set of LEDs.
301
+
302
+ :param ids: Array of LED identifiers
303
+ :type ids: np.ndarray
304
+ :return: List of arrays containing x, y, and z coordinates for each LED
305
+ :rtype: List[np.ndarray]
306
+ """
307
+ file_path = os.path.join(self.path, 'analysis', 'led_search_areas_with_coordinates.csv')
308
+ search_areas_all = np.loadtxt(file_path, delimiter=',')
309
+ search_areas_led_array = []
310
+ for led_id in ids:
311
+ search_areas_led_array.append(search_areas_all[led_id])
312
+ search_areas_led_array = np.array(search_areas_led_array)
313
+ return [search_areas_led_array[:, 3], search_areas_led_array[:, 4], search_areas_led_array[:, 5]]
@@ -0,0 +1,118 @@
1
+ from dataclasses import dataclass
2
+
3
+ from ledsa.analysis.ConfigDataAnalysis import ConfigDataAnalysis
4
+ from ledsa.analysis.Experiment import Camera, Layers
5
+ # Todo: Import init_function for request of missing data
6
+ from ledsa.core.ConfigData import ConfigData
7
+
8
+
9
+ @dataclass
10
+ class ExperimentData:
11
+ """
12
+ Dataclass containing the data for the extinction coefficient calculation
13
+ from the experiment_data.txt input file.
14
+
15
+ :ivar config: Configuration data.
16
+ :type config: ConfigData
17
+ :ivar config_analysis: Analysis configuration data.
18
+ :type config_analysis: ConfigDataAnalysis
19
+ :ivar camera: Camera data.
20
+ :type camera: Camera
21
+ :ivar layers: Layer data.
22
+ :type layers: Layers
23
+ :ivar channels: List of channels.
24
+ :type channels: List[int]
25
+ :ivar led_arrays: List of LED arrays.
26
+ :type led_arrays: List[int]
27
+ :ivar n_cpus: Number of CPUs.
28
+ :type n_cpus: int
29
+ :ivar weighting_preference: Weighting preference for nonlinear solver.
30
+ :type weighting_preference: float
31
+ :ivar weighting_curvature: Weighting curvature for nonlinear solver.
32
+ :type weighting_curvature: float
33
+ :ivar num_iterations: Number of iterations.
34
+ :type num_iterations: int
35
+ :ivar num_ref_images: Number of reference images.
36
+ :type num_ref_images: int
37
+ :ivar reference_property: Reference property to be analysed.
38
+ :type reference_property: str
39
+ :ivar merge_led_arrays: Merge LED arrays option.
40
+ :type merge_led_arrays: str
41
+ :ivar lambda_reg: Regularization parameter for linear solver.
42
+ :type lambda_reg: float
43
+ """
44
+ def __init__(self, load_config_file=True):
45
+ self.config = ConfigData(load_config_file=load_config_file)
46
+ self.config_analysis = ConfigDataAnalysis(load_config_file=load_config_file)
47
+ self.camera = None
48
+ self.layers = None
49
+ self.channels = None
50
+ self.led_arrays = None
51
+ self.n_cpus = None
52
+ self.weighting_preference = None
53
+ self.weighting_curvature = None
54
+ self.num_iterations = None
55
+ self.num_ref_images = None
56
+ self.lambda_reg = None
57
+ self.reference_property = None
58
+ self.merge_led_arrays = None
59
+ self.solver = None
60
+ self.load_config_parameters() # Todo: Does that belong here?
61
+
62
+ def load_config_parameters(self) -> None:
63
+ """
64
+ Load experiment data from configuration file.
65
+
66
+ """
67
+ config_analysis = self.config_analysis
68
+ num_layers = int(config_analysis['model_parameters']['num_layers'])
69
+ self.channels = config_analysis.get_list_of_values('DEFAULT', 'camera_channels')
70
+ self.num_ref_images = int(config_analysis['DEFAULT']['num_ref_images'])
71
+ self.weighting_preference = float(config_analysis['DEFAULT']['weighting_preference'])
72
+ self.weighting_curvature = float(config_analysis['DEFAULT']['weighting_curvature'])
73
+ self.lambda_reg = float(config_analysis['DEFAULT']['lambda_reg'])
74
+ self.num_iterations = int(config_analysis['DEFAULT']['num_iterations'])
75
+ self.reference_property = config_analysis['DEFAULT']['reference_property']
76
+ self.solver = config_analysis['DEFAULT']['solver']
77
+
78
+ self.led_arrays = config_analysis.get_list_of_values('model_parameters', 'led_array_indices')
79
+ if self.led_arrays is None:
80
+ config_analysis.in_led_array_indices()
81
+ config_analysis.save()
82
+ self.led_arrays = config_analysis.get_list_of_values('model_parameters', 'led_array_indices')
83
+
84
+ domain_bounds = config_analysis.get_list_of_values('model_parameters', 'domain_bounds', dtype=float)
85
+ if domain_bounds is None:
86
+ config_analysis.in_domain_bounds()
87
+ config_analysis.save()
88
+ domain_bounds = config_analysis.get_list_of_values('model_parameters', 'domain_bounds', dtype=float)
89
+
90
+ camera_position = config_analysis.get_list_of_values('experiment_geometry', 'camera_position', dtype=float)
91
+ if camera_position is None:
92
+ config_analysis.in_camera_position()
93
+ config_analysis.save()
94
+ camera_position = config_analysis.get_list_of_values('experiment_geometry', 'camera_position', dtype=float)
95
+
96
+ self.layers = Layers(num_layers, *domain_bounds)
97
+ self.camera = Camera(*camera_position)
98
+ self.n_cpus = int(config_analysis['DEFAULT']['num_cores'])
99
+ self.merge_led_arrays = str(self.config['analyse_positions']['merge_led_array_indices'])
100
+
101
+ def request_config_parameters(self) -> None:
102
+ """
103
+ Prompts the user to input missing parameters of analysis configuration and updates the configuration.
104
+
105
+ """
106
+ config = self.config_analysis
107
+ if config['experiment_geometry']['camera_position'] == 'None':
108
+ config.in_camera_position()
109
+ config.save()
110
+ if config['model_parameters']['num_layers'] == 'None':
111
+ config.in_num_layers()
112
+ config.save()
113
+ if config['model_parameters']['domain_bounds'] == 'None':
114
+ config.in_domain_bounds()
115
+ config.save()
116
+ if config['model_parameters']['led_array_indices'] == 'None':
117
+ config.in_led_array_indices()
118
+ config.save()