AOT-biomaps 2.9.138__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 (31) hide show
  1. AOT_biomaps/AOT_Acoustic/AcousticTools.py +35 -115
  2. AOT_biomaps/AOT_Acoustic/StructuredWave.py +2 -2
  3. AOT_biomaps/AOT_Acoustic/_mainAcoustic.py +22 -18
  4. AOT_biomaps/AOT_Experiment/Tomography.py +74 -4
  5. AOT_biomaps/AOT_Experiment/_mainExperiment.py +102 -68
  6. AOT_biomaps/AOT_Optic/_mainOptic.py +124 -58
  7. AOT_biomaps/AOT_Recon/AOT_Optimizers/DEPIERRO.py +72 -108
  8. AOT_biomaps/AOT_Recon/AOT_Optimizers/LS.py +474 -289
  9. AOT_biomaps/AOT_Recon/AOT_Optimizers/MAPEM.py +173 -68
  10. AOT_biomaps/AOT_Recon/AOT_Optimizers/MLEM.py +360 -154
  11. AOT_biomaps/AOT_Recon/AOT_Optimizers/PDHG.py +150 -111
  12. AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/RelativeDifferences.py +10 -14
  13. AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_CSR.py +281 -0
  14. AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_SELL.py +328 -0
  15. AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/__init__.py +2 -0
  16. AOT_biomaps/AOT_Recon/AOT_biomaps_kernels.cubin +0 -0
  17. AOT_biomaps/AOT_Recon/AlgebraicRecon.py +359 -238
  18. AOT_biomaps/AOT_Recon/AnalyticRecon.py +29 -41
  19. AOT_biomaps/AOT_Recon/BayesianRecon.py +165 -91
  20. AOT_biomaps/AOT_Recon/DeepLearningRecon.py +4 -1
  21. AOT_biomaps/AOT_Recon/PrimalDualRecon.py +175 -31
  22. AOT_biomaps/AOT_Recon/ReconEnums.py +38 -3
  23. AOT_biomaps/AOT_Recon/ReconTools.py +184 -77
  24. AOT_biomaps/AOT_Recon/__init__.py +1 -0
  25. AOT_biomaps/AOT_Recon/_mainRecon.py +144 -74
  26. AOT_biomaps/__init__.py +4 -36
  27. {aot_biomaps-2.9.138.dist-info → aot_biomaps-2.9.279.dist-info}/METADATA +2 -1
  28. aot_biomaps-2.9.279.dist-info/RECORD +47 -0
  29. aot_biomaps-2.9.138.dist-info/RECORD +0 -43
  30. {aot_biomaps-2.9.138.dist-info → aot_biomaps-2.9.279.dist-info}/WHEEL +0 -0
  31. {aot_biomaps-2.9.138.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, lambda_reg=None, L_Factor=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
- self.lambda_reg = lambda_reg
36
- self.L_Factor = L_Factor
38
+ self.maxSaves = maxSaves
39
+ self.denominatorThreshold = denominatorThreshold
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,24 +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')
126
- elif self.optimizer == OptimizerType.LS_TV:
127
- SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_MSE_plot_LS_TV_Lambda_{self.lambda_reg}_LFactor_{self.L_Factor}{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')
128
123
  plt.savefig(SavingFolder, dpi=300)
129
- print(f"MSE plot saved to {SavingFolder}")
124
+ if show_logs:
125
+ print(f"MSE plot saved to {SavingFolder}")
130
126
 
131
127
  plt.show()
132
128
 
133
- def show_MSE_bestRecon(self, isSaving=True):
129
+ def show_MSE_bestRecon(self, isSaving=True, show_logs=True):
134
130
  if not self.MSE:
135
131
  raise ValueError("MSE is empty. Please calculate MSE first.")
136
132
 
137
133
 
138
134
  best_idx = np.argmin(self.MSE)
139
- print(best_idx)
140
135
  best_recon = self.reconPhantom[best_idx]
141
136
 
142
137
  # Crée la figure et les axes
@@ -165,7 +160,6 @@ class AlgebraicRecon(Recon):
165
160
 
166
161
  # Right: Reconstruction at last iteration
167
162
  lastRecon = self.reconPhantom[-1]
168
- print(lastRecon.shape)
169
163
  if self.experiment.OpticImage.phantom.shape != lastRecon.shape:
170
164
  lastRecon = lastRecon.T
171
165
  im2 = axs[2].imshow(lastRecon,
@@ -192,19 +186,14 @@ class AlgebraicRecon(Recon):
192
186
  savePath = os.path.join(self.saveDir, 'results')
193
187
  if not os.path.exists(savePath):
194
188
  os.makedirs(savePath)
195
- if self.optimizer == OptimizerType.MLEM:
196
- namePath = f'{self.SMatrix.shape[3]}_SCANS_comparison_MSE_BestANDLastRecon_MLEM_Date_{date_str}.png'
197
- elif self.optimizer == OptimizerType.LS:
198
- namePath = f'{self.SMatrix.shape[3]}_SCANS_comparison_MSE_BestANDLastRecon_LS_Date_{date_str}.png'
199
- elif self.optimizer == OptimizerType.LS_TV:
200
- namePath = f'{self.SMatrix.shape[3]}_SCANS_comparison_MSE_BestANDLastRecon_LS_TV_Lambda_{self.lambda_reg}_LFactor_{self.L_Factor}_Date_{date_str}.png'
201
- 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')
202
190
  plt.savefig(SavingFolder, dpi=300, bbox_inches='tight')
203
- print(f"MSE plot saved to {SavingFolder}")
191
+ if show_logs:
192
+ print(f"MSE plot saved to {SavingFolder}")
204
193
 
205
194
  plt.show()
206
195
 
207
- 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):
208
197
  """
209
198
  Show theta iteration animation with speed proportional to MSE acceleration.
210
199
  In "propMSE" mode: slow down when MSE changes rapidly, speed up when MSE stagnates.
@@ -307,18 +296,19 @@ class AlgebraicRecon(Recon):
307
296
  ani.save(save_path, writer=animation.PillowWriter(fps=100))
308
297
  elif save_path.endswith(".mp4"):
309
298
  ani.save(save_path, writer="ffmpeg", fps=30)
310
- print(f"Animation saved to {save_path}")
299
+ if show_logs:
300
+ print(f"Animation saved to {save_path}")
311
301
 
312
302
  plt.close(fig)
313
303
  return HTML(ani.to_jshtml())
314
304
 
315
- 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):
316
306
  if not self.SSIM:
317
307
  raise ValueError("SSIM is empty. Please calculate SSIM first.")
318
308
 
319
309
  best_idx = self.indices[np.argmax(self.SSIM)]
320
-
321
- 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}")
322
312
  # Plot SSIM curve
323
313
  plt.figure(figsize=(7, 5))
324
314
  plt.plot(self.indices, self.SSIM, 'r-', label="SSIM curve")
@@ -345,18 +335,14 @@ class AlgebraicRecon(Recon):
345
335
  scale_str = "_logx"
346
336
  elif log_scale_y:
347
337
  scale_str = "_logy"
348
- if self.optimizer == OptimizerType.MLEM:
349
- SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_SSIM_plot_MLEM{scale_str}{date_str}.png')
350
- elif self.optimizer == OptimizerType.LS:
351
- SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_SSIM_plot_LS{scale_str}{date_str}.png')
352
- elif self.optimizer == OptimizerType.LS_TV:
353
- SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_SSIM_plot_LS_TV_Lambda_{self.lambda_reg}_LFactor_{self.L_Factor}{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')
354
339
  plt.savefig(SavingFolder, dpi=300)
355
- print(f"SSIM plot saved to {SavingFolder}")
340
+ if show_logs:
341
+ print(f"SSIM plot saved to {SavingFolder}")
356
342
 
357
343
  plt.show()
358
344
 
359
- def show_SSIM_bestRecon(self, isSaving=True):
345
+ def show_SSIM_bestRecon(self, isSaving=True, show_logs=True):
360
346
 
361
347
  if not self.SSIM:
362
348
  raise ValueError("SSIM is empty. Please calculate SSIM first.")
@@ -367,9 +353,6 @@ class AlgebraicRecon(Recon):
367
353
  # ----------------- Plotting -----------------
368
354
  _, axs = plt.subplots(1, 3, figsize=(15, 5)) # 1 row, 3 columns
369
355
 
370
- # Normalization based on LAMBDA max
371
- lambda_max = np.max(self.experiment.OpticImage.laser.intensity)
372
-
373
356
  # Left: Best reconstructed image (normalized)
374
357
  im0 = axs[0].imshow(best_recon,
375
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]),
@@ -402,18 +385,19 @@ class AlgebraicRecon(Recon):
402
385
  if isSaving:
403
386
  now = datetime.now()
404
387
  date_str = now.strftime("%Y_%d_%m_%y")
405
- 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')
406
389
  plt.savefig(SavingFolder, dpi=300)
407
- print(f"SSIM plot saved to {SavingFolder}")
390
+ if show_logs:
391
+ print(f"SSIM plot saved to {SavingFolder}")
408
392
  plt.show()
409
393
 
410
- 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):
411
395
  """
412
396
  Plot CRC (Contrast Recovery Coefficient) vs Noise for each iteration.
413
397
  """
414
398
  if self.reconLaser is None or self.reconLaser == []:
415
399
  raise ValueError("Reconstructed laser is empty. Run reconstruction first.")
416
- if isinstance(self.Laser,list) and len(self.Laser) == 1:
400
+ if isinstance(self.reconLaser, list) and len(self.reconLaser) == 1:
417
401
  raise ValueError("Reconstructed Image without tumor is a single frame. Run reconstruction with isSavingEachIteration=True to get a sequence of frames.")
418
402
  if self.reconPhantom is None or self.reconPhantom == []:
419
403
  raise ValueError("Reconstructed phantom is empty. Run reconstruction first.")
@@ -424,24 +408,21 @@ class AlgebraicRecon(Recon):
424
408
  fin = len(self.reconPhantom) - 1
425
409
 
426
410
  iter_range = self.indices
411
+
412
+ if self.CRC is None:
413
+ self.calculateCRC(use_ROI=use_ROI)
427
414
 
428
- crc_values = []
429
415
  noise_values = []
430
416
 
431
417
  for i in iter_range:
432
418
  recon_without_tumor = self.reconLaser[i].T
433
-
434
- # CRC
435
- crc = self.calculateCRC(iteration=i,ROI_mask=ROI_mask)
436
- crc_values.append(crc)
437
-
438
419
  # Noise
439
420
  noise = np.mean(np.abs(recon_without_tumor - self.experiment.OpticImage.laser.intensity))
440
421
  noise_values.append(noise)
441
422
 
442
423
  plt.figure(figsize=(6, 5))
443
- plt.plot(noise_values, crc_values, 'o-', label='ML-EM')
444
- 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)):
445
426
  plt.text(x, y, str(i), fontsize=5.5, ha='left', va='bottom')
446
427
 
447
428
  plt.xlabel("Noise (mean absolute error)")
@@ -453,13 +434,16 @@ class AlgebraicRecon(Recon):
453
434
  plt.title("CRC vs Noise over Iterations")
454
435
  plt.grid(True)
455
436
  plt.legend()
456
-
457
- if save_path:
458
- plt.savefig(save_path, dpi=300)
459
- 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}")
460
444
  plt.show()
461
445
 
462
- 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):
463
447
  """
464
448
  Show the reconstruction progress for either with or without tumor.
465
449
  If isPropMSE is True, the frame selection is adapted to MSE changes.
@@ -564,114 +548,303 @@ class AlgebraicRecon(Recon):
564
548
  else:
565
549
  save_path = f"{save_path}_{title_suffix}"
566
550
  plt.savefig(save_path, dpi=300)
567
- print(f"Figure saved to: {save_path}")
551
+ if show_logs:
552
+ print(f"Figure saved to: {save_path}")
568
553
 
569
554
  plt.show()
570
555
 
571
- def save(self, withTumor=True):
556
+ def checkExistingFile(self, date = None):
572
557
  """
573
- Save the reconstruction results (reconPhantom is with tumor, reconLaser is without tumor) and indices of the saved recon results, in format numpy.
574
- Warnings : reconPhantom and reconLaser are lists of 2D numpy arrays, each array corresponding to one iteration.
558
+ Check if the reconstruction file already exists, based on current instance parameters.
559
+
560
+ Args:
561
+ withTumor (bool): If True, checks reconPhantom.npy; otherwise, checks reconLaser.npy.
562
+ overwrite (bool): If False, returns False if the file exists.
563
+
564
+ Returns:
565
+ tuple: (bool: whether to save, str: the filepath)
575
566
  """
576
567
  if self.saveDir is None:
577
- raise ValueError("Save directory is not specified. Please set saveDir before saving.")
578
- if withTumor:
579
- if not self.reconPhantom or len(self.reconPhantom) == 0:
580
- raise ValueError("Reconstructed phantom is empty. Run reconstruction first.")
581
- np.save(os.path.join(self.saveDir, 'results', 'reconPhantom.npy'), np.array(self.reconPhantom))
582
- else:
583
- if not self.reconLaser or len(self.reconLaser) == 0:
584
- raise ValueError("Reconstructed laser is empty. Run reconstruction first.")
585
- np.save(os.path.join(self.saveDir, 'results', 'reconLaser.npy'), np.array(self.reconLaser))
586
- np.save(os.path.join(self.saveDir, 'results', 'reconIndices.npy'), np.array(self.indices))
587
- print(f"Reconstruction results saved to {os.path.join(self.saveDir, 'results')}")
568
+ raise ValueError("Save directory is not specified.")
569
+ if date is None:
570
+ date = datetime.now().strftime("%d%m")
571
+ results_dir = os.path.join(self.saveDir, f'results_{date}_{self.optimizer.value}')
572
+ if not os.path.exists(results_dir):
573
+ os.makedirs(results_dir)
588
574
 
589
- def load(self, withTumor=True):
575
+ if os.path.exists(os.path.join(results_dir,"indices.npy")):
576
+ return (True, results_dir)
577
+
578
+ return (False, results_dir)
579
+
580
+ def load(self, withTumor=True, results_date=None, optimizer=None, filePath=None, show_logs=True):
590
581
  """
591
- Load the reconstruction results (reconPhantom is with tumor, reconLaser is without tumor) and indices of the saved recon results, in format numpy.
592
- Warnings : reconPhantom and reconLaser are lists of 2D numpy arrays, each array corresponding to one iteration.
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.
584
+ Args:
585
+ withTumor: If True, loads reconPhantom (with tumor), else reconLaser (without tumor).
586
+ results_date: Date string (format "ddmm") to specify which results to load. If None, uses the most recent date in saveDir.
587
+ optimizer: Optimizer name (as string or enum) to filter results. If None, uses the current optimizer of the instance.
588
+ filePath: Optional. If provided, loads directly from this path (overrides saveDir and results_date).
593
589
  """
594
- if self.saveDir is None:
595
- raise ValueError("Save directory is not specified. Please set saveDir before loading.")
596
- if withTumor:
597
- recon_path = os.path.join(self.saveDir, 'results', 'reconPhantom.npy')
598
- if not os.path.isfile(recon_path):
599
- raise FileNotFoundError(f"Reconstructed phantom file not found: {recon_path}")
600
- self.reconPhantom = list(np.load(recon_path, allow_pickle=True))
590
+ if filePath is not None:
591
+ # Mode chargement direct depuis un fichier
592
+ recon_key = 'reconPhantom' if withTumor else 'reconLaser'
593
+ recon_path = filePath
594
+ if not os.path.exists(recon_path):
595
+ raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
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])]
604
+ else:
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')
613
+ if os.path.exists(indices_path):
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
619
+ else:
620
+ self.indices = None
621
+
622
+ if show_logs:
623
+ print(f"Loaded reconstruction results and indices from {recon_path}")
601
624
  else:
