AOT-biomaps 2.9.176__py3-none-any.whl → 2.9.279__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 +409 -13
  7. AOT_biomaps/AOT_Recon/AOT_Optimizers/MAPEM.py +118 -38
  8. AOT_biomaps/AOT_Recon/AOT_Optimizers/MLEM.py +306 -102
  9. AOT_biomaps/AOT_Recon/AOT_Optimizers/PDHG.py +1 -1
  10. AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/RelativeDifferences.py +10 -14
  11. AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_CSR.py +281 -0
  12. AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_SELL.py +328 -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 +265 -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 +69 -62
  20. AOT_biomaps/AOT_Recon/ReconEnums.py +27 -2
  21. AOT_biomaps/AOT_Recon/ReconTools.py +152 -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 -74
  25. {aot_biomaps-2.9.176.dist-info → aot_biomaps-2.9.279.dist-info}/METADATA +2 -1
  26. aot_biomaps-2.9.279.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.279.dist-info}/WHEEL +0 -0
  29. {aot_biomaps-2.9.176.dist-info → aot_biomaps-2.9.279.dist-info}/top_level.txt +0 -0
@@ -1,39 +1,51 @@
1
+ import concurrent
2
+
3
+ from AOT_biomaps.AOT_Recon.ReconTools import get_apodization_vector_gpu
1
4
  from ._mainRecon import Recon
2
- from .ReconEnums import ReconType, OptimizerType, ProcessType
5
+ from .ReconEnums import ReconType, OptimizerType, ProcessType, SMatrixType
3
6
  from .AOT_Optimizers import MLEM, LS
4
- from .ReconTools import check_gpu_memory, calculate_memory_requirement, mse
5
7
  from AOT_biomaps.Config import config
6
-
8
+ from .AOT_SparseSMatrix import SparseSMatrix_CSR, SparseSMatrix_SELL
7
9
 
8
10
  import os
9
- import sys
10
11
  import subprocess
11
- import warnings
12
12
  import numpy as np
13
13
  import matplotlib.pyplot as plt
14
14
  import matplotlib.animation as animation
15
15
  from IPython.display import HTML
16
16
  from datetime import datetime
17
17
  from tempfile import gettempdir
18
-
19
-
18
+ import cupy as cp
19
+ import cupyx.scipy.sparse as cpsparse
20
+ import gc
21
+ from tqdm import trange
20
22
 
21
23
  class AlgebraicRecon(Recon):
22
24
  """
23
25
  This class implements the Algebraic reconstruction process.
24
26
  It currently does not perform any operations but serves as a template for future implementations.
25
27
  """
26
- def __init__(self, opti = OptimizerType.MLEM, numIterations = 10000, numSubsets = 1, isSavingEachIteration=True, maxSaves = 5000, alpha = None, **kwargs):
28
+ def __init__(self, opti = OptimizerType.MLEM, numIterations = 10000, numSubsets = 1, isSavingEachIteration=True, maxSaves = 5000, alpha = None, denominatorThreshold = 1e-6, smatrixType = SMatrixType.SELL, sparseThreshold=0.1, device = None, **kwargs):
27
29
  super().__init__(**kwargs)
28
30
  self.reconType = ReconType.Algebraic
29
31
  self.optimizer = opti
30
32
  self.reconPhantom = []
31
33
  self.reconLaser = []
34
+ self.indices = []
32
35
  self.numIterations = numIterations
33
36
  self.numSubsets = numSubsets
34
37
  self.isSavingEachIteration = isSavingEachIteration
35
38
  self.maxSaves = maxSaves
39
+ self.denominatorThreshold = denominatorThreshold
36
40
  self.alpha = alpha # Regularization parameter for LS
41
+ self.device = device
42
+ self.SMatrix = None # system matrix
43
+ self.smatrixType = smatrixType # SMatrixType.DENSE if no sparsing, else SMatrixType.SELL or SMatrixType.CSR or SMatrixType.COO
44
+ # Sparse matrix attributes
45
+
46
+ self.sparseThreshold = sparseThreshold
47
+
48
+ self.Z_dim = None # Used for sparse matrix reconstruction
37
49
 
38
50
  if self.numIterations <= 0:
39
51
  raise ValueError("Number of iterations must be greater than 0.")
@@ -45,38 +57,26 @@ class AlgebraicRecon(Recon):
45
57
  raise TypeError("Number of subsets must be an integer.")
46
58
 
47
59
  print("Generating system matrix (processing acoustic fields)...")
48
- self.SMatrix = np.stack([ac_field.field for ac_field in self.experiment.AcousticFields], axis=-1)
60
+ if self.smatrixType == SMatrixType.DENSE:
61
+ self.SMatrix = self._fillDenseSMatrix()
62
+ else:
63
+ self.SMatrix = self._fillSparseSMatrix(isShowLogs=True)
49
64
 
