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
@@ -22,7 +22,7 @@ class AnalyticRecon(Recon):
22
22
  else:
23
23
  raise ValueError(f"Unknown analytic reconstruction type: {processType}")
24
24
 
25
- def checkExistingFile(self, withTumor=True, overwrite=False):
25
+ def checkExistingFile(self, date = None):
26
26
  raise NotImplementedError("checkExistingFile method is not implemented yet.")
27
27
 
28
28
  def _analyticReconPython(self,withTumor):
@@ -51,56 +51,41 @@ class AnalyticRecon(Recon):
51
51
  def _iFourierRecon(self, AOsignal):
52
52
  """
53
53
  Reconstruction d'image utilisant la transformation de Fourier inverse.
54
-
55
- :param AOsignal: Signal dans le domaine temporel.
54
+ :param AOsignal: Signal dans le domaine temporel (shape: N_t, N_theta).
56
55
  :return: Image reconstruite dans le domaine spatial.
57
56
  """
58
- # Signal dans le domaine fréquentiel (FFT sur l'axe temporel)
59
- s_tilde = np.fft.fft(AOsignal, axis=0)
60
-
61
- theta = np.array([af.angle for af in self.experiment.AcousticFields]) # angles (N_theta,)
62
- f_s = np.array([af.f_s for af in self.experiment.AcousticFields]) # spatial freqs (N_theta,)
63
- f_t = np.fft.fftfreq(AOsignal.shape[0], d=self.experiment.dt) # temporal freqs
64
-
57
+ theta = np.array([af.angle for af in self.experiment.AcousticFields])
58
+ f_s = np.array([af.f_s for af in self.experiment.AcousticFields])
59
+ dt = self.experiment.dt
60
+ f_t = np.fft.fftfreq(AOsignal.shape[0], d=dt) # fréquences temporelles
65
61
  x = self.experiment.OpticImage.laser.x
66
62
  z = self.experiment.OpticImage.laser.z
67
- X, Z = np.meshgrid(x, z, indexing='ij') # shape (Nx, Nz)
68
-
69
- N_theta = len(theta)
70
- I_rec = np.zeros((len(x), len(z)), dtype=complex)
71
-
72
- for i, th in enumerate(trange(N_theta, desc="AOT-BioMaps -- Analytic Recontruction Tomography : iFourier (Processing projection) ---- processing on single CPU ----")):
73
- fs = f_s[i]
74
-
75
- # Projection des coordonnées dans le repère tourné
76
- x_prime = X * np.cos(th) + Z * np.sin(th)
77
- z_prime = -X * np.sin(th) + Z * np.cos(th)
78
-
79
- # Signal spectral pour cet angle (1D pour chaque f_t)
80
- s_angle = s_tilde[:, i] # shape (len(f_t),)
63
+ X, Z = np.meshgrid(x, z, indexing='ij') # grille spatiale (Nx, Nz)
81
64
 
82
- # Grille 2D des fréquences
83
- F_t, F_s = np.meshgrid(f_t, [fs], indexing='ij') # F_t: (len(f_t), 1), F_s: (1, 1)
65
+ # Transformée de Fourier du signal
66
+ s_tilde = np.fft.fft(AOsignal, axis=0) # shape: (N_t, N_theta)
84
67
 
85
- # Phase : exp(2iπ(x' f_s + z' f_t)) = (x_prime * f_s + z_prime * f_t)
86
- phase = 2j * np.pi * (x_prime[:, :, None] * fs + z_prime[:, :, None] * f_t[None, None, :])
87
-
88
- # reshape s_angle to (len(f_t), 1, 1)
89
- s_angle = s_angle[:, None, None]
90
-
91
- # Contribution de cet angle
92
- integrand = s_angle * np.exp(phase)
93
-
94
- # Intégration sur f_t (somme discrète)
95
- I_theta = np.sum(integrand, axis=0)
96
-
97
- # Ajout à la reconstruction
98
- I_rec += I_theta
99
-
100
- I_rec /= N_theta
68
+ # Initialisation de l'image reconstruite
69
+ I_rec = np.zeros((len(x), len(z)), dtype=complex)
101
70
 