602
- recon_path = os.path.join(self.saveDir, 'results', 'reconLaser.npy')
603
- if not os.path.isfile(recon_path):
604
- raise FileNotFoundError(f"Reconstructed laser file not found: {recon_path}")
605
- self.reconLaser = list(np.load(recon_path, allow_pickle=True))
606
- indices_path = os.path.join(self.saveDir, 'results', 'reconIndices.npy')
607
- if not os.path.isfile(indices_path):
608
- raise FileNotFoundError(f"Reconstruction indices file not found: {indices_path}")
609
- self.indices = list(np.load(indices_path, allow_pickle=True))
610
- print(f"Reconstruction results loaded from {os.path.join(self.saveDir, 'results')}")
611
-
625
+ # Mode chargement depuis le répertoire de résultats
626
+ if self.saveDir is None:
627
+ raise ValueError("Save directory is not specified. Please set saveDir before loading.")
628
+ # Use current optimizer and potential function if not provided
629
+ opt_name = optimizer.value if optimizer is not None else self.optimizer.value
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}'
645
+ # Find the most recent results directory if no date is specified
646
+ if results_date is None:
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]
648
+ if not dirs:
649
+ raise FileNotFoundError(f"No matching results directory found for pattern '{dir_pattern}' in {self.saveDir}.")
650
+ dirs.sort(reverse=True) # Most recent first
651
+ results_dir = os.path.join(self.saveDir, dirs[0])
652
+ else:
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}'
658
+ if not os.path.exists(results_dir):
659
+ raise FileNotFoundError(f"Directory {results_dir} does not exist.")
660
+ # Load reconstruction results
661
+ recon_key = 'reconPhantom' if withTumor else 'reconLaser'
662
+ recon_path = os.path.join(results_dir, f'{recon_key}.npy')
663
+ if not os.path.exists(recon_path):
664
+ raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
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])]
671
+ else:
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])]
683
+ else:
684
+ self.indices = indices_data
685
+ if show_logs:
686
+ print(f"Loaded reconstruction results and indices from {results_dir}")
687
+
612
688
  def normalizeSMatrix(self):
