AOT-biomaps 2.9.202__tar.gz → 2.9.281__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 (56) hide show
  1. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Acoustic/StructuredWave.py +1 -1
  2. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Acoustic/_mainAcoustic.py +7 -5
  3. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Experiment/Tomography.py +74 -5
  4. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Experiment/_mainExperiment.py +94 -77
  5. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/AOT_Optimizers/DEPIERRO.py +48 -13
  6. aot_biomaps-2.9.281/AOT_biomaps/AOT_Recon/AOT_Optimizers/LS.py +499 -0
  7. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/AOT_Optimizers/MAPEM.py +118 -38
  8. aot_biomaps-2.9.281/AOT_biomaps/AOT_Recon/AOT_Optimizers/MLEM.py +464 -0
  9. aot_biomaps-2.9.281/AOT_biomaps/AOT_Recon/AOT_Optimizers/PDHG.py +555 -0
  10. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/RelativeDifferences.py +10 -14
  11. aot_biomaps-2.9.281/AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_CSR.py +281 -0
  12. aot_biomaps-2.9.281/AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_SELL.py +328 -0
  13. aot_biomaps-2.9.281/AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/__init__.py +2 -0
  14. aot_biomaps-2.9.281/AOT_biomaps/AOT_Recon/AOT_biomaps_kernels.cubin +0 -0
  15. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/AlgebraicRecon.py +218 -108
  16. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/AnalyticRecon.py +26 -41
  17. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/BayesianRecon.py +33 -96
  18. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/PrimalDualRecon.py +14 -18
  19. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/ReconEnums.py +27 -2
  20. aot_biomaps-2.9.281/AOT_biomaps/AOT_Recon/ReconTools.py +489 -0
  21. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/__init__.py +1 -0
  22. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/_mainRecon.py +58 -61
  23. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/__init__.py +4 -98
  24. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps.egg-info/PKG-INFO +2 -1
  25. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps.egg-info/SOURCES.txt +5 -1
  26. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps.egg-info/requires.txt +1 -0
  27. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/PKG-INFO +2 -1
  28. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/setup.py +90 -1
  29. aot_biomaps-2.9.202/AOT_biomaps/AOT_Recon/AOT_Optimizers/LS.py +0 -103
  30. aot_biomaps-2.9.202/AOT_biomaps/AOT_Recon/AOT_Optimizers/MLEM.py +0 -262
  31. aot_biomaps-2.9.202/AOT_biomaps/AOT_Recon/AOT_Optimizers/PDHG.py +0 -221
  32. aot_biomaps-2.9.202/AOT_biomaps/AOT_Recon/ReconTools.py +0 -272
  33. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Acoustic/AcousticEnums.py +0 -0
  34. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Acoustic/AcousticTools.py +0 -0
  35. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Acoustic/FocusedWave.py +0 -0
  36. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Acoustic/IrregularWave.py +0 -0
  37. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Acoustic/PlaneWave.py +0 -0
  38. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Acoustic/__init__.py +0 -0
  39. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Experiment/Focus.py +0 -0
  40. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Experiment/__init__.py +0 -0
  41. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Optic/Absorber.py +0 -0
  42. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Optic/Laser.py +0 -0
  43. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Optic/OpticEnums.py +0 -0
  44. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Optic/__init__.py +0 -0
  45. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Optic/_mainOptic.py +0 -0
  46. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/AOT_Optimizers/__init__.py +0 -0
  47. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/Huber.py +0 -0
  48. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/Quadratic.py +0 -0
  49. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/__init__.py +0 -0
  50. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/AOT_Recon/DeepLearningRecon.py +0 -0
  51. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/Config.py +0 -0
  52. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps/Settings.py +0 -0
  53. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps.egg-info/dependency_links.txt +0 -0
  54. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/AOT_biomaps.egg-info/top_level.txt +0 -0
  55. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/README.md +0 -0
  56. {aot_biomaps-2.9.202 → aot_biomaps-2.9.281}/setup.cfg +0 -0
@@ -293,7 +293,7 @@ class StructuredWave(AcousticField):
293
293
  f"!number of bytes per pixel := 4\n"
294
294
  f"scaling factor (mm/pixel) [1] := {self.params['dx'] * 1000}\n"
295
295
  f"scaling factor (mm/pixel) [2] := {self.params['dz'] * 1000}\n"