71
+ # Boucle sur les angles
72
+ for i, th in enumerate(trange(len(theta), desc="AOT-BioMaps -- iFourier Reconstruction")):
73
+ # Coordonnées tournées
74
+ X_prime = X * np.cos(th) + Z * np.sin(th)
75
+ Z_prime = -X * np.sin(th) + Z * np.cos(th)
76
+
77
+ # Pour chaque fréquence temporelle f_t[j]
78
+ for j in range(len(f_t)):
79
+ # Phase: exp(2jπ (X_prime * f_s[i] + Z_prime * f_t[j]))
80
+ phase = 2j * np.pi * (X_prime * f_s[i] + Z_prime * f_t[j])
81
+ # Contribution de cette fréquence
82
+ I_rec += s_tilde[j, i] * np.exp(phase) * dt # Pondération par dt pour l'intégration
83
+
84
+ # Normalisation
85
+ I_rec /= len(theta)
102
86
  return np.abs(I_rec)
103
87
 
88
+
104
89
  def _iRadonRecon(self, AOsignal):
105
90
  """
106
91
  Reconstruction d'image utilisant la méthode iRadon.
@@ -1,7 +1,7 @@
1
1
  from AOT_biomaps.AOT_Recon.AlgebraicRecon import AlgebraicRecon
2
2
  from AOT_biomaps.AOT_Recon.ReconEnums import ReconType, OptimizerType, PotentialType, ProcessType
3
3
  from .ReconTools import check_gpu_memory, calculate_memory_requirement
4
- from .AOT_Optimizers import MAPEM, DEPIERRO
4
+ from .AOT_Optimizers import MAPEM, MAPEM_STOP, DEPIERRO
5
5
  from AOT_biomaps.Config import config
6
6
 
7
7
  import warnings
@@ -38,7 +38,7 @@ class BayesianRecon(AlgebraicRecon):
38
38
  if not isinstance(self.potentialFunction, PotentialType):
39
39
  raise TypeError(f"Potential functions must be of type PotentialType, got {type(self.potentialFunction)}")
40
40
 
41
- def checkExistingFile(self, date=None, withTumor=True):
41
+ def checkExistingFile(self, date = None):
42
42
  """
43
43
  Check if the reconstruction file already exists, based on current instance parameters.
44
44
 
@@ -66,27 +66,18 @@ class BayesianRecon(AlgebraicRecon):
66
66
  dir_name += f'_Beta_{self.beta}_Sigma_{self.sigma}'
67
67
 
68
68
  results_dir = os.path.join(self.saveDir, dir_name)
69
- recon_key = 'reconPhantom' if withTumor else 'reconLaser'
70
- recon_filepath = os.path.join(results_dir, f'{recon_key}.npy')
69
+ if not os.path.exists(results_dir):
70
+ os.makedirs(results_dir)
71
71
 
72
- if os.path.exists(recon_filepath):
73
- return (True, recon_filepath)
72
+ if os.path.exists(os.path.join(results_dir,"indices.npy")):
73
+ return (True, results_dir)
74
74
 
75
- return (False, recon_filepath)
75
+ return (False, results_dir)
76
76
 
77
- def load(self, withTumor=True, results_date=None, optimizer=None, potential_function=None, beta=None, delta=None, gamma=None, sigma=None, filePath=None):
77
+ def load(self, withTumor=True, results_date=None, optimizer=None, potential_function=None, filePath=None, show_logs=True):
78
78
  """
79
- Load the reconstruction results and indices for Bayesian reconstruction and store them in self.
80
- Args:
81
- withTumor (bool): If True, loads the reconstruction with tumor; otherwise, loads the reconstruction without tumor.
82
- results_date (str): Date string (format "ddmm") to specify which results to load. If None, uses the most recent date in saveDir.
83
- optimizer (OptimizerType): Optimizer type to filter results. If None, uses the current optimizer of the instance.
84
- potential_function (PotentialType): Potential function type to filter results. If None, uses the current potential function of the instance.
85
- beta (float): Beta parameter to match the saved directory. If None, skips this filter.
86
- delta (float): Delta parameter to match the saved directory. If None, skips this filter.
87
- gamma (float): Gamma parameter to match the saved directory. If None, skips this filter.
88
- sigma (float): Sigma parameter to match the saved directory. If None, skips this filter.
89
- filePath (str): Optional. If provided, loads directly from this path (overrides saveDir and results_date).
79
+ Load the reconstruction results and indices as lists of 2D np arrays for Bayesian reconstruction and store them in self.
80
+ If the loaded file is a 3D array, it is split into a list of 2D arrays.
90
81
  """
91
82
  if filePath is not None:
92
83
  # Mode chargement direct depuis un fichier
@@ -94,53 +85,55 @@ class BayesianRecon(AlgebraicRecon):
94
85
  recon_path = filePath
95
86
  if not os.path.exists(recon_path):
96
87
  raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