50
65
  # PUBLIC METHODS
51
66
 
52
- def run(self, processType = ProcessType.PYTHON, withTumor= True):
67
+ def run(self, processType = ProcessType.PYTHON, withTumor= True, show_logs=True):
53
68
  """
54
69
  This method is a placeholder for the Algebraic reconstruction process.
55
70
  It currently does not perform any operations but serves as a template for future implementations.
56
71
  """
57
-
58
72
  if(processType == ProcessType.CASToR):
59
- self._AlgebraicReconCASToR(withTumor)
73
+ self._AlgebraicReconCASToR(withTumor=withTumor, show_logs=show_logs)
60
74
  elif(processType == ProcessType.PYTHON):
61
- self._AlgebraicReconPython(withTumor)
75
+ self._AlgebraicReconPython(withTumor=withTumor, show_logs=show_logs)
62
76
  else:
63
77
  raise ValueError(f"Unknown Algebraic reconstruction type: {processType}")
64
-
65
- def load_reconCASToR(self,withTumor = True):
66
- if withTumor:
67
- folder = 'results_withTumor'
68
- else:
69
- folder = 'results_withoutTumor'
70
-
71
- for thetaFiles in os.path.join(self.saveDir, folder + '_{}'):
72
- if thetaFiles.endswith('.hdr'):
73
- theta = Recon.load_recon(thetaFiles)
74
- if withTumor:
75
- self.reconPhantom.append(theta)
76
- else:
77
- self.reconLaser.append(theta)
78
-
79
- def plot_MSE(self, isSaving=True, log_scale_x=False, log_scale_y=False):
78
+
79
+ def plot_MSE(self, isSaving=True, log_scale_x=False, log_scale_y=False, show_logs=True):
80
80
  """
81
81
  Plot the Mean Squared Error (MSE) of the reconstruction.
82
82
 
@@ -91,8 +91,8 @@ class AlgebraicRecon(Recon):
91
91
  raise ValueError("MSE is empty. Please calculate MSE first.")
92
92
 
93
93
  best_idx = self.indices[np.argmin(self.MSE)]
94
-
95
- print(f"Lowest MSE = {np.min(self.MSE):.4f} at iteration {best_idx+1}")
94
+ if show_logs:
95
+ print(f"Lowest MSE = {np.min(self.MSE):.4f} at iteration {best_idx+1}")
96
96
  # Plot MSE curve
97
97
  plt.figure(figsize=(7, 5))
98
98
  plt.plot(self.indices, self.MSE, 'r-', label="MSE curve")
@@ -119,22 +119,19 @@ class AlgebraicRecon(Recon):
119
119
  scale_str = "_logx"
120
120
  elif log_scale_y:
121
121
  scale_str = "_logy"
122
- if self.optimizer == OptimizerType.MLEM:
123
- SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_MSE_plot_MLEM{scale_str}{date_str}.png')
124
- elif self.optimizer == OptimizerType.LS:
125
- SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_MSE_plot_LS{scale_str}{date_str}.png')
122
+ SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_MSE_plot_{self.optimizer.name}_{scale_str}{date_str}.png')
126
123
  plt.savefig(SavingFolder, dpi=300)
127
- print(f"MSE plot saved to {SavingFolder}")
124
+ if show_logs:
125
+ print(f"MSE plot saved to {SavingFolder}")
128
126
 
129
127
  plt.show()
130
128
 
131
- def show_MSE_bestRecon(self, isSaving=True):
129
+ def show_MSE_bestRecon(self, isSaving=True, show_logs=True):
132
130
  if not self.MSE:
133
131
  raise ValueError("MSE is empty. Please calculate MSE first.")
134
132
 
135
133
 
136
134
  best_idx = np.argmin(self.MSE)
137
- print(best_idx)
138
135
  best_recon = self.reconPhantom[best_idx]
139
136
 
140
137
  # Crée la figure et les axes
@@ -163,7 +160,6 @@ class AlgebraicRecon(Recon):
163
160
 
164
161
  # Right: Reconstruction at last iteration
165
162
  lastRecon = self.reconPhantom[-1]
166
- print(lastRecon.shape)
167
163
  if self.experiment.OpticImage.phantom.shape != lastRecon.shape:
168
164
  lastRecon = lastRecon.T
169
165
  im2 = axs[2].imshow(lastRecon,
@@ -190,17 +186,14 @@ class AlgebraicRecon(Recon):
190
186
  savePath = os.path.join(self.saveDir, 'results')
191
187
  if not os.path.exists(savePath):
192
188
  os.makedirs(savePath)
193
- if self.optimizer == OptimizerType.MLEM:
194
- namePath = f'{self.SMatrix.shape[3]}_SCANS_comparison_MSE_BestANDLastRecon_MLEM_Date_{date_str}.png'
195
- elif self.optimizer == OptimizerType.LS:
196
- namePath = f'{self.SMatrix.shape[3]}_SCANS_comparison_MSE_BestANDLastRecon_LS_Date_{date_str}.png'
197
- SavingFolder = os.path.join(savePath, namePath)
189
+ SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_comparison_MSE_BestANDLastRecon_{self.optimizer.name}_{date_str}.png')
198
190
  plt.savefig(SavingFolder, dpi=300, bbox_inches='tight')
199
- print(f"MSE plot saved to {SavingFolder}")
191
+ if show_logs:
192
+ print(f"MSE plot saved to {SavingFolder}")
200
193
 
201
194
  plt.show()
202
195
 
203
- def show_theta_animation(self, vmin=None, vmax=None, total_duration_ms=3000, save_path=None, max_frames=1000, isPropMSE=True):
196
+ def show_theta_animation(self, vmin=None, vmax=None, total_duration_ms=3000, save_path=None, max_frames=1000, isPropMSE=True, show_logs=True):
204
197
  """