613
- self.SMatrix = self.SMatrix / (self.experiment.params.acoustic['voltage']*self.experiment.params.acoustic['sensitivity'])
689
+ self.SMatrix = self.SMatrix / (float(self.experiment.params.acoustic['voltage'])*float(self.experiment.params.acoustic['sensitivity']))
614
690
 
615
691
  # PRIVATE METHODS
616
692
 
617
- 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):
618
742
 
619
743
  if withTumor:
620
744
  if self.experiment.AOsignal_withTumor is None:
621
745
  raise ValueError("AO signal with tumor is not available. Please generate AO signal with tumor the experiment first in the experiment object.")
622
- if self.optimizer.value == OptimizerType.MLEM.value:
623
- self.reconPhantom, self.indices = self._MLEM(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withTumor, withTumor=withTumor)
624
- elif self.optimizer.value == OptimizerType.LS.value:
625
- self.reconPhantom, self.indices = self._LS(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withTumor, withTumor=withTumor)
626
- elif self.optimizer.value == OptimizerType.LS_TV.value:
627
- self.reconPhantom, self.indices = self._LS_Regularized(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withTumor, withTumor=withTumor)
628
- else:
629
- raise ValueError(f"Only MLEM and LS are supported for simple algebraic reconstruction. {self.optimizer.value} need Bayesian reconstruction")
630
746
  else:
631
747
  if self.experiment.AOsignal_withoutTumor is None:
632
748
  raise ValueError("AO signal without tumor is not available. Please generate AO signal without tumor the experiment first in the experiment object.")
633
- if self.optimizer.value == OptimizerType.MLEM.value:
634
- self.reconLaser, self.indices = self._MLEM(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withoutTumor, withTumor=withTumor)
635
- elif self.optimizer.value == OptimizerType.LS.value:
636
- self.reconLaser, self.indices = self._LS(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withoutTumor, withTumor=withTumor)
637
- elif self.optimizer.value == OptimizerType.LS_TV.value:
638
- self.reconLaser, self.indices = self._LS_Regularized(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withoutTumor, withTumor=withTumor)
639
- else:
640
- raise ValueError(f"Only MLEM and LS are supported for simple algebraic reconstruction. {self.optimizer.value} need Bayesian reconstruction")
641
749
 
642
- def _AlgebraicReconCASToR(self, withTumor):
643
-
644
- # Define variables
645
- smatrix = os.path.join(self.saveDir,"system_matrix")
750
+ if self.optimizer.value == OptimizerType.MLEM.value:
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
+ )
779
+ elif self.optimizer.value == OptimizerType.LS.value:
780
+ if self.alpha is None:
781
+ raise ValueError("Alpha (regularization parameter) must be set for LS reconstruction.")
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
810
+ )
811
+ else:
812
+ raise ValueError(f"Only MLEM and LS are supported for simple algebraic reconstruction. {self.optimizer.value} need Bayesian reconstruction")
646
813
 
