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