205
198
  Show theta iteration animation with speed proportional to MSE acceleration.
206
199
  In "propMSE" mode: slow down when MSE changes rapidly, speed up when MSE stagnates.
@@ -303,18 +296,19 @@ class AlgebraicRecon(Recon):
303
296
  ani.save(save_path, writer=animation.PillowWriter(fps=100))
304
297
  elif save_path.endswith(".mp4"):
305
298
  ani.save(save_path, writer="ffmpeg", fps=30)
306
- print(f"Animation saved to {save_path}")
299
+ if show_logs:
300
+ print(f"Animation saved to {save_path}")
307
301
 
308
302
  plt.close(fig)
309
303
  return HTML(ani.to_jshtml())
310
304
 
311
- def plot_SSIM(self, isSaving=True, log_scale_x=False, log_scale_y=False):
305
+ def plot_SSIM(self, isSaving=True, log_scale_x=False, log_scale_y=False, show_logs=True):
312
306
  if not self.SSIM:
313
307
  raise ValueError("SSIM is empty. Please calculate SSIM first.")
314
308
 
315
309
  best_idx = self.indices[np.argmax(self.SSIM)]
316
-
317
- print(f"Highest SSIM = {np.max(self.SSIM):.4f} at iteration {best_idx+1}")
310
+ if show_logs:
311
+ print(f"Highest SSIM = {np.max(self.SSIM):.4f} at iteration {best_idx+1}")
318
312
  # Plot SSIM curve
319
313
  plt.figure(figsize=(7, 5))
320
314
  plt.plot(self.indices, self.SSIM, 'r-', label="SSIM curve")
@@ -341,16 +335,14 @@ class AlgebraicRecon(Recon):
341
335
  scale_str = "_logx"
342
336
  elif log_scale_y:
343
337
  scale_str = "_logy"
344
- if self.optimizer == OptimizerType.MLEM:
345
- SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_SSIM_plot_MLEM{scale_str}{date_str}.png')
346
- elif self.optimizer == OptimizerType.LS:
347
- SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_SSIM_plot_LS{scale_str}{date_str}.png')
338
+ SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_SSIM_plot_{self.optimizer.name}_{scale_str}{date_str}.png')
348
339
  plt.savefig(SavingFolder, dpi=300)
349
- print(f"SSIM plot saved to {SavingFolder}")
340
+ if show_logs:
341
+ print(f"SSIM plot saved to {SavingFolder}")
350
342
 
351
343
  plt.show()
352
344
 
353
- def show_SSIM_bestRecon(self, isSaving=True):
345
+ def show_SSIM_bestRecon(self, isSaving=True, show_logs=True):
354
346
 
355
347
  if not self.SSIM:
356
348
  raise ValueError("SSIM is empty. Please calculate SSIM first.")
@@ -361,9 +353,6 @@ class AlgebraicRecon(Recon):
361
353
  # ----------------- Plotting -----------------
362
354
  _, axs = plt.subplots(1, 3, figsize=(15, 5)) # 1 row, 3 columns
363
355
 
364
- # Normalization based on LAMBDA max
365
- lambda_max = np.max(self.experiment.OpticImage.laser.intensity)
366
-
367
356
  # Left: Best reconstructed image (normalized)
