AOT-biomaps 2.1.3__py3-none-any.whl → 2.9.233__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 AOT-biomaps might be problematic. Click here for more details.

Files changed (50) hide show
  1. AOT_biomaps/AOT_Acoustic/AcousticEnums.py +64 -0
  2. AOT_biomaps/AOT_Acoustic/AcousticTools.py +221 -0
  3. AOT_biomaps/AOT_Acoustic/FocusedWave.py +244 -0
  4. AOT_biomaps/AOT_Acoustic/IrregularWave.py +66 -0
  5. AOT_biomaps/AOT_Acoustic/PlaneWave.py +43 -0
  6. AOT_biomaps/AOT_Acoustic/StructuredWave.py +392 -0
  7. AOT_biomaps/AOT_Acoustic/__init__.py +15 -0
  8. AOT_biomaps/AOT_Acoustic/_mainAcoustic.py +978 -0
  9. AOT_biomaps/AOT_Experiment/Focus.py +55 -0
  10. AOT_biomaps/AOT_Experiment/Tomography.py +505 -0
  11. AOT_biomaps/AOT_Experiment/__init__.py +9 -0
  12. AOT_biomaps/AOT_Experiment/_mainExperiment.py +532 -0
  13. AOT_biomaps/AOT_Optic/Absorber.py +24 -0
  14. AOT_biomaps/AOT_Optic/Laser.py +70 -0
  15. AOT_biomaps/AOT_Optic/OpticEnums.py +17 -0
  16. AOT_biomaps/AOT_Optic/__init__.py +10 -0
  17. AOT_biomaps/AOT_Optic/_mainOptic.py +204 -0
  18. AOT_biomaps/AOT_Recon/AOT_Optimizers/DEPIERRO.py +191 -0
  19. AOT_biomaps/AOT_Recon/AOT_Optimizers/LS.py +106 -0
  20. AOT_biomaps/AOT_Recon/AOT_Optimizers/MAPEM.py +456 -0
  21. AOT_biomaps/AOT_Recon/AOT_Optimizers/MLEM.py +333 -0
  22. AOT_biomaps/AOT_Recon/AOT_Optimizers/PDHG.py +221 -0
  23. AOT_biomaps/AOT_Recon/AOT_Optimizers/__init__.py +5 -0
  24. AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/Huber.py +90 -0
  25. AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/Quadratic.py +86 -0
  26. AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/RelativeDifferences.py +59 -0
  27. AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/__init__.py +3 -0
  28. AOT_biomaps/AOT_Recon/AlgebraicRecon.py +1023 -0
  29. AOT_biomaps/AOT_Recon/AnalyticRecon.py +154 -0
  30. AOT_biomaps/AOT_Recon/BayesianRecon.py +230 -0
  31. AOT_biomaps/AOT_Recon/DeepLearningRecon.py +35 -0
  32. AOT_biomaps/AOT_Recon/PrimalDualRecon.py +210 -0
  33. AOT_biomaps/AOT_Recon/ReconEnums.py +375 -0
  34. AOT_biomaps/AOT_Recon/ReconTools.py +273 -0
  35. AOT_biomaps/AOT_Recon/__init__.py +11 -0
  36. AOT_biomaps/AOT_Recon/_mainRecon.py +288 -0
  37. AOT_biomaps/Config.py +95 -0
  38. AOT_biomaps/Settings.py +45 -13
  39. AOT_biomaps/__init__.py +271 -18
  40. aot_biomaps-2.9.233.dist-info/METADATA +22 -0
  41. aot_biomaps-2.9.233.dist-info/RECORD +43 -0
  42. {AOT_biomaps-2.1.3.dist-info → aot_biomaps-2.9.233.dist-info}/WHEEL +1 -1
  43. AOT_biomaps/AOT_Acoustic.py +0 -1881
  44. AOT_biomaps/AOT_Experiment.py +0 -541
  45. AOT_biomaps/AOT_Optic.py +0 -219
  46. AOT_biomaps/AOT_Reconstruction.py +0 -1416
  47. AOT_biomaps/config.py +0 -54
  48. AOT_biomaps-2.1.3.dist-info/METADATA +0 -20
  49. AOT_biomaps-2.1.3.dist-info/RECORD +0 -11
  50. {AOT_biomaps-2.1.3.dist-info → aot_biomaps-2.9.233.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,392 @@
1
+ from AOT_biomaps.Config import config
2
+ from ._mainAcoustic import AcousticField
3
+ from .AcousticEnums import WaveType
4
+ from .AcousticTools import detect_space_0_and_space_1, getAngle
5
+
6
+ import os
7
+ import numpy as np
8
+ import matplotlib.pyplot as plt
9
+
10
+
11
+ class StructuredWave(AcousticField):
12
+
13
+ class PatternParams:
14
+ def __init__(self, space_0, space_1, move_head_0_2tail, move_tail_1_2head, len_hex):
15
+ """
16
+ Initialize the PatternParams object with given parameters.
17
+
18
+ Args:
19
+ space_0 (int): Number of zeros in the pattern.
20
+ space_1 (int): Number of ones in the pattern.
21
+ move_head_0_2tail (int): Number of zeros to move from head to tail.
22
+ move_tail_1_2head (int): Number of ones to move from tail to head.
23
+ """
24
+ self.space_0 = space_0
25
+ self.space_1 = space_1
26
+ self.move_head_0_2tail = move_head_0_2tail
27
+ self.move_tail_1_2head = move_tail_1_2head
28
+ self.activeList = None
29
+ self.len_hex = len_hex
30
+
31
+ def __str__(self):
32
+ """Return a string representation of the PatternParams object."""
33
+ return f"PatternParams(space_0={self.space_0}, space_1={self.space_1}, move_head_0_2tail={self.move_head_0_2tail}, move_tail_1_2head={self.move_tail_1_2head}, len_hex={self.len_hex})"
34
+
35
+ def generate_pattern(self):
36
+ """
37
+ Generate a binary pattern and return it as a hex string.
38
+
39
+ Returns:
40
+ str: Hexadecimal representation of the binary pattern.
41
+ """
42
+ try:
43
+ total_bits = self.len_hex * 4
44
+ unit = "0" * self.space_0 + "1" * self.space_1
45
+ repeat_time = (total_bits + len(unit) - 1) // len(unit)
46
+ pattern = (unit * repeat_time)[:total_bits]
47
+
48
+ # Move 0s from head to tail
49
+ if self.move_head_0_2tail > 0:
50
+ head_zeros = '0' * self.move_head_0_2tail
51
+ pattern = pattern[self.move_head_0_2tail:] + head_zeros
52
+
53
+ # Move 1s from tail to head
54
+ if self.move_tail_1_2head > 0:
55
+ tail_ones = '1' * self.move_tail_1_2head
56
+ pattern = tail_ones + pattern[:-self.move_tail_1_2head]
57
+
58
+ # Convert to hex
59
+ hex_output = hex(int(pattern, 2))[2:]
60
+ hex_output = hex_output.zfill(self.len_hex)
61
+
62
+ return hex_output
63
+ except Exception as e:
64
+ print(f"Error generating pattern: {e}")
65
+ return None
66
+
67
+ def generate_paths(self, base_path):
68
+ """Generate the list of system matrix .hdr file paths for this wave."""
69
+ #pattern_str = self.pattern_params.to_string()
70
+ pattern_str = self.generate_pattern()
71
+ paths = []
72
+ for angle in self.angles:
73
+ angle_str = self.format_angle(angle)
74
+ paths.append(f"{base_path}/field_{pattern_str}_{angle_str}.hdr")
75
+ return paths
76
+
77
+ def to_string(self):
78
+ """
79
+ Format the pattern parameters into a string like '0_48_0_0'.
80
+
81
+ Returns:
82
+ str: Formatted string of pattern parameters.
83
+ """
84
+ return f"{self.space_0}_{self.space_1}_{self.move_head_0_2tail}_{self.move_tail_1_2head}"
85
+
86
+ def describe(self):
87
+ """
88
+ Return a readable description of the pattern parameters.
89
+
90
+ Returns:
91
+ str: Description of the pattern parameters.
92
+ """
93
+ return f"Pattern structure: {self.to_string()}"
94
+
95
+ def __init__(self, fileName = None, angle_deg = None, space_0 = None, space_1 = None, move_head_0_2tail = None, move_tail_1_2head = None, **kwargs):
96
+ """
97
+ Initialize the StructuredWave object.
98
+
99
+ Args:
100
+ angle_deg (float): Angle in degrees.
101
+ fileName (str): Name of the file containing the hexadecimal active list and the angle (format : activelisthEXA_Angle)
102
+ space_0 (int): Number of zeros in the pattern.
103
+ space_1 (int): Number of ones in the pattern.
104
+ move_head_0_2tail (int): Number of zeros to move from head to tail.
105
+ move_tail_1_2head (int): Number of ones to move from tail to head.
106
+ **kwargs: Additional keyword arguments.
107
+ """
108
+ try:
109
+ super().__init__(**kwargs)
110
+ self.waveType = WaveType.StructuredWave
111
+ self.kgrid.setTime(int(self.kgrid.Nt*1.5),self.kgrid.dt) # Extend the time grid to allow for delays
112
+ if space_0 is not None and space_1 is not None and move_head_0_2tail is not None and move_tail_1_2head is not None and angle_deg is not None:
113
+ self.pattern = self.PatternParams(space_0, space_1, move_head_0_2tail, move_tail_1_2head, self.params['num_elements'] // 4)
114
+ self.angle = angle_deg
115
+ self.pattern.activeList = self.pattern.generate_pattern()
116
+ elif fileName is not None:
117
+ self.pattern = self.PatternParams(0,0,0,0,self.params['num_elements'] // 4)
118
+ self.pattern.space_0, self.pattern.space_1 = detect_space_0_and_space_1(fileName.split('_')[0])
119
+ self.angle = getAngle(fileName)
120
+ self.pattern.activeList = fileName.split('_')[0]
121
+ else:
122
+ raise ValueError("Invalid pattern parameters, must provide either fileName or all space/move parameters.")
123
+
124
+ self.pattern.len_hex = self.params['num_elements'] // 4
125
+ self.f_s = self._getDecimationFrequency()
126
+
127
+ if self.angle < -20 or self.angle > 20:
128
+ raise ValueError("Angle must be between -20 and 20 degrees.")
129
+
130
+ if len(self.pattern.activeList) != self.params["num_elements"] // 4:
131
+ raise ValueError(f"Active list string must be {self.params['num_elements'] // 4} characters long.")
132
+ self.delayedSignal = self._apply_delay()
133
+ except Exception as e:
134
+ print(f"Error initializing StructuredWave: {e}")
135
+
136
+ def getName_field(self):
137
+ """
138
+ Generate the list of system matrix .hdr file paths for this wave.
139
+
140
+ Returns:
141
+ str: File path for the system matrix .hdr file.
142
+ """
143
+ try:
144
+ pattern_str = self.pattern.activeList
145
+ angle_str = self._format_angle()
146
+ return f"field_{pattern_str}_{angle_str}"
147
+ except Exception as e:
148
+ print(f"Error generating file path: {e}")
149
+ return None
150
+
151
+ def _getDecimationFrequency(self):
152
+ """
153
+ Calculate the decimation frequency based on the pattern parameters.
154
+
155
+ Returns:
156
+ int: Decimation frequency.
157
+ """
158
+ try:
159
+ return 1/(self.pattern.space_0 + self.pattern.space_1)/self.params['element_width']
160
+ except Exception as e:
161
+ print(f"Error calculating decimation frequency: {e}")
162
+ return None
163
+
164
+ ## PRIVATE METHODS ##
165
+
166
+ def _format_angle(self):
167
+ """
168
+ Format an angle into a 3-digit code like '120' for -20°, '020' for +20°.
169
+
170
+ Args:
171
+ angle (float): Angle in degrees.
172
+
173
+ Returns:
174
+ str: Formatted angle string.
175
+ """
176
+ return f"{'1' if self.angle < 0 else '0'}{abs(self.angle):02d}"
177
+
178
+ def _apply_delay(self,dx=None):
179
+ """
180
+ Apply a temporal delay to the signal for each transducer element.
181
+
182
+ Returns:
183
+ ndarray: Array of delayed signals.
184
+ """
185
+ try:
186
+ is_positive = self.angle >= 0
187
+ if dx is None:
188
+ dx = self.params['dx']
189
+ # Calculate the total number of grid points for all elements
190
+ total_grid_points = self.params['num_elements'] * int(round(self.params['element_width'] / dx))
191
+
192
+ # Initialize delays array with size total_grid_points
193
+ delays = np.zeros(total_grid_points)
194
+
195
+ # Calculate the physical positions of the elements starting from Xrange[0]
196
+ element_positions = np.linspace(0, total_grid_points * dx, total_grid_points)
197
+
198
+ # Calculate delays based on physical positions
199
+ for i in range(total_grid_points):
200
+ delays[i] = (element_positions[i] * np.tan(np.deg2rad(abs(self.angle)))) / self.params['c0'] # Delay in seconds
201
+
202
+
203
+ delay_samples = np.round(delays / self.kgrid.dt).astype(int)
204
+ max_delay = np.max(np.abs(delay_samples))
205
+
206
+ delayed_signals = np.zeros((total_grid_points, len(self.burst) + max_delay))
207
+ for i in range(total_grid_points):
208
+ shift = delay_samples[i]
209
+
210
+ if is_positive:
211
+ delayed_signals[i, shift:shift + len(self.burst)] = self.burst # Right shift
212
+ else:
213
+ delayed_signals[i, max_delay - shift:max_delay - shift + len(self.burst)] = self.burst # Left shift
214
+
215
+ return delayed_signals
216
+ except Exception as e:
217
+ print(f"Error applying delay: {e}")
218
+ return None
219
+
220
+ def plot_delay(self):
221
+ """
222
+ Plot the time of the maximum of each delayed signal to visualize the wavefront.
223
+ """
224
+ try:
225
+ # Find the index of the maximum for each delayed signal
226
+ max_indices = np.argmax(self.delayedSignal, axis=1)
227
+ element_indices = np.linspace(0, self.params['num_elements'] - 1, self.delayedSignal.shape[0])
228
+ # Convert indices to time
229
+ max_times = max_indices / self.params['f_AQ']
230
+
231
+ # Plot the times of the maxima
232
+ plt.figure(figsize=(10, 6))
233
+ plt.plot(element_indices, max_times, 'o-')
234
+ plt.title('Time of Maximum for Each Delayed Signal')
235
+ plt.xlabel('Transducer Element Index')
236
+ plt.ylabel('Time of Maximum (s)')
237
+ plt.grid(True)
238
+ plt.show()
239
+ except Exception as e:
240
+ print(f"Error plotting max times: {e}")
241
+
242
+ def _save2D_HDR_IMG(self, pathFolder):
243
+ """
244
+ Save the acoustic field to .img and .hdr files.
245
+
246
+ Args:
247
+ pathFolder (str): Path to the folder where files will be saved.
248
+ """
249
+ try:
250
+ t_ex = 1 / self.params['f_US']
251
+ angle_sign = '1' if self.angle < 0 else '0'
252
+ formatted_angle = f"{angle_sign}{abs(self.angle):02d}"
253
+
254
+ # Define file names (img and hdr)
255
+ file_name = f"field_{self.pattern.activeList}_{formatted_angle}"
256
+
257
+ img_path = os.path.join(pathFolder, file_name + ".img")
258
+ hdr_path = os.path.join(pathFolder, file_name + ".hdr")
259
+
260
+ # Save the acoustic field to the .img file
261
+ with open(img_path, "wb") as f_img:
262
+ self.field.astype('float32').tofile(f_img) # Save in float32 format (equivalent to "single" in MATLAB)
263
+
264
+ # Generate headerFieldGlob
265
+ headerFieldGlob = (
266
+ f"!INTERFILE :=\n"
267
+ f"modality : AOT\n"
268
+ f"voxels number transaxial: {self.field.shape[2]}\n"
269
+ f"voxels number transaxial 2: {self.field.shape[1]}\n"
270
+ f"voxels number axial: {1}\n"
271
+ f"field of view transaxial: {(self.params['Xrange'][1] - self.params['Xrange'][0]) * 1000}\n"
272
+ f"field of view transaxial 2: {(self.params['Zrange'][1] - self.params['Zrange'][0]) * 1000}\n"
273
+ f"field of view axial: {1}\n"
274
+ )
275
+
276
+ # Generate header
277
+ header = (
278
+ f"!INTERFILE :=\n"
279
+ f"!imaging modality := AOT\n\n"
280
+ f"!GENERAL DATA :=\n"
281
+ f"!data offset in bytes := 0\n"
282
+ f"!name of data file := system_matrix/{file_name}.img\n\n"
283
+ f"!GENERAL IMAGE DATA\n"
284
+ f"!total number of images := {self.field.shape[0]}\n"
285
+ f"imagedata byte order := LITTLEENDIAN\n"
286
+ f"!number of frame groups := 1\n\n"
287
+ f"!STATIC STUDY (General) :=\n"
288
+ f"number of dimensions := 3\n"
289
+ f"!matrix size [1] := {self.field.shape[2]}\n"
290
+ f"!matrix size [2] := {self.field.shape[1]}\n"
291
+ f"!matrix size [3] := {self.field.shape[0]}\n"
292
+ f"!number format := short float\n"
293
+ f"!number of bytes per pixel := 4\n"
294
+ f"scaling factor (mm/pixel) [1] := {self.params['dx'] * 1000}\n"
295
+ f"scaling factor (mm/pixel) [2] := {self.params['dz'] * 1000}\n"
296
+ f"scaling factor (s/pixel) [3] := {1 / self.params['f_saving']}\n"
297
+ f"first pixel offset (mm) [1] := {self.params['Xrange'][0] * 1e3}\n"
298
+ f"first pixel offset (mm) [2] := {self.params['Zrange'][0] * 1e3}\n"
299
+ f"first pixel offset (s) [3] := 0\n"
300
+ f"data rescale offset := 0\n"
301
+ f"data rescale slope := 1\n"
302
+ f"quantification units := 1\n\n"
303
+ f"!SPECIFIC PARAMETERS :=\n"
304
+ f"angle (degree) := {self.angle}\n"
305
+ f"activation list := {''.join(f'{int(self.pattern.activeList[i:i+2], 16):08b}' for i in range(0, len(self.pattern.activeList), 2))}\n"
306
+ f"number of US transducers := {self.params['num_elements']}\n"
307
+ f"delay (s) := 0\n"
308
+ f"us frequency (Hz) := {self.params['f_US']}\n"
309
+ f"excitation duration (s) := {t_ex}\n"
310
+ f"!END OF INTERFILE :=\n"
311
+ )
312
+ # Save the .hdr file
313
+ with open(hdr_path, "w") as f_hdr:
314
+ f_hdr.write(header)
315
+
316
+ with open(os.path.join(pathFolder, "field.hdr"), "w") as f_hdr2:
317
+ f_hdr2.write(headerFieldGlob)
318
+ except Exception as e:
319
+ print(f"Error saving HDR/IMG files: {e}")
320
+
321
+ def _SetUpSource(self, source, Nx, dx, factorT):
322
+ """
323
+ Set up source for both 2D and 3D structured waves.
324
+ """
325
+ active_list = np.array([int(char) for char in ''.join(f"{int(self.pattern.activeList[i:i+2], 16):08b}" for i in range(0, len(self.pattern.activeList), 2))])
326
+ element_width_grid_points = int(round(self.params['element_width'] / dx))
327
+
328
+ if source.p_mask.ndim == 2:
329
+ element_width_grid_points = int(round(self.params['element_width'] / dx))
330
+ total_elements_width = self.params['num_elements'] * element_width_grid_points
331
+
332
+ # Vérifier que les éléments rentrent dans le grid
333
+ if total_elements_width > Nx:
334
+ raise ValueError(f"La largeur totale des éléments ({total_elements_width}) dépasse Nx ({Nx}).")
335
+
336
+ remaining_space = Nx - total_elements_width
337
+ if remaining_space < 0:
338
+ raise ValueError(f"Pas assez d'espace pour placer les éléments: total_elements_width ({total_elements_width}) > Nx ({Nx}).")
339
+
340
+ spacing = remaining_space // (self.params['num_elements'] + 1)
341
+ center_index = np.argmin(np.abs(np.linspace(self.params['Xrange'][0], self.params['Xrange'][1], Nx)))
342
+
343
+ activeListGrid = np.zeros(total_elements_width, dtype=int)
344
+ current_position = center_index - (total_elements_width + (self.params['num_elements'] - 1) * spacing) // 2
345
+
346
+ # Placement des éléments actifs
347
+ for i in range(self.params['num_elements']):
348
+ if active_list[i] == 1:
349
+ x_pos = max(0, current_position) # Éviter les indices négatifs
350
+ x_end = x_pos + element_width_grid_points
351
+ if x_end > Nx:
352
+ x_end = Nx # Limiter à Nx
353
+ source.p_mask[x_pos:x_end,0] = 1
354
+
355
+ start_idx = i * element_width_grid_points
356
+ end_idx = start_idx + element_width_grid_points
357
+ if end_idx > total_elements_width:
358
+ end_idx = total_elements_width
359
+ activeListGrid[start_idx:end_idx] = 1
360
+
361
+ current_position += element_width_grid_points + spacing
362
+
363
+ # Chargement des signaux retardés
364
+ if factorT != 1:
365
+ delayedSignal = self._apply_delay(dx=dx)
366
+ else:
367
+ delayedSignal = self.delayedSignal
368
+
369
+ # Vérification de la taille de delayedSignal
370
+ num_active_elements = np.sum(activeListGrid == 1)
371
+ if delayedSignal.shape[0] < num_active_elements:
372
+ raise ValueError(f"delayedSignal a une taille insuffisante: {delayedSignal.shape[0]} < {num_active_elements}.")
373
+
374
+ # Assigner source.p
375
+ source.p = float(self.params['voltage']) * float(self.params['sensitivity']) * delayedSignal[activeListGrid == 1, :]
376
+
377
+
378
+ elif source.p_mask.ndim == 3:
379
+ # --- 3D ---
380
+ center_index_x = Nx // 2
381
+ center_index_y = self.params['Ny'] // 2
382
+ spacing = (Nx - self.params['num_elements'] * element_width_grid_points) // (self.params['num_elements'] + 1)
383
+ current_position = center_index_x - (self.params['num_elements'] * element_width_grid_points + (self.params['num_elements'] - 1) * spacing) // 2
384
+
385
+ for i in range(self.params['num_elements']):
386
+ if active_list[i] == 1:
387
+ x_pos = current_position
388
+ source.p_mask[x_pos:x_pos + element_width_grid_points, center_index_y, 0] = 1
389
+ current_position += element_width_grid_points + spacing
390
+
391
+ delayed_signals = self._apply_delay()
392
+ source.p = float(self.params['voltage']) * float(self.params['sensitivity']) * delayed_signals.T
@@ -0,0 +1,15 @@
1
+ # Importer des fonctions ou classes spécifiques pour les rendre accessibles directement
2
+ from ._mainAcoustic import *
3
+ from .AcousticEnums import *
4
+ from .AcousticTools import *
5
+ from .FocusedWave import *
6
+ from .IrregularWave import *
7
+ from .PlaneWave import *
8
+ from .StructuredWave import *
9
+
10
+
11
+ # Docstring pour documenter le package
12
+ """
13
+ AOT_Acoustic est un package pour le traitement acoustique.
14
+ Il fournit des outils et des classes pour travailler avec des ondes acoustiques.
15
+ """