AOT-biomaps 2.9.333__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.

Potentially problematic release.


This version of AOT-biomaps might be problematic. Click here for more details.

@@ -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,27 +18,8 @@ class Tomography(Experiment):
18
18
  self.patterns = None
19
19
  self.theta = []
20
20
  self.decimations = []
21
-
22
-
23
- for i in range(len(self.AcousticFields)):
24
- profile = hex_to_binary_profile(self.AcousticFields[i].getName_field()[6:-4], self.params.acoustic['num_elements'])
25
- angle = self.AcousticFields[i].angle
26
- self.theta.append(angle)
27
-
28
- if set(self.AcousticFields[i].getName_field()[6:-4].lower().replace(" ", "")) == {'f'}:
29
- fs_key = 0.0 # fs_key est en mm^-1 (0.0 mm^-1)
30
- else:
31
- ft_prof = np.fft.fft(profile)
32
- idx_max = np.argmax(np.abs(ft_prof[1:len(profile)//2])) + 1
33
- freqs = np.fft.fftfreq(len(profile), d=self.params.general['dx'])
34
-
35
- # freqs est en m^-1 car delta_x est en mètres.
36
- fs_m_inv = abs(freqs[idx_max])
37
-
38
- fs_key = fs_m_inv # Fréquence spatiale en mm^-1
39
-
40
- # fs = n * dfx => n = fs / dfx with dfx = 1/(N*delta_x)
41
- self.decimations.append(int(fs_key / (1/(len(profile)*self.params.general['dx']))))
21
+ self.ActiveList = []
22
+ self.DelayLaw = []
42
23
 
43
24
  # PUBLIC METHODS
44
25
  def check(self):
@@ -85,6 +66,29 @@ class Tomography(Experiment):
85
66
  """
86
67
  if self.TypeAcoustic.value == WaveType.StructuredWave.value:
87
68
  self.AcousticFields = self._generateAcousticFields_STRUCT_CPU(fieldDataPath, show_log, nameBlock)
69
+ for i in range(len(self.AcousticFields)):
70
+ profile = hex_to_binary_profile(self.AcousticFields[i].getName_field()[6:-4], self.params.acoustic['num_elements'])
71
+ self.ActiveList.append(profile)
72
+ angle = self.AcousticFields[i].angle
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))
76
+
77
+ if set(self.AcousticFields[i].getName_field()[6:-4].lower().replace(" ", "")) == {'f'}:
78
+ fs_key = 0.0 # fs_key est en mm^-1 (0.0 mm^-1)
79
+ else:
80
+ ft_prof = np.fft.fft(profile)
81
+ idx_max = np.argmax(np.abs(ft_prof[1:len(profile)//2])) + 1
82
+ freqs = np.fft.fftfreq(len(profile), d=self.params.general['dx'])
83
+
84
+ # freqs est en m^-1 car delta_x est en mètres.
85
+ fs_m_inv = abs(freqs[idx_max])
86
+
87
+ fs_key = fs_m_inv # Fréquence spatiale en mm^-1
88
+
89
+ # fs = n * dfx => n = fs / dfx with dfx = 1/(N*delta_x)
90
+ self.decimations.append(int(fs_key / (1/(len(profile)*self.params.general['dx']))))
91
+
88
92
  else:
89
93
  raise ValueError("Unsupported wave type.")
90
94
 
@@ -314,23 +318,10 @@ class Tomography(Experiment):
314
318
  raise ValueError("Generated patterns failed validation.")
315
319
  else:
316
320
  raise ValueError("Either N (>=2) or both decimations and angles must be provided for pattern generation.")
317
-
318
-
321
+
319
322
  def saveAOsignals_matlab(self, filePath):
320
- ActiveList = []
321
- DelayLaw = []
322
- c = self.params.acoustic['c0']
323
- NbElemts = self.params.acoustic['num_elements']
324
- pitch = self.params.acoustic['width']
325
-
326
- for i in range(len(self.AcousticFields)):
327
- profile = hex_to_binary_profile(self.AcousticFields[i].getName_field()[6:-4], NbElemts)
328
- ActiveList.append(profile)
329
- angle = self.AcousticFields[i].angle
330
- Delay = 1000 * (1/c) * np.sin(np.deg2rad(angle)) * np.arange(1, NbElemts + 1) * pitch
331
- DelayLaw.append(Delay - np.min(Delay))
332
323
 
333
- 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})
334
325
 
335
326
  def selectAngles(self, angles):
336
327
 
@@ -383,74 +374,73 @@ class Tomography(Experiment):
383
374
  self.AcousticFields = newAcousticFields
384
375
 
385
376
  def _genereate_patterns_from_decimations(self, decimations, angles):
386
- if isinstance(decimations, list): decimations = np.array(decimations)
387
- 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)
388
381
 
389
382
  angles = np.sort(angles)
390
383
  decimations = np.sort(decimations)
391
384
 
392
385
  num_elements = self.params.acoustic['num_elements']
393
- 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']
394
389
 
395
390
  # --- Calcul du nombre de Scans ---
396
391
  if 0 in decimations:
397
- Nscans = 4 * angles.shape[0] * (decimations.shape[0] - 1) + angles.shape[0]
398
- offSet = angles.shape[0]
392
+ Nscans = 4 * len(angles) * (len(decimations) - 1) + len(angles)
399
393
  else:
400
- Nscans = 4 * angles.shape[0] * decimations.shape[0]
401
- offSet = 0
394
+ Nscans = 4 * len(angles) * len(decimations)
402
395
 
403
396
  ActiveLIST = np.ones((num_elements, Nscans))
404
- Xm = np.arange(1, num_elements + 1) * dx_mm
405
- dFx = 1 / (num_elements * dx_mm)
406
397
 
407
- # 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 ---
408
408
  active_decimations = decimations[decimations != 0]
409
+ dFx = 1 / (Nactuators * Width) # fx de base (en m^-1)
409
410
 
410
411
  for i_dec in range(len(active_decimations)):
411
- idx_base = (np.arange(len(angles))) + (i_dec * 4 * len(angles)) + offSet
412
-
413
- Icos = idx_base
414
- Incos = idx_base + len(angles)
415
- Isin = idx_base + 2 * len(angles)
416
- 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))
417
414
 
418
- 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)
419
419
 
420
- # Remplissage des 4 phases
421
- valid_icos = Icos[Icos < Nscans]
422
- if valid_icos.size > 0:
423
- ActiveLIST[:, valid_icos] = calc_mat_os(Xm, fx, dx_mm, ActiveLIST[:, valid_icos], 'cos')
424
- if (Incos < Nscans).any():
425
- ActiveLIST[:, Incos[Incos < Nscans]] = 1 - ActiveLIST[:, valid_icos]
420
+ fx = dFx * active_decimations[i_dec]
426
421
 
427
- valid_isin = Isin[Isin < Nscans]
428
- if valid_isin.size > 0:
429
- ActiveLIST[:, valid_isin] = calc_mat_os(Xm, fx, dx_mm, ActiveLIST[:, valid_isin], 'sin')
430
- if (Insin < Nscans).any():
431
- 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]
432
427
 
433
428
  # --- Conversion au format attendu ---
434
- # 1. On convertit toute la matrice en liste de strings Hexa
435
429
  hexa_list = convert_to_hex_list(ActiveLIST)
436
430
 
437
- # 2. Fonction interne de formatage d'angle (pour coller à votre ancien code)
438
431
  def format_angle(a):
439
432
  return f"{'1' if a < 0 else '0'}{abs(a):02d}"
440
433
 
441
- # 3. Construction de la liste de dictionnaires
442
434
  patterns = []
443
435
  print(f"Generating {Nscans} patterns from decimations and angles...")
444
436
  for i in range(Nscans):
445
- # On retrouve l'angle correspondant à l'index i
446
- # La logique est cyclique sur la taille de 'angles'
447
437
  angle_val = angles[i % len(angles)]
448
-
449
438
  hex_pattern = hexa_list[i]
450
439
  pair = f"{hex_pattern}_{format_angle(angle_val)}"
451
440
  patterns.append({"fileName": pair})
452
441
 
453
442
  return patterns
443
+
454
444
 
455
445
  def _generate_patterns(self, N,angles = None):
456
446
  def format_angle(a):
@@ -679,6 +669,7 @@ class Tomography(Experiment):
679
669
  AcousticField.save_field(fieldDataPath)
680
670
  listAcousticFields.append(AcousticField)
681
671
  progress_bar.set_postfix_str("")
672
+
682
673
  return listAcousticFields
683
674
 
684
675
  def load_experimentalAO(self, pathAO, withTumor = True, h5name='AOsignal'):
@@ -1,35 +1,35 @@
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:
24
24
  AOsignal = self.experiment.AOsignal_withTumor
25
25
  else:
26
26
  AOsignal = self.experiment.AOsignal_withoutTumor
27
- delta_x = self.params.acoustic['dx'] # en m
28
- n_piezos = self.params.acoustic['num_elements']
27
+ delta_x = self.experiment.params.general['dx'] # en m
28
+ n_piezos = self.experiment.params.acoustic['num_elements']
29
29
  demodulated_data = {}
30
30
  structured_buffer = {}
31
31
 
32
- for i in tqdm(range(len(self.experiment.AcousticFields)), desc="Demodulating AO signals"):
32
+ for i in trange(len(self.experiment.AcousticFields), desc="Demodulating AO signals"):
33
33
  label = self.experiment.AcousticFields[i].getName_field()
34
34
 
35
35
  parts = label.split("_")
@@ -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
 
@@ -98,11 +98,10 @@ class AnalyticRecon(Recon):
98
98
 
99
99
  real = s0 - s_pi
100
100
  imag = s_pi_2 - s_3pi_2
101
-
102
-
101
+
103
102
  demodulated_data[(fs, theta)] = (real - 1j * imag) / (2/np.pi)
104
- self.AOsignal_demoldulated = demodulated_data
105
-
103
+
104
+ return demodulated_data
106
105
 
107
106
  def run(self, processType = ProcessType.PYTHON, withTumor= True):
108
107
  """
@@ -127,107 +126,295 @@ class AnalyticRecon(Recon):
127
126
  Parameters:
128
127
  analyticType: The type of analytic reconstruction to perform (default is iFOURIER).
129
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)
130
134
  if withTumor:
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:
165
+ self.AOsignal_demoldulated = self.parse_and_demodulate(withTumor=False)
138
166
  if self.analyticType == AnalyticType.iFOURIER:
139
- 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
+ )
140
178
  elif self.analyticType == AnalyticType.iRADON:
141
- 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)
142
191
  else:
143
192
  raise ValueError(f"Unknown analytic type: {self.analyticType}")
144
193
 
145
- def _iFourierRecon(self, AOsignal):
146
- """
147
- Reconstruction d'image utilisant la transformation de Fourier inverse.
148
- :param AOsignal: Signal dans le domaine temporel (shape: N_t, N_theta).
149
- :return: Image reconstruite dans le domaine spatial.
150
- """
151
- theta = np.array([af.angle for af in self.experiment.AcousticFields])
152
- f_s = np.array([af.f_s for af in self.experiment.AcousticFields])
153
- dt = self.experiment.dt
154
- f_t = np.fft.fftfreq(AOsignal.shape[0], d=dt) # fréquences temporelles
155
- x = self.experiment.OpticImage.laser.x
156
- z = self.experiment.OpticImage.laser.z
157
- X, Z = np.meshgrid(x, z, indexing='ij') # grille spatiale (Nx, Nz)
158
-
159
- # Transformée de Fourier du signal
160
- s_tilde = np.fft.fft(AOsignal, axis=0) # shape: (N_t, N_theta)
161
-
162
- # Initialisation de l'image reconstruite
163
- I_rec = np.zeros((len(x), len(z)), dtype=complex)
164
-
165
- # Boucle sur les angles
166
- for i, th in enumerate(trange(len(theta), desc="AOT-BioMaps -- iFourier Reconstruction")):
167
- # Coordonnées tournées
168
- X_prime = X * np.cos(th) + Z * np.sin(th)
169
- Z_prime = -X * np.sin(th) + Z * np.cos(th)
170
-
171
- # Pour chaque fréquence temporelle f_t[j]
172
- for j in range(len(f_t)):
173
- # Phase: exp(2jπ (X_prime * f_s[i] + Z_prime * f_t[j]))
174
- phase = 2j * np.pi * (X_prime * f_s[i] + Z_prime * f_t[j])
175
- # Contribution de cette fréquence
176
- I_rec += s_tilde[j, i] * np.exp(phase) * dt # Pondération par dt pour l'intégration
177
-
178
- # Normalisation
179
- I_rec /= len(theta)
180
- return np.abs(I_rec)
181
-
182
-
183
- 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
+ ):
184
323
  """
185
324
  Reconstruction d'image utilisant la méthode iRadon.
186
325
 
187
326
  :return: Image reconstruite.
188
327
  """
