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

@@ -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
@@ -66,25 +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
+ if not os.path.exists(results_dir):
70
+ os.makedirs(results_dir)
69
71
 
70
- if os.path.exists(results_dir):
72
+ if os.path.exists(os.path.join(results_dir,"indices.npy")):
71
73
  return (True, results_dir)
72
74
 
73
75
  return (False, results_dir)
74
76
 
75
- 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):
76
78
  """
77
- Load the reconstruction results and indices for Bayesian reconstruction and store them in self.
78
- Args:
79
- withTumor (bool): If True, loads the reconstruction with tumor; otherwise, loads the reconstruction without tumor.
80
- results_date (str): Date string (format "ddmm") to specify which results to load. If None, uses the most recent date in saveDir.
81
- optimizer (OptimizerType): Optimizer type to filter results. If None, uses the current optimizer of the instance.
82
- potential_function (PotentialType): Potential function type to filter results. If None, uses the current potential function of the instance.
83
- beta (float): Beta parameter to match the saved directory. If None, skips this filter.
84
- delta (float): Delta parameter to match the saved directory. If None, skips this filter.
85
- gamma (float): Gamma parameter to match the saved directory. If None, skips this filter.
86
- sigma (float): Sigma parameter to match the saved directory. If None, skips this filter.
87
- 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.
88
81
  """
89
82
  if filePath is not None:
90
83
  # Mode chargement direct depuis un fichier
@@ -92,53 +85,55 @@ class BayesianRecon(AlgebraicRecon):
92
85
  recon_path = filePath
93
86
  if not os.path.exists(recon_path):
94
87
  raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
95
-
96
- if withTumor:
97
- 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])]
98
96
  else:
99
- self.reconLaser = np.load(recon_path, allow_pickle=True)
100
-
101
- # Essayer de charger les indices (fichier avec suffixe "_indices.npy")
102
- base_dir, file_name = os.path.split(recon_path)
103
- file_base, _ = os.path.splitext(file_name)
104
- 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')
105
105
  if os.path.exists(indices_path):
106
- self.indices = np.load(indices_path, allow_pickle=True)
107
- else:
108
- indices_path = os.path.join(base_dir, 'reconIndices.npy') # Alternative
109
- if os.path.exists(indices_path):
110
- 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])]
111
109
  else:
112
- self.indices = None
113
-
114
- 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}")
115
115
  else:
116
116
  # Mode chargement depuis le répertoire de résultats
117
117
  if self.saveDir is None:
118
118
  raise ValueError("Save directory is not specified. Please set saveDir before loading.")
119
-
120
119
  # Use current optimizer and potential function if not provided
121
120
  opt_name = optimizer.value if optimizer is not None else self.optimizer.value
122
121
  pot_name = potential_function.value if potential_function is not None else self.potentialFunction.value
123
-
124
122
  # Build the base directory pattern
125
123
  dir_pattern = f'results_*_{opt_name}_{pot_name}'
126
-
127
124
  # Add parameters to the pattern based on the optimizer
128
125
  if optimizer is None:
129
126
  optimizer = self.optimizer
130
-
131
127
  if optimizer == OptimizerType.PPGMLEM:
132
- beta_str = f'_Beta_{beta if beta is not None else self.beta}'
133
- delta_str = f'_Delta_{delta if delta is not None else self.delta}'
134
- gamma_str = f'_Gamma_{gamma if gamma is not None else self.gamma}'
135
- 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}'
136
132
  dir_pattern += f'{beta_str}{delta_str}{gamma_str}{sigma_str}'
137
133
  elif optimizer in (OptimizerType.PGC, OptimizerType.DEPIERRO95):
138
- beta_str = f'_Beta_{beta if beta is not None else self.beta}'
139
- 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}'
140
136
  dir_pattern += f'{beta_str}{sigma_str}'
141
-
142
137
  # Find the most recent results directory if no date is specified
143
138
  if results_date is None:
