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