296
- f"scaling factor (s/pixel) [3] := {1 / self.params['f_AQ']}\n"
296
+ f"scaling factor (s/pixel) [3] := {1 / self.params['f_saving']}\n"
297
297
  f"first pixel offset (mm) [1] := {self.params['Xrange'][0] * 1e3}\n"
298
298
  f"first pixel offset (mm) [2] := {self.params['Zrange'][0] * 1e3}\n"
299
299
  f"first pixel offset (s) [3] := 0\n"
@@ -268,7 +268,7 @@ class AcousticField(ABC):
268
268
  print(f"Error in save_field method: {e}")
269
269
  raise
270
270
 
271
- def load_field(self, folderPath, formatSave=FormatSave.HDR_IMG):
271
+ def load_field(self, folderPath, formatSave=FormatSave.HDR_IMG, nameBlock=None):
272
272
  """
273
273
  Load the acoustic field from a file in the specified format.
274
274
 
@@ -289,7 +289,7 @@ class AcousticField(ABC):
289
289
  raise NotImplementedError("3D KWAVE field loading is not implemented yet.")
290
290
  elif formatSave.value == FormatSave.H5.value:
291
291
  if self.params["dim"] == Dim.D2.value:
292
- self._load_field_h5(folderPath)
292
+ self._load_field_h5(folderPath,nameBlock)
293
293
  elif self.params["dim"] == Dim.D3.value:
294
294
  raise NotImplementedError("H5 KWAVE field loading is not implemented yet.")
295
295
  elif formatSave.value == FormatSave.NPY.value:
@@ -655,7 +655,7 @@ class AcousticField(ABC):
655
655
  """
656
656
  pass
657
657
 
658
- def _load_field_h5(self, filePath):
658
+ def _load_field_h5(self, filePath,nameBlock):
659
659
  """
660
660
  Load the 2D acoustic field from an H5 file.
661
661
 
@@ -666,8 +666,10 @@ class AcousticField(ABC):
666
666
  - field (numpy.ndarray): The loaded acoustic field.
667
667
  """
668
668
  try:
669
- with h5py.File(filePath+self.getName_field()+".h5", 'r') as f:
670
- self.field = f['data'][:]
669
+ if nameBlock is None:
670
+ nameBlock = 'data'
671
+ with h5py.File(os.path.join(filePath, self.getName_field()+".h5"), 'r') as f:
672
+ self.field = f[nameBlock][:]
671
673
  except Exception as e:
672
674
  print(f"Error in _load_field_h5 method: {e}")
673
675
  raise
@@ -7,6 +7,8 @@ import psutil
7
7
  import numpy as np
8
8
  import matplotlib.pyplot as plt
9
9
  from tqdm import trange
10
+ import h5py
11
+ from scipy.io import loadmat
10
12
 
11
13
  class Tomography(Experiment):
12
14
  def __init__(self, **kwargs):
@@ -47,7 +49,7 @@ class Tomography(Experiment):
47
49
  return False, f"OpticImage phantom shape {self.OpticImage.phantom.shape} does not match AcousticFields shape {self.AcousticFields[0].field.shape[1:]}."
48
50
  return True, "Experiment is correctly initialized."
49
51
 
50
- def generateAcousticFields(self, fieldDataPath=None, show_log=True):
52
+ def generateAcousticFields(self, fieldDataPath=None, show_log=True, nameBlock=None):
51
53
  """
52
54
  Generate the acoustic fields for simulation.
53
55
  Args:
@@ -57,8 +59,7 @@ class Tomography(Experiment):
57
59
  systemMatrix: A numpy array of the generated fields.
58
60
  """
59
61
  if self.TypeAcoustic.value == WaveType.StructuredWave.value:
60
- self.fieldDataPath = fieldDataPath
61
- self.AcousticFields = self._generateAcousticFields_STRUCT_CPU(fieldDataPath, show_log)
62
+ self.AcousticFields = self._generateAcousticFields_STRUCT_CPU(fieldDataPath, show_log, nameBlock)
62
63
  else:
63
64
  raise ValueError("Unsupported wave type.")
64
65
 
@@ -390,7 +391,7 @@ class Tomography(Experiment):
390
391
  return True
391
392
 
392
393
  # PRIVATE METHODS