144
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]
@@ -149,33 +144,40 @@ class BayesianRecon(AlgebraicRecon):
149
144
  else:
150
145
  results_dir = os.path.join(self.saveDir, f'results_{results_date}_{opt_name}_{pot_name}')
151
146
  if optimizer == OptimizerType.PPGMLEM:
152
- 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}'
153
148
  elif optimizer in (OptimizerType.PGC, OptimizerType.DEPIERRO95):
154
- 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}'
155
150
  if not os.path.exists(results_dir):
156
151
  raise FileNotFoundError(f"Directory {results_dir} does not exist.")
157
-
158
152
  # Load reconstruction results
159
153
  recon_key = 'reconPhantom' if withTumor else 'reconLaser'
160
154
  recon_path = os.path.join(results_dir, f'{recon_key}.npy')
161
155
  if not os.path.exists(recon_path):
162
156
  raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
163
-
164
- if withTumor:
165
- 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])]
166
163
  else:
167
- self.reconLaser = np.load(recon_path, allow_pickle=True)
168
-
169
- # Load saved indices
170
- 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')
171
170
  if not os.path.exists(indices_path):
172
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}")
173
179
 
174
- self.indices = np.load(indices_path, allow_pickle=True)
175
-
176
- print(f"Loaded reconstruction results and indices from {results_dir}")
177
-
178
- def run(self, processType=ProcessType.PYTHON, withTumor=True):
180
+ def run(self, processType=ProcessType.PYTHON, withTumor=True, show_logs=True):
179
181
  """
180
182
  This method is a placeholder for the Bayesian reconstruction process.
181
183
  It currently does not perform any operations but serves as a template for future implementations.
@@ -187,109 +189,42 @@ class BayesianRecon(AlgebraicRecon):
187
189
  else:
188
190
  raise ValueError(f"Unknown Bayesian reconstruction type: {processType}")
189
191
 
190
- def _bayesianReconCASToR(self, withTumor):
192
+ def _bayesianReconCASToR(self, show_logs, withTumor):
191
193
  raise NotImplementedError("CASToR Bayesian reconstruction is not implemented yet.")
192
194
 
193
- def _bayesianReconPython(self, withTumor):
194
-
195
+ def _bayesianReconPython(self, show_logs, withTumor):
195
196
  if withTumor:
196
197
  if self.experiment.AOsignal_withTumor is None:
197
198
  raise ValueError("AO signal with tumor is not available. Please generate AO signal with tumor the experiment first in the experiment object.")
198
199
  if self.optimizer.value == OptimizerType.PPGMLEM.value:
199
- 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)
200
214
  elif self.optimizer.value == OptimizerType.PGC.value:
201
- 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)
202
216
  elif self.optimizer.value == OptimizerType.DEPIERRO95.value:
203
- 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)
204
218
  else:
205
219
  raise ValueError(f"Unknown optimizer type: {self.optimizer.value}")
206
220
  else:
207
221
  if self.experiment.AOsignal_withoutTumor is None:
208
222
  raise ValueError("AO signal without tumor is not available. Please generate AO signal without tumor the experiment first in the experiment object.")
209
223
  if self.optimizer.value == OptimizerType.PPGMLEM.value:
210
- 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)
211
225
  elif self.optimizer.value == OptimizerType.PGC.value:
212
- 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)
213
227
  elif self.optimizer.value == OptimizerType.DEPIERRO95.value:
214
- 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)
215
229
  else:
216
- raise ValueError(f"Unknown optimizer type: {self.optimizer.value}")
217
-
218
- def _MAPEM_STOP(self, SMatrix, y, withTumor):
219
- """
220
- This method implements the MAPEM_STOP algorithm using either CPU or single-GPU PyTorch acceleration.
221
- Multi-GPU and Multi-CPU modes are not implemented for this algorithm.
222
- """
223
- result = None
224
- required_memory = calculate_memory_requirement(SMatrix, y)
225
-
226
- if self.isGPU:
227
- if check_gpu_memory(config.select_best_gpu(), required_memory):
228
- try:
229
- 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)
230
- except Exception as e:
231
- warnings.warn(f"Falling back to CPU implementation due to an error in GPU implementation: {e}")
232
- else:
233
- warnings.warn("Insufficient GPU memory for single GPU MAPEM_STOP. Falling back to CPU.")
234
-
235
- if result is None:
236
- try:
237
- 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)
238
- except Exception as e:
239
- warnings.warn(f"An error occurred in CPU implementation: {e}")
240
- result = None
241
-
242
- return result
243
-
244
- def _MAPEM(self, SMatrix, y, withTumor):
245
- """
246
- This method implements the MAPEM algorithm using either CPU or single-GPU PyTorch acceleration.
247
- Multi-GPU and Multi-CPU modes are not implemented for this algorithm.
248
- """
249
- result = None
250
- required_memory = calculate_memory_requirement(SMatrix, y)
251
-
252
- if self.isGPU:
253
- if check_gpu_memory(config.select_best_gpu(), required_memory):
254
- try:
255
- 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)
256
- except Exception as e:
257
- warnings.warn(f"Falling back to CPU implementation due to an error in GPU implementation: {e}")
258
- else:
259
- warnings.warn("Insufficient GPU memory for single GPU MAPEM. Falling back to CPU.")
260
-
261
- if result is None:
262
- try:
263
- 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)
264
- except Exception as e:
265
- warnings.warn(f"An error occurred in CPU implementation: {e}")
266
- result = None
267
-
268
- return result
269
-
270
- def _DEPIERRO(self, SMatrix, y, withTumor):
271
- """
272
- This method implements the DEPIERRO algorithm using either CPU or single-GPU PyTorch acceleration.
273
- Multi-GPU and Multi-CPU modes are not implemented for this algorithm.
274
- """
275
- result = None
276
- required_memory = calculate_memory_requirement(SMatrix, y)
277
-
278
- if self.isGPU:
279
- if check_gpu_memory(config.select_best_gpu(), required_memory):
280
- try:
281
- 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)
282
- except Exception as e:
283
- warnings.warn(f"Falling back to CPU implementation due to an error in GPU implementation: {e}")
284
- else:
285
- warnings.warn("Insufficient GPU memory for single GPU DEPIERRO. Falling back to CPU.")
286
-
287
- if result is None:
288
- try:
289
- 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)
290
- except Exception as e:
291
- warnings.warn(f"An error occurred in CPU implementation: {e}")
292
- result = None
293
-
294
- return result
295
-
230
+ raise ValueError(f"Unknown optimizer type: {self.optimizer.value}")
@@ -6,6 +6,7 @@ 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
  """
