AOT-biomaps 2.9.176__py3-none-any.whl → 2.9.300__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.

Files changed (29) hide show
  1. AOT_biomaps/AOT_Acoustic/StructuredWave.py +2 -2
  2. AOT_biomaps/AOT_Acoustic/_mainAcoustic.py +11 -6
  3. AOT_biomaps/AOT_Experiment/Tomography.py +74 -4
  4. AOT_biomaps/AOT_Experiment/_mainExperiment.py +95 -55
  5. AOT_biomaps/AOT_Recon/AOT_Optimizers/DEPIERRO.py +48 -13
  6. AOT_biomaps/AOT_Recon/AOT_Optimizers/LS.py +406 -13
  7. AOT_biomaps/AOT_Recon/AOT_Optimizers/MAPEM.py +118 -38
  8. AOT_biomaps/AOT_Recon/AOT_Optimizers/MLEM.py +390 -102
  9. AOT_biomaps/AOT_Recon/AOT_Optimizers/PDHG.py +443 -12
  10. AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/RelativeDifferences.py +10 -14
  11. AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_CSR.py +274 -0
  12. AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_SELL.py +331 -0
  13. AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/__init__.py +2 -0
  14. AOT_biomaps/AOT_Recon/AOT_biomaps_kernels.cubin +0 -0
  15. AOT_biomaps/AOT_Recon/AlgebraicRecon.py +259 -153
  16. AOT_biomaps/AOT_Recon/AnalyticRecon.py +27 -42
  17. AOT_biomaps/AOT_Recon/BayesianRecon.py +84 -151
  18. AOT_biomaps/AOT_Recon/DeepLearningRecon.py +1 -1
  19. AOT_biomaps/AOT_Recon/PrimalDualRecon.py +162 -102
  20. AOT_biomaps/AOT_Recon/ReconEnums.py +27 -2
  21. AOT_biomaps/AOT_Recon/ReconTools.py +229 -12
  22. AOT_biomaps/AOT_Recon/__init__.py +1 -0
  23. AOT_biomaps/AOT_Recon/_mainRecon.py +72 -58
  24. AOT_biomaps/__init__.py +4 -53
  25. {aot_biomaps-2.9.176.dist-info → aot_biomaps-2.9.300.dist-info}/METADATA +2 -1
  26. aot_biomaps-2.9.300.dist-info/RECORD +47 -0
  27. aot_biomaps-2.9.176.dist-info/RECORD +0 -43
  28. {aot_biomaps-2.9.176.dist-info → aot_biomaps-2.9.300.dist-info}/WHEEL +0 -0
  29. {aot_biomaps-2.9.176.dist-info → aot_biomaps-2.9.300.dist-info}/top_level.txt +0 -0
@@ -292,8 +292,8 @@ class StructuredWave(AcousticField):
292
292
  f"!number format := short float\n"
293
293
  f"!number of bytes per pixel := 4\n"
294
294
  f"scaling factor (mm/pixel) [1] := {self.params['dx'] * 1000}\n"
295
- f"scaling factor (mm/pixel) [2] := {self.params['dx'] * 1000}\n"
296
- f"scaling factor (s/pixel) [3] := {1 / self.params['f_AQ']}\n"
295
+ f"scaling factor (mm/pixel) [2] := {self.params['dz'] * 1000}\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"
@@ -19,10 +19,12 @@ from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D
19
19
  from kwave.kspaceFirstOrder2D import kspaceFirstOrder2D
20
20
  from kwave.options.simulation_options import SimulationOptions
21
21
  from kwave.options.simulation_execution_options import SimulationExecutionOptions
22
+ from AOT_biomaps.Settings import Params
22
23
 
23
24
  from tempfile import gettempdir
24
25
  from math import ceil
25
26
  from abc import ABC, abstractmethod
27
+ import logging
26
28
 
27
29
 
28
30
 
@@ -75,7 +77,7 @@ class AcousticField(ABC):
75
77
  print(f"Initialization error: {e}")
76
78
  raise
77
79
  if params != None:
78
- if type(params) != AOT_biomaps.Settings.Params:
80
+ if type(params) != Params:
79
81
  raise TypeError("params must be an instance of the Params class")
80
82
 
