AOT-biomaps 2.9.138__py3-none-any.whl → 2.9.279__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of AOT-biomaps might be problematic. Click here for more details.
- AOT_biomaps/AOT_Acoustic/AcousticTools.py +35 -115
- AOT_biomaps/AOT_Acoustic/StructuredWave.py +2 -2
- AOT_biomaps/AOT_Acoustic/_mainAcoustic.py +22 -18
- AOT_biomaps/AOT_Experiment/Tomography.py +74 -4
- AOT_biomaps/AOT_Experiment/_mainExperiment.py +102 -68
- AOT_biomaps/AOT_Optic/_mainOptic.py +124 -58
- AOT_biomaps/AOT_Recon/AOT_Optimizers/DEPIERRO.py +72 -108
- AOT_biomaps/AOT_Recon/AOT_Optimizers/LS.py +474 -289
- AOT_biomaps/AOT_Recon/AOT_Optimizers/MAPEM.py +173 -68
- AOT_biomaps/AOT_Recon/AOT_Optimizers/MLEM.py +360 -154
- AOT_biomaps/AOT_Recon/AOT_Optimizers/PDHG.py +150 -111
- AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/RelativeDifferences.py +10 -14
- AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_CSR.py +281 -0
- AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_SELL.py +328 -0
- AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/__init__.py +2 -0
- AOT_biomaps/AOT_Recon/AOT_biomaps_kernels.cubin +0 -0
- AOT_biomaps/AOT_Recon/AlgebraicRecon.py +359 -238
- AOT_biomaps/AOT_Recon/AnalyticRecon.py +29 -41
- AOT_biomaps/AOT_Recon/BayesianRecon.py +165 -91
- AOT_biomaps/AOT_Recon/DeepLearningRecon.py +4 -1
- AOT_biomaps/AOT_Recon/PrimalDualRecon.py +175 -31
- AOT_biomaps/AOT_Recon/ReconEnums.py +38 -3
- AOT_biomaps/AOT_Recon/ReconTools.py +184 -77
- AOT_biomaps/AOT_Recon/__init__.py +1 -0
- AOT_biomaps/AOT_Recon/_mainRecon.py +144 -74
- AOT_biomaps/__init__.py +4 -36
- {aot_biomaps-2.9.138.dist-info → aot_biomaps-2.9.279.dist-info}/METADATA +2 -1
- aot_biomaps-2.9.279.dist-info/RECORD +47 -0
- aot_biomaps-2.9.138.dist-info/RECORD +0 -43
- {aot_biomaps-2.9.138.dist-info → aot_biomaps-2.9.279.dist-info}/WHEEL +0 -0
- {aot_biomaps-2.9.138.dist-info → aot_biomaps-2.9.279.dist-info}/top_level.txt +0 -0
|
@@ -1,39 +1,51 @@
|
|
|
1
|
+
import concurrent
|
|
2
|
+
|
|
3
|
+
from AOT_biomaps.AOT_Recon.ReconTools import get_apodization_vector_gpu
|
|
1
4
|
from ._mainRecon import Recon
|
|
2
|
-
from .ReconEnums import ReconType, OptimizerType, ProcessType
|
|
5
|
+
from .ReconEnums import ReconType, OptimizerType, ProcessType, SMatrixType
|
|
3
6
|
from .AOT_Optimizers import MLEM, LS
|
|
4
|
-
from .ReconTools import check_gpu_memory, calculate_memory_requirement, mse
|
|
5
7
|
from AOT_biomaps.Config import config
|
|
6
|
-
|
|
8
|
+
from .AOT_SparseSMatrix import SparseSMatrix_CSR, SparseSMatrix_SELL
|
|
7
9
|
|
|
8
10
|
import os
|
|
9
|
-
import sys
|
|
10
11
|
import subprocess
|
|
11
|
-
import warnings
|
|
12
12
|
import numpy as np
|
|
13
13
|
import matplotlib.pyplot as plt
|
|
14
14
|
import matplotlib.animation as animation
|
|
15
15
|
from IPython.display import HTML
|
|
16
16
|
from datetime import datetime
|
|
17
17
|
from tempfile import gettempdir
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
import cupy as cp
|
|
19
|
+
import cupyx.scipy.sparse as cpsparse
|
|
20
|
+
import gc
|
|
21
|
+
from tqdm import trange
|
|
20
22
|
|
|
21
23
|
class AlgebraicRecon(Recon):
|
|
22
24
|
"""
|
|
23
25
|
This class implements the Algebraic reconstruction process.
|
|
24
26
|
It currently does not perform any operations but serves as a template for future implementations.
|
|
25
27
|
"""
|
|
26
|
-
def __init__(self, opti = OptimizerType.MLEM, numIterations = 10000, numSubsets = 1, isSavingEachIteration=True,
|
|
28
|
+
def __init__(self, opti = OptimizerType.MLEM, numIterations = 10000, numSubsets = 1, isSavingEachIteration=True, maxSaves = 5000, alpha = None, denominatorThreshold = 1e-6, smatrixType = SMatrixType.SELL, sparseThreshold=0.1, device = None, **kwargs):
|
|
27
29
|
super().__init__(**kwargs)
|
|
28
30
|
self.reconType = ReconType.Algebraic
|
|
29
31
|
self.optimizer = opti
|
|
30
32
|
self.reconPhantom = []
|
|
31
33
|
self.reconLaser = []
|
|
34
|
+
self.indices = []
|
|
32
35
|
self.numIterations = numIterations
|
|
33
36
|
self.numSubsets = numSubsets
|
|
34
37
|
self.isSavingEachIteration = isSavingEachIteration
|
|
35
|
-
self.
|
|
36
|
-
self.
|
|
38
|
+
self.maxSaves = maxSaves
|
|
39
|
+
self.denominatorThreshold = denominatorThreshold
|
|
40
|
+
self.alpha = alpha # Regularization parameter for LS
|
|
41
|
+
self.device = device
|
|
42
|
+
self.SMatrix = None # system matrix
|
|
43
|
+
self.smatrixType = smatrixType # SMatrixType.DENSE if no sparsing, else SMatrixType.SELL or SMatrixType.CSR or SMatrixType.COO
|
|
44
|
+
# Sparse matrix attributes
|
|
45
|
+
|
|
46
|
+
self.sparseThreshold = sparseThreshold
|
|
47
|
+
|
|
48
|
+
self.Z_dim = None # Used for sparse matrix reconstruction
|
|
37
49
|
|
|
38
50
|
if self.numIterations <= 0:
|
|
39
51
|
raise ValueError("Number of iterations must be greater than 0.")
|
|
@@ -45,38 +57,26 @@ class AlgebraicRecon(Recon):
|
|
|
45
57
|
raise TypeError("Number of subsets must be an integer.")
|
|
46
58
|
|
|
47
59
|
print("Generating system matrix (processing acoustic fields)...")
|
|
48
|
-
self.
|
|
60
|
+
if self.smatrixType == SMatrixType.DENSE:
|
|
61
|
+
self.SMatrix = self._fillDenseSMatrix()
|
|
62
|
+
else:
|
|
63
|
+
self.SMatrix = self._fillSparseSMatrix(isShowLogs=True)
|
|
49
64
|
|
|
50
65
|
# PUBLIC METHODS
|
|
51
66
|
|
|
52
|
-
def run(self, processType = ProcessType.PYTHON, withTumor= True):
|
|
67
|
+
def run(self, processType = ProcessType.PYTHON, withTumor= True, show_logs=True):
|
|
53
68
|
"""
|
|
54
69
|
This method is a placeholder for the Algebraic reconstruction process.
|
|
55
70
|
It currently does not perform any operations but serves as a template for future implementations.
|
|
56
71
|
"""
|
|
57
|
-
|
|
58
72
|
if(processType == ProcessType.CASToR):
|
|
59
|
-
self._AlgebraicReconCASToR(withTumor)
|
|
73
|
+
self._AlgebraicReconCASToR(withTumor=withTumor, show_logs=show_logs)
|
|
60
74
|
elif(processType == ProcessType.PYTHON):
|
|
61
|
-
self._AlgebraicReconPython(withTumor)
|
|
75
|
+
self._AlgebraicReconPython(withTumor=withTumor, show_logs=show_logs)
|
|
62
76
|
else:
|
|
63
77
|
raise ValueError(f"Unknown Algebraic reconstruction type: {processType}")
|
|
64
|
-
|
|
65
|
-
def
|
|
66
|
-
if withTumor:
|
|
67
|
-
folder = 'results_withTumor'
|
|
68
|
-
else:
|
|
69
|
-
folder = 'results_withoutTumor'
|
|
70
|
-
|
|
71
|
-
for thetaFiles in os.path.join(self.saveDir, folder + '_{}'):
|
|
72
|
-
if thetaFiles.endswith('.hdr'):
|
|
73
|
-
theta = Recon.load_recon(thetaFiles)
|
|
74
|
-
if withTumor:
|
|
75
|
-
self.reconPhantom.append(theta)
|
|
76
|
-
else:
|
|
77
|
-
self.reconLaser.append(theta)
|
|
78
|
-
|
|
79
|
-
def plot_MSE(self, isSaving=True, log_scale_x=False, log_scale_y=False):
|
|
78
|
+
|
|
79
|
+
def plot_MSE(self, isSaving=True, log_scale_x=False, log_scale_y=False, show_logs=True):
|
|
80
80
|
"""
|
|
81
81
|
Plot the Mean Squared Error (MSE) of the reconstruction.
|
|
82
82
|
|
|
@@ -91,8 +91,8 @@ class AlgebraicRecon(Recon):
|
|
|
91
91
|
raise ValueError("MSE is empty. Please calculate MSE first.")
|
|
92
92
|
|
|
93
93
|
best_idx = self.indices[np.argmin(self.MSE)]
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
if show_logs:
|
|
95
|
+
print(f"Lowest MSE = {np.min(self.MSE):.4f} at iteration {best_idx+1}")
|
|
96
96
|
# Plot MSE curve
|
|
97
97
|
plt.figure(figsize=(7, 5))
|
|
98
98
|
plt.plot(self.indices, self.MSE, 'r-', label="MSE curve")
|
|
@@ -119,24 +119,19 @@ class AlgebraicRecon(Recon):
|
|
|
119
119
|
scale_str = "_logx"
|
|
120
120
|
elif log_scale_y:
|
|
121
121
|
scale_str = "_logy"
|
|
122
|
-
|
|
123
|
-
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_MSE_plot_MLEM{scale_str}{date_str}.png')
|
|
124
|
-
elif self.optimizer == OptimizerType.LS:
|
|
125
|
-
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_MSE_plot_LS{scale_str}{date_str}.png')
|
|
126
|
-
elif self.optimizer == OptimizerType.LS_TV:
|
|
127
|
-
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_MSE_plot_LS_TV_Lambda_{self.lambda_reg}_LFactor_{self.L_Factor}{scale_str}{date_str}.png')
|
|
122
|
+
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_MSE_plot_{self.optimizer.name}_{scale_str}{date_str}.png')
|
|
128
123
|
plt.savefig(SavingFolder, dpi=300)
|
|
129
|
-
|
|
124
|
+
if show_logs:
|
|
125
|
+
print(f"MSE plot saved to {SavingFolder}")
|
|
130
126
|
|
|
131
127
|
plt.show()
|
|
132
128
|
|
|
133
|
-
def show_MSE_bestRecon(self, isSaving=True):
|
|
129
|
+
def show_MSE_bestRecon(self, isSaving=True, show_logs=True):
|
|
134
130
|
if not self.MSE:
|
|
135
131
|
raise ValueError("MSE is empty. Please calculate MSE first.")
|
|
136
132
|
|
|
137
133
|
|
|
138
134
|
best_idx = np.argmin(self.MSE)
|
|
139
|
-
print(best_idx)
|
|
140
135
|
best_recon = self.reconPhantom[best_idx]
|
|
141
136
|
|
|
142
137
|
# Crée la figure et les axes
|
|
@@ -165,7 +160,6 @@ class AlgebraicRecon(Recon):
|
|
|
165
160
|
|
|
166
161
|
# Right: Reconstruction at last iteration
|
|
167
162
|
lastRecon = self.reconPhantom[-1]
|
|
168
|
-
print(lastRecon.shape)
|
|
169
163
|
if self.experiment.OpticImage.phantom.shape != lastRecon.shape:
|
|
170
164
|
lastRecon = lastRecon.T
|
|
171
165
|
im2 = axs[2].imshow(lastRecon,
|
|
@@ -192,19 +186,14 @@ class AlgebraicRecon(Recon):
|
|
|
192
186
|
savePath = os.path.join(self.saveDir, 'results')
|
|
193
187
|
if not os.path.exists(savePath):
|
|
194
188
|
os.makedirs(savePath)
|
|
195
|
-
|
|
196
|
-
namePath = f'{self.SMatrix.shape[3]}_SCANS_comparison_MSE_BestANDLastRecon_MLEM_Date_{date_str}.png'
|
|
197
|
-
elif self.optimizer == OptimizerType.LS:
|
|
198
|
-
namePath = f'{self.SMatrix.shape[3]}_SCANS_comparison_MSE_BestANDLastRecon_LS_Date_{date_str}.png'
|
|
199
|
-
elif self.optimizer == OptimizerType.LS_TV:
|
|
200
|
-
namePath = f'{self.SMatrix.shape[3]}_SCANS_comparison_MSE_BestANDLastRecon_LS_TV_Lambda_{self.lambda_reg}_LFactor_{self.L_Factor}_Date_{date_str}.png'
|
|
201
|
-
SavingFolder = os.path.join(savePath, namePath)
|
|
189
|
+
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_comparison_MSE_BestANDLastRecon_{self.optimizer.name}_{date_str}.png')
|
|
202
190
|
plt.savefig(SavingFolder, dpi=300, bbox_inches='tight')
|
|
203
|
-
|
|
191
|
+
if show_logs:
|
|
192
|
+
print(f"MSE plot saved to {SavingFolder}")
|
|
204
193
|
|
|
205
194
|
plt.show()
|
|
206
195
|
|
|
207
|
-
def show_theta_animation(self, vmin=None, vmax=None, total_duration_ms=3000, save_path=None, max_frames=1000, isPropMSE=True):
|
|
196
|
+
def show_theta_animation(self, vmin=None, vmax=None, total_duration_ms=3000, save_path=None, max_frames=1000, isPropMSE=True, show_logs=True):
|
|
208
197
|
"""
|
|
209
198
|
Show theta iteration animation with speed proportional to MSE acceleration.
|
|
210
199
|
In "propMSE" mode: slow down when MSE changes rapidly, speed up when MSE stagnates.
|
|
@@ -307,18 +296,19 @@ class AlgebraicRecon(Recon):
|
|
|
307
296
|
ani.save(save_path, writer=animation.PillowWriter(fps=100))
|
|
308
297
|
elif save_path.endswith(".mp4"):
|
|
309
298
|
ani.save(save_path, writer="ffmpeg", fps=30)
|
|
310
|
-
|
|
299
|
+
if show_logs:
|
|
300
|
+
print(f"Animation saved to {save_path}")
|
|
311
301
|
|
|
312
302
|
plt.close(fig)
|
|
313
303
|
return HTML(ani.to_jshtml())
|
|
314
304
|
|
|
315
|
-
def plot_SSIM(self, isSaving=True, log_scale_x=False, log_scale_y=False):
|
|
305
|
+
def plot_SSIM(self, isSaving=True, log_scale_x=False, log_scale_y=False, show_logs=True):
|
|
316
306
|
if not self.SSIM:
|
|
317
307
|
raise ValueError("SSIM is empty. Please calculate SSIM first.")
|
|
318
308
|
|
|
319
309
|
best_idx = self.indices[np.argmax(self.SSIM)]
|
|
320
|
-
|
|
321
|
-
|
|
310
|
+
if show_logs:
|
|
311
|
+
print(f"Highest SSIM = {np.max(self.SSIM):.4f} at iteration {best_idx+1}")
|
|
322
312
|
# Plot SSIM curve
|
|
323
313
|
plt.figure(figsize=(7, 5))
|
|
324
314
|
plt.plot(self.indices, self.SSIM, 'r-', label="SSIM curve")
|
|
@@ -345,18 +335,14 @@ class AlgebraicRecon(Recon):
|
|
|
345
335
|
scale_str = "_logx"
|
|
346
336
|
elif log_scale_y:
|
|
347
337
|
scale_str = "_logy"
|
|
348
|
-
|
|
349
|
-
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_SSIM_plot_MLEM{scale_str}{date_str}.png')
|
|
350
|
-
elif self.optimizer == OptimizerType.LS:
|
|
351
|
-
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_SSIM_plot_LS{scale_str}{date_str}.png')
|
|
352
|
-
elif self.optimizer == OptimizerType.LS_TV:
|
|
353
|
-
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_SSIM_plot_LS_TV_Lambda_{self.lambda_reg}_LFactor_{self.L_Factor}{scale_str}{date_str}.png')
|
|
338
|
+
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_SSIM_plot_{self.optimizer.name}_{scale_str}{date_str}.png')
|
|
354
339
|
plt.savefig(SavingFolder, dpi=300)
|
|
355
|
-
|
|
340
|
+
if show_logs:
|
|
341
|
+
print(f"SSIM plot saved to {SavingFolder}")
|
|
356
342
|
|
|
357
343
|
plt.show()
|
|
358
344
|
|
|
359
|
-
def show_SSIM_bestRecon(self, isSaving=True):
|
|
345
|
+
def show_SSIM_bestRecon(self, isSaving=True, show_logs=True):
|
|
360
346
|
|
|
361
347
|
if not self.SSIM:
|
|
362
348
|
raise ValueError("SSIM is empty. Please calculate SSIM first.")
|
|
@@ -367,9 +353,6 @@ class AlgebraicRecon(Recon):
|
|
|
367
353
|
# ----------------- Plotting -----------------
|
|
368
354
|
_, axs = plt.subplots(1, 3, figsize=(15, 5)) # 1 row, 3 columns
|
|
369
355
|
|
|
370
|
-
# Normalization based on LAMBDA max
|
|
371
|
-
lambda_max = np.max(self.experiment.OpticImage.laser.intensity)
|
|
372
|
-
|
|
373
356
|
# Left: Best reconstructed image (normalized)
|
|
374
357
|
im0 = axs[0].imshow(best_recon,
|
|
375
358
|
extent=(self.experiment.params.general['Xrange'][0], self.experiment.params.general['Xrange'][1], self.experiment.params.general['Zrange'][1], self.experiment.params.general['Zrange'][0]),
|
|
@@ -402,18 +385,19 @@ class AlgebraicRecon(Recon):
|
|
|
402
385
|
if isSaving:
|
|
403
386
|
now = datetime.now()
|
|
404
387
|
date_str = now.strftime("%Y_%d_%m_%y")
|
|
405
|
-
SavingFolder = os.path.join(self.saveDir,
|
|
388
|
+
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_comparison_SSIM_BestANDLastRecon_{self.optimizer.name}_{date_str}.png')
|
|
406
389
|
plt.savefig(SavingFolder, dpi=300)
|
|
407
|
-
|
|
390
|
+
if show_logs:
|
|
391
|
+
print(f"SSIM plot saved to {SavingFolder}")
|
|
408
392
|
plt.show()
|
|
409
393
|
|
|
410
|
-
def plot_CRC_vs_Noise(self,
|
|
394
|
+
def plot_CRC_vs_Noise(self, use_ROI=True, fin=None, isSaving=True, show_logs=True):
|
|
411
395
|
"""
|
|
412
396
|
Plot CRC (Contrast Recovery Coefficient) vs Noise for each iteration.
|
|
413
397
|
"""
|
|
414
398
|
if self.reconLaser is None or self.reconLaser == []:
|
|
415
399
|
raise ValueError("Reconstructed laser is empty. Run reconstruction first.")
|
|
416
|
-
if isinstance(self.
|
|
400
|
+
if isinstance(self.reconLaser, list) and len(self.reconLaser) == 1:
|
|
417
401
|
raise ValueError("Reconstructed Image without tumor is a single frame. Run reconstruction with isSavingEachIteration=True to get a sequence of frames.")
|
|
418
402
|
if self.reconPhantom is None or self.reconPhantom == []:
|
|
419
403
|
raise ValueError("Reconstructed phantom is empty. Run reconstruction first.")
|
|
@@ -424,24 +408,21 @@ class AlgebraicRecon(Recon):
|
|
|
424
408
|
fin = len(self.reconPhantom) - 1
|
|
425
409
|
|
|
426
410
|
iter_range = self.indices
|
|
411
|
+
|
|
412
|
+
if self.CRC is None:
|
|
413
|
+
self.calculateCRC(use_ROI=use_ROI)
|
|
427
414
|
|
|
428
|
-
crc_values = []
|
|
429
415
|
noise_values = []
|
|
430
416
|
|
|
431
417
|
for i in iter_range:
|
|
432
418
|
recon_without_tumor = self.reconLaser[i].T
|
|
433
|
-
|
|
434
|
-
# CRC
|
|
435
|
-
crc = self.calculateCRC(iteration=i,ROI_mask=ROI_mask)
|
|
436
|
-
crc_values.append(crc)
|
|
437
|
-
|
|
438
419
|
# Noise
|
|
439
420
|
noise = np.mean(np.abs(recon_without_tumor - self.experiment.OpticImage.laser.intensity))
|
|
440
421
|
noise_values.append(noise)
|
|
441
422
|
|
|
442
423
|
plt.figure(figsize=(6, 5))
|
|
443
|
-
plt.plot(noise_values,
|
|
444
|
-
for i, (x, y) in zip(iter_range, zip(noise_values,
|
|
424
|
+
plt.plot(noise_values, self.CRC, 'o-', label=self.optimizer.name)
|
|
425
|
+
for i, (x, y) in zip(iter_range, zip(noise_values, self.CRC)):
|
|
445
426
|
plt.text(x, y, str(i), fontsize=5.5, ha='left', va='bottom')
|
|
446
427
|
|
|
447
428
|
plt.xlabel("Noise (mean absolute error)")
|
|
@@ -453,13 +434,16 @@ class AlgebraicRecon(Recon):
|
|
|
453
434
|
plt.title("CRC vs Noise over Iterations")
|
|
454
435
|
plt.grid(True)
|
|
455
436
|
plt.legend()
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
437
|
+
if isSaving:
|
|
438
|
+
now = datetime.now()
|
|
439
|
+
date_str = now.strftime("%Y_%d_%m_%y")
|
|
440
|
+
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_CRCvsNOISE_{self.optimizer.name}_{date_str}.png')
|
|
441
|
+
plt.savefig(SavingFolder, dpi=300)
|
|
442
|
+
if show_logs:
|
|
443
|
+
print(f"CRCvsNOISE plot saved to {SavingFolder}")
|
|
460
444
|
plt.show()
|
|
461
445
|
|
|
462
|
-
def show_reconstruction_progress(self, start=0, fin=None, save_path=None, with_tumor=True):
|
|
446
|
+
def show_reconstruction_progress(self, start=0, fin=None, save_path=None, with_tumor=True, show_logs=True):
|
|
463
447
|
"""
|
|
464
448
|
Show the reconstruction progress for either with or without tumor.
|
|
465
449
|
If isPropMSE is True, the frame selection is adapted to MSE changes.
|
|
@@ -564,114 +548,303 @@ class AlgebraicRecon(Recon):
|
|
|
564
548
|
else:
|
|
565
549
|
save_path = f"{save_path}_{title_suffix}"
|
|
566
550
|
plt.savefig(save_path, dpi=300)
|
|
567
|
-
|
|
551
|
+
if show_logs:
|
|
552
|
+
print(f"Figure saved to: {save_path}")
|
|
568
553
|
|
|
569
554
|
plt.show()
|
|
570
555
|
|
|
571
|
-
def
|
|
556
|
+
def checkExistingFile(self, date = None):
|
|
572
557
|
"""
|
|
573
|
-
|
|
574
|
-
|
|
558
|
+
Check if the reconstruction file already exists, based on current instance parameters.
|
|
559
|
+
|
|
560
|
+
Args:
|
|
561
|
+
withTumor (bool): If True, checks reconPhantom.npy; otherwise, checks reconLaser.npy.
|
|
562
|
+
overwrite (bool): If False, returns False if the file exists.
|
|
563
|
+
|
|
564
|
+
Returns:
|
|
565
|
+
tuple: (bool: whether to save, str: the filepath)
|
|
575
566
|
"""
|
|
576
567
|
if self.saveDir is None:
|
|
577
|
-
raise ValueError("Save directory is not specified.
|
|
578
|
-
if
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
if not self.reconLaser or len(self.reconLaser) == 0:
|
|
584
|
-
raise ValueError("Reconstructed laser is empty. Run reconstruction first.")
|
|
585
|
-
np.save(os.path.join(self.saveDir, 'results', 'reconLaser.npy'), np.array(self.reconLaser))
|
|
586
|
-
np.save(os.path.join(self.saveDir, 'results', 'reconIndices.npy'), np.array(self.indices))
|
|
587
|
-
print(f"Reconstruction results saved to {os.path.join(self.saveDir, 'results')}")
|
|
568
|
+
raise ValueError("Save directory is not specified.")
|
|
569
|
+
if date is None:
|
|
570
|
+
date = datetime.now().strftime("%d%m")
|
|
571
|
+
results_dir = os.path.join(self.saveDir, f'results_{date}_{self.optimizer.value}')
|
|
572
|
+
if not os.path.exists(results_dir):
|
|
573
|
+
os.makedirs(results_dir)
|
|
588
574
|
|
|
589
|
-
|
|
575
|
+
if os.path.exists(os.path.join(results_dir,"indices.npy")):
|
|
576
|
+
return (True, results_dir)
|
|
577
|
+
|
|
578
|
+
return (False, results_dir)
|
|
579
|
+
|
|
580
|
+
def load(self, withTumor=True, results_date=None, optimizer=None, filePath=None, show_logs=True):
|
|
590
581
|
"""
|
|
591
|
-
Load the reconstruction results (reconPhantom
|
|
592
|
-
|
|
582
|
+
Load the reconstruction results (reconPhantom or reconLaser) and indices as lists of 2D np arrays into self.
|
|
583
|
+
If the loaded file is a 3D array, it is split into a list of 2D arrays.
|
|
584
|
+
Args:
|
|
585
|
+
withTumor: If True, loads reconPhantom (with tumor), else reconLaser (without tumor).
|
|
586
|
+
results_date: Date string (format "ddmm") to specify which results to load. If None, uses the most recent date in saveDir.
|
|
587
|
+
optimizer: Optimizer name (as string or enum) to filter results. If None, uses the current optimizer of the instance.
|
|
588
|
+
filePath: Optional. If provided, loads directly from this path (overrides saveDir and results_date).
|
|
593
589
|
"""
|
|
594
|
-
if
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
recon_path =
|
|
598
|
-
if not os.path.
|
|
599
|
-
raise FileNotFoundError(f"
|
|
600
|
-
|
|
590
|
+
if filePath is not None:
|
|
591
|
+
# Mode chargement direct depuis un fichier
|
|
592
|
+
recon_key = 'reconPhantom' if withTumor else 'reconLaser'
|
|
593
|
+
recon_path = filePath
|
|
594
|
+
if not os.path.exists(recon_path):
|
|
595
|
+
raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
|
|
596
|
+
# Charge le fichier (3D ou liste de 2D)
|
|
597
|
+
data = np.load(recon_path, allow_pickle=True)
|
|
598
|
+
# Découpe en liste de 2D si c'est un tableau 3D
|
|
599
|
+
if isinstance(data, np.ndarray) and data.ndim == 3:
|
|
600
|
+
if withTumor:
|
|
601
|
+
self.reconPhantom = [data[i, :, :] for i in range(data.shape[0])]
|
|
602
|
+
else:
|
|
603
|
+
self.reconLaser = [data[i, :, :] for i in range(data.shape[0])]
|
|
604
|
+
else:
|
|
605
|
+
# Sinon, suppose que c'est déjà une liste de 2D
|
|
606
|
+
if withTumor:
|
|
607
|
+
self.reconPhantom = data
|
|
608
|
+
else:
|
|
609
|
+
self.reconLaser = data
|
|
610
|
+
# Essayer de charger les indices
|
|
611
|
+
base_dir, _ = os.path.split(recon_path)
|
|
612
|
+
indices_path = os.path.join(base_dir, 'indices.npy')
|
|
613
|
+
if os.path.exists(indices_path):
|
|
614
|
+
indices_data = np.load(indices_path, allow_pickle=True)
|
|
615
|
+
if isinstance(indices_data, np.ndarray) and indices_data.ndim == 3:
|
|
616
|
+
self.indices = [indices_data[i, :, :] for i in range(indices_data.shape[0])]
|
|
617
|
+
else:
|
|
618
|
+
self.indices = indices_data
|
|
619
|
+
else:
|
|
620
|
+
self.indices = None
|
|
621
|
+
|
|
622
|
+
if show_logs:
|
|
623
|
+
print(f"Loaded reconstruction results and indices from {recon_path}")
|
|
601
624
|
else:
|
|
602
|
-
|
|
603
|
-
if
|
|
604
|
-
raise
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
625
|
+
# Mode chargement depuis le répertoire de résultats
|
|
626
|
+
if self.saveDir is None:
|
|
627
|
+
raise ValueError("Save directory is not specified. Please set saveDir before loading.")
|
|
628
|
+
# Use current optimizer and potential function if not provided
|
|
629
|
+
opt_name = optimizer.value if optimizer is not None else self.optimizer.value
|
|
630
|
+
# Build the base directory pattern
|
|
631
|
+
dir_pattern = f'results_*_{opt_name}'
|
|
632
|
+
# Add parameters to the pattern based on the optimizer
|
|
633
|
+
if optimizer is None:
|
|
634
|
+
optimizer = self.optimizer
|
|
635
|
+
if optimizer == OptimizerType.PPGMLEM:
|
|
636
|
+
beta_str = f'_Beta_{self.beta}'
|
|
637
|
+
delta_str = f'_Delta_{self.delta}'
|
|
638
|
+
gamma_str = f'_Gamma_{self.gamma}'
|
|
639
|
+
sigma_str = f'_Sigma_{self.sigma}'
|
|
640
|
+
dir_pattern += f'{beta_str}{delta_str}{gamma_str}{sigma_str}'
|
|
641
|
+
elif optimizer in (OptimizerType.PGC, OptimizerType.DEPIERRO95):
|
|
642
|
+
beta_str = f'_Beta_{self.beta}'
|
|
643
|
+
sigma_str = f'_Sigma_{self.sigma}'
|
|
644
|
+
dir_pattern += f'{beta_str}{sigma_str}'
|
|
645
|
+
# Find the most recent results directory if no date is specified
|
|
646
|
+
if results_date is None:
|
|
647
|
+
dirs = [d for d in os.listdir(self.saveDir) if os.path.isdir(os.path.join(self.saveDir, d)) and dir_pattern in d]
|
|
648
|
+
if not dirs:
|
|
649
|
+
raise FileNotFoundError(f"No matching results directory found for pattern '{dir_pattern}' in {self.saveDir}.")
|
|
650
|
+
dirs.sort(reverse=True) # Most recent first
|
|
651
|
+
results_dir = os.path.join(self.saveDir, dirs[0])
|
|
652
|
+
else:
|
|
653
|
+
results_dir = os.path.join(self.saveDir, f'results_{results_date}_{opt_name}')
|
|
654
|
+
if optimizer == OptimizerType.MLEM:
|
|
655
|
+
pass
|
|
656
|
+
elif optimizer == OptimizerType.LS:
|
|
657
|
+
results_dir += f'_Alpha_{self.alpha}'
|
|
658
|
+
if not os.path.exists(results_dir):
|
|
659
|
+
raise FileNotFoundError(f"Directory {results_dir} does not exist.")
|
|
660
|
+
# Load reconstruction results
|
|
661
|
+
recon_key = 'reconPhantom' if withTumor else 'reconLaser'
|
|
662
|
+
recon_path = os.path.join(results_dir, f'{recon_key}.npy')
|
|
663
|
+
if not os.path.exists(recon_path):
|
|
664
|
+
raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
|
|
665
|
+
data = np.load(recon_path, allow_pickle=True)
|
|
666
|
+
if isinstance(data, np.ndarray) and data.ndim == 3:
|
|
667
|
+
if withTumor:
|
|
668
|
+
self.reconPhantom = [data[i, :, :] for i in range(data.shape[0])]
|
|
669
|
+
else:
|
|
670
|
+
self.reconLaser = [data[i, :, :] for i in range(data.shape[0])]
|
|
671
|
+
else:
|
|
672
|
+
if withTumor:
|
|
673
|
+
self.reconPhantom = data
|
|
674
|
+
else:
|
|
675
|
+
self.reconLaser = data
|
|
676
|
+
# Load saved indices as list of 2D arrays
|
|
677
|
+
indices_path = os.path.join(results_dir, 'indices.npy')
|
|
678
|
+
if not os.path.exists(indices_path):
|
|
679
|
+
raise FileNotFoundError(f"No indices file found at {indices_path}.")
|
|
680
|
+
indices_data = np.load(indices_path, allow_pickle=True)
|
|
681
|
+
if isinstance(indices_data, np.ndarray) and indices_data.ndim == 3:
|
|
682
|
+
self.indices = [indices_data[i, :, :] for i in range(indices_data.shape[0])]
|
|
683
|
+
else:
|
|
684
|
+
self.indices = indices_data
|
|
685
|
+
if show_logs:
|
|
686
|
+
print(f"Loaded reconstruction results and indices from {results_dir}")
|
|
687
|
+
|
|
612
688
|
def normalizeSMatrix(self):
|
|
613
|
-
self.SMatrix = self.SMatrix / (self.experiment.params.acoustic['voltage']*self.experiment.params.acoustic['sensitivity'])
|
|
689
|
+
self.SMatrix = self.SMatrix / (float(self.experiment.params.acoustic['voltage'])*float(self.experiment.params.acoustic['sensitivity']))
|
|
614
690
|
|
|
615
691
|
# PRIVATE METHODS
|
|
616
692
|
|
|
617
|
-
def
|
|
693
|
+
def _fillDenseSMatrix(self):
|
|
694
|
+
"""
|
|
695
|
+
Construit une matrice dense en mémoire.
|
|
696
|
+
"""
|
|
697
|
+
T, Z, X = self.experiment.AcousticFields[0].field.shape
|
|
698
|
+
N = len(self.experiment.AcousticFields)
|
|
699
|
+
S = np.empty((T, Z, X, N), dtype=np.float32)
|
|
700
|
+
def copy_block(i):
|
|
701
|
+
np.copyto(S[..., i], self.experiment.AcousticFields[i].field)
|
|
702
|
+
with concurrent.futures.ThreadPoolExecutor() as ex:
|
|
703
|
+
ex.map(copy_block, range(N))
|
|
704
|
+
return S
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
def _fillSparseSMatrix(self, isShowLogs=True):
|
|
708
|
+
if self.smatrixType == SMatrixType.CSR:
|
|
709
|
+
return self._fillSparseSMatrix_CSR(isShowLogs=isShowLogs)
|
|
710
|
+
if self.smatrixType == SMatrixType.COO:
|
|
711
|
+
raise NotImplementedError("COO sparse matrix not implemented yet.")
|
|
712
|
+
if self.smatrixType == SMatrixType.SELL:
|
|
713
|
+
return self._fillSparseSMatrix_SELL(isShowLogs=isShowLogs)
|
|
714
|
+
|
|
715
|
+
def _fillSparseSMatrix_CSR(self, isShowLogs=True):
|
|
716
|
+
"""
|
|
717
|
+
Construit une matrice sparse CSR par morceaux sans concaténation intermédiaire.
|
|
718
|
+
Libère toute la mémoire temporaire à chaque étape.
|
|
719
|
+
"""
|
|
720
|
+
sparse_matrix = SparseSMatrix_CSR(self.experiment,relative_threshold=self.sparseThreshold)
|
|
721
|
+
sparse_matrix.allocate()
|
|
722
|
+
if isShowLogs:
|
|
723
|
+
print(f" Sparse matrix size: {sparse_matrix.getMatrixSize()} GB")
|
|
724
|
+
print(f"Sparse matrix density: {sparse_matrix.compute_density()}")
|
|
725
|
+
return sparse_matrix
|
|
726
|
+
|
|
727
|
+
def _fillSparseSMatrix_SELL(self, isShowLogs=True):
|
|
728
|
+
"""
|
|
729
|
+
Construit une matrice sparse SELL par morceaux sans concaténation intermédiaire.
|
|
730
|
+
Libère toute la mémoire temporaire à chaque étape.
|
|
731
|
+
"""
|
|
732
|
+
sparse_matrix = SparseSMatrix_SELL(self.experiment,relative_threshold=self.sparseThreshold)
|
|
733
|
+
sparse_matrix.allocate()
|
|
734
|
+
# fenetre_gpu = get_apodization_vector_gpu(sparse_matrix)
|
|
735
|
+
# sparse_matrix.apply_apodization_gpu(fenetre_gpu)
|
|
736
|
+
if isShowLogs:
|
|
737
|
+
print(f" Sparse matrix size: {sparse_matrix.getMatrixSize()} GB")
|
|
738
|
+
print(f"Sparse matrix density: {sparse_matrix.compute_density()}")
|
|
739
|
+
return sparse_matrix
|
|
740
|
+
|
|
741
|
+
def _AlgebraicReconPython(self,withTumor, show_logs):
|
|
618
742
|
|
|
619
743
|
if withTumor:
|
|
620
744
|
if self.experiment.AOsignal_withTumor is None:
|
|
621
745
|
raise ValueError("AO signal with tumor is not available. Please generate AO signal with tumor the experiment first in the experiment object.")
|
|
622
|
-
if self.optimizer.value == OptimizerType.MLEM.value:
|
|
623
|
-
self.reconPhantom, self.indices = self._MLEM(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withTumor, withTumor=withTumor)
|
|
624
|
-
elif self.optimizer.value == OptimizerType.LS.value:
|
|
625
|
-
self.reconPhantom, self.indices = self._LS(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withTumor, withTumor=withTumor)
|
|
626
|
-
elif self.optimizer.value == OptimizerType.LS_TV.value:
|
|
627
|
-
self.reconPhantom, self.indices = self._LS_Regularized(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withTumor, withTumor=withTumor)
|
|
628
|
-
else:
|
|
629
|
-
raise ValueError(f"Only MLEM and LS are supported for simple algebraic reconstruction. {self.optimizer.value} need Bayesian reconstruction")
|
|
630
746
|
else:
|
|
631
747
|
if self.experiment.AOsignal_withoutTumor is None:
|
|
632
748
|
raise ValueError("AO signal without tumor is not available. Please generate AO signal without tumor the experiment first in the experiment object.")
|
|
633
|
-
if self.optimizer.value == OptimizerType.MLEM.value:
|
|
634
|
-
self.reconLaser, self.indices = self._MLEM(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withoutTumor, withTumor=withTumor)
|
|
635
|
-
elif self.optimizer.value == OptimizerType.LS.value:
|
|
636
|
-
self.reconLaser, self.indices = self._LS(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withoutTumor, withTumor=withTumor)
|
|
637
|
-
elif self.optimizer.value == OptimizerType.LS_TV.value:
|
|
638
|
-
self.reconLaser, self.indices = self._LS_Regularized(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withoutTumor, withTumor=withTumor)
|
|
639
|
-
else:
|
|
640
|
-
raise ValueError(f"Only MLEM and LS are supported for simple algebraic reconstruction. {self.optimizer.value} need Bayesian reconstruction")
|
|
641
749
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
750
|
+
if self.optimizer.value == OptimizerType.MLEM.value:
|
|
751
|
+
if withTumor:
|
|
752
|
+
self.reconPhantom, self.indices = MLEM(SMatrix=self.SMatrix,
|
|
753
|
+
y=self.experiment.AOsignal_withTumor,
|
|
754
|
+
numIterations=self.numIterations,
|
|
755
|
+
isSavingEachIteration=self.isSavingEachIteration,
|
|
756
|
+
withTumor=withTumor,
|
|
757
|
+
device=self.device,
|
|
758
|
+
use_numba=self.isMultiCPU,
|
|
759
|
+
denominator_threshold=self.denominatorThreshold,
|
|
760
|
+
max_saves=self.maxSaves,
|
|
761
|
+
show_logs=show_logs,
|
|
762
|
+
smatrixType=self.smatrixType,
|
|
763
|
+
Z=self.Z_dim
|
|
764
|
+
)
|
|
765
|
+
else:
|
|
766
|
+
self.reconLaser, self.indices = MLEM(SMatrix=self.SMatrix,
|
|
767
|
+
y=self.experiment.AOsignal_withoutTumor,
|
|
768
|
+
numIterations=self.numIterations,
|
|
769
|
+
isSavingEachIteration=self.isSavingEachIteration,
|
|
770
|
+
withTumor=withTumor,
|
|
771
|
+
device=self.device,
|
|
772
|
+
use_numba=self.isMultiCPU,
|
|
773
|
+
denominator_threshold=self.denominatorThreshold,
|
|
774
|
+
max_saves=self.maxSaves,
|
|
775
|
+
show_logs=show_logs,
|
|
776
|
+
smatrixType=self.smatrixType,
|
|
777
|
+
Z=self.Z_dim
|
|
778
|
+
)
|
|
779
|
+
elif self.optimizer.value == OptimizerType.LS.value:
|
|
780
|
+
if self.alpha is None:
|
|
781
|
+
raise ValueError("Alpha (regularization parameter) must be set for LS reconstruction.")
|
|
782
|
+
if withTumor:
|
|
783
|
+
self.reconPhantom, self.indices = LS(SMatrix=self.SMatrix,
|
|
784
|
+
y=self.experiment.AOsignal_withTumor,
|
|
785
|
+
numIterations=self.numIterations,
|
|
786
|
+
isSavingEachIteration=self.isSavingEachIteration,
|
|
787
|
+
withTumor=withTumor,
|
|
788
|
+
device=self.device,
|
|
789
|
+
use_numba=self.isMultiCPU,
|
|
790
|
+
denominator_threshold=self.denominatorThreshold,
|
|
791
|
+
max_saves=self.maxSaves,
|
|
792
|
+
show_logs=show_logs,
|
|
793
|
+
smatrixType=self.smatrixType,
|
|
794
|
+
Z=self.Z_dim
|
|
795
|
+
)
|
|
796
|
+
else:
|
|
797
|
+
self.reconLaser, self.indices = LS(SMatrix=self.SMatrix,
|
|
798
|
+
y=self.experiment.AOsignal_withoutTumor,
|
|
799
|
+
numIterations=self.numIterations,
|
|
800
|
+
isSavingEachIteration=self.isSavingEachIteration,
|
|
801
|
+
withTumor=withTumor,
|
|
802
|
+
alpha=self.alpha,
|
|
803
|
+
device=self.device,
|
|
804
|
+
use_numba=self.isMultiCPU,
|
|
805
|
+
denominator_threshold=self.denominatorThreshold,
|
|
806
|
+
max_saves=self.maxSaves,
|
|
807
|
+
show_logs=show_logs,
|
|
808
|
+
smatrixType=self.smatrixType,
|
|
809
|
+
Z=self.Z_dim
|
|
810
|
+
)
|
|
811
|
+
else:
|
|
812
|
+
raise ValueError(f"Only MLEM and LS are supported for simple algebraic reconstruction. {self.optimizer.value} need Bayesian reconstruction")
|
|
646
813
|
|
|
814
|
+
def _AlgebraicReconCASToR(self,withTumor, show_logs):
|
|
815
|
+
# Définir les chemins
|
|
816
|
+
smatrix = os.path.join(self.saveDir, "system_matrix")
|
|
647
817
|
if withTumor:
|
|
648
818
|
fileName = 'AOSignals_withTumor.cdh'
|
|
649
819
|
else:
|
|
650
820
|
fileName = 'AOSignals_withoutTumor.cdh'
|
|
651
821
|
|
|
652
|
-
#
|
|
653
|
-
if not os.path.isfile(
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
#
|
|
659
|
-
|
|
822
|
+
# Vérifier et générer les fichiers d'entrée si nécessaire
|
|
823
|
+
if not os.path.isfile(os.path.join(self.saveDir, fileName)):
|
|
824
|
+
if show_logs:
|
|
825
|
+
print(f"Fichier .cdh manquant. Génération de {fileName}...")
|
|
826
|
+
self.experiment.saveAOsignals_Castor(self.saveDir)
|
|
827
|
+
|
|
828
|
+
# Vérifier/générer la matrice système
|
|
829
|
+
if not os.path.isdir(smatrix):
|
|
830
|
+
os.makedirs(smatrix, exist_ok=True)
|
|
831
|
+
if not os.listdir(smatrix):
|
|
832
|
+
if show_logs:
|
|
833
|
+
print("Matrice système manquante. Génération...")
|
|
660
834
|
self.experiment.saveAcousticFields(self.saveDir)
|
|
661
835
|
|
|
662
|
-
# Vérifier que le
|
|
663
|
-
os.
|
|
836
|
+
# Vérifier que le fichier .cdh existe (redondant mais sûr)
|
|
837
|
+
if not os.path.isfile(os.path.join(self.saveDir, fileName)):
|
|
838
|
+
raise FileNotFoundError(f"Le fichier .cdh n'existe toujours pas : {fileName}")
|
|
664
839
|
|
|
665
|
-
#
|
|
666
|
-
|
|
667
|
-
raise FileNotFoundError(f"Le fichier .cdh n'existe pas : {fileName}")
|
|
840
|
+
# Créer le dossier de sortie
|
|
841
|
+
os.makedirs(os.path.join(self.saveDir, 'results', 'recon'), exist_ok=True)
|
|
668
842
|
|
|
669
|
-
#
|
|
843
|
+
# Configuration de l'environnement pour CASToR
|
|
670
844
|
env = os.environ.copy()
|
|
671
|
-
|
|
672
845
|
env.update({
|
|
673
|
-
"CASTOR_DIR":
|
|
674
|
-
"CASTOR_CONFIG":
|
|
846
|
+
"CASTOR_DIR": self.experiment.params.reconstruction['castor_executable'],
|
|
847
|
+
"CASTOR_CONFIG": os.path.join(self.experiment.params.reconstruction['castor_executable'], "config"),
|
|
675
848
|
"CASTOR_64bits": "1",
|
|
676
849
|
"CASTOR_OMP": "1",
|
|
677
850
|
"CASTOR_SIMD": "1",
|
|
@@ -680,13 +853,13 @@ class AlgebraicRecon(Recon):
|
|
|
680
853
|
|
|
681
854
|
# Construire la commande
|
|
682
855
|
cmd = [
|
|
683
|
-
|
|
684
|
-
"-df",
|
|
856
|
+
os.path.join(self.experiment.params.reconstruction['castor_executable'], "bin", "castor-recon"),
|
|
857
|
+
"-df", os.path.join(self.saveDir, fileName),
|
|
685
858
|
"-opti", self.optimizer.value,
|
|
686
859
|
"-it", f"{self.numIterations}:{self.numSubsets}",
|
|
687
860
|
"-proj", "matrix",
|
|
688
|
-
"-dout", os.path.join(self.saveDir, 'results','recon'),
|
|
689
|
-
"-th",
|
|
861
|
+
"-dout", os.path.join(self.saveDir, 'results', 'recon'),
|
|
862
|
+
"-th", str(os.cpu_count()),
|
|
690
863
|
"-vb", "5",
|
|
691
864
|
"-proj-comp", "1",
|
|
692
865
|
"-ignore-scanner",
|
|
@@ -695,90 +868,38 @@ class AlgebraicRecon(Recon):
|
|
|
695
868
|
"-system-matrix", smatrix,
|
|
696
869
|
]
|
|
697
870
|
|
|
698
|
-
#
|
|
699
|
-
|
|
871
|
+
# Afficher la commande (pour débogage)
|
|
872
|
+
if show_logs:
|
|
873
|
+
print("Commande CASToR :")
|
|
874
|
+
print(" ".join(cmd))
|
|
700
875
|
|
|
701
|
-
#
|
|
876
|
+
# Chemin du script temporaire
|
|
702
877
|
recon_script_path = os.path.join(gettempdir(), 'recon.sh')
|
|
878
|
+
|
|
879
|
+
# Écrire le script bash
|
|
703
880
|
with open(recon_script_path, 'w') as f:
|
|
704
881
|
f.write("#!/bin/bash\n")
|
|
882
|
+
f.write(f"export PATH={env['CASTOR_DIR']}/bin:$PATH\n") # Ajoute le chemin de CASToR au PATH
|
|
883
|
+
f.write(f"export LD_LIBRARY_PATH={env['CASTOR_DIR']}/lib:$LD_LIBRARY_PATH\n") # Ajoute les bibliothèques si nécessaire
|
|
705
884
|
f.write(" ".join(cmd) + "\n")
|
|
706
885
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
# --- Run Reconstruction Script ---
|
|
710
|
-
print(f"Running reconstruction script: {recon_script_path}")
|
|
886
|
+
# Rendre le script exécutable et l'exécuter
|
|
711
887
|
subprocess.run(["chmod", "+x", recon_script_path], check=True)
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
888
|
+
if show_logs:
|
|
889
|
+
print(f"Exécution de la reconstruction avec CASToR...")
|
|
890
|
+
result = subprocess.run(recon_script_path, env=env, check=True, capture_output=True, text=True)
|
|
891
|
+
|
|
892
|
+
# Afficher la sortie de CASToR (pour débogage)
|
|
893
|
+
if show_logs:
|
|
894
|
+
print("Sortie CASToR :")
|
|
895
|
+
print(result.stdout)
|
|
896
|
+
if result.stderr:
|
|
897
|
+
print("Erreurs :")
|
|
898
|
+
print(result.stderr)
|
|
899
|
+
|
|
900
|
+
if show_logs:
|
|
901
|
+
print("Reconstruction terminée avec succès.")
|
|
715
902
|
self.load_reconCASToR(withTumor=withTumor)
|
|
716
|
-
|
|
717
|
-
def _MLEM(self, SMatrix, y, withTumor):
|
|
718
|
-
"""
|
|
719
|
-
This method implements the MLEM algorithm using either CPU or single-GPU PyTorch acceleration.
|
|
720
|
-
Multi-GPU mode is disabled due to memory fragmentation issues or lack of availability.
|
|
721
|
-
"""
|
|
722
|
-
result = None
|
|
723
|
-
indices = None
|
|
724
|
-
required_memory = calculate_memory_requirement(SMatrix, y)
|
|
725
|
-
|
|
726
|
-
if self.isGPU:
|
|
727
|
-
if check_gpu_memory(config.select_best_gpu(), required_memory):
|
|
728
|
-
result, indices = MLEM._MLEM_GPU_basic(SMatrix=SMatrix, y=y, numIterations=self.numIterations, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor)
|
|
729
|
-
else:
|
|
730
|
-
warnings.warn("Insufficient GPU memory for single GPU MLEM. Falling back to CPU.")
|
|
731
|
-
|
|
732
|
-
if result is None and self.isMultiCPU:
|
|
733
|
-
result, indices = MLEM._MLEM_CPU_multi(SMatrix=SMatrix, y=y, numIterations=self.numIterations, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor)
|
|
734
|
-
|
|
735
|
-
if result is None:
|
|
736
|
-
result, indices = MLEM._MLEM_CPU_opti(SMatrix=SMatrix, y=y, numIterations=self.numIterations, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor)
|
|
737
|
-
if result is None:
|
|
738
|
-
warnings.warn("Optimized MLEM failed. Falling back to basic CPU MLEM.")
|
|
739
|
-
result, indices = MLEM._MLEM_CPU_basic(SMatrix=SMatrix, y=y, numIterations=self.numIterations, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor)
|
|
740
|
-
|
|
741
|
-
return result, indices
|
|
742
|
-
|
|
743
|
-
def _LS(self, SMatrix, y, withTumor):
|
|
744
|
-
"""
|
|
745
|
-
This method implements the LS algorithm using either CPU or single-GPU PyTorch acceleration.
|
|
746
|
-
Multi-GPU mode is disabled due to memory fragmentation issues or lack of availability.
|
|
747
|
-
"""
|
|
748
|
-
result = None
|
|
749
|
-
indices = None
|
|
750
|
-
required_memory = calculate_memory_requirement(SMatrix, y)
|
|
751
|
-
|
|
752
|
-
if self.isGPU:
|
|
753
|
-
if check_gpu_memory(config.select_best_gpu(), required_memory):
|
|
754
|
-
result, indices = LS._LS_GPU_basic(SMatrix=SMatrix, y=y, numIterations=self.numIterations, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor)
|
|
755
|
-
else:
|
|
756
|
-
warnings.warn("Insufficient GPU memory for single GPU LS. Falling back to CPU.")
|
|
757
|
-
|
|
758
|
-
if result is None and self.isMultiCPU:
|
|
759
|
-
result, indices = LS._LS_CPU_multi(SMatrix=SMatrix, y=y, numIterations=self.numIterations, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor)
|
|
760
|
-
|
|
761
|
-
if result is None:
|
|
762
|
-
result, indices = LS._LS_CPU_opti(SMatrix=SMatrix, y=y, numIterations=self.numIterations, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor)
|
|
763
|
-
if result is None:
|
|
764
|
-
warnings.warn("Optimized LS failed. Falling back to basic CPU LS.")
|
|
765
|
-
result, indices = LS._LS_CPU_basic(SMatrix=SMatrix, y=y, numIterations=self.numIterations, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor)
|
|
766
|
-
|
|
767
|
-
return result, indices
|
|
768
|
-
|
|
769
|
-
def _LS_Regularized(self,SMatrix, y, withTumor):
|
|
770
|
-
result = None
|
|
771
|
-
indices = None
|
|
772
|
-
required_memory = calculate_memory_requirement(SMatrix, y)
|
|
773
|
-
|
|
774
|
-
if check_gpu_memory(config.select_best_gpu(), required_memory):
|
|
775
|
-
if self.lambda_reg is None or self.L_Factor is None:
|
|
776
|
-
raise ValueError("For LS with TV regularization, both lambda_reg and L_Factor must be specified.")
|
|
777
|
-
result, indices = LS._LS_TV_GPU(SMatrix= SMatrix, y=y, numIterations=self.numIterations, isSavingEachIteration=self.isSavingEachIteration, withTumor=withTumor, lambda_tv=self.lambda_reg, L_Factor=self.L_Factor)
|
|
778
|
-
else:
|
|
779
|
-
warnings.warn("Insufficient GPU memory for single GPU LS with TV regularization. Falling back to CPU.")
|
|
780
|
-
raise NotImplementedError("LS with TV regularization is not implemented for CPU.")
|
|
781
|
-
return result, indices
|
|
782
903
|
|
|
783
904
|
# STATIC METHODS
|
|
784
905
|
@staticmethod
|