@@ -48,24 +49,20 @@ class PrimalDualRecon(AlgebraicRecon):
48
49
  )
49
50
  os.makedirs(results_dir, exist_ok=True)
50
51
 
51
- if os.path.exists(results_dir):
52
+ if os.path.exists(os.path.join(results_dir,"indices.npy")):
52
53
  return (True, results_dir)
53
54
 
54
55
  return (False, results_dir)
55
56
 
56
-
57
-
58
- 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):
59
58
  """
60
- 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.
61
61
  Args:
62
- withTumor (bool): If True, loads the reconstruction with tumor; otherwise, loads the reconstruction without tumor.
63
- results_date (str): Date string (format "ddmm") to specify which results to load. If None, uses the most recent date in saveDir.
64
- optimizer (OptimizerType): Optimizer type to filter results. If None, uses the current optimizer of the instance.
65
- alpha (float): Alpha parameter to match the saved directory. If None, uses the current alpha of the instance.
66
- theta (float): Theta parameter to match the saved directory. If None, uses the current theta of the instance.
67
- L (float): L parameter to match the saved directory. If None, uses the current L of the instance.
68
- 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).
69
66
  """
70
67
  if filePath is not None:
71
68
  # Mode chargement direct depuis un fichier
@@ -73,73 +70,86 @@ class PrimalDualRecon(AlgebraicRecon):
73
70
  recon_path = filePath