814
+ def _AlgebraicReconCASToR(self,withTumor, show_logs):
815
+ # Définir les chemins
816
+ smatrix = os.path.join(self.saveDir, "system_matrix")
647
817
  if withTumor:
648
818
  fileName = 'AOSignals_withTumor.cdh'
649
819
  else:
650
820
  fileName = 'AOSignals_withoutTumor.cdh'
651
821
 
652
- # Check if input file exists
653
- if not os.path.isfile(f"{self.saveDir}/{fileName}"):
654
- self.experiment._saveAOsignals_Castor(self.saveDir)
655
- # Check if system matrix directory exists
656
- elif not os.path.isdir(smatrix):
657
- os.mkdir(smatrix)
658
- # check if system matrix is empty
659
- elif not os.listdir(smatrix):
822
+ # Vérifier et générer les fichiers d'entrée si nécessaire
823
+ if not os.path.isfile(os.path.join(self.saveDir, fileName)):
824
+ if show_logs:
825
+ print(f"Fichier .cdh manquant. Génération de {fileName}...")
826
+ self.experiment.saveAOsignals_Castor(self.saveDir)
827
+
828
+ # Vérifier/générer la matrice système
829
+ if not os.path.isdir(smatrix):
830
+ os.makedirs(smatrix, exist_ok=True)
831
+ if not os.listdir(smatrix):
832
+ if show_logs:
833
+ print("Matrice système manquante. Génération...")
660
834
  self.experiment.saveAcousticFields(self.saveDir)