368
357
  im0 = axs[0].imshow(best_recon,
369
358
  extent=(self.experiment.params.general['Xrange'][0], self.experiment.params.general['Xrange'][1], self.experiment.params.general['Zrange'][1], self.experiment.params.general['Zrange'][0]),
@@ -396,12 +385,13 @@ class AlgebraicRecon(Recon):
396
385
  if isSaving:
397
386
  now = datetime.now()
398
387
  date_str = now.strftime("%Y_%d_%m_%y")
399
- SavingFolder = os.path.join(self.saveDir, 'results', f'comparison_SSIM_BestANDLastRecon{date_str}.png')
388
+ SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_comparison_SSIM_BestANDLastRecon_{self.optimizer.name}_{date_str}.png')
400
389
  plt.savefig(SavingFolder, dpi=300)
401
- print(f"SSIM plot saved to {SavingFolder}")
390
+ if show_logs:
391
+ print(f"SSIM plot saved to {SavingFolder}")
402
392
  plt.show()
403
393
 
404
- def plot_CRC_vs_Noise(self, ROI_mask = None, start=0, fin=None, step=10, save_path=None):
394
+ def plot_CRC_vs_Noise(self, use_ROI=True, fin=None, isSaving=True, show_logs=True):
405
395
  """
406
396
  Plot CRC (Contrast Recovery Coefficient) vs Noise for each iteration.
407
397
  """
@@ -418,12 +408,11 @@ class AlgebraicRecon(Recon):
418
408
  fin = len(self.reconPhantom) - 1
419
409
 
420
410
  iter_range = self.indices
421
-
422
- crc_values = []
423
- noise_values = []
424
411
 
425
412
  if self.CRC is None:
426
- self.calculateCRC(use_ROI=True)
413
+ self.calculateCRC(use_ROI=use_ROI)
414
+
415
+ noise_values = []
427
416
 
428
417
  for i in iter_range:
429
418
  recon_without_tumor = self.reconLaser[i].T
@@ -432,8 +421,8 @@ class AlgebraicRecon(Recon):
432
421
  noise_values.append(noise)
433
422
 
434
423
  plt.figure(figsize=(6, 5))
435
- plt.plot(noise_values, crc_values, 'o-', label='ML-EM')
436
- for i, (x, y) in zip(iter_range, zip(noise_values, crc_values)):
424
+ plt.plot(noise_values, self.CRC, 'o-', label=self.optimizer.name)
425
+ for i, (x, y) in zip(iter_range, zip(noise_values, self.CRC)):
437
426
  plt.text(x, y, str(i), fontsize=5.5, ha='left', va='bottom')
438
427
 
439
428
  plt.xlabel("Noise (mean absolute error)")
@@ -445,13 +434,16 @@ class AlgebraicRecon(Recon):
445
434
  plt.title("CRC vs Noise over Iterations")
446
435
  plt.grid(True)
447
436
  plt.legend()
448
-
449
- if save_path:
450
- plt.savefig(save_path, dpi=300)
451
- print(f"Figure saved to: {save_path}")
437
+ if isSaving:
438
+ now = datetime.now()
439
+ date_str = now.strftime("%Y_%d_%m_%y")
440
+ SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_CRCvsNOISE_{self.optimizer.name}_{date_str}.png')
441
+ plt.savefig(SavingFolder, dpi=300)
442
+ if show_logs:
443
+ print(f"CRCvsNOISE plot saved to {SavingFolder}")
452
444
  plt.show()
453
445
 
454
- def show_reconstruction_progress(self, start=0, fin=None, save_path=None, with_tumor=True):
446
+ def show_reconstruction_progress(self, start=0, fin=None, save_path=None, with_tumor=True, show_logs=True):
455
447
  """
456
448
  Show the reconstruction progress for either with or without tumor.
457
449
  If isPropMSE is True, the frame selection is adapted to MSE changes.
@@ -556,11 +548,12 @@ class AlgebraicRecon(Recon):
556
548
  else:
557
549
  save_path = f"{save_path}_{title_suffix}"
558
550
  plt.savefig(save_path, dpi=300)
559
- print(f"Figure saved to: {save_path}")
551
+ if show_logs:
552
+ print(f"Figure saved to: {save_path}")
560
553
 
561
554
  plt.show()
562
555
 
563
- def checkExistingFile(self, withTumor, date):
556
+ def checkExistingFile(self, date = None):
564
557
  """
565
558
  Check if the reconstruction file already exists, based on current instance parameters.
566
559
 
@@ -579,18 +572,15 @@ class AlgebraicRecon(Recon):
579
572
  if not os.path.exists(results_dir):
580
573
  os.makedirs(results_dir)
581
574
 
582
- filename = 'reconPhantom.npy' if withTumor else 'reconLaser.npy'
583
- filepath = os.path.join(results_dir, filename)
575
+ if os.path.exists(os.path.join(results_dir,"indices.npy")):
576
+ return (True, results_dir)
584
577
 
585
- if os.path.exists(filepath):
586
- return (True, filepath)
578
+ return (False, results_dir)
587
579
 
588
- return (False, filepath)
589
-
590
-
591
- def load(self, withTumor=True, results_date=None, optimizer=None, filePath=None):
580
+ def load(self, withTumor=True, results_date=None, optimizer=None, filePath=None, show_logs=True):
592
581
  """
593
- Load the reconstruction results (reconPhantom or reconLaser) and indices into self.
582
+ Load the reconstruction results (reconPhantom or reconLaser) and indices as lists of 2D np arrays into self.
583
+ If the loaded file is a 3D array, it is split into a list of 2D arrays.
594
584
  Args:
595
585
  withTumor: If True, loads reconPhantom (with tumor), else reconLaser (without tumor).
596
586
  results_date: Date string (format "ddmm") to specify which results to load. If None, uses the most recent date in saveDir.
@@ -603,109 +593,225 @@ class AlgebraicRecon(Recon):
603
593
  recon_path = filePath
604
594
  if not os.path.exists(recon_path):
605
595
  raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
606
-
607
- if withTumor:
608
- self.reconPhantom = np.load(recon_path, allow_pickle=True)
596
+ # Charge le fichier (3D ou liste de 2D)
597
+ data = np.load(recon_path, allow_pickle=True)
598
+ # Découpe en liste de 2D si c'est un tableau 3D
599
+ if isinstance(data, np.ndarray) and data.ndim == 3:
600
+ if withTumor:
601
+ self.reconPhantom = [data[i, :, :] for i in range(data.shape[0])]
602
+ else:
603
+ self.reconLaser = [data[i, :, :] for i in range(data.shape[0])]
609
604
  else:
610
- self.reconLaser = np.load(recon_path, allow_pickle=True)
611
-
612
- # Essayer de charger les indices (fichier avec suffixe "_indices.npy")
613
- base_dir, file_name = os.path.split(recon_path)
614
- file_base, _ = os.path.splitext(file_name)
615
- indices_path = os.path.join(base_dir, f"{file_base}_indices.npy")
605
+ # Sinon, suppose que c'est déjà une liste de 2D
606
+ if withTumor:
607
+ self.reconPhantom = data
608
+ else:
609
+ self.reconLaser = data
610
+ # Essayer de charger les indices
611
+ base_dir, _ = os.path.split(recon_path)
612
+ indices_path = os.path.join(base_dir, 'indices.npy')
616
613
  if os.path.exists(indices_path):
617
- self.indices = np.load(indices_path, allow_pickle=True)
614
+ indices_data = np.load(indices_path, allow_pickle=True)
615
+ if isinstance(indices_data, np.ndarray) and indices_data.ndim == 3:
616
+ self.indices = [indices_data[i, :, :] for i in range(indices_data.shape[0])]
617
+ else:
618
+ self.indices = indices_data
618
619
  else:
619
620
  self.indices = None
620
-
621
- print(f"Loaded reconstruction results and indices from {recon_path}")
621
+
622
+ if show_logs:
623
+ print(f"Loaded reconstruction results and indices from {recon_path}")
622
624
  else:
623
625
  # Mode chargement depuis le répertoire de résultats
624
626
  if self.saveDir is None:
625
627
  raise ValueError("Save directory is not specified. Please set saveDir before loading.")
626
-
627
- # Determine optimizer name for path matching
628
+ # Use current optimizer and potential function if not provided
628
629
  opt_name = optimizer.value if optimizer is not None else self.optimizer.value
629
-
630
+ # Build the base directory pattern
631
+ dir_pattern = f'results_*_{opt_name}'
632
+ # Add parameters to the pattern based on the optimizer
633
+ if optimizer is None:
634
+ optimizer = self.optimizer
635
+ if optimizer == OptimizerType.PPGMLEM:
636
+ beta_str = f'_Beta_{self.beta}'
637
+ delta_str = f'_Delta_{self.delta}'
638
+ gamma_str = f'_Gamma_{self.gamma}'
639
+ sigma_str = f'_Sigma_{self.sigma}'
640
+ dir_pattern += f'{beta_str}{delta_str}{gamma_str}{sigma_str}'
641
+ elif optimizer in (OptimizerType.PGC, OptimizerType.DEPIERRO95):
642
+ beta_str = f'_Beta_{self.beta}'
643
+ sigma_str = f'_Sigma_{self.sigma}'
644
+ dir_pattern += f'{beta_str}{sigma_str}'
630
645
  # Find the most recent results directory if no date is specified
631
646
  if results_date is None:
632
- dir_pattern = f'results_*_{opt_name}'
633
647
  dirs = [d for d in os.listdir(self.saveDir) if os.path.isdir(os.path.join(self.saveDir, d)) and dir_pattern in d]
634
648
  if not dirs:
635
- raise FileNotFoundError(f"No results directory found for optimizer '{opt_name}' in {self.saveDir}.")
649
+ raise FileNotFoundError(f"No matching results directory found for pattern '{dir_pattern}' in {self.saveDir}.")
636
650
  dirs.sort(reverse=True) # Most recent first
637
651
  results_dir = os.path.join(self.saveDir, dirs[0])
638
652
  else:
639
- results_dir = os.path.join(self.saveDir, f'results_{results_date}_{opt_name}_')
653
+ results_dir = os.path.join(self.saveDir, f'results_{results_date}_{opt_name}')
654
+ if optimizer == OptimizerType.MLEM:
655
+ pass
656
+ elif optimizer == OptimizerType.LS:
657
+ results_dir += f'_Alpha_{self.alpha}'
640
658
  if not os.path.exists(results_dir):
641
659
  raise FileNotFoundError(f"Directory {results_dir} does not exist.")
642
-
643
660
  # Load reconstruction results
644
661
  recon_key = 'reconPhantom' if withTumor else 'reconLaser'
645
662
  recon_path = os.path.join(results_dir, f'{recon_key}.npy')
646
663
  if not os.path.exists(recon_path):
647
664
  raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
648
-
649
- if withTumor:
650
- self.reconPhantom = np.load(recon_path, allow_pickle=True)
665
+ data = np.load(recon_path, allow_pickle=True)
666
+ if isinstance(data, np.ndarray) and data.ndim == 3:
667
+ if withTumor:
668
+ self.reconPhantom = [data[i, :, :] for i in range(data.shape[0])]
669
+ else:
670
+ self.reconLaser = [data[i, :, :] for i in range(data.shape[0])]
651
671
  else:
652
- self.reconLaser = np.load(recon_path, allow_pickle=True)
653
-
654
- # Try to load saved indices (if file exists)
655
- indices_path = os.path.join(results_dir, f'{recon_key}_indices.npy')
656
- if os.path.exists(indices_path):
657
- self.indices = np.load(indices_path, allow_pickle=True)
672
+ if withTumor:
673
+ self.reconPhantom = data
674
+ else:
675
+ self.reconLaser = data
676
+ # Load saved indices as list of 2D arrays
677
+ indices_path = os.path.join(results_dir, 'indices.npy')
678
+ if not os.path.exists(indices_path):
679
+ raise FileNotFoundError(f"No indices file found at {indices_path}.")
680
+ indices_data = np.load(indices_path, allow_pickle=True)
681
+ if isinstance(indices_data, np.ndarray) and indices_data.ndim == 3:
682
+ self.indices = [indices_data[i, :, :] for i in range(indices_data.shape[0])]
658
683
  else:
659
- self.indices = None
660
-
661
- print(f"Loaded reconstruction results and indices from {results_dir}")
662
-
663
-
664
-
665
-
684
+ self.indices = indices_data
685
+ if show_logs:
686
+ print(f"Loaded reconstruction results and indices from {results_dir}")
687
+
666
688
  def normalizeSMatrix(self):
667
689
  self.SMatrix = self.SMatrix / (float(self.experiment.params.acoustic['voltage'])*float(self.experiment.params.acoustic['sensitivity']))
668
690
 
669
691
  # PRIVATE METHODS
670
692
 
671
- def _AlgebraicReconPython(self,withTumor):
693
+ def _fillDenseSMatrix(self):
694
+ """
695
+ Construit une matrice dense en mémoire.
696
+ """
697
+ T, Z, X = self.experiment.AcousticFields[0].field.shape
698
+ N = len(self.experiment.AcousticFields)
699
+ S = np.empty((T, Z, X, N), dtype=np.float32)
700
+ def copy_block(i):
701
+ np.copyto(S[..., i], self.experiment.AcousticFields[i].field)
702
+ with concurrent.futures.ThreadPoolExecutor() as ex:
703
+ ex.map(copy_block, range(N))
704
+ return S
705
+
706
+
707
+ def _fillSparseSMatrix(self, isShowLogs=True):
708
+ if self.smatrixType == SMatrixType.CSR:
709
+ return self._fillSparseSMatrix_CSR(isShowLogs=isShowLogs)
710
+ if self.smatrixType == SMatrixType.COO:
711
+ raise NotImplementedError("COO sparse matrix not implemented yet.")
712
+ if self.smatrixType == SMatrixType.SELL:
713
+ return self._fillSparseSMatrix_SELL(isShowLogs=isShowLogs)
714
+
715
+ def _fillSparseSMatrix_CSR(self, isShowLogs=True):
716
+ """
717
+ Construit une matrice sparse CSR par morceaux sans concaténation intermédiaire.
718
+ Libère toute la mémoire temporaire à chaque étape.
719
+ """
720
+ sparse_matrix = SparseSMatrix_CSR(self.experiment,relative_threshold=self.sparseThreshold)
721
+ sparse_matrix.allocate()
722
+ if isShowLogs:
723
+ print(f" Sparse matrix size: {sparse_matrix.getMatrixSize()} GB")
724
+ print(f"Sparse matrix density: {sparse_matrix.compute_density()}")
725
+ return sparse_matrix
726
+
727
+ def _fillSparseSMatrix_SELL(self, isShowLogs=True):
728
+ """
729
+ Construit une matrice sparse SELL par morceaux sans concaténation intermédiaire.
730
+ Libère toute la mémoire temporaire à chaque étape.
731
+ """
732
+ sparse_matrix = SparseSMatrix_SELL(self.experiment,relative_threshold=self.sparseThreshold)
733
+ sparse_matrix.allocate()
734
+ # fenetre_gpu = get_apodization_vector_gpu(sparse_matrix)
735
+ # sparse_matrix.apply_apodization_gpu(fenetre_gpu)
736
+ if isShowLogs:
737
+ print(f" Sparse matrix size: {sparse_matrix.getMatrixSize()} GB")
738
+ print(f"Sparse matrix density: {sparse_matrix.compute_density()}")
739
+ return sparse_matrix
740
+
741
+ def _AlgebraicReconPython(self,withTumor, show_logs):
672
742
 
673
743
  if withTumor:
674
744
  if self.experiment.AOsignal_withTumor is None:
675
745
  raise ValueError("AO signal with tumor is not available. Please generate AO signal with tumor the experiment first in the experiment object.")
676
- else:
677
- y = self.experiment.AOsignal_withTumor
678
746
  else:
679
747
  if self.experiment.AOsignal_withoutTumor is None:
680
748
  raise ValueError("AO signal without tumor is not available. Please generate AO signal without tumor the experiment first in the experiment object.")
681
- else:
682
- y = self.experiment.AOsignal_withoutTumor
683
749
 
684
750
  if self.optimizer.value == OptimizerType.MLEM.value:
685
- self.reconPhantom, self.indices = MLEM(SMatrix=self.SMatrix,
686
- y=y,
687
- numIterations=self.numIterations,
688
- isSavingEachIteration=self.isSavingEachIteration,
689
- withTumor=withTumor,
690
- use_multi_gpu= self.isMultiGPU,
691
- use_numba= self.isMultiCPU,
692
- max_saves=self.maxSaves
693
- )
751
+ if withTumor:
752
+ self.reconPhantom, self.indices = MLEM(SMatrix=self.SMatrix,
753
+ y=self.experiment.AOsignal_withTumor,
754
+ numIterations=self.numIterations,
755
+ isSavingEachIteration=self.isSavingEachIteration,
756
+ withTumor=withTumor,
757
+ device=self.device,
758
+ use_numba=self.isMultiCPU,
759
+ denominator_threshold=self.denominatorThreshold,
760
+ max_saves=self.maxSaves,
761
+ show_logs=show_logs,
762
+ smatrixType=self.smatrixType,
763
+ Z=self.Z_dim
764
+ )
765
+ else:
766
+ self.reconLaser, self.indices = MLEM(SMatrix=self.SMatrix,
767
+ y=self.experiment.AOsignal_withoutTumor,
768
+ numIterations=self.numIterations,
769
+ isSavingEachIteration=self.isSavingEachIteration,
770
+ withTumor=withTumor,
771
+ device=self.device,
772
+ use_numba=self.isMultiCPU,
773
+ denominator_threshold=self.denominatorThreshold,
774
+ max_saves=self.maxSaves,
775
+ show_logs=show_logs,
776
+ smatrixType=self.smatrixType,
777
+ Z=self.Z_dim
778
+ )
694
779
  elif self.optimizer.value == OptimizerType.LS.value:
695
780
  if self.alpha is None:
696
781
  raise ValueError("Alpha (regularization parameter) must be set for LS reconstruction.")
697
- self.reconPhantom, self.indices = LS(SMatrix=self.SMatrix,
698
- y=y,
699
- numIterations=self.numIterations,
700
- isSavingEachIteration=self.isSavingEachIteration,
701
- withTumor=withTumor,
702
- alpha=self.alpha,
703
- max_saves=self.maxSaves,
782
+ if withTumor:
783
+ self.reconPhantom, self.indices = LS(SMatrix=self.SMatrix,
784
+ y=self.experiment.AOsignal_withTumor,
785
+ numIterations=self.numIterations,
786
+ isSavingEachIteration=self.isSavingEachIteration,
787
+ withTumor=withTumor,
788
+ device=self.device,
789
+ use_numba=self.isMultiCPU,
790
+ denominator_threshold=self.denominatorThreshold,
791
+ max_saves=self.maxSaves,
792
+ show_logs=show_logs,
793
+ smatrixType=self.smatrixType,
794
+ Z=self.Z_dim
795
+ )
796
+ else:
797
+ self.reconLaser, self.indices = LS(SMatrix=self.SMatrix,
798
+ y=self.experiment.AOsignal_withoutTumor,
799
+ numIterations=self.numIterations,
800
+ isSavingEachIteration=self.isSavingEachIteration,
801
+ withTumor=withTumor,
802
+ alpha=self.alpha,
803
+ device=self.device,
804
+ use_numba=self.isMultiCPU,
805
+ denominator_threshold=self.denominatorThreshold,
806
+ max_saves=self.maxSaves,
807
+ show_logs=show_logs,
808
+ smatrixType=self.smatrixType,
809
+ Z=self.Z_dim
704
810
  )
705
811
  else:
706
812
  raise ValueError(f"Only MLEM and LS are supported for simple algebraic reconstruction. {self.optimizer.value} need Bayesian reconstruction")
707
813
 
708
- def _AlgebraicReconCASToR(self, withTumor):
814
+ def _AlgebraicReconCASToR(self,withTumor, show_logs):
709
815
  # Définir les chemins
710
816
  smatrix = os.path.join(self.saveDir, "system_matrix")
711
817
  if withTumor:
@@ -715,14 +821,16 @@ class AlgebraicRecon(Recon):
715
821
 
716
822
  # Vérifier et générer les fichiers d'entrée si nécessaire
717
823
  if not os.path.isfile(os.path.join(self.saveDir, fileName)):
718
- print(f"Fichier .cdh manquant. Génération de {fileName}...")
824
+ if show_logs:
825
+ print(f"Fichier .cdh manquant. Génération de {fileName}...")
719
826
  self.experiment.saveAOsignals_Castor(self.saveDir)
720
827
 
721
828
  # Vérifier/générer la matrice système
722
829
  if not os.path.isdir(smatrix):
723
830
  os.makedirs(smatrix, exist_ok=True)
724
831
  if not os.listdir(smatrix):
725
- print("Matrice système manquante. Génération...")
832
+ if show_logs:
833
+ print("Matrice système manquante. Génération...")
726
834
  self.experiment.saveAcousticFields(self.saveDir)
727
835
 
728
836
  # Vérifier que le fichier .cdh existe (redondant mais sûr)
@@ -761,8 +869,9 @@ class AlgebraicRecon(Recon):
761
869
  ]
762
870
 
763
871
  # Afficher la commande (pour débogage)
764
- print("Commande CASToR :")
765
- print(" ".join(cmd))
872
+ if show_logs:
873
+ print("Commande CASToR :")
874
+ print(" ".join(cmd))
766
875
 
767
876
  # Chemin du script temporaire
768
877
  recon_script_path = os.path.join(gettempdir(), 'recon.sh')
@@ -776,17 +885,20 @@ class AlgebraicRecon(Recon):
776
885
 
777
886
  # Rendre le script exécutable et l'exécuter
778
887
  subprocess.run(["chmod", "+x", recon_script_path], check=True)
779
- print(f"Exécution de la reconstruction avec CASToR...")
888
+ if show_logs:
889
+ print(f"Exécution de la reconstruction avec CASToR...")
780
890
  result = subprocess.run(recon_script_path, env=env, check=True, capture_output=True, text=True)
781
891
 
782
892
  # Afficher la sortie de CASToR (pour débogage)
783
- print("Sortie CASToR :")
784
- print(result.stdout)
785
- if result.stderr:
786
- print("Erreurs :")
787
- print(result.stderr)
788
-
789
- print("Reconstruction terminée avec succès.")
893
+ if show_logs:
894
+ print("Sortie CASToR :")
895
+ print(result.stdout)
896
+ if result.stderr:
897
+ print("Erreurs :")
898
+ print(result.stderr)
899
+
900
+ if show_logs:
901
+ print("Reconstruction terminée avec succès.")
790
902
  self.load_reconCASToR(withTumor=withTumor)
791
903
 
792
904
  # STATIC METHODS