74
71
  if not os.path.exists(recon_path):
75
72
  raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
76
-
77
- if withTumor:
78
- 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])]
79
81
  else:
80
- self.reconLaser = np.load(recon_path, allow_pickle=True)
81
-
82
- # Essayer de charger les indices (fichier avec suffixe "_indices.npy" ou "reconIndices.npy")
83
- base_dir, file_name = os.path.split(recon_path)
84
- file_base, _ = os.path.splitext(file_name)
85
- indices_path = os.path.join(base_dir, f"{file_base}_indices.npy")
86
- if not os.path.exists(indices_path):
87
- indices_path = os.path.join(base_dir, 'reconIndices.npy') # Alternative
88
-
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")
89
90
  if os.path.exists(indices_path):
90
- 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
91
96
  else:
92
97
  self.indices = None
93
-
94
- 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}")
95
100
  else:
96
101
  # Mode chargement depuis le répertoire de résultats
97
102
  if self.saveDir is None:
98
103
  raise ValueError("Save directory is not specified. Please set saveDir before loading.")
99
-
100
- # Use current optimizer if not provided
104
+ # Determine optimizer name for path matching
101
105
  opt_name = optimizer.value if optimizer is not None else self.optimizer.value
102
-
103
- # 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}'
104
110
  if results_date is None:
105
- 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}'
106
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]
107
112
  if not dirs:
108
113
  raise FileNotFoundError(f"No matching results directory found for pattern '{dir_pattern}' in {self.saveDir}.")
109
114
  dirs.sort(reverse=True) # Most recent first
110
115
  results_dir = os.path.join(self.saveDir, dirs[0])
111
116
  else:
112
- 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}'
113
120
  if not os.path.exists(results_dir):
114
121
  raise FileNotFoundError(f"Directory {results_dir} does not exist.")
115
-
116
122
  # Load reconstruction results
117
123
  recon_key = 'reconPhantom' if withTumor else 'reconLaser'
118
124
  recon_path = os.path.join(results_dir, f'{recon_key}.npy')
119
125
  if not os.path.exists(recon_path):
120
126
  raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
121
-
122
- if withTumor:
123
- 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])]
124
134
  else:
125
- self.reconLaser = np.load(recon_path, allow_pickle=True)
126
-
127
- # Load saved indices
128
- indices_path = os.path.join(results_dir, 'reconIndices.npy')
129
- if not os.path.exists(indices_path):
130
- raise FileNotFoundError(f"No indices file found at {indices_path}.")
131
-
132
- self.indices = np.load(indices_path, allow_pickle=True)
133
-
134
- 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}")
135
151
 
136
152
  def _convexReconPython(self, withTumor):
137
- if withTumor:
138
- y=self.experiment.AOsignal_withTumor
139
-
140
- else:
141
- y=self.experiment.AOsignal_withoutTumor
142
-
143
153
  if self.optimizer == OptimizerType.CP_TV:
144
154
  if withTumor:
