AOT-biomaps 2.9.319__tar.gz → 2.9.332__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (53) hide show
  1. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Acoustic/AcousticTools.py +0 -2
  2. aot_biomaps-2.9.332/AOT_biomaps/AOT_Experiment/ExperimentTools.py +79 -0
  3. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Experiment/Tomography.py +130 -9
  4. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AnalyticRecon.py +93 -0
  5. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/ReconTools.py +46 -4
  6. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/__init__.py +14 -1
  7. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps.egg-info/PKG-INFO +1 -1
  8. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps.egg-info/SOURCES.txt +1 -0
  9. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/PKG-INFO +1 -1
  10. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/setup.py +14 -1
  11. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Acoustic/AcousticEnums.py +0 -0
  12. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Acoustic/FocusedWave.py +0 -0
  13. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Acoustic/IrregularWave.py +0 -0
  14. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Acoustic/PlaneWave.py +0 -0
  15. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Acoustic/StructuredWave.py +0 -0
  16. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Acoustic/__init__.py +0 -0
  17. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Acoustic/_mainAcoustic.py +0 -0
  18. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Experiment/Focus.py +0 -0
  19. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Experiment/__init__.py +0 -0
  20. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Experiment/_mainExperiment.py +0 -0
  21. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Optic/Absorber.py +0 -0
  22. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Optic/Laser.py +0 -0
  23. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Optic/OpticEnums.py +0 -0
  24. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Optic/__init__.py +0 -0
  25. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Optic/_mainOptic.py +0 -0
  26. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_Optimizers/DEPIERRO.py +0 -0
  27. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_Optimizers/LS.py +0 -0
  28. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_Optimizers/MAPEM.py +0 -0
  29. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_Optimizers/MLEM.py +0 -0
  30. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_Optimizers/PDHG.py +0 -0
  31. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_Optimizers/__init__.py +0 -0
  32. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/Huber.py +0 -0
  33. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/Quadratic.py +0 -0
  34. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/RelativeDifferences.py +0 -0
  35. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/__init__.py +0 -0
  36. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_CSR.py +0 -0
  37. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_SELL.py +0 -0
  38. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/__init__.py +0 -0
  39. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_biomaps_kernels.cubin +0 -0
  40. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AlgebraicRecon.py +0 -0
  41. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/BayesianRecon.py +0 -0
  42. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/DeepLearningRecon.py +0 -0
  43. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/PrimalDualRecon.py +0 -0
  44. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/ReconEnums.py +0 -0
  45. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/__init__.py +0 -0
  46. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/_mainRecon.py +0 -0
  47. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/Config.py +0 -0
  48. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps/Settings.py +0 -0
  49. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps.egg-info/dependency_links.txt +0 -0
  50. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps.egg-info/requires.txt +0 -0
  51. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/AOT_biomaps.egg-info/top_level.txt +0 -0
  52. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/README.md +0 -0
  53. {aot_biomaps-2.9.319 → aot_biomaps-2.9.332}/setup.cfg +0 -0
@@ -157,8 +157,6 @@ def calculate_envelope_squared(field):
157
157
  print(f"Erreur dans calculate_envelope_squared: {e}")
158
158
  raise
159
159
 
160
-
161
-
162
160
  def getPattern(pathFile):