97
-
98
- if withTumor:
99
- self.reconPhantom = np.load(recon_path, allow_pickle=True)
88
+ # Charge le fichier (3D ou liste de 2D)
89
+ data = np.load(recon_path, allow_pickle=True)
90
+ # Découpe en liste de 2D si c'est un tableau 3D
91
+ if isinstance(data, np.ndarray) and data.ndim == 3:
92
+ if withTumor:
93
+ self.reconPhantom = [data[i, :, :] for i in range(data.shape[0])]
94
+ else:
95
+ self.reconLaser = [data[i, :, :] for i in range(data.shape[0])]
100
96
  else:
101
- self.reconLaser = np.load(recon_path, allow_pickle=True)
102
-
103
- # Essayer de charger les indices (fichier avec suffixe "_indices.npy")
104
- base_dir, file_name = os.path.split(recon_path)
105
- file_base, _ = os.path.splitext(file_name)
106
- indices_path = os.path.join(base_dir, f"{file_base}_indices.npy")
97
+ # Sinon, suppose que c'est déjà une liste de 2D
98
+ if withTumor:
99
+ self.reconPhantom = data
100
+ else:
101
+ self.reconLaser = data
102
+ # Essayer de charger les indices
103
+ base_dir, _ = os.path.split(recon_path)
104
+ indices_path = os.path.join(base_dir, 'indices.npy')
107
105
  if os.path.exists(indices_path):
108
- self.indices = np.load(indices_path, allow_pickle=True)
109
- else:
110
- indices_path = os.path.join(base_dir, 'reconIndices.npy') # Alternative
111
- if os.path.exists(indices_path):
112
- self.indices = np.load(indices_path, allow_pickle=True)
106
+ indices_data = np.load(indices_path, allow_pickle=True)
107
+ if isinstance(indices_data, np.ndarray) and indices_data.ndim == 3:
108
+ self.indices = [indices_data[i, :, :] for i in range(indices_data.shape[0])]
113
109
  else:
114
- self.indices = None
115
-
116
- print(f"Loaded reconstruction results and indices from {recon_path}")
110
+ self.indices = indices_data
111
+ else:
112
+ self.indices = None
113
+ if show_logs:
114
+ print(f"Loaded reconstruction results and indices from {recon_path}")
117
115
  else:
118
116
  # Mode chargement depuis le répertoire de résultats
119
117
  if self.saveDir is None:
120
118
  raise ValueError("Save directory is not specified. Please set saveDir before loading.")
121
-
122
119
  # Use current optimizer and potential function if not provided
123
120
  opt_name = optimizer.value if optimizer is not None else self.optimizer.value
124
121
  pot_name = potential_function.value if potential_function is not None else self.potentialFunction.value
125
-
126
122
  # Build the base directory pattern
127
123
  dir_pattern = f'results_*_{opt_name}_{pot_name}'
128
-
129
124
  # Add parameters to the pattern based on the optimizer
130
125
  if optimizer is None:
131
126
  optimizer = self.optimizer
132
-
133
127
  if optimizer == OptimizerType.PPGMLEM:
134
- beta_str = f'_Beta_{beta if beta is not None else self.beta}'
135
- delta_str = f'_Delta_{delta if delta is not None else self.delta}'
136
- gamma_str = f'_Gamma_{gamma if gamma is not None else self.gamma}'
137
- sigma_str = f'_Sigma_{sigma if sigma is not None else self.sigma}'
128
+ beta_str = f'_Beta_{self.beta}'
129
+ delta_str = f'_Delta_{self.delta}'
130
+ gamma_str = f'_Gamma_{self.gamma}'
131
+ sigma_str = f'_Sigma_{self.sigma}'
138
132
  dir_pattern += f'{beta_str}{delta_str}{gamma_str}{sigma_str}'
139
133
  elif optimizer in (OptimizerType.PGC, OptimizerType.DEPIERRO95):
140
- beta_str = f'_Beta_{beta if beta is not None else self.beta}'
141
- sigma_str = f'_Sigma_{sigma if sigma is not None else self.sigma}'
134
+ beta_str = f'_Beta_{self.beta}'
135
+ sigma_str = f'_Sigma_{self.sigma}'
142
136
  dir_pattern += f'{beta_str}{sigma_str}'
143
-
144
137
  # Find the most recent results directory if no date is specified
145
138
  if results_date is None:
146
139
  dirs = [d for d in os.listdir(self.saveDir) if os.path.isdir(os.path.join(self.saveDir, d)) and dir_pattern in d]
@@ -151,33 +144,40 @@ class BayesianRecon(AlgebraicRecon):
151
144
  else:
152
145
  results_dir = os.path.join(self.saveDir, f'results_{results_date}_{opt_name}_{pot_name}')
