#!/usr/bin/python
from pyhydrophone.hydrophone import Hydrophone
import os
import numpy as np
import soundfile as sf
import datetime
try:
import scipy.signal as sig
except ModuleNotFoundError:
pass
[docs]class BruelKjaer(Hydrophone):
"""
Init an instance of B&K Nexus.
Check well the Vpp in case you don't have a reference signal! Specially of the recorder used.
Parameters
----------
name: str
Name of the acoustic recorder
model: str or int
Model of the acoustic recorder
serial_number : str or int
Serial number of the acoustic recorder
preamp_gain: float
Amplification selected in the Nexus in db 10*log10((V/uPa)^2)
Vpp: float
Volts peak to peak
string_format: string
Format of the datetime string present in the filename
type_signal : str
Can be 'ref' or 'test'
calibration_file : string or Path
File where the frequency dependent sensitivity values for the calibration are
"""
def __init__(self, name, model, serial_number, preamp_gain, Vpp=2.0, string_format="%y%m%d%H%M%S", type_signal='ref',
max_calibration_time=120.0, calibration_file=None, **kwargs):
self.amplif = np.sqrt(10**(preamp_gain/10)) * 1e6
self.preamp_gain = preamp_gain
self.type_signal = type_signal
self.max_calibration_time = max_calibration_time
self.cal_freq = 159.9
self.min_duration = 10.0
super().__init__(name, model, serial_number, sensitivity=0.0, preamp_gain=preamp_gain,
Vpp=Vpp, string_format=string_format, calibration_file=calibration_file, **kwargs)
def __setattr__(self, name, value):
"""
If the amplif is changed, update the preamp_gain
"""
if name == 'amplif':
self.__dict__['preamp_gain'] = 10 * np.log10((value/1e6)**2)
if name == 'preamp_gain':
self.__dict__['amplif'] = np.sqrt(10**(value/10)) * 1e6
if name == 'type_signal':
if value not in ['ref', 'test']:
raise Exception('%s is not an option in Nexus!' % value)
return super().__setattr__(name, value)
[docs] def get_name_datetime(self, file_name):
"""
Get the data and time of recording from the name of the file
Parameters
----------
file_name : string
File name (not path) of the file
"""
name = file_name.split('.')
date_string = name[0].split('_')[0]
date = super().get_name_datetime(date_string)
return date
[docs] def get_new_name(self, filename, new_date):
"""
Replace the datetime with the appropriate one
Parameters
----------
filename : string
File name (not path) of the file
new_date : datetime object
New datetime to be replaced in the filename
"""
old_date = self.get_name_datetime(filename)
old_date_name = datetime.datetime.strftime(old_date, self.string_format)
new_date_name = datetime.datetime.strftime(new_date, self.string_format)
new_filename = filename.replace(old_date_name, new_date_name)
return new_filename
[docs] def update_calibration(self, ref_signal):
"""
Update the calibration
Parameters
----------
ref_signal : str or Path
File path to the reference file to update the Vpp according to the calibration tone
"""
ref_wav = np.sqrt((ref_signal ** 2).mean())
# Sensitivity is in negative values!
if self.type_signal == 'ref':
# The signal is then 1 V
ref_v = 1
else:
# The signal is the amplification value
ref_v = 100 * self.amplif * np.sqrt(2)
self.preamp_gain = 10 * np.log10((self.amplif / 1e6) ** 2) + 10*np.log10((ref_wav / ref_v)**2)
[docs] def calibrate(self, file_path):
"""
Find the beginning and ending sample of the calibration tone
Returns start and end points, in seconds
Parameters
----------
file_path : string or Path
File where to look for the calibration (at the beginning of the file)
Returns
-------
end sample of the calibration (int)
"""
wav_file = sf.SoundFile(file_path)
tone_samples = int(self.max_calibration_time * wav_file.samplerate)
first_part = wav_file.read(frames=tone_samples)
analytic_signal = sig.hilbert(first_part)
amplitude_envelope = np.abs(analytic_signal)
possible_points = np.zeros(amplitude_envelope.shape)
possible_points[np.where(amplitude_envelope >= 0.05)] = 1
start_points = np.where(np.diff(possible_points) == 1)[0]
end_points = np.where(np.diff(possible_points) == -1)[0]
if start_points.size == 0:
return None
if end_points[0] < start_points[0]:
end_points = end_points[1:]
if start_points.size != end_points.size:
start_points = start_points[0:end_points.size]
select_idx = np.argmax(end_points - start_points)
# Round to a second
start = int(start_points[select_idx])
end = int(end_points[select_idx])
if (end - start) / wav_file.samplerate < self.min_duration:
return 0
self.update_calibration(first_part[start:end])
return end