393
- def _generateAcousticFields_STRUCT_CPU(self, fieldDataPath=None, show_log=False):
394
+ def _generateAcousticFields_STRUCT_CPU(self, fieldDataPath=None, show_log=False, nameBlock=None):
394
395
  if self.patterns is None:
395
396
  raise ValueError("patterns is not initialized. Please load or generate the active list first.")
396
397
  listAcousticFields = []
@@ -416,7 +417,7 @@ class Tomography(Experiment):
416
417
  if pathField is not None and os.path.exists(pathField):
417
418
  progress_bar.set_postfix_str(f"Loading field - {AcousticField.getName_field()} -- Memory used: {memory.percent}%")
418
419
  try:
419
- AcousticField.load_field(fieldDataPath, self.FormatSave)
420
+ AcousticField.load_field(fieldDataPath, self.FormatSave,nameBlock)
420
421
  except:
421
422
  progress_bar.set_postfix_str(f"Error loading field -> Generating field - {AcousticField.getName_field()} -- Memory used: {memory.percent}% ---- processing on {config.get_process().upper()} ----")
422
423
  AcousticField.generate_field(show_log=show_log)
@@ -434,3 +435,71 @@ class Tomography(Experiment):
434
435
  listAcousticFields.append(AcousticField)
435
436
  progress_bar.set_postfix_str("")
436
437
  return listAcousticFields
438
+
439
+ def load_experimentalAO(self, pathAO, withTumor = True, h5name='AOsignal'):
440
+ """
441
+ Load experimental AO signals from specified file paths.
442
+ Args:
443
+ path_withTumor: Path to the AO signal with tumor.
444
+ path_withoutTumor: Path to the AO signal without tumor.
445
+ """
446
+ if not os.path.exists(pathAO):
447
+ raise FileNotFoundError(f"File {pathAO} not found.")
448
+
449
+ if pathAO.endswith('.npy'):
450
+ ao_signal = np.load(pathAO)
451
+ elif pathAO.endswith('.h5'):
452
+ with h5py.File(pathAO, 'r') as f:
453
+ if h5name not in f:
454
+ raise KeyError(f"Dataset '{h5name}' not found in the HDF5 file.")
455
+ ao_signal = f[h5name][:]
456
+ elif pathAO.endswith('.mat'):
457
+ mat_data = loadmat(pathAO)
458
+ if h5name not in mat_data:
459
+ raise KeyError(f"Dataset '{h5name}' not found in the .mat file.")
460
+ ao_signal = mat_data[h5name]
461
+ elif pathAO.endswith('.hdr'):
462
+ ao_signal = self._loadAOSignal(pathAO)
463
+ else:
464
+ raise ValueError("Unsupported file format. Supported formats are: .npy, .h5, .mat, .hdr")
465
+
466
+ if withTumor:
467
+ self.AOsignal_withTumor = ao_signal
468
+ else:
469
+ self.AOsignal_withoutTumor = ao_signal
470
+
471
+ def check_experimentalAO(self, activeListPath, withTumor=True):
472
+ """
473
+ Check if the experimental AO signals are correctly initialized.
474
+ """
475
+ if withTumor:
476
+ if self.AOsignal_withTumor is None:
477
+ raise ValueError("Experimental AOsignal with tumor is not initialized. Please load the experimental AO signal with tumor first.")
478
+ else:
479
+ if self.AOsignal_withoutTumor is None:
480
+ raise ValueError("Experimental AOsignal without tumor is not initialized. Please load the experimental AO signal without tumor first.")
481
+ if self.AcousticFields is not None:
482
+ # get min time shape between all AO signals
483
+ print()
484
+
485
+ if self.AcousticFields[0].field.shape[0] > self.AOsignal_withTumor.shape[0]:
486
+ self.cutAcousticFields(max_t=self.AOsignal_withTumor.shape[0]/float(self.params.acoustic['f_saving']))
487
+ else:
488
+ for i in range(len(self.AcousticFields)):
489
+ min_time_shape = min(self.AcousticFields[i].field.shape[0])
490
+ if withTumor:
491
+ self.AOsignal_withTumor = self.AOsignal_withTumor[:min_time_shape, :]
492
+ else:
493
+ self.AOsignal_withoutTumor = self.AOsignal_withoutTumor[:min_time_shape, :]
494
+
495
+ for field in self.AcousticFields:
496
+ if activeListPath is not None:
497
+ with open(activeListPath, 'r') as file:
498
+ lines = file.readlines()
499
+ expected_name = lines[self.AcousticFields.index(field)].strip()
500
+ nameField = field.getName_field()
501
+ if nameField.startswith("field_"):
502
+ nameField = nameField[len("field_"):]
503
+ if nameField != expected_name:
504
+ raise ValueError(f"Field name {nameField} does not match the expected name {expected_name} from the active list.")
505
+ print("Experimental AO signals are correctly initialized.")
@@ -55,20 +55,10 @@ class Experiment(ABC):
55
55
  """
56
56
  pass
57
57
 
58
- def cutAcousticFields(self, max_t=None, min_t=0,useAOsignalShape=False,overWrite=False, fieldDataPath=None):
59
- if useAOsignalShape:
60
- if self.AOsignal_withTumor is not None:
61
- max_t = self.AOsignal_withTumor.shape[0] / float(self.params.acoustic['f_saving'])
62
- elif self.AOsignal_withoutTumor is not None:
63
- max_t = self.AOsignal_withoutTumor.shape[0] / float(self.params.acoustic['f_saving'])
64
- else:
65
- raise ValueError("Neither AOsignal_withTumor nor AOsignal_withoutTumor is generated. Please generate or load one of them first.")
66
- min_t = 0
67
- else:
68
- if max_t is None:
69
- raise ValueError("max_t must be provided if useAOsignalShape is False.")
70
- max_t = float(max_t)
71
- min_t = float(min_t)
58
+ def cutAcousticFields(self, max_t, min_t=0):
59
+
60
+ max_t = float(max_t)
61
+ min_t = float(min_t)
72
62
 
73
63
  min_sample = int(np.floor(min_t * float(self.params.acoustic['f_saving'])))
74
64
  max_sample = int(np.floor(max_t * float(self.params.acoustic['f_saving'])))
@@ -87,14 +77,6 @@ class Experiment(ABC):
87
77
  raise ValueError(f"Field {field.getName_field()} has an invalid shape: {field.field.shape}. Expected shape to be at least ({max_sample},).")
88
78
  self.AcousticFields[i].field = field.field[min_sample:max_sample, :, :]
89
79
 
90
- if overWrite:
91
- if fieldDataPath is None:
92
- if self.fieldDataPath is None:
93
- raise ValueError("fieldDataPath must be provided when overWrite is True.")
94
- else:
95
- fieldDataPath = self.fieldDataPath
96
- self.saveAcousticFields(fieldDataPath)
97
-
98
80
  def addNoise(self, noiseType='gaussian', noiseLvl=0.1, withTumor=True):
99
81
  """