153
146
  if optimizer == OptimizerType.PPGMLEM:
154
- results_dir += f'_Beta_{beta if beta is not None else self.beta}_Delta_{delta if delta is not None else self.delta}_Gamma_{gamma if gamma is not None else self.gamma}_Sigma_{sigma if sigma is not None else self.sigma}'
147
+ results_dir += f'_Beta_{self.beta}_Delta_{self.delta}_Gamma_{self.gamma}_Sigma_{self.sigma}'
155
148
  elif optimizer in (OptimizerType.PGC, OptimizerType.DEPIERRO95):
156
- results_dir += f'_Beta_{beta if beta is not None else self.beta}_Sigma_{sigma if sigma is not None else self.sigma}'
149
+ results_dir += f'_Beta_{self.beta}_Sigma_{self.sigma}'
157
150
  if not os.path.exists(results_dir):
158
151
  raise FileNotFoundError(f"Directory {results_dir} does not exist.")
159
-
160
152
  # Load reconstruction results
161
153
  recon_key = 'reconPhantom' if withTumor else 'reconLaser'
162
154
  recon_path = os.path.join(results_dir, f'{recon_key}.npy')
163
155
  if not os.path.exists(recon_path):
164
156
  raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
165
-
166
- if withTumor:
167
- self.reconPhantom = np.load(recon_path, allow_pickle=True)
157
+ data = np.load(recon_path, allow_pickle=True)
158
+ if isinstance(data, np.ndarray) and data.ndim == 3:
159
+ if withTumor:
160
+ self.reconPhantom = [data[i, :, :] for i in range(data.shape[0])]
161
+ else:
162
+ self.reconLaser = [data[i, :, :] for i in range(data.shape[0])]
168
163
  else:
169
- self.reconLaser = np.load(recon_path, allow_pickle=True)
170
-
171
- # Load saved indices
172
- indices_path = os.path.join(results_dir, 'reconIndices.npy')
164
+ if withTumor:
165
+ self.reconPhantom = data
166
+ else:
167
+ self.reconLaser = data
168
+ # Load saved indices as list of 2D arrays
169
+ indices_path = os.path.join(results_dir, 'indices.npy')
173
170
  if not os.path.exists(indices_path):
174
171
  raise FileNotFoundError(f"No indices file found at {indices_path}.")
172
+ indices_data = np.load(indices_path, allow_pickle=True)
173
+ if isinstance(indices_data, np.ndarray) and indices_data.ndim == 3:
174
+ self.indices = [indices_data[i, :, :] for i in range(indices_data.shape[0])]
175
+ else:
176
+ self.indices = indices_data
177
+ if show_logs:
178
+ print(f"Loaded reconstruction results and indices from {results_dir}")
175
179
 
