AOT-biomaps 2.9.339__py3-none-any.whl → 2.9.356__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.
@@ -1,32 +1,22 @@
1
1
  import numpy as np
2
2
 
3
- def calc_mat_os(xm, fx, dx, bool_active_list, signal_type):
3
+ def calc_mat_os(xm, fx, bool_active_list, signal_type):
4
+ """
5
+ xm : vecteur des positions réelles des éléments (en m)
6
+ fx : fréquence spatiale (en m^-1)
7
+ signal_type : 'cos' ou 'sin'
8
+ """
4
9
  num_els = len(xm)
5
-
6
- # Cas limite : Fréquence nulle (Décimation 0)
7
- if fx == 0:
8
- if signal_type == 'cos':
9
- # cos(0) = 1 -> Tout est actif
10
- mask = np.ones(num_els, dtype=bool)
11
- else:
12
- # sin(0) = 0 -> Tout est inactif
13
- mask = np.zeros(num_els, dtype=bool)
10
+ num_cols = bool_active_list.shape[1]
11
+
12
+ if signal_type == 'cos':
13
+ mask = (np.cos(2 * np.pi * fx * xm) > 0).astype(float)
14
+ elif signal_type == 'sin':
15
+ mask = (np.sin(2 * np.pi * fx * xm) > 0).astype(float)
14
16
  else:
15
- # Calcul normal pour fx > 0
16
- half_period_elements = round(1 / (2 * fx * dx))
17
-
18
- # Sécurité : si fx est tellement grand que half_period < 1
19
- half_period_elements = max(1, half_period_elements)
20
-
21
- indices = np.arange(num_els)
22
- if signal_type == 'cos':
23
- mask = ((indices // half_period_elements) % 2 == 0)
24
- else:
25
- # Déphasage de 90° pour le sinus : on décale d'une demi-demi-période
26
- shift = half_period_elements // 2
27
- mask = (((indices + shift) // half_period_elements) % 2 == 0)
28
-
29
- return np.tile(mask[:, np.newaxis], (1, bool_active_list.shape[1]))
17
+ mask = np.ones(num_els) # Sécurité
18
+
19
+ return np.tile(mask[:, np.newaxis], (1, num_cols))
30
20
 
31
21
  def convert_to_hex_list(matrix):
32
22
  """
@@ -18,6 +18,8 @@ class Tomography(Experiment):
18
18
  self.patterns = None
19
19
  self.theta = []
20
20
  self.decimations = []
21
+ self.ActiveList = []
22
+ self.DelayLaw = []
21
23
 
22
24
  # PUBLIC METHODS
23
25
  def check(self):
@@ -66,8 +68,11 @@ class Tomography(Experiment):
66
68
  self.AcousticFields = self._generateAcousticFields_STRUCT_CPU(fieldDataPath, show_log, nameBlock)
67
69
  for i in range(len(self.AcousticFields)):
68
70
  profile = hex_to_binary_profile(self.AcousticFields[i].getName_field()[6:-4], self.params.acoustic['num_elements'])
71
+ self.ActiveList.append(profile)
69
72
  angle = self.AcousticFields[i].angle
70
73
  self.theta.append(angle)
74
+ Delay = 1000 * (1/self.params.acoustic['c0']) * np.sin(np.deg2rad(angle)) * np.arange(1, self.params.acoustic['num_elements'] + 1) * self.params.acoustic['element_width']
75
+ self.DelayLaw.append(Delay - np.min(Delay))
71
76
 
72
77
  if set(self.AcousticFields[i].getName_field()[6:-4].lower().replace(" ", "")) == {'f'}:
73
78
  fs_key = 0.0 # fs_key est en mm^-1 (0.0 mm^-1)
@@ -83,6 +88,7 @@ class Tomography(Experiment):
83
88
 
84
89
  # fs = n * dfx => n = fs / dfx with dfx = 1/(N*delta_x)
85
90
  self.decimations.append(int(fs_key / (1/(len(profile)*self.params.general['dx']))))
91
+
86
92
  else:
87
93
  raise ValueError("Unsupported wave type.")
88
94
 
@@ -314,20 +320,8 @@ class Tomography(Experiment):
314
320
  raise ValueError("Either N (>=2) or both decimations and angles must be provided for pattern generation.")
315
321
 
316
322
  def saveAOsignals_matlab(self, filePath):
317
- ActiveList = []
318
- DelayLaw = []
319
- c = self.params.acoustic['c0']
320
- NbElemts = self.params.acoustic['num_elements']
321
- pitch = self.params.acoustic['element_width']
322
-
323
- for i in range(len(self.AcousticFields)):
324
- profile = hex_to_binary_profile(self.AcousticFields[i].getName_field()[6:-4], NbElemts)
325
- ActiveList.append(profile)
326
- angle = self.AcousticFields[i].angle
327
- Delay = 1000 * (1/c) * np.sin(np.deg2rad(angle)) * np.arange(1, NbElemts + 1) * pitch
328
- DelayLaw.append(Delay - np.min(Delay))
329
323
 
330
- savemat(filePath, {'data': self.AOsignal_withTumor, 'thetas': self.theta, 'decimations': self.decimations, 'ActiveList' : ActiveList, 'DelayLaw': DelayLaw})
324
+ savemat(filePath, {'data': self.AOsignal_withTumor, 'thetas': self.theta, 'decimations': self.decimations, 'ActiveList' : self.ActiveList, 'DelayLaw': self.DelayLaw})
331
325
 
332
326
  def selectAngles(self, angles):
333
327
 
@@ -380,74 +374,73 @@ class Tomography(Experiment):
380
374
  self.AcousticFields = newAcousticFields
381
375
 
382
376
  def _genereate_patterns_from_decimations(self, decimations, angles):
383
- if isinstance(decimations, list): decimations = np.array(decimations)
384
- if isinstance(angles, list): angles = np.array(angles)
377
+ if isinstance(decimations, list):
378
+ decimations = np.array(decimations)
379
+ if isinstance(angles, list):
380
+ angles = np.array(angles)
385
381
 
386
382
  angles = np.sort(angles)
387
383
  decimations = np.sort(decimations)
388
384
 
389
385
  num_elements = self.params.acoustic['num_elements']
390
- dx_mm = self.params.general['dx'] * 1e3
386
+ Width = self.params.acoustic['element_width'] # en m
387
+ kerf = self.params.acoustic.get('kerf', 0.00000) # en m
388
+ Nactuators = self.params.acoustic['num_elements']
391
389
 
392
390
  # --- Calcul du nombre de Scans ---
393
391
  if 0 in decimations:
394
- Nscans = 4 * angles.shape[0] * (decimations.shape[0] - 1) + angles.shape[0]
395
- offSet = angles.shape[0]
392
+ Nscans = 4 * len(angles) * (len(decimations) - 1) + len(angles)
396
393
  else:
397
- Nscans = 4 * angles.shape[0] * decimations.shape[0]
398
- offSet = 0
394
+ Nscans = 4 * len(angles) * len(decimations)
399
395
 
400
396
  ActiveLIST = np.ones((num_elements, Nscans))
401
- Xm = np.arange(1, num_elements + 1) * dx_mm
402
- dFx = 1 / (num_elements * dx_mm)
403
397
 
404
- # On traite séparément les décimations non nulles pour la boucle
398
+ # --- Calcul des positions centrées des éléments (en m) ---
399
+ Xc = (Width + (Nactuators - 1) * (kerf + Width)) / 2
400
+ Xm = np.array([Width * (i - 1) + Width / 2 - Xc for i in range(1, Nactuators + 1)])
401
+
402
+ # --- Gestion de l'onde plane (tous les piezo ON) au début ---
403
+ if 0 in decimations:
404
+ I_plane = np.arange(len(angles))
405
+ ActiveLIST[:, I_plane] = 1 # Tous les piezo ON pour les len(angles) premières colonnes
406
+
407
+ # --- Traitement des décimations non nulles ---
405
408
  active_decimations = decimations[decimations != 0]
409
+ dFx = 1 / (Nactuators * Width) # fx de base (en m^-1)
406
410
 
407
411
  for i_dec in range(len(active_decimations)):
408
- idx_base = (np.arange(len(angles))) + (i_dec * 4 * len(angles)) + offSet
409
-
410
- Icos = idx_base
411
- Incos = idx_base + len(angles)
412
- Isin = idx_base + 2 * len(angles)
413
- Insin = idx_base + 3 * len(angles)
412
+ # Décalage des indices pour placer les motifs modulés après l'onde plane
413
+ I = np.arange(len(angles)) + len(angles) + (i_dec * 4 * len(angles))
414
414
 
415
- fx = dFx * active_decimations[i_dec]
415
+ Icos = I
416
+ Incos = I + 1* len(angles)
417
+ Isin = I + 3 * len(angles)
418
+ Insin = I + 2 * len(angles)
416
419
 
417
- # Remplissage des 4 phases
418
- valid_icos = Icos[Icos < Nscans]
419
- if valid_icos.size > 0:
420
- ActiveLIST[:, valid_icos] = calc_mat_os(Xm, fx, dx_mm, ActiveLIST[:, valid_icos], 'cos')
421
- if (Incos < Nscans).any():
422
- ActiveLIST[:, Incos[Incos < Nscans]] = 1 - ActiveLIST[:, valid_icos]
420
+ fx = dFx * active_decimations[i_dec]
423
421
 
424
- valid_isin = Isin[Isin < Nscans]
425
- if valid_isin.size > 0:
426
- ActiveLIST[:, valid_isin] = calc_mat_os(Xm, fx, dx_mm, ActiveLIST[:, valid_isin], 'sin')
427
- if (Insin < Nscans).any():
428
- ActiveLIST[:, Insin[Insin < Nscans]] = 1 - ActiveLIST[:, valid_isin]
422
+ # Appliquer les motifs modulés
423
+ ActiveLIST[:, Icos] = calc_mat_os(Xm, fx, ActiveLIST[:, Icos[:1]], 'cos')
424
+ ActiveLIST[:, Incos] = 1 - ActiveLIST[:, Icos]
425
+ ActiveLIST[:, Isin] = calc_mat_os(Xm, fx, ActiveLIST[:, Isin[:1]], 'sin')
426
+ ActiveLIST[:, Insin] = 1 - ActiveLIST[:, Isin]
429
427
 
430
428
  # --- Conversion au format attendu ---
431
- # 1. On convertit toute la matrice en liste de strings Hexa
432
429
  hexa_list = convert_to_hex_list(ActiveLIST)
433
430
 
434
- # 2. Fonction interne de formatage d'angle (pour coller à votre ancien code)
435
431
  def format_angle(a):
436
432
  return f"{'1' if a < 0 else '0'}{abs(a):02d}"
437
433
 
438
- # 3. Construction de la liste de dictionnaires
439
434
  patterns = []
440
435
  print(f"Generating {Nscans} patterns from decimations and angles...")
441
436
  for i in range(Nscans):
442
- # On retrouve l'angle correspondant à l'index i
443
- # La logique est cyclique sur la taille de 'angles'
444
437
  angle_val = angles[i % len(angles)]
445
-
446
438
  hex_pattern = hexa_list[i]
447
439
  pair = f"{hex_pattern}_{format_angle(angle_val)}"
448
440
  patterns.append({"fileName": pair})
449
441
 
450
442
  return patterns
443
+
451
444
 
452
445
  def _generate_patterns(self, N,angles = None):
453
446
  def format_angle(a):
@@ -1,23 +1,23 @@
1
1
  from ._mainRecon import Recon
2
2
  from .ReconEnums import ReconType, AnalyticType, ProcessType
3
3
  from AOT_biomaps.AOT_Experiment.Tomography import hex_to_binary_profile
4
- from .ReconTools import get_phase_deterministic
4
+ from .ReconTools import fourierz_gpu, get_phase_deterministic, add_sincos_cpu, EvalDelayLawOS_center, ifourierx_gpu, rotate_theta_gpu, filter_radon_gpu, ifourierz_gpu
5
5
 
6
6
  import numpy as np
7
7
  from tqdm import trange
8
- import torch
9
- import tqdm
8
+ import cupy as cp
10
9
 
11
10
 
12
11
  class AnalyticRecon(Recon):
13
- def __init__(self, analyticType, **kwargs):
12
+ def __init__(self, analyticType, Lc = None,**kwargs):
14
13
  super().__init__(**kwargs)
15
14
  self.reconType = ReconType.Analytic
16
15
  self.analyticType = analyticType
16
+ if self.analyticType == AnalyticType.iRADON and Lc is None:
17
+ raise ValueError("Lc parameter must be provided for iRADON analytic reconstruction.")
18
+ self.Lc = Lc # in meters
17
19
  self.AOsignal_demoldulated = None
18
20
 
19
-
20
-
21
21
  def parse_and_demodulate(self, withTumor=True):
22
22
 
23
23
  if withTumor:
@@ -46,7 +46,7 @@ class AnalyticRecon(Recon):
46
46
  # Onde Plane (f_s = 0)
47
47
  if set(hex_pattern.lower().replace(" ", "")) == {'f'}:
48
48
  fs_key = 0.0 # fs_key est en mm^-1 (0.0 mm^-1)
49
- demodulated_data[(fs_key, angle_rad)] = np.array(AOsignal[i])
49
+ demodulated_data[(fs_key, angle_rad)] = np.array(AOsignal[:,i])
50
50
  continue
51
51
 
52
52
  # Onde Structurée
@@ -77,9 +77,9 @@ class AnalyticRecon(Recon):
77
77
 
78
78
  # La moyenne est nécessaire si plusieurs acquisitions ont la même phase (pour le SNR)
79
79
  if phase in structured_buffer[key]:
80
- structured_buffer[key][phase] = (structured_buffer[key][phase] + np.array(AOsignal[i])) / 2
80
+ structured_buffer[key][phase] = (structured_buffer[key][phase] + np.array(AOsignal[:,i])) / 2
81
81
  else:
82
- structured_buffer[key][phase] = np.array(AOsignal[i])
82
+ structured_buffer[key][phase] = np.array(AOsignal[:,i])
83
83
 
84
84
 
85
85
 
@@ -126,109 +126,295 @@ class AnalyticRecon(Recon):
126
126
  Parameters:
127
127
  analyticType: The type of analytic reconstruction to perform (default is iFOURIER).
128
128
  """
129
+ d_t = 1 / float(self.experiment.params.acoustic['f_saving'])
130
+ t_array = np.arange(0, self.experiment.AOsignal_withTumor.shape[0])*d_t
131
+ Z = t_array * self.experiment.params.acoustic['c0']
132
+ X_m = np.arange(0, self.experiment.params.acoustic['num_elements'])* self.experiment.params.general['dx']
133
+ dfX = 1 / (X_m[1] - X_m[0]) / len(X_m)
129
134
  if withTumor:
130
135
  self.AOsignal_demoldulated = self.parse_and_demodulate(withTumor=True)
131
136
  if self.analyticType == AnalyticType.iFOURIER:
132
- self.reconPhantom = self._iFourierRecon(self.experiment.AOsignal_withTumor)
137
+ self.reconPhantom = self._iFourierRecon(
138
+ R = self.experiment.AOsignal_withTumor,
139
+ z = Z,
140
+ X_m=X_m,
141
+ theta=self.experiment.theta,
142
+ decimation=self.experiment.decimations,
143
+ c=self.experiment.params.acoustic['c0'],
144
+ DelayLAWS=self.experiment.DelayLaw,
145
+ ActiveLIST=self.experiment.ActiveList,
146
+ withTumor=True,
147
+ )
148
+
133
149
  elif self.analyticType == AnalyticType.iRADON:
134
- self.reconPhantom = self._iRadonRecon(self.experiment.AOsignal_withTumor)
150
+ self.reconPhantom = self._iRadonRecon(
151
+ R=self.experiment.AOsignal_withTumor,
152
+ z=Z,
153
+ X_m=X_m,
154
+ theta=self.experiment.theta,
155
+ decimation=self.experiment.decimations,
156
+ df0x=dfX,
157
+ Lc =self.Lc,
158
+ c=self.experiment.params.acoustic['c0'],
159
+ DelayLAWS=self.experiment.DelayLaw,
160
+ ActiveLIST=self.experiment.ActiveList,
161
+ withTumor=True)
135
162
  else:
136
163
  raise ValueError(f"Unknown analytic type: {self.analyticType}")
137
164
  else:
138
165
  self.AOsignal_demoldulated = self.parse_and_demodulate(withTumor=False)
139
166
  if self.analyticType == AnalyticType.iFOURIER:
140
- self.reconLaser = self._iFourierRecon(self.experiment.AOsignal_withoutTumor)
167
+ self.reconLaser = self._iFourierRecon(
168
+ R = self.experiment.AOsignal_withoutTumor,
169
+ z = Z,
170
+ X_m=X_m,
171
+ theta=self.experiment.theta,
172
+ decimation=self.experiment.decimations,
173
+ c=self.experiment.params.acoustic['c0'],
174
+ DelayLAWS=self.experiment.DelayLaw,
175
+ ActiveLIST=self.experiment.ActiveList,
176
+ withTumor=False,
177
+ )
141
178
  elif self.analyticType == AnalyticType.iRADON:
142
- self.reconLaser = self._iRadonRecon(self.experiment.AOsignal_withoutTumor)
179
+ self.reconLaser = self._iRadonRecon(
180
+ R=self.experiment.AOsignal_withoutTumor,
181
+ z=Z,
182
+ X_m=X_m,
183
+ theta=self.experiment.theta,
184
+ decimation=self.experiment.decimations,
185
+ df0x=dfX,
186
+ Lc = self.Lc,
187
+ c=self.experiment.params.acoustic['c0'],
188
+ DelayLAWS=self.experiment.DelayLaw,
189
+ ActiveLIST=self.experiment.ActiveList,
190
+ withTumor=False)
143
191
  else:
144
192
  raise ValueError(f"Unknown analytic type: {self.analyticType}")
145
193
 
146
- def _iFourierRecon(self, AOsignal):
147
- """
148
- Reconstruction d'image utilisant la transformation de Fourier inverse.
149
- :param AOsignal: Signal dans le domaine temporel (shape: N_t, N_theta).
150
- :return: Image reconstruite dans le domaine spatial.
151
- """
152
- theta = np.array([af.angle for af in self.experiment.AcousticFields])
153
- f_s = np.array([af.f_s for af in self.experiment.AcousticFields])
154
- dt = self.experiment.dt
155
- f_t = np.fft.fftfreq(AOsignal.shape[0], d=dt) # fréquences temporelles
156
- x = self.experiment.OpticImage.laser.x
157
- z = self.experiment.OpticImage.laser.z
158
- X, Z = np.meshgrid(x, z, indexing='ij') # grille spatiale (Nx, Nz)
159
-
160
- # Transformée de Fourier du signal
161
- s_tilde = np.fft.fft(AOsignal, axis=0) # shape: (N_t, N_theta)
162
-
163
- # Initialisation de l'image reconstruite
164
- I_rec = np.zeros((len(x), len(z)), dtype=complex)
165
-
166
- # Boucle sur les angles
167
- for i, th in enumerate(trange(len(theta), desc="AOT-BioMaps -- iFourier Reconstruction")):
168
- # Coordonnées tournées
169
- X_prime = X * np.cos(th) + Z * np.sin(th)
170
- Z_prime = -X * np.sin(th) + Z * np.cos(th)
171
-
172
- # Pour chaque fréquence temporelle f_t[j]
173
- for j in range(len(f_t)):
174
- # Phase: exp(2jπ (X_prime * f_s[i] + Z_prime * f_t[j]))
175
- phase = 2j * np.pi * (X_prime * f_s[i] + Z_prime * f_t[j])
176
- # Contribution de cette fréquence
177
- I_rec += s_tilde[j, i] * np.exp(phase) * dt # Pondération par dt pour l'intégration
178
-
179
- # Normalisation
180
- I_rec /= len(theta)
181
- return np.abs(I_rec)
182
-
183
-
184
- def _iRadonRecon(self, AOsignal):
194
+ def _iFourierRecon(
195
+ self,
196
+ R,
197
+ z,
198
+ X_m,
199
+ theta,
200
+ decimation,
201
+ c,
202
+ DelayLAWS,
203
+ ActiveLIST,
204
+ withTumor,
205
+ ):
206
+ # --- 1. Préparation des données ---
207
+ R = cp.asarray(R)
208
+ z = cp.asarray(z)
209
+ X_m = cp.asarray(X_m)
210
+ theta = cp.asarray(theta)
211
+ decimation = cp.asarray(decimation)
212
+ DelayLAWS = cp.asarray(DelayLAWS)
213
+ ActiveLIST = cp.asarray(ActiveLIST)
214
+ # Normalisation DelayLAWS (ms → s si besoin)
215
+ DelayLAWS_s = cp.where(cp.max(DelayLAWS) > 1e-3, DelayLAWS / 1000.0, DelayLAWS)
216
+ # Regroupement tirs (décimation, angle)
217
+ ScanParam_cpu = cp.asnumpy(
218
+ cp.stack([decimation, cp.round(theta, 4)], axis=1)
219
+ )
220
+
221
+ _, ia_cpu, ib_cpu = np.unique(
222
+ ScanParam_cpu, axis=0, return_index=True, return_inverse=True
223
+ )
224
+
225
+ ia = cp.asarray(ia_cpu)
226
+ ib = cp.asarray(ib_cpu)
227
+
228
+
229
+ # --- 2. Structuration complexe ---
230
+ # add_sincos_gpu doit être l'équivalent GPU de add_sincos_cpu
231
+ F_complex_cpu, theta_u_cpu, decim_u_cpu = add_sincos_cpu(
232
+ cp.asnumpy(R),
233
+ cp.asnumpy(decimation),
234
+ np.radians(cp.asnumpy(theta))
235
+ )
236
+ M0 = EvalDelayLawOS_center(
237
+ X_m,
238
+ theta_u_cpu,
239
+ DelayLAWS_s.T[:, ia],
240
+ ActiveLIST.T[:, ia],
241
+ c
242
+ )
243
+
244
+ # Transfert GPU (UNE FOIS)
245
+ F_complex = cp.asarray(F_complex_cpu)
246
+ theta_u = cp.asarray(theta_u_cpu)
247
+ decim_u = cp.asarray(decim_u_cpu)
248
+
249
+ Nz = z.size
250
+ Nx = X_m.size
251
+ dx = X_m[1] - X_m[0]
252
+
253
+ X_grid, Z_grid = cp.meshgrid(X_m, z)
254
+ idx0_x = Nx // 2
255
+ # Angles uniques après compression
256
+ angles_group, ia_u, ib_u = cp.unique(
257
+ theta_u, return_index=True, return_inverse=True
258
+ )
259
+ Ntheta = angles_group.size
260
+
261
+ I_final = cp.zeros((Nz, Nx), dtype=cp.complex64)
262
+
263
+ # --- 3. Boucle InverseFourierX ---
264
+ for i_ang in trange(Ntheta, desc=f"AOT-BioMaps -- Analytic Reconstruction Tomography: iFourier ({'with tumor' if withTumor else 'without tumor'}) ---- processing on single GPU ----", unit="angle"):
265
+
266
+ # Grille (z, fx)
267
+ F_fx_z = cp.zeros((Nz, Nx), dtype=cp.complex64)
268
+
269
+ # Indices correspondant à cet angle
270
+ indices = cp.where(ib_u == i_ang)[0]
271
+
272
+ for idx in indices:
273
+ n = int(decim_u[idx])
274
+ trace_z = F_complex[:, idx]
275
+
276
+ # Mapping positif
277
+ ip = idx0_x + n
278
+ if 0 <= ip < Nx:
279
+ F_fx_z[:, ip] = trace_z
280
+
281
+ # Mapping négatif (symétrie hermitienne Matlab)
282
+ if n != 0:
283
+ im = idx0_x - n
284
+ if 0 <= im < Nx:
285
+ col_conj = cp.zeros(Nz, dtype=cp.complex64)
286
+ col_conj[1:] = cp.conj(trace_z[:-1])
287
+ F_fx_z[:, im] = col_conj
288
+
289
+ # Correction DC
290
+ F_fx_z[:, idx0_x] *= 0.5
291
+
292
+ # --- Inverse Fourier X (GPU) ---
293
+ I_spatial = ifourierx_gpu(F_fx_z, dx) * Nx
294
+
295
+ # --- Rotation spatiale GPU ---
296
+ I_rot = rotate_theta_gpu(
297
+ X_grid,
298
+ Z_grid,
299
+ I_spatial,
300
+ -angles_group[i_ang],
301
+ M0[i_ang, :]
302
+ )
303
+
304
+ # Somme incohérente (OriginIm = sum)
305
+ I_final += I_rot
306
+ I_final /= Ntheta
307
+ return cp.real(I_final).get()
308
+
309
+ def _iRadonRecon(
310
+ self,
311
+ R,
312
+ z,
313
+ X_m,
314
+ theta,
315
+ decimation,
316
+ df0x,
317
+ Lc,
318
+ c,
319
+ DelayLAWS,
320
+ ActiveLIST,
321
+ withTumor,
322
+ ):
185
323
  """
186
324
  Reconstruction d'image utilisant la méthode iRadon.
187
325
 
188
326
  :return: Image reconstruite.
189
327
  """
190
- @staticmethod
191
- def trapz(y, x):
192
- """Compute the trapezoidal rule for integration."""
193
- return np.sum((y[:-1] + y[1:]) * (x[1:] - x[:-1]) / 2)
194
-
195
- # Initialisation de l'image reconstruite
196
- I_rec = np.zeros((len(self.experiment.OpticImage.laser.x), len(self.experiment.OpticImage.laser.z)), dtype=complex)
197
-
198
- # Transformation de Fourier du signal
199
- s_tilde = np.fft.fft(AOsignal, axis=0)
200
-
201
- # Extraction des angles et des fréquences spatiales
202
- theta = [acoustic_field.angle for acoustic_field in self.experiment.AcousticFields]
203
- f_s = [acoustic_field.f_s for acoustic_field in self.experiment.AcousticFields]
204
-
205
- # Calcul des coordonnées transformées et intégrales
206
- with trange(len(theta) * 2, desc="AOT-BioMaps -- Analytic Reconstruction Tomography: iRadon") as pbar:
207
- for i in range(len(theta)):
208
- pbar.set_description("AOT-BioMaps -- Analytic Reconstruction Tomography: iRadon (Processing frequency contributions) ---- processing on single CPU ----")
209
- th = theta[i]
210
- x_prime = self.experiment.OpticImage.x[:, np.newaxis] * np.cos(th) - self.experiment.OpticImage.z[np.newaxis, :] * np.sin(th)
211
- z_prime = self.experiment.OpticImage.z[np.newaxis, :] * np.cos(th) + self.experiment.OpticImage.x[:, np.newaxis] * np.sin(th)
212
-
213
- # Première intégrale : partie réelle
214
- for j in range(len(f_s)):
215
- fs = f_s[j]
216
- integrand = s_tilde[i, j] * np.exp(2j * np.pi * (x_prime * fs + z_prime * fs))
217
- integral = self.trapz(integrand * fs, fs)
218
- I_rec += 2 * np.real(integral)
219
- pbar.update(1)
220
-
221
- for i in range(len(theta)):
222
- pbar.set_description("AOT-BioMaps -- Analytic Reconstruction Tomography: iRadon (Processing central contributions) ---- processing on single CPU ----")
223
- th = theta[i]
224
- x_prime = self.experiment.OpticImage.x[:, np.newaxis] * np.cos(th) - self.experiment.OpticImage.z[np.newaxis, :] * np.sin(th)
225
- z_prime = self.experiment.OpticImage.z[np.newaxis, :] * np.cos(th) + self.experiment.OpticImage.x[:, np.newaxis] * np.sin(th)
226
-
227
- # Filtrer les fréquences spatiales pour ne garder que celles inférieures ou égales à f_s_max
228
- filtered_f_s = np.array([fs for fs in f_s if fs <= self.f_s_max])
229
- integrand = s_tilde[i, np.where(np.array(f_s) == 0)[0][0]] * np.exp(2j * np.pi * z_prime * filtered_f_s)
230
- integral = self.trapz(integrand * filtered_f_s, filtered_f_s)
231
- I_rec += integral
232
- pbar.update(1)
233
-
234
- return np.abs(I_rec)
328
+
329
+ # ======================================================
330
+ # 1. AddSinCos (structuration)
331
+ # ======================================================
332
+ theta = np.radians(theta)
333
+ F_ct_kx, theta_u, decim_u = add_sincos_cpu(R, decimation, theta)
334
+ ScanParam = np.stack([decimation, theta], axis=1)
335
+ _, ia, _ = np.unique(ScanParam, axis=0, return_index=True, return_inverse=True)
336
+ ActiveLIST = np.asarray(ActiveLIST).T
337
+ DelayLAWS = np.asarray(DelayLAWS).T
338
+ ActiveLIST_unique = ActiveLIST[:,ia]
339
+
340
+ # ======================================================
341
+ # 2. FFT z
342
+ # ======================================================
343
+ z_gpu = cp.asarray(z)
344
+ Fin = fourierz_gpu(z, F_ct_kx)
345
+
346
+
347
+ dz = float(z[1] - z[0])
348
+ fz = cp.fft.fftshift(cp.fft.fftfreq(len(z), d=dz))
349
+
350
+ Nz, Nk = Fin.shape
351
+
352
+ # ======================================================
353
+ # 3. Filtrage OS exact
354
+ # ======================================================
355
+ decim_gpu = cp.asarray(decim_u)
356
+ I0 = decim_gpu == 0
357
+ F0 = Fin * I0[None, :] # I0 broadcastée sur les lignes
358
+
359
+ DEC, FZ = cp.meshgrid(decim_gpu, fz)
360
+
361
+ Hinf = cp.abs(FZ) < cp.abs(DEC) * df0x
362
+ Hsup = FZ >= 0
363
+
364
+ Fc = 1 / Lc
365
+ FILTER = filter_radon_gpu(fz, Fc)[:, None]
366
+
367
+ Finf = F0 * FILTER[:, :F0.shape[1]] * Hinf[:, :F0.shape[1]]
368
+ Fsup = Fin * FILTER * Hsup
369
+
370
+ # ======================================================
371
+ # 4. Retour espace z
372
+ # ======================================================
373
+ Finf = ifourierz_gpu(z, Finf)
374
+ Fsup = ifourierz_gpu(z, Fsup)
375
+
376
+ # ======================================================
377
+ # 5. Grille image
378
+ # ======================================================
379
+ X_gpu = cp.asarray(X_m)
380
+ X, Z = cp.meshgrid(X_gpu, z_gpu)
381
+ Xc = float(np.mean(X_m))
382
+
383
+ # ======================================================
384
+ # 6. Calcul du centre M0 pour chaque angle
385
+ # ======================================================
386
+ M0 = EvalDelayLawOS_center(X_m,theta, DelayLAWS[:, ia], ActiveLIST_unique, c)
387
+ M0_gpu = cp.asarray(M0)
388
+
389
+ # ======================================================
390
+ # 7. Rétroprojection
391
+ # ======================================================
392
+ Irec_list = []
393
+ Irec = cp.zeros_like(X, dtype=cp.complex64)
394
+ for i in trange(len(theta_u), desc=f"AOT-BioMaps -- Analytic Reconstruction Tomography: iRadon ({'with tumor' if withTumor else 'without tumor'}) ---- processing on single GPU ----", unit="angle"):
395
+ th = float(theta_u[i])
396
+
397
+ # calcul de T, S, h0...
398
+ T = (X - M0_gpu[i,0]) * cp.sin(th) + (Z - M0_gpu[i,1]) * cp.cos(th) + M0_gpu[i,1]
399
+ S = (X - Xc) * cp.cos(th) - (Z - M0_gpu[i,1]) * cp.sin(th)
400
+ h0 = cp.exp(1j * 2*cp.pi * decim_u[i] * df0x * S)
401
+
402
+ # interpolation GPU
403
+ Tind = (T - z_gpu[0]) / (z_gpu[1] - z_gpu[0])
404
+ i0 = cp.floor(Tind).astype(cp.int32)
405
+ i1 = i0 + 1
406
+ i0 = cp.clip(i0, 0, Nz-1)
407
+ i1 = cp.clip(i1, 0, Nz-1)
408
+ w = Tind - i0
409
+
410
+ proj_sup = (1-w)*Fsup[i0, i] + w*Fsup[i1, i]
411
+ proj_inf = (1-w)*Finf[i0, i] + w*Finf[i1, i]
412
+
413
+ Irec += 2*h0*proj_sup + proj_inf
414
+ Irec /= i+1
415
+ # Ajouter à la liste
416
+ Irec_list.append(cp.real(Irec).get())
417
+
418
+
419
+ return Irec_list
420
+
@@ -7,6 +7,8 @@ from numba import njit, prange
7
7
  from torch_sparse import coalesce
8
8
  from scipy.signal.windows import hann
9
9
  from itertools import groupby
10
+ import cupy as cp
11
+ from cupyx.scipy.ndimage import map_coordinates
10
12
 
11
13
  def load_recon(hdr_path):
12
14
  """
@@ -528,4 +530,163 @@ def get_phase_deterministic(profile):
528
530
  elif idx == 4 :
529
531
  phase = 3*np.pi/2
530
532
 
531
- return phase
533
+ return phase
534
+
535
+ def add_sincos_cpu(R, decimation, theta):
536
+ decimation = np.asarray(decimation)
537
+ theta = np.asarray(theta)
538
+
539
+ ScanParam = np.stack([decimation, theta], axis=1)
540
+ uniq, ia, ib = np.unique(ScanParam, axis=0, return_index=True, return_inverse=True)
541
+
542
+ theta_u = uniq[:,1]
543
+ decim_u = uniq[:,0]
544
+
545
+ theta0 = np.unique(theta_u)
546
+ N0 = len(theta0)
547
+
548
+ Rg = np.asarray(R)
549
+ Nz = Rg.shape[0]
550
+ Nk = N0 + (Rg.shape[1] - N0)//4
551
+
552
+ Iout = np.zeros((Nz, Nk), dtype=np.complex64)
553
+ # fx = 0 (onde plane)
554
+ Iout[:, :N0] = Rg[:, :N0]
555
+
556
+ k = N0
557
+ for i in range(N0, len(ia)):
558
+ idx = np.where(ib == i)[0]
559
+ h1, h2, h3, h4 = Rg[:, idx].T
560
+ Iout[:, k] = ((h1 - h2) - 1j*(h3 - h4)) / 2
561
+ k += 1
562
+
563
+ return Iout, theta_u, decim_u
564
+
565
+ def fourierz_gpu(z, X):
566
+ dz = float(z[1] - z[0])
567
+ Nz = X.shape[0]
568
+
569
+ return cp.fft.fftshift(
570
+ cp.fft.fft(
571
+ cp.fft.ifftshift(X, axes=0),
572
+ axis=0
573
+ ),
574
+ axes=0
575
+ ) * (Nz * dz)
576
+
577
+ def ifourierz_gpu(z, X):
578
+ dz = float(z[1] - z[0])
579
+ Nz = X.shape[0]
580
+
581
+ return cp.fft.ifftshift(
582
+ cp.fft.ifft(
583
+ cp.fft.fftshift(X, axes=0),
584
+ axis=0
585
+ ),
586
+ axes=0
587
+ ) * (1 / dz)
588
+
589
+ def ifourierx_gpu(F_fx_z, dx):
590
+ """
591
+ Inverse Fourier along X (axis=1), Matlab-compatible
592
+ F_fx_z : (Nz, Nx) complex cupy array
593
+ dx : scalar (spacing in x)
594
+ """
595
+
596
+ return (
597
+ cp.fft.ifftshift(
598
+ cp.fft.ifft(
599
+ cp.fft.fftshift(F_fx_z, axes=1),
600
+ axis=1
601
+ ),
602
+ axes=1
603
+ ) * (1.0 / dx)
604
+ )
605
+
606
+ def EvalDelayLawOS_center(X_m, theta, DelayLAWS, ActiveLIST, c):
607
+ """
608
+ Retourne le centre de rotation C pour chaque angle
609
+ X_m : positions des éléments de la sonde
610
+ DelayLAWS : delays en secondes (chaque colonne = angle, chaque ligne = élément)
611
+ ActiveLIST : masque des éléments actifs (1 = actif)
612
+ c : vitesse du son
613
+ """
614
+ Nangle = DelayLAWS.shape[1]
615
+ C = np.zeros((Nangle, 2))
616
+
617
+ ct = DelayLAWS * c # convert seconds to distance
618
+
619
+ for i in range(Nangle):
620
+ active_idx = np.where(ActiveLIST[:, i] == 1)[0]
621
+ if len(active_idx) == 0:
622
+ continue
623
+
624
+
625
+ angle_i = np.round(theta[i], 5)
626
+ # unit vector orthogonal to wavefront
627
+ u = np.array([np.sin(angle_i), np.cos(angle_i)])
628
+
629
+ # initial positions X0, Z0
630
+ X0 = X_m - u[0] * ct[:, i]
631
+ Z0 = 0 - u[1] * ct[:, i]
632
+
633
+ if Z0[-1] - Z0[0] != 0:
634
+ C[i, 0] = (Z0[-1]*X0[0] - Z0[0]*X0[-1]) / (Z0[-1] - Z0[0])
635
+ C[i, 1] = 0
636
+
637
+ return C
638
+
639
+ def rotate_theta_gpu(X, Z, Iin, theta, C):
640
+ """
641
+ GPU equivalent of RotateTheta.m
642
+ X, Z, Iin : cupy arrays (Nz, Nx)
643
+ theta : scalar (float)
644
+ C : (2,) array-like
645
+ """
646
+
647
+ # --- Translation ---
648
+ X_rel = X - C[0]
649
+ Z_rel = Z - C[1]
650
+
651
+ c = cp.cos(theta)
652
+ s = cp.sin(theta)
653
+
654
+ # --- Rotation (Matlab convention) ---
655
+ Xout = c * X_rel + s * Z_rel
656
+ Zout = -s * X_rel + c * Z_rel
657
+
658
+ # Back to original frame
659
+ Xout += C[0]
660
+ Zout += C[1]
661
+
662
+ # --- Conversion coordonnées -> indices ---
663
+ # Grille régulière supposée
664
+ dx = X[0, 1] - X[0, 0]
665
+ dz = Z[1, 0] - Z[0, 0]
666
+
667
+ x0 = X[0, 0]
668
+ z0 = Z[0, 0]
669
+
670
+ ix = (Xout - x0) / dx
671
+ iz = (Zout - z0) / dz
672
+
673
+ # --- Interpolation bilinéaire GPU ---
674
+ # map_coordinates attend (ndim, Npoints)
675
+ coords = cp.stack([iz.ravel(), ix.ravel()])
676
+
677
+ Iout = map_coordinates(
678
+ Iin,
679
+ coords,
680
+ order=1, # bilinear
681
+ mode='constant',
682
+ cval=0.0
683
+ )
684
+
685
+ return Iout.reshape(Iin.shape)
686
+
687
+ def filter_radon_gpu(fz, Fc):
688
+ FILTER = cp.abs(fz)
689
+ FILTER = cp.where(cp.abs(fz) > Fc, 0, FILTER)
690
+ FILTER *= cp.exp(-2 * cp.abs(fz / Fc)**10)
691
+ return FILTER
692
+
@@ -169,7 +169,9 @@ class Recon(ABC):
169
169
 
170
170
  def show(self, withTumor=True, savePath=None):
171
171
  if withTumor:
172
- if self.reconPhantom is None or self.reconPhantom == []:
172
+ if self.reconPhantom is None:
173
+ raise ValueError("Reconstructed phantom with tumor is empty. Run reconstruction first.")
174
+ if isinstance(self.reconPhantom, (list, tuple)) and len(self.reconPhantom) == 0:
173
175
  raise ValueError("Reconstructed phantom with tumor is empty. Run reconstruction first.")
174
176
  if isinstance(self.reconPhantom, list):
175
177
  image = self.reconPhantom[-1]
@@ -217,7 +219,9 @@ class Recon(ABC):
217
219
  axs[0].tick_params(axis='both', which='major', labelsize=8)
218
220
  axs[0].tick_params(axis='y', which='both', left=False, right=False, labelleft=False)
219
221
  else:
220
- if self.reconLaser is None or self.reconLaser == []:
222
+ if self.reconLaser is None:
223
+ raise ValueError("Reconstructed laser without tumor is empty. Run reconstruction first.")
224
+ if isinstance(self.reconLaser, (list, tuple)) and len(self.reconLaser) == 0:
221
225
  raise ValueError("Reconstructed laser without tumor is empty. Run reconstruction first.")
222
226
  if isinstance(self.reconLaser, list):
223
227
  image = self.reconLaser[-1]
AOT_biomaps/__init__.py CHANGED
@@ -85,7 +85,7 @@ from .AOT_Recon.AOT_PotentialFunctions.RelativeDifferences import *
85
85
  from .Config import config
86
86
  from .Settings import *
87
87
 
88
- __version__ = '2.9.339'
88
+ __version__ = '2.9.356'
89
89
  __process__ = config.get_process()
90
90
 
91
91
  def initialize(process=None):
@@ -185,6 +185,23 @@ def initialize(process=None):
185
185
 
186
186
 
187
187
 
188
+
189
+
190
+
191
+
192
+
193
+
194
+
195
+
196
+
197
+
198
+
199
+
200
+
201
+
202
+
203
+
204
+
188
205
 
189
206
 
190
207
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AOT_biomaps
3
- Version: 2.9.339
3
+ Version: 2.9.356
4
4
  Summary: Acousto-Optic Tomography
5
5
  Home-page: https://github.com/LucasDuclos/AcoustoOpticTomography
6
6
  Author: Lucas Duclos
@@ -1,6 +1,6 @@
1
1
  AOT_biomaps/Config.py,sha256=ghEOP1n8aO1pR-su13wMeAZAxZRfry5hH67NbtZ8SqI,3614
2
2
  AOT_biomaps/Settings.py,sha256=v8fPhnvvcfBJP29m1RLOTEr3jndGLGwbUiORXmsj2Bo,2853
3
- AOT_biomaps/__init__.py,sha256=r0H6FXsW0n-ethz4CgAmwp24IOM9OaxvBLKxLkZ0We0,4340
3
+ AOT_biomaps/__init__.py,sha256=prD0ZZNE6dc-NWf_N0QIPLTrsRD3fAtK78ptZscj3Yo,4374
4
4
  AOT_biomaps/AOT_Acoustic/AcousticEnums.py,sha256=s5kXa6jKzbS4btwbubrVcynLOr0yg5tth5vL_FGfbMk,1802
5
5
  AOT_biomaps/AOT_Acoustic/AcousticTools.py,sha256=h2sCtGVcDtyLtEF1q7sLZmuWivWmesVGUBPnW-ndQqc,7535
6
6
  AOT_biomaps/AOT_Acoustic/FocusedWave.py,sha256=3kGKKDx_3Msy5COYqIwzROPORGWvNjw8UsDanBfkMXE,11037
@@ -9,9 +9,9 @@ AOT_biomaps/AOT_Acoustic/PlaneWave.py,sha256=xza-rj5AUWDecLkGDxRcULrwZVWeBvGnEP2
9
9
  AOT_biomaps/AOT_Acoustic/StructuredWave.py,sha256=jTLVlOhYLWJb5MxZPxhq3OFVlz2McoyMPBmfLvnekDU,18209
10
10
  AOT_biomaps/AOT_Acoustic/__init__.py,sha256=t9M2rRqa_L9pk7W2FeELTkHEMuP4DBr4gBRldMqsQbg,491
11
11
  AOT_biomaps/AOT_Acoustic/_mainAcoustic.py,sha256=RdmhRF1i0KAlpsP7_wnZ7F4J27br3eUc4XR91Qq7C64,44158
12
- AOT_biomaps/AOT_Experiment/ExperimentTools.py,sha256=EyTIwgxTK-FqJYlhdjgirfWCSL1kTp-IOS0tTgiAVNA,3153
12
+ AOT_biomaps/AOT_Experiment/ExperimentTools.py,sha256=aFvJw6J_jfFVTDFnG7J3a61SHEgORdZKZS0UI82VMaY,2637
13
13
  AOT_biomaps/AOT_Experiment/Focus.py,sha256=B2nBawmv-NG2AWJx9zgQ8GlN6aFB9FwTSqX-M-phKXg,3193
14
- AOT_biomaps/AOT_Experiment/Tomography.py,sha256=3JlslI9GNZy-u0uYgf0u9qyaVAQki6cYTHhcGK33aFg,36747
14
+ AOT_biomaps/AOT_Experiment/Tomography.py,sha256=GGbXVUnRhvSRLL1QEg5ZgAhtWIg9RfH7aADJ5u-78HQ,36383
15
15
  AOT_biomaps/AOT_Experiment/__init__.py,sha256=H9zMLeBLA6uhbaHohAa-2u5mDDxqJi8oE5c6tShdQp8,308
16
16
  AOT_biomaps/AOT_Experiment/_mainExperiment.py,sha256=zSfuNrsz7nhiKrGIdK6CAXjlI2T6qYC5-JXHFgPNzhc,24674
17
17
  AOT_biomaps/AOT_Optic/Absorber.py,sha256=jEodzRy7gkEH-wbazVasRQiri0dU16BfapmR-qnTSvM,867
@@ -21,14 +21,14 @@ AOT_biomaps/AOT_Optic/__init__.py,sha256=HSUVhfz0NzwHHZZ9KP9Xyfu33IgP_rYJX86J-gE
21
21
  AOT_biomaps/AOT_Optic/_mainOptic.py,sha256=Wk63CcgWbU-ygMfjNK80islaUbGGJpTXgZY3_C2KQNY,8179
22
22
  AOT_biomaps/AOT_Recon/AOT_biomaps_kernels.cubin,sha256=JWy-bdtBTZdnNlDbJGZKwXyF-2u1wICtmlOC_YxEL6o,82528
23
23
  AOT_biomaps/AOT_Recon/AlgebraicRecon.py,sha256=CGBXZyYEZ3TOTFOKSt-h7NGuFbuI9PNr3YTWTbSLxDo,46832
24
- AOT_biomaps/AOT_Recon/AnalyticRecon.py,sha256=NKO7nE2Roq3E9HzMf_eJobm7rNQK1xzaQ0W9zXic9CY,11067
24
+ AOT_biomaps/AOT_Recon/AnalyticRecon.py,sha256=uZp4Va9z04dG7neZFGAmmAUygs8JyfrDuOr1_lKIERc,16651
25
25
  AOT_biomaps/AOT_Recon/BayesianRecon.py,sha256=RnnPa-tTcvirwiNPnCRZnSM4NWeEEltYET-piBbp34g,12671
26
26
  AOT_biomaps/AOT_Recon/DeepLearningRecon.py,sha256=RfVcEsi4GeGqJn0_SPxwQPQx6IQjin79WKh2UarMRLI,1383
27
27
  AOT_biomaps/AOT_Recon/PrimalDualRecon.py,sha256=JbFhxiyUoSTnlJgHbOWIfUUwhwfZoi39RJMnfkagegY,16504
28
28
  AOT_biomaps/AOT_Recon/ReconEnums.py,sha256=KAf55RqHAr2ilt6pxFrUBGQOn-7HA8NP6TyL-1FNiXo,19714
29
- AOT_biomaps/AOT_Recon/ReconTools.py,sha256=-ZbzRHSzUprjzPRGCJeBiow_2AEvS2IzCSrv3XfzpLs,21307
29
+ AOT_biomaps/AOT_Recon/ReconTools.py,sha256=CV2BwdEwvNd3B02G5LYoKsRGlONwIupuv617S2AOWZE,25322
30
30
  AOT_biomaps/AOT_Recon/__init__.py,sha256=xs_argJqXKFl76xP7-jiUc1ynOEEtY7XZ0gDxD5uVZc,246
31
- AOT_biomaps/AOT_Recon/_mainRecon.py,sha256=exoa2UBMfMHjemxAU9dW0mhEfsP6Oe1qjSfrTrgbIcY,13125
31
+ AOT_biomaps/AOT_Recon/_mainRecon.py,sha256=MvDnfsiJ7v-UmtCFmA1vmKGzV9zd2xdnt27CAWBubko,13470
32
32
  AOT_biomaps/AOT_Recon/AOT_Optimizers/DEPIERRO.py,sha256=qA1n722GLQJH3V8HcLr5q_GxEwBS_NRlIT3E6JZk-Ag,9479
33
33
  AOT_biomaps/AOT_Recon/AOT_Optimizers/LS.py,sha256=bCu1rKzFXPbYQ7jV3L3E_jVQpb6LIEC5MIlN1-mCNdY,22814
34
34
  AOT_biomaps/AOT_Recon/AOT_Optimizers/MAPEM.py,sha256=vQLCB0L4FSXJKn2_6kdIdWrI6WZ82KuqUh7CSqBGVuo,25766
@@ -42,7 +42,7 @@ AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/__init__.py,sha256=RwrJdLOFbAFBFnRx
42
42
  AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_CSR.py,sha256=RACc2P5oxmp0uPLAGnNj9mEtAxa_OlepNgCawKij3jI,12062
43
43
  AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_SELL.py,sha256=ti3dZQsb_Uu62C7Bn65Z-yf-R5NKCFsmnBT5GlLd_HY,15138
44
44
  AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/__init__.py,sha256=8nou-hqjQjuCTLhoL5qv4EM_lMPFviAZAZKSPhi84jE,67
45
- aot_biomaps-2.9.339.dist-info/METADATA,sha256=3XoTP8HukgXdJdBPQC66FjZ5q-SvOn5LhVETzFDXvfY,700
46
- aot_biomaps-2.9.339.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
47
- aot_biomaps-2.9.339.dist-info/top_level.txt,sha256=6STF-lT4kaAnBHJYCripmN5mZABoHjMuY689JdiDphk,12
48
- aot_biomaps-2.9.339.dist-info/RECORD,,
45
+ aot_biomaps-2.9.356.dist-info/METADATA,sha256=eiJaF8-8bnGyUaK14BMcAiv645TibZb5uk8thsji2bE,700
46
+ aot_biomaps-2.9.356.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
47
+ aot_biomaps-2.9.356.dist-info/top_level.txt,sha256=6STF-lT4kaAnBHJYCripmN5mZABoHjMuY689JdiDphk,12
48
+ aot_biomaps-2.9.356.dist-info/RECORD,,