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.
- AOT_biomaps/AOT_Acoustic/StructuredWave.py +2 -2
- AOT_biomaps/AOT_Acoustic/_mainAcoustic.py +11 -6
- AOT_biomaps/AOT_Experiment/Tomography.py +74 -4
- AOT_biomaps/AOT_Experiment/_mainExperiment.py +95 -55
- AOT_biomaps/AOT_Recon/AOT_Optimizers/DEPIERRO.py +48 -13
- AOT_biomaps/AOT_Recon/AOT_Optimizers/LS.py +9 -6
- AOT_biomaps/AOT_Recon/AOT_Optimizers/MAPEM.py +118 -38
- AOT_biomaps/AOT_Recon/AOT_Optimizers/MLEM.py +268 -102
- AOT_biomaps/AOT_Recon/AOT_Optimizers/PDHG.py +1 -1
- AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/RelativeDifferences.py +10 -14
- AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_CSR.py +252 -0
- AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_SELL.py +322 -0
- AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/__init__.py +2 -0
- AOT_biomaps/AOT_Recon/AlgebraicRecon.py +231 -108
- AOT_biomaps/AOT_Recon/AnalyticRecon.py +26 -41
- AOT_biomaps/AOT_Recon/BayesianRecon.py +81 -146
- AOT_biomaps/AOT_Recon/PrimalDualRecon.py +63 -53
- AOT_biomaps/AOT_Recon/ReconEnums.py +27 -2
- AOT_biomaps/AOT_Recon/ReconTools.py +84 -13
- AOT_biomaps/AOT_Recon/__init__.py +1 -0
- AOT_biomaps/AOT_Recon/_mainRecon.py +60 -53
- AOT_biomaps/__init__.py +4 -102
- {aot_biomaps-2.9.186.dist-info → aot_biomaps-2.9.261.dist-info}/METADATA +2 -1
- aot_biomaps-2.9.261.dist-info/RECORD +46 -0
- aot_biomaps-2.9.186.dist-info/RECORD +0 -43
- {aot_biomaps-2.9.186.dist-info → aot_biomaps-2.9.261.dist-info}/WHEEL +0 -0
- {aot_biomaps-2.9.186.dist-info → aot_biomaps-2.9.261.dist-info}/top_level.txt +0 -0
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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 =
|
|
113
|
-
|
|
114
|
-
|
|
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_{
|
|
133
|
-
delta_str = f'_Delta_{
|
|
134
|
-
gamma_str = f'_Gamma_{
|
|
135
|
-
sigma_str = f'_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_{
|
|
139
|
-
sigma_str = f'_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_{
|
|
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_{
|
|
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
|
|
165
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
63
|
-
results_date
|
|
64
|
-
optimizer
|
|
65
|
-
|
|
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
|
-
|
|
78
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}
|
|
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
|
-
|
|
123
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
357
|
+
"""Poisson noise."""
|
|
358
358
|
GAUSSIAN = 'gaussian'
|
|
359
|
-
"""Gaussian noise
|
|
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
|
|
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
|
|
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
|
-
"""
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
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,
|
|
189
|
+
free_memory, _ = torch.cuda.mem_get_info(f"cuda:{device_index}")
|
|
165
190
|
free_memory_gb = free_memory / 1024**3
|
|
166
|
-
|
|
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
|