176
- self.indices = np.load(indices_path, allow_pickle=True)
177
-
178
- print(f"Loaded reconstruction results and indices from {results_dir}")
179
-
180
- def run(self, processType=ProcessType.PYTHON, withTumor=True):
180
+ def run(self, processType=ProcessType.PYTHON, withTumor=True, show_logs=True):
181
181
  """
182
182
  This method is a placeholder for the Bayesian reconstruction process.
183
183
  It currently does not perform any operations but serves as a template for future implementations.
@@ -189,109 +189,42 @@ class BayesianRecon(AlgebraicRecon):
189
189
  else:
190
190
  raise ValueError(f"Unknown Bayesian reconstruction type: {processType}")
191
191
 
192
- def _bayesianReconCASToR(self, withTumor):
192
+ def _bayesianReconCASToR(self, show_logs, withTumor):
193
193
  raise NotImplementedError("CASToR Bayesian reconstruction is not implemented yet.")
194
194
 
195
- def _bayesianReconPython(self, withTumor):
196
-
195
+ def _bayesianReconPython(self, show_logs, withTumor):
197
196
  if withTumor:
198
197
  if self.experiment.AOsignal_withTumor is None:
199
198
  raise ValueError("AO signal with tumor is not available. Please generate AO signal with tumor the experiment first in the experiment object.")
200
199
  if self.optimizer.value == OptimizerType.PPGMLEM.value:
201
- self.reconPhantom, self.indices = self._MAPEM_STOP(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withTumor, withTumor=withTumor)
200
+ self.reconPhantom, self.indices = MAPEM_STOP(
201
+ SMatrix=self.SMatrix,
202
+ y=self.experiment.AOsignal_withTumor,
203
+ Omega=self.potentialFunction,
204
+ beta=self.beta,
205
+ delta=self.delta,
206
+ gamma=self.gamma,
207
+ sigma=self.sigma,
208
+ numIterations=self.numIterations,
209
+ isSavingEachIteration=self.isSavingEachIteration,
210
+ withTumor=withTumor,
211
+ device=self.device,
212
+ max_saves=5000,
213
+ show_logs=True)
202
214
  elif self.optimizer.value == OptimizerType.PGC.value:
203
- self.reconPhantom, self.indices = self._MAPEM(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withTumor, withTumor=withTumor)
215
+ self.reconPhantom, self.indices = MAPEM(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withTumor, withTumor=withTumor, show_logs=show_logs)
204
216
  elif self.optimizer.value == OptimizerType.DEPIERRO95.value:
205
- self.reconPhantom, self.indices = self._DEPIERRO(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withTumor, withTumor=withTumor)
217
+ self.reconPhantom, self.indices = DEPIERRO(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withTumor, withTumor=withTumor, show_logs=show_logs)
206
218
  else:
207
219
  raise ValueError(f"Unknown optimizer type: {self.optimizer.value}")
208
220
  else:
209
221
  if self.experiment.AOsignal_withoutTumor is None:
210
222
  raise ValueError("AO signal without tumor is not available. Please generate AO signal without tumor the experiment first in the experiment object.")
211
223
  if self.optimizer.value == OptimizerType.PPGMLEM.value:
212
- self.reconLaser, self.indices = self._MAPEM_STOP(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withoutTumor, withTumor=withTumor)
224
+ self.reconLaser, self.indices = MAPEM_STOP(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withoutTumor, withTumor=withTumor, show_logs=show_logs)
213
225
  elif self.optimizer.value == OptimizerType.PGC.value:
214
- self.reconLaser, self.indices = self._MAPEM(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withoutTumor, withTumor=withTumor)
226
+ self.reconLaser, self.indices = MAPEM(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withoutTumor, withTumor=withTumor, show_logs=show_logs)
215
227
  elif self.optimizer.value == OptimizerType.DEPIERRO95.value:
216
- self.reconLaser, self.indices = self._DEPIERRO(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withoutTumor, withTumor=withTumor)
228
+ self.reconLaser, self.indices = DEPIERRO(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withoutTumor, withTumor=withTumor, show_logs=show_logs)
217
229
  else:
218
- raise ValueError(f"Unknown optimizer type: {self.optimizer.value}")
219
-
220
- def _MAPEM_STOP(self, SMatrix, y, withTumor):
221
- """
222
- This method implements the MAPEM_STOP algorithm using either CPU or single-GPU PyTorch acceleration.
223
- Multi-GPU and Multi-CPU modes are not implemented for this algorithm.
224
- """
225
- result = None
226
- required_memory = calculate_memory_requirement(SMatrix, y)
227
-
228
- if self.isGPU:
229
- if check_gpu_memory(config.select_best_gpu(), required_memory):
230
- try:
231
- result = MAPEM._MAPEM_GPU_STOP(SMatrix=SMatrix, y=y, Omega=self.potentialFunction, numIterations=self.numIterations, beta=self.beta, delta=self.delta, gamma=self.gamma, sigma=self.sigma, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor)
232
- except Exception as e:
233
- warnings.warn(f"Falling back to CPU implementation due to an error in GPU implementation: {e}")
234
- else:
235
- warnings.warn("Insufficient GPU memory for single GPU MAPEM_STOP. Falling back to CPU.")
236
-
237
- if result is None:
238
- try:
239
- result = MAPEM._MAPEM_CPU_STOP(SMatrix=SMatrix, y=y, Omega=self.potentialFunction, numIterations=self.numIterations, beta=self.beta, delta=self.delta, gamma=self.gamma, sigma=self.sigma, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor)
240
- except Exception as e:
241
- warnings.warn(f"An error occurred in CPU implementation: {e}")
242
- result = None
243
-
244
- return result
245
-
246
- def _MAPEM(self, SMatrix, y, withTumor):
247
- """
248
- This method implements the MAPEM algorithm using either CPU or single-GPU PyTorch acceleration.
249
- Multi-GPU and Multi-CPU modes are not implemented for this algorithm.
250
- """
251
- result = None
252
- required_memory = calculate_memory_requirement(SMatrix, y)
253
-
254
- if self.isGPU:
255
- if check_gpu_memory(config.select_best_gpu(), required_memory):
256
- try:
257
- result = MAPEM._MAPEM_GPU(SMatrix=SMatrix, y=y, Omega=self.potentialFunction, numIterations=self.numIterations, beta=self.beta, delta=self.delta, gamma=self.gamma, sigma=self.sigma, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor)
258
- except Exception as e:
259
- warnings.warn(f"Falling back to CPU implementation due to an error in GPU implementation: {e}")
260
- else:
261
- warnings.warn("Insufficient GPU memory for single GPU MAPEM. Falling back to CPU.")
262
-
263
- if result is None:
264
- try:
265
- result = MAPEM._MAPEM_CPU(SMatrix=SMatrix, y=y, Omega=self.potentialFunction, numIterations=self.numIterations, beta=self.beta, delta=self.delta, gamma=self.gamma, sigma=self.sigma, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor)
266
- except Exception as e:
267
- warnings.warn(f"An error occurred in CPU implementation: {e}")
268
- result = None
269
-
270
- return result
271
-
272
- def _DEPIERRO(self, SMatrix, y, withTumor):
273
- """
274
- This method implements the DEPIERRO algorithm using either CPU or single-GPU PyTorch acceleration.
275
- Multi-GPU and Multi-CPU modes are not implemented for this algorithm.
276
- """
277
- result = None
278
- required_memory = calculate_memory_requirement(SMatrix, y)
279
-
280
- if self.isGPU:
281
- if check_gpu_memory(config.select_best_gpu(), required_memory):
282
- try:
283
- result = DEPIERRO._DEPIERRO_GPU(SMatrix=SMatrix, y=y, Omega=self.potentialFunction, numIterations=self.numIterations, beta=self.beta, sigma=self.sigma, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor)
284
- except Exception as e:
285
- warnings.warn(f"Falling back to CPU implementation due to an error in GPU implementation: {e}")
286
- else:
287
- warnings.warn("Insufficient GPU memory for single GPU DEPIERRO. Falling back to CPU.")
288
-
289
- if result is None:
290
- try:
291
- result = DEPIERRO._DEPIERRO_CPU(SMatrix=SMatrix, y=y, Omega=self.potentialFunction, numIterations=self.numIterations, beta=self.beta, sigma=self.sigma, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor)
292
- except Exception as e:
293
- warnings.warn(f"An error occurred in CPU implementation: {e}")
294
- result = None
295
-
296
- return result
297
-
230
+ raise ValueError(f"Unknown optimizer type: {self.optimizer.value}")
@@ -31,5 +31,5 @@ class DeepLearningRecon(Recon):
31
31
  def _deepLearningReconPython(self):
32
32
  pass
33
33
 
34
- def checkExistingFile(self, withTumor=True, overwrite=False):
34
+ def checkExistingFile(self, date = None):
35
35
  raise NotImplementedError("checkExistingFile method is not implemented yet.")
@@ -6,16 +6,16 @@ from AOT_biomaps.AOT_Recon.ReconEnums import OptimizerType
6
6
  import os
7
7
  from datetime import datetime
8
8
  import numpy as np
9
+ import re
9
10
 
10
11
  class PrimalDualRecon(AlgebraicRecon):
11
12
  """
