AOT-biomaps 2.9.167__py3-none-any.whl → 2.9.270__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 +14 -7
- AOT_biomaps/AOT_Experiment/Tomography.py +74 -4
- AOT_biomaps/AOT_Experiment/_mainExperiment.py +95 -55
- AOT_biomaps/AOT_Recon/AOT_Optimizers/DEPIERRO.py +48 -13
- AOT_biomaps/AOT_Recon/AOT_Optimizers/LS.py +9 -6
- AOT_biomaps/AOT_Recon/AOT_Optimizers/MAPEM.py +118 -38
- AOT_biomaps/AOT_Recon/AOT_Optimizers/MLEM.py +305 -102
- AOT_biomaps/AOT_Recon/AOT_Optimizers/PDHG.py +1 -1
- AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/RelativeDifferences.py +10 -14
- AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_CSR.py +281 -0
- AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_SELL.py +295 -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 +262 -149
- 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 +69 -62
- AOT_biomaps/AOT_Recon/ReconEnums.py +27 -2
- AOT_biomaps/AOT_Recon/ReconTools.py +120 -12
- AOT_biomaps/AOT_Recon/__init__.py +1 -0
- AOT_biomaps/AOT_Recon/_mainRecon.py +73 -59
- AOT_biomaps/__init__.py +4 -74
- {aot_biomaps-2.9.167.dist-info → aot_biomaps-2.9.270.dist-info}/METADATA +2 -1
- aot_biomaps-2.9.270.dist-info/RECORD +47 -0
- aot_biomaps-2.9.167.dist-info/RECORD +0 -43
- {aot_biomaps-2.9.167.dist-info → aot_biomaps-2.9.270.dist-info}/WHEEL +0 -0
- {aot_biomaps-2.9.167.dist-info → aot_biomaps-2.9.270.dist-info}/top_level.txt +0 -0
|
@@ -1,37 +1,49 @@
|
|
|
1
|
+
import concurrent
|
|
1
2
|
from ._mainRecon import Recon
|
|
2
|
-
from .ReconEnums import ReconType, OptimizerType, ProcessType
|
|
3
|
+
from .ReconEnums import ReconType, OptimizerType, ProcessType, SMatrixType
|
|
3
4
|
from .AOT_Optimizers import MLEM, LS
|
|
4
|
-
from .ReconTools import check_gpu_memory, calculate_memory_requirement, mse
|
|
5
5
|
from AOT_biomaps.Config import config
|
|
6
|
-
|
|
6
|
+
from .AOT_SparseSMatrix import SparseSMatrix_CSR, SparseSMatrix_SELL
|
|
7
7
|
|
|
8
8
|
import os
|
|
9
|
-
import sys
|
|
10
9
|
import subprocess
|
|
11
|
-
import warnings
|
|
12
10
|
import numpy as np
|
|
13
11
|
import matplotlib.pyplot as plt
|
|
14
12
|
import matplotlib.animation as animation
|
|
15
13
|
from IPython.display import HTML
|
|
16
14
|
from datetime import datetime
|
|
17
15
|
from tempfile import gettempdir
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
import cupy as cp
|
|
17
|
+
import cupyx.scipy.sparse as cpsparse
|
|
18
|
+
import gc
|
|
19
|
+
from tqdm import trange
|
|
20
20
|
|
|
21
21
|
class AlgebraicRecon(Recon):
|
|
22
22
|
"""
|
|
23
23
|
This class implements the Algebraic reconstruction process.
|
|
24
24
|
It currently does not perform any operations but serves as a template for future implementations.
|
|
25
25
|
"""
|
|
26
|
-
def __init__(self, opti = OptimizerType.MLEM, numIterations = 10000, numSubsets = 1, isSavingEachIteration=True, **kwargs):
|
|
26
|
+
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
27
|
super().__init__(**kwargs)
|
|
28
28
|
self.reconType = ReconType.Algebraic
|
|
29
29
|
self.optimizer = opti
|
|
30
30
|
self.reconPhantom = []
|
|
31
31
|
self.reconLaser = []
|
|
32
|
+
self.indices = []
|
|
32
33
|
self.numIterations = numIterations
|
|
33
34
|
self.numSubsets = numSubsets
|
|
34
35
|
self.isSavingEachIteration = isSavingEachIteration
|
|
36
|
+
self.maxSaves = maxSaves
|
|
37
|
+
self.denominatorThreshold = denominatorThreshold
|
|
38
|
+
self.alpha = alpha # Regularization parameter for LS
|
|
39
|
+
self.device = device
|
|
40
|
+
self.SMatrix = None # system matrix
|
|
41
|
+
self.smatrixType = smatrixType # SMatrixType.DENSE if no sparsing, else SMatrixType.SELL or SMatrixType.CSR or SMatrixType.COO
|
|
42
|
+
# Sparse matrix attributes
|
|
43
|
+
|
|
44
|
+
self.sparseThreshold = sparseThreshold
|
|
45
|
+
|
|
46
|
+
self.Z_dim = None # Used for sparse matrix reconstruction
|
|
35
47
|
|
|
36
48
|
if self.numIterations <= 0:
|
|
37
49
|
raise ValueError("Number of iterations must be greater than 0.")
|
|
@@ -43,38 +55,26 @@ class AlgebraicRecon(Recon):
|
|
|
43
55
|
raise TypeError("Number of subsets must be an integer.")
|
|
44
56
|
|
|
45
57
|
print("Generating system matrix (processing acoustic fields)...")
|
|
46
|
-
self.
|
|
58
|
+
if self.smatrixType == SMatrixType.DENSE:
|
|
59
|
+
self.SMatrix = self._fillDenseSMatrix()
|
|
60
|
+
else:
|
|
61
|
+
self.SMatrix = self._fillSparseSMatrix(isShowLogs=True)
|
|
47
62
|
|
|
48
63
|
# PUBLIC METHODS
|
|
49
64
|
|
|
50
|
-
def run(self, processType = ProcessType.PYTHON, withTumor= True):
|
|
65
|
+
def run(self, processType = ProcessType.PYTHON, withTumor= True, show_logs=True):
|
|
51
66
|
"""
|
|
52
67
|
This method is a placeholder for the Algebraic reconstruction process.
|
|
53
68
|
It currently does not perform any operations but serves as a template for future implementations.
|
|
54
69
|
"""
|
|
55
|
-
|
|
56
70
|
if(processType == ProcessType.CASToR):
|
|
57
|
-
self._AlgebraicReconCASToR(withTumor)
|
|
71
|
+
self._AlgebraicReconCASToR(withTumor=withTumor, show_logs=show_logs)
|
|
58
72
|
elif(processType == ProcessType.PYTHON):
|
|
59
|
-
self._AlgebraicReconPython(withTumor)
|
|
73
|
+
self._AlgebraicReconPython(withTumor=withTumor, show_logs=show_logs)
|
|
60
74
|
else:
|
|
61
75
|
raise ValueError(f"Unknown Algebraic reconstruction type: {processType}")
|
|
62
|
-
|
|
63
|
-
def
|
|
64
|
-
if withTumor:
|
|
65
|
-
folder = 'results_withTumor'
|
|
66
|
-
else:
|
|
67
|
-
folder = 'results_withoutTumor'
|
|
68
|
-
|
|
69
|
-
for thetaFiles in os.path.join(self.saveDir, folder + '_{}'):
|
|
70
|
-
if thetaFiles.endswith('.hdr'):
|
|
71
|
-
theta = Recon.load_recon(thetaFiles)
|
|
72
|
-
if withTumor:
|
|
73
|
-
self.reconPhantom.append(theta)
|
|
74
|
-
else:
|
|
75
|
-
self.reconLaser.append(theta)
|
|
76
|
-
|
|
77
|
-
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):
|
|
78
78
|
"""
|
|
79
79
|
Plot the Mean Squared Error (MSE) of the reconstruction.
|
|
80
80
|
|
|
@@ -89,8 +89,8 @@ class AlgebraicRecon(Recon):
|
|
|
89
89
|
raise ValueError("MSE is empty. Please calculate MSE first.")
|
|
90
90
|
|
|
91
91
|
best_idx = self.indices[np.argmin(self.MSE)]
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
if show_logs:
|
|
93
|
+
print(f"Lowest MSE = {np.min(self.MSE):.4f} at iteration {best_idx+1}")
|
|
94
94
|
# Plot MSE curve
|
|
95
95
|
plt.figure(figsize=(7, 5))
|
|
96
96
|
plt.plot(self.indices, self.MSE, 'r-', label="MSE curve")
|
|
@@ -117,22 +117,19 @@ class AlgebraicRecon(Recon):
|
|
|
117
117
|
scale_str = "_logx"
|
|
118
118
|
elif log_scale_y:
|
|
119
119
|
scale_str = "_logy"
|
|
120
|
-
|
|
121
|
-
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_MSE_plot_MLEM{scale_str}{date_str}.png')
|
|
122
|
-
elif self.optimizer == OptimizerType.LS:
|
|
123
|
-
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')
|
|
124
121
|
plt.savefig(SavingFolder, dpi=300)
|
|
125
|
-
|
|
122
|
+
if show_logs:
|
|
123
|
+
print(f"MSE plot saved to {SavingFolder}")
|
|
126
124
|
|
|
127
125
|
plt.show()
|
|
128
126
|
|
|
129
|
-
def show_MSE_bestRecon(self, isSaving=True):
|
|
127
|
+
def show_MSE_bestRecon(self, isSaving=True, show_logs=True):
|
|
130
128
|
if not self.MSE:
|
|
131
129
|
raise ValueError("MSE is empty. Please calculate MSE first.")
|
|
132
130
|
|
|
133
131
|
|
|
134
132
|
best_idx = np.argmin(self.MSE)
|
|
135
|
-
print(best_idx)
|
|
136
133
|
best_recon = self.reconPhantom[best_idx]
|
|
137
134
|
|
|
138
135
|
# Crée la figure et les axes
|
|
@@ -161,7 +158,6 @@ class AlgebraicRecon(Recon):
|
|
|
161
158
|
|
|
162
159
|
# Right: Reconstruction at last iteration
|
|
163
160
|
lastRecon = self.reconPhantom[-1]
|
|
164
|
-
print(lastRecon.shape)
|
|
165
161
|
if self.experiment.OpticImage.phantom.shape != lastRecon.shape:
|
|
166
162
|
lastRecon = lastRecon.T
|
|
167
163
|
im2 = axs[2].imshow(lastRecon,
|
|
@@ -188,17 +184,14 @@ class AlgebraicRecon(Recon):
|
|
|
188
184
|
savePath = os.path.join(self.saveDir, 'results')
|
|
189
185
|
if not os.path.exists(savePath):
|
|
190
186
|
os.makedirs(savePath)
|
|
191
|
-
|
|
192
|
-
namePath = f'{self.SMatrix.shape[3]}_SCANS_comparison_MSE_BestANDLastRecon_MLEM_Date_{date_str}.png'
|
|
193
|
-
elif self.optimizer == OptimizerType.LS:
|
|
194
|
-
namePath = f'{self.SMatrix.shape[3]}_SCANS_comparison_MSE_BestANDLastRecon_LS_Date_{date_str}.png'
|
|
195
|
-
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')
|
|
196
188
|
plt.savefig(SavingFolder, dpi=300, bbox_inches='tight')
|
|
197
|
-
|
|
189
|
+
if show_logs:
|
|
190
|
+
print(f"MSE plot saved to {SavingFolder}")
|
|
198
191
|
|
|
199
192
|
plt.show()
|
|
200
193
|
|
|
201
|
-
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):
|
|
202
195
|
"""
|
|
203
196
|
Show theta iteration animation with speed proportional to MSE acceleration.
|
|
204
197
|
In "propMSE" mode: slow down when MSE changes rapidly, speed up when MSE stagnates.
|
|
@@ -301,18 +294,19 @@ class AlgebraicRecon(Recon):
|
|
|
301
294
|
ani.save(save_path, writer=animation.PillowWriter(fps=100))
|
|
302
295
|
elif save_path.endswith(".mp4"):
|
|
303
296
|
ani.save(save_path, writer="ffmpeg", fps=30)
|
|
304
|
-
|
|
297
|
+
if show_logs:
|
|
298
|
+
print(f"Animation saved to {save_path}")
|
|
305
299
|
|
|
306
300
|
plt.close(fig)
|
|
307
301
|
return HTML(ani.to_jshtml())
|
|
308
302
|
|
|
309
|
-
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):
|
|
310
304
|
if not self.SSIM:
|
|
311
305
|
raise ValueError("SSIM is empty. Please calculate SSIM first.")
|
|
312
306
|
|
|
313
307
|
best_idx = self.indices[np.argmax(self.SSIM)]
|
|
314
|
-
|
|
315
|
-
|
|
308
|
+
if show_logs:
|
|
309
|
+
print(f"Highest SSIM = {np.max(self.SSIM):.4f} at iteration {best_idx+1}")
|
|
316
310
|
# Plot SSIM curve
|
|
317
311
|
plt.figure(figsize=(7, 5))
|
|
318
312
|
plt.plot(self.indices, self.SSIM, 'r-', label="SSIM curve")
|
|
@@ -339,16 +333,14 @@ class AlgebraicRecon(Recon):
|
|
|
339
333
|
scale_str = "_logx"
|
|
340
334
|
elif log_scale_y:
|
|
341
335
|
scale_str = "_logy"
|
|
342
|
-
|
|
343
|
-
SavingFolder = os.path.join(self.saveDir, f'{self.SMatrix.shape[3]}_SCANS_SSIM_plot_MLEM{scale_str}{date_str}.png')
|
|
344
|
-
elif self.optimizer == OptimizerType.LS:
|
|
345
|
-
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')
|
|
346
337
|
plt.savefig(SavingFolder, dpi=300)
|
|
347
|
-
|
|
338
|
+
if show_logs:
|
|
339
|
+
print(f"SSIM plot saved to {SavingFolder}")
|
|
348
340
|
|
|
349
341
|
plt.show()
|
|
350
342
|
|
|
351
|
-
def show_SSIM_bestRecon(self, isSaving=True):
|
|
343
|
+
def show_SSIM_bestRecon(self, isSaving=True, show_logs=True):
|
|
352
344
|
|
|
353
345
|
if not self.SSIM:
|
|
354
346
|
raise ValueError("SSIM is empty. Please calculate SSIM first.")
|
|
@@ -359,9 +351,6 @@ class AlgebraicRecon(Recon):
|
|
|
359
351
|
# ----------------- Plotting -----------------
|
|
360
352
|
_, axs = plt.subplots(1, 3, figsize=(15, 5)) # 1 row, 3 columns
|
|
361
353
|
|
|
362
|
-
# Normalization based on LAMBDA max
|
|
363
|
-
lambda_max = np.max(self.experiment.OpticImage.laser.intensity)
|
|
364
|
-
|
|
365
354
|
# Left: Best reconstructed image (normalized)
|
|
366
355
|
im0 = axs[0].imshow(best_recon,
|
|
367
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]),
|
|
@@ -394,12 +383,13 @@ class AlgebraicRecon(Recon):
|
|
|
394
383
|
if isSaving:
|
|
395
384
|
now = datetime.now()
|
|
396
385
|
date_str = now.strftime("%Y_%d_%m_%y")
|
|
397
|
-
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')
|
|
398
387
|
plt.savefig(SavingFolder, dpi=300)
|
|
399
|
-
|
|
388
|
+
if show_logs:
|
|
389
|
+
print(f"SSIM plot saved to {SavingFolder}")
|
|
400
390
|
plt.show()
|
|
401
391
|
|
|
402
|
-
def plot_CRC_vs_Noise(self,
|
|
392
|
+
def plot_CRC_vs_Noise(self, use_ROI=True, fin=None, isSaving=True, show_logs=True):
|
|
403
393
|
"""
|
|
404
394
|
Plot CRC (Contrast Recovery Coefficient) vs Noise for each iteration.
|
|
405
395
|
"""
|
|
@@ -416,12 +406,11 @@ class AlgebraicRecon(Recon):
|
|
|
416
406
|
fin = len(self.reconPhantom) - 1
|
|
417
407
|
|
|
418
408
|
iter_range = self.indices
|
|
419
|
-
|
|
420
|
-
crc_values = []
|
|
421
|
-
noise_values = []
|
|
422
409
|
|
|
423
410
|
if self.CRC is None:
|
|
424
|
-
self.calculateCRC(use_ROI=
|
|
411
|
+
self.calculateCRC(use_ROI=use_ROI)
|
|
412
|
+
|
|
413
|
+
noise_values = []
|
|
425
414
|
|
|
426
415
|
for i in iter_range:
|
|
427
416
|
recon_without_tumor = self.reconLaser[i].T
|
|
@@ -430,8 +419,8 @@ class AlgebraicRecon(Recon):
|
|
|
430
419
|
noise_values.append(noise)
|
|
431
420
|
|
|
432
421
|
plt.figure(figsize=(6, 5))
|
|
433
|
-
plt.plot(noise_values,
|
|
434
|
-
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)):
|
|
435
424
|
plt.text(x, y, str(i), fontsize=5.5, ha='left', va='bottom')
|
|
436
425
|
|
|
437
426
|
plt.xlabel("Noise (mean absolute error)")
|
|
@@ -443,13 +432,16 @@ class AlgebraicRecon(Recon):
|
|
|
443
432
|
plt.title("CRC vs Noise over Iterations")
|
|
444
433
|
plt.grid(True)
|
|
445
434
|
plt.legend()
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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}")
|
|
450
442
|
plt.show()
|
|
451
443
|
|
|
452
|
-
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):
|
|
453
445
|
"""
|
|
454
446
|
Show the reconstruction progress for either with or without tumor.
|
|
455
447
|
If isPropMSE is True, the frame selection is adapted to MSE changes.
|
|
@@ -554,11 +546,12 @@ class AlgebraicRecon(Recon):
|
|
|
554
546
|
else:
|
|
555
547
|
save_path = f"{save_path}_{title_suffix}"
|
|
556
548
|
plt.savefig(save_path, dpi=300)
|
|
557
|
-
|
|
549
|
+
if show_logs:
|
|
550
|
+
print(f"Figure saved to: {save_path}")
|
|
558
551
|
|
|
559
552
|
plt.show()
|
|
560
553
|
|
|
561
|
-
def checkExistingFile(self, date=None
|
|
554
|
+
def checkExistingFile(self, date = None):
|
|
562
555
|
"""
|
|
563
556
|
Check if the reconstruction file already exists, based on current instance parameters.
|
|
564
557
|
|
|
@@ -573,22 +566,19 @@ class AlgebraicRecon(Recon):
|
|
|
573
566
|
raise ValueError("Save directory is not specified.")
|
|
574
567
|
if date is None:
|
|
575
568
|
date = datetime.now().strftime("%d%m")
|
|
576
|
-
results_dir = os.path.join(self.saveDir, f'results_{date}_{self.optimizer.value}
|
|
569
|
+
results_dir = os.path.join(self.saveDir, f'results_{date}_{self.optimizer.value}')
|
|
577
570
|
if not os.path.exists(results_dir):
|
|
578
571
|
os.makedirs(results_dir)
|
|
579
572
|
|
|
580
|
-
|
|
581
|
-
|
|
573
|
+
if os.path.exists(os.path.join(results_dir,"indices.npy")):
|
|
574
|
+
return (True, results_dir)
|
|
582
575
|
|
|
583
|
-
|
|
584
|
-
return (True, filepath)
|
|
576
|
+
return (False, results_dir)
|
|
585
577
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
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):
|
|
590
579
|
"""
|
|
591
|
-
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.
|
|
592
582
|
Args:
|
|
593
583
|
withTumor: If True, loads reconPhantom (with tumor), else reconLaser (without tumor).
|
|
594
584
|
results_date: Date string (format "ddmm") to specify which results to load. If None, uses the most recent date in saveDir.
|
|
@@ -601,97 +591,214 @@ class AlgebraicRecon(Recon):
|
|
|
601
591
|
recon_path = filePath
|
|
602
592
|
if not os.path.exists(recon_path):
|
|
603
593
|
raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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])]
|
|
607
602
|
else:
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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')
|
|
614
611
|
if os.path.exists(indices_path):
|
|
615
|
-
|
|
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
|
|
616
617
|
else:
|
|
617
618
|
self.indices = None
|
|
618
|
-
|
|
619
|
-
|
|
619
|
+
|
|
620
|
+
if show_logs:
|
|
621
|
+
print(f"Loaded reconstruction results and indices from {recon_path}")
|
|
620
622
|
else:
|
|
621
623
|
# Mode chargement depuis le répertoire de résultats
|
|
622
624
|
if self.saveDir is None:
|
|
623
625
|
raise ValueError("Save directory is not specified. Please set saveDir before loading.")
|
|
624
|
-
|
|
625
|
-
# Determine optimizer name for path matching
|
|
626
|
+
# Use current optimizer and potential function if not provided
|
|
626
627
|
opt_name = optimizer.value if optimizer is not None else self.optimizer.value
|
|
627
|
-
|
|
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}'
|
|
628
643
|
# Find the most recent results directory if no date is specified
|
|
629
644
|
if results_date is None:
|
|
630
|
-
dir_pattern = f'results_*_{opt_name}_'
|
|
631
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]
|
|
632
646
|
if not dirs:
|
|
633
|
-
raise FileNotFoundError(f"No results directory found for
|
|
647
|
+
raise FileNotFoundError(f"No matching results directory found for pattern '{dir_pattern}' in {self.saveDir}.")
|
|
634
648
|
dirs.sort(reverse=True) # Most recent first
|
|
635
649
|
results_dir = os.path.join(self.saveDir, dirs[0])
|
|
636
650
|
else:
|
|
637
|
-
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}'
|
|
638
656
|
if not os.path.exists(results_dir):
|
|
639
657
|
raise FileNotFoundError(f"Directory {results_dir} does not exist.")
|
|
640
|
-
|
|
641
658
|
# Load reconstruction results
|
|
642
659
|
recon_key = 'reconPhantom' if withTumor else 'reconLaser'
|
|
643
660
|
recon_path = os.path.join(results_dir, f'{recon_key}.npy')
|
|
644
661
|
if not os.path.exists(recon_path):
|
|
645
662
|
raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
|
|
646
|
-
|
|
647
|
-
if
|
|
648
|
-
|
|
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])]
|
|
649
669
|
else:
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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])]
|
|
656
681
|
else:
|
|
657
|
-
self.indices =
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
682
|
+
self.indices = indices_data
|
|
683
|
+
if show_logs:
|
|
684
|
+
print(f"Loaded reconstruction results and indices from {results_dir}")
|
|
685
|
+
|
|
664
686
|
def normalizeSMatrix(self):
|
|
665
687
|
self.SMatrix = self.SMatrix / (float(self.experiment.params.acoustic['voltage'])*float(self.experiment.params.acoustic['sensitivity']))
|
|
666
688
|
|
|
667
689
|
# PRIVATE METHODS
|
|
668
690
|
|
|
669
|
-
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
|
+
if isShowLogs:
|
|
733
|
+
print(f" Sparse matrix size: {sparse_matrix.getMatrixSize()} GB")
|
|
734
|
+
print(f"Sparse matrix density: {sparse_matrix.compute_density()}")
|
|
735
|
+
return sparse_matrix
|
|
736
|
+
|
|
737
|
+
def _AlgebraicReconPython(self,withTumor, show_logs):
|
|
670
738
|
|
|
671
739
|
if withTumor:
|
|
672
740
|
if self.experiment.AOsignal_withTumor is None:
|
|
673
741
|
raise ValueError("AO signal with tumor is not available. Please generate AO signal with tumor the experiment first in the experiment object.")
|
|
674
|
-
if self.optimizer.value == OptimizerType.MLEM.value:
|
|
675
|
-
self.reconPhantom, self.indices = MLEM(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withTumor, withTumor=withTumor)
|
|
676
|
-
elif self.optimizer.value == OptimizerType.LS.value:
|
|
677
|
-
self.reconPhantom, self.indices = LS(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withTumor, withTumor=withTumor)
|
|
678
|
-
elif self.optimizer.value == OptimizerType.LS_TV.value:
|
|
679
|
-
self.reconPhantom, self.indices = self._LS_Regularized(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withTumor, withTumor=withTumor)
|
|
680
|
-
else:
|
|
681
|
-
raise ValueError(f"Only MLEM and LS are supported for simple algebraic reconstruction. {self.optimizer.value} need Bayesian reconstruction")
|
|
682
742
|
else:
|
|
683
743
|
if self.experiment.AOsignal_withoutTumor is None:
|
|
684
744
|
raise ValueError("AO signal without tumor is not available. Please generate AO signal without tumor the experiment first in the experiment object.")
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
self.
|
|
689
|
-
|
|
690
|
-
|
|
745
|
+
|
|
746
|
+
if self.optimizer.value == OptimizerType.MLEM.value:
|
|
747
|
+
if withTumor:
|
|
748
|
+
self.reconPhantom, self.indices = MLEM(SMatrix=self.SMatrix,
|
|
749
|
+
y=self.experiment.AOsignal_withTumor,
|
|
750
|
+
numIterations=self.numIterations,
|
|
751
|
+
isSavingEachIteration=self.isSavingEachIteration,
|
|
752
|
+
withTumor=withTumor,
|
|
753
|
+
device=self.device,
|
|
754
|
+
use_numba=self.isMultiCPU,
|
|
755
|
+
denominator_threshold=self.denominatorThreshold,
|
|
756
|
+
max_saves=self.maxSaves,
|
|
757
|
+
show_logs=show_logs,
|
|
758
|
+
smatrixType=self.smatrixType,
|
|
759
|
+
Z=self.Z_dim
|
|
760
|
+
)
|
|
691
761
|
else:
|
|
692
|
-
|
|
762
|
+
self.reconLaser, self.indices = MLEM(SMatrix=self.SMatrix,
|
|
763
|
+
y=self.experiment.AOsignal_withoutTumor,
|
|
764
|
+
numIterations=self.numIterations,
|
|
765
|
+
isSavingEachIteration=self.isSavingEachIteration,
|
|
766
|
+
withTumor=withTumor,
|
|
767
|
+
device=self.device,
|
|
768
|
+
use_numba=self.isMultiCPU,
|
|
769
|
+
denominator_threshold=self.denominatorThreshold,
|
|
770
|
+
max_saves=self.maxSaves,
|
|
771
|
+
show_logs=show_logs,
|
|
772
|
+
smatrixType=self.smatrixType,
|
|
773
|
+
Z=self.Z_dim
|
|
774
|
+
)
|
|
775
|
+
elif self.optimizer.value == OptimizerType.LS.value:
|
|
776
|
+
if self.alpha is None:
|
|
777
|
+
raise ValueError("Alpha (regularization parameter) must be set for LS reconstruction.")
|
|
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
|
+
alpha=self.alpha,
|
|
785
|
+
max_saves=self.maxSaves,
|
|
786
|
+
show_logs=show_logs
|
|
787
|
+
)
|
|
788
|
+
else:
|
|
789
|
+
self.reconLaser, self.indices = LS(SMatrix=self.SMatrix,
|
|
790
|
+
y=self.experiment.AOsignal_withoutTumor,
|
|
791
|
+
numIterations=self.numIterations,
|
|
792
|
+
isSavingEachIteration=self.isSavingEachIteration,
|
|
793
|
+
withTumor=withTumor,
|
|
794
|
+
alpha=self.alpha,
|
|
795
|
+
max_saves=self.maxSaves,
|
|
796
|
+
show_logs=show_logs
|
|
797
|
+
)
|
|
798
|
+
else:
|
|
799
|
+
raise ValueError(f"Only MLEM and LS are supported for simple algebraic reconstruction. {self.optimizer.value} need Bayesian reconstruction")
|
|
693
800
|
|
|
694
|
-
def _AlgebraicReconCASToR(self,
|
|
801
|
+
def _AlgebraicReconCASToR(self,withTumor, show_logs):
|
|
695
802
|
# Définir les chemins
|
|
696
803
|
smatrix = os.path.join(self.saveDir, "system_matrix")
|
|
697
804
|
if withTumor:
|
|
@@ -701,14 +808,16 @@ class AlgebraicRecon(Recon):
|
|
|
701
808
|
|
|
702
809
|
# Vérifier et générer les fichiers d'entrée si nécessaire
|
|
703
810
|
if not os.path.isfile(os.path.join(self.saveDir, fileName)):
|
|
704
|
-
|
|
811
|
+
if show_logs:
|
|
812
|
+
print(f"Fichier .cdh manquant. Génération de {fileName}...")
|
|
705
813
|
self.experiment.saveAOsignals_Castor(self.saveDir)
|
|
706
814
|
|
|
707
815
|
# Vérifier/générer la matrice système
|
|
708
816
|
if not os.path.isdir(smatrix):
|
|
709
817
|
os.makedirs(smatrix, exist_ok=True)
|
|
710
818
|
if not os.listdir(smatrix):
|
|
711
|
-
|
|
819
|
+
if show_logs:
|
|
820
|
+
print("Matrice système manquante. Génération...")
|
|
712
821
|
self.experiment.saveAcousticFields(self.saveDir)
|
|
713
822
|
|
|
714
823
|
# Vérifier que le fichier .cdh existe (redondant mais sûr)
|
|
@@ -747,8 +856,9 @@ class AlgebraicRecon(Recon):
|
|
|
747
856
|
]
|
|
748
857
|
|
|
749
858
|
# Afficher la commande (pour débogage)
|
|
750
|
-
|
|
751
|
-
|
|
859
|
+
if show_logs:
|
|
860
|
+
print("Commande CASToR :")
|
|
861
|
+
print(" ".join(cmd))
|
|
752
862
|
|
|
753
863
|
# Chemin du script temporaire
|
|
754
864
|
recon_script_path = os.path.join(gettempdir(), 'recon.sh')
|
|
@@ -762,17 +872,20 @@ class AlgebraicRecon(Recon):
|
|
|
762
872
|
|
|
763
873
|
# Rendre le script exécutable et l'exécuter
|
|
764
874
|
subprocess.run(["chmod", "+x", recon_script_path], check=True)
|
|
765
|
-
|
|
875
|
+
if show_logs:
|
|
876
|
+
print(f"Exécution de la reconstruction avec CASToR...")
|
|
766
877
|
result = subprocess.run(recon_script_path, env=env, check=True, capture_output=True, text=True)
|
|
767
878
|
|
|
768
879
|
# Afficher la sortie de CASToR (pour débogage)
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
880
|
+
if show_logs:
|
|
881
|
+
print("Sortie CASToR :")
|
|
882
|
+
print(result.stdout)
|
|
883
|
+
if result.stderr:
|
|
884
|
+
print("Erreurs :")
|
|
885
|
+
print(result.stderr)
|
|
886
|
+
|
|
887
|
+
if show_logs:
|
|
888
|
+
print("Reconstruction terminée avec succès.")
|
|
776
889
|
self.load_reconCASToR(withTumor=withTumor)
|
|
777
890
|
|
|
778
891
|
# STATIC METHODS
|