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.
- ledsa/__init__.py +0 -0
- ledsa/__main__.py +48 -0
- ledsa/analysis/ConfigDataAnalysis.py +163 -0
- ledsa/analysis/Experiment.py +313 -0
- ledsa/analysis/ExperimentData.py +118 -0
- ledsa/analysis/ExtinctionCoefficients.py +216 -0
- ledsa/analysis/ExtinctionCoefficientsLinear.py +147 -0
- ledsa/analysis/ExtinctionCoefficientsNonLinear.py +136 -0
- ledsa/analysis/__init__.py +0 -0
- ledsa/analysis/data_preparation.py +28 -0
- ledsa/core/ConfigData.py +385 -0
- ledsa/core/__init__.py +0 -0
- ledsa/core/file_handling.py +400 -0
- ledsa/core/image_handling.py +39 -0
- ledsa/core/image_reading.py +100 -0
- ledsa/core/parser_arguments_declaration.py +109 -0
- ledsa/core/parser_arguments_run.py +244 -0
- ledsa/data_extraction/DataExtractor.py +266 -0
- ledsa/data_extraction/LEDAnalysisData.py +85 -0
- ledsa/data_extraction/__init__.py +0 -0
- ledsa/data_extraction/init_functions.py +228 -0
- ledsa/data_extraction/model.py +103 -0
- ledsa/data_extraction/step_1_functions.py +58 -0
- ledsa/data_extraction/step_2_functions.py +428 -0
- ledsa/data_extraction/step_3_functions.py +312 -0
- ledsa/demo/__init__.py +0 -0
- ledsa/demo/demo_run.py +29 -0
- ledsa/demo/demo_setup.py +252 -0
- ledsa/ledpositions/__init__.py +0 -0
- ledsa/ledpositions/coordinates.py +222 -0
- ledsa/postprocessing/__init__.py +0 -0
- ledsa/postprocessing/helper_functions.py +3 -0
- ledsa/postprocessing/simulation.py +410 -0
- ledsa/tests/AcceptanceTests/LedsaATestLibrary.py +226 -0
- ledsa/tests/AcceptanceTests/TestExperiment.py +148 -0
- ledsa/tests/AcceptanceTests/__init__.py +0 -0
- ledsa/tests/AcceptanceTests/__main__.py +14 -0
- ledsa/tests/__init__.py +0 -0
- ledsa/tests/__main__.py +0 -0
- ledsa/tools/__init__.py +0 -0
- ledsa/tools/photo_renamer.py +160 -0
- ledsa-0.9.2.dist-info/METADATA +174 -0
- ledsa-0.9.2.dist-info/RECORD +45 -0
- ledsa-0.9.2.dist-info/WHEEL +5 -0
- 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()
|