163
161
  """
164
162
  Get the pattern from a file path.
@@ -0,0 +1,79 @@
1
+ import numpy as np
2
+
3
+ def calc_mat_os(xm, fx, dx, bool_active_list, signal_type):
4
+ 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)
14
+ 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]))
30
+
31
+ def convert_to_hex_list(matrix):
32
+ """
33
+ Convertit une matrice binaire en liste de strings hexa (paquets de 4 bits).
34
+ Chaque colonne devient une chaîne de caractères.
35
+ """
36
+ n_els, n_scans = matrix.shape
37
+
38
+ # 1. Padding pour s'assurer que n_els est multiple de 4
39
+ remainder = n_els % 4
40
+ if remainder != 0:
41
+ padding = np.zeros((4 - remainder, n_scans))
42
+ matrix = np.vstack([matrix, padding])
43
+
44
+ # 2. Reshape pour isoler des blocs de 4 bits (nibbles)
45
+ # Shape résultante : (Nombre de blocs, 4 bits, Nombre de scans)
46
+ blocks = matrix.reshape(-1, 4, n_scans)
47
+
48
+ # 3. Calcul de la valeur décimale de chaque bloc (0 à 15)
49
+ # On considère le premier élément comme le bit de poids faible (LSB)
50
+ weights = np.array([1, 2, 4, 8]).reshape(1, 4, 1)
51
+ dec_values = np.sum(blocks * weights, axis=1).astype(int)
52
+
53
+ # 4. Conversion en caractères Hexadécimaux
54
+ # On définit la table de conversion pour la rapidité
55
+ hex_table = np.array(list("0123456789abcdef"))
56
+ hex_matrix = hex_table[dec_values]
57
+
58
+ # 5. Assemblage des chaînes (de l'élément N vers 0 pour l'ordre Shift Register standard)
59
+ return ["".join(hex_matrix[::-1, col]) for col in range(n_scans)]
60
+
61
+ def hex_to_binary_profile(hex_string, n_piezos=192):
62
+ hex_string = hex_string.strip().replace(" ", "").replace("\n", "")
63
+ if set(hex_string.lower()) == {'f'}:
64
+ return np.ones(n_piezos, dtype=int)
65
+
66
+ try:
67
+ n_char = len(hex_string)
68
+ n_bits = n_char * 4
69
+ binary_str = bin(int(hex_string, 16))[2:].zfill(n_bits)
70
+ if len(binary_str) < n_piezos:
71
+ # Tronquer/padder en fonction de la taille réelle de la sonde
72
+ binary_str = binary_str.ljust(n_piezos, '0')
73
+ elif len(binary_str) > n_piezos:
74
+ binary_str = binary_str[:n_piezos]
75
+ return np.array([int(b) for b in binary_str])
76
+ except ValueError:
77
+ return np.zeros(n_piezos, dtype=int)
78
+
79
+
@@ -2,18 +2,43 @@ from ._mainExperiment import Experiment
2
2
  from AOT_biomaps.AOT_Acoustic.AcousticEnums import WaveType
3
3
  from AOT_biomaps.AOT_Acoustic.StructuredWave import StructuredWave
4
4
  from AOT_biomaps.Config import config
5
+ from AOT_biomaps.AOT_Experiment.ExperimentTools import calc_mat_os, convert_to_hex_list, hex_to_binary_profile
5
6
  import os
6
7
  import psutil
7
8
  import numpy as np
8
9
  import matplotlib.pyplot as plt
9
10
  from tqdm import trange
10
11
  import h5py
11
- from scipy.io import loadmat
12
+ from scipy.io import loadmat, savemat
13
+
12
14
 
13
15
  class Tomography(Experiment):
14
16
  def __init__(self, **kwargs):
15
17
  super().__init__(**kwargs)
16
18
  self.patterns = None
19
+ self.theta = []
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']))))
17
42
 
18
43
  # PUBLIC METHODS
19
44
  def check(self):
@@ -273,7 +298,7 @@ class Tomography(Experiment):
273
298
  line = f"({coords}, {angles})\n"
274
299
  file.write(line)
275
300
 
276
- def generateActiveList(self, N):
301
+ def generateActiveList(self, N = None, decimations = None, angles = None):
277
302
  """
278
303
  Génère une liste de patterns d'activation équilibrés et réguliers.
279
304
  Args:
@@ -281,11 +306,31 @@ class Tomography(Experiment):
281
306
  Returns:
282
307
  list: Liste de strings au format "hex_angle".