81
83
  self.params = {
@@ -225,6 +227,7 @@ class AcousticField(ABC):
225
227
  Generate the acoustic field based on the specified simulation type and parameters.
226
228
  """
227
229
  try:
230
+ logging.getLogger('root').setLevel(logging.ERROR)
228
231
  if self.params['typeSim'] == TypeSim.FIELD2.value:
229
232
  raise NotImplementedError("FIELD2 simulation is not implemented yet.")
230
233
  elif self.params['typeSim'] == TypeSim.KWAVE.value:
@@ -265,7 +268,7 @@ class AcousticField(ABC):
265
268
  print(f"Error in save_field method: {e}")
266
269
  raise
267
270
 
268
- def load_field(self, folderPath, formatSave=FormatSave.HDR_IMG):
271
+ def load_field(self, folderPath, formatSave=FormatSave.HDR_IMG, nameBlock=None):
269
272
  """
270
273
  Load the acoustic field from a file in the specified format.
271
274
 
@@ -286,7 +289,7 @@ class AcousticField(ABC):
286
289
  raise NotImplementedError("3D KWAVE field loading is not implemented yet.")
287
290
  elif formatSave.value == FormatSave.H5.value:
288
291
  if self.params["dim"] == Dim.D2.value:
289
- self._load_field_h5(folderPath)
292
+ self._load_field_h5(folderPath,nameBlock)
290
293
  elif self.params["dim"] == Dim.D3.value:
291
294
  raise NotImplementedError("H5 KWAVE field loading is not implemented yet.")
292
295
  elif formatSave.value == FormatSave.NPY.value:
@@ -652,7 +655,7 @@ class AcousticField(ABC):
652
655
  """
653
656
  pass
654
657
 
655
- def _load_field_h5(self, filePath):
658
+ def _load_field_h5(self, filePath,nameBlock):
656
659
  """
657
660
  Load the 2D acoustic field from an H5 file.
658
661
 
@@ -663,8 +666,10 @@ class AcousticField(ABC):
663
666
  - field (numpy.ndarray): The loaded acoustic field.
664
667
  """
665
668
  try:
666
- with h5py.File(filePath+self.getName_field()+".h5", 'r') as f:
667
- 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][:]
668
673
  except Exception as e:
669
674
  print(f"Error in _load_field_h5 method: {e}")
670
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,7 +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.AcousticFields = self._generateAcousticFields_STRUCT_CPU(fieldDataPath, show_log)
62
+ self.AcousticFields = self._generateAcousticFields_STRUCT_CPU(fieldDataPath, show_log, nameBlock)
61
63
  else:
62
64
  raise ValueError("Unsupported wave type.")
63
65
 
@@ -389,7 +391,7 @@ class Tomography(Experiment):
389
391
  return True
390
392
 
391
393
  # PRIVATE METHODS
392
- def _generateAcousticFields_STRUCT_CPU(self, fieldDataPath=None, show_log=False):
394
+ def _generateAcousticFields_STRUCT_CPU(self, fieldDataPath=None, show_log=False, nameBlock=None):
393
395
  if self.patterns is None:
394
396
  raise ValueError("patterns is not initialized. Please load or generate the active list first.")
395
397
  listAcousticFields = []
@@ -415,7 +417,7 @@ class Tomography(Experiment):
415
417
  if pathField is not None and os.path.exists(pathField):
416
418
  progress_bar.set_postfix_str(f"Loading field - {AcousticField.getName_field()} -- Memory used: {memory.percent}%")
417
419
  try:
418
- AcousticField.load_field(fieldDataPath, self.FormatSave)
420
+ AcousticField.load_field(fieldDataPath, self.FormatSave,nameBlock)
419
421
  except:
420
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()} ----")
421
423
  AcousticField.generate_field(show_log=show_log)
@@ -433,3 +435,71 @@ class Tomography(Experiment):
433
435
  listAcousticFields.append(AcousticField)
434
436
  progress_bar.set_postfix_str("")
435
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.")
@@ -56,6 +56,7 @@ class Experiment(ABC):
56
56
  pass
57
57
 
58
58
  def cutAcousticFields(self, max_t, min_t=0):
59
+
59
60
  max_t = float(max_t)
60
61
  min_t = float(min_t)
61
62
 
@@ -113,7 +114,6 @@ class Experiment(ABC):
113
114
  noiseSignals[:, i] = noisy_signal
114
115
  return noiseSignals
115
116
 
116
-
117
117
  def reduceDims(self, mode='avg'):
118
118
  """
119
119
  Réduit les dimensions T, X, Z d'un numpy array (T, X, Z) par 2 en utilisant une convolution.
@@ -154,6 +154,16 @@ class Experiment(ABC):
154
154
  for param in ['dx', 'dy', 'dz']:
155
155
  convert_and_update(self.params.general, param, lambda x: x * 2)
156
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
+
157
167
  def saveAcousticFields(self, save_directory):
158
168
  progress_bar = trange(len(self.AcousticFields), desc="Saving Acoustic Fields")
159
169
  for i in progress_bar:
@@ -230,67 +240,97 @@ class Experiment(ABC):
230
240
  return ani
231
241
 
232
242
  def generateAOsignal(self, withTumor=True, AOsignalDataPath=None):
233
- if self.AcousticFields is None:
234
- raise ValueError("AcousticFields is not initialized. Please generate the system matrix first.")
235
-
236
- if self.OpticImage is None:
237
- raise ValueError("OpticImage is not initialized. Please generate the phantom first.")
238
243
 
239
244
  if AOsignalDataPath is not None:
240
245
  if not os.path.exists(AOsignalDataPath):
241
246
  raise FileNotFoundError(f"AO file {AOsignalDataPath} not found.")
242
- AOmatrix = self._load_AOSignal(AOsignalDataPath)
243
- if AOmatrix.shape[0] != self.AcousticFields[0].field.shape[0]:
244
- 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)
245
252
  else:
246
- return AOmatrix
247
-
248
- if not all(field.field.shape == self.AcousticFields[0].field.shape for field in self.AcousticFields):
249
- minShape = min([field.field.shape[0] for field in self.AcousticFields])
250
- self.cutAcousticFields(minShape * self.params['fs_aq'])
251
- else:
252
- shape_field = self.AcousticFields[0].field.shape
253
-
254
- AOsignal = np.zeros((shape_field[0], len(self.AcousticFields)), dtype=np.float32)
255
-
256
- if withTumor:
257
- description = "Generating AO Signal with Tumor"
258
- else:
259
- 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
260
269
 
261
- for i in trange(len(self.AcousticFields), desc=description):
262
- for t in range(self.AcousticFields[i].field.shape[0]):
263
- if withTumor:
264
- interaction = self.OpticImage.phantom * self.AcousticFields[i].field[t, :, :]
265
- else:
266
- interaction = self.OpticImage.laser.intensity * self.AcousticFields[i].field[t, :, :]
267
- AOsignal[t, i] = np.sum(interaction)
270
+ AOsignal = np.zeros((shape_field[0], len(self.AcousticFields)), dtype=np.float32)
268
271
 
269
- if withTumor:
270
- self.AOsignal_withTumor = AOsignal
271
- else:
272
- 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
273
289
 
274
290
  @staticmethod
275
- def _loadAOSignal(cdh_file):
276
- with open(cdh_file, "r") as file:
277
- cdh_content = file.readlines()
278
-
279
- n_events = int([line.split(":")[1].strip() for line in cdh_content if "Number of events" in line][0])
280
- n_acquisitions = int([line.split(":")[1].strip() for line in cdh_content if "Number of acquisitions per event" in line][0])
281
-
282
- AOsignal_matrix = np.zeros((n_events, n_acquisitions), dtype=np.float32)
283
-
284
- with open(cdh_file.replace(".cdh", ".cdf"), "rb") as file:
285
- for event in range(n_events):
286
- num_elements = int([line.split(":")[1].strip() for line in cdh_content if "Number of US transducers" in line][0])
287
- hex_length = (num_elements + 3) // 4
288
- file.read(hex_length // 2)
289
-
290
- signal = np.frombuffer(file.read(n_acquisitions * 4), dtype=np.float32)
291
- AOsignal_matrix[event, :] = signal
292
-
293
- return AOsignal_matrix
291
+ def _loadAOSignal(AOsignalPath):
292
+ if AOsignalPath.endswith(".cdh"):
293
+ with open(AOsignalPath, "r") as file:
294
+ cdh_content = file.readlines()
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.")
294
334
 
295
335
  def saveAOsignals_Castor(self, save_directory, withTumor=True):
296
336
  if withTumor:
@@ -318,10 +358,10 @@ class Experiment(ABC):
318
358
  header_content = (
319
359
  f"Data filename: {'AOSignals_withTumor.cdf' if withTumor else 'AOSignals_withoutTumor.cdf'}\n"
320
360
  f"Number of events: {nScan}\n"
321
- f"Number of acquisitions per event: {AO_signal.shape[1]}\n"
361
+ f"Number of acquisitions per event: {AO_signal.shape[0]}\n"
322
362
  f"Start time (s): 0\n"
323
363
  f"Duration (s): 1\n"
324
- f"Acquisition frequency (Hz): {1/self.AcousticFields[0].kgrid.dt}\n"
364
+ f"Acquisition frequency (Hz): {self.params.acoustic['f_saving']}\n"
325
365
  f"Data mode: histogram\n"
326
366
  f"Data type: AOT\n"
327
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