AOT-biomaps 2.9.15__tar.gz → 2.9.354__tar.gz

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.
Files changed (69) hide show
  1. aot_biomaps-2.9.354/AOT_biomaps/AOT_Acoustic/AcousticTools.py +219 -0
  2. aot_biomaps-2.9.354/AOT_biomaps/AOT_Acoustic/FocusedWave.py +244 -0
  3. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps/AOT_Acoustic/PlaneWave.py +2 -2
  4. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps/AOT_Acoustic/StructuredWave.py +48 -209
  5. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps/AOT_Acoustic/_mainAcoustic.py +231 -27
  6. aot_biomaps-2.9.354/AOT_biomaps/AOT_Experiment/ExperimentTools.py +69 -0
  7. aot_biomaps-2.9.354/AOT_biomaps/AOT_Experiment/Tomography.py +741 -0
  8. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps/AOT_Experiment/_mainExperiment.py +203 -70
  9. aot_biomaps-2.9.354/AOT_biomaps/AOT_Optic/_mainOptic.py +204 -0
  10. aot_biomaps-2.9.354/AOT_biomaps/AOT_Recon/AOT_Optimizers/DEPIERRO.py +191 -0
  11. aot_biomaps-2.9.354/AOT_biomaps/AOT_Recon/AOT_Optimizers/LS.py +496 -0
  12. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps/AOT_Recon/AOT_Optimizers/MAPEM.py +173 -68
  13. aot_biomaps-2.9.354/AOT_biomaps/AOT_Recon/AOT_Optimizers/MLEM.py +551 -0
  14. aot_biomaps-2.9.354/AOT_biomaps/AOT_Recon/AOT_Optimizers/PDHG.py +652 -0
  15. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps/AOT_Recon/AOT_Optimizers/__init__.py +2 -1
  16. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/RelativeDifferences.py +10 -14
  17. aot_biomaps-2.9.354/AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_CSR.py +274 -0
  18. aot_biomaps-2.9.354/AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_SELL.py +360 -0
  19. aot_biomaps-2.9.354/AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/__init__.py +2 -0
  20. aot_biomaps-2.9.354/AOT_biomaps/AOT_Recon/AOT_biomaps_kernels.cubin +0 -0
  21. aot_biomaps-2.9.354/AOT_biomaps/AOT_Recon/AlgebraicRecon.py +950 -0
  22. aot_biomaps-2.9.354/AOT_biomaps/AOT_Recon/AnalyticRecon.py +419 -0
  23. aot_biomaps-2.9.354/AOT_biomaps/AOT_Recon/BayesianRecon.py +230 -0
  24. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps/AOT_Recon/DeepLearningRecon.py +4 -1
  25. aot_biomaps-2.9.354/AOT_biomaps/AOT_Recon/PrimalDualRecon.py +263 -0
  26. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps/AOT_Recon/ReconEnums.py +51 -2
  27. aot_biomaps-2.9.354/AOT_biomaps/AOT_Recon/ReconTools.py +692 -0
  28. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps/AOT_Recon/__init__.py +1 -0
  29. aot_biomaps-2.9.354/AOT_biomaps/AOT_Recon/_mainRecon.py +291 -0
  30. aot_biomaps-2.9.354/AOT_biomaps/Config.py +95 -0
  31. aot_biomaps-2.9.354/AOT_biomaps/__init__.py +220 -0
  32. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps.egg-info/PKG-INFO +3 -4
  33. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps.egg-info/SOURCES.txt +7 -1
  34. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps.egg-info/requires.txt +2 -3
  35. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/PKG-INFO +3 -4
  36. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/README.md +4 -25
  37. aot_biomaps-2.9.354/setup.py +378 -0
  38. aot_biomaps-2.9.15/AOT_biomaps/AOT_Acoustic/AcousticTools.py +0 -258
  39. aot_biomaps-2.9.15/AOT_biomaps/AOT_Acoustic/FocusedWave.py +0 -329
  40. aot_biomaps-2.9.15/AOT_biomaps/AOT_Experiment/Tomography.py +0 -315
  41. aot_biomaps-2.9.15/AOT_biomaps/AOT_Optic/_mainOptic.py +0 -138
  42. aot_biomaps-2.9.15/AOT_biomaps/AOT_Recon/AOT_Optimizers/DEPIERRO.py +0 -132
  43. aot_biomaps-2.9.15/AOT_biomaps/AOT_Recon/AOT_Optimizers/MLEM.py +0 -275
  44. aot_biomaps-2.9.15/AOT_biomaps/AOT_Recon/AOT_Optimizers/PDHG.py +0 -182
  45. aot_biomaps-2.9.15/AOT_biomaps/AOT_Recon/AlgebraicRecon.py +0 -629
  46. aot_biomaps-2.9.15/AOT_biomaps/AOT_Recon/AnalyticRecon.py +0 -151
  47. aot_biomaps-2.9.15/AOT_biomaps/AOT_Recon/BayesianRecon.py +0 -156
  48. aot_biomaps-2.9.15/AOT_biomaps/AOT_Recon/PrimalDualRecon.py +0 -66
  49. aot_biomaps-2.9.15/AOT_biomaps/AOT_Recon/ReconTools.py +0 -272
  50. aot_biomaps-2.9.15/AOT_biomaps/AOT_Recon/_mainRecon.py +0 -149
  51. aot_biomaps-2.9.15/AOT_biomaps/Config.py +0 -70
  52. aot_biomaps-2.9.15/AOT_biomaps/__init__.py +0 -65
  53. aot_biomaps-2.9.15/setup.py +0 -40
  54. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps/AOT_Acoustic/AcousticEnums.py +0 -0
  55. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps/AOT_Acoustic/IrregularWave.py +0 -0
  56. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps/AOT_Acoustic/__init__.py +0 -0
  57. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps/AOT_Experiment/Focus.py +0 -0
  58. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps/AOT_Experiment/__init__.py +0 -0
  59. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps/AOT_Optic/Absorber.py +0 -0
  60. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps/AOT_Optic/Laser.py +0 -0
  61. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps/AOT_Optic/OpticEnums.py +0 -0
  62. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps/AOT_Optic/__init__.py +0 -0
  63. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/Huber.py +0 -0
  64. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/Quadratic.py +0 -0
  65. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/__init__.py +0 -0
  66. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps/Settings.py +0 -0
  67. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps.egg-info/dependency_links.txt +0 -0
  68. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/AOT_biomaps.egg-info/top_level.txt +0 -0
  69. {aot_biomaps-2.9.15 → aot_biomaps-2.9.354}/setup.cfg +0 -0