283
308
  """
284
- if N < 1:
285
- raise ValueError("N must be a positive integer.")
286
- self.patterns = self._generate_patterns(N)
287
- if not self._check_patterns(self.patterns):
288
- raise ValueError("Generated patterns failed validation.")
309
+ if decimations is not None and angles is not None:
310
+ self.patterns = self._genereate_patterns_from_decimations(decimations, angles)
311
+ elif N is not None and N > 1:
312
+ self.patterns = self._generate_patterns(N)
313
+ if not self._check_patterns(self.patterns):
314
+ raise ValueError("Generated patterns failed validation.")
315
+ else:
316
+ raise ValueError("Either N (>=2) or both decimations and angles must be provided for pattern generation.")
317
+
318
+
319
+ 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
+
333
+ savemat(filePath, {'data': self.AOsignal_withTumor, 'thetas': self.theta, 'decimations': self.decimations, 'ActiveList' : ActiveList, 'DelayLaw': DelayLaw})
289
334
 
290
335
  def selectAngles(self, angles):
291
336
 
@@ -337,7 +382,77 @@ class Tomography(Experiment):
337
382
  self.AOsignal_withoutTumor = self.AOsignal_withoutTumor[:, indices]
338
383
  self.AcousticFields = newAcousticFields
339
384
 
340
- def _generate_patterns(self, N):
385
+ 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)
388
+
389
+ angles = np.sort(angles)
390
+ decimations = np.sort(decimations)
391
+
392
+ num_elements = self.params.acoustic['num_elements']
393
+ dx_mm = self.params.general['dx'] * 1e3
394
+
395
+ # --- Calcul du nombre de Scans ---
396
+ if 0 in decimations:
397
+ Nscans = 4 * angles.shape[0] * (decimations.shape[0] - 1) + angles.shape[0]
398
+ offSet = angles.shape[0]
399
+ else:
400
+ Nscans = 4 * angles.shape[0] * decimations.shape[0]
401
+ offSet = 0
402
+
403
+ ActiveLIST = np.ones((num_elements, Nscans))
404
+ Xm = np.arange(1, num_elements + 1) * dx_mm
405
+ dFx = 1 / (num_elements * dx_mm)
406
+
407
+ # On traite séparément les décimations non nulles pour la boucle
408
+ active_decimations = decimations[decimations != 0]
409
+
410
+ 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)
417
+
418
+ fx = dFx * active_decimations[i_dec]
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]
426
+
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]
432
+
433
+ # --- Conversion au format attendu ---
434
+ # 1. On convertit toute la matrice en liste de strings Hexa
435
+ hexa_list = convert_to_hex_list(ActiveLIST)
436
+
437
+ # 2. Fonction interne de formatage d'angle (pour coller à votre ancien code)
438
+ def format_angle(a):
439
+ return f"{'1' if a < 0 else '0'}{abs(a):02d}"
440
+
441
+ # 3. Construction de la liste de dictionnaires
442
+ patterns = []
443
+ print(f"Generating {Nscans} patterns from decimations and angles...")
444
+ 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
+ angle_val = angles[i % len(angles)]
448
+
449
+ hex_pattern = hexa_list[i]
450
+ pair = f"{hex_pattern}_{format_angle(angle_val)}"
451
+ patterns.append({"fileName": pair})
452
+
453
+ return patterns
454
+
455
+ def _generate_patterns(self, N,angles = None):
341
456
  def format_angle(a):
342
457
  return f"{'1' if a < 0 else '0'}{abs(a):02d}"
343
458
 
@@ -348,7 +463,13 @@ class Tomography(Experiment):
348
463
  return hex_string
349
464
 
350
465
  num_elements = self.params.acoustic['num_elements']
351
- angle_choices = list(range(-20, 21))
466
+ if angles is None:
467
+ angle_choices = list(range(-20, 21))
468
+ else:
469
+ # convert np.array to list if necessary
470
+ if isinstance(angles, np.ndarray):
471
+ angles = angles.tolist()
472
+ angle_choices = angles
352
473
 
353
474
  # 1. Trouver TOUS les diviseurs PAIRS de num_elements (y compris num_elements)
354
475
  divs = [d for d in range(2, num_elements + 1) if num_elements % d == 0 and d % 2 == 0]
@@ -1,8 +1,13 @@
1
1
  from ._mainRecon import Recon
2
2
  from .ReconEnums import ReconType, AnalyticType, ProcessType
3
+ from AOT_biomaps.AOT_Experiment.Tomography import hex_to_binary_profile
4
+ from .ReconTools import get_phase_deterministic
3
5
 
4
6
  import numpy as np
5
7
  from tqdm import trange
8
+ import torch
9
+ import tqdm
10
+
6
11
 
7
12
  class AnalyticRecon(Recon):
8
13
  def __init__(self, analyticType, **kwargs):
@@ -10,6 +15,94 @@ class AnalyticRecon(Recon):
10
15
  self.reconType = ReconType.Analytic
11
16
  self.analyticType = analyticType
12
17
 
18
+
19
+
20
+ def parse_and_demodulate(self, withTumor=True):
21
+
22
+ if withTumor:
23
+ AOsignal = self.experiment.AOsignal_withTumor
24
+ else:
25
+ AOsignal = self.experiment.AOsignal_withoutTumor
26
+ delta_x = self.params.acoustic['dx'] # en m
27
+ n_piezos = self.params.acoustic['num_elements']
28
+ demodulated_data = {}
29
+ structured_buffer = {}
30
+
31
+ for i in tqdm(range(len(self.experiment.AcousticFields)), desc="Demodulating AO signals"):
32
+ label = self.experiment.AcousticFields[i].getName_field()
33
+
34
+ parts = label.split("_")
35
+ hex_pattern = parts[0]
36
+ angle_code = parts[-1]
37
+
38
+ # Angle
39
+ if angle_code.startswith("1"):
40
+ angle_deg = -int(angle_code[1:])
41
+ else:
42
+ angle_deg = int(angle_code)
43
+ angle_rad = np.deg2rad(angle_deg)
44
+
45
+ # Onde Plane (f_s = 0)
46
+ if set(hex_pattern.lower().replace(" ", "")) == {'f'}:
47
+ fs_key = 0.0 # fs_key est en mm^-1 (0.0 mm^-1)
48
+ demodulated_data[(fs_key, angle_rad)] = np.array(AOsignal[i])
49
+ continue
50
+
51
+ # Onde Structurée
52
+ profile = hex_to_binary_profile(hex_pattern, n_piezos)
53
+
54
+ # Calcul FS (Fréquence de Structuration)
55
+ ft_prof = np.fft.fft(profile)
56
+ # On regarde uniquement la partie positive non DC
57
+ idx_max = np.argmax(np.abs(ft_prof[1:len(profile)//2])) + 1
58
+ freqs = np.fft.fftfreq(len(profile), d=delta_x)
59
+
60
+ # freqs est en m^-1 car delta_x est en mètres.
61
+ fs_m_inv = abs(freqs[idx_max])
62
+
63
+ # *** CORRECTION 1: Conversion de f_s en mm^-1 (mm^-1 est utilisé dans iRadon) ***
64
+ fs_key = fs_m_inv / 1000.0 # Fréquence spatiale en mm^-1
65
+
66
+
67
+ if fs_key == 0: continue
68
+
69
+ # Calcul de la Phase (Shift)
70
+ phase = get_phase_deterministic(profile)
71
+
72
+ # Stockage par (fs, theta) et phase
73
+ key = (fs_key, angle_rad)
74
+ if key not in structured_buffer:
75
+ structured_buffer[key] = {}
76
+
77
+ # La moyenne est nécessaire si plusieurs acquisitions ont la même phase (pour le SNR)
78
+ if phase in structured_buffer[key]:
79
+ structured_buffer[key][phase] = (structured_buffer[key][phase] + np.array(AOsignal[i])) / 2
80
+ else:
81
+ structured_buffer[key][phase] = np.array(AOsignal[i])
82
+
83
+
84
+
85
+ for (fs, theta), phases in structured_buffer.items():
86
+ s0 = phases.get(0.0, 0)
87
+ s_pi_2 = phases.get(np.pi/2, 0)
88
+ s_pi = phases.get(np.pi, 0)
89
+ s_3pi_2 = phases.get(3*np.pi/2, 0)
90
+
91
+ # Assurer que les zéros sont des vecteurs de la bonne taille
92
+ example = next(val for val in phases.values() if not isinstance(val, int))
93
+ if isinstance(s0, int): s0 = np.zeros_like(example)
94
+ if isinstance(s_pi, int): s_pi = np.zeros_like(example)
95
+ if isinstance(s_pi_2, int): s_pi_2 = np.zeros_like(example)
96
+ if isinstance(s_3pi_2, int): s_3pi_2 = np.zeros_like(example)
97
+
98
+ real = s0 - s_pi
99
+ imag = s_pi_2 - s_3pi_2
100
+
101
+
102
+ demodulated_data[(fs, theta)] = (real - 1j * imag) / (2/np.pi)
103
+
104
+ return demodulated_data
105
+
13
106
  def run(self, processType = ProcessType.PYTHON, withTumor= True):
14
107
  """