661
835
 
662
- # Vérifier que le dossier de sortie existe
663
- os.makedirs(os.path.join(self.saveDir, 'results','recon'), exist_ok=True)
836
+ # Vérifier que le fichier .cdh existe (redondant mais sûr)
837
+ if not os.path.isfile(os.path.join(self.saveDir, fileName)):
838
+ raise FileNotFoundError(f"Le fichier .cdh n'existe toujours pas : {fileName}")
664
839
 
665
- # Vérifier que le fichier .cdh existe
666
- if not os.path.isfile(fileName):
667
- raise FileNotFoundError(f"Le fichier .cdh n'existe pas : {fileName}")
840
+ # Créer le dossier de sortie
841
+ os.makedirs(os.path.join(self.saveDir, 'results', 'recon'), exist_ok=True)
668
842
 
669
- # Créer une copie de l'environnement actuel et ajouter les variables CAStOR
843
+ # Configuration de l'environnement pour CASToR
670
844
  env = os.environ.copy()
671
-
672
845
  env.update({
673
- "CASTOR_DIR": f"{self.experiment.params.reconstruction['castor_executable']}",
674
- "CASTOR_CONFIG": f"{self.experiment.params.reconstruction['castor_executable']}/config",
846
+ "CASTOR_DIR": self.experiment.params.reconstruction['castor_executable'],
847
+ "CASTOR_CONFIG": os.path.join(self.experiment.params.reconstruction['castor_executable'], "config"),
675
848
  "CASTOR_64bits": "1",
676
849
  "CASTOR_OMP": "1",
677
850
  "CASTOR_SIMD": "1",
@@ -680,13 +853,13 @@ class AlgebraicRecon(Recon):
680
853
 
681
854
  # Construire la commande
682
855
  cmd = [
683
- f"{self.experiment.params.reconstruction['castor_executable']}/bin/castor-recon",
684
- "-df", f"{self.saveDir}/{fileName}",
856
+ os.path.join(self.experiment.params.reconstruction['castor_executable'], "bin", "castor-recon"),
857
+ "-df", os.path.join(self.saveDir, fileName),
685
858
  "-opti", self.optimizer.value,
686
859
  "-it", f"{self.numIterations}:{self.numSubsets}",
687
860
  "-proj", "matrix",
688
- "-dout", os.path.join(self.saveDir, 'results','recon'),
689
- "-th", f"{ os.cpu_count()}",
861
+ "-dout", os.path.join(self.saveDir, 'results', 'recon'),
862
+ "-th", str(os.cpu_count()),
690
863
  "-vb", "5",
691
864
  "-proj-comp", "1",
692
865
  "-ignore-scanner",
@@ -695,90 +868,38 @@ class AlgebraicRecon(Recon):
695
868
  "-system-matrix", smatrix,
696
869
  ]
697
870
 
698
- # Print the command
699
- print(" ".join(cmd))
871
+ # Afficher la commande (pour débogage)
872
+ if show_logs:
873
+ print("Commande CASToR :")
874
+ print(" ".join(cmd))
700
875
 
701
- #save the command to a script file
876
+ # Chemin du script temporaire
702
877
  recon_script_path = os.path.join(gettempdir(), 'recon.sh')
878
+
879
+ # Écrire le script bash
703
880
  with open(recon_script_path, 'w') as f:
704
881
  f.write("#!/bin/bash\n")
882
+ f.write(f"export PATH={env['CASTOR_DIR']}/bin:$PATH\n") # Ajoute le chemin de CASToR au PATH
883
+ f.write(f"export LD_LIBRARY_PATH={env['CASTOR_DIR']}/lib:$LD_LIBRARY_PATH\n") # Ajoute les bibliothèques si nécessaire
705
884
  f.write(" ".join(cmd) + "\n")
706
885
 
707
- sys.exit(0)
708
-
709
- # --- Run Reconstruction Script ---
710
- print(f"Running reconstruction script: {recon_script_path}")
886
+ # Rendre le script exécutable et l'exécuter
711
887
  subprocess.run(["chmod", "+x", recon_script_path], check=True)
712
- subprocess.run([recon_script_path], check=True)
713
- print("Reconstruction script executed.")
714
-
888
+ if show_logs:
889
+ print(f"Exécution de la reconstruction avec CASToR...")
890
+ result = subprocess.run(recon_script_path, env=env, check=True, capture_output=True, text=True)
891
+
892
+ # Afficher la sortie de CASToR (pour débogage)
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.")
715
902
  self.load_reconCASToR(withTumor=withTumor)
716
-
717
- def _MLEM(self, SMatrix, y, withTumor):
718
- """
719
- This method implements the MLEM algorithm using either CPU or single-GPU PyTorch acceleration.
720
- Multi-GPU mode is disabled due to memory fragmentation issues or lack of availability.
721
- """
722
- result = None
723
- indices = None
724
- required_memory = calculate_memory_requirement(SMatrix, y)
725
-
726
- if self.isGPU:
727
- if check_gpu_memory(config.select_best_gpu(), required_memory):
728
- result, indices = MLEM._MLEM_GPU_basic(SMatrix=SMatrix, y=y, numIterations=self.numIterations, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor)
729
- else:
730
- warnings.warn("Insufficient GPU memory for single GPU MLEM. Falling back to CPU.")
731
-
732
- if result is None and self.isMultiCPU:
733
- result, indices = MLEM._MLEM_CPU_multi(SMatrix=SMatrix, y=y, numIterations=self.numIterations, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor)
734
-
735
- if result is None:
736
- result, indices = MLEM._MLEM_CPU_opti(SMatrix=SMatrix, y=y, numIterations=self.numIterations, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor)
737
- if result is None:
738
- warnings.warn("Optimized MLEM failed. Falling back to basic CPU MLEM.")
739
- result, indices = MLEM._MLEM_CPU_basic(SMatrix=SMatrix, y=y, numIterations=self.numIterations, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor)
740
-
741
- return result, indices
742
-
743
- def _LS(self, SMatrix, y, withTumor):
744
- """
745
- This method implements the LS algorithm using either CPU or single-GPU PyTorch acceleration.
746
- Multi-GPU mode is disabled due to memory fragmentation issues or lack of availability.
747
- """
748
- result = None
749
- indices = None
750
- required_memory = calculate_memory_requirement(SMatrix, y)
751
-
752
- if self.isGPU:
753
- if check_gpu_memory(config.select_best_gpu(), required_memory):
754
- result, indices = LS._LS_GPU_basic(SMatrix=SMatrix, y=y, numIterations=self.numIterations, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor)
755
- else:
756
- warnings.warn("Insufficient GPU memory for single GPU LS. Falling back to CPU.")
757
-
758
- if result is None and self.isMultiCPU:
759
- result, indices = LS._LS_CPU_multi(SMatrix=SMatrix, y=y, numIterations=self.numIterations, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor)
760
-
761
- if result is None:
762
- result, indices = LS._LS_CPU_opti(SMatrix=SMatrix, y=y, numIterations=self.numIterations, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor)
763
- if result is None:
764
- warnings.warn("Optimized LS failed. Falling back to basic CPU LS.")
765
- result, indices = LS._LS_CPU_basic(SMatrix=SMatrix, y=y, numIterations=self.numIterations, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor)
766
-
767
- return result, indices
768
-
769
- def _LS_Regularized(self,SMatrix, y, withTumor):
770
- result = None
771
- indices = None
772
- required_memory = calculate_memory_requirement(SMatrix, y)
773
-
774
- if check_gpu_memory(config.select_best_gpu(), required_memory):
775
- if self.lambda_reg is None or self.L_Factor is None:
776
- raise ValueError("For LS with TV regularization, both lambda_reg and L_Factor must be specified.")
777
- result, indices = LS._LS_TV_GPU(SMatrix= SMatrix, y=y, numIterations=self.numIterations, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor, lambda_tv=self.lambda_reg, L_Factor=self.L_Factor)
778
- else:
779
- warnings.warn("Insufficient GPU memory for single GPU LS with TV regularization. Falling back to CPU.")
780
- raise NotImplementedError("LS with TV regularization is not implemented for CPU.")
781
- return result, indices
782
903
 
783
904
  # STATIC METHODS
784
905
  @staticmethod