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.
- AOT_biomaps/AOT_Acoustic/AcousticEnums.py +64 -0
- AOT_biomaps/AOT_Acoustic/AcousticTools.py +221 -0
- AOT_biomaps/AOT_Acoustic/FocusedWave.py +244 -0
- AOT_biomaps/AOT_Acoustic/IrregularWave.py +66 -0
- AOT_biomaps/AOT_Acoustic/PlaneWave.py +43 -0
- AOT_biomaps/AOT_Acoustic/StructuredWave.py +392 -0
- AOT_biomaps/AOT_Acoustic/__init__.py +15 -0
- AOT_biomaps/AOT_Acoustic/_mainAcoustic.py +978 -0
- AOT_biomaps/AOT_Experiment/Focus.py +55 -0
- AOT_biomaps/AOT_Experiment/Tomography.py +505 -0
- AOT_biomaps/AOT_Experiment/__init__.py +9 -0
- AOT_biomaps/AOT_Experiment/_mainExperiment.py +532 -0
- AOT_biomaps/AOT_Optic/Absorber.py +24 -0
- AOT_biomaps/AOT_Optic/Laser.py +70 -0
- AOT_biomaps/AOT_Optic/OpticEnums.py +17 -0
- AOT_biomaps/AOT_Optic/__init__.py +10 -0
- AOT_biomaps/AOT_Optic/_mainOptic.py +204 -0
- AOT_biomaps/AOT_Recon/AOT_Optimizers/DEPIERRO.py +191 -0
- AOT_biomaps/AOT_Recon/AOT_Optimizers/LS.py +106 -0
- AOT_biomaps/AOT_Recon/AOT_Optimizers/MAPEM.py +456 -0
- AOT_biomaps/AOT_Recon/AOT_Optimizers/MLEM.py +333 -0
- AOT_biomaps/AOT_Recon/AOT_Optimizers/PDHG.py +221 -0
- AOT_biomaps/AOT_Recon/AOT_Optimizers/__init__.py +5 -0
- AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/Huber.py +90 -0
- AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/Quadratic.py +86 -0
- AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/RelativeDifferences.py +59 -0
- AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/__init__.py +3 -0
- AOT_biomaps/AOT_Recon/AlgebraicRecon.py +1023 -0
- AOT_biomaps/AOT_Recon/AnalyticRecon.py +154 -0
- AOT_biomaps/AOT_Recon/BayesianRecon.py +230 -0
- AOT_biomaps/AOT_Recon/DeepLearningRecon.py +35 -0
- AOT_biomaps/AOT_Recon/PrimalDualRecon.py +210 -0
- AOT_biomaps/AOT_Recon/ReconEnums.py +375 -0
- AOT_biomaps/AOT_Recon/ReconTools.py +273 -0
- AOT_biomaps/AOT_Recon/__init__.py +11 -0
- AOT_biomaps/AOT_Recon/_mainRecon.py +288 -0
- AOT_biomaps/Config.py +95 -0
- AOT_biomaps/Settings.py +45 -13
- AOT_biomaps/__init__.py +271 -18
- aot_biomaps-2.9.233.dist-info/METADATA +22 -0
- aot_biomaps-2.9.233.dist-info/RECORD +43 -0
- {AOT_biomaps-2.1.3.dist-info → aot_biomaps-2.9.233.dist-info}/WHEEL +1 -1
- AOT_biomaps/AOT_Acoustic.py +0 -1881
- AOT_biomaps/AOT_Experiment.py +0 -541
- AOT_biomaps/AOT_Optic.py +0 -219
- AOT_biomaps/AOT_Reconstruction.py +0 -1416
- AOT_biomaps/config.py +0 -54
- AOT_biomaps-2.1.3.dist-info/METADATA +0 -20
- AOT_biomaps-2.1.3.dist-info/RECORD +0 -11
- {AOT_biomaps-2.1.3.dist-info → aot_biomaps-2.9.233.dist-info}/top_level.txt +0 -0
AOT_biomaps/AOT_Experiment.py
DELETED
|
@@ -1,541 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
from .Settings import *
|
|
3
|
-
from .AOT_Optic import *
|
|
4
|
-
from .AOT_Acoustic import *
|
|
5
|
-
import os
|
|
6
|
-
import ast
|
|
7
|
-
import matplotlib.pyplot as plt
|
|
8
|
-
import matplotlib.animation as animation
|
|
9
|
-
import numpy as np
|
|
10
|
-
import matplotlib as mpl
|
|
11
|
-
from datetime import datetime
|
|
12
|
-
from tqdm import trange
|
|
13
|
-
from enum import Enum
|
|
14
|
-
from .config import config
|
|
15
|
-
|
|
16
|
-
class Experiment():
|
|
17
|
-
def __init__(self, params):
|
|
18
|
-
self.params = params
|
|
19
|
-
self.OpticImage = AOT_biomaps.AOT_Optic.Phantom(params=params)
|
|
20
|
-
self.AOsignal = None
|
|
21
|
-
self.AcousticFields = None
|
|
22
|
-
|
|
23
|
-
if type(params) != AOT_biomaps.Settings.Params:
|
|
24
|
-
raise TypeError("params must be an instance of the Params class")
|
|
25
|
-
|
|
26
|
-
class Focus(Experiment):
|
|
27
|
-
def __init__(self, **kwargs):
|
|
28
|
-
super().__init__(**kwargs)
|
|
29
|
-
|
|
30
|
-
class Tomography(Experiment):
|
|
31
|
-
|
|
32
|
-
def __init__(self, acousticType = AOT_biomaps.AOT_Acoustic.WaveType.StructuredWave ,fieldParamPath=None, fieldDataPath = None, formatSave = AOT_biomaps.AOT_Acoustic.FormatSave.HDR_IMG, **kwargs):
|
|
33
|
-
super().__init__(**kwargs)
|
|
34
|
-
|
|
35
|
-
if fieldParamPath is None:
|
|
36
|
-
raise ValueError("fieldParamPath must be provided for Tomography experiment")
|
|
37
|
-
if type(acousticType).__name__ != "WaveType":
|
|
38
|
-
raise TypeError("acousticType must be an instance of the WaveType class")
|
|
39
|
-
self.TypeAcoustic = acousticType
|
|
40
|
-
self.FormatSave = formatSave
|
|
41
|
-
self.fieldDataPath = fieldDataPath
|
|
42
|
-
self.AcousticFields = self._generate_system_matrix(fieldDataPath, fieldParamPath)
|
|
43
|
-
self.OpticImage = AOT_biomaps.AOT_Optic.Phantom(params=self.params)
|
|
44
|
-
self.AOsignal = self._generate_AO_signal()
|
|
45
|
-
|
|
46
|
-
if type(self.params)!= AOT_biomaps.Settings.Params:
|
|
47
|
-
raise TypeError("params must be an instance of the Params class")
|
|
48
|
-
|
|
49
|
-
def _generate_system_matrix(self, fieldDataPath, fieldParamPath):
|
|
50
|
-
"""
|
|
51
|
-
Generate the system matrix for the structured wave.
|
|
52
|
-
|
|
53
|
-
Args:
|
|
54
|
-
fieldDataPath: Path to save the generated fields.
|
|
55
|
-
fieldParamPath: Path to the field parameters file.
|
|
56
|
-
params: Parameters object containing simulation settings.
|
|
57
|
-
|
|
58
|
-
Returns:
|
|
59
|
-
systemMatrix: A numpy array of the generated fields.
|
|
60
|
-
"""
|
|
61
|
-
if self.TypeAcoustic.value == AOT_biomaps.AOT_Acoustic.WaveType.StructuredWave.value:
|
|
62
|
-
return self._generate_system_matrixSTRUCT(fieldDataPath, fieldParamPath)
|
|
63
|
-
else:
|
|
64
|
-
raise ValueError("Unsupported wave type.")
|
|
65
|
-
|
|
66
|
-
def _generate_system_matrixSTRUCT(self, fieldDataPath, fieldParamPath):
|
|
67
|
-
if not os.path.exists(fieldParamPath):
|
|
68
|
-
raise FileNotFoundError(f"Field parameter file {fieldParamPath} not found.")
|
|
69
|
-
print(fieldDataPath)
|
|
70
|
-
if not fieldDataPath is None:
|
|
71
|
-
os.makedirs(fieldDataPath, exist_ok=True)
|
|
72
|
-
listAcousticFields = []
|
|
73
|
-
patternList = []
|
|
74
|
-
with open(fieldParamPath, 'r') as file:
|
|
75
|
-
lines = file.readlines()
|
|
76
|
-
for line in lines:
|
|
77
|
-
line = line.strip()
|
|
78
|
-
if not line:
|
|
79
|
-
continue # skip empty lines
|
|
80
|
-
|
|
81
|
-
try:
|
|
82
|
-
# Sécurise l'évaluation en supprimant accès à builtins
|
|
83
|
-
parsed = eval(line, {"__builtins__": None})
|
|
84
|
-
|
|
85
|
-
if isinstance(parsed, tuple) and len(parsed) == 2:
|
|
86
|
-
coords, angles = parsed
|
|
87
|
-
for angle in angles:
|
|
88
|
-
patternList.append([*coords, angle])
|
|
89
|
-
else:
|
|
90
|
-
raise ValueError("Ligne inattendue (pas un tuple de deux éléments)")
|
|
91
|
-
|
|
92
|
-
except Exception as e:
|
|
93
|
-
print(f"Erreur de parsing sur la ligne : {line}\n{e}")
|
|
94
|
-
|
|
95
|
-
if config.get_process() == 'gpu':
|
|
96
|
-
print("Using GPU for computation.")
|
|
97
|
-
else:
|
|
98
|
-
print("Using CPU for computation.")
|
|
99
|
-
progress_bar = trange(1,len(patternList), desc="Generating system matrix")
|
|
100
|
-
|
|
101
|
-
for i in progress_bar:
|
|
102
|
-
pattern = patternList[i]
|
|
103
|
-
if len(pattern) != 5:
|
|
104
|
-
raise ValueError(f"Invalid pattern format: {pattern}. Expected 5 values.")
|
|
105
|
-
# Initialisation de l'objet AcousticField
|
|
106
|
-
AcousticField = AOT_biomaps.AOT_Acoustic.StructuredWave(
|
|
107
|
-
angle_deg=pattern[4],
|
|
108
|
-
space_0=pattern[0],
|
|
109
|
-
space_1=pattern[1],
|
|
110
|
-
move_head_0_2tail=pattern[2],
|
|
111
|
-
move_tail_1_2head=pattern[3],
|
|
112
|
-
params=self.params
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
if fieldDataPath is None:
|
|
116
|
-
pathField = None
|
|
117
|
-
else:
|
|
118
|
-
pathField = os.path.join(fieldDataPath, os.path.basename(AcousticField.getName_field() + self.FormatSave.value))
|
|
119
|
-
|
|
120
|
-
if not pathField is None and os.path.exists(pathField):
|
|
121
|
-
progress_bar.set_postfix_str(f"Loading field - {AcousticField.getName_field()}")
|
|
122
|
-
AcousticField.load_field(fieldDataPath, self.FormatSave)
|
|
123
|
-
|
|
124
|
-
elif pathField is None or not os.path.exists(pathField):
|
|
125
|
-
progress_bar.set_postfix_str(f"Generating field - {AcousticField.getName_field()}")
|
|
126
|
-
AcousticField.generate_field()
|
|
127
|
-
|
|
128
|
-
progress_bar.set_postfix_str(f"Calculating squared envelope of the field - {AcousticField.getName_field()}")
|
|
129
|
-
AcousticField.calculate_envelope_squared()
|
|
130
|
-
|
|
131
|
-
progress_bar.set_postfix_str(f"Saving system matrix - {AcousticField.getName_field()}")
|
|
132
|
-
|
|
133
|
-
if not pathField is None and not os.path.exists(pathField):
|
|
134
|
-
os.makedirs(os.path.dirname(pathField), exist_ok=True)
|
|
135
|
-
AcousticField.save_field(fieldDataPath)
|
|
136
|
-
|
|
137
|
-
listAcousticFields.append(AcousticField)
|
|
138
|
-
# Réinitialiser le texte de la barre de progression pour l'itération suivante
|
|
139
|
-
progress_bar.set_postfix_str("")
|
|
140
|
-
|
|
141
|
-
return listAcousticFields
|
|
142
|
-
|
|
143
|
-
def _generate_AO_signal(self):
|
|
144
|
-
if self.AcousticFields is None:
|
|
145
|
-
raise ValueError("AcousticFields is not initialized. Please generate the system matrix first.")
|
|
146
|
-
# for field in self.AcousticFields:
|
|
147
|
-
# print(field)
|
|
148
|
-
#check if all AcousticFields.field have the same shape
|
|
149
|
-
if not all(field.enveloppe.shape == self.AcousticFields[0].enveloppe.shape for field in self.AcousticFields):
|
|
150
|
-
minShape = min([field.enveloppe.shape[0] for field in self.AcousticFields])
|
|
151
|
-
self.cutAcousticFields(minShape*self.params['fs_aq'])
|
|
152
|
-
else:
|
|
153
|
-
shape_field = self.AcousticFields[0].enveloppe.shape
|
|
154
|
-
|
|
155
|
-
AOsignal = np.zeros((shape_field[0], len(self.AcousticFields)), dtype=np.float32)
|
|
156
|
-
|
|
157
|
-
for i in trange(len(self.AcousticFields), desc="Generating AO Signal"):
|
|
158
|
-
for t in range(self.AcousticFields[i].enveloppe.shape[0]):
|
|
159
|
-
interaction = self.OpticImage.phantom * self.AcousticFields[i].enveloppe[t, :, :]
|
|
160
|
-
AOsignal[t,i] = np.sum(interaction)
|
|
161
|
-
|
|
162
|
-
return AOsignal
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def cutAcousticFields(self,max_t,min_t=0,saveFields=False):
|
|
166
|
-
|
|
167
|
-
min_sample = int(np.floor(min_t * float(self.params.acoustic['f_AQ'])))
|
|
168
|
-
max_sample = int(np.floor(max_t * float(self.params.acoustic['f_AQ'])))
|
|
169
|
-
|
|
170
|
-
print(f"Cutting fields from {min_sample} to {max_sample} samples.")
|
|
171
|
-
if min_sample < 0 or max_sample < 0:
|
|
172
|
-
raise ValueError("min_sample and max_sample must be non-negative integers.")
|
|
173
|
-
if min_sample >= max_sample:
|
|
174
|
-
raise ValueError("min_sample must be less than max_sample.")
|
|
175
|
-
|
|
176
|
-
if not self.AcousticFields:
|
|
177
|
-
raise ValueError("AcousticFields is empty. Cannot cut fields.")
|
|
178
|
-
|
|
179
|
-
for i in trange(len(self.AcousticFields), desc="Cutting Acoustic Fields"):
|
|
180
|
-
field = self.AcousticFields[i]
|
|
181
|
-
if field.field.shape[0] < max_sample:
|
|
182
|
-
raise ValueError(f"Field {field.getName_field()} has an invalid shape: {field.enveloppe.shape}. Expected shape to be at least ({max_sample},).")
|
|
183
|
-
self.AcousticFields[i].enveloppe = field.enveloppe[min_sample:max_sample, :, :]
|
|
184
|
-
self.AcousticFields[i].field = field.field[min_sample:max_sample, :, :]
|
|
185
|
-
|
|
186
|
-
self.AOsignal = self._generate_AO_signal()
|
|
187
|
-
|
|
188
|
-
if saveFields:
|
|
189
|
-
self._saveSMatrixForCastor(self.fieldDataPath)
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
def _saveSMatrixForCastor(self, save_directory):
|
|
193
|
-
for i in trange(len(self.AcousticFields), desc="Saving Acoustic Fields"):
|
|
194
|
-
field = self.AcousticFields[i]
|
|
195
|
-
field.save_field(save_directory,formatSave = self.FormatSave)
|
|
196
|
-
|
|
197
|
-
def _saveAOsignalForCastor(self,save_directory):
|
|
198
|
-
"""
|
|
199
|
-
Sauvegarde le signal AO au format .cdf et .cdh (comme dans le script MATLAB)
|
|
200
|
-
|
|
201
|
-
:param AOsignal: np.ndarray de taille (times, angles)
|
|
202
|
-
:param save_directory: chemin de sauvegarde
|
|
203
|
-
:param set_id: identifiant du set
|
|
204
|
-
:param n_experiment: identifiant de l'expérience
|
|
205
|
-
:param param: dictionnaire contenant les paramètres nécessaires (comme fs_aq, Nt, angles, etc.)
|
|
206
|
-
"""
|
|
207
|
-
|
|
208
|
-
# Noms des fichiers de sortie
|
|
209
|
-
cdf_location = os.path.join(save_directory, "AOSignals.cdf")
|
|
210
|
-
cdh_location = os.path.join(save_directory, "AOSignals.cdh")
|
|
211
|
-
info_location = os.path.join(save_directory, "info.txt")
|
|
212
|
-
|
|
213
|
-
# Calcul des angles (en degrés) si nécessaire
|
|
214
|
-
|
|
215
|
-
nScan = self.AOsignal.shape[1] # Nombre de scans ou d'événements
|
|
216
|
-
|
|
217
|
-
# **1. Sauvegarde du fichier .cdf**
|
|
218
|
-
with open(cdf_location, "wb") as fileID:
|
|
219
|
-
for j in range(self.AOsignal.shape[1]):
|
|
220
|
-
active_list = self.AcousticFields[j].pattern.activeList
|
|
221
|
-
angle = self.AcousticFields[j].angle
|
|
222
|
-
# Écrire les identifiants hexadécimaux
|
|
223
|
-
active_list_str = ''.join(map(str, active_list))
|
|
224
|
-
|
|
225
|
-
nb_padded_zeros = (4 - len(active_list_str) % 4) % 4 # Calcul du nombre de 0 nécessaires
|
|
226
|
-
active_list_str += '0' * nb_padded_zeros # Ajout des zéros à la fin de la chaîne
|
|
227
|
-
|
|
228
|
-
# Regrouper par paquets de 4 bits et convertir chaque paquet en hexadécimal
|
|
229
|
-
active_list_hex = ''.join([hex(int(active_list_str[i:i+4], 2))[2:] for i in range(0, len(active_list_str), 4)])
|
|
230
|
-
for i in range(0, len(active_list_hex), 2): # Chaque 2 caractères hex représentent 1 octet
|
|
231
|
-
byte_value = int(active_list_hex[i:i + 2], 16) # Convertit l'hexadécimal en entier
|
|
232
|
-
fileID.write(byte_value.to_bytes(1, byteorder='big')) # Écriture en big endian
|
|
233
|
-
|
|
234
|
-
fileID.write(np.int8(angle).tobytes())
|
|
235
|
-
|
|
236
|
-
# Écrire le signal AO correspondant (times x 1) en single (float32)
|
|
237
|
-
fileID.write(self.AOsignal[:, j].astype(np.float32).tobytes())
|
|
238
|
-
|
|
239
|
-
# **2. Sauvegarde du fichier .cdh**
|
|
240
|
-
header_content = (
|
|
241
|
-
f"Data filename: AOSignals.cdf\n"
|
|
242
|
-
f"Number of events: {nScan}\n"
|
|
243
|
-
f"Number of acquisitions per event: {self.AOsignal.shape[1]}\n"
|
|
244
|
-
f"Start time (s): 0\n"
|
|
245
|
-
f"Duration (s): 1\n"
|
|
246
|
-
f"Acquisition frequency (Hz): {1/self.AcousticFields[0].kgrid.dt}\n"
|
|
247
|
-
f"Data mode: histogram\n"
|
|
248
|
-
f"Data type: AOT\n"
|
|
249
|
-
f"Number of US transducers: {self.params['num_elements']}"
|
|
250
|
-
)
|
|
251
|
-
with open(cdh_location, "w") as fileID:
|
|
252
|
-
fileID.write(header_content)
|
|
253
|
-
|
|
254
|
-
with open(info_location, "w") as fileID:
|
|
255
|
-
for field in self.AcousticFields:
|
|
256
|
-
fileID.write(field.getName_field() + "\n")
|
|
257
|
-
|
|
258
|
-
print(f"Fichiers .cdf, .cdh et info.txt sauvegardés dans {save_directory}")
|
|
259
|
-
|
|
260
|
-
def load_AOSignal(self,cdh_file):
|
|
261
|
-
# Lire les métadonnées depuis le fichier .cdh
|
|
262
|
-
with open(cdh_file, "r") as file:
|
|
263
|
-
cdh_content = file.readlines()
|
|
264
|
-
|
|
265
|
-
# Extraire les dimensions des données à partir des métadonnées
|
|
266
|
-
n_events = int([line.split(":")[1].strip() for line in cdh_content if "Number of events" in line][0])
|
|
267
|
-
n_acquisitions = int([line.split(":")[1].strip() for line in cdh_content if "Number of acquisitions per event" in line][0])
|
|
268
|
-
|
|
269
|
-
# Initialiser la matrice pour stocker les données
|
|
270
|
-
AOsignal_matrix = np.zeros((n_events, n_acquisitions), dtype=np.float32)
|
|
271
|
-
|
|
272
|
-
# Lire les données binaires depuis le fichier .cdf
|
|
273
|
-
with open(cdh_file.replace(".cdh", ".cdf"), "rb") as file:
|
|
274
|
-
for event in range(n_events):
|
|
275
|
-
# Lire et ignorer la chaîne hexadécimale (active_list)
|
|
276
|
-
num_elements = int([line.split(":")[1].strip() for line in cdh_content if "Number of US transducers" in line][0])
|
|
277
|
-
hex_length = (num_elements + 3) // 4 # Nombre de caractères hex nécessaires
|
|
278
|
-
file.read(hex_length // 2) # Ignorer la chaîne hexadécimale
|
|
279
|
-
|
|
280
|
-
# Lire l'angle (int8)
|
|
281
|
-
angle = int.from_bytes(file.read(1), byteorder='big', signed=True)
|
|
282
|
-
|
|
283
|
-
# Lire le signal AO correspondant (float32)
|
|
284
|
-
signal = np.frombuffer(file.read(n_acquisitions * 4), dtype=np.float32) # 4 octets par float32
|
|
285
|
-
|
|
286
|
-
# Stocker le signal dans la matrice
|
|
287
|
-
AOsignal_matrix[event, :] = signal
|
|
288
|
-
|
|
289
|
-
self.AOsignal = AOsignal_matrix
|
|
290
|
-
|
|
291
|
-
def plot_animations_A_matrix(self, wave_name=None, desired_duration_ms = 5000, save_dir=None):
|
|
292
|
-
"""
|
|
293
|
-
Plot synchronized animations of A_matrix slices for selected angles.
|
|
294
|
-
|
|
295
|
-
Args:
|
|
296
|
-
A_matrix: 4D numpy array (time, z, x, angles)
|
|
297
|
-
z: array of z-axis positions
|
|
298
|
-
x: array of x-axis positions
|
|
299
|
-
angles_to_plot: list of angles to visualize
|
|
300
|
-
wave_name: optional name for labeling the subplots (e.g., "wave1")
|
|
301
|
-
step: time step between frames (default every 10 frames)
|
|
302
|
-
save_dir: directory to save the animation gif; if None, animation will not be saved
|
|
303
|
-
|
|
304
|
-
Returns:
|
|
305
|
-
ani: Matplotlib FuncAnimation object
|
|
306
|
-
"""
|
|
307
|
-
|
|
308
|
-
# Set the maximum embedded animation size to 100 MB
|
|
309
|
-
mpl.rcParams['animation.embed_limit'] = 100
|
|
310
|
-
|
|
311
|
-
if save_dir is not None:
|
|
312
|
-
os.makedirs(save_dir, exist_ok=True)
|
|
313
|
-
|
|
314
|
-
num_plots = len(self.AcousticFields)
|
|
315
|
-
|
|
316
|
-
# Automatically adjust layout
|
|
317
|
-
if num_plots <= 5:
|
|
318
|
-
nrows, ncols = 1, num_plots
|
|
319
|
-
else:
|
|
320
|
-
ncols = 5
|
|
321
|
-
nrows = (num_plots + ncols - 1) // ncols
|
|
322
|
-
|
|
323
|
-
# Create figure and subplots
|
|
324
|
-
fig, axes = plt.subplots(nrows, ncols, figsize=(5 * ncols, 5.3 * nrows))
|
|
325
|
-
if isinstance(axes, plt.Axes):
|
|
326
|
-
axes = np.array([axes])
|
|
327
|
-
axes = axes.flatten()
|
|
328
|
-
|
|
329
|
-
ims = []
|
|
330
|
-
|
|
331
|
-
# Set main title
|
|
332
|
-
fig.suptitle(f"System Matrix Animation {wave_name}",
|
|
333
|
-
fontsize=12, y=0.98)
|
|
334
|
-
|
|
335
|
-
# Ensure start_idx_in_A_matrix exists
|
|
336
|
-
if not hasattr(self, 'start_idx_in_A_matrix'):
|
|
337
|
-
raise AttributeError("StructuredWave must have attribute 'start_idx_in_A_matrix' to locate A_matrix slices.")
|
|
338
|
-
|
|
339
|
-
for idx in range(num_plots):
|
|
340
|
-
ax = axes[idx]
|
|
341
|
-
|
|
342
|
-
im = ax.imshow(self.AcousticFields[0, :, :, idx],
|
|
343
|
-
# extent=(x[0]*1000, x[-1]*1000, z[-1]*1000, z[0]*1000), vmax = 0.2*np.max(A_matrix[:,:,:,global_index]),
|
|
344
|
-
extent=(self.params['Xrange'][0], self.params['Xrange'][1], self.params['Zrange'][1],self.params['Zrange'][0]), vmax = 1,
|
|
345
|
-
aspect='equal', cmap='jet', animated=True)
|
|
346
|
-
ax.set_xlabel("x (mm)", fontsize=8)
|
|
347
|
-
ax.set_ylabel("z (mm)", fontsize=8)
|
|
348
|
-
ims.append((im, ax, idx))
|
|
349
|
-
|
|
350
|
-
# Remove unused axes if any
|
|
351
|
-
for j in range(num_plots, len(axes)):
|
|
352
|
-
fig.delaxes(axes[j])
|
|
353
|
-
|
|
354
|
-
# Adjust layout to leave space for main title
|
|
355
|
-
plt.tight_layout(rect=[0, 0, 1, 0.95])
|
|
356
|
-
|
|
357
|
-
# Unified update function for all subplots
|
|
358
|
-
def update(frame):
|
|
359
|
-
artists = []
|
|
360
|
-
for im, ax, idx in ims:
|
|
361
|
-
im.set_array(self.AcousticFields[frame, :, :,idx ])
|
|
362
|
-
fig.suptitle(f"System Matrix Animation {wave_name} t = {frame * 25e-6 * 1000:.2f} ms", fontsize=10)
|
|
363
|
-
artists.append(im)
|
|
364
|
-
return artists
|
|
365
|
-
|
|
366
|
-
interval = desired_duration_ms / self.AcousticFields.shape[0]
|
|
367
|
-
# Create animation
|
|
368
|
-
ani = animation.FuncAnimation(
|
|
369
|
-
fig, update,
|
|
370
|
-
frames=range(0, self.AcousticFields.shape[0]),
|
|
371
|
-
interval=interval, blit=True
|
|
372
|
-
)
|
|
373
|
-
|
|
374
|
-
# Save animation if needed
|
|
375
|
-
if save_dir is not None:
|
|
376
|
-
now = datetime.now()
|
|
377
|
-
date_str = now.strftime("%Y_%d_%m_%y")
|
|
378
|
-
save_filename = f"AcousticField_{wave_name}_{date_str}.gif"
|
|
379
|
-
save_path = os.path.join(save_dir, save_filename)
|
|
380
|
-
ani.save(save_path, writer='pillow', fps=20)
|
|
381
|
-
print(f"Saved: {save_path}")
|
|
382
|
-
|
|
383
|
-
plt.close(fig)
|
|
384
|
-
|
|
385
|
-
return ani
|
|
386
|
-
|
|
387
|
-
def plot_animated_y_A_LAMBDA(self, fileOfAcousticField, save_dir=None, desired_duration_ms = 5000):
|
|
388
|
-
|
|
389
|
-
mpl.rcParams['animation.embed_limit'] = 100
|
|
390
|
-
|
|
391
|
-
pattern_str = AOT_biomaps.AOT_Acoustic.StructuredWave.getPattern(fileOfAcousticField)
|
|
392
|
-
angle = AOT_biomaps.AOT_Acoustic.StructuredWave.getAngle(fileOfAcousticField)
|
|
393
|
-
|
|
394
|
-
fieldToPlot = None
|
|
395
|
-
|
|
396
|
-
for field in self.AcousticFields:
|
|
397
|
-
if field.get_path() == fileOfAcousticField:
|
|
398
|
-
break
|
|
399
|
-
fieldToPlot = field
|
|
400
|
-
idx = self.AcousticFields.index(field)
|
|
401
|
-
else:
|
|
402
|
-
raise ValueError(f"Field {fileOfAcousticField} not found in AcousticFields.")
|
|
403
|
-
|
|
404
|
-
if wave_name is None:
|
|
405
|
-
wave_name = f"Pattern structure {pattern_str}"
|
|
406
|
-
|
|
407
|
-
fig, axs = plt.subplots(1, 2, figsize=(6 * 2, 5.3 * 1))
|
|
408
|
-
|
|
409
|
-
if isinstance(axs, plt.Axes):
|
|
410
|
-
axs = np.array([axs])
|
|
411
|
-
|
|
412
|
-
fig.suptitle(f"AO Signal Animation {wave_name} | Angle {angle}°", fontsize=12, y=0.98)
|
|
413
|
-
|
|
414
|
-
# Left: LAMBDA at bottom
|
|
415
|
-
axs[0].imshow(self.OpticImage.T, cmap='hot', alpha=1, origin='upper',
|
|
416
|
-
extent=(self.params['Xrange'][0], self.params['Xrange'][1], self.params['Zrange'][1], self.params['Zrange'][0]),
|
|
417
|
-
aspect='equal')
|
|
418
|
-
|
|
419
|
-
# Acoustic field drawn on top of LAMBDA
|
|
420
|
-
im_field = axs[0].imshow(fieldToPlot[0, :, :, idx], cmap='jet', origin='upper',
|
|
421
|
-
extent=(self.params['Xrange'][0], self.params['Xrange'][1], self.params['Zrange'][1], self.params['Zrange'][0]),
|
|
422
|
-
vmax = 1, vmin = 0.01, alpha=0.8,
|
|
423
|
-
aspect='equal')
|
|
424
|
-
|
|
425
|
-
axs[0].set_title(f"{wave_name} | Angle {angle}° | t = 0.00 ms", fontsize=10)
|
|
426
|
-
axs[0].set_xlabel("x (mm)", fontsize=8)
|
|
427
|
-
axs[0].set_ylabel("z (mm)", fontsize=8)
|
|
428
|
-
|
|
429
|
-
# Center: AO signal y
|
|
430
|
-
time_axis = np.arange(self.AOsignal.shape[0]) * 25e-6 * 1000 # in ms
|
|
431
|
-
line_y, = axs[1].plot(time_axis, self.AOsignal[:, idx])
|
|
432
|
-
# vertical_line = axs[1].axvline(x=time_axis[0], color='r', linestyle='--')
|
|
433
|
-
vertical_line, = axs[1].plot([time_axis[0], time_axis[0]], [0, self.AOsignal[0, idx]], 'r--')
|
|
434
|
-
axs[1].set_xlabel("Time (ms)", fontsize=8)
|
|
435
|
-
axs[1].set_ylabel("Value", fontsize=8)
|
|
436
|
-
axs[1].set_title(f"{wave_name} | Angle {angle}° | t = 0.00 ms", fontsize=10)
|
|
437
|
-
|
|
438
|
-
plt.tight_layout(rect=[0, 0, 1, 0.95])
|
|
439
|
-
|
|
440
|
-
def update(frame):
|
|
441
|
-
current_time_ms = frame * 25e-6 * 1000
|
|
442
|
-
|
|
443
|
-
# Apply masking to suppress background
|
|
444
|
-
frame_data = fieldToPlot[frame, :, :, idx]
|
|
445
|
-
masked_data = np.where(frame_data > 0.02, frame_data, np.nan)
|
|
446
|
-
im_field.set_data(masked_data)
|
|
447
|
-
|
|
448
|
-
axs[0].set_title(f"{wave_name} | Angle {angle}° | t = {current_time_ms:.2f} ms", fontsize=10)
|
|
449
|
-
|
|
450
|
-
# Copy partial y signal
|
|
451
|
-
y_vals = self.AOsignal[:, idx]
|
|
452
|
-
y_copy = np.full_like(y_vals, np.nan)
|
|
453
|
-
y_copy[:frame + 1] = y_vals[:frame + 1]
|
|
454
|
-
line_y.set_data(time_axis, y_copy)
|
|
455
|
-
|
|
456
|
-
# Red vertical line
|
|
457
|
-
vertical_line.set_data([time_axis[frame], time_axis[frame]], [0, y_vals[frame]])
|
|
458
|
-
|
|
459
|
-
axs[1].set_title(f"{wave_name} | Angle {angle}° | t = {current_time_ms:.2f} ms", fontsize=10)
|
|
460
|
-
|
|
461
|
-
return [im_field, vertical_line, line_y]
|
|
462
|
-
|
|
463
|
-
interval = desired_duration_ms / fieldToPlot.shape[0]
|
|
464
|
-
|
|
465
|
-
# Create the animation
|
|
466
|
-
ani = animation.FuncAnimation(
|
|
467
|
-
fig, update,
|
|
468
|
-
frames=range(0, self.AcousticFields.shape[0]),
|
|
469
|
-
interval=interval, blit=True
|
|
470
|
-
)
|
|
471
|
-
|
|
472
|
-
if save_dir is not None:
|
|
473
|
-
now = datetime.now()
|
|
474
|
-
date_str = now.strftime("%Y_%d_%m_%y")
|
|
475
|
-
os.makedirs(save_dir, exist_ok=True)
|
|
476
|
-
save_filename = f"A_y_LAMBDA_overlay_{pattern_str}_{angle}_{date_str}.gif"
|
|
477
|
-
save_path = os.path.join(save_dir, save_filename)
|
|
478
|
-
ani.save(save_path, writer='pillow', fps=20)
|
|
479
|
-
print(f"Saved: {save_path}")
|
|
480
|
-
|
|
481
|
-
plt.close(fig)
|
|
482
|
-
|
|
483
|
-
return ani
|
|
484
|
-
|
|
485
|
-
def plot_AO_signal_y(self, save_dir=None, wave_name=None):
|
|
486
|
-
"""
|
|
487
|
-
Plot AO signals y(t) for selected angles.
|
|
488
|
-
|
|
489
|
-
Args:
|
|
490
|
-
y: 2D numpy array (time, angles)
|
|
491
|
-
angles_to_plot: list of angles to visualize
|
|
492
|
-
save_dir: directory to save the figure; if None, only display
|
|
493
|
-
wave_name: optional name for labeling; default uses pattern structure
|
|
494
|
-
"""
|
|
495
|
-
|
|
496
|
-
# Time axis in milliseconds
|
|
497
|
-
time_axis = np.arange(self.AOsignal.shape[0]) * 25e-6 * 1000
|
|
498
|
-
|
|
499
|
-
# Set up layout
|
|
500
|
-
num_plots = self.AOsignal.shape[1]
|
|
501
|
-
|
|
502
|
-
if num_plots <= 5:
|
|
503
|
-
nrows, ncols = 1, num_plots
|
|
504
|
-
else:
|
|
505
|
-
ncols = 5
|
|
506
|
-
nrows = (num_plots + ncols - 1) // ncols
|
|
507
|
-
|
|
508
|
-
fig, axes = plt.subplots(nrows, ncols, figsize=(5 * ncols, 5.3 * nrows))
|
|
509
|
-
if isinstance(axes, plt.Axes):
|
|
510
|
-
axes = np.array([axes])
|
|
511
|
-
axes = axes.flatten()
|
|
512
|
-
|
|
513
|
-
# Set main title
|
|
514
|
-
fig.suptitle(f"AO Signal Plot {wave_name}", fontsize=12, y=0.98)
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
for idx in range(num_plots):
|
|
518
|
-
ax = axes[idx]
|
|
519
|
-
ax.plot(time_axis, self.AOsignal[:,idx])
|
|
520
|
-
ax.set_xlabel("Time (ms)", fontsize=8)
|
|
521
|
-
ax.set_ylabel("Value", fontsize=8)
|
|
522
|
-
|
|
523
|
-
# Remove unused axes
|
|
524
|
-
for j in range(num_plots, len(axes)):
|
|
525
|
-
fig.delaxes(axes[j])
|
|
526
|
-
|
|
527
|
-
plt.tight_layout(rect=[0, 0, 1, 0.95])
|
|
528
|
-
|
|
529
|
-
# Save if needed
|
|
530
|
-
if save_dir is not None:
|
|
531
|
-
now = datetime.now()
|
|
532
|
-
date_str = now.strftime("%Y_%d_%m_%y")
|
|
533
|
-
os.makedirs(save_dir, exist_ok=True)
|
|
534
|
-
save_filename = f"Static_y_Plot{wave_name}_{date_str}.png"
|
|
535
|
-
save_path = os.path.join(save_dir, save_filename)
|
|
536
|
-
plt.savefig(save_path, dpi=200)
|
|
537
|
-
print(f"Saved: {save_path}")
|
|
538
|
-
|
|
539
|
-
plt.show()
|
|
540
|
-
plt.close(fig)
|
|
541
|
-
|