AOT-biomaps 2.9.176__py3-none-any.whl → 2.9.300__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 +406 -13
- AOT_biomaps/AOT_Recon/AOT_Optimizers/MAPEM.py +118 -38
- AOT_biomaps/AOT_Recon/AOT_Optimizers/MLEM.py +390 -102
- AOT_biomaps/AOT_Recon/AOT_Optimizers/PDHG.py +443 -12
- AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/RelativeDifferences.py +10 -14
- AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_CSR.py +274 -0
- AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_SELL.py +331 -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 +259 -153
- AOT_biomaps/AOT_Recon/AnalyticRecon.py +27 -42
- AOT_biomaps/AOT_Recon/BayesianRecon.py +84 -151
- AOT_biomaps/AOT_Recon/DeepLearningRecon.py +1 -1
- AOT_biomaps/AOT_Recon/PrimalDualRecon.py +162 -102
- AOT_biomaps/AOT_Recon/ReconEnums.py +27 -2
- AOT_biomaps/AOT_Recon/ReconTools.py +229 -12
- AOT_biomaps/AOT_Recon/__init__.py +1 -0
- AOT_biomaps/AOT_Recon/_mainRecon.py +72 -58
- AOT_biomaps/__init__.py +4 -53
- {aot_biomaps-2.9.176.dist-info → aot_biomaps-2.9.300.dist-info}/METADATA +2 -1
- aot_biomaps-2.9.300.dist-info/RECORD +47 -0
- aot_biomaps-2.9.176.dist-info/RECORD +0 -43
- {aot_biomaps-2.9.176.dist-info → aot_biomaps-2.9.300.dist-info}/WHEEL +0 -0
- {aot_biomaps-2.9.176.dist-info → aot_biomaps-2.9.300.dist-info}/top_level.txt +0 -0
|
@@ -1,39 +1,49 @@
|
|
|
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, maxSaves = 5000, alpha = None, **kwargs):
|
|
28
|
+
def __init__(self, opti = OptimizerType.MLEM, numIterations = 10000, numSubsets = 1, isSavingEachIteration=True, maxSaves = 5000, alpha = None, denominatorThreshold = 1e-6, smatrixType = SMatrixType.SELL, sparseThreshold=0.1, device = None, **kwargs):
|
|
27
29
|
super().__init__(**kwargs)
|
|
28
30
|
self.reconType = ReconType.Algebraic
|
|
29
31
|
self.optimizer = opti
|
|
30
32
|
self.reconPhantom = []
|
|
31
33
|
self.reconLaser = []
|
|
34
|
+
self.indices = []
|
|
32
35
|
self.numIterations = numIterations
|
|
33
36
|
self.numSubsets = numSubsets
|
|
34
37
|
self.isSavingEachIteration = isSavingEachIteration
|
|
35
38
|
self.maxSaves = maxSaves
|
|
39
|
+
self.denominatorThreshold = denominatorThreshold
|
|
36
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
|
|
37
47
|
|
|
38
48
|
if self.numIterations <= 0:
|
|
39
49
|
raise ValueError("Number of iterations must be greater than 0.")
|
|
@@ -45,38 +55,26 @@ class AlgebraicRecon(Recon):
|
|
|
45
55
|
raise TypeError("Number of subsets must be an integer.")
|
|
46
56
|
|
|
47
57
|
print("Generating system matrix (processing acoustic fields)...")
|
|
48
|
-
self.
|
|
58
|
+
if self.smatrixType == SMatrixType.DENSE:
|
|
59
|
+
self.SMatrix = self._fillDenseSMatrix()
|
|
60
|
+
else:
|
|
61
|
+
self.SMatrix = self._fillSparseSMatrix(isShowLogs=True)
|
|
49
62
|
|
|
50
63
|
# PUBLIC METHODS
|
|
51
64
|
|
|
52
|
-
def run(self, processType = ProcessType.PYTHON, withTumor= True):
|
|
65
|
+
def run(self, processType = ProcessType.PYTHON, withTumor= True, show_logs=True):
|
|
53
66
|
"""
|
|
54
67
|
This method is a placeholder for the Algebraic reconstruction process.
|
|
55
68
|
It currently does not perform any operations but serves as a template for future implementations.
|
|
56
69
|
"""
|
|
57
|
-
|
|
58
70
|
if(processType == ProcessType.CASToR):
|
|
59
|
-
self._AlgebraicReconCASToR(withTumor)
|
|
71
|
+
self._AlgebraicReconCASToR(withTumor=withTumor, show_logs=show_logs)
|
|
60
72
|
elif(processType == ProcessType.PYTHON):
|
|
61
|
-
self._AlgebraicReconPython(withTumor)
|
|
73
|
+
self._AlgebraicReconPython(withTumor=withTumor, show_logs=show_logs)
|
|
62
74
|
else:
|
|
63
75
|
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):
|
|
76
|
+
|
|
77
|
+
def plot_MSE(self, isSaving=True, log_scale_x=False, log_scale_y=False, show_logs=True):
|
|
80
78
|
"""
|
|
81
79
|
Plot the Mean Squared Error (MSE) of the reconstruction.
|
|
82
80
|
|
|
@@ -91,8 +89,8 @@ class AlgebraicRecon(Recon):
|
|
|
91
89
|
raise ValueError("MSE is empty. Please calculate MSE first.")
|
|
92
90
|
|
|
93
91
|
best_idx = self.indices[np.argmin(self.MSE)]
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
if show_logs:
|
|
93
|
+
print(f"Lowest MSE = {np.min(self.MSE):.4f} at iteration {best_idx+1}")
|
|
96
94
|
# Plot MSE curve
|
|
97
95
|
plt.figure(figsize=(7, 5))
|
|
98
96
|
plt.plot(self.indices, self.MSE, 'r-', label="MSE curve")
|
|
@@ -119,22 +117,19 @@ class AlgebraicRecon(Recon):
|
|
|
119
117
|
scale_str = "_logx"
|
|
120
118
|
elif log_scale_y:
|
|
121
119
|
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')
|
|
120
|
+
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_MSE_plot_{self.optimizer.name}_{scale_str}{date_str}.png')
|
|
126
121
|
plt.savefig(SavingFolder, dpi=300)
|
|
127
|
-
|
|
122
|
+
if show_logs:
|
|
123
|
+
print(f"MSE plot saved to {SavingFolder}")
|
|
128
124
|
|
|
129
125
|
plt.show()
|
|
130
126
|
|
|
131
|
-
def show_MSE_bestRecon(self, isSaving=True):
|
|
127
|
+
def show_MSE_bestRecon(self, isSaving=True, show_logs=True):
|
|
132
128
|
if not self.MSE:
|
|
133
129
|
raise ValueError("MSE is empty. Please calculate MSE first.")
|
|
134
130
|
|
|
135
131
|
|
|
136
132
|
best_idx = np.argmin(self.MSE)
|
|
137
|
-
print(best_idx)
|
|
138
133
|
best_recon = self.reconPhantom[best_idx]
|
|
139
134
|
|
|
140
135
|
# Crée la figure et les axes
|
|
@@ -163,7 +158,6 @@ class AlgebraicRecon(Recon):
|
|
|
163
158
|
|
|
164
159
|
# Right: Reconstruction at last iteration
|
|
165
160
|
lastRecon = self.reconPhantom[-1]
|
|
166
|
-
print(lastRecon.shape)
|
|
167
161
|
if self.experiment.OpticImage.phantom.shape != lastRecon.shape:
|
|
168
162
|
lastRecon = lastRecon.T
|
|
169
163
|
im2 = axs[2].imshow(lastRecon,
|
|
@@ -190,17 +184,14 @@ class AlgebraicRecon(Recon):
|
|
|
190
184
|
savePath = os.path.join(self.saveDir, 'results')
|
|
191
185
|
if not os.path.exists(savePath):
|
|
192
186
|
os.makedirs(savePath)
|
|
193
|
-
|
|
194
|
-
namePath = f'{self.SMatrix.shape[3]}_SCANS_comparison_MSE_BestANDLastRecon_MLEM_Date_{date_str}.png'
|
|
195
|
-
elif self.optimizer == OptimizerType.LS:
|
|
196
|
-
namePath = f'{self.SMatrix.shape[3]}_SCANS_comparison_MSE_BestANDLastRecon_LS_Date_{date_str}.png'
|
|
197
|
-
SavingFolder = os.path.join(savePath, namePath)
|
|
187
|
+
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_comparison_MSE_BestANDLastRecon_{self.optimizer.name}_{date_str}.png')
|
|
198
188
|
plt.savefig(SavingFolder, dpi=300, bbox_inches='tight')
|
|
199
|
-
|
|
189
|
+
if show_logs:
|
|
190
|
+
print(f"MSE plot saved to {SavingFolder}")
|
|
200
191
|
|
|
201
192
|
plt.show()
|
|
202
193
|
|
|
203
|
-
def show_theta_animation(self, vmin=None, vmax=None, total_duration_ms=3000, save_path=None, max_frames=1000, isPropMSE=True):
|
|
194
|
+
def show_theta_animation(self, vmin=None, vmax=None, total_duration_ms=3000, save_path=None, max_frames=1000, isPropMSE=True, show_logs=True):
|
|
204
195
|
"""
|
|
205
196
|
Show theta iteration animation with speed proportional to MSE acceleration.
|
|
206
197
|
In "propMSE" mode: slow down when MSE changes rapidly, speed up when MSE stagnates.
|
|
@@ -303,18 +294,19 @@ class AlgebraicRecon(Recon):
|
|
|
303
294
|
ani.save(save_path, writer=animation.PillowWriter(fps=100))
|
|
304
295
|
elif save_path.endswith(".mp4"):
|
|
305
296
|
ani.save(save_path, writer="ffmpeg", fps=30)
|
|
306
|
-
|
|
297
|
+
if show_logs:
|
|
298
|
+
print(f"Animation saved to {save_path}")
|
|
307
299
|
|
|
308
300
|
plt.close(fig)
|
|
309
301
|
return HTML(ani.to_jshtml())
|
|
310
302
|
|
|
311
|
-
def plot_SSIM(self, isSaving=True, log_scale_x=False, log_scale_y=False):
|
|
303
|
+
def plot_SSIM(self, isSaving=True, log_scale_x=False, log_scale_y=False, show_logs=True):
|
|
312
304
|
if not self.SSIM:
|
|
313
305
|
raise ValueError("SSIM is empty. Please calculate SSIM first.")
|
|
314
306
|
|
|
315
307
|
best_idx = self.indices[np.argmax(self.SSIM)]
|
|
316
|
-
|
|
317
|
-
|
|
308
|
+
if show_logs:
|
|
309
|
+
print(f"Highest SSIM = {np.max(self.SSIM):.4f} at iteration {best_idx+1}")
|
|
318
310
|
# Plot SSIM curve
|
|
319
311
|
plt.figure(figsize=(7, 5))
|
|
320
312
|
plt.plot(self.indices, self.SSIM, 'r-', label="SSIM curve")
|
|
@@ -341,16 +333,14 @@ class AlgebraicRecon(Recon):
|
|
|
341
333
|
scale_str = "_logx"
|
|
342
334
|
elif log_scale_y:
|
|
343
335
|
scale_str = "_logy"
|
|
344
|
-
|
|
345
|
-
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_SSIM_plot_MLEM{scale_str}{date_str}.png')
|
|
346
|
-
elif self.optimizer == OptimizerType.LS:
|
|
347
|
-
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_SSIM_plot_LS{scale_str}{date_str}.png')
|
|
336
|
+
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_SSIM_plot_{self.optimizer.name}_{scale_str}{date_str}.png')
|
|
348
337
|
plt.savefig(SavingFolder, dpi=300)
|
|
349
|
-
|
|
338
|
+
if show_logs:
|
|
339
|
+
print(f"SSIM plot saved to {SavingFolder}")
|
|
350
340
|
|
|
351
341
|
plt.show()
|
|
352
342
|
|
|
353
|
-
def show_SSIM_bestRecon(self, isSaving=True):
|
|
343
|
+
def show_SSIM_bestRecon(self, isSaving=True, show_logs=True):
|
|
354
344
|
|
|
355
345
|
if not self.SSIM:
|
|
356
346
|
raise ValueError("SSIM is empty. Please calculate SSIM first.")
|
|
@@ -361,9 +351,6 @@ class AlgebraicRecon(Recon):
|
|
|
361
351
|
# ----------------- Plotting -----------------
|
|
362
352
|
_, axs = plt.subplots(1, 3, figsize=(15, 5)) # 1 row, 3 columns
|
|
363
353
|
|
|
364
|
-
# Normalization based on LAMBDA max
|
|
365
|
-
lambda_max = np.max(self.experiment.OpticImage.laser.intensity)
|
|
366
|
-
|
|
367
354
|
# Left: Best reconstructed image (normalized)
|
|
368
355
|
im0 = axs[0].imshow(best_recon,
|
|
369
356
|
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]),
|
|
@@ -396,12 +383,13 @@ class AlgebraicRecon(Recon):
|
|
|
396
383
|
if isSaving:
|
|
397
384
|
now = datetime.now()
|
|
398
385
|
date_str = now.strftime("%Y_%d_%m_%y")
|
|
399
|
-
SavingFolder = os.path.join(self.saveDir,
|
|
386
|
+
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_comparison_SSIM_BestANDLastRecon_{self.optimizer.name}_{date_str}.png')
|
|
400
387
|
plt.savefig(SavingFolder, dpi=300)
|
|
401
|
-
|
|
388
|
+
if show_logs:
|
|
389
|
+
print(f"SSIM plot saved to {SavingFolder}")
|
|
402
390
|
plt.show()
|
|
403
391
|
|
|
404
|
-
def plot_CRC_vs_Noise(self,
|
|
392
|
+
def plot_CRC_vs_Noise(self, use_ROI=True, fin=None, isSaving=True, show_logs=True):
|
|
405
393
|
"""
|
|
406
394
|
Plot CRC (Contrast Recovery Coefficient) vs Noise for each iteration.
|
|
407
395
|
"""
|
|
@@ -418,12 +406,11 @@ class AlgebraicRecon(Recon):
|
|
|
418
406
|
fin = len(self.reconPhantom) - 1
|
|
419
407
|
|
|
420
408
|
iter_range = self.indices
|
|
421
|
-
|
|
422
|
-
crc_values = []
|
|
423
|
-
noise_values = []
|
|
424
409
|
|
|
425
410
|
if self.CRC is None:
|
|
426
|
-
self.calculateCRC(use_ROI=
|
|
411
|
+
self.calculateCRC(use_ROI=use_ROI)
|
|
412
|
+
|
|
413
|
+
noise_values = []
|
|
427
414
|
|
|
428
415
|
for i in iter_range:
|
|
429
416
|
recon_without_tumor = self.reconLaser[i].T
|
|
@@ -432,8 +419,8 @@ class AlgebraicRecon(Recon):
|
|
|
432
419
|
noise_values.append(noise)
|
|
433
420
|
|
|
434
421
|
plt.figure(figsize=(6, 5))
|
|
435
|
-
plt.plot(noise_values,
|
|
436
|
-
for i, (x, y) in zip(iter_range, zip(noise_values,
|
|
422
|
+
plt.plot(noise_values, self.CRC, 'o-', label=self.optimizer.name)
|
|
423
|
+
for i, (x, y) in zip(iter_range, zip(noise_values, self.CRC)):
|
|
437
424
|
plt.text(x, y, str(i), fontsize=5.5, ha='left', va='bottom')
|
|
438
425
|
|
|
439
426
|
plt.xlabel("Noise (mean absolute error)")
|
|
@@ -445,13 +432,16 @@ class AlgebraicRecon(Recon):
|
|
|
445
432
|
plt.title("CRC vs Noise over Iterations")
|
|
446
433
|
plt.grid(True)
|
|
447
434
|
plt.legend()
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
435
|
+
if isSaving:
|
|
436
|
+
now = datetime.now()
|
|
437
|
+
date_str = now.strftime("%Y_%d_%m_%y")
|
|
438
|
+
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_CRCvsNOISE_{self.optimizer.name}_{date_str}.png')
|
|
439
|
+
plt.savefig(SavingFolder, dpi=300)
|
|
440
|
+
if show_logs:
|
|
441
|
+
print(f"CRCvsNOISE plot saved to {SavingFolder}")
|
|
452
442
|
plt.show()
|
|
453
443
|
|
|
454
|
-
def show_reconstruction_progress(self, start=0, fin=None, save_path=None, with_tumor=True):
|
|
444
|
+
def show_reconstruction_progress(self, start=0, fin=None, save_path=None, with_tumor=True, show_logs=True):
|
|
455
445
|
"""
|
|
456
446
|
Show the reconstruction progress for either with or without tumor.
|
|
457
447
|
If isPropMSE is True, the frame selection is adapted to MSE changes.
|
|
@@ -556,11 +546,12 @@ class AlgebraicRecon(Recon):
|
|
|
556
546
|
else:
|
|
557
547
|
save_path = f"{save_path}_{title_suffix}"
|
|
558
548
|
plt.savefig(save_path, dpi=300)
|
|
559
|
-
|
|
549
|
+
if show_logs:
|
|
550
|
+
print(f"Figure saved to: {save_path}")
|
|
560
551
|
|
|
561
552
|
plt.show()
|
|
562
553
|
|
|
563
|
-
def checkExistingFile(self,
|
|
554
|
+
def checkExistingFile(self, date = None):
|
|
564
555
|
"""
|
|
565
556
|
Check if the reconstruction file already exists, based on current instance parameters.
|
|
566
557
|
|
|
@@ -579,18 +570,15 @@ class AlgebraicRecon(Recon):
|
|
|
579
570
|
if not os.path.exists(results_dir):
|
|
580
571
|
os.makedirs(results_dir)
|
|
581
572
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
if os.path.exists(filepath):
|
|
586
|
-
return (True, filepath)
|
|
573
|
+
if os.path.exists(os.path.join(results_dir,"indices.npy")):
|
|
574
|
+
return (True, results_dir)
|
|
587
575
|
|
|
588
|
-
return (False,
|
|
576
|
+
return (False, results_dir)
|
|
589
577
|
|
|
590
|
-
|
|
591
|
-
def load(self, withTumor=True, results_date=None, optimizer=None, filePath=None):
|
|
578
|
+
def load(self, withTumor=True, results_date=None, optimizer=None, filePath=None, show_logs=True):
|
|
592
579
|
"""
|
|
593
|
-
Load the reconstruction results (reconPhantom or reconLaser) and indices into self.
|
|
580
|
+
Load the reconstruction results (reconPhantom or reconLaser) and indices as lists of 2D np arrays into self.
|
|
581
|
+
If the loaded file is a 3D array, it is split into a list of 2D arrays.
|
|
594
582
|
Args:
|
|
595
583
|
withTumor: If True, loads reconPhantom (with tumor), else reconLaser (without tumor).
|
|
596
584
|
results_date: Date string (format "ddmm") to specify which results to load. If None, uses the most recent date in saveDir.
|
|
@@ -603,109 +591,221 @@ class AlgebraicRecon(Recon):
|
|
|
603
591
|
recon_path = filePath
|
|
604
592
|
if not os.path.exists(recon_path):
|
|
605
593
|
raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
594
|
+
# Charge le fichier (3D ou liste de 2D)
|
|
595
|
+
data = np.load(recon_path, allow_pickle=True)
|
|
596
|
+
# Découpe en liste de 2D si c'est un tableau 3D
|
|
597
|
+
if isinstance(data, np.ndarray) and data.ndim == 3:
|
|
598
|
+
if withTumor:
|
|
599
|
+
self.reconPhantom = [data[i, :, :] for i in range(data.shape[0])]
|
|
600
|
+
else:
|
|
601
|
+
self.reconLaser = [data[i, :, :] for i in range(data.shape[0])]
|
|
609
602
|
else:
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
603
|
+
# Sinon, suppose que c'est déjà une liste de 2D
|
|
604
|
+
if withTumor:
|
|
605
|
+
self.reconPhantom = data
|
|
606
|
+
else:
|
|
607
|
+
self.reconLaser = data
|
|
608
|
+
# Essayer de charger les indices
|
|
609
|
+
base_dir, _ = os.path.split(recon_path)
|
|
610
|
+
indices_path = os.path.join(base_dir, 'indices.npy')
|
|
616
611
|
if os.path.exists(indices_path):
|
|
617
|
-
|
|
612
|
+
indices_data = np.load(indices_path, allow_pickle=True)
|
|
613
|
+
if isinstance(indices_data, np.ndarray) and indices_data.ndim == 3:
|
|
614
|
+
self.indices = [indices_data[i, :, :] for i in range(indices_data.shape[0])]
|
|
615
|
+
else:
|
|
616
|
+
self.indices = indices_data
|
|
618
617
|
else:
|
|
619
618
|
self.indices = None
|
|
620
|
-
|
|
621
|
-
|
|
619
|
+
|
|
620
|
+
if show_logs:
|
|
621
|
+
print(f"Loaded reconstruction results and indices from {recon_path}")
|
|
622
622
|
else:
|
|
623
623
|
# Mode chargement depuis le répertoire de résultats
|
|
624
624
|
if self.saveDir is None:
|
|
625
625
|
raise ValueError("Save directory is not specified. Please set saveDir before loading.")
|
|
626
|
-
|
|
627
|
-
# Determine optimizer name for path matching
|
|
626
|
+
# Use current optimizer and potential function if not provided
|
|
628
627
|
opt_name = optimizer.value if optimizer is not None else self.optimizer.value
|
|
629
|
-
|
|
628
|
+
# Build the base directory pattern
|
|
629
|
+
dir_pattern = f'results_*_{opt_name}'
|
|
630
|
+
# Add parameters to the pattern based on the optimizer
|
|
631
|
+
if optimizer is None:
|
|
632
|
+
optimizer = self.optimizer
|
|
633
|
+
if optimizer == OptimizerType.PPGMLEM:
|
|
634
|
+
beta_str = f'_Beta_{self.beta}'
|
|
635
|
+
delta_str = f'_Delta_{self.delta}'
|
|
636
|
+
gamma_str = f'_Gamma_{self.gamma}'
|
|
637
|
+
sigma_str = f'_Sigma_{self.sigma}'
|
|
638
|
+
dir_pattern += f'{beta_str}{delta_str}{gamma_str}{sigma_str}'
|
|
639
|
+
elif optimizer in (OptimizerType.PGC, OptimizerType.DEPIERRO95):
|
|
640
|
+
beta_str = f'_Beta_{self.beta}'
|
|
641
|
+
sigma_str = f'_Sigma_{self.sigma}'
|
|
642
|
+
dir_pattern += f'{beta_str}{sigma_str}'
|
|
630
643
|
# Find the most recent results directory if no date is specified
|
|
631
644
|
if results_date is None:
|
|
632
|
-
dir_pattern = f'results_*_{opt_name}'
|
|
633
645
|
dirs = [d for d in os.listdir(self.saveDir) if os.path.isdir(os.path.join(self.saveDir, d)) and dir_pattern in d]
|
|
634
646
|
if not dirs:
|
|
635
|
-
raise FileNotFoundError(f"No results directory found for
|
|
647
|
+
raise FileNotFoundError(f"No matching results directory found for pattern '{dir_pattern}' in {self.saveDir}.")
|
|
636
648
|
dirs.sort(reverse=True) # Most recent first
|
|
637
649
|
results_dir = os.path.join(self.saveDir, dirs[0])
|
|
638
650
|
else:
|
|
639
|
-
results_dir = os.path.join(self.saveDir, f'results_{results_date}_{opt_name}
|
|
651
|
+
results_dir = os.path.join(self.saveDir, f'results_{results_date}_{opt_name}')
|
|
652
|
+
if optimizer == OptimizerType.MLEM:
|
|
653
|
+
pass
|
|
654
|
+
elif optimizer == OptimizerType.LS:
|
|
655
|
+
results_dir += f'_Alpha_{self.alpha}'
|
|
640
656
|
if not os.path.exists(results_dir):
|
|
641
657
|
raise FileNotFoundError(f"Directory {results_dir} does not exist.")
|
|
642
|
-
|
|
643
658
|
# Load reconstruction results
|
|
644
659
|
recon_key = 'reconPhantom' if withTumor else 'reconLaser'
|
|
645
660
|
recon_path = os.path.join(results_dir, f'{recon_key}.npy')
|
|
646
661
|
if not os.path.exists(recon_path):
|
|
647
662
|
raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
|
|
648
|
-
|
|
649
|
-
if
|
|
650
|
-
|
|
663
|
+
data = np.load(recon_path, allow_pickle=True)
|
|
664
|
+
if isinstance(data, np.ndarray) and data.ndim == 3:
|
|
665
|
+
if withTumor:
|
|
666
|
+
self.reconPhantom = [data[i, :, :] for i in range(data.shape[0])]
|
|
667
|
+
else:
|
|
668
|
+
self.reconLaser = [data[i, :, :] for i in range(data.shape[0])]
|
|
651
669
|
else:
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
670
|
+
if withTumor:
|
|
671
|
+
self.reconPhantom = data
|
|
672
|
+
else:
|
|
673
|
+
self.reconLaser = data
|
|
674
|
+
# Load saved indices as list of 2D arrays
|
|
675
|
+
indices_path = os.path.join(results_dir, 'indices.npy')
|
|
676
|
+
if not os.path.exists(indices_path):
|
|
677
|
+
raise FileNotFoundError(f"No indices file found at {indices_path}.")
|
|
678
|
+
indices_data = np.load(indices_path, allow_pickle=True)
|
|
679
|
+
if isinstance(indices_data, np.ndarray) and indices_data.ndim == 3:
|
|
680
|
+
self.indices = [indices_data[i, :, :] for i in range(indices_data.shape[0])]
|
|
658
681
|
else:
|
|
659
|
-
self.indices =
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
682
|
+
self.indices = indices_data
|
|
683
|
+
if show_logs:
|
|
684
|
+
print(f"Loaded reconstruction results and indices from {results_dir}")
|
|
685
|
+
|
|
666
686
|
def normalizeSMatrix(self):
|
|
667
687
|
self.SMatrix = self.SMatrix / (float(self.experiment.params.acoustic['voltage'])*float(self.experiment.params.acoustic['sensitivity']))
|
|
668
688
|
|
|
669
689
|
# PRIVATE METHODS
|
|
670
690
|
|
|
671
|
-
def
|
|
691
|
+
def _fillDenseSMatrix(self):
|
|
692
|
+
"""
|
|
693
|
+
Construit une matrice dense en mémoire.
|
|
694
|
+
"""
|
|
695
|
+
T, Z, X = self.experiment.AcousticFields[0].field.shape
|
|
696
|
+
N = len(self.experiment.AcousticFields)
|
|
697
|
+
S = np.empty((T, Z, X, N), dtype=np.float32)
|
|
698
|
+
def copy_block(i):
|
|
699
|
+
np.copyto(S[..., i], self.experiment.AcousticFields[i].field)
|
|
700
|
+
with concurrent.futures.ThreadPoolExecutor() as ex:
|
|
701
|
+
ex.map(copy_block, range(N))
|
|
702
|
+
return S
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
def _fillSparseSMatrix(self, isShowLogs=True):
|
|
706
|
+
if self.smatrixType == SMatrixType.CSR:
|
|
707
|
+
return self._fillSparseSMatrix_CSR(isShowLogs=isShowLogs)
|
|
708
|
+
if self.smatrixType == SMatrixType.COO:
|
|
709
|
+
raise NotImplementedError("COO sparse matrix not implemented yet.")
|
|
710
|
+
if self.smatrixType == SMatrixType.SELL:
|
|
711
|
+
return self._fillSparseSMatrix_SELL(isShowLogs=isShowLogs)
|
|
712
|
+
|
|
713
|
+
def _fillSparseSMatrix_CSR(self, isShowLogs=True):
|
|
714
|
+
"""
|
|
715
|
+
Construit une matrice sparse CSR par morceaux sans concaténation intermédiaire.
|
|
716
|
+
Libère toute la mémoire temporaire à chaque étape.
|
|
717
|
+
"""
|
|
718
|
+
sparse_matrix = SparseSMatrix_CSR(self.experiment,relative_threshold=self.sparseThreshold)
|
|
719
|
+
sparse_matrix.allocate()
|
|
720
|
+
if isShowLogs:
|
|
721
|
+
print(f" Sparse matrix size: {sparse_matrix.getMatrixSize()} GB")
|
|
722
|
+
print(f"Sparse matrix density: {sparse_matrix.compute_density()}")
|
|
723
|
+
return sparse_matrix
|
|
724
|
+
|
|
725
|
+
def _fillSparseSMatrix_SELL(self, isShowLogs=True):
|
|
726
|
+
"""
|
|
727
|
+
Construit une matrice sparse SELL par morceaux sans concaténation intermédiaire.
|
|
728
|
+
Libère toute la mémoire temporaire à chaque étape.
|
|
729
|
+
"""
|
|
730
|
+
sparse_matrix = SparseSMatrix_SELL(self.experiment,relative_threshold=self.sparseThreshold)
|
|
731
|
+
sparse_matrix.allocate()
|
|
732
|
+
# fenetre_gpu = get_apodization_vector_gpu(sparse_matrix)
|
|
733
|
+
# sparse_matrix.apply_apodization_gpu(fenetre_gpu)
|
|
734
|
+
if isShowLogs:
|
|
735
|
+
print(f" Sparse matrix size: {sparse_matrix.getMatrixSize()} GB")
|
|
736
|
+
print(f"Sparse matrix density: {sparse_matrix.compute_density()}")
|
|
737
|
+
return sparse_matrix
|
|
738
|
+
|
|
739
|
+
def _AlgebraicReconPython(self,withTumor, show_logs):
|
|
672
740
|
|
|
673
741
|
if withTumor:
|
|
674
742
|
if self.experiment.AOsignal_withTumor is None:
|
|
675
743
|
raise ValueError("AO signal with tumor is not available. Please generate AO signal with tumor the experiment first in the experiment object.")
|
|
676
|
-
else:
|
|
677
|
-
y = self.experiment.AOsignal_withTumor
|
|
678
744
|
else:
|
|
679
745
|
if self.experiment.AOsignal_withoutTumor is None:
|
|
680
746
|
raise ValueError("AO signal without tumor is not available. Please generate AO signal without tumor the experiment first in the experiment object.")
|
|
681
|
-
else:
|
|
682
|
-
y = self.experiment.AOsignal_withoutTumor
|
|
683
747
|
|
|
684
748
|
if self.optimizer.value == OptimizerType.MLEM.value:
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
749
|
+
if withTumor:
|
|
750
|
+
self.reconPhantom, self.indices = MLEM(SMatrix=self.SMatrix,
|
|
751
|
+
y=self.experiment.AOsignal_withTumor,
|
|
752
|
+
numIterations=self.numIterations,
|
|
753
|
+
isSavingEachIteration=self.isSavingEachIteration,
|
|
754
|
+
withTumor=withTumor,
|
|
755
|
+
device=self.device,
|
|
756
|
+
use_numba=self.isMultiCPU,
|
|
757
|
+
denominator_threshold=self.denominatorThreshold,
|
|
758
|
+
max_saves=self.maxSaves,
|
|
759
|
+
show_logs=show_logs,
|
|
760
|
+
smatrixType=self.smatrixType,
|
|
761
|
+
)
|
|
762
|
+
else:
|
|
763
|
+
self.reconLaser, self.indices = MLEM(SMatrix=self.SMatrix,
|
|
764
|
+
y=self.experiment.AOsignal_withoutTumor,
|
|
765
|
+
numIterations=self.numIterations,
|
|
766
|
+
isSavingEachIteration=self.isSavingEachIteration,
|
|
767
|
+
withTumor=withTumor,
|
|
768
|
+
device=self.device,
|
|
769
|
+
use_numba=self.isMultiCPU,
|
|
770
|
+
denominator_threshold=self.denominatorThreshold,
|
|
771
|
+
max_saves=self.maxSaves,
|
|
772
|
+
show_logs=show_logs,
|
|
773
|
+
smatrixType=self.smatrixType,
|
|
774
|
+
)
|
|
694
775
|
elif self.optimizer.value == OptimizerType.LS.value:
|
|
695
776
|
if self.alpha is None:
|
|
696
777
|
raise ValueError("Alpha (regularization parameter) must be set for LS reconstruction.")
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
778
|
+
if withTumor:
|
|
779
|
+
self.reconPhantom, self.indices = LS(SMatrix=self.SMatrix,
|
|
780
|
+
y=self.experiment.AOsignal_withTumor,
|
|
781
|
+
numIterations=self.numIterations,
|
|
782
|
+
isSavingEachIteration=self.isSavingEachIteration,
|
|
783
|
+
withTumor=withTumor,
|
|
784
|
+
device=self.device,
|
|
785
|
+
use_numba=self.isMultiCPU,
|
|
786
|
+
denominator_threshold=self.denominatorThreshold,
|
|
787
|
+
max_saves=self.maxSaves,
|
|
788
|
+
show_logs=show_logs,
|
|
789
|
+
smatrixType=self.smatrixType
|
|
790
|
+
)
|
|
791
|
+
else:
|
|
792
|
+
self.reconLaser, self.indices = LS(SMatrix=self.SMatrix,
|
|
793
|
+
y=self.experiment.AOsignal_withoutTumor,
|
|
794
|
+
numIterations=self.numIterations,
|
|
795
|
+
isSavingEachIteration=self.isSavingEachIteration,
|
|
796
|
+
withTumor=withTumor,
|
|
797
|
+
alpha=self.alpha,
|
|
798
|
+
device=self.device,
|
|
799
|
+
use_numba=self.isMultiCPU,
|
|
800
|
+
denominator_threshold=self.denominatorThreshold,
|
|
801
|
+
max_saves=self.maxSaves,
|
|
802
|
+
show_logs=show_logs,
|
|
803
|
+
smatrixType=self.smatrixType
|
|
704
804
|
)
|
|
705
805
|
else:
|
|
706
806
|
raise ValueError(f"Only MLEM and LS are supported for simple algebraic reconstruction. {self.optimizer.value} need Bayesian reconstruction")
|
|
707
807
|
|
|
708
|
-
def _AlgebraicReconCASToR(self,
|
|
808
|
+
def _AlgebraicReconCASToR(self,withTumor, show_logs):
|
|
709
809
|
# Définir les chemins
|
|
710
810
|
smatrix = os.path.join(self.saveDir, "system_matrix")
|
|
711
811
|
if withTumor:
|
|
@@ -715,14 +815,16 @@ class AlgebraicRecon(Recon):
|
|
|
715
815
|
|
|
716
816
|
# Vérifier et générer les fichiers d'entrée si nécessaire
|
|
717
817
|
if not os.path.isfile(os.path.join(self.saveDir, fileName)):
|
|
718
|
-
|
|
818
|
+
if show_logs:
|
|
819
|
+
print(f"Fichier .cdh manquant. Génération de {fileName}...")
|
|
719
820
|
self.experiment.saveAOsignals_Castor(self.saveDir)
|
|
720
821
|
|
|
721
822
|
# Vérifier/générer la matrice système
|
|
722
823
|
if not os.path.isdir(smatrix):
|
|
723
824
|
os.makedirs(smatrix, exist_ok=True)
|
|
724
825
|
if not os.listdir(smatrix):
|
|
725
|
-
|
|
826
|
+
if show_logs:
|
|
827
|
+
print("Matrice système manquante. Génération...")
|
|
726
828
|
self.experiment.saveAcousticFields(self.saveDir)
|
|
727
829
|
|
|
728
830
|
# Vérifier que le fichier .cdh existe (redondant mais sûr)
|
|
@@ -761,8 +863,9 @@ class AlgebraicRecon(Recon):
|
|
|
761
863
|
]
|
|
762
864
|
|
|
763
865
|
# Afficher la commande (pour débogage)
|
|
764
|
-
|
|
765
|
-
|
|
866
|
+
if show_logs:
|
|
867
|
+
print("Commande CASToR :")
|
|
868
|
+
print(" ".join(cmd))
|
|
766
869
|
|
|
767
870
|
# Chemin du script temporaire
|
|
768
871
|
recon_script_path = os.path.join(gettempdir(), 'recon.sh')
|
|
@@ -776,17 +879,20 @@ class AlgebraicRecon(Recon):
|
|
|
776
879
|
|
|
777
880
|
# Rendre le script exécutable et l'exécuter
|
|
778
881
|
subprocess.run(["chmod", "+x", recon_script_path], check=True)
|
|
779
|
-
|
|
882
|
+
if show_logs:
|
|
883
|
+
print(f"Exécution de la reconstruction avec CASToR...")
|
|
780
884
|
result = subprocess.run(recon_script_path, env=env, check=True, capture_output=True, text=True)
|
|
781
885
|
|
|
782
886
|
# Afficher la sortie de CASToR (pour débogage)
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
887
|
+
if show_logs:
|
|
888
|
+
print("Sortie CASToR :")
|
|
889
|
+
print(result.stdout)
|
|
890
|
+
if result.stderr:
|
|
891
|
+
print("Erreurs :")
|
|
892
|
+
print(result.stderr)
|
|
893
|
+
|
|
894
|
+
if show_logs:
|
|
895
|
+
print("Reconstruction terminée avec succès.")
|
|
790
896
|
self.load_reconCASToR(withTumor=withTumor)
|
|
791
897
|
|
|
792
898
|
# STATIC METHODS
|