100
82
  Ajoute du bruit (gaussien ou poisson) au signal AO sélectionné.
@@ -132,7 +114,6 @@ class Experiment(ABC):
132
114
  noiseSignals[:, i] = noisy_signal
133
115
  return noiseSignals
134
116
 
135
-
136
117
  def reduceDims(self, mode='avg'):
137
118
  """
138
119
  Réduit les dimensions T, X, Z d'un numpy array (T, X, Z) par 2 en utilisant une convolution.
@@ -173,6 +154,16 @@ class Experiment(ABC):
173
154
  for param in ['dx', 'dy', 'dz']:
174
155
  convert_and_update(self.params.general, param, lambda x: x * 2)
175
156
 
157
+ def normalizeAOsignals(self, withTumor=True):
158
+ if withTumor and self.AOsignal_withTumor is None:
159
+ raise ValueError("AO signal with tumor is not generated. Please generate it first.")
160
+ if not withTumor and self.AOsignal_withoutTumor is None:
161
+ raise ValueError("AO signal without tumor is not generated. Please generate it first.")
162
+ if withTumor:
163
+ self.AOsignal_withTumor = self.AOsignal_withTumor - np.min(self.AOsignal_withTumor)/(np.max(self.AOsignal_withTumor)-np.min(self.AOsignal_withTumor))
164
+ else:
165
+ self.AOsignal_withoutTumor = self.AOsignal_withoutTumor - np.min(self.AOsignal_withoutTumor)/(np.max(self.AOsignal_withoutTumor)-np.min(self.AOsignal_withoutTumor))
166
+
176
167
  def saveAcousticFields(self, save_directory):
177
168
  progress_bar = trange(len(self.AcousticFields), desc="Saving Acoustic Fields")
178
169
  for i in progress_bar:
@@ -249,71 +240,97 @@ class Experiment(ABC):
249
240
  return ani
250
241
 
251
242
  def generateAOsignal(self, withTumor=True, AOsignalDataPath=None):
252
- if self.AcousticFields is None:
253
- raise ValueError("AcousticFields is not initialized. Please generate the system matrix first.")
254
-
255
- if self.OpticImage is None:
256
- raise ValueError("OpticImage is not initialized. Please generate the phantom first.")
257
243
 
258
244
  if AOsignalDataPath is not None:
259
245
  if not os.path.exists(AOsignalDataPath):
260
246
  raise FileNotFoundError(f"AO file {AOsignalDataPath} not found.")
261
- AOmatrix = self._loadAOSignal(AOsignalDataPath)
262
- if AOmatrix.shape[0] != self.AcousticFields[0].field.shape[0]:
263
- print(f"AO signal shape {AOmatrix.shape} does not match the expected shape {self.AcousticFields[0].field.shape}. Generating corrected AO signal to match...")
247
+ if withTumor:
248
+ self.AOsignal_withTumor = self._loadAOSignal(AOsignalDataPath)
249
+ if self.AOsignal_withTumor.shape[0] != self.AcousticFields[0].field.shape[0]:
250
+ print(f"AO signal shape {self.AOsignal_withTumor.shape} does not match the expected shape {self.AcousticFields[0].field.shape}. Resizing Acoustic fields...")
251
+ self.cutAcousticFields(max_t=self.AOsignal_withTumor.shape[0] / float(self.params.acoustic['f_saving']), min_t=0)
264
252
  else:
265
- return AOmatrix
266
-
267
- if not all(field.field.shape == self.AcousticFields[0].field.shape for field in self.AcousticFields):
268
- minShape = min([field.field.shape[0] for field in self.AcousticFields])
269
- self.cutAcousticFields(minShape * self.params['fs_aq'])
270
- else:
271
- shape_field = self.AcousticFields[0].field.shape
272
-
273
- AOsignal = np.zeros((shape_field[0], len(self.AcousticFields)), dtype=np.float32)
274
-
275
- if withTumor:
276
- description = "Generating AO Signal with Tumor"
277
- else:
278
- description = "Generating AO Signal without Tumor"
253
+ self.AOsignal_withoutTumor = self._loadAOSignal(AOsignalDataPath)
254
+ if self.AOsignal_withoutTumor.shape[0] != self.AcousticFields[0].field.shape[0]:
255
+ print(f"AO signal shape {self.AOsignal_withoutTumor.shape} does not match the expected shape {self.AcousticFields[0].field.shape}. Resizing Acoustic fields...")
256
+ self.cutAcousticFields(max_t=self.AOsignal_withoutTumor.shape[0] / float(self.params.acoustic['f_saving']), min_t=0)
257
+ else:
258
+ if self.AcousticFields is None:
259
+ raise ValueError("AcousticFields is not initialized. Please generate the system matrix first.")
260
+
261
+ if self.OpticImage is None:
262
+ raise ValueError("OpticImage is not initialized. Please generate the phantom first.")
263
+
264
+ if not all(field.field.shape == self.AcousticFields[0].field.shape for field in self.AcousticFields):
265
+ minShape = min([field.field.shape[0] for field in self.AcousticFields])
266
+ self.cutAcousticFields(max_t=minShape * self.params['fs_aq'])
267
+ else:
268
+ shape_field = self.AcousticFields[0].field.shape
279
269
 
280
- for i in trange(len(self.AcousticFields), desc=description):
281
- for t in range(self.AcousticFields[i].field.shape[0]):
282
- if withTumor:
283
- interaction = self.OpticImage.phantom * self.AcousticFields[i].field[t, :, :]
284
- else:
285
- interaction = self.OpticImage.laser.intensity * self.AcousticFields[i].field[t, :, :]
286
- AOsignal[t, i] = np.sum(interaction)
270
+ AOsignal = np.zeros((shape_field[0], len(self.AcousticFields)), dtype=np.float32)
287
271
 
288
- if withTumor:
289
- self.AOsignal_withTumor = AOsignal
290
- else:
291
- self.AOsignal_withoutTumor = AOsignal
272
+ if withTumor:
273
+ description = "Generating AO Signal with Tumor"
274
+ else:
275
+ description = "Generating AO Signal without Tumor"
276
+
277
+ for i in trange(len(self.AcousticFields), desc=description):
278
+ for t in range(self.AcousticFields[i].field.shape[0]):
279
+ if withTumor:
280
+ interaction = self.OpticImage.phantom * self.AcousticFields[i].field[t, :, :]
281
+ else:
282
+ interaction = self.OpticImage.laser.intensity * self.AcousticFields[i].field[t, :, :]
283
+ AOsignal[t, i] = np.sum(interaction)
284
+
285
+ if withTumor:
286
+ self.AOsignal_withTumor = AOsignal
287
+ else:
288
+ self.AOsignal_withoutTumor = AOsignal
292
289
 
293
290
  @staticmethod
294
291
  def _loadAOSignal(AOsignalPath):
295
- # if extension is .cdh load .cdf file
296
292
  if AOsignalPath.endswith(".cdh"):
297
293
  with open(AOsignalPath, "r") as file:
298
294
  cdh_content = file.readlines()
299
-
300
- n_events = int([line.split(":")[1].strip() for line in cdh_content if "Number of events" in line][0])
301
- n_acquisitions = int([line.split(":")[1].strip() for line in cdh_content if "Number of acquisitions per event" in line][0])
302
-
303
- AOsignal_matrix = np.zeros((n_events, n_acquisitions), dtype=np.float32)
304
-
305
- with open(AOsignalPath.replace(".cdh", ".cdf"), "rb") as file:
306
- for event in range(n_events):
307
- num_elements = int([line.split(":")[1].strip() for line in cdh_content if "Number of US transducers" in line][0])
308
- hex_length = (num_elements + 3) // 4
309
- file.read(hex_length // 2)
310
-
311
- signal = np.frombuffer(file.read(n_acquisitions * 4), dtype=np.float32)
312
- AOsignal_matrix[event, :] = signal
313
-
314
- return AOsignal_matrix
315
- if AOsignalPath.endswith(".npy"):
316
- return np.load(AOsignalPath)
295
+
296
+ cdf_path = AOsignalPath.replace(".cdh", ".cdf")
297
+
298
+ # Extraire les paramètres depuis le fichier .cdh
299
+ n_scans = int([line.split(":")[1].strip() for line in cdh_content if "Number of events" in line][0])
300
+ n_acquisitions_per_event = int([line.split(":")[1].strip() for line in cdh_content if "Number of acquisitions per event" in line][0])
301
+ num_elements = int([line.split(":")[1].strip() for line in cdh_content if "Number of US transducers" in line][0])
302
+
303
+ # Initialisation des structures
304
+ AO_signal = np.zeros((n_acquisitions_per_event, n_scans), dtype=np.float32)
305
+ active_lists = []
306
+ angles = []
307
+
308
+ # Lecture du fichier binaire
309
+ with open(cdf_path, "rb") as file:
310
+ for j in trange(n_scans, desc="Lecture des événements"):
311
+ # Lire l'activeList : 48 caractères hex = 24 bytes
312
+ active_list_bytes = file.read(24)
313
+ active_list_hex = active_list_bytes.hex()
314
+ active_lists.append(active_list_hex)
315
+
316
+ # Lire l'angle (1 byte signé)
317
+ angle_byte = file.read(1)
318
+ angle = np.frombuffer(angle_byte, dtype=np.int8)[0]
319
+ angles.append(angle)
320
+
321
+ # Lire le signal AO (float32)
322
+ data = np.frombuffer(file.read(n_acquisitions_per_event * 4), dtype=np.float32)
323
+ if len(data) != n_acquisitions_per_event:
324
+ raise ValueError(f"Erreur à l'événement {j} : attendu {n_acquisitions_per_event}, obtenu {len(data)}")
325
+ AO_signal[:, j] = data
326
+
327
+ return AO_signal
328
+
329
+
330
+ elif AOsignalPath.endswith(".npy"):
331
+ return np.load(AOsignalPath) # Supposé déjà au bon format
332
+ else:
333
+ raise ValueError("Format de fichier non supporté. Utilisez .cdh/.cdf ou .npy.")
317
334
 
318
335
  def saveAOsignals_Castor(self, save_directory, withTumor=True):
319
336
  if withTumor:
@@ -341,10 +358,10 @@ class Experiment(ABC):
341
358
  header_content = (
342
359
  f"Data filename: {'AOSignals_withTumor.cdf' if withTumor else 'AOSignals_withoutTumor.cdf'}\n"
343
360
  f"Number of events: {nScan}\n"
344
- f"Number of acquisitions per event: {AO_signal.shape[1]}\n"
361
+ f"Number of acquisitions per event: {AO_signal.shape[0]}\n"
345
362
  f"Start time (s): 0\n"
346
363
  f"Duration (s): 1\n"
347
- f"Acquisition frequency (Hz): {1/self.AcousticFields[0].kgrid.dt}\n"
364
+ f"Acquisition frequency (Hz): {self.params.acoustic['f_saving']}\n"
348
365
  f"Data mode: histogram\n"
349
366
  f"Data type: AOT\n"
350
367
  f"Number of US transducers: {self.params.acoustic['num_elements']}"
@@ -1,6 +1,8 @@
1
1
  from AOT_biomaps.AOT_Recon.ReconEnums import PotentialType
2
- from AOT_biomaps.AOT_Recon.ReconTools import _build_adjacency_sparse
2
+ from AOT_biomaps.AOT_Recon.ReconTools import _build_adjacency_sparse, calculate_memory_requirement, check_gpu_memory
3
3
  from AOT_biomaps.Config import config
4
+
5
+ import warnings
4
6
  import numpy as np
5
7
  import torch
6
8
  from tqdm import trange
@@ -11,9 +13,42 @@ if config.get_process() == 'gpu':
11
13
  except ImportError:
12
14
  raise ImportError("torch_scatter and torch_sparse are required for GPU processing. Please install them using 'pip install torch-scatter torch-sparse' with correct link (follow instructions https://github.com/LucasDuclos/AcoustoOpticTomography/edit/main/README.md).")
13
15
 
14
- def _DEPIERRO_GPU(SMatrix, y, numIterations, beta, sigma, isSavingEachIteration, withTumor, max_saves=5000):
15
- # Initialisation du device
16
- device = torch.device(f"cuda:{config.select_best_gpu()}")
16
+ def DEPIERRO(
17
+ SMatrix,
18
+ y,
19
+ numIterations,
20
+ beta,
21
+ sigma,
22
+ isSavingEachIteration,
23
+ withTumor,
24
+ max_saves,
25
+ show_logs):
26
+ """
27
+ This method implements the DEPIERRO algorithm using either CPU or single-GPU PyTorch acceleration.
28
+ Multi-GPU and Multi-CPU modes are not implemented for this algorithm.
29
+ """
30
+ try:
31
+ tumor_str = "WITH" if withTumor else "WITHOUT"
32
+ # Auto-select device and method
33
+ if device is None:
34
+ if torch.cuda.is_available() and check_gpu_memory(config.select_best_gpu(), calculate_memory_requirement(SMatrix, y), show_logs=show_logs):
35
+ device = torch.device(f"cuda:{config.select_best_gpu()}")
36
+ use_gpu = True
37
+ else:
38
+ device = torch.device("cpu")
39
+ use_gpu = False
40
+ else:
41
+ use_gpu = device.type == "cuda"
42
+ # Dispatch to the appropriate implementation
43
+ if use_gpu:
44
+ return _DEPIERRO_GPU(SMatrix, y, numIterations, beta, sigma, isSavingEachIteration, tumor_str, device, max_saves, show_logs)
45
+ else:
46
+ return _DEPIERRO_CPU(SMatrix, y, numIterations, beta, sigma, isSavingEachIteration, tumor_str, device, max_saves, show_logs)
47
+ except Exception as e:
48
+ print(f"Error in MLEM: {type(e).__name__}: {e}")
49
+ return None, None
50
+
51
+ def _DEPIERRO_GPU(SMatrix, y, numIterations, beta, sigma, isSavingEachIteration, tumor_str, device, max_saves, show_logs=True):
17
52
  # Conversion des données en tenseurs PyTorch (float64)
18
53
  A_matrix_torch = torch.tensor(SMatrix, dtype=torch.float64, device=device)
19
54
  y_torch = torch.tensor(y, dtype=torch.float64, device=device)
@@ -33,7 +68,7 @@ def _DEPIERRO_GPU(SMatrix, y, numIterations, beta, sigma, isSavingEachIteration,
33
68
  # Construction de la matrice d'adjacence
34
69
  adj_index, adj_values = _build_adjacency_sparse(Z, X, device=device, dtype=torch.float64)
35
70
  # Description pour la barre de progression
36
- description = f"AOT-BioMaps -- Bayesian Reconstruction Tomography: DE PIERRO (Sparse QUADRATIC β:{beta:.4f}, σ:{sigma:.4f}) ---- {'WITH' if withTumor else 'WITHOUT'} TUMOR ---- processing on single GPU no.{torch.cuda.current_device()}"
71
+ description = f"AOT-BioMaps -- Bayesian Reconstruction Tomography: DE PIERRO (Sparse QUADRATIC β:{beta:.4f}, σ:{sigma:.4f}) ---- {tumor_str} TUMOR ---- processing on single GPU no.{torch.cuda.current_device()}"
37
72
  # Configuration pour la sauvegarde des itérations
38
73
  saved_indices = [0]
39
74
 
@@ -47,7 +82,8 @@ def _DEPIERRO_GPU(SMatrix, y, numIterations, beta, sigma, isSavingEachIteration,
47
82
  save_indices.append(numIterations - 1)
48
83
 
49
84
  # Boucle principale MAP-EM
50
- for it in trange(numIterations, desc=description):
85
+ iterator = trange(numIterations, desc=description) if show_logs else range(numIterations)
86
+ for it in iterator:
51
87
  theta_p = matrix_theta_torch[-1]
52
88
  theta_p_flat = theta_p.reshape(-1)
53
89
  # Étape 1 : Projection avant
@@ -92,10 +128,8 @@ def _DEPIERRO_GPU(SMatrix, y, numIterations, beta, sigma, isSavingEachIteration,
92
128
  else:
93
129
  return matrix_theta_torch[-1].cpu().numpy(), None
94
130
 
95
- def _DEPIERRO_CPU(SMatrix, y, Omega, numIterations, beta, sigma, isSavingEachIteration, withTumor, max_saves=5000):
131
+ def _DEPIERRO_CPU(SMatrix, y, numIterations, beta, sigma, isSavingEachIteration, tumor_str, device, max_saves, show_logs=True):
96
132
  try:
97
- if Omega != PotentialType.QUADRATIC:
98
- raise ValueError("Depierro95 optimizer only supports QUADRATIC potential function.")
99
133
  if beta is None or sigma is None:
100
134
  raise ValueError("Depierro95 optimizer requires beta and sigma parameters.")
101
135
 
@@ -122,9 +156,10 @@ def _DEPIERRO_CPU(SMatrix, y, Omega, numIterations, beta, sigma, isSavingEachIte
122
156
  if save_indices[-1] != numIterations - 1:
123
157
  save_indices.append(numIterations - 1)
124
158
 
125
- description = f"AOT-BioMaps -- Bayesian Reconstruction Tomography: DE PIERRO (Sparse QUADRATIC β:{beta:.4f}, σ:{sigma:.4f}) ---- {'WITH' if withTumor else 'WITHOUT'} TUMOR ---- processing on single CPU ----"
159
+ description = f"AOT-BioMaps -- Bayesian Reconstruction Tomography: DE PIERRO (Sparse QUADRATIC β:{beta:.4f}, σ:{sigma:.4f}) ---- {tumor_str} TUMOR ---- processing on single CPU ----"
126
160
 
127
- for p in trange(numIterations, desc=description):
161
+ iterator = trange(numIterations, desc=description) if show_logs else range(numIterations)
162
+ for it in iterator:
128
163
  theta_p = matrix_theta[-1]
129
164
  theta_p_flat = theta_p.reshape(-1)
130
165
  q_flat = np.dot(A_flat, theta_p_flat)
@@ -143,9 +178,9 @@ def _DEPIERRO_CPU(SMatrix, y, Omega, numIterations, beta, sigma, isSavingEachIte
143
178
  theta_p_plus_1_flat = np.clip(theta_p_plus_1_flat, a_min=0, a_max=None)
144
179
  theta_next = theta_p_plus_1_flat.reshape(Z, X)
145
180
  matrix_theta[-1] = theta_next
146
- if isSavingEachIteration and p in save_indices:
181
+ if isSavingEachIteration and it in save_indices:
147
182
  I_reconMatrix.append(theta_next.copy())
148
- saved_indices.append(p)
183
+ saved_indices.append(it)
149
184
 
150
185
  if isSavingEachIteration:
151
186
  return I_reconMatrix, saved_indices