15
108
  This method is a placeholder for the analytic reconstruction process.
@@ -6,6 +6,7 @@ import pycuda.driver as drv
6
6
  from numba import njit, prange
7
7
  from torch_sparse import coalesce
8
8
  from scipy.signal.windows import hann
9
+ from itertools import groupby
9
10
 
10
11
  def load_recon(hdr_path):
11
12
  """
@@ -221,7 +222,6 @@ def calculate_memory_requirement(SMatrix, y):
221
222
  # --- 3. Final Result ---
222
223
  return total_bytes / (1024 ** 3)
223
224
 
224
-
225
225
  def check_gpu_memory(device_index, required_memory, show_logs=True):
226
226
  """Check if enough memory is available on the specified GPU."""
227
227
  free_memory, _ = torch.cuda.mem_get_info(f"cuda:{device_index}")
@@ -252,7 +252,6 @@ def _backward_projection(SMatrix, e_p, c_p):
252
252
  total += SMatrix[_t, _z, _x, _n] * e_p[_t, _n]
253
253
  c_p[_z, _x] = total
254
254
 
255
-
256
255
  def _build_adjacency_sparse(Z, X, device, corner=(0.5 - np.sqrt(2) / 4) / np.sqrt(2), face=0.5 - np.sqrt(2) / 4,dtype=torch.float32):
257
256
  rows, cols, weights = [], [], []
258
257
  for z in range(Z):
@@ -273,7 +272,6 @@ def _build_adjacency_sparse(Z, X, device, corner=(0.5 - np.sqrt(2) / 4) / np.sqr
273
272
  index, values = coalesce(index, values, m=Z*X, n=Z*X)
274
273
  return index, values
275
274
 
276
-
277
275
  def power_method(P, PT, data, Z, X, n_it=10):
278
276
  x = torch.randn(Z * X, device=data.device)
279
277
  x = x / torch.norm(x)
@@ -486,4 +484,48 @@ def power_method_estimate_L__SELL(SMatrix, stream, n_it=20, block_size=256):
486
484
  g.free()
487
485
  except:
488
486
  pass
489
- return max(L_sq, 1e-6)
487
+ return max(L_sq, 1e-6)
488
+
489
+ def get_phase_deterministic(profile):
490
+ """
491
+ Détermine la phase en se basant sur la valeur initiale (0 ou 1) et l'état
492
+ de décalage (is_shifted) de la séquence binaire.
493
+
494
+ ATTENTION: Cette fonction est conservée mais la logique est souvent simplifiée
495
+ en pratique si les labels garantissent les phases 0, pi/2, pi, 3pi/2.
496
+ """
497
+ runs = [(k, sum(1 for _ in g)) for k, g in groupby(profile)]
498
+ if not runs: return 0.0
499
+
500
+ nominal_half_period = max([r[1] for r in runs])
501
+ if nominal_half_period == 0: return 0.0
502
+
503
+ first_val = runs[0][0] # 0 ou 1
504
+ first_len = runs[0][1]
505
+ # Détection de cycle 50%
506
+ is_shifted = (0.3 < first_len / nominal_half_period < 0.7)
507
+
508
+ # --- LOGIQUE DE MAPPAGE DE PHASE SIMPLIFIÉE (idx 1 à 4) ---
509
+
510
+ if first_val == 0:
511
+ if is_shifted:
512
+ idx = 3 # C1/C3 décalé (phi_1 ou phi_3)
513
+ else:
514
+ idx = 4 # C2/C4 non décalé
515
+ else: # first_val == 1
516
+ if is_shifted:
517
+ idx = 1 # C1/C3 décalé (phi_1 ou phi_3)
518
+ else:
519
+ idx = 2 # C2/C4 non décalé
520
+
521
+ # On utilise les phases de quadrature 0, pi/2, pi, 3pi/2
522
+ if idx == 1:
523
+ phase = 0
524
+ elif idx == 2 :
525
+ phase = np.pi/2
526
+ elif idx == 3 :
527
+ phase = np.pi
528
+ elif idx == 4 :
529
+ phase = 3*np.pi/2
530
+
531
+ return phase
@@ -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.319'
88
+ __version__ = '2.9.332'
89
89
  __process__ = config.get_process()
90
90
 
91
91
  def initialize(process=None):
@@ -169,6 +169,19 @@ def initialize(process=None):
169
169
 
170
170
 
171
171
 
172
+
173
+
174
+
175
+
176
+
177
+
178
+
179
+
180
+
181
+
182
+
183
+
184
+
172
185
 
173
186
 
174
187
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AOT_biomaps
3
- Version: 2.9.319
3
+ Version: 2.9.332
4
4
  Summary: Acousto-Optic Tomography
5
5
  Home-page: https://github.com/LucasDuclos/AcoustoOpticTomography
6
6
  Author: Lucas Duclos
@@ -16,6 +16,7 @@ AOT_biomaps/AOT_Acoustic/PlaneWave.py
16
16
  AOT_biomaps/AOT_Acoustic/StructuredWave.py
17
17
  AOT_biomaps/AOT_Acoustic/__init__.py
18
18
  AOT_biomaps/AOT_Acoustic/_mainAcoustic.py
19
+ AOT_biomaps/AOT_Experiment/ExperimentTools.py
19
20
  AOT_biomaps/AOT_Experiment/Focus.py
20
21
  AOT_biomaps/AOT_Experiment/Tomography.py
21
22
  AOT_biomaps/AOT_Experiment/__init__.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AOT_biomaps
3
- Version: 2.9.319
3
+ Version: 2.9.332
4
4
  Summary: Acousto-Optic Tomography
5
5
  Home-page: https://github.com/LucasDuclos/AcoustoOpticTomography
6
6
  Author: Lucas Duclos
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='AOT_biomaps',
5
- version='2.9.319',
5
+ version='2.9.332',
6
6
  packages=find_packages(),
7
7
  include_package_data=True,
8
8
 
@@ -327,6 +327,19 @@ setup(
327
327
 
328
328
 
329
329
 
330
+
331
+
332
+
333
+
334
+
335
+
336
+
337
+
338
+
339
+
340
+
341
+
342
+
330
343
 
331
344
 
332
345
 
File without changes
File without changes