AOT-biomaps 2.9.333__py3-none-any.whl → 2.9.356__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_Experiment/ExperimentTools.py +15 -25
- AOT_biomaps/AOT_Experiment/Tomography.py +61 -70
- AOT_biomaps/AOT_Recon/AnalyticRecon.py +291 -104
- AOT_biomaps/AOT_Recon/ReconTools.py +162 -1
- AOT_biomaps/AOT_Recon/_mainRecon.py +6 -2
- AOT_biomaps/__init__.py +24 -1
- {aot_biomaps-2.9.333.dist-info → aot_biomaps-2.9.356.dist-info}/METADATA +1 -1
- {aot_biomaps-2.9.333.dist-info → aot_biomaps-2.9.356.dist-info}/RECORD +10 -10
- {aot_biomaps-2.9.333.dist-info → aot_biomaps-2.9.356.dist-info}/WHEEL +0 -0
- {aot_biomaps-2.9.333.dist-info → aot_biomaps-2.9.356.dist-info}/top_level.txt +0 -0
|
@@ -1,32 +1,22 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
|
|
3
|
-
def calc_mat_os(xm, fx,
|
|
3
|
+
def calc_mat_os(xm, fx, bool_active_list, signal_type):
|
|
4
|
+
"""
|
|
5
|
+
xm : vecteur des positions réelles des éléments (en m)
|
|
6
|
+
fx : fréquence spatiale (en m^-1)
|
|
7
|
+
signal_type : 'cos' ou 'sin'
|
|
8
|
+
"""
|
|
4
9
|
num_els = len(xm)
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
if
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
else:
|
|
12
|
-
# sin(0) = 0 -> Tout est inactif
|
|
13
|
-
mask = np.zeros(num_els, dtype=bool)
|
|
10
|
+
num_cols = bool_active_list.shape[1]
|
|
11
|
+
|
|
12
|
+
if signal_type == 'cos':
|
|
13
|
+
mask = (np.cos(2 * np.pi * fx * xm) > 0).astype(float)
|
|
14
|
+
elif signal_type == 'sin':
|
|
15
|
+
mask = (np.sin(2 * np.pi * fx * xm) > 0).astype(float)
|
|
14
16
|
else:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
# Sécurité : si fx est tellement grand que half_period < 1
|
|
19
|
-
half_period_elements = max(1, half_period_elements)
|
|
20
|
-
|
|
21
|
-
indices = np.arange(num_els)
|
|
22
|
-
if signal_type == 'cos':
|
|
23
|
-
mask = ((indices // half_period_elements) % 2 == 0)
|
|
24
|
-
else:
|
|
25
|
-
# Déphasage de 90° pour le sinus : on décale d'une demi-demi-période
|
|
26
|
-
shift = half_period_elements // 2
|
|
27
|
-
mask = (((indices + shift) // half_period_elements) % 2 == 0)
|
|
28
|
-
|
|
29
|
-
return np.tile(mask[:, np.newaxis], (1, bool_active_list.shape[1]))
|
|
17
|
+
mask = np.ones(num_els) # Sécurité
|
|
18
|
+
|
|
19
|
+
return np.tile(mask[:, np.newaxis], (1, num_cols))
|
|
30
20
|
|
|
31
21
|
def convert_to_hex_list(matrix):
|
|
32
22
|
"""
|
|
@@ -18,27 +18,8 @@ class Tomography(Experiment):
|
|
|
18
18
|
self.patterns = None
|
|
19
19
|
self.theta = []
|
|
20
20
|
self.decimations = []
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
for i in range(len(self.AcousticFields)):
|
|
24
|
-
profile = hex_to_binary_profile(self.AcousticFields[i].getName_field()[6:-4], self.params.acoustic['num_elements'])
|
|
25
|
-
angle = self.AcousticFields[i].angle
|
|
26
|
-
self.theta.append(angle)
|
|
27
|
-
|
|
28
|
-
if set(self.AcousticFields[i].getName_field()[6:-4].lower().replace(" ", "")) == {'f'}:
|
|
29
|
-
fs_key = 0.0 # fs_key est en mm^-1 (0.0 mm^-1)
|
|
30
|
-
else:
|
|
31
|
-
ft_prof = np.fft.fft(profile)
|
|
32
|
-
idx_max = np.argmax(np.abs(ft_prof[1:len(profile)//2])) + 1
|
|
33
|
-
freqs = np.fft.fftfreq(len(profile), d=self.params.general['dx'])
|
|
34
|
-
|
|
35
|
-
# freqs est en m^-1 car delta_x est en mètres.
|
|
36
|
-
fs_m_inv = abs(freqs[idx_max])
|
|
37
|
-
|
|
38
|
-
fs_key = fs_m_inv # Fréquence spatiale en mm^-1
|
|
39
|
-
|
|
40
|
-
# fs = n * dfx => n = fs / dfx with dfx = 1/(N*delta_x)
|
|
41
|
-
self.decimations.append(int(fs_key / (1/(len(profile)*self.params.general['dx']))))
|
|
21
|
+
self.ActiveList = []
|
|
22
|
+
self.DelayLaw = []
|
|
42
23
|
|
|
43
24
|
# PUBLIC METHODS
|
|
44
25
|
def check(self):
|
|
@@ -85,6 +66,29 @@ class Tomography(Experiment):
|
|
|
85
66
|
"""
|
|
86
67
|
if self.TypeAcoustic.value == WaveType.StructuredWave.value:
|
|
87
68
|
self.AcousticFields = self._generateAcousticFields_STRUCT_CPU(fieldDataPath, show_log, nameBlock)
|
|
69
|
+
for i in range(len(self.AcousticFields)):
|
|
70
|
+
profile = hex_to_binary_profile(self.AcousticFields[i].getName_field()[6:-4], self.params.acoustic['num_elements'])
|
|
71
|
+
self.ActiveList.append(profile)
|
|
72
|
+
angle = self.AcousticFields[i].angle
|
|
73
|
+
self.theta.append(angle)
|
|
74
|
+
Delay = 1000 * (1/self.params.acoustic['c0']) * np.sin(np.deg2rad(angle)) * np.arange(1, self.params.acoustic['num_elements'] + 1) * self.params.acoustic['element_width']
|
|
75
|
+
self.DelayLaw.append(Delay - np.min(Delay))
|
|
76
|
+
|
|
77
|
+
if set(self.AcousticFields[i].getName_field()[6:-4].lower().replace(" ", "")) == {'f'}:
|
|
78
|
+
fs_key = 0.0 # fs_key est en mm^-1 (0.0 mm^-1)
|
|
79
|
+
else:
|
|
80
|
+
ft_prof = np.fft.fft(profile)
|
|
81
|
+
idx_max = np.argmax(np.abs(ft_prof[1:len(profile)//2])) + 1
|
|
82
|
+
freqs = np.fft.fftfreq(len(profile), d=self.params.general['dx'])
|
|
83
|
+
|
|
84
|
+
# freqs est en m^-1 car delta_x est en mètres.
|
|
85
|
+
fs_m_inv = abs(freqs[idx_max])
|
|
86
|
+
|
|
87
|
+
fs_key = fs_m_inv # Fréquence spatiale en mm^-1
|
|
88
|
+
|
|
89
|
+
# fs = n * dfx => n = fs / dfx with dfx = 1/(N*delta_x)
|
|
90
|
+
self.decimations.append(int(fs_key / (1/(len(profile)*self.params.general['dx']))))
|
|
91
|
+
|
|
88
92
|
else:
|
|
89
93
|
raise ValueError("Unsupported wave type.")
|
|
90
94
|
|
|
@@ -314,23 +318,10 @@ class Tomography(Experiment):
|
|
|
314
318
|
raise ValueError("Generated patterns failed validation.")
|
|
315
319
|
else:
|
|
316
320
|
raise ValueError("Either N (>=2) or both decimations and angles must be provided for pattern generation.")
|
|
317
|
-
|
|
318
|
-
|
|
321
|
+
|
|
319
322
|
def saveAOsignals_matlab(self, filePath):
|
|
320
|
-
ActiveList = []
|
|
321
|
-
DelayLaw = []
|
|
322
|
-
c = self.params.acoustic['c0']
|
|
323
|
-
NbElemts = self.params.acoustic['num_elements']
|
|
324
|
-
pitch = self.params.acoustic['width']
|
|
325
|
-
|
|
326
|
-
for i in range(len(self.AcousticFields)):
|
|
327
|
-
profile = hex_to_binary_profile(self.AcousticFields[i].getName_field()[6:-4], NbElemts)
|
|
328
|
-
ActiveList.append(profile)
|
|
329
|
-
angle = self.AcousticFields[i].angle
|
|
330
|
-
Delay = 1000 * (1/c) * np.sin(np.deg2rad(angle)) * np.arange(1, NbElemts + 1) * pitch
|
|
331
|
-
DelayLaw.append(Delay - np.min(Delay))
|
|
332
323
|
|
|
333
|
-
savemat(filePath, {'data': self.AOsignal_withTumor, 'thetas': self.theta, 'decimations': self.decimations, 'ActiveList' : ActiveList, 'DelayLaw': DelayLaw})
|
|
324
|
+
savemat(filePath, {'data': self.AOsignal_withTumor, 'thetas': self.theta, 'decimations': self.decimations, 'ActiveList' : self.ActiveList, 'DelayLaw': self.DelayLaw})
|
|
334
325
|
|
|
335
326
|
def selectAngles(self, angles):
|
|
336
327
|
|
|
@@ -383,74 +374,73 @@ class Tomography(Experiment):
|
|
|
383
374
|
self.AcousticFields = newAcousticFields
|
|
384
375
|
|
|
385
376
|
def _genereate_patterns_from_decimations(self, decimations, angles):
|
|
386
|
-
if isinstance(decimations, list):
|
|
387
|
-
|
|
377
|
+
if isinstance(decimations, list):
|
|
378
|
+
decimations = np.array(decimations)
|
|
379
|
+
if isinstance(angles, list):
|
|
380
|
+
angles = np.array(angles)
|
|
388
381
|
|
|
389
382
|
angles = np.sort(angles)
|
|
390
383
|
decimations = np.sort(decimations)
|
|
391
384
|
|
|
392
385
|
num_elements = self.params.acoustic['num_elements']
|
|
393
|
-
|
|
386
|
+
Width = self.params.acoustic['element_width'] # en m
|
|
387
|
+
kerf = self.params.acoustic.get('kerf', 0.00000) # en m
|
|
388
|
+
Nactuators = self.params.acoustic['num_elements']
|
|
394
389
|
|
|
395
390
|
# --- Calcul du nombre de Scans ---
|
|
396
391
|
if 0 in decimations:
|
|
397
|
-
Nscans = 4 * angles
|
|
398
|
-
offSet = angles.shape[0]
|
|
392
|
+
Nscans = 4 * len(angles) * (len(decimations) - 1) + len(angles)
|
|
399
393
|
else:
|
|
400
|
-
Nscans = 4 * angles
|
|
401
|
-
offSet = 0
|
|
394
|
+
Nscans = 4 * len(angles) * len(decimations)
|
|
402
395
|
|
|
403
396
|
ActiveLIST = np.ones((num_elements, Nscans))
|
|
404
|
-
Xm = np.arange(1, num_elements + 1) * dx_mm
|
|
405
|
-
dFx = 1 / (num_elements * dx_mm)
|
|
406
397
|
|
|
407
|
-
#
|
|
398
|
+
# --- Calcul des positions centrées des éléments (en m) ---
|
|
399
|
+
Xc = (Width + (Nactuators - 1) * (kerf + Width)) / 2
|
|
400
|
+
Xm = np.array([Width * (i - 1) + Width / 2 - Xc for i in range(1, Nactuators + 1)])
|
|
401
|
+
|
|
402
|
+
# --- Gestion de l'onde plane (tous les piezo ON) au début ---
|
|
403
|
+
if 0 in decimations:
|
|
404
|
+
I_plane = np.arange(len(angles))
|
|
405
|
+
ActiveLIST[:, I_plane] = 1 # Tous les piezo ON pour les len(angles) premières colonnes
|
|
406
|
+
|
|
407
|
+
# --- Traitement des décimations non nulles ---
|
|
408
408
|
active_decimations = decimations[decimations != 0]
|
|
409
|
+
dFx = 1 / (Nactuators * Width) # fx de base (en m^-1)
|
|
409
410
|
|
|
410
411
|
for i_dec in range(len(active_decimations)):
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
Icos = idx_base
|
|
414
|
-
Incos = idx_base + len(angles)
|
|
415
|
-
Isin = idx_base + 2 * len(angles)
|
|
416
|
-
Insin = idx_base + 3 * len(angles)
|
|
412
|
+
# Décalage des indices pour placer les motifs modulés après l'onde plane
|
|
413
|
+
I = np.arange(len(angles)) + len(angles) + (i_dec * 4 * len(angles))
|
|
417
414
|
|
|
418
|
-
|
|
415
|
+
Icos = I
|
|
416
|
+
Incos = I + 1* len(angles)
|
|
417
|
+
Isin = I + 3 * len(angles)
|
|
418
|
+
Insin = I + 2 * len(angles)
|
|
419
419
|
|
|
420
|
-
|
|
421
|
-
valid_icos = Icos[Icos < Nscans]
|
|
422
|
-
if valid_icos.size > 0:
|
|
423
|
-
ActiveLIST[:, valid_icos] = calc_mat_os(Xm, fx, dx_mm, ActiveLIST[:, valid_icos], 'cos')
|
|
424
|
-
if (Incos < Nscans).any():
|
|
425
|
-
ActiveLIST[:, Incos[Incos < Nscans]] = 1 - ActiveLIST[:, valid_icos]
|
|
420
|
+
fx = dFx * active_decimations[i_dec]
|
|
426
421
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
422
|
+
# Appliquer les motifs modulés
|
|
423
|
+
ActiveLIST[:, Icos] = calc_mat_os(Xm, fx, ActiveLIST[:, Icos[:1]], 'cos')
|
|
424
|
+
ActiveLIST[:, Incos] = 1 - ActiveLIST[:, Icos]
|
|
425
|
+
ActiveLIST[:, Isin] = calc_mat_os(Xm, fx, ActiveLIST[:, Isin[:1]], 'sin')
|
|
426
|
+
ActiveLIST[:, Insin] = 1 - ActiveLIST[:, Isin]
|
|
432
427
|
|
|
433
428
|
# --- Conversion au format attendu ---
|
|
434
|
-
# 1. On convertit toute la matrice en liste de strings Hexa
|
|
435
429
|
hexa_list = convert_to_hex_list(ActiveLIST)
|
|
436
430
|
|
|
437
|
-
# 2. Fonction interne de formatage d'angle (pour coller à votre ancien code)
|
|
438
431
|
def format_angle(a):
|
|
439
432
|
return f"{'1' if a < 0 else '0'}{abs(a):02d}"
|
|
440
433
|
|
|
441
|
-
# 3. Construction de la liste de dictionnaires
|
|
442
434
|
patterns = []
|
|
443
435
|
print(f"Generating {Nscans} patterns from decimations and angles...")
|
|
444
436
|
for i in range(Nscans):
|
|
445
|
-
# On retrouve l'angle correspondant à l'index i
|
|
446
|
-
# La logique est cyclique sur la taille de 'angles'
|
|
447
437
|
angle_val = angles[i % len(angles)]
|
|
448
|
-
|
|
449
438
|
hex_pattern = hexa_list[i]
|
|
450
439
|
pair = f"{hex_pattern}_{format_angle(angle_val)}"
|
|
451
440
|
patterns.append({"fileName": pair})
|
|
452
441
|
|
|
453
442
|
return patterns
|
|
443
|
+
|
|
454
444
|
|
|
455
445
|
def _generate_patterns(self, N,angles = None):
|
|
456
446
|
def format_angle(a):
|
|
@@ -679,6 +669,7 @@ class Tomography(Experiment):
|
|
|
679
669
|
AcousticField.save_field(fieldDataPath)
|
|
680
670
|
listAcousticFields.append(AcousticField)
|
|
681
671
|
progress_bar.set_postfix_str("")
|
|
672
|
+
|
|
682
673
|
return listAcousticFields
|
|
683
674
|
|
|
684
675
|
def load_experimentalAO(self, pathAO, withTumor = True, h5name='AOsignal'):
|
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
from ._mainRecon import Recon
|
|
2
2
|
from .ReconEnums import ReconType, AnalyticType, ProcessType
|
|
3
3
|
from AOT_biomaps.AOT_Experiment.Tomography import hex_to_binary_profile
|
|
4
|
-
from .ReconTools import get_phase_deterministic
|
|
4
|
+
from .ReconTools import fourierz_gpu, get_phase_deterministic, add_sincos_cpu, EvalDelayLawOS_center, ifourierx_gpu, rotate_theta_gpu, filter_radon_gpu, ifourierz_gpu
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
from tqdm import trange
|
|
8
|
-
import
|
|
9
|
-
import tqdm
|
|
8
|
+
import cupy as cp
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
class AnalyticRecon(Recon):
|
|
13
|
-
def __init__(self, analyticType,
|
|
12
|
+
def __init__(self, analyticType, Lc = None,**kwargs):
|
|
14
13
|
super().__init__(**kwargs)
|
|
15
14
|
self.reconType = ReconType.Analytic
|
|
16
15
|
self.analyticType = analyticType
|
|
16
|
+
if self.analyticType == AnalyticType.iRADON and Lc is None:
|
|
17
|
+
raise ValueError("Lc parameter must be provided for iRADON analytic reconstruction.")
|
|
18
|
+
self.Lc = Lc # in meters
|
|
17
19
|
self.AOsignal_demoldulated = None
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
21
|
def parse_and_demodulate(self, withTumor=True):
|
|
22
22
|
|
|
23
23
|
if withTumor:
|
|
24
24
|
AOsignal = self.experiment.AOsignal_withTumor
|
|
25
25
|
else:
|
|
26
26
|
AOsignal = self.experiment.AOsignal_withoutTumor
|
|
27
|
-
delta_x = self.params.
|
|
28
|
-
n_piezos = self.params.acoustic['num_elements']
|
|
27
|
+
delta_x = self.experiment.params.general['dx'] # en m
|
|
28
|
+
n_piezos = self.experiment.params.acoustic['num_elements']
|
|
29
29
|
demodulated_data = {}
|
|
30
30
|
structured_buffer = {}
|
|
31
31
|
|
|
32
|
-
for i in
|
|
32
|
+
for i in trange(len(self.experiment.AcousticFields), desc="Demodulating AO signals"):
|
|
33
33
|
label = self.experiment.AcousticFields[i].getName_field()
|
|
34
34
|
|
|
35
35
|
parts = label.split("_")
|
|
@@ -46,7 +46,7 @@ class AnalyticRecon(Recon):
|
|
|
46
46
|
# Onde Plane (f_s = 0)
|
|
47
47
|
if set(hex_pattern.lower().replace(" ", "")) == {'f'}:
|
|
48
48
|
fs_key = 0.0 # fs_key est en mm^-1 (0.0 mm^-1)
|
|
49
|
-
demodulated_data[(fs_key, angle_rad)] = np.array(AOsignal[i])
|
|
49
|
+
demodulated_data[(fs_key, angle_rad)] = np.array(AOsignal[:,i])
|
|
50
50
|
continue
|
|
51
51
|
|
|
52
52
|
# Onde Structurée
|
|
@@ -77,9 +77,9 @@ class AnalyticRecon(Recon):
|
|
|
77
77
|
|
|
78
78
|
# La moyenne est nécessaire si plusieurs acquisitions ont la même phase (pour le SNR)
|
|
79
79
|
if phase in structured_buffer[key]:
|
|
80
|
-
structured_buffer[key][phase] = (structured_buffer[key][phase] + np.array(AOsignal[i])) / 2
|
|
80
|
+
structured_buffer[key][phase] = (structured_buffer[key][phase] + np.array(AOsignal[:,i])) / 2
|
|
81
81
|
else:
|
|
82
|
-
structured_buffer[key][phase] = np.array(AOsignal[i])
|
|
82
|
+
structured_buffer[key][phase] = np.array(AOsignal[:,i])
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
|
|
@@ -98,11 +98,10 @@ class AnalyticRecon(Recon):
|
|
|
98
98
|
|
|
99
99
|
real = s0 - s_pi
|
|
100
100
|
imag = s_pi_2 - s_3pi_2
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
|
|
103
102
|
demodulated_data[(fs, theta)] = (real - 1j * imag) / (2/np.pi)
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
|
|
104
|
+
return demodulated_data
|
|
106
105
|
|
|
107
106
|
def run(self, processType = ProcessType.PYTHON, withTumor= True):
|
|
108
107
|
"""
|
|
@@ -127,107 +126,295 @@ class AnalyticRecon(Recon):
|
|
|
127
126
|
Parameters:
|
|
128
127
|
analyticType: The type of analytic reconstruction to perform (default is iFOURIER).
|
|
129
128
|
"""
|
|
129
|
+
d_t = 1 / float(self.experiment.params.acoustic['f_saving'])
|
|
130
|
+
t_array = np.arange(0, self.experiment.AOsignal_withTumor.shape[0])*d_t
|
|
131
|
+
Z = t_array * self.experiment.params.acoustic['c0']
|
|
132
|
+
X_m = np.arange(0, self.experiment.params.acoustic['num_elements'])* self.experiment.params.general['dx']
|
|
133
|
+
dfX = 1 / (X_m[1] - X_m[0]) / len(X_m)
|
|
130
134
|
if withTumor:
|
|
135
|
+
self.AOsignal_demoldulated = self.parse_and_demodulate(withTumor=True)
|
|
131
136
|
if self.analyticType == AnalyticType.iFOURIER:
|
|
132
|
-
self.reconPhantom = self._iFourierRecon(
|
|
137
|
+
self.reconPhantom = self._iFourierRecon(
|
|
138
|
+
R = self.experiment.AOsignal_withTumor,
|
|
139
|
+
z = Z,
|
|
140
|
+
X_m=X_m,
|
|
141
|
+
theta=self.experiment.theta,
|
|
142
|
+
decimation=self.experiment.decimations,
|
|
143
|
+
c=self.experiment.params.acoustic['c0'],
|
|
144
|
+
DelayLAWS=self.experiment.DelayLaw,
|
|
145
|
+
ActiveLIST=self.experiment.ActiveList,
|
|
146
|
+
withTumor=True,
|
|
147
|
+
)
|
|
148
|
+
|
|
133
149
|
elif self.analyticType == AnalyticType.iRADON:
|
|
134
|
-
self.reconPhantom = self._iRadonRecon(
|
|
150
|
+
self.reconPhantom = self._iRadonRecon(
|
|
151
|
+
R=self.experiment.AOsignal_withTumor,
|
|
152
|
+
z=Z,
|
|
153
|
+
X_m=X_m,
|
|
154
|
+
theta=self.experiment.theta,
|
|
155
|
+
decimation=self.experiment.decimations,
|
|
156
|
+
df0x=dfX,
|
|
157
|
+
Lc =self.Lc,
|
|
158
|
+
c=self.experiment.params.acoustic['c0'],
|
|
159
|
+
DelayLAWS=self.experiment.DelayLaw,
|
|
160
|
+
ActiveLIST=self.experiment.ActiveList,
|
|
161
|
+
withTumor=True)
|
|
135
162
|
else:
|
|
136
163
|
raise ValueError(f"Unknown analytic type: {self.analyticType}")
|
|
137
164
|
else:
|
|
165
|
+
self.AOsignal_demoldulated = self.parse_and_demodulate(withTumor=False)
|
|
138
166
|
if self.analyticType == AnalyticType.iFOURIER:
|
|
139
|
-
self.reconLaser = self._iFourierRecon(
|
|
167
|
+
self.reconLaser = self._iFourierRecon(
|
|
168
|
+
R = self.experiment.AOsignal_withoutTumor,
|
|
169
|
+
z = Z,
|
|
170
|
+
X_m=X_m,
|
|
171
|
+
theta=self.experiment.theta,
|
|
172
|
+
decimation=self.experiment.decimations,
|
|
173
|
+
c=self.experiment.params.acoustic['c0'],
|
|
174
|
+
DelayLAWS=self.experiment.DelayLaw,
|
|
175
|
+
ActiveLIST=self.experiment.ActiveList,
|
|
176
|
+
withTumor=False,
|
|
177
|
+
)
|
|
140
178
|
elif self.analyticType == AnalyticType.iRADON:
|
|
141
|
-
self.reconLaser = self._iRadonRecon(
|
|
179
|
+
self.reconLaser = self._iRadonRecon(
|
|
180
|
+
R=self.experiment.AOsignal_withoutTumor,
|
|
181
|
+
z=Z,
|
|
182
|
+
X_m=X_m,
|
|
183
|
+
theta=self.experiment.theta,
|
|
184
|
+
decimation=self.experiment.decimations,
|
|
185
|
+
df0x=dfX,
|
|
186
|
+
Lc = self.Lc,
|
|
187
|
+
c=self.experiment.params.acoustic['c0'],
|
|
188
|
+
DelayLAWS=self.experiment.DelayLaw,
|
|
189
|
+
ActiveLIST=self.experiment.ActiveList,
|
|
190
|
+
withTumor=False)
|
|
142
191
|
else:
|
|
143
192
|
raise ValueError(f"Unknown analytic type: {self.analyticType}")
|
|
144
193
|
|
|
145
|
-
def _iFourierRecon(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
#
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
194
|
+
def _iFourierRecon(
|
|
195
|
+
self,
|
|
196
|
+
R,
|
|
197
|
+
z,
|
|
198
|
+
X_m,
|
|
199
|
+
theta,
|
|
200
|
+
decimation,
|
|
201
|
+
c,
|
|
202
|
+
DelayLAWS,
|
|
203
|
+
ActiveLIST,
|
|
204
|
+
withTumor,
|
|
205
|
+
):
|
|
206
|
+
# --- 1. Préparation des données ---
|
|
207
|
+
R = cp.asarray(R)
|
|
208
|
+
z = cp.asarray(z)
|
|
209
|
+
X_m = cp.asarray(X_m)
|
|
210
|
+
theta = cp.asarray(theta)
|
|
211
|
+
decimation = cp.asarray(decimation)
|
|
212
|
+
DelayLAWS = cp.asarray(DelayLAWS)
|
|
213
|
+
ActiveLIST = cp.asarray(ActiveLIST)
|
|
214
|
+
# Normalisation DelayLAWS (ms → s si besoin)
|
|
215
|
+
DelayLAWS_s = cp.where(cp.max(DelayLAWS) > 1e-3, DelayLAWS / 1000.0, DelayLAWS)
|
|
216
|
+
# Regroupement tirs (décimation, angle)
|
|
217
|
+
ScanParam_cpu = cp.asnumpy(
|
|
218
|
+
cp.stack([decimation, cp.round(theta, 4)], axis=1)
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
_, ia_cpu, ib_cpu = np.unique(
|
|
222
|
+
ScanParam_cpu, axis=0, return_index=True, return_inverse=True
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
ia = cp.asarray(ia_cpu)
|
|
226
|
+
ib = cp.asarray(ib_cpu)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# --- 2. Structuration complexe ---
|
|
230
|
+
# add_sincos_gpu doit être l'équivalent GPU de add_sincos_cpu
|
|
231
|
+
F_complex_cpu, theta_u_cpu, decim_u_cpu = add_sincos_cpu(
|
|
232
|
+
cp.asnumpy(R),
|
|
233
|
+
cp.asnumpy(decimation),
|
|
234
|
+
np.radians(cp.asnumpy(theta))
|
|
235
|
+
)
|
|
236
|
+
M0 = EvalDelayLawOS_center(
|
|
237
|
+
X_m,
|
|
238
|
+
theta_u_cpu,
|
|
239
|
+
DelayLAWS_s.T[:, ia],
|
|
240
|
+
ActiveLIST.T[:, ia],
|
|
241
|
+
c
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# Transfert GPU (UNE FOIS)
|
|
245
|
+
F_complex = cp.asarray(F_complex_cpu)
|
|
246
|
+
theta_u = cp.asarray(theta_u_cpu)
|
|
247
|
+
decim_u = cp.asarray(decim_u_cpu)
|
|
248
|
+
|
|
249
|
+
Nz = z.size
|
|
250
|
+
Nx = X_m.size
|
|
251
|
+
dx = X_m[1] - X_m[0]
|
|
252
|
+
|
|
253
|
+
X_grid, Z_grid = cp.meshgrid(X_m, z)
|
|
254
|
+
idx0_x = Nx // 2
|
|
255
|
+
# Angles uniques après compression
|
|
256
|
+
angles_group, ia_u, ib_u = cp.unique(
|
|
257
|
+
theta_u, return_index=True, return_inverse=True
|
|
258
|
+
)
|
|
259
|
+
Ntheta = angles_group.size
|
|
260
|
+
|
|
261
|
+
I_final = cp.zeros((Nz, Nx), dtype=cp.complex64)
|
|
262
|
+
|
|
263
|
+
# --- 3. Boucle InverseFourierX ---
|
|
264
|
+
for i_ang in trange(Ntheta, desc=f"AOT-BioMaps -- Analytic Reconstruction Tomography: iFourier ({'with tumor' if withTumor else 'without tumor'}) ---- processing on single GPU ----", unit="angle"):
|
|
265
|
+
|
|
266
|
+
# Grille (z, fx)
|
|
267
|
+
F_fx_z = cp.zeros((Nz, Nx), dtype=cp.complex64)
|
|
268
|
+
|
|
269
|
+
# Indices correspondant à cet angle
|
|
270
|
+
indices = cp.where(ib_u == i_ang)[0]
|
|
271
|
+
|
|
272
|
+
for idx in indices:
|
|
273
|
+
n = int(decim_u[idx])
|
|
274
|
+
trace_z = F_complex[:, idx]
|
|
275
|
+
|
|
276
|
+
# Mapping positif
|
|
277
|
+
ip = idx0_x + n
|
|
278
|
+
if 0 <= ip < Nx:
|
|
279
|
+
F_fx_z[:, ip] = trace_z
|
|
280
|
+
|
|
281
|
+
# Mapping négatif (symétrie hermitienne Matlab)
|
|
282
|
+
if n != 0:
|
|
283
|
+
im = idx0_x - n
|
|
284
|
+
if 0 <= im < Nx:
|
|
285
|
+
col_conj = cp.zeros(Nz, dtype=cp.complex64)
|
|
286
|
+
col_conj[1:] = cp.conj(trace_z[:-1])
|
|
287
|
+
F_fx_z[:, im] = col_conj
|
|
288
|
+
|
|
289
|
+
# Correction DC
|
|
290
|
+
F_fx_z[:, idx0_x] *= 0.5
|
|
291
|
+
|
|
292
|
+
# --- Inverse Fourier X (GPU) ---
|
|
293
|
+
I_spatial = ifourierx_gpu(F_fx_z, dx) * Nx
|
|
294
|
+
|
|
295
|
+
# --- Rotation spatiale GPU ---
|
|
296
|
+
I_rot = rotate_theta_gpu(
|
|
297
|
+
X_grid,
|
|
298
|
+
Z_grid,
|
|
299
|
+
I_spatial,
|
|
300
|
+
-angles_group[i_ang],
|
|
301
|
+
M0[i_ang, :]
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# Somme incohérente (OriginIm = sum)
|
|
305
|
+
I_final += I_rot
|
|
306
|
+
I_final /= Ntheta
|
|
307
|
+
return cp.real(I_final).get()
|
|
308
|
+
|
|
309
|
+
def _iRadonRecon(
|
|
310
|
+
self,
|
|
311
|
+
R,
|
|
312
|
+
z,
|
|
313
|
+
X_m,
|
|
314
|
+
theta,
|
|
315
|
+
decimation,
|
|
316
|
+
df0x,
|
|
317
|
+
Lc,
|
|
318
|
+
c,
|
|
319
|
+
DelayLAWS,
|
|
320
|
+
ActiveLIST,
|
|
321
|
+
withTumor,
|
|
322
|
+
):
|
|
184
323
|
"""
|
|
185
324
|
Reconstruction d'image utilisant la méthode iRadon.
|
|
186
325
|
|
|
187
326
|
:return: Image reconstruite.
|
|
188
327
|
"""
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
328
|
+
|
|
329
|
+
# ======================================================
|
|
330
|
+
# 1. AddSinCos (structuration)
|
|
331
|
+
# ======================================================
|
|
332
|
+
theta = np.radians(theta)
|
|
333
|
+
F_ct_kx, theta_u, decim_u = add_sincos_cpu(R, decimation, theta)
|
|
334
|
+
ScanParam = np.stack([decimation, theta], axis=1)
|
|
335
|
+
_, ia, _ = np.unique(ScanParam, axis=0, return_index=True, return_inverse=True)
|
|
336
|
+
ActiveLIST = np.asarray(ActiveLIST).T
|
|
337
|
+
DelayLAWS = np.asarray(DelayLAWS).T
|
|
338
|
+
ActiveLIST_unique = ActiveLIST[:,ia]
|
|
339
|
+
|
|
340
|
+
# ======================================================
|
|
341
|
+
# 2. FFT z
|
|
342
|
+
# ======================================================
|
|
343
|
+
z_gpu = cp.asarray(z)
|
|
344
|
+
Fin = fourierz_gpu(z, F_ct_kx)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
dz = float(z[1] - z[0])
|
|
348
|
+
fz = cp.fft.fftshift(cp.fft.fftfreq(len(z), d=dz))
|
|
349
|
+
|
|
350
|
+
Nz, Nk = Fin.shape
|
|
351
|
+
|
|
352
|
+
# ======================================================
|
|
353
|
+
# 3. Filtrage OS exact
|
|
354
|
+
# ======================================================
|
|
355
|
+
decim_gpu = cp.asarray(decim_u)
|
|
356
|
+
I0 = decim_gpu == 0
|
|
357
|
+
F0 = Fin * I0[None, :] # I0 broadcastée sur les lignes
|
|
358
|
+
|
|
359
|
+
DEC, FZ = cp.meshgrid(decim_gpu, fz)
|
|
360
|
+
|
|
361
|
+
Hinf = cp.abs(FZ) < cp.abs(DEC) * df0x
|
|
362
|
+
Hsup = FZ >= 0
|
|
363
|
+
|
|
364
|
+
Fc = 1 / Lc
|
|
365
|
+
FILTER = filter_radon_gpu(fz, Fc)[:, None]
|
|
366
|
+
|
|
367
|
+
Finf = F0 * FILTER[:, :F0.shape[1]] * Hinf[:, :F0.shape[1]]
|
|
368
|
+
Fsup = Fin * FILTER * Hsup
|
|
369
|
+
|
|
370
|
+
# ======================================================
|
|
371
|
+
# 4. Retour espace z
|
|
372
|
+
# ======================================================
|
|
373
|
+
Finf = ifourierz_gpu(z, Finf)
|
|
374
|
+
Fsup = ifourierz_gpu(z, Fsup)
|
|
375
|
+
|
|
376
|
+
# ======================================================
|
|
377
|
+
# 5. Grille image
|
|
378
|
+
# ======================================================
|
|
379
|
+
X_gpu = cp.asarray(X_m)
|
|
380
|
+
X, Z = cp.meshgrid(X_gpu, z_gpu)
|
|
381
|
+
Xc = float(np.mean(X_m))
|
|
382
|
+
|
|
383
|
+
# ======================================================
|
|
384
|
+
# 6. Calcul du centre M0 pour chaque angle
|
|
385
|
+
# ======================================================
|
|
386
|
+
M0 = EvalDelayLawOS_center(X_m,theta, DelayLAWS[:, ia], ActiveLIST_unique, c)
|
|
387
|
+
M0_gpu = cp.asarray(M0)
|
|
388
|
+
|
|
389
|
+
# ======================================================
|
|
390
|
+
# 7. Rétroprojection
|
|
391
|
+
# ======================================================
|
|
392
|
+
Irec_list = []
|
|
393
|
+
Irec = cp.zeros_like(X, dtype=cp.complex64)
|
|
394
|
+
for i in trange(len(theta_u), desc=f"AOT-BioMaps -- Analytic Reconstruction Tomography: iRadon ({'with tumor' if withTumor else 'without tumor'}) ---- processing on single GPU ----", unit="angle"):
|
|
395
|
+
th = float(theta_u[i])
|
|
396
|
+
|
|
397
|
+
# calcul de T, S, h0...
|
|
398
|
+
T = (X - M0_gpu[i,0]) * cp.sin(th) + (Z - M0_gpu[i,1]) * cp.cos(th) + M0_gpu[i,1]
|
|
399
|
+
S = (X - Xc) * cp.cos(th) - (Z - M0_gpu[i,1]) * cp.sin(th)
|
|
400
|
+
h0 = cp.exp(1j * 2*cp.pi * decim_u[i] * df0x * S)
|
|
401
|
+
|
|
402
|
+
# interpolation GPU
|
|
403
|
+
Tind = (T - z_gpu[0]) / (z_gpu[1] - z_gpu[0])
|
|
404
|
+
i0 = cp.floor(Tind).astype(cp.int32)
|
|
405
|
+
i1 = i0 + 1
|
|
406
|
+
i0 = cp.clip(i0, 0, Nz-1)
|
|
407
|
+
i1 = cp.clip(i1, 0, Nz-1)
|
|
408
|
+
w = Tind - i0
|
|
409
|
+
|
|
410
|
+
proj_sup = (1-w)*Fsup[i0, i] + w*Fsup[i1, i]
|
|
411
|
+
proj_inf = (1-w)*Finf[i0, i] + w*Finf[i1, i]
|
|
412
|
+
|
|
413
|
+
Irec += 2*h0*proj_sup + proj_inf
|
|
414
|
+
Irec /= i+1
|
|
415
|
+
# Ajouter à la liste
|
|
416
|
+
Irec_list.append(cp.real(Irec).get())
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
return Irec_list
|
|
420
|
+
|
|
@@ -7,6 +7,8 @@ from numba import njit, prange
|
|
|
7
7
|
from torch_sparse import coalesce
|
|
8
8
|
from scipy.signal.windows import hann
|
|
9
9
|
from itertools import groupby
|
|
10
|
+
import cupy as cp
|
|
11
|
+
from cupyx.scipy.ndimage import map_coordinates
|
|
10
12
|
|
|
11
13
|
def load_recon(hdr_path):
|
|
12
14
|
"""
|
|
@@ -528,4 +530,163 @@ def get_phase_deterministic(profile):
|
|
|
528
530
|
elif idx == 4 :
|
|
529
531
|
phase = 3*np.pi/2
|
|
530
532
|
|
|
531
|
-
return phase
|
|
533
|
+
return phase
|
|
534
|
+
|
|
535
|
+
def add_sincos_cpu(R, decimation, theta):
|
|
536
|
+
decimation = np.asarray(decimation)
|
|
537
|
+
theta = np.asarray(theta)
|
|
538
|
+
|
|
539
|
+
ScanParam = np.stack([decimation, theta], axis=1)
|
|
540
|
+
uniq, ia, ib = np.unique(ScanParam, axis=0, return_index=True, return_inverse=True)
|
|
541
|
+
|
|
542
|
+
theta_u = uniq[:,1]
|
|
543
|
+
decim_u = uniq[:,0]
|
|
544
|
+
|
|
545
|
+
theta0 = np.unique(theta_u)
|
|
546
|
+
N0 = len(theta0)
|
|
547
|
+
|
|
548
|
+
Rg = np.asarray(R)
|
|
549
|
+
Nz = Rg.shape[0]
|
|
550
|
+
Nk = N0 + (Rg.shape[1] - N0)//4
|
|
551
|
+
|
|
552
|
+
Iout = np.zeros((Nz, Nk), dtype=np.complex64)
|
|
553
|
+
# fx = 0 (onde plane)
|
|
554
|
+
Iout[:, :N0] = Rg[:, :N0]
|
|
555
|
+
|
|
556
|
+
k = N0
|
|
557
|
+
for i in range(N0, len(ia)):
|
|
558
|
+
idx = np.where(ib == i)[0]
|
|
559
|
+
h1, h2, h3, h4 = Rg[:, idx].T
|
|
560
|
+
Iout[:, k] = ((h1 - h2) - 1j*(h3 - h4)) / 2
|
|
561
|
+
k += 1
|
|
562
|
+
|
|
563
|
+
return Iout, theta_u, decim_u
|
|
564
|
+
|
|
565
|
+
def fourierz_gpu(z, X):
|
|
566
|
+
dz = float(z[1] - z[0])
|
|
567
|
+
Nz = X.shape[0]
|
|
568
|
+
|
|
569
|
+
return cp.fft.fftshift(
|
|
570
|
+
cp.fft.fft(
|
|
571
|
+
cp.fft.ifftshift(X, axes=0),
|
|
572
|
+
axis=0
|
|
573
|
+
),
|
|
574
|
+
axes=0
|
|
575
|
+
) * (Nz * dz)
|
|
576
|
+
|
|
577
|
+
def ifourierz_gpu(z, X):
|
|
578
|
+
dz = float(z[1] - z[0])
|
|
579
|
+
Nz = X.shape[0]
|
|
580
|
+
|
|
581
|
+
return cp.fft.ifftshift(
|
|
582
|
+
cp.fft.ifft(
|
|
583
|
+
cp.fft.fftshift(X, axes=0),
|
|
584
|
+
axis=0
|
|
585
|
+
),
|
|
586
|
+
axes=0
|
|
587
|
+
) * (1 / dz)
|
|
588
|
+
|
|
589
|
+
def ifourierx_gpu(F_fx_z, dx):
|
|
590
|
+
"""
|
|
591
|
+
Inverse Fourier along X (axis=1), Matlab-compatible
|
|
592
|
+
F_fx_z : (Nz, Nx) complex cupy array
|
|
593
|
+
dx : scalar (spacing in x)
|
|
594
|
+
"""
|
|
595
|
+
|
|
596
|
+
return (
|
|
597
|
+
cp.fft.ifftshift(
|
|
598
|
+
cp.fft.ifft(
|
|
599
|
+
cp.fft.fftshift(F_fx_z, axes=1),
|
|
600
|
+
axis=1
|
|
601
|
+
),
|
|
602
|
+
axes=1
|
|
603
|
+
) * (1.0 / dx)
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
def EvalDelayLawOS_center(X_m, theta, DelayLAWS, ActiveLIST, c):
|
|
607
|
+
"""
|
|
608
|
+
Retourne le centre de rotation C pour chaque angle
|
|
609
|
+
X_m : positions des éléments de la sonde
|
|
610
|
+
DelayLAWS : delays en secondes (chaque colonne = angle, chaque ligne = élément)
|
|
611
|
+
ActiveLIST : masque des éléments actifs (1 = actif)
|
|
612
|
+
c : vitesse du son
|
|
613
|
+
"""
|
|
614
|
+
Nangle = DelayLAWS.shape[1]
|
|
615
|
+
C = np.zeros((Nangle, 2))
|
|
616
|
+
|
|
617
|
+
ct = DelayLAWS * c # convert seconds to distance
|
|
618
|
+
|
|
619
|
+
for i in range(Nangle):
|
|
620
|
+
active_idx = np.where(ActiveLIST[:, i] == 1)[0]
|
|
621
|
+
if len(active_idx) == 0:
|
|
622
|
+
continue
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
angle_i = np.round(theta[i], 5)
|
|
626
|
+
# unit vector orthogonal to wavefront
|
|
627
|
+
u = np.array([np.sin(angle_i), np.cos(angle_i)])
|
|
628
|
+
|
|
629
|
+
# initial positions X0, Z0
|
|
630
|
+
X0 = X_m - u[0] * ct[:, i]
|
|
631
|
+
Z0 = 0 - u[1] * ct[:, i]
|
|
632
|
+
|
|
633
|
+
if Z0[-1] - Z0[0] != 0:
|
|
634
|
+
C[i, 0] = (Z0[-1]*X0[0] - Z0[0]*X0[-1]) / (Z0[-1] - Z0[0])
|
|
635
|
+
C[i, 1] = 0
|
|
636
|
+
|
|
637
|
+
return C
|
|
638
|
+
|
|
639
|
+
def rotate_theta_gpu(X, Z, Iin, theta, C):
|
|
640
|
+
"""
|
|
641
|
+
GPU equivalent of RotateTheta.m
|
|
642
|
+
X, Z, Iin : cupy arrays (Nz, Nx)
|
|
643
|
+
theta : scalar (float)
|
|
644
|
+
C : (2,) array-like
|
|
645
|
+
"""
|
|
646
|
+
|
|
647
|
+
# --- Translation ---
|
|
648
|
+
X_rel = X - C[0]
|
|
649
|
+
Z_rel = Z - C[1]
|
|
650
|
+
|
|
651
|
+
c = cp.cos(theta)
|
|
652
|
+
s = cp.sin(theta)
|
|
653
|
+
|
|
654
|
+
# --- Rotation (Matlab convention) ---
|
|
655
|
+
Xout = c * X_rel + s * Z_rel
|
|
656
|
+
Zout = -s * X_rel + c * Z_rel
|
|
657
|
+
|
|
658
|
+
# Back to original frame
|
|
659
|
+
Xout += C[0]
|
|
660
|
+
Zout += C[1]
|
|
661
|
+
|
|
662
|
+
# --- Conversion coordonnées -> indices ---
|
|
663
|
+
# Grille régulière supposée
|
|
664
|
+
dx = X[0, 1] - X[0, 0]
|
|
665
|
+
dz = Z[1, 0] - Z[0, 0]
|
|
666
|
+
|
|
667
|
+
x0 = X[0, 0]
|
|
668
|
+
z0 = Z[0, 0]
|
|
669
|
+
|
|
670
|
+
ix = (Xout - x0) / dx
|
|
671
|
+
iz = (Zout - z0) / dz
|
|
672
|
+
|
|
673
|
+
# --- Interpolation bilinéaire GPU ---
|
|
674
|
+
# map_coordinates attend (ndim, Npoints)
|
|
675
|
+
coords = cp.stack([iz.ravel(), ix.ravel()])
|
|
676
|
+
|
|
677
|
+
Iout = map_coordinates(
|
|
678
|
+
Iin,
|
|
679
|
+
coords,
|
|
680
|
+
order=1, # bilinear
|
|
681
|
+
mode='constant',
|
|
682
|
+
cval=0.0
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
return Iout.reshape(Iin.shape)
|
|
686
|
+
|
|
687
|
+
def filter_radon_gpu(fz, Fc):
|
|
688
|
+
FILTER = cp.abs(fz)
|
|
689
|
+
FILTER = cp.where(cp.abs(fz) > Fc, 0, FILTER)
|
|
690
|
+
FILTER *= cp.exp(-2 * cp.abs(fz / Fc)**10)
|
|
691
|
+
return FILTER
|
|
692
|
+
|
|
@@ -169,7 +169,9 @@ class Recon(ABC):
|
|
|
169
169
|
|
|
170
170
|
def show(self, withTumor=True, savePath=None):
|
|
171
171
|
if withTumor:
|
|
172
|
-
if self.reconPhantom is None
|
|
172
|
+
if self.reconPhantom is None:
|
|
173
|
+
raise ValueError("Reconstructed phantom with tumor is empty. Run reconstruction first.")
|
|
174
|
+
if isinstance(self.reconPhantom, (list, tuple)) and len(self.reconPhantom) == 0:
|
|
173
175
|
raise ValueError("Reconstructed phantom with tumor is empty. Run reconstruction first.")
|
|
174
176
|
if isinstance(self.reconPhantom, list):
|
|
175
177
|
image = self.reconPhantom[-1]
|
|
@@ -217,7 +219,9 @@ class Recon(ABC):
|
|
|
217
219
|
axs[0].tick_params(axis='both', which='major', labelsize=8)
|
|
218
220
|
axs[0].tick_params(axis='y', which='both', left=False, right=False, labelleft=False)
|
|
219
221
|
else:
|
|
220
|
-
if self.reconLaser is None
|
|
222
|
+
if self.reconLaser is None:
|
|
223
|
+
raise ValueError("Reconstructed laser without tumor is empty. Run reconstruction first.")
|
|
224
|
+
if isinstance(self.reconLaser, (list, tuple)) and len(self.reconLaser) == 0:
|
|
221
225
|
raise ValueError("Reconstructed laser without tumor is empty. Run reconstruction first.")
|
|
222
226
|
if isinstance(self.reconLaser, list):
|
|
223
227
|
image = self.reconLaser[-1]
|
AOT_biomaps/__init__.py
CHANGED
|
@@ -85,7 +85,7 @@ from .AOT_Recon.AOT_PotentialFunctions.RelativeDifferences import *
|
|
|
85
85
|
from .Config import config
|
|
86
86
|
from .Settings import *
|
|
87
87
|
|
|
88
|
-
__version__ = '2.9.
|
|
88
|
+
__version__ = '2.9.356'
|
|
89
89
|
__process__ = config.get_process()
|
|
90
90
|
|
|
91
91
|
def initialize(process=None):
|
|
@@ -177,6 +177,29 @@ def initialize(process=None):
|
|
|
177
177
|
|
|
178
178
|
|
|
179
179
|
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
180
203
|
|
|
181
204
|
|
|
182
205
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
AOT_biomaps/Config.py,sha256=ghEOP1n8aO1pR-su13wMeAZAxZRfry5hH67NbtZ8SqI,3614
|
|
2
2
|
AOT_biomaps/Settings.py,sha256=v8fPhnvvcfBJP29m1RLOTEr3jndGLGwbUiORXmsj2Bo,2853
|
|
3
|
-
AOT_biomaps/__init__.py,sha256=
|
|
3
|
+
AOT_biomaps/__init__.py,sha256=prD0ZZNE6dc-NWf_N0QIPLTrsRD3fAtK78ptZscj3Yo,4374
|
|
4
4
|
AOT_biomaps/AOT_Acoustic/AcousticEnums.py,sha256=s5kXa6jKzbS4btwbubrVcynLOr0yg5tth5vL_FGfbMk,1802
|
|
5
5
|
AOT_biomaps/AOT_Acoustic/AcousticTools.py,sha256=h2sCtGVcDtyLtEF1q7sLZmuWivWmesVGUBPnW-ndQqc,7535
|
|
6
6
|
AOT_biomaps/AOT_Acoustic/FocusedWave.py,sha256=3kGKKDx_3Msy5COYqIwzROPORGWvNjw8UsDanBfkMXE,11037
|
|
@@ -9,9 +9,9 @@ AOT_biomaps/AOT_Acoustic/PlaneWave.py,sha256=xza-rj5AUWDecLkGDxRcULrwZVWeBvGnEP2
|
|
|
9
9
|
AOT_biomaps/AOT_Acoustic/StructuredWave.py,sha256=jTLVlOhYLWJb5MxZPxhq3OFVlz2McoyMPBmfLvnekDU,18209
|
|
10
10
|
AOT_biomaps/AOT_Acoustic/__init__.py,sha256=t9M2rRqa_L9pk7W2FeELTkHEMuP4DBr4gBRldMqsQbg,491
|
|
11
11
|
AOT_biomaps/AOT_Acoustic/_mainAcoustic.py,sha256=RdmhRF1i0KAlpsP7_wnZ7F4J27br3eUc4XR91Qq7C64,44158
|
|
12
|
-
AOT_biomaps/AOT_Experiment/ExperimentTools.py,sha256=
|
|
12
|
+
AOT_biomaps/AOT_Experiment/ExperimentTools.py,sha256=aFvJw6J_jfFVTDFnG7J3a61SHEgORdZKZS0UI82VMaY,2637
|
|
13
13
|
AOT_biomaps/AOT_Experiment/Focus.py,sha256=B2nBawmv-NG2AWJx9zgQ8GlN6aFB9FwTSqX-M-phKXg,3193
|
|
14
|
-
AOT_biomaps/AOT_Experiment/Tomography.py,sha256=
|
|
14
|
+
AOT_biomaps/AOT_Experiment/Tomography.py,sha256=GGbXVUnRhvSRLL1QEg5ZgAhtWIg9RfH7aADJ5u-78HQ,36383
|
|
15
15
|
AOT_biomaps/AOT_Experiment/__init__.py,sha256=H9zMLeBLA6uhbaHohAa-2u5mDDxqJi8oE5c6tShdQp8,308
|
|
16
16
|
AOT_biomaps/AOT_Experiment/_mainExperiment.py,sha256=zSfuNrsz7nhiKrGIdK6CAXjlI2T6qYC5-JXHFgPNzhc,24674
|
|
17
17
|
AOT_biomaps/AOT_Optic/Absorber.py,sha256=jEodzRy7gkEH-wbazVasRQiri0dU16BfapmR-qnTSvM,867
|
|
@@ -21,14 +21,14 @@ AOT_biomaps/AOT_Optic/__init__.py,sha256=HSUVhfz0NzwHHZZ9KP9Xyfu33IgP_rYJX86J-gE
|
|
|
21
21
|
AOT_biomaps/AOT_Optic/_mainOptic.py,sha256=Wk63CcgWbU-ygMfjNK80islaUbGGJpTXgZY3_C2KQNY,8179
|
|
22
22
|
AOT_biomaps/AOT_Recon/AOT_biomaps_kernels.cubin,sha256=JWy-bdtBTZdnNlDbJGZKwXyF-2u1wICtmlOC_YxEL6o,82528
|
|
23
23
|
AOT_biomaps/AOT_Recon/AlgebraicRecon.py,sha256=CGBXZyYEZ3TOTFOKSt-h7NGuFbuI9PNr3YTWTbSLxDo,46832
|
|
24
|
-
AOT_biomaps/AOT_Recon/AnalyticRecon.py,sha256=
|
|
24
|
+
AOT_biomaps/AOT_Recon/AnalyticRecon.py,sha256=uZp4Va9z04dG7neZFGAmmAUygs8JyfrDuOr1_lKIERc,16651
|
|
25
25
|
AOT_biomaps/AOT_Recon/BayesianRecon.py,sha256=RnnPa-tTcvirwiNPnCRZnSM4NWeEEltYET-piBbp34g,12671
|
|
26
26
|
AOT_biomaps/AOT_Recon/DeepLearningRecon.py,sha256=RfVcEsi4GeGqJn0_SPxwQPQx6IQjin79WKh2UarMRLI,1383
|
|
27
27
|
AOT_biomaps/AOT_Recon/PrimalDualRecon.py,sha256=JbFhxiyUoSTnlJgHbOWIfUUwhwfZoi39RJMnfkagegY,16504
|
|
28
28
|
AOT_biomaps/AOT_Recon/ReconEnums.py,sha256=KAf55RqHAr2ilt6pxFrUBGQOn-7HA8NP6TyL-1FNiXo,19714
|
|
29
|
-
AOT_biomaps/AOT_Recon/ReconTools.py,sha256
|
|
29
|
+
AOT_biomaps/AOT_Recon/ReconTools.py,sha256=CV2BwdEwvNd3B02G5LYoKsRGlONwIupuv617S2AOWZE,25322
|
|
30
30
|
AOT_biomaps/AOT_Recon/__init__.py,sha256=xs_argJqXKFl76xP7-jiUc1ynOEEtY7XZ0gDxD5uVZc,246
|
|
31
|
-
AOT_biomaps/AOT_Recon/_mainRecon.py,sha256=
|
|
31
|
+
AOT_biomaps/AOT_Recon/_mainRecon.py,sha256=MvDnfsiJ7v-UmtCFmA1vmKGzV9zd2xdnt27CAWBubko,13470
|
|
32
32
|
AOT_biomaps/AOT_Recon/AOT_Optimizers/DEPIERRO.py,sha256=qA1n722GLQJH3V8HcLr5q_GxEwBS_NRlIT3E6JZk-Ag,9479
|
|
33
33
|
AOT_biomaps/AOT_Recon/AOT_Optimizers/LS.py,sha256=bCu1rKzFXPbYQ7jV3L3E_jVQpb6LIEC5MIlN1-mCNdY,22814
|
|
34
34
|
AOT_biomaps/AOT_Recon/AOT_Optimizers/MAPEM.py,sha256=vQLCB0L4FSXJKn2_6kdIdWrI6WZ82KuqUh7CSqBGVuo,25766
|
|
@@ -42,7 +42,7 @@ AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/__init__.py,sha256=RwrJdLOFbAFBFnRx
|
|
|
42
42
|
AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_CSR.py,sha256=RACc2P5oxmp0uPLAGnNj9mEtAxa_OlepNgCawKij3jI,12062
|
|
43
43
|
AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_SELL.py,sha256=ti3dZQsb_Uu62C7Bn65Z-yf-R5NKCFsmnBT5GlLd_HY,15138
|
|
44
44
|
AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/__init__.py,sha256=8nou-hqjQjuCTLhoL5qv4EM_lMPFviAZAZKSPhi84jE,67
|
|
45
|
-
aot_biomaps-2.9.
|
|
46
|
-
aot_biomaps-2.9.
|
|
47
|
-
aot_biomaps-2.9.
|
|
48
|
-
aot_biomaps-2.9.
|
|
45
|
+
aot_biomaps-2.9.356.dist-info/METADATA,sha256=eiJaF8-8bnGyUaK14BMcAiv645TibZb5uk8thsji2bE,700
|
|
46
|
+
aot_biomaps-2.9.356.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
47
|
+
aot_biomaps-2.9.356.dist-info/top_level.txt,sha256=6STF-lT4kaAnBHJYCripmN5mZABoHjMuY689JdiDphk,12
|
|
48
|
+
aot_biomaps-2.9.356.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|