tictacsync 0.1a10__py2-none-any.whl → 0.1a11__py2-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 tictacsync might be problematic. Click here for more details.
- tictacsync/device_scanner.py +19 -7
- tictacsync/entry.py +1 -1
- tictacsync/yaltc.py +134 -109
- {tictacsync-0.1a10.dist-info → tictacsync-0.1a11.dist-info}/METADATA +1 -1
- tictacsync-0.1a11.dist-info/RECORD +11 -0
- tictacsync-0.1a10.dist-info/RECORD +0 -11
- {tictacsync-0.1a10.dist-info → tictacsync-0.1a11.dist-info}/LICENSE +0 -0
- {tictacsync-0.1a10.dist-info → tictacsync-0.1a11.dist-info}/WHEEL +0 -0
- {tictacsync-0.1a10.dist-info → tictacsync-0.1a11.dist-info}/entry_points.txt +0 -0
- {tictacsync-0.1a10.dist-info → tictacsync-0.1a11.dist-info}/top_level.txt +0 -0
tictacsync/device_scanner.py
CHANGED
|
@@ -173,11 +173,11 @@ class Scanner:
|
|
|
173
173
|
continue
|
|
174
174
|
devices = [m['dev UID'] for m in media_same_length]
|
|
175
175
|
if len(set(devices)) !=1:
|
|
176
|
-
print('files with same length but
|
|
176
|
+
print('There are files with same length but from different devices?')
|
|
177
177
|
for media in media_same_length:
|
|
178
|
-
print(' %s'%media['path'])
|
|
179
|
-
print('put the offending file in its own folder and')
|
|
180
|
-
print('
|
|
178
|
+
print(' [gold1]%s[/gold1]'%media['path'])
|
|
179
|
+
print('Please put the offending file in its own folder and rerun.')
|
|
180
|
+
print('Quitting...')
|
|
181
181
|
quit()
|
|
182
182
|
self.found_multifiles.append(media_same_length)
|
|
183
183
|
self.found_media_files = unifile_recordings
|
|
@@ -326,6 +326,13 @@ class Scanner:
|
|
|
326
326
|
|
|
327
327
|
Returns nothing
|
|
328
328
|
"""
|
|
329
|
+
def _list_duplicates(seq):
|
|
330
|
+
seen = set()
|
|
331
|
+
seen_add = seen.add
|
|
332
|
+
# adds all elements it doesn't know yet to seen and all other to seen_twice
|
|
333
|
+
seen_twice = set( x for x in seq if x in seen or seen_add(x) )
|
|
334
|
+
# turn the set into a list (as requested)
|
|
335
|
+
return list( seen_twice )
|
|
329
336
|
folder_key = lambda m: m['path'].parent
|
|
330
337
|
medias = sorted(self.found_media_files, key=folder_key)
|
|
331
338
|
# build lists for multiple reference of iterators
|
|
@@ -335,9 +342,14 @@ class Scanner:
|
|
|
335
342
|
name_of_folders = [p.name for p in complete_path_folders]
|
|
336
343
|
logger.debug('complete_path_folders with media files %s'%complete_path_folders)
|
|
337
344
|
logger.debug('name_of_folders with media files %s'%name_of_folders)
|
|
338
|
-
unique_folder_names = set(name_of_folders)
|
|
339
|
-
|
|
340
|
-
|
|
345
|
+
# unique_folder_names = set(name_of_folders)
|
|
346
|
+
repeated_folders = _list_duplicates(name_of_folders)
|
|
347
|
+
logger.debug('repeated_folders %s'%repeated_folders)
|
|
348
|
+
if repeated_folders:
|
|
349
|
+
print('There are conflicts for some repeated folder names:')
|
|
350
|
+
for f in [str(p) for p in repeated_folders]:
|
|
351
|
+
print(' [gold1]%s[/gold1]'%f)
|
|
352
|
+
print('Here are the complete paths:')
|
|
341
353
|
for f in [str(p) for p in complete_path_folders]:
|
|
342
354
|
print(' [gold1]%s[/gold1]'%f)
|
|
343
355
|
print('please rename and rerun. Quitting..')
|
tictacsync/entry.py
CHANGED
|
@@ -110,7 +110,7 @@ def main():
|
|
|
110
110
|
# logger.add(sys.stdout, filter="device_scanner")
|
|
111
111
|
# logger.add(sys.stdout, filter="yaltc")
|
|
112
112
|
# logger.add(sys.stdout, filter="timeline")
|
|
113
|
-
# logger.add(sys.stdout, filter=lambda r: r["function"] == "
|
|
113
|
+
# logger.add(sys.stdout, filter=lambda r: r["function"] == "_get_word_envelope")
|
|
114
114
|
# logger.add(sys.stdout, filter=lambda r: r["function"] == "get_timecode")
|
|
115
115
|
# logger.add(sys.stdout, filter=lambda r: r["function"] == "_get_BFSK_symbols_boundaries")
|
|
116
116
|
# logger.add(sys.stdout, filter=lambda r: r["function"] == "_get_BFSK_word_boundaries")
|
tictacsync/yaltc.py
CHANGED
|
@@ -63,6 +63,7 @@ SYMBOL_LENGTH = 14.286 # ms, from FSKfreqCalculator.py
|
|
|
63
63
|
N_SYMBOLS_SAMD21 = 35 # including sync pulse
|
|
64
64
|
##################
|
|
65
65
|
|
|
66
|
+
BPF_LOW_FRQ, BPF_HIGH_FRQ = (0.5*F1, 2*F2)
|
|
66
67
|
|
|
67
68
|
try:
|
|
68
69
|
layouts, _ = (
|
|
@@ -190,17 +191,18 @@ class Decoder:
|
|
|
190
191
|
SN_ratio : float
|
|
191
192
|
signal over noise ratio in dB.
|
|
192
193
|
|
|
193
|
-
|
|
194
|
+
pulse_detection_level : float
|
|
194
195
|
level used to detect sync pulse
|
|
195
196
|
|
|
196
197
|
silent_zone_indices : tuple of ints
|
|
197
198
|
silent zone boundary positions relative to the start
|
|
198
199
|
of self.sound_extract.
|
|
200
|
+
|
|
201
|
+
estimated_pulse_position : int
|
|
202
|
+
pulse position (samples) relative to the start of self.sound_extract
|
|
199
203
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
of pulse;
|
|
203
|
-
key 'type' : "estimated" or "detected"
|
|
204
|
+
detected_pulse_position : int
|
|
205
|
+
pulse position (samples) relative to the start of self.sound_extract
|
|
204
206
|
|
|
205
207
|
cached_convolution_fit : dict
|
|
206
208
|
if _fit_triangular_signal_to_convoluted_env() has already been called,
|
|
@@ -223,10 +225,10 @@ class Decoder:
|
|
|
223
225
|
def clear_decoder(self):
|
|
224
226
|
self.sound_data_extract = None
|
|
225
227
|
self.cached_convolution_fit = {'sound_extract_position': None}
|
|
226
|
-
self.
|
|
228
|
+
self.pulse_detection_level = None
|
|
227
229
|
self.silent_zone_indices = None
|
|
228
|
-
self.
|
|
229
|
-
|
|
230
|
+
self.detected_pulse_position = None
|
|
231
|
+
self.estimated_pulse_position = None
|
|
230
232
|
|
|
231
233
|
def set_sound_extract_and_sr(self, extract, s_r, where):
|
|
232
234
|
self.sound_extract = extract
|
|
@@ -254,21 +256,10 @@ class Decoder:
|
|
|
254
256
|
|
|
255
257
|
"""
|
|
256
258
|
WINDOW_LENGTH, POLYORDER = (15, 3) # parameters found by experiment, hit and miss
|
|
257
|
-
|
|
258
|
-
envelope = scipy.signal.savgol_filter(
|
|
259
|
+
absolute_of_hilbert = np.abs(scipy.signal.hilbert(self.sound_extract))
|
|
260
|
+
envelope = scipy.signal.savgol_filter(absolute_of_hilbert,
|
|
259
261
|
WINDOW_LENGTH, POLYORDER)
|
|
260
|
-
|
|
261
|
-
# plt.plot(self.sound_extract)
|
|
262
|
-
# plt.plot(abs_hil)
|
|
263
|
-
# plt.plot(envelope)
|
|
264
|
-
# plt.show()
|
|
265
|
-
|
|
266
|
-
# mean = envelope.mean()
|
|
267
|
-
# if mean: # in case of zero padding
|
|
268
|
-
# factor = 0.5/mean # since 50% duty cycle
|
|
269
|
-
# else:
|
|
270
|
-
# factor = 1
|
|
271
|
-
# return factor*envelope
|
|
262
|
+
logger.debug('self.sound_extract envelope length %i samples'%len(envelope))
|
|
272
263
|
return envelope
|
|
273
264
|
|
|
274
265
|
def _get_signal_level(self):
|
|
@@ -277,30 +268,30 @@ class Decoder:
|
|
|
277
268
|
|
|
278
269
|
def _get_pulse_position(self):
|
|
279
270
|
# relative to extract beginning
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
return self.
|
|
271
|
+
if self.detected_pulse_position:
|
|
272
|
+
logger.debug('returning detected value')
|
|
273
|
+
return self.detected_pulse_position
|
|
274
|
+
if self.estimated_pulse_position:
|
|
275
|
+
return self.estimated_pulse_position
|
|
283
276
|
_, silence_center_x = self._fit_triangular_signal_to_convoluted_env()
|
|
284
277
|
# symbol_width_samples = 1e-3*SYMBOL_LENGTH
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
self.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
def _get_pulse_level(self):
|
|
278
|
+
self.estimated_pulse_position = silence_center_x + int(0.5*(
|
|
279
|
+
0.5 - 1e-3*SYMBOL_LENGTH)*self.samplerate)
|
|
280
|
+
logger.debug('returning estimated value from silence mid position')
|
|
281
|
+
return self.estimated_pulse_position
|
|
282
|
+
|
|
283
|
+
def _get_pulse_detection_level(self):
|
|
293
284
|
# return the geometric mean between silence and BFSK levels
|
|
294
|
-
if self.
|
|
285
|
+
if self.pulse_detection_level is None:
|
|
295
286
|
silence_floor = self._get_silence_floor()
|
|
296
287
|
# lower_BFSK_level = silence_floor
|
|
297
|
-
pulse_position = self._get_pulse_position()
|
|
298
|
-
lower_BFSK_level = self._get_minimal_bfsk(
|
|
288
|
+
# pulse_position = self._get_pulse_position()
|
|
289
|
+
lower_BFSK_level = self._get_minimal_bfsk()
|
|
299
290
|
value = math.sqrt(silence_floor * lower_BFSK_level)
|
|
300
|
-
self.
|
|
291
|
+
self.pulse_detection_level = value
|
|
301
292
|
return value
|
|
302
293
|
else:
|
|
303
|
-
return self.
|
|
294
|
+
return self.pulse_detection_level
|
|
304
295
|
|
|
305
296
|
def _get_square_convolution(self):
|
|
306
297
|
"""
|
|
@@ -333,37 +324,50 @@ class Decoder:
|
|
|
333
324
|
x = range(start, len(convol) + start)
|
|
334
325
|
return [*x], convol
|
|
335
326
|
|
|
336
|
-
def _get_word_envelope(self
|
|
327
|
+
def _get_word_envelope(self):
|
|
337
328
|
"""
|
|
338
329
|
Chop the signal envelope keeping the word region and smooth it over the
|
|
339
330
|
longest BFSK period
|
|
340
331
|
"""
|
|
341
|
-
|
|
342
|
-
logger.debug('max BFSK period %i samples'%max_period)
|
|
343
|
-
period_window = np.ones(max_period,dtype=int)/max_period
|
|
332
|
+
SR = self.samplerate
|
|
344
333
|
envelope = self._get_envelope()
|
|
345
|
-
|
|
334
|
+
pulse_position = self._get_pulse_position()
|
|
335
|
+
samples_to_end = len(self.sound_extract) - pulse_position
|
|
336
|
+
is_too_near_the_end = samples_to_end/SR < 0.5
|
|
337
|
+
logger.debug('pulse_position is_too_near_the_end %s'%
|
|
338
|
+
is_too_near_the_end)
|
|
339
|
+
if is_too_near_the_end:
|
|
340
|
+
pulse_position -= SR # one second sooner
|
|
341
|
+
symbol_width_samples = 1e-3*SYMBOL_LENGTH*SR
|
|
346
342
|
word_start = int(pulse_position + 3*symbol_width_samples)
|
|
347
|
-
word_end = int(pulse_position + 0.5*
|
|
348
|
-
word_end -= 2*symbol_width_samples # slide to the left a little
|
|
349
|
-
logger.debug('word start, end: %i %i'%(
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
343
|
+
word_end = int(pulse_position + 0.5*SR)
|
|
344
|
+
word_end -= int(2*symbol_width_samples) # slide to the left a little
|
|
345
|
+
logger.debug('word start, end: %i %i (in file)'%(
|
|
346
|
+
word_start + self.sound_extract_position,
|
|
347
|
+
word_end + self.sound_extract_position))
|
|
348
|
+
w_envelope = envelope[word_start : word_end]
|
|
349
|
+
word_envelope_truncated = word_end-word_start != len(w_envelope)
|
|
350
|
+
logger.debug('w_envelope is sliced out of bounds: %s'%(
|
|
351
|
+
str(word_envelope_truncated)))
|
|
352
|
+
logger.debug('word envelope length %i samples %f secs'%(
|
|
353
|
+
len(w_envelope), len(w_envelope)/SR))
|
|
354
|
+
max_period = int(self.samplerate*max(1/F1,1/F2))
|
|
355
|
+
logger.debug('max BFSK period %i in samples'%max_period)
|
|
356
|
+
period_window = np.ones(max_period,dtype=int)/max_period
|
|
357
|
+
# smooth over longest BFSK period
|
|
358
|
+
return np.convolve(w_envelope, period_window, mode='same')
|
|
355
359
|
|
|
356
|
-
def _get_minimal_bfsk(self
|
|
360
|
+
def _get_minimal_bfsk(self):
|
|
357
361
|
"""
|
|
358
362
|
because of non-flat frequency response, bfsk bits dont have the same
|
|
359
363
|
amplitude. This returns the least of both by detecting a bimodal
|
|
360
364
|
gaussian distribution
|
|
361
365
|
|
|
362
366
|
"""
|
|
363
|
-
# w_envelope = self._get_word_envelope(
|
|
367
|
+
# w_envelope = self._get_word_envelope()
|
|
364
368
|
# word_start = int(min_position + shift + 0.3*self.samplerate)
|
|
365
369
|
# word = w_envelope[word_start : int(word_start + 0.4*self.samplerate)]
|
|
366
|
-
word = self._get_word_envelope(
|
|
370
|
+
word = self._get_word_envelope()
|
|
367
371
|
# plt.plot(word)
|
|
368
372
|
# plt.show()
|
|
369
373
|
n = len(word)
|
|
@@ -407,12 +411,11 @@ class Decoder:
|
|
|
407
411
|
logger.debug('yes, fit values cached:')
|
|
408
412
|
v1 = self.cached_convolution_fit['chi_square']
|
|
409
413
|
v2 = self.cached_convolution_fit['minimum position']
|
|
410
|
-
|
|
414
|
+
v2_file = v2 + self.sound_extract_position
|
|
415
|
+
logger.debug('cached chi_sq: %s minimum position in file: %s'%(v1, v2_file))
|
|
411
416
|
return (v1, v2)
|
|
412
417
|
# cached!
|
|
413
418
|
x_shifted, convolution = self._get_square_convolution()
|
|
414
|
-
|
|
415
|
-
shift = x_shifted[0] # convolution is shorter than sound envelope
|
|
416
419
|
# see numpy.convolve(..., mode='valid')
|
|
417
420
|
x = np.arange(len(convolution))
|
|
418
421
|
trig_params = lmfit.Parameters()
|
|
@@ -441,12 +444,13 @@ class Decoder:
|
|
|
441
444
|
args=(x,), kws={'signal_data': convolution}
|
|
442
445
|
)
|
|
443
446
|
chi_square = fit_trig.chisqr
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
+
shift = x_shifted[0] # convolution is shorter than sound envelope
|
|
448
|
+
min_position = int(fit_trig.params['min_position'].value) + shift
|
|
449
|
+
logger.debug('chi_square %.1f minimum convolution position %i in file'%
|
|
450
|
+
(chi_square, min_position + self.sound_extract_position))
|
|
447
451
|
self.cached_convolution_fit['sound_extract_position'] = self.sound_extract_position
|
|
448
452
|
self.cached_convolution_fit['chi_square'] = chi_square
|
|
449
|
-
self.cached_convolution_fit['minimum position'] = min_position
|
|
453
|
+
self.cached_convolution_fit['minimum position'] = min_position
|
|
450
454
|
|
|
451
455
|
return chi_square, min_position + shift
|
|
452
456
|
|
|
@@ -468,9 +472,7 @@ class Decoder:
|
|
|
468
472
|
def _get_silent_zone_indices(self):
|
|
469
473
|
"""
|
|
470
474
|
Returns silent zone boundary positions relative to the start
|
|
471
|
-
of self.sound_extract.
|
|
472
|
-
0.5 second signal is at the left of the silent zone for word
|
|
473
|
-
decoding.
|
|
475
|
+
of self.sound_extract.
|
|
474
476
|
|
|
475
477
|
Returns
|
|
476
478
|
-------
|
|
@@ -480,7 +482,7 @@ class Decoder:
|
|
|
480
482
|
right indice.
|
|
481
483
|
|
|
482
484
|
"""
|
|
483
|
-
if self.silent_zone_indices
|
|
485
|
+
if self.silent_zone_indices:
|
|
484
486
|
return self.silent_zone_indices
|
|
485
487
|
_, silence_center_position = self._fit_triangular_signal_to_convoluted_env()
|
|
486
488
|
srate = self.samplerate
|
|
@@ -488,9 +490,10 @@ class Decoder:
|
|
|
488
490
|
left_window_boundary = silence_center_position - half_window
|
|
489
491
|
right_window_boundary = silence_center_position + half_window
|
|
490
492
|
# margin = 0.75 * srate
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
493
|
+
values = np.array([left_window_boundary, right_window_boundary,
|
|
494
|
+
silence_center_position])
|
|
495
|
+
values += self.sound_extract_position # samples pos in file
|
|
496
|
+
logger.debug('silent zone, left: %i, right %i, center %i'%tuple(values))
|
|
494
497
|
self.silent_zone_indices = (left_window_boundary, right_window_boundary)
|
|
495
498
|
return self.silent_zone_indices
|
|
496
499
|
|
|
@@ -520,7 +523,7 @@ class Decoder:
|
|
|
520
523
|
x_convolution, convolution = self._get_square_convolution()
|
|
521
524
|
scaled_convo = self._get_signal_level()*convolution
|
|
522
525
|
# since 0 < convolution < 1
|
|
523
|
-
trig_level = self.
|
|
526
|
+
trig_level = self._get_pulse_detection_level()
|
|
524
527
|
sound_extract_position = self.sound_extract_position
|
|
525
528
|
def x2f(nx):
|
|
526
529
|
return nx + sound_extract_position
|
|
@@ -545,7 +548,7 @@ class Decoder:
|
|
|
545
548
|
approx_pulse_x, 0.1, 0.9,
|
|
546
549
|
transform=xt, linewidth=1, colors='yellow'
|
|
547
550
|
)
|
|
548
|
-
bfsk_min = self._get_minimal_bfsk(
|
|
551
|
+
bfsk_min = self._get_minimal_bfsk()
|
|
549
552
|
ax.hlines(
|
|
550
553
|
bfsk_min, 0, 1,
|
|
551
554
|
transform=yt, linewidth=1, colors='red'
|
|
@@ -598,15 +601,18 @@ class Decoder:
|
|
|
598
601
|
|
|
599
602
|
def _detect_sync_pulse_position(self):
|
|
600
603
|
"""
|
|
601
|
-
Determines noise level during silence period and use it to detect
|
|
602
|
-
|
|
604
|
+
Determines noise level during silence period and use it to detect the
|
|
605
|
+
sync pulse position. Computes SN_ratio and stores it. Start searching
|
|
606
|
+
around end of silent zone. Adjustment are made so a complete 0.5
|
|
607
|
+
second signal is at the right of the starting search position so a
|
|
608
|
+
complete 0.5 s word is available for decoding.
|
|
609
|
+
|
|
603
610
|
Returns the pulse position relative to the extract beginning.
|
|
604
611
|
"""
|
|
605
|
-
|
|
606
|
-
pulse_level = self._get_pulse_level()
|
|
612
|
+
pulse_detection_level = self._get_pulse_detection_level()
|
|
607
613
|
abs_signal = abs(self.sound_extract)
|
|
608
614
|
mean_during_word = 2*abs_signal.mean()
|
|
609
|
-
self.SN_ratio = 20*math.log10(mean_during_word/
|
|
615
|
+
self.SN_ratio = 20*math.log10(mean_during_word/pulse_detection_level)
|
|
610
616
|
logger.debug('SN ratio: %f dB'%(self.SN_ratio))
|
|
611
617
|
search_pulse_start_point = self._get_pulse_position()
|
|
612
618
|
search_pulse_start_point -= 3*SYMBOL_LENGTH*1e-3*self.samplerate
|
|
@@ -624,17 +630,20 @@ class Decoder:
|
|
|
624
630
|
logger.debug('search_pulse_start_point: %i in extract'%
|
|
625
631
|
search_pulse_start_point)
|
|
626
632
|
abs_signal_after_silence = abs_signal[search_pulse_start_point:]
|
|
633
|
+
# here the real searching with numpy.argmax()
|
|
627
634
|
first_point = \
|
|
628
|
-
|
|
635
|
+
np.argmax(abs_signal_after_silence > pulse_detection_level)
|
|
629
636
|
first_point += search_pulse_start_point
|
|
630
637
|
logger.debug('found sync pulse at %i in extract'%first_point)
|
|
638
|
+
self.detected_pulse_position = first_point
|
|
631
639
|
return first_point
|
|
632
640
|
|
|
633
|
-
def _get_word_width_parameters(self
|
|
641
|
+
def _get_word_width_parameters(self):
|
|
634
642
|
abs_signal = abs(self.sound_extract)
|
|
643
|
+
pulse_position = self._get_pulse_position()
|
|
635
644
|
# half_amplitude = abs_signal.mean() # since 50% duty cycle OLD
|
|
636
645
|
# params = {'word_width_threshold':WORDWIDTHFACTOR*half_amplitude} OLD
|
|
637
|
-
bfsk_min = self._get_minimal_bfsk(
|
|
646
|
+
bfsk_min = self._get_minimal_bfsk()
|
|
638
647
|
params = {'word_width_threshold': 0.8*bfsk_min}
|
|
639
648
|
sr = self.samplerate
|
|
640
649
|
presumed_symbol_length = SYMBOL_LENGTH*1e-3*sr
|
|
@@ -649,10 +658,11 @@ class Decoder:
|
|
|
649
658
|
params['presumed_symbol_length'] = presumed_symbol_length
|
|
650
659
|
return params
|
|
651
660
|
|
|
652
|
-
def _get_BFSK_word_boundaries(self
|
|
661
|
+
def _get_BFSK_word_boundaries(self):
|
|
653
662
|
n_bits = N_SYMBOLS_SAMD21 - 1
|
|
654
663
|
sr = self.samplerate
|
|
655
|
-
wwp = self._get_word_width_parameters(
|
|
664
|
+
wwp = self._get_word_width_parameters()
|
|
665
|
+
pulse_position = self._get_pulse_position()
|
|
656
666
|
# search_start_position = wwp['search_start_position']
|
|
657
667
|
search_end_position = wwp['search_end_position']
|
|
658
668
|
word_width_threshold = wwp['word_width_threshold']
|
|
@@ -689,10 +699,11 @@ class Decoder:
|
|
|
689
699
|
logger.debug(' relative discrepancy %.4f%%'%(abs(100*relative_error)))
|
|
690
700
|
return status, left_boundary, right_boundary
|
|
691
701
|
|
|
692
|
-
def _get_BFSK_symbols_boundaries(self
|
|
702
|
+
def _get_BFSK_symbols_boundaries(self):
|
|
693
703
|
# returns indices of start of each slice and boundaries
|
|
704
|
+
pulse_position = self._get_pulse_position()
|
|
694
705
|
boundaries_OK, left_boundary, right_boundary = \
|
|
695
|
-
self._get_BFSK_word_boundaries(
|
|
706
|
+
self._get_BFSK_word_boundaries()
|
|
696
707
|
if left_boundary is None:
|
|
697
708
|
return None, None, None
|
|
698
709
|
symbol_width_samples = \
|
|
@@ -741,7 +752,8 @@ class Decoder:
|
|
|
741
752
|
logger.debug('slicing intervals, word_intervals = %s'%
|
|
742
753
|
word_intervals)
|
|
743
754
|
# skip sample after pulse, start at BFSK word
|
|
744
|
-
|
|
755
|
+
filtered_sound_extract = self._band_pass_filter(self.sound_extract)
|
|
756
|
+
slices = [filtered_sound_extract[slice(*pair)]
|
|
745
757
|
for pair in word_intervals]
|
|
746
758
|
np.set_printoptions(threshold=5)
|
|
747
759
|
# logger.debug('data slices: \n%s'%pprint.pformat(slices))
|
|
@@ -756,13 +768,17 @@ class Decoder:
|
|
|
756
768
|
freq_in_hertz = abs(freq * self.samplerate)
|
|
757
769
|
return int(round(freq_in_hertz))
|
|
758
770
|
|
|
771
|
+
# def _get_bit_from_freq(self, freq):
|
|
772
|
+
# if math.isclose(freq, F1, abs_tol=FSK_TOLERANCE):
|
|
773
|
+
# return '0'
|
|
774
|
+
# if math.isclose(freq, F2, abs_tol=FSK_TOLERANCE):
|
|
775
|
+
# return '1'
|
|
776
|
+
# else:
|
|
777
|
+
# return None
|
|
778
|
+
|
|
759
779
|
def _get_bit_from_freq(self, freq):
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
if math.isclose(freq, F2, abs_tol=FSK_TOLERANCE):
|
|
763
|
-
return '1'
|
|
764
|
-
else:
|
|
765
|
-
return None
|
|
780
|
+
mid_FSK = 0.5*(F1 + F2)
|
|
781
|
+
return '1' if freq > mid_FSK else '0'
|
|
766
782
|
|
|
767
783
|
def _get_int_from_binary_str(self, string_of_01s):
|
|
768
784
|
return int(''.join(reversed(string_of_01s)),2)
|
|
@@ -791,15 +807,14 @@ class Decoder:
|
|
|
791
807
|
# save figure in filename if set, otherwise
|
|
792
808
|
# start an interactive plot, title is for matplotlib
|
|
793
809
|
signal = self.sound_extract
|
|
810
|
+
# signal = self._band_pass_filter(signal)
|
|
794
811
|
start = self.sound_extract_position
|
|
795
812
|
x_signal_in_file = range(
|
|
796
813
|
start,
|
|
797
814
|
start + len(signal)
|
|
798
815
|
)
|
|
816
|
+
wwp = self._get_word_width_parameters()
|
|
799
817
|
start_silent_zone, end_silent_zone = self._get_silent_zone_indices()
|
|
800
|
-
# sync_pulse = self._detect_sync_pulse_position()
|
|
801
|
-
wwp = self._get_word_width_parameters(sync_pulse)
|
|
802
|
-
# search_start_position = wwp['search_start_position'] + start
|
|
803
818
|
search_end_position = wwp['search_end_position'] + start
|
|
804
819
|
fig, ax = plt.subplots()
|
|
805
820
|
plt.title(title)
|
|
@@ -836,9 +851,8 @@ class Decoder:
|
|
|
836
851
|
[end_silent_zone + start], [0],
|
|
837
852
|
marker='<', markersize='10',
|
|
838
853
|
linewidth=0.3, color='green', alpha=0.3)
|
|
839
|
-
# symbols_indices = self._get_BFSK_symbols_boundaries(sync_pulse)
|
|
840
854
|
boundaries_OK, word_lft, word_rght = \
|
|
841
|
-
self._get_BFSK_word_boundaries(
|
|
855
|
+
self._get_BFSK_word_boundaries()
|
|
842
856
|
ax.vlines(
|
|
843
857
|
word_lft + start, 0, 1,
|
|
844
858
|
transform=ax.get_xaxis_transform(),
|
|
@@ -869,18 +883,27 @@ class Decoder:
|
|
|
869
883
|
dpi=height/fig.get_size_inches()[1])
|
|
870
884
|
plt.close()
|
|
871
885
|
|
|
886
|
+
def _band_pass_filter(self, data):
|
|
887
|
+
# return filtered data
|
|
888
|
+
def _bandpass(data: np.ndarray, edges: list[float], sample_rate: float, poles: int = 5):
|
|
889
|
+
sos = scipy.signal.butter(poles, edges, 'bandpass', fs=sample_rate, output='sos')
|
|
890
|
+
filtered_data = scipy.signal.sosfiltfilt(sos, data)
|
|
891
|
+
return filtered_data
|
|
892
|
+
sample_rate = self.samplerate
|
|
893
|
+
times = np.arange(len(data))/sample_rate
|
|
894
|
+
return _bandpass(data, [BPF_LOW_FRQ, BPF_HIGH_FRQ], sample_rate)
|
|
895
|
+
|
|
872
896
|
def get_time_in_sound_extract(self, plots):
|
|
873
897
|
if self.sound_extract is None:
|
|
874
898
|
return None
|
|
875
899
|
if plots:
|
|
876
900
|
self.make_silence_analysis_plot()
|
|
877
|
-
start_silent_zone, end_silent_zone = self._get_silent_zone_indices()
|
|
878
901
|
pulse_position = self._detect_sync_pulse_position()
|
|
879
902
|
pulse_pos_in_file = pulse_position + self.sound_extract_position
|
|
880
903
|
pulse_position_sec = pulse_pos_in_file/self.samplerate
|
|
881
904
|
logger.debug('found sync pulse at sample %i in file'%pulse_pos_in_file)
|
|
882
905
|
symbols_indices, word_lft, word_rght = \
|
|
883
|
-
self._get_BFSK_symbols_boundaries(
|
|
906
|
+
self._get_BFSK_symbols_boundaries()
|
|
884
907
|
if plots:
|
|
885
908
|
title = 'Bit slicing at %s, %.2f s'%(pulse_pos_in_file,
|
|
886
909
|
pulse_position_sec)
|
|
@@ -891,6 +914,10 @@ class Decoder:
|
|
|
891
914
|
if symbols_indices is None:
|
|
892
915
|
return None
|
|
893
916
|
sliced_data = self._slice_sound_extract(symbols_indices)
|
|
917
|
+
# sliced_data = [self._band_pass_filter(data_slice)
|
|
918
|
+
# for data_slice
|
|
919
|
+
# in sliced_data
|
|
920
|
+
# ]
|
|
894
921
|
frequencies = [self._get_main_frequency(data_slice)
|
|
895
922
|
for data_slice
|
|
896
923
|
in sliced_data
|
|
@@ -902,12 +929,12 @@ class Decoder:
|
|
|
902
929
|
length_ratio = eff_symbol_length / SYMBOL_LENGTH
|
|
903
930
|
logger.debug('symbol length_ratio (eff/supposed) %f'%length_ratio)
|
|
904
931
|
corrected_freq = np.array(frequencies)*length_ratio
|
|
905
|
-
logger.debug('
|
|
932
|
+
logger.debug('corrected freq (using symbol length) = %s'%corrected_freq)
|
|
906
933
|
bits = [self._get_bit_from_freq(f) for f in corrected_freq]
|
|
907
934
|
for i, bit in enumerate(bits):
|
|
908
935
|
if bit == None:
|
|
909
936
|
logger.warning('cant decode frequency %i for bit at %i-%i'%(
|
|
910
|
-
|
|
937
|
+
corrected_freq[i],
|
|
911
938
|
symbols_indices[i],
|
|
912
939
|
symbols_indices[i+1]))
|
|
913
940
|
if None in bits:
|
|
@@ -923,8 +950,6 @@ class Decoder:
|
|
|
923
950
|
time_values['clock source'] = 'GPS' \
|
|
924
951
|
if time_values['clock source'] == 1 else 'RTC'
|
|
925
952
|
if self._demod_values_are_OK(time_values):
|
|
926
|
-
self.pulse_position['value'] = pulse_position
|
|
927
|
-
self.pulse_position['type'] = 'detected'
|
|
928
953
|
return time_values
|
|
929
954
|
else:
|
|
930
955
|
return None
|
|
@@ -940,10 +965,10 @@ class Recording:
|
|
|
940
965
|
media_without_YaLTC : pathlib.path
|
|
941
966
|
path of video+sound file stripped of YaLTC channel
|
|
942
967
|
|
|
943
|
-
device: str
|
|
968
|
+
device : str
|
|
944
969
|
identifies the device used for the recording, set in __init__()
|
|
945
970
|
|
|
946
|
-
new_rec_name: str
|
|
971
|
+
new_rec_name : str
|
|
947
972
|
built using the device name, ex: "CAM_A001"
|
|
948
973
|
set by Timeline._rename_all_recs()
|
|
949
974
|
|
|
@@ -958,25 +983,25 @@ class Recording:
|
|
|
958
983
|
decoder : yaltc.decoder
|
|
959
984
|
associated decoder object, if file is audiovideo
|
|
960
985
|
|
|
961
|
-
true_samplerate: float
|
|
986
|
+
true_samplerate : float
|
|
962
987
|
true sample rate using GPS time
|
|
963
988
|
|
|
964
|
-
start_time: datetime or str
|
|
989
|
+
start_time : datetime or str
|
|
965
990
|
time and date of the first sample in the file, cached
|
|
966
991
|
after a call to get_start_time(). Value on initialization
|
|
967
992
|
is None.
|
|
968
993
|
|
|
969
|
-
sync_position: int
|
|
994
|
+
sync_position : int
|
|
970
995
|
position of first detected syn pulse
|
|
971
996
|
|
|
972
|
-
is_reference: bool (True for ref rec only)
|
|
997
|
+
is_reference : bool (True for ref rec only)
|
|
973
998
|
in multi recorders set-ups, user decides if a sound-only recording
|
|
974
999
|
is the time reference for all other audio recordings. By
|
|
975
1000
|
default any video recording is the time reference for other audio,
|
|
976
1001
|
so this attribute is only relevant to sound recordings and is
|
|
977
1002
|
implicitly True for each video recordings (but not set)
|
|
978
1003
|
|
|
979
|
-
device_relative_speed: float
|
|
1004
|
+
device_relative_speed : float
|
|
980
1005
|
the ratio of the recording device clock speed relative to the
|
|
981
1006
|
reference_rec clock device, in order to correct clock drift with
|
|
982
1007
|
pysox tempo transform. If value < 1.0 then the recording is slower
|
|
@@ -985,7 +1010,7 @@ class Recording:
|
|
|
985
1010
|
sound). A mean is calculated for all recordings of the same device
|
|
986
1011
|
in Montage._get_concatenated_audiofile_for()
|
|
987
1012
|
|
|
988
|
-
time_position: float
|
|
1013
|
+
time_position : float
|
|
989
1014
|
The time (in seconds) at which the recording starts relative to the
|
|
990
1015
|
reference recording. Updated by each Montage instance so the value
|
|
991
1016
|
can change depending on the reference recording (a video or main
|
|
@@ -1000,7 +1025,7 @@ class Recording:
|
|
|
1000
1025
|
contains the path of audio only of self.final_synced_file. Absolute
|
|
1001
1026
|
path to tempfile.
|
|
1002
1027
|
|
|
1003
|
-
in_cam_audio_sync_error: int
|
|
1028
|
+
in_cam_audio_sync_error : int
|
|
1004
1029
|
in cam audio sync error, read in the camera folder. Negative value
|
|
1005
1030
|
for lagging video (audio leads) positive value for lagging audio
|
|
1006
1031
|
(video leads)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
tictacsync/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
tictacsync/device_scanner.py,sha256=ybMGhSPbrfFvSWEjv0zYtwDSClVQRfGYQVJcakpuU54,18522
|
|
3
|
+
tictacsync/entry.py,sha256=kboDNw_VA5-bOPMiJS6JMftqlTW0FsHxrsHMfW5ckp0,8689
|
|
4
|
+
tictacsync/timeline.py,sha256=q0jhHqhj2cdjL4UIWv6_dAC7VfjQUb69Tu4VP5kULzg,34829
|
|
5
|
+
tictacsync/yaltc.py,sha256=qWLhV2PP1KWoLmSVlSGGYCiCzImm6bnbPTVYilYrzzQ,69123
|
|
6
|
+
tictacsync-0.1a11.dist-info/LICENSE,sha256=ZAOPXLh1zlQAnhHUd7oLslKM01YZ5UiAu3STYjwIxck,1068
|
|
7
|
+
tictacsync-0.1a11.dist-info/METADATA,sha256=RvufymvGNYowOv8iwEREhXTHXT96VaqtyRn1iFui4Y0,4256
|
|
8
|
+
tictacsync-0.1a11.dist-info/WHEEL,sha256=pqI-DBMA-Z6OTNov1nVxs7mwm6Yj2kHZGNp_6krVn1E,92
|
|
9
|
+
tictacsync-0.1a11.dist-info/entry_points.txt,sha256=7Ih9Xas4RWMDqt2adwXpt7x9j2YtXwj_jl-jNhkIArg,54
|
|
10
|
+
tictacsync-0.1a11.dist-info/top_level.txt,sha256=eaCWG-BsYTRR-gLTJbK4RfcaXajr0gjQ6wG97MkGRrg,11
|
|
11
|
+
tictacsync-0.1a11.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
tictacsync/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
tictacsync/device_scanner.py,sha256=8xz2Ztnnml2y5K6-XkobvcbfoOcVo9eXSQKqcI6ysHU,17889
|
|
3
|
-
tictacsync/entry.py,sha256=nLSxP0WxH02-3s1asqV_N81mgHvvCoeE5cwNAbPChw8,8698
|
|
4
|
-
tictacsync/timeline.py,sha256=q0jhHqhj2cdjL4UIWv6_dAC7VfjQUb69Tu4VP5kULzg,34829
|
|
5
|
-
tictacsync/yaltc.py,sha256=kq6qgP5R4zTcgUAM2AMXZEu1er-arWQJ11gNELmVF-Q,67508
|
|
6
|
-
tictacsync-0.1a10.dist-info/LICENSE,sha256=ZAOPXLh1zlQAnhHUd7oLslKM01YZ5UiAu3STYjwIxck,1068
|
|
7
|
-
tictacsync-0.1a10.dist-info/METADATA,sha256=KTkUJlrf3VNkUdt0zMMzXVASEFz4nB5ST5Q2m12x6_k,4256
|
|
8
|
-
tictacsync-0.1a10.dist-info/WHEEL,sha256=pqI-DBMA-Z6OTNov1nVxs7mwm6Yj2kHZGNp_6krVn1E,92
|
|
9
|
-
tictacsync-0.1a10.dist-info/entry_points.txt,sha256=7Ih9Xas4RWMDqt2adwXpt7x9j2YtXwj_jl-jNhkIArg,54
|
|
10
|
-
tictacsync-0.1a10.dist-info/top_level.txt,sha256=eaCWG-BsYTRR-gLTJbK4RfcaXajr0gjQ6wG97MkGRrg,11
|
|
11
|
-
tictacsync-0.1a10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|