@@ -0,0 +1,219 @@
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
+ def getPattern(pathFile):
161
+ """
162
+ Get the pattern from a file path.
163
+
164
+ Args:
165
+ pathFile (str): Path to the file containing the pattern.
166
+
167
+ Returns:
168
+ str: The pattern string.
169
+ """
170
+ try:
171
+ # Pattern between first _ and last _
172
+ pattern = os.path.basename(pathFile).split('_')[1:-1]
173
+ pattern_str = ''.join(pattern)
174
+ return pattern_str
175
+ except Exception as e:
176
+ print(f"Error reading pattern from file: {e}")
177
+ return None
178
+
179
+ def detect_space_0_and_space_1(hex_string):
180
+ binary_string = bin(int(hex_string, 16))[2:].zfill(len(hex_string) * 4)
181
+
182
+ # Trouver la plus longue séquence de 0 consécutifs
183
+ zeros_groups = [len(s) for s in binary_string.split('1')]
184
+ space_0 = max(zeros_groups) if zeros_groups else 0
185
+
186
+ # Trouver la plus longue séquence de 1 consécutifs
187
+ ones_groups = [len(s) for s in binary_string.split('0')]
188
+ space_1 = max(ones_groups) if ones_groups else 0
189
+
190
+ return space_0, space_1
191
+
192
+ def getAngle(pathFile):
193
+ """
194
+ Get the angle from a file path.
195
+
196
+ Args:
197
+ pathFile (str): Path to the file containing the angle.
198
+
199
+ Returns:
200
+ int: The angle in degrees.
201
+ """
202
+ try:
203
+ # Angle between last _ and .
204
+ angle_str = os.path.basename(pathFile).split('_')[-1].replace('.', '')
205
+ if angle_str.startswith('0'):
206
+ angle_str = angle_str[1:]
207
+ elif angle_str.startswith('1'):
208
+ angle_str = '-' + angle_str[1:]
209
+ else:
210
+ raise ValueError("Invalid angle format in file name.")
211
+ return int(angle_str)
212
+ except Exception as e:
213
+ print(f"Error reading angle from file: {e}")
214
+ return None
215
+
216
+ def next_power_of_2(n):
217
+ """Calculate the next power of 2 greater than or equal to n."""
218
+ return int(2 ** np.ceil(np.log2(n)))
219
+
@@ -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
+
@@ -3,7 +3,7 @@ from .AcousticEnums import WaveType
3
3
 
4
4
 
5
5
  class PlaneWave(StructuredWave):
6
- def __init__(self, angle, space_0 = 0, space_1 = 192, move_head_0_2tail = 0, move_tail_1_2head = 0, **kwargs):
6
+ def __init__(self, angle_deg, **kwargs):
7
7
  """
8
8
  Initialize the PlaneWave object.
9
9
 
@@ -12,7 +12,7 @@ class PlaneWave(StructuredWave):
12
12
  **kwargs: Additional keyword arguments.
13
13
  """
14
14
  try:
15
- super().__init__(angle, space_0, space_1, move_head_0_2tail, move_tail_1_2head, **kwargs)
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
16
  self.waveType = WaveType.PlaneWave
17
17
  except Exception as e:
18
18
  print(f"Error initializing PlaneWave: {e}")