12
13
  This class implements the convex reconstruction process.
13
14
  It currently does not perform any operations but serves as a template for future implementations.
14
15
  """
15
- def __init__(self, alpha, theta=1.0, L=None, **kwargs):
16
+ def __init__(self, theta=1.0, L=None, **kwargs):
16
17
  super().__init__(**kwargs)
17
18
  self.reconType = ReconType.Convex
18
- self.alpha = alpha # regularization parameter
19
19
  self.theta = theta # relaxation parameter (between 1 and 2)
20
20
  self.L = L # norme spectrale de l'opérateur linéaire défini par les matrices P et P^T
21
21
 
@@ -35,40 +35,34 @@ class PrimalDualRecon(AlgebraicRecon):
35
35
  raise NotImplementedError("CASToR convex reconstruction is not implemented yet.")
36
36
 
37
37
 
38
- def checkExistingFile(self, date = None, withTumor=True):
38
+ def checkExistingFile(self, date = None):
39
39
  """
40
40
  Check if the file already exists, based on current instance parameters.
41
41
  Returns:
42
42
  tuple: (bool: whether to save, str: the filepath)
43
43
  """
44
- date = datetime.now().strftime("%d%m")
44
+ if date is None:
45
+ date = datetime.now().strftime("%d%m")
45
46
  results_dir = os.path.join(
46
47
  self.saveDir,
47
48
  f'results_{date}_{self.optimizer.value}_Alpha_{self.alpha}_Theta_{self.theta}_L_{self.L}'
48
49
  )
49
50
  os.makedirs(results_dir, exist_ok=True)
50
51
 
51
- filename = 'reconPhantom.npy' if withTumor else 'reconLaser.npy'
52
- filepath = os.path.join(results_dir, filename)
52
+ if os.path.exists(os.path.join(results_dir,"indices.npy")):
53
+ return (True, results_dir)
53
54
 
54
- if os.path.exists(filepath):
55
- return (True, filepath)
55
+ return (False, results_dir)
56
56
 
57
- return (False, filepath)
58
-
59
-
60
-
61
- def load(self, withTumor=True, results_date=None, optimizer=None, alpha=None, theta=None, L=None, filePath=None):
57
+ def load(self, withTumor=True, results_date=None, optimizer=None, filePath=None, show_logs=True):
62
58
  """
