AOT-biomaps 2.1.3__py3-none-any.whl → 2.9.233__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/AcousticEnums.py +64 -0
- AOT_biomaps/AOT_Acoustic/AcousticTools.py +221 -0
- AOT_biomaps/AOT_Acoustic/FocusedWave.py +244 -0
- AOT_biomaps/AOT_Acoustic/IrregularWave.py +66 -0
- AOT_biomaps/AOT_Acoustic/PlaneWave.py +43 -0
- AOT_biomaps/AOT_Acoustic/StructuredWave.py +392 -0
- AOT_biomaps/AOT_Acoustic/__init__.py +15 -0
- AOT_biomaps/AOT_Acoustic/_mainAcoustic.py +978 -0
- AOT_biomaps/AOT_Experiment/Focus.py +55 -0
- AOT_biomaps/AOT_Experiment/Tomography.py +505 -0
- AOT_biomaps/AOT_Experiment/__init__.py +9 -0
- AOT_biomaps/AOT_Experiment/_mainExperiment.py +532 -0
- AOT_biomaps/AOT_Optic/Absorber.py +24 -0
- AOT_biomaps/AOT_Optic/Laser.py +70 -0
- AOT_biomaps/AOT_Optic/OpticEnums.py +17 -0
- AOT_biomaps/AOT_Optic/__init__.py +10 -0
- AOT_biomaps/AOT_Optic/_mainOptic.py +204 -0
- AOT_biomaps/AOT_Recon/AOT_Optimizers/DEPIERRO.py +191 -0
- AOT_biomaps/AOT_Recon/AOT_Optimizers/LS.py +106 -0
- AOT_biomaps/AOT_Recon/AOT_Optimizers/MAPEM.py +456 -0
- AOT_biomaps/AOT_Recon/AOT_Optimizers/MLEM.py +333 -0
- AOT_biomaps/AOT_Recon/AOT_Optimizers/PDHG.py +221 -0
- AOT_biomaps/AOT_Recon/AOT_Optimizers/__init__.py +5 -0
- AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/Huber.py +90 -0
- AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/Quadratic.py +86 -0
- AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/RelativeDifferences.py +59 -0
- AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/__init__.py +3 -0
- AOT_biomaps/AOT_Recon/AlgebraicRecon.py +1023 -0
- AOT_biomaps/AOT_Recon/AnalyticRecon.py +154 -0
- AOT_biomaps/AOT_Recon/BayesianRecon.py +230 -0
- AOT_biomaps/AOT_Recon/DeepLearningRecon.py +35 -0
- AOT_biomaps/AOT_Recon/PrimalDualRecon.py +210 -0
- AOT_biomaps/AOT_Recon/ReconEnums.py +375 -0
- AOT_biomaps/AOT_Recon/ReconTools.py +273 -0
- AOT_biomaps/AOT_Recon/__init__.py +11 -0
- AOT_biomaps/AOT_Recon/_mainRecon.py +288 -0
- AOT_biomaps/Config.py +95 -0
- AOT_biomaps/Settings.py +45 -13
- AOT_biomaps/__init__.py +271 -18
- aot_biomaps-2.9.233.dist-info/METADATA +22 -0
- aot_biomaps-2.9.233.dist-info/RECORD +43 -0
- {AOT_biomaps-2.1.3.dist-info → aot_biomaps-2.9.233.dist-info}/WHEEL +1 -1
- AOT_biomaps/AOT_Acoustic.py +0 -1881
- AOT_biomaps/AOT_Experiment.py +0 -541
- AOT_biomaps/AOT_Optic.py +0 -219
- AOT_biomaps/AOT_Reconstruction.py +0 -1416
- AOT_biomaps/config.py +0 -54
- AOT_biomaps-2.1.3.dist-info/METADATA +0 -20
- AOT_biomaps-2.1.3.dist-info/RECORD +0 -11
- {AOT_biomaps-2.1.3.dist-info → aot_biomaps-2.9.233.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
from ._mainRecon import Recon
|
|
2
|
+
from .ReconEnums import ReconType, AnalyticType, ProcessType
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from tqdm import trange
|
|
6
|
+
|
|
7
|
+
class AnalyticRecon(Recon):
|
|
8
|
+
def __init__(self, analyticType, **kwargs):
|
|
9
|
+
super().__init__(**kwargs)
|
|
10
|
+
self.reconType = ReconType.Analytic
|
|
11
|
+
self.analyticType = analyticType
|
|
12
|
+
|
|
13
|
+
def run(self, processType = ProcessType.PYTHON, withTumor= True):
|
|
14
|
+
"""
|
|
15
|
+
This method is a placeholder for the analytic reconstruction process.
|
|
16
|
+
It currently does not perform any operations but serves as a template for future implementations.
|
|
17
|
+
"""
|
|
18
|
+
if(processType == ProcessType.CASToR):
|
|
19
|
+
raise NotImplementedError("CASToR analytic reconstruction is not implemented yet.")
|
|
20
|
+
elif(processType == ProcessType.PYTHON):
|
|
21
|
+
self._analyticReconPython(withTumor)
|
|
22
|
+
else:
|
|
23
|
+
raise ValueError(f"Unknown analytic reconstruction type: {processType}")
|
|
24
|
+
|
|
25
|
+
def checkExistingFile(self, date = None):
|
|
26
|
+
raise NotImplementedError("checkExistingFile method is not implemented yet.")
|
|
27
|
+
|
|
28
|
+
def _analyticReconPython(self,withTumor):
|
|
29
|
+
"""
|
|
30
|
+
This method is a placeholder for the analytic reconstruction process in Python.
|
|
31
|
+
It currently does not perform any operations but serves as a template for future implementations.
|
|
32
|
+
|
|
33
|
+
Parameters:
|
|
34
|
+
analyticType: The type of analytic reconstruction to perform (default is iFOURIER).
|
|
35
|
+
"""
|
|
36
|
+
if withTumor:
|
|
37
|
+
if self.analyticType == AnalyticType.iFOURIER:
|
|
38
|
+
self.reconPhantom = self._iFourierRecon(self.experiment.AOsignal_withTumor)
|
|
39
|
+
elif self.analyticType == AnalyticType.iRADON:
|
|
40
|
+
self.reconPhantom = self._iRadonRecon(self.experiment.AOsignal_withTumor)
|
|
41
|
+
else:
|
|
42
|
+
raise ValueError(f"Unknown analytic type: {self.analyticType}")
|
|
43
|
+
else:
|
|
44
|
+
if self.analyticType == AnalyticType.iFOURIER:
|
|
45
|
+
self.reconLaser = self._iFourierRecon(self.experiment.AOsignal_withoutTumor)
|
|
46
|
+
elif self.analyticType == AnalyticType.iRADON:
|
|
47
|
+
self.reconLaser = self._iRadonRecon(self.experiment.AOsignal_withoutTumor)
|
|
48
|
+
else:
|
|
49
|
+
raise ValueError(f"Unknown analytic type: {self.analyticType}")
|
|
50
|
+
|
|
51
|
+
def _iFourierRecon(self, AOsignal):
|
|
52
|
+
"""
|
|
53
|
+
Reconstruction d'image utilisant la transformation de Fourier inverse.
|
|
54
|
+
|
|
55
|
+
:param AOsignal: Signal dans le domaine temporel.
|
|
56
|
+
:return: Image reconstruite dans le domaine spatial.
|
|
57
|
+
"""
|
|
58
|
+
# Signal dans le domaine fréquentiel (FFT sur l'axe temporel)
|
|
59
|
+
s_tilde = np.fft.fft(AOsignal, axis=0)
|
|
60
|
+
|
|
61
|
+
theta = np.array([af.angle for af in self.experiment.AcousticFields]) # angles (N_theta,)
|
|
62
|
+
f_s = np.array([af.f_s for af in self.experiment.AcousticFields]) # spatial freqs (N_theta,)
|
|
63
|
+
f_t = np.fft.fftfreq(AOsignal.shape[0], d=self.experiment.dt) # temporal freqs
|
|
64
|
+
|
|
65
|
+
x = self.experiment.OpticImage.laser.x
|
|
66
|
+
z = self.experiment.OpticImage.laser.z
|
|
67
|
+
X, Z = np.meshgrid(x, z, indexing='ij') # shape (Nx, Nz)
|
|
68
|
+
|
|
69
|
+
N_theta = len(theta)
|
|
70
|
+
I_rec = np.zeros((len(x), len(z)), dtype=complex)
|
|
71
|
+
|
|
72
|
+
for i, th in enumerate(trange(N_theta, desc="AOT-BioMaps -- Analytic Recontruction Tomography : iFourier (Processing projection) ---- processing on single CPU ----")):
|
|
73
|
+
fs = f_s[i]
|
|
74
|
+
|
|
75
|
+
# Projection des coordonnées dans le repère tourné
|
|
76
|
+
x_prime = X * np.cos(th) + Z * np.sin(th)
|
|
77
|
+
z_prime = -X * np.sin(th) + Z * np.cos(th)
|
|
78
|
+
|
|
79
|
+
# Signal spectral pour cet angle (1D pour chaque f_t)
|
|
80
|
+
s_angle = s_tilde[:, i] # shape (len(f_t),)
|
|
81
|
+
|
|
82
|
+
# Grille 2D des fréquences
|
|
83
|
+
F_t, F_s = np.meshgrid(f_t, [fs], indexing='ij') # F_t: (len(f_t), 1), F_s: (1, 1)
|
|
84
|
+
|
|
85
|
+
# Phase : exp(2iπ(x' f_s + z' f_t)) = (x_prime * f_s + z_prime * f_t)
|
|
86
|
+
phase = 2j * np.pi * (x_prime[:, :, None] * fs + z_prime[:, :, None] * f_t[None, None, :])
|
|
87
|
+
|
|
88
|
+
# reshape s_angle to (len(f_t), 1, 1)
|
|
89
|
+
s_angle = s_angle[:, None, None]
|
|
90
|
+
|
|
91
|
+
# Contribution de cet angle
|
|
92
|
+
integrand = s_angle * np.exp(phase)
|
|
93
|
+
|
|
94
|
+
# Intégration sur f_t (somme discrète)
|
|
95
|
+
I_theta = np.sum(integrand, axis=0)
|
|
96
|
+
|
|
97
|
+
# Ajout à la reconstruction
|
|
98
|
+
I_rec += I_theta
|
|
99
|
+
|
|
100
|
+
I_rec /= N_theta
|
|
101
|
+
|
|
102
|
+
return np.abs(I_rec)
|
|
103
|
+
|
|
104
|
+
def _iRadonRecon(self, AOsignal):
|
|
105
|
+
"""
|
|
106
|
+
Reconstruction d'image utilisant la méthode iRadon.
|
|
107
|
+
|
|
108
|
+
:return: Image reconstruite.
|
|
109
|
+
"""
|
|
110
|
+
@staticmethod
|
|
111
|
+
def trapz(y, x):
|
|
112
|
+
"""Compute the trapezoidal rule for integration."""
|
|
113
|
+
return np.sum((y[:-1] + y[1:]) * (x[1:] - x[:-1]) / 2)
|
|
114
|
+
|
|
115
|
+
# Initialisation de l'image reconstruite
|
|
116
|
+
I_rec = np.zeros((len(self.experiment.OpticImage.laser.x), len(self.experiment.OpticImage.laser.z)), dtype=complex)
|
|
117
|
+
|
|
118
|
+
# Transformation de Fourier du signal
|
|
119
|
+
s_tilde = np.fft.fft(AOsignal, axis=0)
|
|
120
|
+
|
|
121
|
+
# Extraction des angles et des fréquences spatiales
|
|
122
|
+
theta = [acoustic_field.angle for acoustic_field in self.experiment.AcousticFields]
|
|
123
|
+
f_s = [acoustic_field.f_s for acoustic_field in self.experiment.AcousticFields]
|
|
124
|
+
|
|
125
|
+
# Calcul des coordonnées transformées et intégrales
|
|
126
|
+
with trange(len(theta) * 2, desc="AOT-BioMaps -- Analytic Reconstruction Tomography: iRadon") as pbar:
|
|
127
|
+
for i in range(len(theta)):
|
|
128
|
+
pbar.set_description("AOT-BioMaps -- Analytic Reconstruction Tomography: iRadon (Processing frequency contributions) ---- processing on single CPU ----")
|
|
129
|
+
th = theta[i]
|
|
130
|
+
x_prime = self.experiment.OpticImage.x[:, np.newaxis] * np.cos(th) - self.experiment.OpticImage.z[np.newaxis, :] * np.sin(th)
|
|
131
|
+
z_prime = self.experiment.OpticImage.z[np.newaxis, :] * np.cos(th) + self.experiment.OpticImage.x[:, np.newaxis] * np.sin(th)
|
|
132
|
+
|
|
133
|
+
# Première intégrale : partie réelle
|
|
134
|
+
for j in range(len(f_s)):
|
|
135
|
+
fs = f_s[j]
|
|
136
|
+
integrand = s_tilde[i, j] * np.exp(2j * np.pi * (x_prime * fs + z_prime * fs))
|
|
137
|
+
integral = self.trapz(integrand * fs, fs)
|
|
138
|
+
I_rec += 2 * np.real(integral)
|
|
139
|
+
pbar.update(1)
|
|
140
|
+
|
|
141
|
+
for i in range(len(theta)):
|
|
142
|
+
pbar.set_description("AOT-BioMaps -- Analytic Reconstruction Tomography: iRadon (Processing central contributions) ---- processing on single CPU ----")
|
|
143
|
+
th = theta[i]
|
|
144
|
+
x_prime = self.experiment.OpticImage.x[:, np.newaxis] * np.cos(th) - self.experiment.OpticImage.z[np.newaxis, :] * np.sin(th)
|
|
145
|
+
z_prime = self.experiment.OpticImage.z[np.newaxis, :] * np.cos(th) + self.experiment.OpticImage.x[:, np.newaxis] * np.sin(th)
|
|
146
|
+
|
|
147
|
+
# Filtrer les fréquences spatiales pour ne garder que celles inférieures ou égales à f_s_max
|
|
148
|
+
filtered_f_s = np.array([fs for fs in f_s if fs <= self.f_s_max])
|
|
149
|
+
integrand = s_tilde[i, np.where(np.array(f_s) == 0)[0][0]] * np.exp(2j * np.pi * z_prime * filtered_f_s)
|
|
150
|
+
integral = self.trapz(integrand * filtered_f_s, filtered_f_s)
|
|
151
|
+
I_rec += integral
|
|
152
|
+
pbar.update(1)
|
|
153
|
+
|
|
154
|
+
return np.abs(I_rec)
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
from AOT_biomaps.AOT_Recon.AlgebraicRecon import AlgebraicRecon
|
|
2
|
+
from AOT_biomaps.AOT_Recon.ReconEnums import ReconType, OptimizerType, PotentialType, ProcessType
|
|
3
|
+
from .ReconTools import check_gpu_memory, calculate_memory_requirement
|
|
4
|
+
from .AOT_Optimizers import MAPEM, MAPEM_STOP, DEPIERRO
|
|
5
|
+
from AOT_biomaps.Config import config
|
|
6
|
+
|
|
7
|
+
import warnings
|
|
8
|
+
import numpy as np
|
|
9
|
+
import os
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
class BayesianRecon(AlgebraicRecon):
|
|
13
|
+
"""
|
|
14
|
+
This class implements the Bayesian reconstruction process.
|
|
15
|
+
It currently does not perform any operations but serves as a template for future implementations.
|
|
16
|
+
"""
|
|
17
|
+
def __init__(self,
|
|
18
|
+
opti = OptimizerType.PGC,
|
|
19
|
+
potentialFunction = PotentialType.HUBER_PIECEWISE,
|
|
20
|
+
beta=None,
|
|
21
|
+
delta=None,
|
|
22
|
+
gamma=None,
|
|
23
|
+
sigma=None,
|
|
24
|
+
corner = (0.5-np.sqrt(2)/4)/np.sqrt(2),
|
|
25
|
+
face = 0.5-np.sqrt(2)/4,
|
|
26
|
+
**kwargs):
|
|
27
|
+
super().__init__(**kwargs)
|
|
28
|
+
self.reconType = ReconType.Bayesian
|
|
29
|
+
self.potentialFunction = potentialFunction
|
|
30
|
+
self.optimizer = opti
|
|
31
|
+
self.beta = beta
|
|
32
|
+
self.delta = delta # typical value is 0.1
|
|
33
|
+
self.gamma = gamma # typical value is 0.01
|
|
34
|
+
self.sigma = sigma # typical value is 1.0
|
|
35
|
+
self.corner = corner # typical value is (0.5-np.sqrt(2)/4)/np.sqrt(2)
|
|
36
|
+
self.face = face # typical value is 0.5-np.sqrt(2)/4
|
|
37
|
+
|
|
38
|
+
if not isinstance(self.potentialFunction, PotentialType):
|
|
39
|
+
raise TypeError(f"Potential functions must be of type PotentialType, got {type(self.potentialFunction)}")
|
|
40
|
+
|
|
41
|
+
def checkExistingFile(self, date = None):
|
|
42
|
+
"""
|
|
43
|
+
Check if the reconstruction file already exists, based on current instance parameters.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
withTumor (bool): If True, checks the phantom file; otherwise, checks the laser file.
|
|
47
|
+
overwrite (bool): If False, returns False if the file exists.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
tuple: (bool: whether to save, str: the filepath)
|
|
51
|
+
"""
|
|
52
|
+
if self.saveDir is None:
|
|
53
|
+
raise ValueError("Save directory is not specified.")
|
|
54
|
+
|
|
55
|
+
# Construction du chemin du fichier
|
|
56
|
+
if date is None:
|
|
57
|
+
date = datetime.now().strftime("%d%m")
|
|
58
|
+
|
|
59
|
+
opt_name = self.optimizer.value
|
|
60
|
+
pot_name = self.potentialFunction.value
|
|
61
|
+
dir_name = f'results_{date}_{opt_name}_{pot_name}'
|
|
62
|
+
|
|
63
|
+
if self.optimizer == OptimizerType.PPGMLEM:
|
|
64
|
+
dir_name += f'_Beta_{self.beta}_Delta_{self.delta}_Gamma_{self.gamma}_Sigma_{self.sigma}'
|
|
65
|
+
elif self.optimizer in (OptimizerType.PGC, OptimizerType.DEPIERRO95):
|
|
66
|
+
dir_name += f'_Beta_{self.beta}_Sigma_{self.sigma}'
|
|
67
|
+
|
|
68
|
+
results_dir = os.path.join(self.saveDir, dir_name)
|
|
69
|
+
if not os.path.exists(results_dir):
|
|
70
|
+
os.makedirs(results_dir)
|
|
71
|
+
|
|
72
|
+
if os.path.exists(os.path.join(results_dir,"indices.npy")):
|
|
73
|
+
return (True, results_dir)
|
|
74
|
+
|
|
75
|
+
return (False, results_dir)
|
|
76
|
+
|
|
77
|
+
def load(self, withTumor=True, results_date=None, optimizer=None, potential_function=None, filePath=None, show_logs=True):
|
|
78
|
+
"""
|
|
79
|
+
Load the reconstruction results and indices as lists of 2D np arrays for Bayesian reconstruction and store them in self.
|
|
80
|
+
If the loaded file is a 3D array, it is split into a list of 2D arrays.
|
|
81
|
+
"""
|
|
82
|
+
if filePath is not None:
|
|
83
|
+
# Mode chargement direct depuis un fichier
|
|
84
|
+
recon_key = 'reconPhantom' if withTumor else 'reconLaser'
|
|
85
|
+
recon_path = filePath
|
|
86
|
+
if not os.path.exists(recon_path):
|
|
87
|
+
raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
|
|
88
|
+
# Charge le fichier (3D ou liste de 2D)
|
|
89
|
+
data = np.load(recon_path, allow_pickle=True)
|
|
90
|
+
# Découpe en liste de 2D si c'est un tableau 3D
|
|
91
|
+
if isinstance(data, np.ndarray) and data.ndim == 3:
|
|
92
|
+
if withTumor:
|
|
93
|
+
self.reconPhantom = [data[i, :, :] for i in range(data.shape[0])]
|
|
94
|
+
else:
|
|
95
|
+
self.reconLaser = [data[i, :, :] for i in range(data.shape[0])]
|
|
96
|
+
else:
|
|
97
|
+
# Sinon, suppose que c'est déjà une liste de 2D
|
|
98
|
+
if withTumor:
|
|
99
|
+
self.reconPhantom = data
|
|
100
|
+
else:
|
|
101
|
+
self.reconLaser = data
|
|
102
|
+
# Essayer de charger les indices
|
|
103
|
+
base_dir, _ = os.path.split(recon_path)
|
|
104
|
+
indices_path = os.path.join(base_dir, 'indices.npy')
|
|
105
|
+
if os.path.exists(indices_path):
|
|
106
|
+
indices_data = np.load(indices_path, allow_pickle=True)
|
|
107
|
+
if isinstance(indices_data, np.ndarray) and indices_data.ndim == 3:
|
|
108
|
+
self.indices = [indices_data[i, :, :] for i in range(indices_data.shape[0])]
|
|
109
|
+
else:
|
|
110
|
+
self.indices = indices_data
|
|
111
|
+
else:
|
|
112
|
+
self.indices = None
|
|
113
|
+
if show_logs:
|
|
114
|
+
print(f"Loaded reconstruction results and indices from {recon_path}")
|
|
115
|
+
else:
|
|
116
|
+
# Mode chargement depuis le répertoire de résultats
|
|
117
|
+
if self.saveDir is None:
|
|
118
|
+
raise ValueError("Save directory is not specified. Please set saveDir before loading.")
|
|
119
|
+
# Use current optimizer and potential function if not provided
|
|
120
|
+
opt_name = optimizer.value if optimizer is not None else self.optimizer.value
|
|
121
|
+
pot_name = potential_function.value if potential_function is not None else self.potentialFunction.value
|
|
122
|
+
# Build the base directory pattern
|
|
123
|
+
dir_pattern = f'results_*_{opt_name}_{pot_name}'
|
|
124
|
+
# Add parameters to the pattern based on the optimizer
|
|
125
|
+
if optimizer is None:
|
|
126
|
+
optimizer = self.optimizer
|
|
127
|
+
if optimizer == OptimizerType.PPGMLEM:
|
|
128
|
+
beta_str = f'_Beta_{self.beta}'
|
|
129
|
+
delta_str = f'_Delta_{self.delta}'
|
|
130
|
+
gamma_str = f'_Gamma_{self.gamma}'
|
|
131
|
+
sigma_str = f'_Sigma_{self.sigma}'
|
|
132
|
+
dir_pattern += f'{beta_str}{delta_str}{gamma_str}{sigma_str}'
|
|
133
|
+
elif optimizer in (OptimizerType.PGC, OptimizerType.DEPIERRO95):
|
|
134
|
+
beta_str = f'_Beta_{self.beta}'
|
|
135
|
+
sigma_str = f'_Sigma_{self.sigma}'
|
|
136
|
+
dir_pattern += f'{beta_str}{sigma_str}'
|
|
137
|
+
# Find the most recent results directory if no date is specified
|
|
138
|
+
if results_date is None:
|
|
139
|
+
dirs = [d for d in os.listdir(self.saveDir) if os.path.isdir(os.path.join(self.saveDir, d)) and dir_pattern in d]
|
|
140
|
+
if not dirs:
|
|
141
|
+
raise FileNotFoundError(f"No matching results directory found for pattern '{dir_pattern}' in {self.saveDir}.")
|
|
142
|
+
dirs.sort(reverse=True) # Most recent first
|
|
143
|
+
results_dir = os.path.join(self.saveDir, dirs[0])
|
|
144
|
+
else:
|
|
145
|
+
results_dir = os.path.join(self.saveDir, f'results_{results_date}_{opt_name}_{pot_name}')
|
|
146
|
+
if optimizer == OptimizerType.PPGMLEM:
|
|
147
|
+
results_dir += f'_Beta_{self.beta}_Delta_{self.delta}_Gamma_{self.gamma}_Sigma_{self.sigma}'
|
|
148
|
+
elif optimizer in (OptimizerType.PGC, OptimizerType.DEPIERRO95):
|
|
149
|
+
results_dir += f'_Beta_{self.beta}_Sigma_{self.sigma}'
|
|
150
|
+
if not os.path.exists(results_dir):
|
|
151
|
+
raise FileNotFoundError(f"Directory {results_dir} does not exist.")
|
|
152
|
+
# Load reconstruction results
|
|
153
|
+
recon_key = 'reconPhantom' if withTumor else 'reconLaser'
|
|
154
|
+
recon_path = os.path.join(results_dir, f'{recon_key}.npy')
|
|
155
|
+
if not os.path.exists(recon_path):
|
|
156
|
+
raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
|
|
157
|
+
data = np.load(recon_path, allow_pickle=True)
|
|
158
|
+
if isinstance(data, np.ndarray) and data.ndim == 3:
|
|
159
|
+
if withTumor:
|
|
160
|
+
self.reconPhantom = [data[i, :, :] for i in range(data.shape[0])]
|
|
161
|
+
else:
|
|
162
|
+
self.reconLaser = [data[i, :, :] for i in range(data.shape[0])]
|
|
163
|
+
else:
|
|
164
|
+
if withTumor:
|
|
165
|
+
self.reconPhantom = data
|
|
166
|
+
else:
|
|
167
|
+
self.reconLaser = data
|
|
168
|
+
# Load saved indices as list of 2D arrays
|
|
169
|
+
indices_path = os.path.join(results_dir, 'indices.npy')
|
|
170
|
+
if not os.path.exists(indices_path):
|
|
171
|
+
raise FileNotFoundError(f"No indices file found at {indices_path}.")
|
|
172
|
+
indices_data = np.load(indices_path, allow_pickle=True)
|
|
173
|
+
if isinstance(indices_data, np.ndarray) and indices_data.ndim == 3:
|
|
174
|
+
self.indices = [indices_data[i, :, :] for i in range(indices_data.shape[0])]
|
|
175
|
+
else:
|
|
176
|
+
self.indices = indices_data
|
|
177
|
+
if show_logs:
|
|
178
|
+
print(f"Loaded reconstruction results and indices from {results_dir}")
|
|
179
|
+
|
|
180
|
+
def run(self, processType=ProcessType.PYTHON, withTumor=True, show_logs=True):
|
|
181
|
+
"""
|
|
182
|
+
This method is a placeholder for the Bayesian reconstruction process.
|
|
183
|
+
It currently does not perform any operations but serves as a template for future implementations.
|
|
184
|
+
"""
|
|
185
|
+
if(processType == ProcessType.CASToR):
|
|
186
|
+
self._bayesianReconCASToR(withTumor)
|
|
187
|
+
elif(processType == ProcessType.PYTHON):
|
|
188
|
+
self._bayesianReconPython(withTumor)
|
|
189
|
+
else:
|
|
190
|
+
raise ValueError(f"Unknown Bayesian reconstruction type: {processType}")
|
|
191
|
+
|
|
192
|
+
def _bayesianReconCASToR(self, show_logs, withTumor):
|
|
193
|
+
raise NotImplementedError("CASToR Bayesian reconstruction is not implemented yet.")
|
|
194
|
+
|
|
195
|
+
def _bayesianReconPython(self, show_logs, withTumor):
|
|
196
|
+
if withTumor:
|
|
197
|
+
if self.experiment.AOsignal_withTumor is None:
|
|
198
|
+
raise ValueError("AO signal with tumor is not available. Please generate AO signal with tumor the experiment first in the experiment object.")
|
|
199
|
+
if self.optimizer.value == OptimizerType.PPGMLEM.value:
|
|
200
|
+
self.reconPhantom, self.indices = MAPEM_STOP(
|
|
201
|
+
SMatrix=self.SMatrix,
|
|
202
|
+
y=self.experiment.AOsignal_withTumor,
|
|
203
|
+
Omega=self.potentialFunction,
|
|
204
|
+
beta=self.beta,
|
|
205
|
+
delta=self.delta,
|
|
206
|
+
gamma=self.gamma,
|
|
207
|
+
sigma=self.sigma,
|
|
208
|
+
numIterations=self.numIterations,
|
|
209
|
+
isSavingEachIteration=self.isSavingEachIteration,
|
|
210
|
+
withTumor=withTumor,
|
|
211
|
+
device=self.device,
|
|
212
|
+
max_saves=5000,
|
|
213
|
+
show_logs=True)
|
|
214
|
+
elif self.optimizer.value == OptimizerType.PGC.value:
|
|
215
|
+
self.reconPhantom, self.indices = MAPEM(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withTumor, withTumor=withTumor, show_logs=show_logs)
|
|
216
|
+
elif self.optimizer.value == OptimizerType.DEPIERRO95.value:
|
|
217
|
+
self.reconPhantom, self.indices = DEPIERRO(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withTumor, withTumor=withTumor, show_logs=show_logs)
|
|
218
|
+
else:
|
|
219
|
+
raise ValueError(f"Unknown optimizer type: {self.optimizer.value}")
|
|
220
|
+
else:
|
|
221
|
+
if self.experiment.AOsignal_withoutTumor is None:
|
|
222
|
+
raise ValueError("AO signal without tumor is not available. Please generate AO signal without tumor the experiment first in the experiment object.")
|
|
223
|
+
if self.optimizer.value == OptimizerType.PPGMLEM.value:
|
|
224
|
+
self.reconLaser, self.indices = MAPEM_STOP(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withoutTumor, withTumor=withTumor, show_logs=show_logs)
|
|
225
|
+
elif self.optimizer.value == OptimizerType.PGC.value:
|
|
226
|
+
self.reconLaser, self.indices = MAPEM(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withoutTumor, withTumor=withTumor, show_logs=show_logs)
|
|
227
|
+
elif self.optimizer.value == OptimizerType.DEPIERRO95.value:
|
|
228
|
+
self.reconLaser, self.indices = DEPIERRO(SMatrix=self.SMatrix, y=self.experiment.AOsignal_withoutTumor, withTumor=withTumor, show_logs=show_logs)
|
|
229
|
+
else:
|
|
230
|
+
raise ValueError(f"Unknown optimizer type: {self.optimizer.value}")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from AOT_biomaps.AOT_Recon._mainRecon import Recon
|
|
2
|
+
from AOT_biomaps.AOT_Recon.ReconEnums import ReconType, ProcessType
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DeepLearningRecon(Recon):
|
|
6
|
+
"""
|
|
7
|
+
This class implements the deep learning reconstruction process.
|
|
8
|
+
It currently does not perform any operations but serves as a template for future implementations.
|
|
9
|
+
"""
|
|
10
|
+
def __init__(self, **kwargs):
|
|
11
|
+
super().__init__(**kwargs)
|
|
12
|
+
self.reconType = ReconType.DeepLearning
|
|
13
|
+
self.model = None # Placeholder for the deep learning model
|
|
14
|
+
self.theta_matrix = []
|
|
15
|
+
|
|
16
|
+
def run(self, processType=ProcessType.PYTHON):
|
|
17
|
+
"""
|
|
18
|
+
This method is a placeholder for the deep learning reconstruction process.
|
|
19
|
+
It currently does not perform any operations but serves as a template for future implementations.
|
|
20
|
+
"""
|
|
21
|
+
if(processType == ProcessType.CASToR):
|
|
22
|
+
self._deepLearningReconCASToR()
|
|
23
|
+
elif(processType == ProcessType.PYTHON):
|
|
24
|
+
self._deepLearningReconPython()
|
|
25
|
+
else:
|
|
26
|
+
raise ValueError(f"Unknown deep learning reconstruction type: {processType}")
|
|
27
|
+
|
|
28
|
+
def _deepLearningReconCASToR(self):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
def _deepLearningReconPython(self):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
def checkExistingFile(self, date = None):
|
|
35
|
+
raise NotImplementedError("checkExistingFile method is not implemented yet.")
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
from AOT_biomaps.AOT_Recon.AlgebraicRecon import AlgebraicRecon
|
|
2
|
+
from AOT_biomaps.AOT_Recon.ReconEnums import ReconType, ProcessType
|
|
3
|
+
from AOT_biomaps.AOT_Recon.AOT_Optimizers import CP_KL, CP_TV
|
|
4
|
+
from AOT_biomaps.AOT_Recon.ReconEnums import OptimizerType
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
import numpy as np
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
class PrimalDualRecon(AlgebraicRecon):
|
|
12
|
+
"""
|
|
13
|
+
This class implements the convex reconstruction process.
|
|
14
|
+
It currently does not perform any operations but serves as a template for future implementations.
|
|
15
|
+
"""
|
|
16
|
+
def __init__(self, theta=1.0, L=None, **kwargs):
|
|
17
|
+
super().__init__(**kwargs)
|
|
18
|
+
self.reconType = ReconType.Convex
|
|
19
|
+
self.theta = theta # relaxation parameter (between 1 and 2)
|
|
20
|
+
self.L = L # norme spectrale de l'opérateur linéaire défini par les matrices P et P^T
|
|
21
|
+
|
|
22
|
+
def run(self, processType=ProcessType.PYTHON, withTumor=True):
|
|
23
|
+
"""
|
|
24
|
+
This method is a placeholder for the convex reconstruction process.
|
|
25
|
+
It currently does not perform any operations but serves as a template for future implementations.
|
|
26
|
+
"""
|
|
27
|
+
if(processType == ProcessType.CASToR):
|
|
28
|
+
self._convexReconCASToR(withTumor)
|
|
29
|
+
elif(processType == ProcessType.PYTHON):
|
|
30
|
+
self._convexReconPython(withTumor)
|
|
31
|
+
else:
|
|
32
|
+
raise ValueError(f"Unknown convex reconstruction type: {processType}")
|
|
33
|
+
|
|
34
|
+
def _convexReconCASToR(self, withTumor):
|
|
35
|
+
raise NotImplementedError("CASToR convex reconstruction is not implemented yet.")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def checkExistingFile(self, date = None):
|
|
39
|
+
"""
|
|
40
|
+
Check if the file already exists, based on current instance parameters.
|
|
41
|
+
Returns:
|
|
42
|
+
tuple: (bool: whether to save, str: the filepath)
|
|
43
|
+
"""
|
|
44
|
+
if date is None:
|
|
45
|
+
date = datetime.now().strftime("%d%m")
|
|
46
|
+
results_dir = os.path.join(
|
|
47
|
+
self.saveDir,
|
|
48
|
+
f'results_{date}_{self.optimizer.value}_Alpha_{self.alpha}_Theta_{self.theta}_L_{self.L}'
|
|
49
|
+
)
|
|
50
|
+
os.makedirs(results_dir, exist_ok=True)
|
|
51
|
+
|
|
52
|
+
if os.path.exists(os.path.join(results_dir,"indices.npy")):
|
|
53
|
+
return (True, results_dir)
|
|
54
|
+
|
|
55
|
+
return (False, results_dir)
|
|
56
|
+
|
|
57
|
+
def load(self, withTumor=True, results_date=None, optimizer=None, filePath=None, show_logs=True):
|
|
58
|
+
"""
|
|
59
|
+
Load the reconstruction results (reconPhantom or reconLaser) and indices as lists of 2D np arrays into self.
|
|
60
|
+
If the loaded file is a 3D array, it is split into a list of 2D arrays.
|
|
61
|
+
Args:
|
|
62
|
+
withTumor: If True, loads reconPhantom (with tumor), else reconLaser (without tumor).
|
|
63
|
+
results_date: Date string (format "ddmm") to specify which results to load. If None, uses the most recent date in saveDir.
|
|
64
|
+
optimizer: Optimizer name (as string or enum) to filter results. If None, uses the current optimizer of the instance.
|
|
65
|
+
filePath: Optional. If provided, loads directly from this path (overrides saveDir and results_date).
|
|
66
|
+
"""
|
|
67
|
+
if filePath is not None:
|
|
68
|
+
# Mode chargement direct depuis un fichier
|
|
69
|
+
recon_key = 'reconPhantom' if withTumor else 'reconLaser'
|
|
70
|
+
recon_path = filePath
|
|
71
|
+
if not os.path.exists(recon_path):
|
|
72
|
+
raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
|
|
73
|
+
# Charge les données
|
|
74
|
+
data = np.load(recon_path, allow_pickle=True)
|
|
75
|
+
# Découpe en liste de 2D si c'est un tableau 3D
|
|
76
|
+
if isinstance(data, np.ndarray) and data.ndim == 3:
|
|
77
|
+
if withTumor:
|
|
78
|
+
self.reconPhantom = [data[i, :, :] for i in range(data.shape[0])]
|
|
79
|
+
else:
|
|
80
|
+
self.reconLaser = [data[i, :, :] for i in range(data.shape[0])]
|
|
81
|
+
else:
|
|
82
|
+
# Sinon, suppose que c'est déjà une liste de 2D
|
|
83
|
+
if withTumor:
|
|
84
|
+
self.reconPhantom = data
|
|
85
|
+
else:
|
|
86
|
+
self.reconLaser = data
|
|
87
|
+
# Essayer de charger les indices
|
|
88
|
+
base_dir, _ = os.path.split(recon_path)
|
|
89
|
+
indices_path = os.path.join(base_dir, "indices.npy")
|
|
90
|
+
if os.path.exists(indices_path):
|
|
91
|
+
indices_data = np.load(indices_path, allow_pickle=True)
|
|
92
|
+
if isinstance(indices_data, np.ndarray) and indices_data.ndim == 3:
|
|
93
|
+
self.indices = [indices_data[i, :, :] for i in range(indices_data.shape[0])]
|
|
94
|
+
else:
|
|
95
|
+
self.indices = indices_data
|
|
96
|
+
else:
|
|
97
|
+
self.indices = None
|
|
98
|
+
if show_logs:
|
|
99
|
+
print(f"Loaded reconstruction results and indices from {recon_path}")
|
|
100
|
+
else:
|
|
101
|
+
# Mode chargement depuis le répertoire de résultats
|
|
102
|
+
if self.saveDir is None:
|
|
103
|
+
raise ValueError("Save directory is not specified. Please set saveDir before loading.")
|
|
104
|
+
# Determine optimizer name for path matching
|
|
105
|
+
opt_name = optimizer.value if optimizer is not None else self.optimizer.value
|
|
106
|
+
# Find the most recent results directory if no date is specified
|
|
107
|
+
dir_pattern = f'results_*_{opt_name}'
|
|
108
|
+
if opt_name == OptimizerType.CP_TV.value or opt_name == OptimizerType.CP_KL.value:
|
|
109
|
+
dir_pattern += f'_Alpha_{self.alpha}_Theta_{self.theta}_L_{self.L}'
|
|
110
|
+
if results_date is None:
|
|
111
|
+
dirs = [d for d in os.listdir(self.saveDir) if os.path.isdir(os.path.join(self.saveDir, d)) and dir_pattern in d]
|
|
112
|
+
if not dirs:
|
|
113
|
+
raise FileNotFoundError(f"No matching results directory found for pattern '{dir_pattern}' in {self.saveDir}.")
|
|
114
|
+
dirs.sort(reverse=True) # Most recent first
|
|
115
|
+
results_dir = os.path.join(self.saveDir, dirs[0])
|
|
116
|
+
else:
|
|
117
|
+
results_dir = os.path.join(self.saveDir, f'results_{results_date}_{opt_name}')
|
|
118
|
+
if opt_name == OptimizerType.CP_TV.value or opt_name == OptimizerType.CP_KL.value:
|
|
119
|
+
results_dir += f'_Alpha_{self.alpha}_Theta_{self.theta}_L_{self.L}'
|
|
120
|
+
if not os.path.exists(results_dir):
|
|
121
|
+
raise FileNotFoundError(f"Directory {results_dir} does not exist.")
|
|
122
|
+
# Load reconstruction results
|
|
123
|
+
recon_key = 'reconPhantom' if withTumor else 'reconLaser'
|
|
124
|
+
recon_path = os.path.join(results_dir, f'{recon_key}.npy')
|
|
125
|
+
if not os.path.exists(recon_path):
|
|
126
|
+
raise FileNotFoundError(f"No reconstruction file found at {recon_path}.")
|
|
127
|
+
data = np.load(recon_path, allow_pickle=True)
|
|
128
|
+
# Découpe en liste de 2D si c'est un tableau 3D
|
|
129
|
+
if isinstance(data, np.ndarray) and data.ndim == 3:
|
|
130
|
+
if withTumor:
|
|
131
|
+
self.reconPhantom = [data[i, :, :] for i in range(data.shape[0])]
|
|
132
|
+
else:
|
|
133
|
+
self.reconLaser = [data[i, :, :] for i in range(data.shape[0])]
|
|
134
|
+
else:
|
|
135
|
+
if withTumor:
|
|
136
|
+
self.reconPhantom = data
|
|
137
|
+
else:
|
|
138
|
+
self.reconLaser = data
|
|
139
|
+
# Try to load saved indices (if file exists)
|
|
140
|
+
indices_path = os.path.join(results_dir, 'indices.npy')
|
|
141
|
+
if os.path.exists(indices_path):
|
|
142
|
+
indices_data = np.load(indices_path, allow_pickle=True)
|
|
143
|
+
if isinstance(indices_data, np.ndarray) and indices_data.ndim == 3:
|
|
144
|
+
self.indices = [indices_data[i, :, :] for i in range(indices_data.shape[0])]
|
|
145
|
+
else:
|
|
146
|
+
self.indices = indices_data
|
|
147
|
+
else:
|
|
148
|
+
self.indices = None
|
|
149
|
+
if show_logs:
|
|
150
|
+
print(f"Loaded reconstruction results and indices from {results_dir}")
|
|
151
|
+
|
|
152
|
+
def _convexReconPython(self, withTumor):
|
|
153
|
+
if self.optimizer == OptimizerType.CP_TV:
|
|
154
|
+
if withTumor:
|
|
155
|
+
self.reconPhantom, self.indices = CP_TV(
|
|
156
|
+
self.SMatrix,
|
|
157
|
+
y=self.experiment.AOsignal_withTumor,
|
|
158
|
+
alpha=self.alpha,
|
|
159
|
+
theta=self.theta,
|
|
160
|
+
numIterations=self.numIterations,
|
|
161
|
+
isSavingEachIteration=self.isSavingEachIteration,
|
|
162
|
+
L=self.L,
|
|
163
|
+
withTumor=withTumor,
|
|
164
|
+
device=None
|
|
165
|
+
)
|
|
166
|
+
else:
|
|
167
|
+
self.reconLaser, self.indices = CP_TV(
|
|
168
|
+
self.SMatrix,
|
|
169
|
+
y=self.experiment.AOsignal_withoutTumor,
|
|
170
|
+
alpha=self.alpha,
|
|
171
|
+
theta=self.theta,
|
|
172
|
+
numIterations=self.numIterations,
|
|
173
|
+
isSavingEachIteration=self.isSavingEachIteration,
|
|
174
|
+
L=self.L,
|
|
175
|
+
withTumor=withTumor,
|
|
176
|
+
device=None
|
|
177
|
+
)
|
|
178
|
+
elif self.optimizer == OptimizerType.CP_KL:
|
|
179
|
+
if withTumor:
|
|
180
|
+
self.reconPhantom, self.indices = CP_KL(
|
|
181
|
+
self.SMatrix,
|
|
182
|
+
y=self.experiment.AOsignal_withTumor,
|
|
183
|
+
alpha=self.alpha,
|
|
184
|
+
theta=self.theta,
|
|
185
|
+
numIterations=self.numIterations,
|
|
186
|
+
isSavingEachIteration=self.isSavingEachIteration,
|
|
187
|
+
L=self.L,
|
|
188
|
+
withTumor=withTumor,
|
|
189
|
+
device=None
|
|
190
|
+
)
|
|
191
|
+
else:
|
|
192
|
+
self.reconLaser, self.indices = CP_KL(
|
|
193
|
+
self.SMatrix,
|
|
194
|
+
y=self.experiment.AOsignal_withoutTumor,
|
|
195
|
+
alpha=self.alpha,
|
|
196
|
+
theta=self.theta,
|
|
197
|
+
numIterations=self.numIterations,
|
|
198
|
+
isSavingEachIteration=self.isSavingEachIteration,
|
|
199
|
+
L=self.L,
|
|
200
|
+
withTumor=withTumor,
|
|
201
|
+
device=None
|
|
202
|
+
)
|
|
203
|
+
else:
|
|
204
|
+
raise ValueError(f"Optimizer value must be CP_TV or CP_KL, got {self.optimizer}")
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
|