AOT-biomaps 2.9.312__tar.gz → 2.9.332__tar.gz
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-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Acoustic/AcousticTools.py +0 -2
- aot_biomaps-2.9.332/AOT_biomaps/AOT_Experiment/ExperimentTools.py +79 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Experiment/Tomography.py +254 -9
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_Optimizers/MLEM.py +2 -1
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AnalyticRecon.py +93 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/ReconTools.py +46 -4
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/__init__.py +21 -1
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps.egg-info/PKG-INFO +1 -1
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps.egg-info/SOURCES.txt +1 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/PKG-INFO +1 -1
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/setup.py +21 -1
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Acoustic/AcousticEnums.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Acoustic/FocusedWave.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Acoustic/IrregularWave.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Acoustic/PlaneWave.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Acoustic/StructuredWave.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Acoustic/__init__.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Acoustic/_mainAcoustic.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Experiment/Focus.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Experiment/__init__.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Experiment/_mainExperiment.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Optic/Absorber.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Optic/Laser.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Optic/OpticEnums.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Optic/__init__.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Optic/_mainOptic.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_Optimizers/DEPIERRO.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_Optimizers/LS.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_Optimizers/MAPEM.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_Optimizers/PDHG.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_Optimizers/__init__.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/Huber.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/Quadratic.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/RelativeDifferences.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/__init__.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_CSR.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/SparseSMatrix_SELL.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/__init__.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_biomaps_kernels.cubin +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AlgebraicRecon.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/BayesianRecon.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/DeepLearningRecon.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/PrimalDualRecon.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/ReconEnums.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/__init__.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/_mainRecon.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/Config.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/Settings.py +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps.egg-info/dependency_links.txt +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps.egg-info/requires.txt +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps.egg-info/top_level.txt +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/README.md +0 -0
- {aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/setup.cfg +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
def calc_mat_os(xm, fx, dx, bool_active_list, signal_type):
|
|
4
|
+
num_els = len(xm)
|
|
5
|
+
|
|
6
|
+
# Cas limite : Fréquence nulle (Décimation 0)
|
|
7
|
+
if fx == 0:
|
|
8
|
+
if signal_type == 'cos':
|
|
9
|
+
# cos(0) = 1 -> Tout est actif
|
|
10
|
+
mask = np.ones(num_els, dtype=bool)
|
|
11
|
+
else:
|
|
12
|
+
# sin(0) = 0 -> Tout est inactif
|
|
13
|
+
mask = np.zeros(num_els, dtype=bool)
|
|
14
|
+
else:
|
|
15
|
+
# Calcul normal pour fx > 0
|
|
16
|
+
half_period_elements = round(1 / (2 * fx * dx))
|
|
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]))
|
|
30
|
+
|
|
31
|
+
def convert_to_hex_list(matrix):
|
|
32
|
+
"""
|
|
33
|
+
Convertit une matrice binaire en liste de strings hexa (paquets de 4 bits).
|
|
34
|
+
Chaque colonne devient une chaîne de caractères.
|
|
35
|
+
"""
|
|
36
|
+
n_els, n_scans = matrix.shape
|
|
37
|
+
|
|
38
|
+
# 1. Padding pour s'assurer que n_els est multiple de 4
|
|
39
|
+
remainder = n_els % 4
|
|
40
|
+
if remainder != 0:
|
|
41
|
+
padding = np.zeros((4 - remainder, n_scans))
|
|
42
|
+
matrix = np.vstack([matrix, padding])
|
|
43
|
+
|
|
44
|
+
# 2. Reshape pour isoler des blocs de 4 bits (nibbles)
|
|
45
|
+
# Shape résultante : (Nombre de blocs, 4 bits, Nombre de scans)
|
|
46
|
+
blocks = matrix.reshape(-1, 4, n_scans)
|
|
47
|
+
|
|
48
|
+
# 3. Calcul de la valeur décimale de chaque bloc (0 à 15)
|
|
49
|
+
# On considère le premier élément comme le bit de poids faible (LSB)
|
|
50
|
+
weights = np.array([1, 2, 4, 8]).reshape(1, 4, 1)
|
|
51
|
+
dec_values = np.sum(blocks * weights, axis=1).astype(int)
|
|
52
|
+
|
|
53
|
+
# 4. Conversion en caractères Hexadécimaux
|
|
54
|
+
# On définit la table de conversion pour la rapidité
|
|
55
|
+
hex_table = np.array(list("0123456789abcdef"))
|
|
56
|
+
hex_matrix = hex_table[dec_values]
|
|
57
|
+
|
|
58
|
+
# 5. Assemblage des chaînes (de l'élément N vers 0 pour l'ordre Shift Register standard)
|
|
59
|
+
return ["".join(hex_matrix[::-1, col]) for col in range(n_scans)]
|
|
60
|
+
|
|
61
|
+
def hex_to_binary_profile(hex_string, n_piezos=192):
|
|
62
|
+
hex_string = hex_string.strip().replace(" ", "").replace("\n", "")
|
|
63
|
+
if set(hex_string.lower()) == {'f'}:
|
|
64
|
+
return np.ones(n_piezos, dtype=int)
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
n_char = len(hex_string)
|
|
68
|
+
n_bits = n_char * 4
|
|
69
|
+
binary_str = bin(int(hex_string, 16))[2:].zfill(n_bits)
|
|
70
|
+
if len(binary_str) < n_piezos:
|
|
71
|
+
# Tronquer/padder en fonction de la taille réelle de la sonde
|
|
72
|
+
binary_str = binary_str.ljust(n_piezos, '0')
|
|
73
|
+
elif len(binary_str) > n_piezos:
|
|
74
|
+
binary_str = binary_str[:n_piezos]
|
|
75
|
+
return np.array([int(b) for b in binary_str])
|
|
76
|
+
except ValueError:
|
|
77
|
+
return np.zeros(n_piezos, dtype=int)
|
|
78
|
+
|
|
79
|
+
|
|
@@ -2,18 +2,43 @@ from ._mainExperiment import Experiment
|
|
|
2
2
|
from AOT_biomaps.AOT_Acoustic.AcousticEnums import WaveType
|
|
3
3
|
from AOT_biomaps.AOT_Acoustic.StructuredWave import StructuredWave
|
|
4
4
|
from AOT_biomaps.Config import config
|
|
5
|
+
from AOT_biomaps.AOT_Experiment.ExperimentTools import calc_mat_os, convert_to_hex_list, hex_to_binary_profile
|
|
5
6
|
import os
|
|
6
7
|
import psutil
|
|
7
8
|
import numpy as np
|
|
8
9
|
import matplotlib.pyplot as plt
|
|
9
10
|
from tqdm import trange
|
|
10
11
|
import h5py
|
|
11
|
-
from scipy.io import loadmat
|
|
12
|
+
from scipy.io import loadmat, savemat
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
class Tomography(Experiment):
|
|
14
16
|
def __init__(self, **kwargs):
|
|
15
17
|
super().__init__(**kwargs)
|
|
16
18
|
self.patterns = None
|
|
19
|
+
self.theta = []
|
|
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']))))
|
|
17
42
|
|
|
18
43
|
# PUBLIC METHODS
|
|
19
44
|
def check(self):
|
|
@@ -273,7 +298,7 @@ class Tomography(Experiment):
|
|
|
273
298
|
line = f"({coords}, {angles})\n"
|
|
274
299
|
file.write(line)
|
|
275
300
|
|
|
276
|
-
def generateActiveList(self, N):
|
|
301
|
+
def generateActiveList(self, N = None, decimations = None, angles = None):
|
|
277
302
|
"""
|
|
278
303
|
Génère une liste de patterns d'activation équilibrés et réguliers.
|
|
279
304
|
Args:
|
|
@@ -281,13 +306,153 @@ class Tomography(Experiment):
|
|
|
281
306
|
Returns:
|
|
282
307
|
list: Liste de strings au format "hex_angle".
|
|
283
308
|
"""
|
|
284
|
-
if
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
309
|
+
if decimations is not None and angles is not None:
|
|
310
|
+
self.patterns = self._genereate_patterns_from_decimations(decimations, angles)
|
|
311
|
+
elif N is not None and N > 1:
|
|
312
|
+
self.patterns = self._generate_patterns(N)
|
|
313
|
+
if not self._check_patterns(self.patterns):
|
|
314
|
+
raise ValueError("Generated patterns failed validation.")
|
|
315
|
+
else:
|
|
316
|
+
raise ValueError("Either N (>=2) or both decimations and angles must be provided for pattern generation.")
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
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
|
+
|
|
333
|
+
savemat(filePath, {'data': self.AOsignal_withTumor, 'thetas': self.theta, 'decimations': self.decimations, 'ActiveList' : ActiveList, 'DelayLaw': DelayLaw})
|
|
334
|
+
|
|
335
|
+
def selectAngles(self, angles):
|
|
336
|
+
|
|
337
|
+
if self.AOsignal_withTumor is None and self.AOsignal_withoutTumor is None:
|
|
338
|
+
raise ValueError("AO signals are not initialized. Please load or generate the AO signals first.")
|
|
339
|
+
if self.AcousticFields is None or len(self.AcousticFields) == 0:
|
|
340
|
+
raise ValueError("AcousticFields is not initialized. Please generate the system matrix first.")
|
|
341
|
+
newAcousticFields = []
|
|
342
|
+
index = []
|
|
343
|
+
for i,field in enumerate(self.AcousticFields):
|
|
344
|
+
if field.angle in angles:
|
|
345
|
+
newAcousticFields.append(field)
|
|
346
|
+
index.append(i)
|
|
347
|
+
if self.AOsignal_withTumor is not None:
|
|
348
|
+
self.AOsignal_withTumor = self.AOsignal_withTumor[:, index]
|
|
349
|
+
if self.AOsignal_withoutTumor is not None:
|
|
350
|
+
self.AOsignal_withoutTumor = self.AOsignal_withoutTumor[:, index]
|
|
351
|
+
self.AcousticFields = newAcousticFields
|
|
352
|
+
|
|
353
|
+
def selectPatterns(self, pattern_names):
|
|
354
|
+
if self.AOsignal_withTumor is None and self.AOsignal_withoutTumor is None:
|
|
355
|
+
raise ValueError("AO signals are not initialized. Please load or generate the AO signals first.")
|
|
356
|
+
if self.AcousticFields is None or len(self.AcousticFields) == 0:
|
|
357
|
+
raise ValueError("AcousticFields is not initialized. Please generate the system matrix first.")
|
|
358
|
+
newAcousticFields = []
|
|
359
|
+
index = []
|
|
360
|
+
for i,field in enumerate(self.AcousticFields):
|
|
361
|
+
if field.pattern.activeList in pattern_names:
|
|
362
|
+
newAcousticFields.append(field)
|
|
363
|
+
index.append(i)
|
|
364
|
+
if self.AOsignal_withTumor is not None:
|
|
365
|
+
self.AOsignal_withTumor = self.AOsignal_withTumor[:, index]
|
|
366
|
+
if self.AOsignal_withoutTumor is not None:
|
|
367
|
+
self.AOsignal_withoutTumor = self.AOsignal_withoutTumor[:, index]
|
|
368
|
+
self.AcousticFields = newAcousticFields
|
|
369
|
+
|
|
370
|
+
def selectRandom(self,N):
|
|
371
|
+
if self.AOsignal_withTumor is None and self.AOsignal_withoutTumor is None:
|
|
372
|
+
raise ValueError("AO signals are not initialized. Please load or generate the AO signals first.")
|
|
373
|
+
if self.AcousticFields is None or len(self.AcousticFields) == 0:
|
|
374
|
+
raise ValueError("AcousticFields is not initialized. Please generate the system matrix first.")
|
|
375
|
+
if N > len(self.AcousticFields):
|
|
376
|
+
raise ValueError("N is larger than the number of available AcousticFields.")
|
|
377
|
+
indices = np.random.choice(len(self.AcousticFields), size=N, replace=False)
|
|
378
|
+
newAcousticFields = [self.AcousticFields[i] for i in indices]
|
|
379
|
+
if self.AOsignal_withTumor is not None:
|
|
380
|
+
self.AOsignal_withTumor = self.AOsignal_withTumor[:, indices]
|
|
381
|
+
if self.AOsignal_withoutTumor is not None:
|
|
382
|
+
self.AOsignal_withoutTumor = self.AOsignal_withoutTumor[:, indices]
|
|
383
|
+
self.AcousticFields = newAcousticFields
|
|
384
|
+
|
|
385
|
+
def _genereate_patterns_from_decimations(self, decimations, angles):
|
|
386
|
+
if isinstance(decimations, list): decimations = np.array(decimations)
|
|
387
|
+
if isinstance(angles, list): angles = np.array(angles)
|
|
388
|
+
|
|
389
|
+
angles = np.sort(angles)
|
|
390
|
+
decimations = np.sort(decimations)
|
|
391
|
+
|
|
392
|
+
num_elements = self.params.acoustic['num_elements']
|
|
393
|
+
dx_mm = self.params.general['dx'] * 1e3
|
|
394
|
+
|
|
395
|
+
# --- Calcul du nombre de Scans ---
|
|
396
|
+
if 0 in decimations:
|
|
397
|
+
Nscans = 4 * angles.shape[0] * (decimations.shape[0] - 1) + angles.shape[0]
|
|
398
|
+
offSet = angles.shape[0]
|
|
399
|
+
else:
|
|
400
|
+
Nscans = 4 * angles.shape[0] * decimations.shape[0]
|
|
401
|
+
offSet = 0
|
|
402
|
+
|
|
403
|
+
ActiveLIST = np.ones((num_elements, Nscans))
|
|
404
|
+
Xm = np.arange(1, num_elements + 1) * dx_mm
|
|
405
|
+
dFx = 1 / (num_elements * dx_mm)
|
|
406
|
+
|
|
407
|
+
# On traite séparément les décimations non nulles pour la boucle
|
|
408
|
+
active_decimations = decimations[decimations != 0]
|
|
409
|
+
|
|
410
|
+
for i_dec in range(len(active_decimations)):
|
|
411
|
+
idx_base = (np.arange(len(angles))) + (i_dec * 4 * len(angles)) + offSet
|
|
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)
|
|
417
|
+
|
|
418
|
+
fx = dFx * active_decimations[i_dec]
|
|
419
|
+
|
|
420
|
+
# Remplissage des 4 phases
|
|
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]
|
|
426
|
+
|
|
427
|
+
valid_isin = Isin[Isin < Nscans]
|
|
428
|
+
if valid_isin.size > 0:
|
|
429
|
+
ActiveLIST[:, valid_isin] = calc_mat_os(Xm, fx, dx_mm, ActiveLIST[:, valid_isin], 'sin')
|
|
430
|
+
if (Insin < Nscans).any():
|
|
431
|
+
ActiveLIST[:, Insin[Insin < Nscans]] = 1 - ActiveLIST[:, valid_isin]
|
|
432
|
+
|
|
433
|
+
# --- Conversion au format attendu ---
|
|
434
|
+
# 1. On convertit toute la matrice en liste de strings Hexa
|
|
435
|
+
hexa_list = convert_to_hex_list(ActiveLIST)
|
|
436
|
+
|
|
437
|
+
# 2. Fonction interne de formatage d'angle (pour coller à votre ancien code)
|
|
438
|
+
def format_angle(a):
|
|
439
|
+
return f"{'1' if a < 0 else '0'}{abs(a):02d}"
|
|
440
|
+
|
|
441
|
+
# 3. Construction de la liste de dictionnaires
|
|
442
|
+
patterns = []
|
|
443
|
+
print(f"Generating {Nscans} patterns from decimations and angles...")
|
|
444
|
+
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
|
+
angle_val = angles[i % len(angles)]
|
|
448
|
+
|
|
449
|
+
hex_pattern = hexa_list[i]
|
|
450
|
+
pair = f"{hex_pattern}_{format_angle(angle_val)}"
|
|
451
|
+
patterns.append({"fileName": pair})
|
|
289
452
|
|
|
290
|
-
|
|
453
|
+
return patterns
|
|
454
|
+
|
|
455
|
+
def _generate_patterns(self, N,angles = None):
|
|
291
456
|
def format_angle(a):
|
|
292
457
|
return f"{'1' if a < 0 else '0'}{abs(a):02d}"
|
|
293
458
|
|
|
@@ -298,7 +463,13 @@ class Tomography(Experiment):
|
|
|
298
463
|
return hex_string
|
|
299
464
|
|
|
300
465
|
num_elements = self.params.acoustic['num_elements']
|
|
301
|
-
|
|
466
|
+
if angles is None:
|
|
467
|
+
angle_choices = list(range(-20, 21))
|
|
468
|
+
else:
|
|
469
|
+
# convert np.array to list if necessary
|
|
470
|
+
if isinstance(angles, np.ndarray):
|
|
471
|
+
angles = angles.tolist()
|
|
472
|
+
angle_choices = angles
|
|
302
473
|
|
|
303
474
|
# 1. Trouver TOUS les diviseurs PAIRS de num_elements (y compris num_elements)
|
|
304
475
|
divs = [d for d in range(2, num_elements + 1) if num_elements % d == 0 and d % 2 == 0]
|
|
@@ -390,6 +561,80 @@ class Tomography(Experiment):
|
|
|
390
561
|
|
|
391
562
|
return True
|
|
392
563
|
|
|
564
|
+
def applyApodisation(self, alpha=0.3, divergence_deg=0.5):
|
|
565
|
+
"""
|
|
566
|
+
Applique une apodisation dynamique sur les champs acoustiques stockés dans l'objet.
|
|
567
|
+
L'apodisation suit l'angle d'émission et la divergence naturelle du faisceau pour
|
|
568
|
+
supprimer les lobes de diffraction (artefacts de bord) sans toucher au signal utile.
|
|
569
|
+
Args:
|
|
570
|
+
probe_width (float): Largeur physique active de la sonde (ex: 40e-3 pour 40mm).
|
|
571
|
+
alpha (float): Paramètre de Tukey (0.0=rectangle, 1.0=hann). 0.3 est un bon compromis.
|
|
572
|
+
divergence_deg (float): Angle d'ouverture du masque pour suivre l'élargissement du faisceau.
|
|
573
|
+
0.0 = Droit, 0.5 = Légère ouverture (conseillé).
|
|
574
|
+
"""
|
|
575
|
+
print(f"Applying apodization (Alpha={alpha}, Div={divergence_deg}°) on {len(self.AcousticFields)} fields...")
|
|
576
|
+
|
|
577
|
+
probe_width = self.params.acoustic['num_elements'] * self.params.acoustic['element_width']
|
|
578
|
+
|
|
579
|
+
for i in trange(len(self.AcousticFields), desc="Apodisation"):
|
|
580
|
+
# 1. Récupération des données et de l'angle
|
|
581
|
+
field = self.AcousticFields[i].field # Peut être (Z, X) ou (Time, Z, X)
|
|
582
|
+
angle = self.AcousticFields[i].angle # L'angle de l'onde plane
|
|
583
|
+
|
|
584
|
+
# 2. Récupération ou construction des axes physiques
|
|
585
|
+
nz, nx = field.shape[-2:]
|
|
586
|
+
|
|
587
|
+
if hasattr(self, 'x_axis') and self.x_axis is not None:
|
|
588
|
+
x_axis = self.x_axis
|
|
589
|
+
else:
|
|
590
|
+
# Génération par défaut centrée sur 0 (ex: -20mm à +20mm)
|
|
591
|
+
x_axis = np.linspace(-probe_width/2, probe_width/2, nx)
|
|
592
|
+
|
|
593
|
+
if hasattr(self, 'z_axis') and self.z_axis is not None:
|
|
594
|
+
z_axis = self.z_axis
|
|
595
|
+
else:
|
|
596
|
+
# Génération par défaut (ex: 0 à 40mm, basé sur un pitch standard ou arbitraire)
|
|
597
|
+
estimated_depth = 40e-3 # Valeur arbitraire si inconnue
|
|
598
|
+
z_axis = np.linspace(0, estimated_depth, nz)
|
|
599
|
+
|
|
600
|
+
# 3. Préparation des grilles pour le masque
|
|
601
|
+
Z, X = np.meshgrid(z_axis, x_axis, indexing='ij')
|
|
602
|
+
|
|
603
|
+
# 4. Calcul de la géométrie orientée (Steering)
|
|
604
|
+
angle_rad = np.deg2rad(angle)
|
|
605
|
+
X_aligned = X - Z * np.tan(angle_rad)
|
|
606
|
+
|
|
607
|
+
# 5. Calcul de la largeur dynamique du masque (Divergence)
|
|
608
|
+
div_rad = np.deg2rad(divergence_deg)
|
|
609
|
+
current_half_width = (probe_width / 2.0) + Z * np.tan(div_rad)
|
|
610
|
+
|
|
611
|
+
# 6. Normalisation et création du masque Tukey
|
|
612
|
+
X_norm = np.divide(X_aligned, current_half_width, out=np.zeros_like(X_aligned), where=current_half_width!=0)
|
|
613
|
+
|
|
614
|
+
mask = np.zeros_like(X_norm)
|
|
615
|
+
plateau_threshold = 1.0 * (1 - alpha)
|
|
616
|
+
|
|
617
|
+
# Zone centrale (plateau = 1)
|
|
618
|
+
mask[np.abs(X_norm) <= plateau_threshold] = 1.0
|
|
619
|
+
|
|
620
|
+
# Zone de transition (cosinus)
|
|
621
|
+
transition_indices = (np.abs(X_norm) > plateau_threshold) & (np.abs(X_norm) <= 1.0)
|
|
622
|
+
if np.any(transition_indices):
|
|
623
|
+
x_trans = np.abs(X_norm[transition_indices]) - plateau_threshold
|
|
624
|
+
width_trans = 1.0 * alpha
|
|
625
|
+
mask[transition_indices] = 0.5 * (1 + np.cos(np.pi * x_trans / width_trans))
|
|
626
|
+
|
|
627
|
+
# 7. Application du masque (Gestion 2D vs 3D)
|
|
628
|
+
if field.ndim == 3:
|
|
629
|
+
field_apodized = field * mask[np.newaxis, :, :]
|
|
630
|
+
else:
|
|
631
|
+
field_apodized = field * mask
|
|
632
|
+
|
|
633
|
+
# 8. Mise à jour de l'objet
|
|
634
|
+
self.AcousticFields[i].field = field_apodized
|
|
635
|
+
|
|
636
|
+
print("Apodisation done.")
|
|
637
|
+
|
|
393
638
|
# PRIVATE METHODS
|
|
394
639
|
def _generateAcousticFields_STRUCT_CPU(self, fieldDataPath=None, show_log=False, nameBlock=None):
|
|
395
640
|
if self.patterns is None:
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
from ._mainRecon import Recon
|
|
2
2
|
from .ReconEnums import ReconType, AnalyticType, ProcessType
|
|
3
|
+
from AOT_biomaps.AOT_Experiment.Tomography import hex_to_binary_profile
|
|
4
|
+
from .ReconTools import get_phase_deterministic
|
|
3
5
|
|
|
4
6
|
import numpy as np
|
|
5
7
|
from tqdm import trange
|
|
8
|
+
import torch
|
|
9
|
+
import tqdm
|
|
10
|
+
|
|
6
11
|
|
|
7
12
|
class AnalyticRecon(Recon):
|
|
8
13
|
def __init__(self, analyticType, **kwargs):
|
|
@@ -10,6 +15,94 @@ class AnalyticRecon(Recon):
|
|
|
10
15
|
self.reconType = ReconType.Analytic
|
|
11
16
|
self.analyticType = analyticType
|
|
12
17
|
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def parse_and_demodulate(self, withTumor=True):
|
|
21
|
+
|
|
22
|
+
if withTumor:
|
|
23
|
+
AOsignal = self.experiment.AOsignal_withTumor
|
|
24
|
+
else:
|
|
25
|
+
AOsignal = self.experiment.AOsignal_withoutTumor
|
|
26
|
+
delta_x = self.params.acoustic['dx'] # en m
|
|
27
|
+
n_piezos = self.params.acoustic['num_elements']
|
|
28
|
+
demodulated_data = {}
|
|
29
|
+
structured_buffer = {}
|
|
30
|
+
|
|
31
|
+
for i in tqdm(range(len(self.experiment.AcousticFields)), desc="Demodulating AO signals"):
|
|
32
|
+
label = self.experiment.AcousticFields[i].getName_field()
|
|
33
|
+
|
|
34
|
+
parts = label.split("_")
|
|
35
|
+
hex_pattern = parts[0]
|
|
36
|
+
angle_code = parts[-1]
|
|
37
|
+
|
|
38
|
+
# Angle
|
|
39
|
+
if angle_code.startswith("1"):
|
|
40
|
+
angle_deg = -int(angle_code[1:])
|
|
41
|
+
else:
|
|
42
|
+
angle_deg = int(angle_code)
|
|
43
|
+
angle_rad = np.deg2rad(angle_deg)
|
|
44
|
+
|
|
45
|
+
# Onde Plane (f_s = 0)
|
|
46
|
+
if set(hex_pattern.lower().replace(" ", "")) == {'f'}:
|
|
47
|
+
fs_key = 0.0 # fs_key est en mm^-1 (0.0 mm^-1)
|
|
48
|
+
demodulated_data[(fs_key, angle_rad)] = np.array(AOsignal[i])
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
# Onde Structurée
|
|
52
|
+
profile = hex_to_binary_profile(hex_pattern, n_piezos)
|
|
53
|
+
|
|
54
|
+
# Calcul FS (Fréquence de Structuration)
|
|
55
|
+
ft_prof = np.fft.fft(profile)
|
|
56
|
+
# On regarde uniquement la partie positive non DC
|
|
57
|
+
idx_max = np.argmax(np.abs(ft_prof[1:len(profile)//2])) + 1
|
|
58
|
+
freqs = np.fft.fftfreq(len(profile), d=delta_x)
|
|
59
|
+
|
|
60
|
+
# freqs est en m^-1 car delta_x est en mètres.
|
|
61
|
+
fs_m_inv = abs(freqs[idx_max])
|
|
62
|
+
|
|
63
|
+
# *** CORRECTION 1: Conversion de f_s en mm^-1 (mm^-1 est utilisé dans iRadon) ***
|
|
64
|
+
fs_key = fs_m_inv / 1000.0 # Fréquence spatiale en mm^-1
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if fs_key == 0: continue
|
|
68
|
+
|
|
69
|
+
# Calcul de la Phase (Shift)
|
|
70
|
+
phase = get_phase_deterministic(profile)
|
|
71
|
+
|
|
72
|
+
# Stockage par (fs, theta) et phase
|
|
73
|
+
key = (fs_key, angle_rad)
|
|
74
|
+
if key not in structured_buffer:
|
|
75
|
+
structured_buffer[key] = {}
|
|
76
|
+
|
|
77
|
+
# La moyenne est nécessaire si plusieurs acquisitions ont la même phase (pour le SNR)
|
|
78
|
+
if phase in structured_buffer[key]:
|
|
79
|
+
structured_buffer[key][phase] = (structured_buffer[key][phase] + np.array(AOsignal[i])) / 2
|
|
80
|
+
else:
|
|
81
|
+
structured_buffer[key][phase] = np.array(AOsignal[i])
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
for (fs, theta), phases in structured_buffer.items():
|
|
86
|
+
s0 = phases.get(0.0, 0)
|
|
87
|
+
s_pi_2 = phases.get(np.pi/2, 0)
|
|
88
|
+
s_pi = phases.get(np.pi, 0)
|
|
89
|
+
s_3pi_2 = phases.get(3*np.pi/2, 0)
|
|
90
|
+
|
|
91
|
+
# Assurer que les zéros sont des vecteurs de la bonne taille
|
|
92
|
+
example = next(val for val in phases.values() if not isinstance(val, int))
|
|
93
|
+
if isinstance(s0, int): s0 = np.zeros_like(example)
|
|
94
|
+
if isinstance(s_pi, int): s_pi = np.zeros_like(example)
|
|
95
|
+
if isinstance(s_pi_2, int): s_pi_2 = np.zeros_like(example)
|
|
96
|
+
if isinstance(s_3pi_2, int): s_3pi_2 = np.zeros_like(example)
|
|
97
|
+
|
|
98
|
+
real = s0 - s_pi
|
|
99
|
+
imag = s_pi_2 - s_3pi_2
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
demodulated_data[(fs, theta)] = (real - 1j * imag) / (2/np.pi)
|
|
103
|
+
|
|
104
|
+
return demodulated_data
|
|
105
|
+
|
|
13
106
|
def run(self, processType = ProcessType.PYTHON, withTumor= True):
|
|
14
107
|
"""
|
|
15
108
|
This method is a placeholder for the analytic reconstruction process.
|
|
@@ -6,6 +6,7 @@ import pycuda.driver as drv
|
|
|
6
6
|
from numba import njit, prange
|
|
7
7
|
from torch_sparse import coalesce
|
|
8
8
|
from scipy.signal.windows import hann
|
|
9
|
+
from itertools import groupby
|
|
9
10
|
|
|
10
11
|
def load_recon(hdr_path):
|
|
11
12
|
"""
|
|
@@ -221,7 +222,6 @@ def calculate_memory_requirement(SMatrix, y):
|
|
|
221
222
|
# --- 3. Final Result ---
|
|
222
223
|
return total_bytes / (1024 ** 3)
|
|
223
224
|
|
|
224
|
-
|
|
225
225
|
def check_gpu_memory(device_index, required_memory, show_logs=True):
|
|
226
226
|
"""Check if enough memory is available on the specified GPU."""
|
|
227
227
|
free_memory, _ = torch.cuda.mem_get_info(f"cuda:{device_index}")
|
|
@@ -252,7 +252,6 @@ def _backward_projection(SMatrix, e_p, c_p):
|
|
|
252
252
|
total += SMatrix[_t, _z, _x, _n] * e_p[_t, _n]
|
|
253
253
|
c_p[_z, _x] = total
|
|
254
254
|
|
|
255
|
-
|
|
256
255
|
def _build_adjacency_sparse(Z, X, device, corner=(0.5 - np.sqrt(2) / 4) / np.sqrt(2), face=0.5 - np.sqrt(2) / 4,dtype=torch.float32):
|
|
257
256
|
rows, cols, weights = [], [], []
|
|
258
257
|
for z in range(Z):
|
|
@@ -273,7 +272,6 @@ def _build_adjacency_sparse(Z, X, device, corner=(0.5 - np.sqrt(2) / 4) / np.sqr
|
|
|
273
272
|
index, values = coalesce(index, values, m=Z*X, n=Z*X)
|
|
274
273
|
return index, values
|
|
275
274
|
|
|
276
|
-
|
|
277
275
|
def power_method(P, PT, data, Z, X, n_it=10):
|
|
278
276
|
x = torch.randn(Z * X, device=data.device)
|
|
279
277
|
x = x / torch.norm(x)
|
|
@@ -486,4 +484,48 @@ def power_method_estimate_L__SELL(SMatrix, stream, n_it=20, block_size=256):
|
|
|
486
484
|
g.free()
|
|
487
485
|
except:
|
|
488
486
|
pass
|
|
489
|
-
return max(L_sq, 1e-6)
|
|
487
|
+
return max(L_sq, 1e-6)
|
|
488
|
+
|
|
489
|
+
def get_phase_deterministic(profile):
|
|
490
|
+
"""
|
|
491
|
+
Détermine la phase en se basant sur la valeur initiale (0 ou 1) et l'état
|
|
492
|
+
de décalage (is_shifted) de la séquence binaire.
|
|
493
|
+
|
|
494
|
+
ATTENTION: Cette fonction est conservée mais la logique est souvent simplifiée
|
|
495
|
+
en pratique si les labels garantissent les phases 0, pi/2, pi, 3pi/2.
|
|
496
|
+
"""
|
|
497
|
+
runs = [(k, sum(1 for _ in g)) for k, g in groupby(profile)]
|
|
498
|
+
if not runs: return 0.0
|
|
499
|
+
|
|
500
|
+
nominal_half_period = max([r[1] for r in runs])
|
|
501
|
+
if nominal_half_period == 0: return 0.0
|
|
502
|
+
|
|
503
|
+
first_val = runs[0][0] # 0 ou 1
|
|
504
|
+
first_len = runs[0][1]
|
|
505
|
+
# Détection de cycle 50%
|
|
506
|
+
is_shifted = (0.3 < first_len / nominal_half_period < 0.7)
|
|
507
|
+
|
|
508
|
+
# --- LOGIQUE DE MAPPAGE DE PHASE SIMPLIFIÉE (idx 1 à 4) ---
|
|
509
|
+
|
|
510
|
+
if first_val == 0:
|
|
511
|
+
if is_shifted:
|
|
512
|
+
idx = 3 # C1/C3 décalé (phi_1 ou phi_3)
|
|
513
|
+
else:
|
|
514
|
+
idx = 4 # C2/C4 non décalé
|
|
515
|
+
else: # first_val == 1
|
|
516
|
+
if is_shifted:
|
|
517
|
+
idx = 1 # C1/C3 décalé (phi_1 ou phi_3)
|
|
518
|
+
else:
|
|
519
|
+
idx = 2 # C2/C4 non décalé
|
|
520
|
+
|
|
521
|
+
# On utilise les phases de quadrature 0, pi/2, pi, 3pi/2
|
|
522
|
+
if idx == 1:
|
|
523
|
+
phase = 0
|
|
524
|
+
elif idx == 2 :
|
|
525
|
+
phase = np.pi/2
|
|
526
|
+
elif idx == 3 :
|
|
527
|
+
phase = np.pi
|
|
528
|
+
elif idx == 4 :
|
|
529
|
+
phase = 3*np.pi/2
|
|
530
|
+
|
|
531
|
+
return phase
|
|
@@ -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.332'
|
|
89
89
|
__process__ = config.get_process()
|
|
90
90
|
|
|
91
91
|
def initialize(process=None):
|
|
@@ -156,6 +156,26 @@ def initialize(process=None):
|
|
|
156
156
|
|
|
157
157
|
|
|
158
158
|
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
159
179
|
|
|
160
180
|
|
|
161
181
|
|
|
@@ -16,6 +16,7 @@ AOT_biomaps/AOT_Acoustic/PlaneWave.py
|
|
|
16
16
|
AOT_biomaps/AOT_Acoustic/StructuredWave.py
|
|
17
17
|
AOT_biomaps/AOT_Acoustic/__init__.py
|
|
18
18
|
AOT_biomaps/AOT_Acoustic/_mainAcoustic.py
|
|
19
|
+
AOT_biomaps/AOT_Experiment/ExperimentTools.py
|
|
19
20
|
AOT_biomaps/AOT_Experiment/Focus.py
|
|
20
21
|
AOT_biomaps/AOT_Experiment/Tomography.py
|
|
21
22
|
AOT_biomaps/AOT_Experiment/__init__.py
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name='AOT_biomaps',
|
|
5
|
-
version='2.9.
|
|
5
|
+
version='2.9.332',
|
|
6
6
|
packages=find_packages(),
|
|
7
7
|
include_package_data=True,
|
|
8
8
|
|
|
@@ -314,6 +314,26 @@ setup(
|
|
|
314
314
|
|
|
315
315
|
|
|
316
316
|
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
|
|
317
337
|
|
|
318
338
|
|
|
319
339
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_Optimizers/DEPIERRO.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_Optimizers/__init__.py
RENAMED
|
File without changes
|
{aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/Huber.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_PotentialFunctions/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aot_biomaps-2.9.312 → aot_biomaps-2.9.332}/AOT_biomaps/AOT_Recon/AOT_SparseSMatrix/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|