63
- Load the reconstruction results and indices and store them in self.
59
+ Load the reconstruction results (reconPhantom or reconLaser) and indices as lists of 2D np arrays into self.
60
+ If the loaded file is a 3D array, it is split into a list of 2D arrays.
64
61
  Args:
65
- withTumor (bool): If True, loads the reconstruction with tumor; otherwise, loads the reconstruction without tumor.
66
- results_date (str): Date string (format "ddmm") to specify which results to load. If None, uses the most recent date in saveDir.
67
- optimizer (OptimizerType): Optimizer type to filter results. If None, uses the current optimizer of the instance.
68
- alpha (float): Alpha parameter to match the saved directory. If None, uses the current alpha of the instance.
69
- theta (float): Theta parameter to match the saved directory. If None, uses the current theta of the instance.
70
- L (float): L parameter to match the saved directory. If None, uses the current L of the instance.
71
- filePath (str): Optional. If provided, loads directly from this path (overrides saveDir and results_date).
62
+ withTumor: If True, loads reconPhantom (with tumor), else reconLaser (without tumor).
63
+ results_date: Date string (format "ddmm") to specify which results to load. If None, uses the most recent date in saveDir.
64
+ optimizer: Optimizer name (as string or enum) to filter results. If None, uses the current optimizer of the instance.
65
+ filePath: Optional. If provided, loads directly from this path (overrides saveDir and results_date).
72
66
  """
73
67
  if filePath is not None:
74
68
  # Mode chargement direct depuis un fichier
@@ -76,73 +70,86 @@ class PrimalDualRecon(AlgebraicRecon):
76
70
  recon_path = filePath
77
71
  if not os.path.exists(recon_path):
78
72
  raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
79
-
80
- if withTumor:
81
- self.reconPhantom = np.load(recon_path, allow_pickle=True)
73
+ # Charge les données
74
+ data = np.load(recon_path, allow_pickle=True)
75
+ # Découpe en liste de 2D si c'est un tableau 3D
76
+ if isinstance(data, np.ndarray) and data.ndim == 3:
77
+ if withTumor:
78
+ self.reconPhantom = [data[i, :, :] for i in range(data.shape[0])]
79
+ else:
80
+ self.reconLaser = [data[i, :, :] for i in range(data.shape[0])]
82
81
  else:
83
- self.reconLaser = np.load(recon_path, allow_pickle=True)
84
-
85
- # Essayer de charger les indices (fichier avec suffixe "_indices.npy" ou "reconIndices.npy")
86
- base_dir, file_name = os.path.split(recon_path)
87
- file_base, _ = os.path.splitext(file_name)
88
- indices_path = os.path.join(base_dir, f"{file_base}_indices.npy")
89
- if not os.path.exists(indices_path):
90
- indices_path = os.path.join(base_dir, 'reconIndices.npy') # Alternative
91
-
82
+ # Sinon, suppose que c'est déjà une liste de 2D
83
+ if withTumor:
84
+ self.reconPhantom = data
85
+ else:
86
+ self.reconLaser = data
87
+ # Essayer de charger les indices
88
+ base_dir, _ = os.path.split(recon_path)
89
+ indices_path = os.path.join(base_dir, "indices.npy")
92
90
  if os.path.exists(indices_path):
93
- self.indices = np.load(indices_path, allow_pickle=True)
91
+ indices_data = np.load(indices_path, allow_pickle=True)
92
+ if isinstance(indices_data, np.ndarray) and indices_data.ndim == 3:
93
+ self.indices = [indices_data[i, :, :] for i in range(indices_data.shape[0])]
94
+ else:
95
+ self.indices = indices_data
94
96
  else:
95
97
  self.indices = None
96
-
97
- print(f"Loaded reconstruction results and indices from {recon_path}")
98
+ if show_logs:
99
+ print(f"Loaded reconstruction results and indices from {recon_path}")
98
100
  else:
99
101
  # Mode chargement depuis le répertoire de résultats
100
102
  if self.saveDir is None:
101
103
  raise ValueError("Save directory is not specified. Please set saveDir before loading.")
102
-
103
- # Use current optimizer if not provided
104
+ # Determine optimizer name for path matching
104
105
  opt_name = optimizer.value if optimizer is not None else self.optimizer.value
105
-
106
- # Build the directory path
106
+ # Find the most recent results directory if no date is specified
107
+ dir_pattern = f'results_*_{opt_name}'
108
+ if opt_name == OptimizerType.CP_TV.value or opt_name == OptimizerType.CP_KL.value:
109
+ dir_pattern += f'_Alpha_{self.alpha}_Theta_{self.theta}_L_{self.L}'
107
110
  if results_date is None:
108
- dir_pattern = f'results_*_{opt_name}_Alpha_{alpha if alpha is not None else self.alpha}_Theta_{theta if theta is not None else self.theta}_L_{L if L is not None else self.L}'
109
111
  dirs = [d for d in os.listdir(self.saveDir) if os.path.isdir(os.path.join(self.saveDir, d)) and dir_pattern in d]
110
112
  if not dirs:
111
113
  raise FileNotFoundError(f"No matching results directory found for pattern '{dir_pattern}' in {self.saveDir}.")
112
114
  dirs.sort(reverse=True) # Most recent first
113
115
  results_dir = os.path.join(self.saveDir, dirs[0])
114
116
  else:
115
- results_dir = os.path.join(self.saveDir, f'results_{results_date}_{opt_name}_Alpha_{alpha if alpha is not None else self.alpha}_Theta_{theta if theta is not None else self.theta}_L_{L if L is not None else self.L}')
117
+ results_dir = os.path.join(self.saveDir, f'results_{results_date}_{opt_name}')
118
+ if opt_name == OptimizerType.CP_TV.value or opt_name == OptimizerType.CP_KL.value:
119
+ results_dir += f'_Alpha_{self.alpha}_Theta_{self.theta}_L_{self.L}'
116
120
  if not os.path.exists(results_dir):
117
121
  raise FileNotFoundError(f"Directory {results_dir} does not exist.")
118
-
119
122
  # Load reconstruction results
120
123
  recon_key = 'reconPhantom' if withTumor else 'reconLaser'
121
124
  recon_path = os.path.join(results_dir, f'{recon_key}.npy')
122
125
  if not os.path.exists(recon_path):
123
126
  raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
124
-
125
- if withTumor:
126
- self.reconPhantom = np.load(recon_path, allow_pickle=True)
127
+ data = np.load(recon_path, allow_pickle=True)
128
+ # Découpe en liste de 2D si c'est un tableau 3D
129
+ if isinstance(data, np.ndarray) and data.ndim == 3:
130
+ if withTumor:
131
+ self.reconPhantom = [data[i, :, :] for i in range(data.shape[0])]
132
+ else:
133
+ self.reconLaser = [data[i, :, :] for i in range(data.shape[0])]
127
134
  else:
128
- self.reconLaser = np.load(recon_path, allow_pickle=True)
129
-
130
- # Load saved indices
131
- indices_path = os.path.join(results_dir, 'reconIndices.npy')
132
- if not os.path.exists(indices_path):
133
- raise FileNotFoundError(f"No indices file found at {indices_path}.")
134
-
135
- self.indices = np.load(indices_path, allow_pickle=True)
136
-
137
- print(f"Loaded reconstruction results and indices from {results_dir}")
135
+ if withTumor:
136
+ self.reconPhantom = data
137
+ else:
138
+ self.reconLaser = data
139
+ # Try to load saved indices (if file exists)
140
+ indices_path = os.path.join(results_dir, 'indices.npy')
141
+ if os.path.exists(indices_path):
142
+ indices_data = np.load(indices_path, allow_pickle=True)
143
+ if isinstance(indices_data, np.ndarray) and indices_data.ndim == 3:
144
+ self.indices = [indices_data[i, :, :] for i in range(indices_data.shape[0])]
145
+ else:
146
+ self.indices = indices_data
147
+ else:
148
+ self.indices = None
149
+ if show_logs:
150
+ print(f"Loaded reconstruction results and indices from {results_dir}")
138
151
 
139
152
  def _convexReconPython(self, withTumor):
140
- if withTumor:
141
- y=self.experiment.AOsignal_withTumor
142
-
143
- else:
144
- y=self.experiment.AOsignal_withoutTumor
145
-
146
153
  if self.optimizer == OptimizerType.CP_TV:
147
154
  if withTumor:
148
155
  self.reconPhantom, self.indices = CP_TV(