145
155
  self.reconPhantom, self.indices = CP_TV(
@@ -354,8 +354,33 @@ class NoiseType(Enum):
354
354
  - None: No noise is applied.
355
355
  """
356
356
  POISSON = 'poisson'
357
- """Poisson noise, typically used for emission data."""
357
+ """Poisson noise."""
358
358
  GAUSSIAN = 'gaussian'
359
- """Gaussian noise, typically used for transmission data."""
359
+ """Gaussian noise."""
360
360
  None_ = 'none'
361
361
  """No noise is applied."""
362
+
363
+ class SMatrixType(Enum):
364
+ """
365
+ Enum for different sparsing methods used in reconstructions.
366
+
367
+ Selection of sparsing methods:
368
+ - Thresholding: Sparsing based on a threshold value.
369
+ - TopK: Sparsing by retaining the top K values.
370
+ - None: No sparsing is applied.
371
+ """
372
+ DENSE = 'DENSE'
373
+ """No sparsing is applied."""
374
+ CSR = 'CSR'
375
+ """Sparsing based on a threshold value."""
376
+ COO = 'COO'
377
+ """Sparsing by retaining the top K values."""
378
+ SELL = 'SELL'
379
+ """Sparsing using sell C sigma method.
380
+ Optimized variant of ELLPACK, dividing the matrix into fixed-size "chunks" of `C` rows.
381
+ Non-zero elements are sorted by column within each chunk to improve memory coalescing on GPUs.
382
+ Rows are padded with zeros to align their length to the longest row in the chunk.
383
+ ** Ref : Kreutzer, M., Hager, G., Wellein, G., Fehske, H., & Bishop, A. R. (2014).
384
+ "A Unified Sparse Matrix Data Format for Efficient General Sparse Matrix-Vector Multiply on Modern Processors".
385
+ ACM Transactions on Mathematical Software, 41(2), 1–24. DOI: 10.1145/2592376.
386
+ """
@@ -3,7 +3,7 @@ import torch
3
3
  import numpy as np
4
4
  from numba import njit, prange
5
5
  from torch_sparse import coalesce
6
- import torch.nn.functional as F
6
+ import cupyx.scipy.sparse as cpsparse
7
7
 
8
8
  def load_recon(hdr_path):
9
9
  """
@@ -78,7 +78,7 @@ def load_recon(hdr_path):
78
78
  rescale_offset = float(header.get('data rescale offset', 0))
79
79
  image = image * rescale_slope + rescale_offset
80
80
 
81
- return image.T
81
+ return image
82
82
 
83
83
  def mse(y_true, y_pred):
84
84
  """
@@ -150,20 +150,46 @@ def ssim(img1, img2, win_size=7, k1=0.01, k2=0.03, L=1.0):
150
150
  return np.mean(ssim_map)
151
151
 
152
152
  def calculate_memory_requirement(SMatrix, y):
153
- """Calculate the memory requirement for the given matrices in GB."""
154
- num_elements_SMatrix = SMatrix.size
155
- num_elements_y = y.size
156
- num_elements_theta = SMatrix.shape[1] * SMatrix.shape[2] # Assuming theta has shape (Z, X)
157
-
158
- # Calculate total memory requirement in GB
159
- total_memory = (num_elements_SMatrix + num_elements_y + num_elements_theta) * 32 / 8 / 1024**3
160
- return total_memory
153
+ """
154
+ Calcule la mémoire requise (en Go) pour :
155
+ - SMatrix : np.ndarray (dense) ou cpsparse.csr_matrix (sparse)
156
+ - y : vecteur (NumPy ou CuPy, float32)
161
157
 
162
- def check_gpu_memory(device_index, required_memory):
158
+ Args:
159
+ SMatrix: np.ndarray ou cpsparse.csr_matrix
160
+ y: vecteur (float32)
161
+ """
162
+ # 1. Mémoire pour SMatrix
163
+ if isinstance(SMatrix, cpsparse.csr_matrix):
164
+ # Matrice CSR CuPy
165
+ nnz = SMatrix.nnz
166
+ num_cols = SMatrix.shape[1]
167
+ size_SMatrix = nnz * (4 + 4) + (num_cols + 1) * 4 # data (float32) + indices (int32) + indptr (int32)
168
+ elif isinstance(SMatrix, np.ndarray):
169
+ # Tableau NumPy dense (float32)
170
+ size_SMatrix = SMatrix.nbytes
171
+ else:
172
+ raise ValueError("SMatrix doit être un np.ndarray ou une matrice CSR CuPy.")
173
+
174
+ # 2. Mémoire pour y (float32)
175
+ if hasattr(y, 'nbytes'):
176
+ size_y = y.nbytes
177
+ else:
178
+ size_y = np.prod(y.shape) * 4 # float32
179
+
180
+ # Total en Go
181
+ total_bytes = size_SMatrix + size_y
182
+ total_GB = total_bytes / 1024**3
183
+
184
+ return total_GB
185
+
186
+
187
+ def check_gpu_memory(device_index, required_memory, show_logs=True):
163
188
  """Check if enough memory is available on the specified GPU."""
164
- free_memory, total_memory = torch.cuda.mem_get_info(f"cuda:{device_index}")
189
+ free_memory, _ = torch.cuda.mem_get_info(f"cuda:{device_index}")
165
190
  free_memory_gb = free_memory / 1024**3
166
- print(f"Free memory on GPU {device_index}: {free_memory_gb:.2f} GB, Required memory: {required_memory:.2f} GB")
191
+ if show_logs:
192
+ print(f"Free memory on GPU {device_index}: {free_memory_gb:.2f} GB, Required memory: {required_memory:.2f} GB")
167
193
  return free_memory_gb >= required_memory
168
194
 
169
195
  @njit(parallel=True)
@@ -270,3 +296,48 @@ def prox_F_star(y, sigma, a):
270
296
  def prox_G(x, tau, K):
271
297
  return torch.clamp(x - tau * K, min=0)
272
298
 
299
+ def filter_radon(f, N, filter_type, Fc):
300
+ """
301
+ Implémente les filtres pour la rétroprojection filtrée (iRadon).
302
+ Inspirée de la fonction MATLAB FilterRadon de Mamouna Bocoum.
303
+
304
+ Paramètres :
305
+ ------------
306
+ f : np.ndarray
307
+ Vecteur des fréquences (ex: f_t ou f_z).
308
+ N : int
309
+ Taille du filtre (longueur de f).
310
+ filter_type : str
311
+ Type de filtre : 'ram-lak', 'shepp-logan', 'cosine', 'hamming', 'hann'.
312
+ Fc : float
313
+ Fréquence de coupure.
314
+
315
+ Retourne :
316
+ -----------
317
+ FILTER : np.ndarray
318
+ Filtre appliqué aux fréquences.
319
+ """
320
+ FILTER = np.abs(f)
321
+
322
+ if filter_type == 'ram-lak':
323
+ pass # FILTER = |f| (déjà calculé)
324
+ elif filter_type == 'shepp-logan':
325
+ # Évite la division par zéro
326
+ with np.errstate(divide='ignore', invalid='ignore'):
327
+ FILTER = FILTER * (np.sinc(2 * f / (2 * Fc))) # sin(2πf/(2Fc))/(2πf/(4Fc)) = sinc(2f/(2Fc))
328
+ FILTER[np.isnan(FILTER)] = 1.0 # Pour f=0
329
+ elif filter_type == 'cosine':
330
+ FILTER = FILTER * np.cos(2 * np.pi * f / (4 * Fc))
331
+ elif filter_type == 'hamming':
332
+ FILTER = FILTER * (0.54 + 0.46 * np.cos(2 * np.pi * f / Fc))
333
+ elif filter_type == 'hann':
334
+ FILTER = FILTER * (1 + np.cos(2 * np.pi * f / (4 * Fc))) / 2
335
+ else:
336
+ raise ValueError(f"Type de filtre inconnu : {filter_type}")
337
+
338
+ # Coupure des fréquences au-delà de Fc
339
+ FILTER[np.abs(f) > Fc] = 0
340
+ # Atténuation exponentielle (optionnelle, comme dans le code MATLAB)
341
+ FILTER = FILTER * np.exp(-2 * (np.abs(f) / Fc)**10)
342
+
343
+ return FILTER
@@ -9,3 +9,4 @@ from .ReconTools import *
9
9
 
10
10
 
11
11
 
12
+