imptube.tube
In this module, three main classes are defined - Measurement, Tube and Sample.
1'''In this module, three main classes are defined - Measurement, Tube and Sample. 2''' 3 4import sys 5import sounddevice as sd 6import numpy as np 7import pandas as pd 8import soundfile as sf 9import os 10from scipy.signal import chirp 11from scipy.signal.windows import hann 12from time import sleep, strftime 13from imptube.utils import make_foldertree 14from imptube.processing import ( 15 calibration_from_files, 16 transfer_function_from_path, 17 alpha_from_path, 18 harmonic_distortion_filter, 19 calc_rms_pressure_level 20) 21from typing import Protocol 22import logging 23 24 25class Measurement: 26 """Contains information about measurement from the perspective of 27 signal and boundary conditions. 28 29 Attributes 30 ---------- 31 fs : int 32 measurement sampling frequency 33 channels_in : list[int] 34 list of input channel numbers 35 channels_out : list[int] 36 list of output channel numbers (usually one member list) 37 device : str 38 string specifying part of sound card name 39 List of available devices can be obtained with 40 `python3 -m sounddevice` command. 41 samples : int 42 number of samples in the generated log sweep 43 typically 2**n 44 window_len : int 45 length of the Hann half-window applied to the ends of the sweep 46 sub_measurements : int 47 number of measurements taken for each specimen 48 Normally, no differences between sweep measurements should occur, 49 this attribute mainly compensates for potential playback artifacts. 50 f_low : int 51 lower frequency limit for the generated sweep 52 f_high : int 53 higher frequency limit for the generated sweep 54 fs_to_spl : float 55 conversion level from dBFS to dB SPL for microphone 1 56 sweep_lvl : float 57 level of the sweep in dBFS 58 """ 59 60 def __init__( 61 self, 62 fs : int=48000, 63 channels_in : list[int]=[1,2], 64 channels_out : list[int]=[1], 65 device : str='Scarlett', 66 samples : int=131072, 67 window_len : int=8192, 68 sub_measurements : int=2, 69 f_low : int=10, 70 f_high : int=1000, 71 fs_to_spl : float=130, 72 sweep_lvl : float=-6 73 ): 74 self.fs = fs 75 self.channels_in = channels_in 76 self.channels_out = channels_out 77 self.device = device 78 self.samples = samples 79 self.window_len = window_len 80 self.sub_measurements = sub_measurements 81 self.f_limits = [f_low, f_high] 82 self.fs_to_spl = fs_to_spl 83 self.sweep_lvl = sweep_lvl 84 85 self.make_sweep() 86 sd.default.samplerate = fs 87 sd.default.channels = len(channels_in), len(channels_out) 88 sd.default.device = device 89 90 91 def make_sweep(self) -> np.ndarray: 92 """Generates numpy array with log sweep. 93 94 Parameters 95 ---------- 96 fs : int 97 measurement sampling frequency 98 samples : int 99 number of samples in the generated log sweep 100 typically 2**n 101 window_len : int 102 length of the Hann half-window applied to the ends 103 of the sweep 104 f_low : int 105 lower frequency limit for the generated sweep 106 f_high : int 107 higher frequency limit for the generated sweep 108 109 Returns 110 ------- 111 log_sweep : np.ndarray 112 numpy array containing mono log sweep 113 """ 114 t = np.linspace(0,self.samples/self.fs,self.samples, dtype=np.float32) 115 116 half_win = int(self.window_len) 117 log_sweep = chirp(t, self.f_limits[0], t[-1], self.f_limits[1], method="log", phi=90) 118 window = hann(int(self.window_len*2)) 119 120 log_sweep[:half_win] = log_sweep[:half_win]*window[:half_win] 121 log_sweep[-half_win:] = log_sweep[-half_win:]*window[half_win:] 122 lvl_to_factor = 10**(self.sweep_lvl/20) 123 log_sweep = log_sweep*lvl_to_factor 124 125 self.sweep = log_sweep 126 return log_sweep 127 128 def regen_sweep(self): 129 """Regenerates the sweep.""" 130 self.make_sweep() 131 132 def measure(self, 133 out_path : str='', 134 thd_filter : bool=True, 135 export : bool=True 136 ) -> tuple[np.ndarray, int]: 137 """Performs measurement and saves the recording. 138 139 Parameters 140 ---------- 141 out_path : str 142 path where the recording should be saved, including filename 143 thd_filter : bool 144 enables harmonic distortion filtering 145 This affects the files saved. 146 export : bool 147 enables export to specified path 148 149 Returns 150 ------- 151 data : np.ndarray 152 measured audio data 153 fs : int 154 sampling rate 155 """ 156 data = sd.playrec( 157 self.sweep, 158 input_mapping=self.channels_in, 159 output_mapping=self.channels_out) 160 sd.wait() 161 data = np.asarray(data) 162 163 #filtration 164 if thd_filter: 165 data = data.T 166 data = harmonic_distortion_filter( 167 data, 168 self.sweep, 169 f_low=self.f_limits[0], 170 f_high=self.f_limits[1] 171 ) 172 data = data.T 173 174 if export: 175 sf.write( 176 file=out_path, 177 data=data, 178 samplerate=self.fs, 179 format='WAV', 180 subtype='FLOAT' 181 ) 182 183 return data, self.fs 184 185class Tube: 186 """Class representing tube geometry. 187 188 Attributes 189 ---------- 190 further_mic_dist : float 191 further microphone distance from sample 192 closer_mic_dist : float 193 closer mic distance from sample 194 freq_limit : int 195 higher frequency limit for exports 196 """ 197 def __init__(self, 198 further_mic_dist : float=0.400115, #x_1 199 closer_mic_dist : float=0.101755, #x_2 200 freq_limit : int=2000, 201 ): 202 self.further_mic_dist = further_mic_dist 203 self.closer_mic_dist = closer_mic_dist 204 self.mic_spacing = further_mic_dist - closer_mic_dist 205 self.freq_limit = freq_limit 206 207class Sample: 208 """A class representing sample and its boundary conditions. 209 210 Attributes 211 ---------- 212 name : str 213 name of the sample 214 temperature : float 215 ambient temperature in degC 216 rel_humidity : float 217 ambient relative humidity in % 218 tube : Tube 219 impedance tube definition object 220 timestamp : str 221 strftime timestamp in a format '%y-%m-%d_%H-%M' 222 folder : str 223 path to project data folder, defaults to "data" 224 """ 225 def __init__(self, 226 name : str, 227 temperature : float, 228 rel_humidity : float, 229 atm_pressure : float = 101325, 230 tube : Tube=Tube(), 231 timestamp : str = strftime("%y-%m-%d_%H-%M"), 232 folder = "data", 233 ): 234 self.name = name 235 self.timestamp = timestamp 236 self.temperature = temperature 237 self.atm_pressure = atm_pressure 238 self.rel_humidity = rel_humidity 239 self.tube = tube 240 self.folder = folder 241 self.trees = make_foldertree( 242 self.name, 243 self.folder, 244 self.timestamp 245 ) 246 bound_dict = { 247 'temp': [self.temperature], 248 'RH': [self.rel_humidity], 249 'atm_pressure': [self.atm_pressure], 250 'x1': [self.tube.further_mic_dist], 251 'x2': [self.tube.closer_mic_dist], 252 'lim': [self.tube.freq_limit], 253 'dbfs_to_spl': [130], 254 } 255 self.boundary_df = pd.DataFrame(bound_dict) 256 self.boundary_df.to_csv( 257 os.path.join( 258 self.trees[2],self.trees[1]+"_bound_cond.csv" 259 ) 260 ) 261 262 def migrate_cal(self, cal_name, cal_stamp, cal_parent="data"): 263 """Migrates calibration files from different measurement. 264 265 Parameters 266 ---------- 267 cal_name : str 268 calibration sample name 269 cal_stamp : str 270 calibration sample timestamp i a '%y-%m-%d_%H-%M' format 271 cal_parent : str 272 parent data folder, defaults to 'data' 273 """ 274 cal_trees = make_foldertree( 275 variant=cal_name, 276 time_stamp=cal_stamp, 277 parent=cal_parent 278 ) 279 cal_parent_folder = cal_trees[2] 280 import_folder = cal_trees[3][1] 281 freqs = np.load( 282 os.path.join(cal_parent_folder, cal_trees[1]+"_freqs.npy") 283 ) #freq import 284 cf = np.load( 285 os.path.join(import_folder, cal_trees[1]+"_cal_f_12.npy") 286 ) #cf import 287 288 parent_folder = self.trees[2] 289 export_folder = self.trees[3][1] 290 np.save( 291 os.path.join(parent_folder, self.trees[1]+"_freqs.npy"), 292 freqs 293 ) #freq export 294 np.save( 295 os.path.join(export_folder, self.trees[1]+"_cal_f_12.npy"), 296 cf 297 ) #cf export 298 299def calibration( 300 sample : Sample, 301 measurement : Measurement, 302 thd_filter = True 303 ): 304 """Performs CLI calibration measurement. 305 306 Parameters 307 ---------- 308 309 sample : imptube.tube.Sample 310 311 measurement : Measurement 312 313 thd_filter : bool 314 Enables harmonic distortion filtering 315 """ 316 caltree = sample.trees[3][0] 317 if not os.path.exists(caltree): 318 os.makedirs(caltree) 319 320 m = measurement 321 running = True 322 while running: 323 for c in range(1, 3): 324 ready = input(f"Calibrate in configuration {c}? [Y/n]") 325 if ready.lower() == "n": 326 break 327 else: 328 for s in range(m.sub_measurements): 329 f = os.path.join(caltree, sample.trees[1]+f"_cal_wav_conf{c}_{s}.wav") 330 print(f) 331 m.measure(f, thd_filter=thd_filter) 332 sleep(0.5) 333 if input("Repeat calibration process? [y/N]").lower() == "y": 334 continue 335 else: 336 running = False 337 input("Move the microphones to original position before measurement!") 338 339 return calibration_from_files(parent_folder=sample.trees[2]) 340 341def single_measurement( 342 sample : Sample, 343 measurement : Measurement, 344 depth : float, 345 thd_filter : bool= True, 346 calc_spl : bool = True 347 ) -> tuple[list[np.ndarray], int]: 348 """Performs measurement. 349 350 Parameters 351 ---------- 352 353 sample : imptube.tube.Sample 354 355 measurement : Measurement 356 357 depth : float 358 current depth of the sample 359 thd_filter : bool 360 Enables harmonic distortion filtering 361 362 Returns 363 ------- 364 sub_measurement_data : list[np.ndarray] 365 list of audio recordings taken 366 fs : float 367 sampling rate of the recording 368 """ 369 m = measurement 370 sub_measurement_data = [] 371 for s in range(m.sub_measurements): 372 f = os.path.join(sample.trees[4][0], sample.trees[1]+f"_wav_d{depth}_{s}.wav") 373 data, fs = m.measure(f, thd_filter=thd_filter) 374 sub_measurement_data.append(data) 375 sleep(0.5) 376 377 if calc_spl: 378 rms_spl = calc_rms_pressure_level(data.T[0], m.fs_to_spl) 379 logging.info(f"RMS SPL: {rms_spl} dB") 380 m.rms_spl = rms_spl 381 return sub_measurement_data, fs 382 383def calculate_alpha( 384 sample : Sample, 385 return_r : bool = False, 386 return_z : bool = False, 387 ) -> tuple[np.ndarray, np.ndarray]: 388 """Performs transfer function and alpha calculations from audio data 389 found in a valid folder structure. 390 391 Parameters 392 ---------- 393 sample : Sample 394 395 Returns 396 ------- 397 alpha : np.ndarray 398 sound absorption coefficient for frequencies lower than 399 limit specified in sample.tube.freq_limit 400 freqs : np.ndarray 401 frequency values for the alpha array 402 """ 403 sample.unique_d, sample.tfs = transfer_function_from_path(sample.trees[2]) 404 results = alpha_from_path( 405 sample.trees[2], 406 return_f=True, 407 return_r=return_r, 408 return_z=return_z 409 ) 410 return results 411 412class Sensor(Protocol): 413 """A protocol for Sensor class implementation.""" 414 def read_temperature(self) -> float: 415 ... 416 417 def read_humidity(self) -> float: 418 ... 419 420 def read_pressure(self) -> float: 421 ... 422 423def read_env_bc(sensor : Sensor) -> tuple[float, float, float]: 424 for i in range(5): 425 try: 426 temperature = sensor.read_temperature() 427 rel_humidity = sensor.read_humidity() 428 atm_pressure = sensor.read_pressure() 429 break 430 except: 431 print(f"Reading {i+1} not succesful.") 432 if i == 4: 433 print("Unable to read data from sensor, try manually enter temperature and RH on initialization.") 434 sys.exit() 435 return temperature, rel_humidity, atm_pressure 436 437 # TODO save bc as config file... 438 # bound_dict = { 439 # 'temp': [self.temperature], 440 # 'RH': [self.RH], 441 # 'x1': [self.x_1], 442 # 'x2': [self.x_2], 443 # 'lim': [self.limit], 444 # } 445 # self.boundary_df = pd.DataFrame(bound_dict) 446 # self.trees = make_foldertree(self.name, self.folder) 447 # self.boundary_df.to_csv( 448 # os.path.join( 449 # self.trees[2],self.trees[1]+"_bound_cond.csv" 450 # ) 451 # ) 452
26class Measurement: 27 """Contains information about measurement from the perspective of 28 signal and boundary conditions. 29 30 Attributes 31 ---------- 32 fs : int 33 measurement sampling frequency 34 channels_in : list[int] 35 list of input channel numbers 36 channels_out : list[int] 37 list of output channel numbers (usually one member list) 38 device : str 39 string specifying part of sound card name 40 List of available devices can be obtained with 41 `python3 -m sounddevice` command. 42 samples : int 43 number of samples in the generated log sweep 44 typically 2**n 45 window_len : int 46 length of the Hann half-window applied to the ends of the sweep 47 sub_measurements : int 48 number of measurements taken for each specimen 49 Normally, no differences between sweep measurements should occur, 50 this attribute mainly compensates for potential playback artifacts. 51 f_low : int 52 lower frequency limit for the generated sweep 53 f_high : int 54 higher frequency limit for the generated sweep 55 fs_to_spl : float 56 conversion level from dBFS to dB SPL for microphone 1 57 sweep_lvl : float 58 level of the sweep in dBFS 59 """ 60 61 def __init__( 62 self, 63 fs : int=48000, 64 channels_in : list[int]=[1,2], 65 channels_out : list[int]=[1], 66 device : str='Scarlett', 67 samples : int=131072, 68 window_len : int=8192, 69 sub_measurements : int=2, 70 f_low : int=10, 71 f_high : int=1000, 72 fs_to_spl : float=130, 73 sweep_lvl : float=-6 74 ): 75 self.fs = fs 76 self.channels_in = channels_in 77 self.channels_out = channels_out 78 self.device = device 79 self.samples = samples 80 self.window_len = window_len 81 self.sub_measurements = sub_measurements 82 self.f_limits = [f_low, f_high] 83 self.fs_to_spl = fs_to_spl 84 self.sweep_lvl = sweep_lvl 85 86 self.make_sweep() 87 sd.default.samplerate = fs 88 sd.default.channels = len(channels_in), len(channels_out) 89 sd.default.device = device 90 91 92 def make_sweep(self) -> np.ndarray: 93 """Generates numpy array with log sweep. 94 95 Parameters 96 ---------- 97 fs : int 98 measurement sampling frequency 99 samples : int 100 number of samples in the generated log sweep 101 typically 2**n 102 window_len : int 103 length of the Hann half-window applied to the ends 104 of the sweep 105 f_low : int 106 lower frequency limit for the generated sweep 107 f_high : int 108 higher frequency limit for the generated sweep 109 110 Returns 111 ------- 112 log_sweep : np.ndarray 113 numpy array containing mono log sweep 114 """ 115 t = np.linspace(0,self.samples/self.fs,self.samples, dtype=np.float32) 116 117 half_win = int(self.window_len) 118 log_sweep = chirp(t, self.f_limits[0], t[-1], self.f_limits[1], method="log", phi=90) 119 window = hann(int(self.window_len*2)) 120 121 log_sweep[:half_win] = log_sweep[:half_win]*window[:half_win] 122 log_sweep[-half_win:] = log_sweep[-half_win:]*window[half_win:] 123 lvl_to_factor = 10**(self.sweep_lvl/20) 124 log_sweep = log_sweep*lvl_to_factor 125 126 self.sweep = log_sweep 127 return log_sweep 128 129 def regen_sweep(self): 130 """Regenerates the sweep.""" 131 self.make_sweep() 132 133 def measure(self, 134 out_path : str='', 135 thd_filter : bool=True, 136 export : bool=True 137 ) -> tuple[np.ndarray, int]: 138 """Performs measurement and saves the recording. 139 140 Parameters 141 ---------- 142 out_path : str 143 path where the recording should be saved, including filename 144 thd_filter : bool 145 enables harmonic distortion filtering 146 This affects the files saved. 147 export : bool 148 enables export to specified path 149 150 Returns 151 ------- 152 data : np.ndarray 153 measured audio data 154 fs : int 155 sampling rate 156 """ 157 data = sd.playrec( 158 self.sweep, 159 input_mapping=self.channels_in, 160 output_mapping=self.channels_out) 161 sd.wait() 162 data = np.asarray(data) 163 164 #filtration 165 if thd_filter: 166 data = data.T 167 data = harmonic_distortion_filter( 168 data, 169 self.sweep, 170 f_low=self.f_limits[0], 171 f_high=self.f_limits[1] 172 ) 173 data = data.T 174 175 if export: 176 sf.write( 177 file=out_path, 178 data=data, 179 samplerate=self.fs, 180 format='WAV', 181 subtype='FLOAT' 182 ) 183 184 return data, self.fs
Contains information about measurement from the perspective of signal and boundary conditions.
Attributes
- fs (int): measurement sampling frequency
- channels_in (list[int]): list of input channel numbers
- channels_out (list[int]): list of output channel numbers (usually one member list)
- device (str):
string specifying part of sound card name
List of available devices can be obtained with
python3 -m sounddevice
command. - samples (int): number of samples in the generated log sweep typically 2**n
- window_len (int): length of the Hann half-window applied to the ends of the sweep
- sub_measurements (int): number of measurements taken for each specimen Normally, no differences between sweep measurements should occur, this attribute mainly compensates for potential playback artifacts.
- f_low (int): lower frequency limit for the generated sweep
- f_high (int): higher frequency limit for the generated sweep
- fs_to_spl (float): conversion level from dBFS to dB SPL for microphone 1
- sweep_lvl (float): level of the sweep in dBFS
92 def make_sweep(self) -> np.ndarray: 93 """Generates numpy array with log sweep. 94 95 Parameters 96 ---------- 97 fs : int 98 measurement sampling frequency 99 samples : int 100 number of samples in the generated log sweep 101 typically 2**n 102 window_len : int 103 length of the Hann half-window applied to the ends 104 of the sweep 105 f_low : int 106 lower frequency limit for the generated sweep 107 f_high : int 108 higher frequency limit for the generated sweep 109 110 Returns 111 ------- 112 log_sweep : np.ndarray 113 numpy array containing mono log sweep 114 """ 115 t = np.linspace(0,self.samples/self.fs,self.samples, dtype=np.float32) 116 117 half_win = int(self.window_len) 118 log_sweep = chirp(t, self.f_limits[0], t[-1], self.f_limits[1], method="log", phi=90) 119 window = hann(int(self.window_len*2)) 120 121 log_sweep[:half_win] = log_sweep[:half_win]*window[:half_win] 122 log_sweep[-half_win:] = log_sweep[-half_win:]*window[half_win:] 123 lvl_to_factor = 10**(self.sweep_lvl/20) 124 log_sweep = log_sweep*lvl_to_factor 125 126 self.sweep = log_sweep 127 return log_sweep
Generates numpy array with log sweep.
Parameters
- fs (int): measurement sampling frequency
- samples (int): number of samples in the generated log sweep typically 2**n
- window_len (int): length of the Hann half-window applied to the ends of the sweep
- f_low (int): lower frequency limit for the generated sweep
- f_high (int): higher frequency limit for the generated sweep
Returns
- log_sweep (np.ndarray): numpy array containing mono log sweep
133 def measure(self, 134 out_path : str='', 135 thd_filter : bool=True, 136 export : bool=True 137 ) -> tuple[np.ndarray, int]: 138 """Performs measurement and saves the recording. 139 140 Parameters 141 ---------- 142 out_path : str 143 path where the recording should be saved, including filename 144 thd_filter : bool 145 enables harmonic distortion filtering 146 This affects the files saved. 147 export : bool 148 enables export to specified path 149 150 Returns 151 ------- 152 data : np.ndarray 153 measured audio data 154 fs : int 155 sampling rate 156 """ 157 data = sd.playrec( 158 self.sweep, 159 input_mapping=self.channels_in, 160 output_mapping=self.channels_out) 161 sd.wait() 162 data = np.asarray(data) 163 164 #filtration 165 if thd_filter: 166 data = data.T 167 data = harmonic_distortion_filter( 168 data, 169 self.sweep, 170 f_low=self.f_limits[0], 171 f_high=self.f_limits[1] 172 ) 173 data = data.T 174 175 if export: 176 sf.write( 177 file=out_path, 178 data=data, 179 samplerate=self.fs, 180 format='WAV', 181 subtype='FLOAT' 182 ) 183 184 return data, self.fs
Performs measurement and saves the recording.
Parameters
- out_path (str): path where the recording should be saved, including filename
- thd_filter (bool): enables harmonic distortion filtering This affects the files saved.
- export (bool): enables export to specified path
Returns
- data (np.ndarray): measured audio data
- fs (int): sampling rate
186class Tube: 187 """Class representing tube geometry. 188 189 Attributes 190 ---------- 191 further_mic_dist : float 192 further microphone distance from sample 193 closer_mic_dist : float 194 closer mic distance from sample 195 freq_limit : int 196 higher frequency limit for exports 197 """ 198 def __init__(self, 199 further_mic_dist : float=0.400115, #x_1 200 closer_mic_dist : float=0.101755, #x_2 201 freq_limit : int=2000, 202 ): 203 self.further_mic_dist = further_mic_dist 204 self.closer_mic_dist = closer_mic_dist 205 self.mic_spacing = further_mic_dist - closer_mic_dist 206 self.freq_limit = freq_limit
Class representing tube geometry.
Attributes
- further_mic_dist (float): further microphone distance from sample
- closer_mic_dist (float): closer mic distance from sample
- freq_limit (int): higher frequency limit for exports
208class Sample: 209 """A class representing sample and its boundary conditions. 210 211 Attributes 212 ---------- 213 name : str 214 name of the sample 215 temperature : float 216 ambient temperature in degC 217 rel_humidity : float 218 ambient relative humidity in % 219 tube : Tube 220 impedance tube definition object 221 timestamp : str 222 strftime timestamp in a format '%y-%m-%d_%H-%M' 223 folder : str 224 path to project data folder, defaults to "data" 225 """ 226 def __init__(self, 227 name : str, 228 temperature : float, 229 rel_humidity : float, 230 atm_pressure : float = 101325, 231 tube : Tube=Tube(), 232 timestamp : str = strftime("%y-%m-%d_%H-%M"), 233 folder = "data", 234 ): 235 self.name = name 236 self.timestamp = timestamp 237 self.temperature = temperature 238 self.atm_pressure = atm_pressure 239 self.rel_humidity = rel_humidity 240 self.tube = tube 241 self.folder = folder 242 self.trees = make_foldertree( 243 self.name, 244 self.folder, 245 self.timestamp 246 ) 247 bound_dict = { 248 'temp': [self.temperature], 249 'RH': [self.rel_humidity], 250 'atm_pressure': [self.atm_pressure], 251 'x1': [self.tube.further_mic_dist], 252 'x2': [self.tube.closer_mic_dist], 253 'lim': [self.tube.freq_limit], 254 'dbfs_to_spl': [130], 255 } 256 self.boundary_df = pd.DataFrame(bound_dict) 257 self.boundary_df.to_csv( 258 os.path.join( 259 self.trees[2],self.trees[1]+"_bound_cond.csv" 260 ) 261 ) 262 263 def migrate_cal(self, cal_name, cal_stamp, cal_parent="data"): 264 """Migrates calibration files from different measurement. 265 266 Parameters 267 ---------- 268 cal_name : str 269 calibration sample name 270 cal_stamp : str 271 calibration sample timestamp i a '%y-%m-%d_%H-%M' format 272 cal_parent : str 273 parent data folder, defaults to 'data' 274 """ 275 cal_trees = make_foldertree( 276 variant=cal_name, 277 time_stamp=cal_stamp, 278 parent=cal_parent 279 ) 280 cal_parent_folder = cal_trees[2] 281 import_folder = cal_trees[3][1] 282 freqs = np.load( 283 os.path.join(cal_parent_folder, cal_trees[1]+"_freqs.npy") 284 ) #freq import 285 cf = np.load( 286 os.path.join(import_folder, cal_trees[1]+"_cal_f_12.npy") 287 ) #cf import 288 289 parent_folder = self.trees[2] 290 export_folder = self.trees[3][1] 291 np.save( 292 os.path.join(parent_folder, self.trees[1]+"_freqs.npy"), 293 freqs 294 ) #freq export 295 np.save( 296 os.path.join(export_folder, self.trees[1]+"_cal_f_12.npy"), 297 cf 298 ) #cf export
A class representing sample and its boundary conditions.
Attributes
- name (str): name of the sample
- temperature (float): ambient temperature in degC
- rel_humidity (float): ambient relative humidity in %
- tube (Tube): impedance tube definition object
- timestamp (str): strftime timestamp in a format '%y-%m-%d_%H-%M'
- folder (str): path to project data folder, defaults to "data"
263 def migrate_cal(self, cal_name, cal_stamp, cal_parent="data"): 264 """Migrates calibration files from different measurement. 265 266 Parameters 267 ---------- 268 cal_name : str 269 calibration sample name 270 cal_stamp : str 271 calibration sample timestamp i a '%y-%m-%d_%H-%M' format 272 cal_parent : str 273 parent data folder, defaults to 'data' 274 """ 275 cal_trees = make_foldertree( 276 variant=cal_name, 277 time_stamp=cal_stamp, 278 parent=cal_parent 279 ) 280 cal_parent_folder = cal_trees[2] 281 import_folder = cal_trees[3][1] 282 freqs = np.load( 283 os.path.join(cal_parent_folder, cal_trees[1]+"_freqs.npy") 284 ) #freq import 285 cf = np.load( 286 os.path.join(import_folder, cal_trees[1]+"_cal_f_12.npy") 287 ) #cf import 288 289 parent_folder = self.trees[2] 290 export_folder = self.trees[3][1] 291 np.save( 292 os.path.join(parent_folder, self.trees[1]+"_freqs.npy"), 293 freqs 294 ) #freq export 295 np.save( 296 os.path.join(export_folder, self.trees[1]+"_cal_f_12.npy"), 297 cf 298 ) #cf export
Migrates calibration files from different measurement.
Parameters
- cal_name (str): calibration sample name
- cal_stamp (str): calibration sample timestamp i a '%y-%m-%d_%H-%M' format
- cal_parent (str): parent data folder, defaults to 'data'
300def calibration( 301 sample : Sample, 302 measurement : Measurement, 303 thd_filter = True 304 ): 305 """Performs CLI calibration measurement. 306 307 Parameters 308 ---------- 309 310 sample : imptube.tube.Sample 311 312 measurement : Measurement 313 314 thd_filter : bool 315 Enables harmonic distortion filtering 316 """ 317 caltree = sample.trees[3][0] 318 if not os.path.exists(caltree): 319 os.makedirs(caltree) 320 321 m = measurement 322 running = True 323 while running: 324 for c in range(1, 3): 325 ready = input(f"Calibrate in configuration {c}? [Y/n]") 326 if ready.lower() == "n": 327 break 328 else: 329 for s in range(m.sub_measurements): 330 f = os.path.join(caltree, sample.trees[1]+f"_cal_wav_conf{c}_{s}.wav") 331 print(f) 332 m.measure(f, thd_filter=thd_filter) 333 sleep(0.5) 334 if input("Repeat calibration process? [y/N]").lower() == "y": 335 continue 336 else: 337 running = False 338 input("Move the microphones to original position before measurement!") 339 340 return calibration_from_files(parent_folder=sample.trees[2])
Performs CLI calibration measurement.
Parameters
sample (Sample):
measurement (Measurement):
thd_filter (bool): Enables harmonic distortion filtering
342def single_measurement( 343 sample : Sample, 344 measurement : Measurement, 345 depth : float, 346 thd_filter : bool= True, 347 calc_spl : bool = True 348 ) -> tuple[list[np.ndarray], int]: 349 """Performs measurement. 350 351 Parameters 352 ---------- 353 354 sample : imptube.tube.Sample 355 356 measurement : Measurement 357 358 depth : float 359 current depth of the sample 360 thd_filter : bool 361 Enables harmonic distortion filtering 362 363 Returns 364 ------- 365 sub_measurement_data : list[np.ndarray] 366 list of audio recordings taken 367 fs : float 368 sampling rate of the recording 369 """ 370 m = measurement 371 sub_measurement_data = [] 372 for s in range(m.sub_measurements): 373 f = os.path.join(sample.trees[4][0], sample.trees[1]+f"_wav_d{depth}_{s}.wav") 374 data, fs = m.measure(f, thd_filter=thd_filter) 375 sub_measurement_data.append(data) 376 sleep(0.5) 377 378 if calc_spl: 379 rms_spl = calc_rms_pressure_level(data.T[0], m.fs_to_spl) 380 logging.info(f"RMS SPL: {rms_spl} dB") 381 m.rms_spl = rms_spl 382 return sub_measurement_data, fs
Performs measurement.
Parameters
sample (Sample):
measurement (Measurement):
depth (float): current depth of the sample
- thd_filter (bool): Enables harmonic distortion filtering
Returns
- sub_measurement_data (list[np.ndarray]): list of audio recordings taken
- fs (float): sampling rate of the recording
384def calculate_alpha( 385 sample : Sample, 386 return_r : bool = False, 387 return_z : bool = False, 388 ) -> tuple[np.ndarray, np.ndarray]: 389 """Performs transfer function and alpha calculations from audio data 390 found in a valid folder structure. 391 392 Parameters 393 ---------- 394 sample : Sample 395 396 Returns 397 ------- 398 alpha : np.ndarray 399 sound absorption coefficient for frequencies lower than 400 limit specified in sample.tube.freq_limit 401 freqs : np.ndarray 402 frequency values for the alpha array 403 """ 404 sample.unique_d, sample.tfs = transfer_function_from_path(sample.trees[2]) 405 results = alpha_from_path( 406 sample.trees[2], 407 return_f=True, 408 return_r=return_r, 409 return_z=return_z 410 ) 411 return results
Performs transfer function and alpha calculations from audio data found in a valid folder structure.
Parameters
- sample (Sample):
Returns
- alpha (np.ndarray): sound absorption coefficient for frequencies lower than limit specified in sample.tube.freq_limit
- freqs (np.ndarray): frequency values for the alpha array
413class Sensor(Protocol): 414 """A protocol for Sensor class implementation.""" 415 def read_temperature(self) -> float: 416 ... 417 418 def read_humidity(self) -> float: 419 ... 420 421 def read_pressure(self) -> float: 422 ...
A protocol for Sensor class implementation.