189
- @staticmethod
190
- def trapz(y, x):
191
- """Compute the trapezoidal rule for integration."""
192
- return np.sum((y[:-1] + y[1:]) * (x[1:] - x[:-1]) / 2)
193
-
194
- # Initialisation de l'image reconstruite
195
- I_rec = np.zeros((len(self.experiment.OpticImage.laser.x), len(self.experiment.OpticImage.laser.z)), dtype=complex)
196
-
197
- # Transformation de Fourier du signal
198
- s_tilde = np.fft.fft(AOsignal, axis=0)
199
-
200
- # Extraction des angles et des fréquences spatiales
201
- theta = [acoustic_field.angle for acoustic_field in self.experiment.AcousticFields]
202
- f_s = [acoustic_field.f_s for acoustic_field in self.experiment.AcousticFields]
203
-
204
- # Calcul des coordonnées transformées et intégrales
205
- with trange(len(theta) * 2, desc="AOT-BioMaps -- Analytic Reconstruction Tomography: iRadon") as pbar:
206
- for i in range(len(theta)):
207
- pbar.set_description("AOT-BioMaps -- Analytic Reconstruction Tomography: iRadon (Processing frequency contributions) ---- processing on single CPU ----")
208
- th = theta[i]
209
- x_prime = self.experiment.OpticImage.x[:, np.newaxis] * np.cos(th) - self.experiment.OpticImage.z[np.newaxis, :] * np.sin(th)
210
- z_prime = self.experiment.OpticImage.z[np.newaxis, :] * np.cos(th) + self.experiment.OpticImage.x[:, np.newaxis] * np.sin(th)
211
-
212
- # Première intégrale : partie réelle
213
- for j in range(len(f_s)):
214
- fs = f_s[j]
215
- integrand = s_tilde[i, j] * np.exp(2j * np.pi * (x_prime * fs + z_prime * fs))
216
- integral = self.trapz(integrand * fs, fs)
217
- I_rec += 2 * np.real(integral)
218
- pbar.update(1)
219
-
220
- for i in range(len(theta)):
221
- pbar.set_description("AOT-BioMaps -- Analytic Reconstruction Tomography: iRadon (Processing central contributions) ---- processing on single CPU ----")
222
- th = theta[i]
223
- x_prime = self.experiment.OpticImage.x[:, np.newaxis] * np.cos(th) - self.experiment.OpticImage.z[np.newaxis, :] * np.sin(th)
224
- z_prime = self.experiment.OpticImage.z[np.newaxis, :] * np.cos(th) + self.experiment.OpticImage.x[:, np.newaxis] * np.sin(th)
225
-
226
- # Filtrer les fréquences spatiales pour ne garder que celles inférieures ou égales à f_s_max
227
- filtered_f_s = np.array([fs for fs in f_s if fs <= self.f_s_max])
228
- integrand = s_tilde[i, np.where(np.array(f_s) == 0)[0][0]] * np.exp(2j * np.pi * z_prime * filtered_f_s)
229
- integral = self.trapz(integrand * filtered_f_s, filtered_f_s)
230
- I_rec += integral
231
- pbar.update(1)
232
-
233
- 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.333'
88
+ __version__ = '2.9.356'
89
89
  __process__ = config.get_process()
90
90
 
91
91
  def initialize(process=None):
@@ -177,6 +177,29 @@ def initialize(process=None):
177
177
 
178
178
 
179
179
 
180
+
181
+
182
+
183
+
184
+
185
+
186
+
187
+
188
+
189
+
190
+
191
+
192
+
193
+
194
+
195
+
196
+
197
+
198
+
199
+
200
+
201
+
202
+
180
203
 
181
204
 
182
205
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AOT_biomaps
3
- Version: 2.9.333
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=KTexXkgmJ_3hKllZPzxJIRkrNj2plmep-xD4ANfDxao,4328
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=87tdUjYJbrNU2S3FT7pCsnB4jFEYZQFqD4o_FBKekkc,36689
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=9MNztNRZuF8zM1WfJh1GaTC8b7UDb_iuh0T0LGwycUY,10905
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.333.dist-info/METADATA,sha256=bnzNzvd1PZh5qMbP_pSbFR59oQ8YUrW4fYQmhKhY0Uo,700
46
- aot_biomaps-2.9.333.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
47
- aot_biomaps-2.9.333.dist-info/top_level.txt,sha256=6STF-lT4kaAnBHJYCripmN5mZABoHjMuY689JdiDphk,12
48
- aot_biomaps-2.9.333.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,,