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,64 @@
1
+ from enum import Enum
2
+
3
+ class TypeSim(Enum):
4
+ """
5
+ Enum for the type of simulation to be performed.
6
+
7
+ Selection of simulation types:
8
+ - KWAVE: k-Wave simulation.
9
+ - FIELD2: Field II simulation.
10
+ - HYDRO: Hydrophone acquisition.
11
+ """
12
+ KWAVE = 'k-wave'
13
+ """k-Wave simulation."""
14
+
15
+ FIELD2 = 'Field2'
16
+ """Field II simulation."""
17
+
18
+ HYDRO = 'Hydrophone'
19
+ """Hydrophone acquisition."""
20
+
21
+ class Dim(Enum):
22
+ """
23
+ Enum for the dimension of the acoustic field.
24
+
25
+ Selection of dimensions:
26
+ - D2: 2D field.
27
+ - D3: 3D field.
28
+ """
29
+ D2 = '2D'
30
+ """2D field."""
31
+ D3 = '3D'
32
+ """3D field."""
33
+
34
+ class FormatSave(Enum):
35
+ """
36
+ Enum for different file formats to save the acoustic field.
37
+
38
+ Selection of file formats:
39
+ - HDR_IMG: Interfile format (.hdr and .img).
40
+ - H5: HDF5 format (.h5).
41
+ - NPY: NumPy format (.npy).
42
+ """
43
+ HDR_IMG = '.hdr'
44
+ """Interfile format (.hdr and .img)."""
45
+ H5 = '.h5'
46
+ """HDF5 format (.h5)."""
47
+ NPY = '.npy'
48
+ """NumPy format (.npy)."""
49
+
50
+ class WaveType(Enum):
51
+ """
52
+ Enum for different types of acoustic waves.
53
+
54
+ Selection of wave types:
55
+ - FocusedWave: A wave type where the energy is focused at a specific point.
56
+ - StructuredWave: A wave type characterized by a specific pattern or structure.
57
+ - PlaneWave: A wave type where the wavefronts are parallel and travel in a single direction.
58
+ """
59
+ FocusedWave = 'focus'
60
+ """A wave type where the energy is focused at a specific point."""
61
+ StructuredWave = 'structured'
62
+ """A wave type characterized by a specific pattern or structure."""
63
+ PlaneWave = 'plane'
64
+ """A wave type where the wavefronts are parallel and travel in a single direction."""
@@ -0,0 +1,221 @@
1
+ from AOT_biomaps.Config import config
2
+
3
+ from scipy.signal import hilbert
4
+ import os
5
+ import h5py
6
+ import numpy as np
7
+ import torch
8
+ from concurrent.futures import ThreadPoolExecutor
9
+
10
+ def loadmat(param_path_mat):
11
+ """
12
+ Charge un fichier .mat (format HDF5) sans SciPy.
13
+ Args:
14
+ param_path_mat: Chemin vers le fichier .mat.
15
+ Returns:
16
+ Dictionnaire contenant les variables du fichier.
17
+ """
18
+ with h5py.File(param_path_mat, 'r') as f:
19
+ data = {}
20
+ for key in f.keys():
21
+ # Récupère les données et convertit en numpy array si nécessaire
22
+ item = f[key]
23
+ if isinstance(item, h5py.Dataset):
24
+ data[key] = item[()] # Convertit en numpy array
25
+ elif isinstance(item, h5py.Group):
26
+ # Pour les structures MATLAB (nested)
27
+ data[key] = {}
28
+ for subkey in item:
29
+ data[key][subkey] = item[subkey][()]
30
+ return data
31
+
32
+ def reshape_field(field, factor, device=None):
33
+ """
34
+ Downsample a 3D or 4D field using PyTorch interpolation (auto-detects GPU/CPU).
35
+ Args:
36
+ field: Input field (numpy array or torch.Tensor).
37
+ factor: Downsampling factor (tuple of ints).
38
+ device: Force device ('cpu' or 'cuda'). If None, auto-detects GPU.
39
+ Returns:
40
+ Downsampled field (same type as input: numpy array or torch.Tensor).
41
+ """
42
+ # Check input
43
+ if field is None:
44
+ raise ValueError("Acoustic field is not generated. Please generate the field first.")
45
+
46
+ # Auto-detect device if not specified
47
+ if device is None:
48
+ device = 'cuda' if torch.cuda.is_available() else 'cpu'
49
+ else:
50
+ device = device.lower()
51
+ if device not in ['cpu', 'cuda']:
52
+ raise ValueError("Device must be 'cpu' or 'cuda'.")
53
+
54
+ # Convert to torch.Tensor if needed
55
+ if isinstance(field, np.ndarray):
56
+ field = torch.from_numpy(field)
57
+ elif not isinstance(field, torch.Tensor):
58
+ raise TypeError("Input must be a numpy array or torch.Tensor.")
59
+
60
+ # Move to the target device
61
+ field = field.to(device)
62
+
63
+ # Add batch and channel dimensions (required by torch.interpolate)
64
+ if len(factor) == 3:
65
+ if field.dim() != 3:
66
+ raise ValueError("Expected 3D field.")
67
+ field = field.unsqueeze(0).unsqueeze(0) # (1, 1, D, H, W)
68
+
69
+ # Calculate new shape
70
+ new_shape = [
71
+ field.shape[2] // factor[0],
72
+ field.shape[3] // factor[1],
73
+ field.shape[4] // factor[2]
74
+ ]
75
+
76
+ # Trilinear interpolation
77
+ downsampled = torch.nn.functional.interpolate(
78
+ field,
79
+ size=new_shape,
80
+ mode='trilinear',
81
+ align_corners=True
82
+ )
83
+ downsampled = downsampled.squeeze(0).squeeze(0) # Remove batch/channel dims
84
+
85
+ elif len(factor) == 4:
86
+ if field.dim() != 4:
87
+ raise ValueError("Expected 4D field.")
88
+ field = field.unsqueeze(0).unsqueeze(0) # (1, 1, T, D, H, W)
89
+
90
+ new_shape = [
91
+ field.shape[2] // factor[0],
92
+ field.shape[3] // factor[1],
93
+ field.shape[4] // factor[2],
94
+ field.shape[5] // factor[3]
95
+ ]
96
+
97
+ # Tetra-linear interpolation
98
+ downsampled = torch.nn.functional.interpolate(
99
+ field,
100
+ size=new_shape,
101
+ mode='trilinear', # PyTorch uses 'trilinear' for both 3D and 4D
102
+ align_corners=True
103
+ )
104
+ downsampled = downsampled.squeeze(0).squeeze(0)
105
+
106
+ else:
107
+ raise ValueError("Unsupported dimension. Only 3D and 4D fields are supported.")
108
+
109
+ # Convert back to numpy if input was numpy
110
+ if isinstance(field, np.ndarray):
111
+ return downsampled.cpu().numpy()
112
+ else:
113
+ return downsampled.cpu().numpy()
114
+
115
+ def calculate_envelope_squared(field):
116
+ """
117
+ Calcule l'enveloppe au carré du champ acoustique en utilisant scipy.signal.hilbert (CPU uniquement).
118
+
119
+ Args:
120
+ field: Champ acoustique (numpy.ndarray) de forme (T, X, Z) ou (T, X, Y, Z).
121
+
122
+ Returns:
123
+ envelope (numpy.ndarray): Enveloppe au carré du champ acoustique.
124
+ """
125
+ try:
126
+ if field is None:
127
+ raise ValueError("Le champ acoustique n'est pas généré. Veuillez d'abord générer le champ.")
128
+
129
+ if not isinstance(field, np.ndarray):
130
+ if hasattr(field, 'cpu'):
131
+ field = field.cpu().numpy() # Si c'est un tenseur PyTorch sur GPU/CPU
132
+ else:
133
+ field = np.array(field) # Conversion générique
134
+
135
+ if len(field.shape) not in [3, 4]:
136
+ raise ValueError("Le champ acoustique doit être un tableau 3D (T, X, Z) ou 4D (T, X, Y, Z).")
137
+
138
+ # Calcul de l'enveloppe avec scipy.signal.hilbert
139
+ if len(field.shape) == 3:
140
+ T, X, Z = field.shape
141
+ envelope = np.zeros_like(field)
142
+ for x in range(X):
143
+ for z in range(Z):
144
+ envelope[:, x, z] = np.abs(hilbert(field[:, x, z], axis=0)) ** 2
145
+ elif len(field.shape) == 4:
146
+ T, X, Y, Z = field.shape
147
+ envelope = np.zeros_like(field)
148
+ for x in range(X):
149
+ for y in range(Y):
150
+ for z in range(Z):
151
+ envelope[:, x, y, z] = np.abs(hilbert(field[:, x, y, z], axis=0)) ** 2
152
+
153
+
154
+ return envelope
155
+
156
+ except Exception as e:
157
+ print(f"Erreur dans calculate_envelope_squared: {e}")
158
+ raise
159
+
160
+
161
+
162
+ def getPattern(pathFile):
163
+ """
164
+ Get the pattern from a file path.
165
+
166
+ Args:
167
+ pathFile (str): Path to the file containing the pattern.
168
+
169
+ Returns:
170
+ str: The pattern string.
171
+ """
172
+ try:
173
+ # Pattern between first _ and last _
174
+ pattern = os.path.basename(pathFile).split('_')[1:-1]
175
+ pattern_str = ''.join(pattern)
176
+ return pattern_str
177
+ except Exception as e:
178
+ print(f"Error reading pattern from file: {e}")
179
+ return None
180
+
181
+ def detect_space_0_and_space_1(hex_string):
182
+ binary_string = bin(int(hex_string, 16))[2:].zfill(len(hex_string) * 4)
183
+
184
+ # Trouver la plus longue séquence de 0 consécutifs
185
+ zeros_groups = [len(s) for s in binary_string.split('1')]
186
+ space_0 = max(zeros_groups) if zeros_groups else 0
187
+
188
+ # Trouver la plus longue séquence de 1 consécutifs
189
+ ones_groups = [len(s) for s in binary_string.split('0')]
190
+ space_1 = max(ones_groups) if ones_groups else 0
191
+
192
+ return space_0, space_1
193
+
194
+ def getAngle(pathFile):
195
+ """
196
+ Get the angle from a file path.
197
+
198
+ Args:
199
+ pathFile (str): Path to the file containing the angle.
200
+
201
+ Returns:
202
+ int: The angle in degrees.
203
+ """
204
+ try:
205
+ # Angle between last _ and .
206
+ angle_str = os.path.basename(pathFile).split('_')[-1].replace('.', '')
207
+ if angle_str.startswith('0'):
208
+ angle_str = angle_str[1:]
209
+ elif angle_str.startswith('1'):
210
+ angle_str = '-' + angle_str[1:]
211
+ else:
212
+ raise ValueError("Invalid angle format in file name.")
213
+ return int(angle_str)
214
+ except Exception as e:
215
+ print(f"Error reading angle from file: {e}")
216
+ return None
217
+
218
+ def next_power_of_2(n):
219
+ """Calculate the next power of 2 greater than or equal to n."""
220
+ return int(2 ** np.ceil(np.log2(n)))
221
+
@@ -0,0 +1,244 @@
1
+ from ._mainAcoustic import AcousticField
2
+ from .AcousticEnums import WaveType
3
+
4
+ import os
5
+ import numpy as np
6
+ import matplotlib.pyplot as plt
7
+
8
+
9
+
10
+ class FocusedWave(AcousticField):
11
+
12
+ def __init__(self, focal_line, **kwargs):
13
+ """
14
+ Initialize the FocusedWave object.
15
+
16
+ Parameters:
17
+ - focal_line (tuple): The focal line coordinates (x) in meters.
18
+ - **kwargs: Additional keyword arguments for AcousticField initialization.
19
+ """
20
+ super().__init__(**kwargs)
21
+ self.waveType = WaveType.FocusedWave
22
+ self.kgrid.setTime(int(self.kgrid.Nt*2),self.kgrid.dt) # Extend the time grid to allow for delays
23
+ self.focal_line = focal_line
24
+ self.delayedSignal = self._apply_delay()
25
+
26
+ def getName_field(self):
27
+ """
28
+ Generate the name for the field file based on the focal line.
29
+
30
+ Returns:
31
+ str: File name for the system matrix file.
32
+ """
33
+ try:
34
+ return f"field_focused_X{self.focal_line*1000:.2f}"
35
+ except Exception as e:
36
+ print(f"Error generating file name: {e}")
37
+ return None
38
+
39
+ def _apply_delay(self, dx=None):
40
+ try:
41
+ if dx is None:
42
+ dx = self.params['dx']
43
+
44
+ # 1. Grid and element setup (utilise les paramètres utilisateur)
45
+ element_width_grid_points = int(round(self.params['element_width'] / dx))
46
+ total_grid_points = self.params['num_elements'] * element_width_grid_points
47
+ element_positions = np.linspace(
48
+ self.params['Xrange'][0] + self.params['element_width']/2,
49
+ self.params['Xrange'][1] - self.params['element_width'],
50
+ self.params['num_elements']
51
+ )
52
+
53
+ # 2. Select active elements (utilise les paramètres utilisateur)
54
+ center_idx = np.argmin(np.abs(element_positions - self.focal_line))
55
+ half_N = self.params['N_piezoFocal'] // 2
56
+ start_idx = max(0, center_idx - half_N)
57
+ end_idx = min(self.params['num_elements'] - 1, center_idx + half_N - 1)
58
+ active_elements = np.arange(start_idx, end_idx + 1)
59
+ active_element_positions = element_positions[active_elements]
60
+
61
+ # 3. Calculate relative positions
62
+ x_rel = active_element_positions - self.focal_line
63
+ a = np.max(np.abs(x_rel))
64
+
65
+ # 4. Calculate the parabolic delays (utilise les paramètres utilisateur)
66
+ tau_max = (a**2) / (2 * self.params['Foc'] * self.params['c0'])
67
+ delays = tau_max * (1 - (x_rel / a)**2)
68
+
69
+ # 5. Force the delays at the edges to be zero
70
+ tau_edge = delays[0]
71
+ delays -= tau_edge
72
+
73
+ # 6. Convert delays to samples
74
+ delay_samples = np.round(delays / self.kgrid.dt).astype(int)
75
+ max_delay_samples = np.max(delay_samples)
76
+ print(f"Max delay (samples): {max_delay_samples}")
77
+
78
+ # 7. Initialize output
79
+ delayed_signals = np.zeros((total_grid_points, len(self.burst) + max_delay_samples))
80
+
81
+ # 8. Apply delays
82
+ for elem_idx, elem_delay in zip(active_elements, delay_samples):
83
+ start_grid = elem_idx * element_width_grid_points
84
+ end_grid = start_grid + element_width_grid_points
85
+ for grid_idx in range(start_grid, end_grid):
86
+ if elem_delay >= 0 and elem_delay + len(self.burst) <= delayed_signals.shape[1]:
87
+ delayed_signals[grid_idx, elem_delay:elem_delay + len(self.burst)] = self.burst
88
+
89
+ return delayed_signals
90
+
91
+ except Exception as e:
92
+ print(f"Error applying delay: {e}")
93
+ return None
94
+
95
+
96
+ def plot_delay(self):
97
+ """
98
+ Plot the time of the maximum of each delayed signal to visualize the wavefront.
99
+ """
100
+ try:
101
+ # Find the index of the maximum for each delayed signal
102
+ max_indices = np.argmax(self.delayedSignal, axis=1)
103
+ element_indices = np.linspace(0, self.params['num_elements'] - 1, self.delayedSignal.shape[0])
104
+ # Convert indices to time
105
+ max_times = max_indices / self.params['f_AQ'] * 1e6
106
+
107
+ # Détermine la valeur minimale des temps de maximum (pour les éléments actifs)
108
+ min_active_time = np.min(max_times[max_times > 0])
109
+
110
+ # Plot the times of the maxima
111
+ plt.figure(figsize=(10, 6))
112
+ plt.plot(element_indices, max_times, 'o-')
113
+ plt.title('Time of Maximum for Each Delayed Signal')
114
+ plt.xlabel('Transducer Element Index')
115
+ plt.ylabel('Time of Maximum (µs)')
116
+ plt.grid(True)
117
+
118
+ # Ajuste l'échelle de l'axe Y pour commencer à la valeur minimale des éléments actifs
119
+ plt.ylim(bottom=min_active_time * 0.95) # Ajoute une marge de 5% pour plus de lisibilité
120
+ plt.show()
121
+ except Exception as e:
122
+ print(f"Error plotting max times: {e}")
123
+
124
+ def _SetUpSource(self, source, Nx, dx, factorT):
125
+ """
126
+ Set up source for both 2D and 3D focused waves.
127
+ """
128
+ element_width_grid_points = int(round(self.params['element_width'] / dx))
129
+
130
+ if source.p_mask.ndim == 2:
131
+ # --- 2D ---
132
+ element_positions = np.linspace(
133
+ self.params['Xrange'][0] + self.params['element_width'] / 2,
134
+ self.params['Xrange'][1] - self.params['element_width'] / 2,
135
+ self.params['num_elements']
136
+ )
137
+ center_idx = np.argmin(np.abs(element_positions - self.focal_line))
138
+ start_idx = max(0, center_idx - self.params['N_piezoFocal'] // 2)
139
+ end_idx = min(self.params['num_elements'] - 1, start_idx + self.params['N_piezoFocal'] - 1)
140
+ selected_indices = np.arange(start_idx, end_idx + 1)
141
+
142
+ current_position = (Nx - self.params['num_elements'] * element_width_grid_points) // 2
143
+ active_grid_indices = []
144
+ for i in range(self.params['num_elements']):
145
+ x_start = current_position
146
+ x_end = current_position + element_width_grid_points
147
+ if i in selected_indices:
148
+ source.p_mask[x_start:x_end, 0] = 1
149
+ active_grid_indices.extend(range(x_start, x_end))
150
+ current_position += element_width_grid_points
151
+
152
+ delayed_signals = self._apply_delay(dx=dx) if factorT != 1 else self.delayedSignal
153
+ source.p = float(self.params['voltage']) * float(self.params['sensitivity']) * delayed_signals[active_grid_indices, :]
154
+
155
+ elif source.p_mask.ndim == 3:
156
+ # --- 3D ---
157
+ center_index_x = Nx // 2
158
+ center_index_y = self.params['Ny'] // 2
159
+
160
+ for i in range(self.params['num_elements']):
161
+ x_pos = center_index_x - (self.params['num_elements'] // 2) * element_width_grid_points + i * element_width_grid_points
162
+ source.p_mask[x_pos, center_index_y, 0] = 1
163
+
164
+ delayed_signals = self._apply_delay()
165
+ source.p = float(self.params['voltage']) * float(self.params['sensitivity']) * delayed_signals.T
166
+
167
+ def _save2D_HDR_IMG(self, filePath):
168
+ """
169
+ Save the acoustic field to .img and .hdr files.
170
+
171
+ Parameters:
172
+ - filePath (str): Path to the folder where files will be saved.
173
+ """
174
+ try:
175
+ t_ex = 1 / self.params['f_US']
176
+ x_focal, z_focal = self.focal_point
177
+
178
+ # Define file names (img and hdr)
179
+ file_name = f"field_focused_{x_focal:.2f}_{z_focal:.2f}"
180
+
181
+ img_path = os.path.join(filePath, file_name + ".img")
182
+ hdr_path = os.path.join(filePath, file_name + ".hdr")
183
+
184
+ # Save the acoustic field to the .img file
185
+ with open(img_path, "wb") as f_img:
186
+ self.field.astype('float32').tofile(f_img)
187
+
188
+ # Generate headerFieldGlob
189
+ headerFieldGlob = (
190
+ f"!INTERFILE :=\n"
191
+ f"modality : AOT\n"
192
+ f"voxels number transaxial: {self.field.shape[2]}\n"
193
+ f"voxels number transaxial 2: {self.field.shape[1]}\n"
194
+ f"voxels number axial: {1}\n"
195
+ f"field of view transaxial: {(self.params['Xrange'][1] - self.params['Xrange'][0]) * 1000}\n"
196
+ f"field of view transaxial 2: {(self.params['Zrange'][1] - self.params['Zrange'][0]) * 1000}\n"
197
+ f"field of view axial: {1}\n"
198
+ )
199
+
200
+ # Generate header
201
+ header = (
202
+ f"!INTERFILE :=\n"
203
+ f"!imaging modality := AOT\n\n"
204
+ f"!GENERAL DATA :=\n"
205
+ f"!data offset in bytes := 0\n"
206
+ f"!name of data file := system_matrix/{file_name}.img\n\n"
207
+ f"!GENERAL IMAGE DATA\n"
208
+ f"!total number of images := {self.field.shape[0]}\n"
209
+ f"imagedata byte order := LITTLEENDIAN\n"
210
+ f"!number of frame groups := 1\n\n"
211
+ f"!STATIC STUDY (General) :=\n"
212
+ f"number of dimensions := 3\n"
213
+ f"!matrix size [1] := {self.field.shape[2]}\n"
214
+ f"!matrix size [2] := {self.field.shape[1]}\n"
215
+ f"!matrix size [3] := {self.field.shape[0]}\n"
216
+ f"!number format := short float\n"
217
+ f"!number of bytes per pixel := 4\n"
218
+ f"scaling factor (mm/pixel) [1] := {self.params['dx'] * 1000}\n"
219
+ f"scaling factor (mm/pixel) [2] := {self.params['dx'] * 1000}\n"
220
+ f"scaling factor (s/pixel) [3] := {1 / self.params['f_AQ']}\n"
221
+ f"first pixel offset (mm) [1] := {self.params['Xrange'][0] * 1e3}\n"
222
+ f"first pixel offset (mm) [2] := {self.params['Zrange'][0] * 1e3}\n"
223
+ f"first pixel offset (s) [3] := 0\n"
224
+ f"data rescale offset := 0\n"
225
+ f"data rescale slope := 1\n"
226
+ f"quantification units := 1\n\n"
227
+ f"!SPECIFIC PARAMETERS :=\n"
228
+ f"focal point (x, z) := {x_focal}, {z_focal}\n"
229
+ f"number of US transducers := {self.params['num_elements']}\n"
230
+ f"delay (s) := 0\n"
231
+ f"us frequency (Hz) := {self.params['f_US']}\n"
232
+ f"excitation duration (s) := {t_ex}\n"
233
+ f"!END OF INTERFILE :=\n"
234
+ )
235
+
236
+ # Save the .hdr file
237
+ with open(hdr_path, "w") as f_hdr:
238
+ f_hdr.write(header)
239
+
240
+ with open(os.path.join(filePath, "field.hdr"), "w") as f_hdr2:
241
+ f_hdr2.write(headerFieldGlob)
242
+ except Exception as e:
243
+ print(f"Error saving HDR/IMG files: {e}")
244
+
@@ -0,0 +1,66 @@
1
+ from AOT_biomaps.AOT_Acoustic._mainAcoustic import AcousticField
2
+ from .AcousticEnums import WaveType, TypeSim
3
+
4
+ import numpy as np
5
+
6
+ class IrregularWave(AcousticField):
7
+ """
8
+ Class for irregular wave types, inheriting from AcousticField.
9
+ This class is a placeholder for future implementation of irregular wave types.
10
+ """
11
+
12
+ def __init__(self, **kwargs):
13
+ super().__init__(**kwargs)
14
+ self.waveType = WaveType.IrregularWave
15
+ self.params = {
16
+ 'typeSim': TypeSim.IRREGULAR.value,
17
+ }
18
+
19
+ def getName_field(self):
20
+ raise NotImplementedError("getName_field method not implemented for IrregularWave.")
21
+
22
+ def _generate_diverse_structurations(self,num_elements, num_sequences, num_frequencies):
23
+ """
24
+ Génère num_sequences structurations irrégulières ON/OFF pour une sonde de num_elements éléments.
25
+ Chaque structuration contient exactement num_frequencies fréquences spatiales distinctes.
26
+
27
+ :param num_elements: Nombre total d'éléments piézoélectriques de la sonde.
28
+ :param num_sequences: Nombre total de structurations générées.
29
+ :param num_frequencies: Nombre de fréquences spatiales distinctes par structuration.
30
+ :return: Matrice de structuration de taille (num_sequences, num_elements)
31
+ """
32
+
33
+ # Définition des fréquences spatiales disponibles
34
+ max_freq = num_elements // 2 # Nyquist limit
35
+ available_frequencies = np.arange(1, max_freq + 1) # Fréquences possibles
36
+
37
+ # Matrice des structurations
38
+ structurations = np.zeros((num_sequences, num_elements), dtype=int)
39
+
40
+ # Sélectionner des fréquences uniques pour chaque structuration
41
+ chosen_frequencies = []
42
+ for _ in range(num_sequences):
43
+ freqs = np.random.choice(available_frequencies, size=num_frequencies, replace=False)
44
+ chosen_frequencies.append(freqs)
45
+
46
+ # Construire la structuration correspondante
47
+ structuration = np.zeros(num_elements)
48
+ for f in freqs:
49
+ structuration += np.cos(2 * np.pi * f * np.arange(num_elements) / num_elements) # Ajouter la fréquence
50
+
51
+ structuration = np.where(structuration >= 0, 1, 0) # Binarisation ON/OFF
52
+ structurations[_] = structuration
53
+
54
+ return structurations, chosen_frequencies
55
+
56
+ def getName_field(self):
57
+ raise NotImplementedError("getName_field method not implemented for IrregularWave.")
58
+
59
+ def _generate_2Dacoustic_field_KWAVE(self):
60
+ raise NotImplementedError("2D acoustic field generation not implemented for IrregularWave.")
61
+
62
+ def _generate_3Dacoustic_field_KWAVE(self):
63
+ raise NotImplementedError("3D acoustic field generation not implemented for IrregularWave.")
64
+
65
+ def _save2D_HDR_IMG(self, filePath):
66
+ raise NotImplementedError("HDR/IMG saving not implemented for IrregularWave.")
@@ -0,0 +1,43 @@
1
+ from .StructuredWave import StructuredWave
2
+ from .AcousticEnums import WaveType
3
+
4
+
5
+ class PlaneWave(StructuredWave):
6
+ def __init__(self, angle_deg, **kwargs):
7
+ """
8
+ Initialize the PlaneWave object.
9
+
10
+ Args:
11
+ angle_deg (float): Angle in degrees.
12
+ **kwargs: Additional keyword arguments.
13
+ """
14
+ try:
15
+ super().__init__(angle_deg=angle_deg, fileName=None, space_0=0, space_1=192, move_head_0_2tail=0, move_tail_1_2head=0, **kwargs)
16
+ self.waveType = WaveType.PlaneWave
17
+ except Exception as e:
18
+ print(f"Error initializing PlaneWave: {e}")
19
+ raise
20
+
21
+ def _check_angle(self):
22
+ """
23
+ Check if the angle is within the valid range.
24
+
25
+ Raises:
26
+ ValueError: If the angle is not between -20 and 20 degrees.
27
+ """
28
+ if self.angle < -20 or self.angle > 20:
29
+ raise ValueError("Angle must be between -20 and 20 degrees.")
30
+
31
+ def getName_field(self):
32
+ """
33
+ Generate the list of system matrix .hdr file paths for this wave.
34
+
35
+ Returns:
36
+ str: File path for the system matrix .hdr file.
37
+ """
38
+ try:
39
+ angle_str = self._format_angle()
40
+ return f"field_{self.pattern.activeList}_{angle_str}"
41
+ except Exception as e:
42
+ print(f"Error generating file path: {e}")
43
+ return None