AOT-biomaps 2.9.339__py3-none-any.whl → 2.9.373__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/AcousticTools.py +17 -0
- AOT_biomaps/AOT_Acoustic/StructuredWave.py +15 -1
- AOT_biomaps/AOT_Acoustic/_mainAcoustic.py +42 -15
- AOT_biomaps/AOT_Experiment/ExperimentTools.py +15 -25
- AOT_biomaps/AOT_Experiment/Tomography.py +61 -48
- AOT_biomaps/AOT_Recon/AnalyticRecon.py +321 -179
- AOT_biomaps/AOT_Recon/ReconTools.py +162 -1
- AOT_biomaps/AOT_Recon/_mainRecon.py +117 -10
- AOT_biomaps/__init__.py +35 -1
- {aot_biomaps-2.9.339.dist-info → aot_biomaps-2.9.373.dist-info}/METADATA +1 -1
- {aot_biomaps-2.9.339.dist-info → aot_biomaps-2.9.373.dist-info}/RECORD +13 -13
- {aot_biomaps-2.9.339.dist-info → aot_biomaps-2.9.373.dist-info}/WHEEL +0 -0
- {aot_biomaps-2.9.339.dist-info → aot_biomaps-2.9.373.dist-info}/top_level.txt +0 -0
|
@@ -217,3 +217,20 @@ def next_power_of_2(n):
|
|
|
217
217
|
"""Calculate the next power of 2 greater than or equal to n."""
|
|
218
218
|
return int(2 ** np.ceil(np.log2(n)))
|
|
219
219
|
|
|
220
|
+
def hex_to_binary_profile(hex_string, n_piezos=192):
|
|
221
|
+
hex_string = hex_string.strip().replace(" ", "").replace("\n", "")
|
|
222
|
+
if set(hex_string.lower()) == {'f'}:
|
|
223
|
+
return np.ones(n_piezos, dtype=int)
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
n_char = len(hex_string)
|
|
227
|
+
n_bits = n_char * 4
|
|
228
|
+
binary_str = bin(int(hex_string, 16))[2:].zfill(n_bits)
|
|
229
|
+
if len(binary_str) < n_piezos:
|
|
230
|
+
# Tronquer/padder en fonction de la taille réelle de la sonde
|
|
231
|
+
binary_str = binary_str.ljust(n_piezos, '0')
|
|
232
|
+
elif len(binary_str) > n_piezos:
|
|
233
|
+
binary_str = binary_str[:n_piezos]
|
|
234
|
+
return np.array([int(b) for b in binary_str])
|
|
235
|
+
except ValueError:
|
|
236
|
+
return np.zeros(n_piezos, dtype=int)
|
|
@@ -2,6 +2,7 @@ from AOT_biomaps.Config import config
|
|
|
2
2
|
from ._mainAcoustic import AcousticField
|
|
3
3
|
from .AcousticEnums import WaveType
|
|
4
4
|
from .AcousticTools import detect_space_0_and_space_1, getAngle
|
|
5
|
+
from .AcousticTools import hex_to_binary_profile
|
|
5
6
|
|
|
6
7
|
import os
|
|
7
8
|
import numpy as np
|
|
@@ -156,7 +157,20 @@ class StructuredWave(AcousticField):
|
|
|
156
157
|
int: Decimation frequency.
|
|
157
158
|
"""
|
|
158
159
|
try:
|
|
159
|
-
|
|
160
|
+
profile = hex_to_binary_profile(self.getName_field()[6:-4], self.params['num_elements'])
|
|
161
|
+
|
|
162
|
+
if set(self.getName_field()[6:-4].lower().replace(" ", "")) == {'f'}:
|
|
163
|
+
fs_key = 0.0 # fs_key est en mm^-1 (0.0 mm^-1)
|
|
164
|
+
else:
|
|
165
|
+
ft_prof = np.fft.fft(profile)
|
|
166
|
+
idx_max = np.argmax(np.abs(ft_prof[1:len(profile)//2])) + 1
|
|
167
|
+
freqs = np.fft.fftfreq(len(profile), d=self.params['dx'])
|
|
168
|
+
|
|
169
|
+
# freqs est en m^-1 car delta_x est en mètres.
|
|
170
|
+
fs_m_inv = abs(freqs[idx_max])
|
|
171
|
+
|
|
172
|
+
fs_key = fs_m_inv # Fréquence spatiale en mm^-1
|
|
173
|
+
return int(fs_key / (1/(len(profile)*self.params['dx'])))
|
|
160
174
|
except Exception as e:
|
|
161
175
|
print(f"Error calculating decimation frequency: {e}")
|
|
162
176
|
return None
|
|
@@ -93,6 +93,8 @@ class AcousticField(ABC):
|
|
|
93
93
|
'num_elements': params.acoustic['num_elements'],
|
|
94
94
|
'element_width': params.acoustic['element_width'],
|
|
95
95
|
'element_height': params.acoustic['element_height'],
|
|
96
|
+
'height_phantom': params.acoustic['phantom']['height'] if 'phantom' in params.acoustic and 'height' in params.acoustic['phantom'] else None,
|
|
97
|
+
'width_phantom': params.acoustic['phantom']['width'] if 'phantom' in params.acoustic and 'width' in params.acoustic['phantom'] else None,
|
|
96
98
|
'Xrange': params.general['Xrange'],
|
|
97
99
|
'Yrange': params.general['Yrange'],
|
|
98
100
|
'Zrange': params.general['Zrange'],
|
|
@@ -104,6 +106,7 @@ class AcousticField(ABC):
|
|
|
104
106
|
'Nx': int(np.round((params.general['Xrange'][1] - params.general['Xrange'][0])/params.general['dx'])),
|
|
105
107
|
'Ny': int(np.round((params.general['Yrange'][1] - params.general['Yrange'][0])/params.general['dy'])) if params.general['Yrange'] is not None else 1,
|
|
106
108
|
'Nz': int(np.round((params.general['Zrange'][1] - params.general['Zrange'][0])/params.general['dz'])),
|
|
109
|
+
'Nt': params.general['Nt'] if 'Nt' in params.general else None,
|
|
107
110
|
'probeWidth': params.acoustic['num_elements'] * params.acoustic['element_width'],
|
|
108
111
|
'IsAbsorbingMedium': params.acoustic['isAbsorbingMedium'],
|
|
109
112
|
}
|
|
@@ -114,8 +117,11 @@ class AcousticField(ABC):
|
|
|
114
117
|
|
|
115
118
|
self.params['f_AQ'] = int(1/self.kgrid.dt)
|
|
116
119
|
else:
|
|
117
|
-
|
|
118
|
-
|
|
120
|
+
if self.params['Nt'] is None:
|
|
121
|
+
Nt = ceil((self.params['Zrange'][1] - self.params['Zrange'][0])*float(params.acoustic['f_AQ']) / self.params['c0'])
|
|
122
|
+
self.params['Nt'] = Nt
|
|
123
|
+
else:
|
|
124
|
+
Nt = self.params['Nt']
|
|
119
125
|
self.kgrid.setTime(Nt,1/float(params.acoustic['f_AQ']))
|
|
120
126
|
self.params['f_AQ'] = int(float(params.acoustic['f_AQ']))
|
|
121
127
|
|
|
@@ -505,13 +511,25 @@ class AcousticField(ABC):
|
|
|
505
511
|
try:
|
|
506
512
|
# --- 1. Grid setup ---
|
|
507
513
|
dx = self.params['dx']
|
|
508
|
-
if dx >= self.params['element_width']
|
|
514
|
+
if dx >= self.params['element_width']:
|
|
509
515
|
dx = self.params['element_width'] / 2
|
|
510
|
-
|
|
511
|
-
|
|
516
|
+
if self.params['width_phantom'] is not None:
|
|
517
|
+
Nx = int(np.round((self.params['width_phantom'])/dx))
|
|
518
|
+
else:
|
|
519
|
+
Nx = int(round((self.params['Xrange'][1] - self.params['Xrange'][0]) / dx))
|
|
520
|
+
if self.params['height_phantom'] is not None:
|
|
521
|
+
Nz = int(np.round((self.params['height_phantom'])/dx))
|
|
522
|
+
else:
|
|
523
|
+
Nz = int(round((self.params['Zrange'][1] - self.params['Zrange'][0]) / dx))
|
|
512
524
|
else:
|
|
513
|
-
|
|
514
|
-
|
|
525
|
+
if self.params['width_phantom'] is not None:
|
|
526
|
+
Nx = int(np.round((self.params['width_phantom'])/self.params['dx']))
|
|
527
|
+
else:
|
|
528
|
+
Nx = int(round((self.params['Xrange'][1] - self.params['Xrange'][0]) / self.params['dx']))
|
|
529
|
+
if self.params['height_phantom'] is not None:
|
|
530
|
+
Nz = int(np.round((self.params['height_phantom'])/self.params['dz']))
|
|
531
|
+
else:
|
|
532
|
+
Nz = int(round((self.params['Zrange'][1] - self.params['Zrange'][0]) / self.params['dz']))
|
|
515
533
|
|
|
516
534
|
# --- 2. Time and space factors ---
|
|
517
535
|
self.factorT = int(np.ceil(self.params['f_AQ'] / self.params['f_saving']))
|
|
@@ -530,17 +548,14 @@ class AcousticField(ABC):
|
|
|
530
548
|
sensor.mask = np.ones((Nx, Nz))
|
|
531
549
|
|
|
532
550
|
# --- 5. PML setup ---
|
|
533
|
-
total_size_x = next_power_of_2(Nx)
|
|
534
551
|
total_size_z = next_power_of_2(Nz)
|
|
535
|
-
pml_x_size = (total_size_x - Nx) // 2
|
|
536
552
|
pml_z_size = (total_size_z - Nz) // 2
|
|
537
|
-
pml_x_size = max(pml_x_size, 50) # Ensure a minimum PML size of 50 grid points to avoid parasitic reflections
|
|
538
553
|
pml_z_size = max(pml_z_size, 50) # Ensure a minimum PML size of 50 grid points to avoid parasitic reflections
|
|
539
554
|
|
|
540
555
|
# --- 6. Simulation options ---
|
|
541
556
|
simulation_options = SimulationOptions(
|
|
542
557
|
pml_inside=False,
|
|
543
|
-
pml_size=[
|
|
558
|
+
pml_size=[0, pml_z_size],
|
|
544
559
|
use_sg=False,
|
|
545
560
|
save_to_disk=True,
|
|
546
561
|
input_filename=os.path.join(gettempdir(), "KwaveIN.h5"),
|
|
@@ -583,11 +598,23 @@ class AcousticField(ABC):
|
|
|
583
598
|
dx = self.params['dx']
|
|
584
599
|
if dx >= self.params['element_width']:
|
|
585
600
|
dx = self.params['element_width'] / 2
|
|
586
|
-
|
|
587
|
-
|
|
601
|
+
if self.params['width_phantom'] is not None:
|
|
602
|
+
Nx = int(np.round((self.params['width_phantom'])/dx))
|
|
603
|
+
else:
|
|
604
|
+
Nx = int(round((self.params['Xrange'][1] - self.params['Xrange'][0]) / dx))
|
|
605
|
+
if self.params['height_phantom'] is not None:
|
|
606
|
+
Nz = int(np.round((self.params['height_phantom'])/dx))
|
|
607
|
+
else:
|
|
608
|
+
Nz = int(round((self.params['Zrange'][1] - self.params['Zrange'][0]) / dx))
|
|
588
609
|
else:
|
|
589
|
-
|
|
590
|
-
|
|
610
|
+
if self.params['width_phantom'] is not None:
|
|
611
|
+
Nx = int(np.round((self.params['width_phantom'])/self.params['dx']))
|
|
612
|
+
else:
|
|
613
|
+
Nx = int(round((self.params['Xrange'][1] - self.params['Xrange'][0]) / self.params['dx']))
|
|
614
|
+
if self.params['height_phantom'] is not None:
|
|
615
|
+
Nz = int(np.round((self.params['height_phantom'])/self.params['dz']))
|
|
616
|
+
else:
|
|
617
|
+
Nz = int(round((self.params['Zrange'][1] - self.params['Zrange'][0]) / self.params['dz']))
|
|
591
618
|
|
|
592
619
|
# --- 2. Time and space factors (common) ---
|
|
593
620
|
factorT = int(np.ceil(self.params['f_AQ'] / self.params['f_saving']))
|
|
@@ -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,6 +18,8 @@ class Tomography(Experiment):
|
|
|
18
18
|
self.patterns = None
|
|
19
19
|
self.theta = []
|
|
20
20
|
self.decimations = []
|
|
21
|
+
self.ActiveList = []
|
|
22
|
+
self.DelayLaw = []
|
|
21
23
|
|
|
22
24
|
# PUBLIC METHODS
|
|
23
25
|
def check(self):
|
|
@@ -66,8 +68,11 @@ class Tomography(Experiment):
|
|
|
66
68
|
self.AcousticFields = self._generateAcousticFields_STRUCT_CPU(fieldDataPath, show_log, nameBlock)
|
|
67
69
|
for i in range(len(self.AcousticFields)):
|
|
68
70
|
profile = hex_to_binary_profile(self.AcousticFields[i].getName_field()[6:-4], self.params.acoustic['num_elements'])
|
|
71
|
+
self.ActiveList.append(profile)
|
|
69
72
|
angle = self.AcousticFields[i].angle
|
|
70
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))
|
|
71
76
|
|
|
72
77
|
if set(self.AcousticFields[i].getName_field()[6:-4].lower().replace(" ", "")) == {'f'}:
|
|
73
78
|
fs_key = 0.0 # fs_key est en mm^-1 (0.0 mm^-1)
|
|
@@ -83,6 +88,7 @@ class Tomography(Experiment):
|
|
|
83
88
|
|
|
84
89
|
# fs = n * dfx => n = fs / dfx with dfx = 1/(N*delta_x)
|
|
85
90
|
self.decimations.append(int(fs_key / (1/(len(profile)*self.params.general['dx']))))
|
|
91
|
+
|
|
86
92
|
else:
|
|
87
93
|
raise ValueError("Unsupported wave type.")
|
|
88
94
|
|
|
@@ -314,20 +320,8 @@ class Tomography(Experiment):
|
|
|
314
320
|
raise ValueError("Either N (>=2) or both decimations and angles must be provided for pattern generation.")
|
|
315
321
|
|
|
316
322
|
def saveAOsignals_matlab(self, filePath):
|
|
317
|
-
ActiveList = []
|
|
318
|
-
DelayLaw = []
|
|
319
|
-
c = self.params.acoustic['c0']
|
|
320
|
-
NbElemts = self.params.acoustic['num_elements']
|
|
321
|
-
pitch = self.params.acoustic['element_width']
|
|
322
|
-
|
|
323
|
-
for i in range(len(self.AcousticFields)):
|
|
324
|
-
profile = hex_to_binary_profile(self.AcousticFields[i].getName_field()[6:-4], NbElemts)
|
|
325
|
-
ActiveList.append(profile)
|
|
326
|
-
angle = self.AcousticFields[i].angle
|
|
327
|
-
Delay = 1000 * (1/c) * np.sin(np.deg2rad(angle)) * np.arange(1, NbElemts + 1) * pitch
|
|
328
|
-
DelayLaw.append(Delay - np.min(Delay))
|
|
329
323
|
|
|
330
|
-
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})
|
|
331
325
|
|
|
332
326
|
def selectAngles(self, angles):
|
|
333
327
|
|
|
@@ -346,6 +340,27 @@ class Tomography(Experiment):
|
|
|
346
340
|
if self.AOsignal_withoutTumor is not None:
|
|
347
341
|
self.AOsignal_withoutTumor = self.AOsignal_withoutTumor[:, index]
|
|
348
342
|
self.AcousticFields = newAcousticFields
|
|
343
|
+
self.theta = [field.angle for field in newAcousticFields]
|
|
344
|
+
self.decimations = [field.f_s for field in newAcousticFields]
|
|
345
|
+
|
|
346
|
+
def selectDecimations(self, decimations):
|
|
347
|
+
if self.AOsignal_withTumor is None and self.AOsignal_withoutTumor is None:
|
|
348
|
+
raise ValueError("AO signals are not initialized. Please load or generate the AO signals first.")
|
|
349
|
+
if self.AcousticFields is None or len(self.AcousticFields) == 0:
|
|
350
|
+
raise ValueError("AcousticFields is not initialized. Please generate the system matrix first.")
|
|
351
|
+
newAcousticFields = []
|
|
352
|
+
index = []
|
|
353
|
+
for i, field in enumerate(self.AcousticFields):
|
|
354
|
+
if field.f_s in decimations:
|
|
355
|
+
newAcousticFields.append(field)
|
|
356
|
+
index.append(i)
|
|
357
|
+
if self.AOsignal_withTumor is not None:
|
|
358
|
+
self.AOsignal_withTumor = self.AOsignal_withTumor[:, index]
|
|
359
|
+
if self.AOsignal_withoutTumor is not None:
|
|
360
|
+
self.AOsignal_withoutTumor = self.AOsignal_withoutTumor[:, index]
|
|
361
|
+
self.AcousticFields = newAcousticFields
|
|
362
|
+
self.decimations = [field.f_s for field in newAcousticFields]
|
|
363
|
+
self.theta = [field.angle for field in newAcousticFields]
|
|
349
364
|
|
|
350
365
|
def selectPatterns(self, pattern_names):
|
|
351
366
|
if self.AOsignal_withTumor is None and self.AOsignal_withoutTumor is None:
|
|
@@ -380,75 +395,73 @@ class Tomography(Experiment):
|
|
|
380
395
|
self.AcousticFields = newAcousticFields
|
|
381
396
|
|
|
382
397
|
def _genereate_patterns_from_decimations(self, decimations, angles):
|
|
383
|
-
if isinstance(decimations, list):
|
|
384
|
-
|
|
398
|
+
if isinstance(decimations, list):
|
|
399
|
+
decimations = np.array(decimations)
|
|
400
|
+
if isinstance(angles, list):
|
|
401
|
+
angles = np.array(angles)
|
|
385
402
|
|
|
386
403
|
angles = np.sort(angles)
|
|
387
404
|
decimations = np.sort(decimations)
|
|
388
405
|
|
|
389
406
|
num_elements = self.params.acoustic['num_elements']
|
|
390
|
-
|
|
407
|
+
Width = self.params.acoustic['element_width'] # en m
|
|
408
|
+
kerf = self.params.acoustic.get('kerf', 0.00000) # en m
|
|
409
|
+
Nactuators = self.params.acoustic['num_elements']
|
|
391
410
|
|
|
392
411
|
# --- Calcul du nombre de Scans ---
|
|
393
412
|
if 0 in decimations:
|
|
394
|
-
Nscans = 4 * angles
|
|
395
|
-
offSet = angles.shape[0]
|
|
413
|
+
Nscans = 4 * len(angles) * (len(decimations) - 1) + len(angles)
|
|
396
414
|
else:
|
|
397
|
-
Nscans = 4 * angles
|
|
398
|
-
offSet = 0
|
|
415
|
+
Nscans = 4 * len(angles) * len(decimations)
|
|
399
416
|
|
|
400
417
|
ActiveLIST = np.ones((num_elements, Nscans))
|
|
401
|
-
Xm = np.arange(1, num_elements + 1) * dx_mm
|
|
402
|
-
dFx = 1 / (num_elements * dx_mm)
|
|
403
418
|
|
|
404
|
-
#
|
|
419
|
+
# --- Calcul des positions centrées des éléments (en m) ---
|
|
420
|
+
Xc = (Width + (Nactuators - 1) * (kerf + Width)) / 2
|
|
421
|
+
Xm = np.array([Width * (i - 1) + Width / 2 - Xc for i in range(1, Nactuators + 1)])
|
|
422
|
+
|
|
423
|
+
# --- Gestion de l'onde plane (tous les piezo ON) au début ---
|
|
424
|
+
if 0 in decimations:
|
|
425
|
+
I_plane = np.arange(len(angles))
|
|
426
|
+
ActiveLIST[:, I_plane] = 1 # Tous les piezo ON pour les len(angles) premières colonnes
|
|
427
|
+
|
|
428
|
+
# --- Traitement des décimations non nulles ---
|
|
405
429
|
active_decimations = decimations[decimations != 0]
|
|
430
|
+
dFx = 1 / (Nactuators * Width) # fx de base (en m^-1)
|
|
406
431
|
|
|
407
432
|
for i_dec in range(len(active_decimations)):
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
Icos = idx_base
|
|
411
|
-
Incos = idx_base + len(angles)
|
|
412
|
-
Isin = idx_base + 2 * len(angles)
|
|
413
|
-
Insin = idx_base + 3 * len(angles)
|
|
433
|
+
# Décalage des indices pour placer les motifs modulés après l'onde plane
|
|
434
|
+
I = np.arange(len(angles)) + len(angles) + (i_dec * 4 * len(angles))
|
|
414
435
|
|
|
415
|
-
|
|
436
|
+
Icos = I
|
|
437
|
+
Incos = I + 1* len(angles)
|
|
438
|
+
Isin = I + 3 * len(angles)
|
|
439
|
+
Insin = I + 2 * len(angles)
|
|
416
440
|
|
|
417
|
-
|
|
418
|
-
valid_icos = Icos[Icos < Nscans]
|
|
419
|
-
if valid_icos.size > 0:
|
|
420
|
-
ActiveLIST[:, valid_icos] = calc_mat_os(Xm, fx, dx_mm, ActiveLIST[:, valid_icos], 'cos')
|
|
421
|
-
if (Incos < Nscans).any():
|
|
422
|
-
ActiveLIST[:, Incos[Incos < Nscans]] = 1 - ActiveLIST[:, valid_icos]
|
|
441
|
+
fx = dFx * active_decimations[i_dec]
|
|
423
442
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
443
|
+
# Appliquer les motifs modulés
|
|
444
|
+
ActiveLIST[:, Icos] = calc_mat_os(Xm, fx, ActiveLIST[:, Icos[:1]], 'cos')
|
|
445
|
+
ActiveLIST[:, Incos] = 1 - ActiveLIST[:, Icos]
|
|
446
|
+
ActiveLIST[:, Isin] = calc_mat_os(Xm, fx, ActiveLIST[:, Isin[:1]], 'sin')
|
|
447
|
+
ActiveLIST[:, Insin] = 1 - ActiveLIST[:, Isin]
|
|
429
448
|
|
|
430
449
|
# --- Conversion au format attendu ---
|
|
431
|
-
# 1. On convertit toute la matrice en liste de strings Hexa
|
|
432
450
|
hexa_list = convert_to_hex_list(ActiveLIST)
|
|
433
451
|
|
|
434
|
-
# 2. Fonction interne de formatage d'angle (pour coller à votre ancien code)
|
|
435
452
|
def format_angle(a):
|
|
436
453
|
return f"{'1' if a < 0 else '0'}{abs(a):02d}"
|
|
437
454
|
|
|
438
|
-
# 3. Construction de la liste de dictionnaires
|
|
439
455
|
patterns = []
|
|
440
456
|
print(f"Generating {Nscans} patterns from decimations and angles...")
|
|
441
457
|
for i in range(Nscans):
|
|
442
|
-
# On retrouve l'angle correspondant à l'index i
|
|
443
|
-
# La logique est cyclique sur la taille de 'angles'
|
|
444
458
|
angle_val = angles[i % len(angles)]
|
|
445
|
-
|
|
446
459
|
hex_pattern = hexa_list[i]
|
|
447
460
|
pair = f"{hex_pattern}_{format_angle(angle_val)}"
|
|
448
461
|
patterns.append({"fileName": pair})
|
|
449
462
|
|
|
450
463
|
return patterns
|
|
451
|
-
|
|
464
|
+
|
|
452
465
|
def _generate_patterns(self, N,angles = None):
|
|
453
466
|
def format_angle(a):
|
|
454
467
|
return f"{'1' if a < 0 else '0'}{abs(a):02d}"
|