AOT-biomaps 2.9.186__py3-none-any.whl → 2.9.294__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 +303 -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 +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 +243 -113
- AOT_biomaps/AOT_Recon/AnalyticRecon.py +26 -41
- AOT_biomaps/AOT_Recon/BayesianRecon.py +81 -146
- AOT_biomaps/AOT_Recon/PrimalDualRecon.py +157 -94
- 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 +60 -53
- AOT_biomaps/__init__.py +4 -69
- {aot_biomaps-2.9.186.dist-info → aot_biomaps-2.9.294.dist-info}/METADATA +2 -1
- aot_biomaps-2.9.294.dist-info/RECORD +47 -0
- aot_biomaps-2.9.186.dist-info/RECORD +0 -43
- {aot_biomaps-2.9.186.dist-info → aot_biomaps-2.9.294.dist-info}/WHEEL +0 -0
- {aot_biomaps-2.9.186.dist-info → aot_biomaps-2.9.294.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
|
|
8
|
+
from .AOT_SparseSMatrix import SparseSMatrix_CSR, SparseSMatrix_SELL
|
|
6
9
|
|
|
7
10
|
import os
|
|
8
|
-
import sys
|
|
9
11
|
import subprocess
|
|
10
|
-
import warnings
|
|
11
12
|
import numpy as np
|
|
12
13
|
import matplotlib.pyplot as plt
|
|
13
14
|
import matplotlib.animation as animation
|
|
14
15
|
from IPython.display import HTML
|
|
15
16
|
from datetime import datetime
|
|
16
17
|
from tempfile import gettempdir
|
|
17
|
-
import
|
|
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")
|
|
@@ -121,17 +119,17 @@ class AlgebraicRecon(Recon):
|
|
|
121
119
|
scale_str = "_logy"
|
|
122
120
|
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_MSE_plot_{self.optimizer.name}_{scale_str}{date_str}.png')
|
|
123
121
|
plt.savefig(SavingFolder, dpi=300)
|
|
124
|
-
|
|
122
|
+
if show_logs:
|
|
123
|
+
print(f"MSE plot saved to {SavingFolder}")
|
|
125
124
|
|
|
126
125
|
plt.show()
|
|
127
126
|
|
|
128
|
-
def show_MSE_bestRecon(self, isSaving=True):
|
|
127
|
+
def show_MSE_bestRecon(self, isSaving=True, show_logs=True):
|
|
129
128
|
if not self.MSE:
|
|
130
129
|
raise ValueError("MSE is empty. Please calculate MSE first.")
|
|
131
130
|
|
|
132
131
|
|
|
133
132
|
best_idx = np.argmin(self.MSE)
|
|
134
|
-
print(best_idx)
|
|
135
133
|
best_recon = self.reconPhantom[best_idx]
|
|
136
134
|
|
|
137
135
|
# Crée la figure et les axes
|
|
@@ -160,7 +158,6 @@ class AlgebraicRecon(Recon):
|
|
|
160
158
|
|
|
161
159
|
# Right: Reconstruction at last iteration
|
|
162
160
|
lastRecon = self.reconPhantom[-1]
|
|
163
|
-
print(lastRecon.shape)
|
|
164
161
|
if self.experiment.OpticImage.phantom.shape != lastRecon.shape:
|
|
165
162
|
lastRecon = lastRecon.T
|
|
166
163
|
im2 = axs[2].imshow(lastRecon,
|
|
@@ -189,11 +186,12 @@ class AlgebraicRecon(Recon):
|
|
|
189
186
|
os.makedirs(savePath)
|
|
190
187
|
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_comparison_MSE_BestANDLastRecon_{self.optimizer.name}_{date_str}.png')
|
|
191
188
|
plt.savefig(SavingFolder, dpi=300, bbox_inches='tight')
|
|
192
|
-
|
|
189
|
+
if show_logs:
|
|
190
|
+
print(f"MSE plot saved to {SavingFolder}")
|
|
193
191
|
|
|
194
192
|
plt.show()
|
|
195
193
|
|
|
196
|
-
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):
|
|
197
195
|
"""
|
|
198
196
|
Show theta iteration animation with speed proportional to MSE acceleration.
|
|
199
197
|
In "propMSE" mode: slow down when MSE changes rapidly, speed up when MSE stagnates.
|
|
@@ -296,18 +294,19 @@ class AlgebraicRecon(Recon):
|
|
|
296
294
|
ani.save(save_path, writer=animation.PillowWriter(fps=100))
|
|
297
295
|
elif save_path.endswith(".mp4"):
|
|
298
296
|
ani.save(save_path, writer="ffmpeg", fps=30)
|
|
299
|
-
|
|
297
|
+
if show_logs:
|
|
298
|
+
print(f"Animation saved to {save_path}")
|
|
300
299
|
|
|
301
300
|
plt.close(fig)
|
|
302
301
|
return HTML(ani.to_jshtml())
|
|
303
302
|
|
|
304
|
-
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):
|
|
305
304
|
if not self.SSIM:
|
|
306
305
|
raise ValueError("SSIM is empty. Please calculate SSIM first.")
|
|
307
306
|
|
|
308
307
|
best_idx = self.indices[np.argmax(self.SSIM)]
|
|
309
|
-
|
|
310
|
-
|
|
308
|
+
if show_logs:
|
|
309
|
+
print(f"Highest SSIM = {np.max(self.SSIM):.4f} at iteration {best_idx+1}")
|
|
311
310
|
# Plot SSIM curve
|
|
312
311
|
plt.figure(figsize=(7, 5))
|
|
313
312
|
plt.plot(self.indices, self.SSIM, 'r-', label="SSIM curve")
|
|
@@ -336,11 +335,12 @@ class AlgebraicRecon(Recon):
|
|
|
336
335
|
scale_str = "_logy"
|
|
337
336
|
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_SSIM_plot_{self.optimizer.name}_{scale_str}{date_str}.png')
|
|
338
337
|
plt.savefig(SavingFolder, dpi=300)
|
|
339
|
-
|
|
338
|
+
if show_logs:
|
|
339
|
+
print(f"SSIM plot saved to {SavingFolder}")
|
|
340
340
|
|
|
341
341
|
plt.show()
|
|
342
342
|
|
|
343
|
-
def show_SSIM_bestRecon(self, isSaving=True):
|
|
343
|
+
def show_SSIM_bestRecon(self, isSaving=True, show_logs=True):
|
|
344
344
|
|
|
345
345
|
if not self.SSIM:
|
|
346
346
|
raise ValueError("SSIM is empty. Please calculate SSIM first.")
|
|
@@ -385,10 +385,11 @@ class AlgebraicRecon(Recon):
|
|
|
385
385
|
date_str = now.strftime("%Y_%d_%m_%y")
|
|
386
386
|
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_comparison_SSIM_BestANDLastRecon_{self.optimizer.name}_{date_str}.png')
|
|
387
387
|
plt.savefig(SavingFolder, dpi=300)
|
|
388
|
-
|
|
388
|
+
if show_logs:
|
|
389
|
+
print(f"SSIM plot saved to {SavingFolder}")
|
|
389
390
|
plt.show()
|
|
390
391
|
|
|
391
|
-
def plot_CRC_vs_Noise(self, use_ROI=True, fin=None, isSaving=True):
|
|
392
|
+
def plot_CRC_vs_Noise(self, use_ROI=True, fin=None, isSaving=True, show_logs=True):
|
|
392
393
|
"""
|
|
393
394
|
Plot CRC (Contrast Recovery Coefficient) vs Noise for each iteration.
|
|
394
395
|
"""
|
|
@@ -436,10 +437,11 @@ class AlgebraicRecon(Recon):
|
|
|
436
437
|
date_str = now.strftime("%Y_%d_%m_%y")
|
|
437
438
|
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_CRCvsNOISE_{self.optimizer.name}_{date_str}.png')
|
|
438
439
|
plt.savefig(SavingFolder, dpi=300)
|
|
439
|
-
|
|
440
|
+
if show_logs:
|
|
441
|
+
print(f"CRCvsNOISE plot saved to {SavingFolder}")
|
|
440
442
|
plt.show()
|
|
441
443
|
|
|
442
|
-
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):
|
|
443
445
|
"""
|
|
444
446
|
Show the reconstruction progress for either with or without tumor.
|
|
445
447
|
If isPropMSE is True, the frame selection is adapted to MSE changes.
|
|
@@ -544,7 +546,8 @@ class AlgebraicRecon(Recon):
|
|
|
544
546
|
else:
|
|
545
547
|
save_path = f"{save_path}_{title_suffix}"
|
|
546
548
|
plt.savefig(save_path, dpi=300)
|
|
547
|
-
|
|
549
|
+
if show_logs:
|
|
550
|
+
print(f"Figure saved to: {save_path}")
|
|
548
551
|
|
|
549
552
|
plt.show()
|
|
550
553
|
|
|
@@ -567,14 +570,15 @@ class AlgebraicRecon(Recon):
|
|
|
567
570
|
if not os.path.exists(results_dir):
|
|
568
571
|
os.makedirs(results_dir)
|
|
569
572
|
|
|
570
|
-
if os.path.exists(results_dir):
|
|
573
|
+
if os.path.exists(os.path.join(results_dir,"indices.npy")):
|
|
571
574
|
return (True, results_dir)
|
|
572
575
|
|
|
573
576
|
return (False, results_dir)
|
|
574
577
|
|
|
575
|
-
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):
|
|
576
579
|
"""
|
|
577
|
-
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.
|
|
578
582
|
Args:
|
|
579
583
|
withTumor: If True, loads reconPhantom (with tumor), else reconLaser (without tumor).
|
|
580
584
|
results_date: Date string (format "ddmm") to specify which results to load. If None, uses the most recent date in saveDir.
|
|
@@ -587,39 +591,68 @@ class AlgebraicRecon(Recon):
|
|
|
587
591
|
recon_path = filePath
|
|
588
592
|
if not os.path.exists(recon_path):
|
|
589
593
|
raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
|
|
590
|
-
|
|
591
|
-
|
|
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])]
|
|
592
602
|
else:
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
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')
|
|
598
611
|
if os.path.exists(indices_path):
|
|
599
|
-
|
|
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
|
|
600
617
|
else:
|
|
601
618
|
self.indices = None
|
|
602
|
-
|
|
619
|
+
|
|
620
|
+
if show_logs:
|
|
621
|
+
print(f"Loaded reconstruction results and indices from {recon_path}")
|
|
603
622
|
else:
|
|
604
623
|
# Mode chargement depuis le répertoire de résultats
|
|
605
624
|
if self.saveDir is None:
|
|
606
625
|
raise ValueError("Save directory is not specified. Please set saveDir before loading.")
|
|
607
|
-
#
|
|
626
|
+
# Use current optimizer and potential function if not provided
|
|
608
627
|
opt_name = optimizer.value if optimizer is not None else self.optimizer.value
|
|
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}'
|
|
609
643
|
# Find the most recent results directory if no date is specified
|
|
610
644
|
if results_date is None:
|
|
611
|
-
|
|
612
|
-
dirs = [
|
|
613
|
-
d for d in os.listdir(self.saveDir)
|
|
614
|
-
if os.path.isdir(os.path.join(self.saveDir, d))
|
|
615
|
-
and re.match(r'results_\d{4}_' + re.escape(opt_name) + r'($|_)', d)
|
|
616
|
-
]
|
|
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]
|
|
617
646
|
if not dirs:
|
|
618
|
-
raise FileNotFoundError(f"No results directory found for
|
|
619
|
-
dirs.sort(reverse=True) # Most recent first
|
|
647
|
+
raise FileNotFoundError(f"No matching results directory found for pattern '{dir_pattern}' in {self.saveDir}.")
|
|
648
|
+
dirs.sort(reverse=True) # Most recent first
|
|
620
649
|
results_dir = os.path.join(self.saveDir, dirs[0])
|
|
621
650
|
else:
|
|
622
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}'
|
|
623
656
|
if not os.path.exists(results_dir):
|
|
624
657
|
raise FileNotFoundError(f"Directory {results_dir} does not exist.")
|
|
625
658
|
# Load reconstruction results
|
|
@@ -627,61 +660,152 @@ class AlgebraicRecon(Recon):
|
|
|
627
660
|
recon_path = os.path.join(results_dir, f'{recon_key}.npy')
|
|
628
661
|
if not os.path.exists(recon_path):
|
|
629
662
|
raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
|
|
630
|
-
|
|
631
|
-
|
|
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])]
|
|
632
669
|
else:
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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])]
|
|
638
681
|
else:
|
|
639
|
-
self.indices =
|
|
640
|
-
|
|
641
|
-
|
|
682
|
+
self.indices = indices_data
|
|
683
|
+
if show_logs:
|
|
684
|
+
print(f"Loaded reconstruction results and indices from {results_dir}")
|
|
685
|
+
|
|
642
686
|
def normalizeSMatrix(self):
|
|
643
687
|
self.SMatrix = self.SMatrix / (float(self.experiment.params.acoustic['voltage'])*float(self.experiment.params.acoustic['sensitivity']))
|
|
644
688
|
|
|
645
689
|
# PRIVATE METHODS
|
|
646
690
|
|
|
647
|
-
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):
|
|
648
740
|
|
|
649
741
|
if withTumor:
|
|
650
742
|
if self.experiment.AOsignal_withTumor is None:
|
|
651
743
|
raise ValueError("AO signal with tumor is not available. Please generate AO signal with tumor the experiment first in the experiment object.")
|
|
652
|
-
else:
|
|
653
|
-
y = self.experiment.AOsignal_withTumor
|
|
654
744
|
else:
|
|
655
745
|
if self.experiment.AOsignal_withoutTumor is None:
|
|
656
746
|
raise ValueError("AO signal without tumor is not available. Please generate AO signal without tumor the experiment first in the experiment object.")
|
|
657
|
-
else:
|
|
658
|
-
y = self.experiment.AOsignal_withoutTumor
|
|
659
747
|
|
|
660
748
|
if self.optimizer.value == OptimizerType.MLEM.value:
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
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
|
+
)
|
|
670
775
|
elif self.optimizer.value == OptimizerType.LS.value:
|
|
671
776
|
if self.alpha is None:
|
|
672
777
|
raise ValueError("Alpha (regularization parameter) must be set for LS reconstruction.")
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
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
|
|
680
804
|
)
|
|
681
805
|
else:
|
|
682
806
|
raise ValueError(f"Only MLEM and LS are supported for simple algebraic reconstruction. {self.optimizer.value} need Bayesian reconstruction")
|
|
683
807
|
|
|
684
|
-
def _AlgebraicReconCASToR(self,
|
|
808
|
+
def _AlgebraicReconCASToR(self,withTumor, show_logs):
|
|
685
809
|
# Définir les chemins
|
|
686
810
|
smatrix = os.path.join(self.saveDir, "system_matrix")
|
|
687
811
|
if withTumor:
|
|
@@ -691,14 +815,16 @@ class AlgebraicRecon(Recon):
|
|
|
691
815
|
|
|
692
816
|
# Vérifier et générer les fichiers d'entrée si nécessaire
|
|
693
817
|
if not os.path.isfile(os.path.join(self.saveDir, fileName)):
|
|
694
|
-
|
|
818
|
+
if show_logs:
|
|
819
|
+
print(f"Fichier .cdh manquant. Génération de {fileName}...")
|
|
695
820
|
self.experiment.saveAOsignals_Castor(self.saveDir)
|
|
696
821
|
|
|
697
822
|
# Vérifier/générer la matrice système
|
|
698
823
|
if not os.path.isdir(smatrix):
|
|
699
824
|
os.makedirs(smatrix, exist_ok=True)
|
|
700
825
|
if not os.listdir(smatrix):
|
|
701
|
-
|
|
826
|
+
if show_logs:
|
|
827
|
+
print("Matrice système manquante. Génération...")
|
|
702
828
|
self.experiment.saveAcousticFields(self.saveDir)
|
|
703
829
|
|
|
704
830
|
# Vérifier que le fichier .cdh existe (redondant mais sûr)
|
|
@@ -737,8 +863,9 @@ class AlgebraicRecon(Recon):
|
|
|
737
863
|
]
|
|
738
864
|
|
|
739
865
|
# Afficher la commande (pour débogage)
|
|
740
|
-
|
|
741
|
-
|
|
866
|
+
if show_logs:
|
|
867
|
+
print("Commande CASToR :")
|
|
868
|
+
print(" ".join(cmd))
|
|
742
869
|
|
|
743
870
|
# Chemin du script temporaire
|
|
744
871
|
recon_script_path = os.path.join(gettempdir(), 'recon.sh')
|
|
@@ -752,17 +879,20 @@ class AlgebraicRecon(Recon):
|
|
|
752
879
|
|
|
753
880
|
# Rendre le script exécutable et l'exécuter
|
|
754
881
|
subprocess.run(["chmod", "+x", recon_script_path], check=True)
|
|
755
|
-
|
|
882
|
+
if show_logs:
|
|
883
|
+
print(f"Exécution de la reconstruction avec CASToR...")
|
|
756
884
|
result = subprocess.run(recon_script_path, env=env, check=True, capture_output=True, text=True)
|
|
757
885
|
|
|
758
886
|
# Afficher la sortie de CASToR (pour débogage)
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
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.")
|
|
766
896
|
self.load_reconCASToR(withTumor=withTumor)
|
|
767
897
|
|
|
768
898
|
# STATIC METHODS
|