def main(): stock = FrequencyResponse.read_from_csv('data/HE400S stock.csv') focus = FrequencyResponse.read_from_csv('data/HE400S focus.csv') base = FrequencyResponse.read_from_csv('innerfidelity/data/HiFiMAN HE400S/HiFiMAN HE400S.csv') stock.interpolate(f_min=20, f_max=20000) stock.center() focus.interpolate(f_min=20, f_max=20000) focus.center() base.interpolate(f=stock.frequency) base.center() diff = FrequencyResponse(name='Diff', frequency=stock.frequency, raw=focus.raw-stock.raw) fr = FrequencyResponse(name='HE400S with Focus Pads', frequency=stock.frequency, raw=base.raw+diff.raw) _fr = FrequencyResponse(name='debug', frequency=stock.frequency, raw=base.raw, smoothed=diff.raw, equalization=fr.raw) _fr.plot_graph() fr.smoothen_fractional_octave(window_size=1 / 5, iterations=10, treble_window_size=1 / 2, treble_iterations=100) #stock.plot_graph() #focus.plot_graph() #diff.plot_graph() #base.plot_graph() #fr.plot_graph() os.makedirs('innerfidelity/data/HiFiMAN HE400S with Focus Pads', exist_ok=True) fr.write_to_csv(file_path='innerfidelity/data/HiFiMAN HE400S with Focus Pads/HiFiMAN HE400S with Focus Pads ORIG.csv') fr.equalize(max_gain=12, smoothen=True, window_size=1 / 5, bass_target=4) fr.write_to_csv(file_path='innerfidelity/data/HiFiMAN HE400S with Focus Pads/HiFiMAN HE400S with Focus Pads.csv') fig, ax = fr.plot_graph(show=False, file_path='innerfidelity/data/HiFiMAN HE400S with Focus Pads/HiFiMAN HE400S with Focus Pads.png') plt.close(fig) fr.write_eqapo_graphic_eq('innerfidelity/data/HiFiMAN HE400S with Focus Pads/HiFiMAN HE400S with Focus Pads EqAPO.txt')
def process(self, item, file_paths): fr = FrequencyResponse(name=item.true_name) fr.raw = np.zeros(fr.frequency.shape) for fp in file_paths: if re.search(r'\.mdat$', fp): # Read mdat file for Gras headphone measurements raise TypeError( 'Crinacle\'s Gras measurements are not supported yet!') else: # Read text file for IEM and Ears-711 headphone measurements with open(fp, 'r', encoding='utf-8') as fh: s = fh.read() freq = [] raw = [] for line in s.split('\n'): if len(line) == 0 or line[0] == '*': # Skip empty lines and comments if 'C-weighting compensation: On' in line: print(f'C-weighted measurement: {item.false_name}') continue frp = line.split(' ') if len(frp) == 1: frp = line.split('\t') if len(frp) == 2: f, r = frp elif len(frp) == 3: f, r, p = frp else: # Must be comment line continue if f == '?' or r == '?': # Skip lines with missing data continue try: freq.append(float(f)) raw.append(float(r)) except ValueError as err: # Failed to convert values to floats, must be header or comment row, skip continue # Create standard fr object _fr = FrequencyResponse(name=item.true_name, frequency=freq, raw=raw) _fr.interpolate() _fr.center() fr.raw += _fr.raw fr.raw /= len(file_paths) # Save dir_path = os.path.join(DIR_PATH, 'data', item.form, fr.name) os.makedirs(dir_path, exist_ok=True) file_path = os.path.join(dir_path, f'{fr.name}.csv') fr.write_to_csv(file_path) print(f'Saved "{fr.name}" to "{file_path}"')
def parse_json(json_data): header = json_data['header'] data = np.array(json_data['data']) frequency = data[:, header.index('Frequency')] target = data[:, header.index('Target Response')] left_col = header.index('Left') right_col = header.index('Right') for row in data: if row[left_col] == np.array(None): row[left_col] = row[len(header) - header[::-1].index('Left') - 1] if row[right_col] == np.array(None): row[right_col] = row[len(header) - header[::-1].index('Right') - 1] left = data[:, left_col] right = data[:, right_col] raw = np.mean([left, right], axis=0) if np.std(target) > 0: raw += target fr = FrequencyResponse(name='fr', frequency=frequency, raw=raw) target = FrequencyResponse(name='target', frequency=frequency, raw=target) return fr, target
def process(self, item, file_paths, target_dir=None): if item.form == 'ignore': return if target_dir is None: raise TypeError('"target_dir" must be given') avg_fr = FrequencyResponse(name=item.true_name) avg_fr.raw = np.zeros(avg_fr.frequency.shape) for fp in file_paths: with open(fp, 'r', encoding='utf-8') as fh: s = fh.read() freq = [] raw = [] for line in s.split('\n'): if len(line) == 0 or line[0] == '*': # Skip empty lines and comments if 'C-weighting compensation: On' in line: print(f'C-weighted measurement: {item.false_name}') continue frp = line.split(', ') if len(frp) == 1: frp = line.split('\t') if len(frp) == 1: frp = line.split(' ') if len(frp) == 2: f, r = frp elif len(frp) == 3: f, r, p = frp else: # Must be comment line continue if f == '?' or r == '?': # Skip lines with missing data continue try: freq.append(float(f)) raw.append(float(r)) except ValueError as err: # Failed to convert values to floats, must be header or comment row, skip continue # Create standard fr object fr = FrequencyResponse(name=item.true_name, frequency=freq, raw=raw) fr.interpolate() fr.center() avg_fr.raw += fr.raw avg_fr.raw /= len(file_paths) # Save dir_path = os.path.join(target_dir, avg_fr.name) os.makedirs(dir_path, exist_ok=True) file_path = os.path.join(dir_path, f'{avg_fr.name}.csv') avg_fr.write_to_csv(file_path) print(f'Saved "{avg_fr.name}" to "{file_path}"')
def main(): pop = pop_frequency_distribution() elc = equal_loudness_contour(80) elc.center() elc.raw = -elc.raw loudness = FrequencyResponse('Loudness', frequency=pop.frequency, raw=pop.raw + elc.raw) #loudness.center() fig, ax = pop.plot_graph(show=False) #elc.plot_graph(fig=fig, ax=ax, show=False) loudness.plot_graph(fig=fig, ax=ax, show=False) v = FrequencyResponse(name='V', frequency=[20, 1000, 20000], raw=[10, -10, 10]) v.interpolate(pol_order=2) v.interpolate(f=loudness.frequency) v.plot_graph(fig=fig, ax=ax, show=False) v_l = FrequencyResponse(name='V Loudness', frequency=v.frequency, raw=v.raw + loudness.raw) v_l.plot_graph(fig=fig, ax=ax, show=False) a = FrequencyResponse(name='A', frequency=[20, 1000, 20000], raw=[-10, 5, -10]) a.interpolate(pol_order=2) a.interpolate(f=loudness.frequency) a.plot_graph(fig=fig, ax=ax, show=False) a_l = FrequencyResponse(name='A Loudness', frequency=a.frequency, raw=a.raw + loudness.raw) a_l.plot_graph(fig=fig, ax=ax, show=False, a_min=-20, a_max=90) plt.legend([ 'Pop Music Frequency Distribution', 'Loudness', 'V', 'V+L', 'A', 'A+L' ]) print('V: {}'.format(20 * np.log10(np.mean(np.power(v.raw / 20, 10))))) print('V loudness: {}'.format( 20 * np.log10(np.mean(np.power(v_l.raw / 20, 10))))) print('A: {}'.format(20 * np.log10(np.mean(np.power(a.raw / 20, 10))))) print('A loudness: {}'.format( 20 * np.log10(np.mean(np.power(a_l.raw / 20, 10))))) plt.show() loudness.write_to_csv('music_loudness_contour.csv') plt.close(fig)
def parse_json(json_data, name): header = json_data['header'] data = np.array(json_data['data']) frequency = data[:, header.index('Frequency')] left = data[:, header.index('Left')] right = data[:, header.index('Right')] target = data[:, header.index('Target Response')] raw = np.mean([left, right], axis=0) if np.std(target) > 0: raw += target fr = FrequencyResponse(name=name, frequency=frequency, raw=raw) target = FrequencyResponse(name='', frequency=frequency, raw=target) return fr, target
def main(): fig, ax = plt.subplots() diffs = [] # Calculate differences for all models for file in glob(os.path.join('compensation', 'compensated', '**', '*.csv'), recursive=True): file = os.path.abspath(file) comp = FrequencyResponse.read_from_csv(file) comp.interpolate() comp.center() raw_data_path = file.replace('compensated', 'raw') raw = FrequencyResponse.read_from_csv(raw_data_path) raw.interpolate() raw.center() diff = FrequencyResponse(name=comp.name, frequency=comp.frequency, raw=raw.raw - comp.raw) plt.plot(diff.frequency, diff.raw) diffs.append(diff.raw) # Average and smoothen difference f = FrequencyResponse.generate_frequencies() diffs = np.vstack(diffs) diff = np.mean(diffs, axis=0) diff = FrequencyResponse(name='Headphone.com Compensation', frequency=f, raw=diff) diff.smoothen_fractional_octave(window_size=1 / 9, iterations=10) diff.raw = diff.smoothed diff.smoothed = np.array([]) plt.xlabel('Frequency (Hz)') plt.semilogx() plt.xlim([20, 20000]) plt.ylabel('Amplitude (dBr)') plt.ylim([-15, 15]) plt.grid(which='major') plt.grid(which='minor') plt.title('Headphone.com Compensation Function') ax.xaxis.set_major_formatter(ticker.StrMethodFormatter('{x:.0f}')) plt.show() diff.write_to_csv('headphonecom_compensation.csv') diff.plot_graph(show=True, f_min=10, f_max=20000, file_path='headphonecom_compensation.png')
def limited_delta(cls, x, y, limit): peak_finder = cls(name='peak_finder', frequency=x, raw=y) peak_finder.smoothen_fractional_octave(window_size=1 / 12, treble_window_size=1 / 3, treble_f_lower=9000, treble_f_upper=11000) peaks = scipy.signal.find_peaks(-peak_finder.smoothed)[0] forward_start = peaks[0] backward_start = len(x) - peaks[-1] - 1 limited_forward = cls.limited_forward_delta(x, y, limit, start_index=forward_start) limited_backward = cls.limited_forward_delta( np.flip(x), np.flip(y), -limit, start_index=backward_start) limited_backward = np.flip(limited_backward) _fr = FrequencyResponse(name='limiter', frequency=x.copy(), raw=np.min(np.vstack( [limited_forward, limited_backward]), axis=0)) _fr.smoothen_fractional_octave(window_size=1 / 6, treble_window_size=1 / 6) return _fr.smoothed.copy()
def main(): trans = FrequencyResponse.read_from_csv( 'resources\innerfidelity_transformation_SBAF-Serious.csv') comp16 = FrequencyResponse.read_from_csv( 'resources\innerfidelity_compensation_2016.csv') comp17 = FrequencyResponse.read_from_csv( 'resources\innerfidelity_compensation_2017.csv') trans.interpolate() trans.center() comp = FrequencyResponse(name='Serious Compensation', frequency=comp16.frequency, raw=comp16.raw + trans.raw) fig, ax = trans.plot_graph(show=False) comp16.plot_graph(fig=fig, ax=ax, show=False) comp.plot_graph(fig=fig, ax=ax, show=False) comp17.plot_graph(fig=fig, ax=ax, show=False) fig.legend(['Serious', '2016', '2016+Serious', '2017']) plt.show() comp.write_to_csv('resources\innerfidelity_compensation_SBAF-Serious.csv') comp.plot_graph( show=False, close=True, file_path='resources\innerfidelity_compensation_SBAF_Serious.png')
def average_measurements(input_dir=None, output_dir=None): if input_dir is None: raise TypeError('Input directory path is required!') if output_dir is None: output_dir = os.path.abspath(input_dir) input_dir = os.path.abspath(input_dir) output_dir = os.path.abspath(output_dir) models = {} for file_path in glob(os.path.join(input_dir, '*')): model = os.path.split(file_path)[-1] if not re.search(MOD_REGEX, model, re.IGNORECASE): continue norm = re.sub(MOD_REGEX, '', model, 0, flags=re.IGNORECASE) try: models[norm].append(model) except KeyError as err: models[norm] = [model] for norm, origs in models.items(): if len(origs) > 1: f = FrequencyResponse.generate_frequencies() avg = np.zeros(len(f)) for model in origs: fr = FrequencyResponse.read_from_csv(os.path.join(input_dir, model, model + '.csv')) fr.interpolate() fr.center() avg += fr.raw avg /= len(origs) fr = FrequencyResponse(name=norm, frequency=f, raw=avg) d = os.path.join(output_dir, norm) os.makedirs(d, exist_ok=True) file_path = os.path.join(d, norm + '.csv') fr.write_to_csv(file_path)
def main(): models = {} for file_path in glob(os.path.join(DIR, '*')): model = os.path.split(file_path)[-1] if not (re.search(' sample [a-zA-Z0-9]$', model, re.IGNORECASE) or re.search(' sn[a-zA-Z0-9]+$', model, re.IGNORECASE)): # Skip measurements with sample or serial number, those have averaged results continue norm = re.sub(' sample [a-zA-Z0-9]$', '', model, 0, re.IGNORECASE) norm = re.sub(' sn[a-zA-Z0-9]+$', '', norm, 0, re.IGNORECASE) try: models[norm].append(model) except KeyError as err: models[norm] = [model] for norm, origs in models.items(): if len(origs) > 1: print(norm, origs) avg = np.zeros(613) f = FrequencyResponse.generate_frequencies() for model in origs: fr = FrequencyResponse.read_from_csv( os.path.join(DIR, model, model + '.csv')) fr.interpolate() fr.center() avg += fr.raw avg /= len(origs) fr = FrequencyResponse(name=norm, frequency=f, raw=avg) d = os.path.join(OUT_DIR, norm) if not os.path.isdir(d): os.makedirs(d) fr.write_to_csv(os.path.join(d, norm + '.csv'))
def parse_image(im, model): """Parses graph image downloaded from innerfidelity.com""" # Crop by left and right edges box = (69, 31, 550, 290) im = im.crop(box) px_a_max = 0 px_a_min = im.size[1] # im.show() # X axis f_min = 20 f_max = 20000 f_step = (f_max / f_min)**(1 / im.size[0]) f = [f_min] for _ in range(1, im.size[0]): f.append(f[-1] * f_step) # Y axis a_max = 150 a_min = 66 a_res = (a_max - a_min) / (px_a_min - px_a_max) # Try blue curve _im = im.copy() inspection = _im.load() amplitude, _im, _inspection = ReferenceAudioAnalyzerCrawler.find_curve( _im, inspection, 203, 206, 0.8, 1.0, a_max, a_res) if len([x for x in amplitude if x is None]) >= 0.5 * len(amplitude): # More than half of the pixels were discarded, try green curve _im = im.copy() inspection = _im.load() amplitude, _im, _inspection = ReferenceAudioAnalyzerCrawler.find_curve( _im, inspection, 119, 121, 0.8, 1.0, a_max, a_res) # Inspection image draw = ImageDraw.Draw(_im) x0 = np.log(30 / f_min) / np.log(f_step) x1 = np.log(10000 / f_min) / np.log(f_step) y_0 = px_a_max + 12 / a_res y_1 = px_a_min - 12 / a_res draw.rectangle(((x0, y_0), (x1, y_1)), outline='magenta') draw.rectangle(((x0 + 1, y_0 + 1), (x1 - 1, y_1 - 1)), outline='magenta') # Create frequency response fr = FrequencyResponse(model, f, amplitude) fr.interpolate() if len(fr.frequency) < 2: im.show() raise ValueError(f'Failed to parse image for {fr.name}') fr.smoothen_fractional_octave(window_size=1 / 3, treble_window_size=1 / 3) fr.raw = fr.smoothed.copy() fr.smoothed = np.array([]) fr.center() return fr, _im
def parse_cropped(im, name='fr', f_min=20, f_max=20000, a_min=-20, a_max=20): """Parses an image which has been cropped tightly to given boundaries. Image left boundary must be cropped to f_min, right boundary to f_max, bottom boundary to a_min and top boundary to a_max. Only colored pixels will be scanned. Args: im: Image name: Name of the image / produced FrequencyResponse f_min: Frequency at left boundary of the image f_max: Frequency at right boundary of the image a_min: Amplitude at bottom boundary of the image a_max: Amplitude at top boundary of the image Returns: FrequencyResponse created from colored pixels in the image """ # X axis (frequencies) f_step = (f_max / f_min)**(1 / im.size[0]) f = [f_min] for _ in range(1, im.size[0]): f.append(f[-1] * f_step) # Y axis (amplitude) a_res = (a_max - a_min) / im.size[1] # dB / px _im = im.copy() pix = _im.load() amplitude = [] for x in range(im.size[0]): pxs = [] # Graph pixels # Iterate each row (pixel in column) for y in range(im.size[1]): # Convert read RGB pixel values and convert to HSV h, s, v = colorsys.rgb_to_hsv( *[v / 255.0 for v in im.getpixel((x, y))]) # Graph pixels are colored if s > 0.8: pxs.append(float(y)) else: p = im.getpixel((x, y)) pix[x, y] = (int(0.9 * p[0]), int(255 * 0.1 + 0.9 * p[1]), int(0 + 0.9 * p[2])) if not pxs: # No graph pixels found on this column amplitude.append(None) else: # Mean of recorded pixels v = np.mean(pxs) # Convert to dB value v = a_max - v * a_res amplitude.append(v) return FrequencyResponse(name=name, frequency=f, raw=amplitude)
def parse_json(json_data): """Parses Rtings.com JSON data The columns should be "Frequency", "Left", "Right", "Target Response", "Left", "Right". Some rows might have data in the first Left and Right, some might have it in the second left and right Args: json_data: JSON data object as returned by Rtings API Returns: - Parsed raw frequency response as FrequencyResponse - Parsed target response as FrequencyResponse """ header = json_data['header'] data = np.array(json_data['data']) frequency = data[:, header.index('Frequency')] target = data[:, header.index('Target Response')] left_col = header.index('Left') right_col = header.index('Right') for row in data: if row[left_col] == np.array(None): # Data missing at the first "Left" column, use the last "Left" column row[left_col] = row[len(header) - header[::-1].index('Left') - 1] if row[right_col] == np.array(None): # Data missing at the first "Right" column, use the last "Right" column row[right_col] = row[len(header) - header[::-1].index('Right') - 1] left = data[:, left_col] right = data[:, right_col] raw = np.mean([left, right], axis=0) if np.std(target) > 0: raw += target fr = FrequencyResponse(name='fr', frequency=frequency, raw=raw) target = FrequencyResponse(name='target', frequency=frequency, raw=target) return fr, target
def pop_frequency_distribution(): # Approximation of pop music frequency distribution fr = FrequencyResponse(name='Pop Music Frequency Distribution', frequency=[ 20, 100, 100 * math.sqrt(10), 1000, 1000 * math.sqrt(10), 10000, 20000 ], raw=[-30, 0, -3, -6, -10, -20, -50]) fr.raw += 80 fr.interpolate(pol_order=3, f_min=20, f_max=12500) #fr.center() return fr
def main(): fs = 48000 f_res = 60 input_dir = os.path.join('oratory1990', 'data', 'onear') glob_files = glob(os.path.join(input_dir, '**', '*.csv'), recursive=True) for input_file_path in glob_files: fr = FrequencyResponse.read_from_csv(input_file_path) fr.equalization = fr.raw fr.raw = np.array([]) mp = fr.minimum_phase_impulse_response(fs=fs, f_res=f_res, normalize=False) mp = np.concatenate([mp, np.zeros(fs//10 - len(mp))]) f_mp, mp = fft(mp, fs) f_mp[0] = 0.1 mp = FrequencyResponse(name='Minimum phase', frequency=f_mp, raw=mp) mp.center() lp = fr.linear_phase_impulse_response(fs=fs, f_res=f_res, normalize=False) lp = np.concatenate([lp, np.zeros(fs//10 - len(lp))]) f_lp, lp = fft(lp, fs) f_lp[0] = 0.1 lp = FrequencyResponse(name='Linear phase', frequency=f_lp, raw=lp) lp.center() fig, ax = plt.subplots() fig.set_size_inches(15, 10) plt.plot(fr.frequency, fr.equalization) plt.plot(mp.frequency, mp.raw, '.-') plt.plot(lp.frequency, lp.raw, '.-') plt.legend(['Raw', 'Minimum phase', 'Linear phase']) plt.semilogx() plt.xlabel('Frequency (Hz)') plt.xlim([1, 20000]) plt.ylabel('Gain (dBr)') plt.ylim([-20, 20]) plt.title(fr.name) plt.grid(True, which='major') plt.grid(True, which='minor') plt.show()
def parse_headphonecom(im, model, scale=40): """Parses graph image downloaded from headphone.com""" # Crop out everything but graph area px_top = 24 # Pixels from top to +30dB px_bottom = 125 # Pixels from bottom to -30dB px_left = 51 # Pixels from left to 10Hz px_right = 50 # Pixels from right edge box = (px_left, px_top, im.size[0] - px_right, im.size[1] - px_bottom) im = im.crop(box) # X axis f_min = 10 f_max = 20000 px_f_max = 71 f_step = (f_max / f_min)**(1 / (im.size[0] - (px_f_max - px_right))) f = [f_min] for _ in range(1, im.size[0]): f.append(f[-1] * f_step) # Y axis a_max = scale a_min = -scale a_res = (a_max - a_min) / im.size[1] # dB / px amplitude = [] # Iterate each column for x in range(im.size[0]): pxs = [] # Graph pixels # Iterate each row (pixel in column) for y in range(im.size[1]): # Convert read RGB pixel values and convert to HSV h, l, s = colorsys.rgb_to_hls( *[v / 255.0 for v in im.getpixel((x, y))]) # Scale hue to 0-255 h *= 255 # Graph pixels are blue if s > 0.5 and 140 < h < 160: pxs.append(float(y)) if not pxs: # No graph pixels found on this column amplitude.append(None) else: # Mean of recorded pixels v = sum(pxs) / len(pxs) # Convert to dB value v = a_max - v * a_res amplitude.append(v) fr = FrequencyResponse(model, f, amplitude) return fr
def peq2fr(fc, q, gain, filts): if type(fc) in [float, int]: fc = np.array([fc]) if type(q) in [float, int]: q = np.array([q]) if type(gain) in [float, int]: gain = np.array([gain]) if type(filts) == str: filts = [filts] * len(fc) fr = FrequencyResponse(name='PEG') c = np.zeros(fr.frequency.shape) for i, filt in enumerate(filts): a0, a1, a2, b0, b1, b2 = fns[filt](fc[i], q[i], gain[i], fs=fs) c += digital_coeffs(fr.frequency, fs, a0, a1, a2, b0, b1, b2) fr.raw = c return fr
def save(df, column, name): fr = FrequencyResponse(name=name, frequency=df['Frequency'], raw=df[column]) fr.interpolate() fr.center() name = name.lower().replace(' ', '_') fr.write_to_csv('compensation/{}.csv'.format(name)) fr.plot_graph(file_path='compensation/{}.png'.format(name), show=False, color=None) ind = np.argmin(fr.raw[:400]) fr.raw[:ind] = fr.raw[ind] fr.write_to_csv('compensation/{}_wo_bass.csv'.format(name)) fr.plot_graph(file_path='compensation/{}_wo_bass.png'.format(name), show=False, color=None)
def main(): dt770 = FrequencyResponse.read_from_csv('headphonecom/data/onear/Beyerdynamic DT770/Beyerdynamic DT770.csv') dt770.interpolate() he400s = FrequencyResponse.read_from_csv('innerfidelity/data/onear/HiFiMAN HE400S/HiFiMAN HE400S.csv') he400s.interpolate() calibration = FrequencyResponse.read_from_csv('calibration/headphonecom_raw_to_innerfidelity_raw.csv') calibration.interpolate() compensation = FrequencyResponse.read_from_csv('innerfidelity/resources/innerfidelity_compensation_2017.csv') compensation.interpolate() dt770_calibrated = FrequencyResponse(name='DT 770 calibrated', frequency=dt770.frequency, raw=dt770.raw) dt770_calibrated.calibrate(calibration) dt770_calibrated.compensate(he400s) #dt770_calibrated.compensate(compensation) dt770_calibrated.center() dt770_calibrated.smoothen() dt770_calibrated.equalize(smoothen=True) dt770_calibrated.plot_graph(a_min=-20, a_max=20)
def main(input_dir=None, output_dir=None): if input_dir is None: raise TypeError('Input directory path is required!') if output_dir is None: raise TypeError('Output directory path is required!') input_dir = os.path.abspath(input_dir) output_dir = os.path.abspath(output_dir) models = {} for file_path in glob(os.path.join(input_dir, '*')): model = os.path.split(file_path)[-1] if not (re.search(' sample [a-zA-Z0-9]$', model, re.IGNORECASE) or re.search(' sn[a-zA-Z0-9]+$', model, re.IGNORECASE)): continue norm = re.sub(' sample [a-zA-Z0-9]$', '', model, 0, re.IGNORECASE) norm = re.sub(' sn[a-zA-Z0-9]+$', '', norm, 0, re.IGNORECASE) try: models[norm].append(model) except KeyError as err: models[norm] = [model] for norm, origs in models.items(): if len(origs) > 1: print(norm, origs) avg = np.zeros(613) f = FrequencyResponse.generate_frequencies() for model in origs: fr = FrequencyResponse.read_from_csv( os.path.join(input_dir, model, model + '.csv')) fr.interpolate() fr.center() avg += fr.raw avg /= len(origs) fr = FrequencyResponse(name=norm, frequency=f, raw=avg) d = os.path.join(output_dir, norm) if not os.path.isdir(d): os.makedirs(d) fr.write_to_csv(os.path.join(d, norm + '.csv'))
def equal_loudness_contour(phon): f = [ 20, 25, 31.5, 40, 50, 63, 80, 100, 125, 160, 200, 250, 315, 400, 500, 630, 800, 1000, 1250, 1600, 2000, 2500, 3150, 4000, 5000, 6300, 8000, 10000, 12500 ] af = [ 0.532, 0.506, 0.480, 0.455, 0.432, 0.409, 0.387, 0.367, 0.349, 0.330, 0.315, 0.301, 0.288, 0.276, 0.267, 0.259, 0.253, 0.250, 0.246, 0.244, 0.243, 0.243, 0.243, 0.242, 0.242, 0.245, 0.254, 0.271, 0.301 ] Lu = [ -31.6, -27.2, -23.0, -19.1, -15.9, -13.0, -10.3, -8.1, -6.2, -4.5, -3.1, -2.0, -1.1, -0.4, 0.0, 0.3, 0.5, 0.0, -2.7, -4.1, -1.0, 1.7, 2.5, 1.2, -2.1, -7.1, -11.2, -10.7, -3.1 ] Tf = [ 78.5, 68.7, 59.5, 51.1, 44.0, 37.5, 31.5, 26.5, 22.1, 17.9, 14.4, 11.4, 8.6, 6.2, 4.4, 3.0, 2.2, 2.4, 3.5, 1.7, -1.3, -4.2, -6.0, -5.4, -1.5, 6.0, 12.6, 13.9, 12.3 ] Ln = phon Lps = [] for i in range(0, len(f)): Af = 0.00447 * (10.0**(Ln / 40.0) - 1.15) + (10.0**(( (Tf[i] + Lu[i]) / 10.0) - 9.0) / 2.5)**af[i] Lp = ((10.0 / af[i]) * math.log10(Af)) - Lu[i] + 94.0 Lps.append(Lp) Lps = np.array(Lps) fr = FrequencyResponse(name='{} phon Equal Loudness Contour'.format(phon), frequency=f, raw=Lps) fr.interpolate(pol_order=3, f_min=20, f_max=12500) fr.center() return fr
def main(oe, ie): oe18 = FrequencyResponse.read_from_csv( 'compensation/harman_over-ear_2018.csv') oe18wob = FrequencyResponse.read_from_csv( 'compensation/harman_over-ear_2018_wo_bass.csv') ie17 = FrequencyResponse.read_from_csv( 'compensation/harman_in-ear_2017-1.csv') ie17wob = FrequencyResponse.read_from_csv( 'compensation/harman_in-ear_2017-1_wo_bass.csv') if oe: bass = FrequencyResponse(name='Bass', frequency=oe18.frequency) bass.raw = bass._sigmoid(35, 280, 4, -2.0) fig, ax = oe18.plot_graph(show=False, color=None) oe18wob.plot_graph(fig=fig, ax=ax, show=False, color=None) bass.plot_graph(fig=fig, ax=ax, show=False, color=None) boost = FrequencyResponse(name='Boost', frequency=bass.frequency, raw=bass.raw) boost.center() oe18wob_boosted = FrequencyResponse(name='Boosted OE18', frequency=oe18.frequency, raw=oe18wob.raw + boost.raw) oe18wob_boosted.plot_graph(fig=fig, ax=ax, show=False, color=None) ax.legend(['OE18', 'OE18 w/o bass', 'Bass', 'Boosted']) plt.show() if ie: bass = FrequencyResponse(name='Bass', frequency=ie17.frequency) bass.raw = bass._sigmoid(25, 350, 8, -1.7) fig, ax = ie17.plot_graph(show=False, color=None) ie17wob.plot_graph(fig=fig, ax=ax, show=False, color=None) bass.plot_graph(fig=fig, ax=ax, show=False, color=None) boost = FrequencyResponse(name='Boost', frequency=bass.frequency, raw=bass.raw) boost.center() ie17wob_boosted = FrequencyResponse(name='Boosted IE17', frequency=ie17.frequency, raw=ie17wob.raw + boost.raw) ie17wob_boosted.plot_graph(fig=fig, ax=ax, show=False, color=None) ax.legend(['IE17', 'IE17 w/o bass', 'Bass', 'Boosted']) plt.show()
def main(): harman_onear = FrequencyResponse.read_from_csv( os.path.join(ROOT_DIR, 'compensation', 'harman_over-ear_2018.csv')) harman_onear_wo_bass = FrequencyResponse.read_from_csv( os.path.join(ROOT_DIR, 'compensation', 'harman_over-ear_2018_wo_bass.csv')) harman_inear = FrequencyResponse.read_from_csv( os.path.join(ROOT_DIR, 'compensation', 'harman_in-ear_2019v2.csv')) harman_inear_wo_bass = FrequencyResponse.read_from_csv( os.path.join(ROOT_DIR, 'compensation', 'harman_in-ear_2019v2_wo_bass.csv')) oratory1990_onear = get_measurements( os.path.join(MEASUREMENTS, 'oratory1990', 'data', 'onear')) oratory1990_inear = get_measurements( os.path.join(MEASUREMENTS, 'oratory1990', 'data', 'inear')) crinacle_inear = get_measurements( os.path.join(MEASUREMENTS, 'crinacle', 'data', 'inear')) inear_ref = oratory1990_inear.copy() inear_names = [fr.name for fr in inear_ref] for fr in crinacle_inear: if fr.name not in inear_names: inear_ref.append(fr) dbs = [ ('crinacle_harman_in-ear_2019v2_wo_bass', crinacle_inear, oratory1990_inear, None), ('crinacle_ears-711_harman_over-ear_2018_wo_bass', get_measurements( os.path.join(MEASUREMENTS, 'crinacle', 'data', 'onear', 'Ears-711')), oratory1990_onear, None), ('headphonecom_harman_over-ear_2018_wo_bass', get_measurements( os.path.join(MEASUREMENTS, 'headphonecom', 'data', 'onear')), oratory1990_onear, FrequencyResponse.read_from_csv( os.path.join(MEASUREMENTS, 'headphonecom', 'resources', 'headphonecom_compensation_sbaf-serious.csv'))), ('headphonecom_harman_in-ear_2019v2_wo_bass', get_measurements( os.path.join(MEASUREMENTS, 'headphonecom', 'data', 'inear')), inear_ref, FrequencyResponse.read_from_csv( os.path.join(MEASUREMENTS, 'headphonecom', 'resources', 'headphonecom_compensation_sbaf-serious.csv'))), ('innerfidelity_harman_over-ear_2018_wo_bass', get_measurements( os.path.join(MEASUREMENTS, 'innerfidelity', 'data', 'onear')), oratory1990_onear, FrequencyResponse.read_from_csv( os.path.join(MEASUREMENTS, 'innerfidelity', 'resources', 'innerfidelity_compensation_sbaf-serious.csv'))), ('innerfidelity_harman_in-ear_2019v2_wo_bass', get_measurements( os.path.join(MEASUREMENTS, 'innerfidelity', 'data', 'inear')), inear_ref, FrequencyResponse.read_from_csv( os.path.join(MEASUREMENTS, 'innerfidelity', 'resources', 'innerfidelity_compensation_sbaf-serious.csv'))), ('referenceaudioanalyzer_hdm-x_harman_over-ear_2018_wo_bass', get_measurements( os.path.join(MEASUREMENTS, 'referenceaudioanalyzer', 'data', 'onear', 'HDM-X')), oratory1990_onear, None), ('referenceaudioanalyzer_hdm1_harman_over-ear_2018_wo_bass', get_measurements( os.path.join(MEASUREMENTS, 'referenceaudioanalyzer', 'data', 'onear', 'HDM1')), oratory1990_onear, None), ('referenceaudioanalyzer_siec_harman_in-ear_2019v2_wo_bass', get_measurements( os.path.join(MEASUREMENTS, 'referenceaudioanalyzer', 'data', 'inear', 'SIEC')), inear_ref, None), ('rtings_harman_over-ear_2018_wo_bass', get_measurements(os.path.join(MEASUREMENTS, 'rtings', 'data', 'onear')), oratory1990_onear, FrequencyResponse.read_from_csv( os.path.join(MEASUREMENTS, 'rtings', 'resources', 'rtings_compensation_avg.csv'))), ('rtings_harman_in-ear_2019v2_wo_bass', get_measurements(os.path.join(MEASUREMENTS, 'rtings', 'data', 'inear')), inear_ref, FrequencyResponse.read_from_csv( os.path.join(MEASUREMENTS, 'rtings', 'resources', 'rtings_compensation_avg.csv'))), ('crinacle_gras_43ag-7_harman_over-ear_2018_wo_bass', get_measurements( os.path.join(MEASUREMENTS, 'crinacle', 'data', 'onear', 'GRAS 43AG-7')), oratory1990_onear, None) ] stds = [] for name, measurements, ref, original_target in dbs: print(f'Calibrating {name}...') # Find matching pairs pairs = [] for fr in measurements: for candidate in ref: if fr.name.lower() == candidate.name.lower(): pairs.append((fr, candidate)) fig, axs = plt.subplots(1, 3) fig.set_size_inches(30, 8) fig.suptitle(name) description = 'Calibrated against reference measurements with headphones: ' line_len = len(description) for fr, _ in pairs: if line_len > 240: description += '\n' line_len = 0 description += f'{fr.name}, ' line_len += len(fr.name) + 2 description = description[:-2] fig.text(0.5, -0.05, description, ha='center') # Individual errors errors = [] i = 0 for fr, target in pairs: fr.compensate(target, min_mean_error=True) errors.append(fr.error) fr.raw = fr.error.copy() fr.error = [] fr.target = [] fr.plot_graph(fig=fig, ax=axs[0], show=False, raw_plot_kwargs={ 'color': 'C0', 'alpha': 0.3 }) i += 1 axs[0].set_ylim([-15, 15]) axs[0].set_title('Individual Errors') axs[0].legend(['Error']) # Mean and standard deviation errors = np.vstack(errors) mean = np.mean(errors, axis=0) std = np.std(errors, axis=0) stds.append(FrequencyResponse(name=name, raw=std)) fr = FrequencyResponse(name='Mean and Standard Deviation') fr.raw = mean fr.smoothen_fractional_octave(window_size=1 / 3, treble_window_size=1 / 3) fr.raw = fr.smoothed.copy() fr.smoothed = [] fr.plot_graph(fig=fig, ax=axs[1], color='C0', show=False) axs[1].fill_between(fr.frequency, mean - std, mean + std, facecolor='#c1dff5') axs[1].set_ylim([-15, 15]) axs[1].legend(['Mean', 'STD']) # Target curves ref_target = harman_onear_wo_bass if 'over-ear' in name else harman_inear_wo_bass ref_target.plot_graph(fig=fig, ax=axs[2], show=False, color='C0') target = ref_target.copy() target.name = name target.raw += fr.raw target.plot_graph(fig=fig, ax=axs[2], show=False, color='C1') if original_target is not None: original_target.plot_graph(fig=fig, ax=axs[2], show=False, color='C2') axs[2].legend([ref_target.name, target.name, original_target.name]) else: axs[2].legend([ref_target.name, target.name]) axs[2].set_title(f'{name} target') axs[2].set_ylim([-15, 15]) fig.savefig(os.path.join(DIR_PATH, f'calibration_{name}.png'), bbox_inches='tight') target.plot_graph(show=False, file_path=os.path.join(DIR_PATH, f'{name}.png'), color='C0') target.write_to_csv(file_path=os.path.join(DIR_PATH, f'{name}.csv')) plt.close(fig) fig, axs = plt.subplots(1, 2) fig.set_size_inches(20, 8) onear_labels = [] inear_labels = [] for fr in stds: if 'over-ear' in fr.name: fr.plot_graph(fig=fig, ax=axs[0], color=f'C{len(onear_labels)}', show=False) onear_labels.append(fr.name) else: fr.plot_graph(fig=fig, ax=axs[1], color=f'C{len(inear_labels)}', show=False) inear_labels.append(fr.name) axs[0].legend(onear_labels) axs[1].legend(inear_labels) axs[0].set_title('On-ear') axs[1].set_title('In-ear') axs[0].set_ylim([0, 8]) axs[1].set_ylim([0, 8]) fig.savefig(os.path.join(DIR_PATH, 'STDs.png')) plt.close(fig)
def parse_innerfidelity(im, model, px_top=800, px_bottom=4600, px_left=500, px_right=2500): """Parses graph image downloaded from innerfidelity.com""" # Crop out everything but graph area (roughly) box = (px_left, px_top, im.size[0]-px_right, im.size[1]-px_bottom) im = im.crop(box) # Find graph edges (thick lines) v_lines = ImageGraphParser.find_lines(im, 'vertical') h_lines = ImageGraphParser.find_lines(im, 'horizontal') px_top = h_lines[0] px_bottom = h_lines[-1] px_left = v_lines[0] px_right = v_lines[-1] im = im.crop((px_left, px_top, px_right, px_bottom)) # Crop right edge to 30kHz lines = ImageGraphParser.find_lines(im, 'vertical') px_30khz = lines[-8] im = im.crop((0, 0, px_30khz, im.size[1])) # X axis f_min = 10 f_max = 30000 f_step = (f_max / f_min)**(1/im.size[0]) f = [f_min] for _ in range(1, im.size[0]): f.append(f[-1] * f_step) # Y axis a_max = 20 a_min = -50 a_res = (a_max - a_min) / im.size[1] # dB / px # Check crop _im = im.crop((20, 20, im.size[0] - 20, im.size[1] - 20)) # im.show() n_h = len(ImageGraphParser.find_lines(_im, 'horizontal')) n_v = len(ImageGraphParser.find_lines(_im, 'vertical')) if n_v != 28: print(n_v) raise ValueError('Innerfidelity image parsing for "{}" failed because X-axis is not correct!'.format(model)) if n_h != 13: print(n_h) raise ValueError('Innerfidelity image parsing for "{}" failed because Y-axis is not correct!'.format(model)) _im = im.copy() pix = _im.load() amplitude = [] # Iterate each column for x in range(im.size[0]): pxs = [] # Graph pixels # Iterate each row (pixel in column) for y in range(im.size[1]): # Convert read RGB pixel values and convert to HSV h, s, v = colorsys.rgb_to_hsv(*[v/255.0 for v in im.getpixel((x, y))]) # Graph pixels are colored if s > 0.8: pxs.append(float(y)) else: p = im.getpixel((x, y)) pix[x, y] = (int(0.9*p[0]), int(255*0.1+0.9*p[1]), int(0+0.9*p[2])) if not pxs: # No graph pixels found on this column amplitude.append(None) else: # Mean of recorded pixels v = np.mean(pxs) # Convert to dB value v = a_max - v * a_res amplitude.append(v) draw = ImageDraw.Draw(_im) x0 = np.log(20/f_min) / np.log(f_step) x1 = np.log(20000/f_min) / np.log(f_step) draw.rectangle(((x0, 10/a_res), (x1, 60/a_res)), outline='magenta') fr = FrequencyResponse(model, f, amplitude) return fr, _im
def parse_image(im, model, px_top=800, px_bottom=4400, px_left=0, px_right=2500): """Parses graph image downloaded from innerfidelity.com""" # Crop out everything but graph area (roughly) box = (px_left, px_top, im.size[0] - px_right, im.size[1] - px_bottom) im = im.crop(box) # im.show() # Find graph edges v_lines = ImageGraphParser.find_lines(im, 'vertical') h_lines = ImageGraphParser.find_lines(im, 'horizontal') # Crop by graph edges box = (v_lines[0], h_lines[0], v_lines[1], h_lines[1]) im = im.crop(box) # im.show() # X axis f_min = 10 f_max = 20000 f_step = (f_max / f_min)**(1 / im.size[0]) f = [f_min] for _ in range(1, im.size[0]): f.append(f[-1] * f_step) # Y axis a_max = 30 a_min = -20 a_res = (a_max - a_min) / im.size[1] _im = im.copy() pix = _im.load() amplitude = [] y_legend = 40 / 50 * im.size[1] x0_legend = np.log(70 / f_min) / np.log(f_step) x1_legend = np.log(1000 / f_min) / np.log(f_step) # Iterate each column for x in range(im.size[0]): pxs = [] # Graph pixels # Iterate each row (pixel in column) for y in range(im.size[1]): if y > y_legend and x0_legend < x < x1_legend: # Skip pixels in the legend box continue # Convert read RGB pixel values and convert to HSV h, s, v = colorsys.rgb_to_hsv( *[v / 255.0 for v in im.getpixel((x, y))]) # Graph pixels are colored if 0.7 < s < 0.9 and 20 / 360 < h < 30 / 360: pxs.append(float(y)) else: p = im.getpixel((x, y)) pix[x, y] = (int(0.3 * p[0]), int(255 * 0.7 + 0.3 * p[1]), int(0.3 * p[2])) if not pxs: # No graph pixels found on this column amplitude.append(None) else: # Mean of recorded pixels v = np.mean(pxs) # Convert to dB value v = a_max - v * a_res amplitude.append(v) # Inspection image draw = ImageDraw.Draw(_im) x0 = np.log(20 / f_min) / np.log(f_step) x1 = np.log(10000 / f_min) / np.log(f_step) draw.rectangle(((x0, 10 / a_res), (x1, 40 / a_res)), outline='magenta') draw.rectangle(((x0 + 1, 10 / a_res + 1), (x1 - 1, 40 / a_res - 1)), outline='magenta') fr = FrequencyResponse(model, f, amplitude) fr.interpolate() fr.center() return fr, _im
def parse_image(im, model): """Parses graph image downloaded from innerfidelity.com""" # Crop by left and right edges box = (69, 31, 550, 290) im = im.crop(box) px_a_max = 0 px_a_min = im.size[1] #im.show() # X axis f_min = 20 f_max = 20000 f_step = (f_max / f_min)**(1 / im.size[0]) f = [f_min] for _ in range(1, im.size[0]): f.append(f[-1] * f_step) # Y axis a_max = 150 a_min = 66 a_res = (a_max - a_min) / (px_a_min - px_a_max) # Colors #line_color = np.array([50, 155, 254]) line_color = np.array([0, 135, 0]) _im = im.copy() inspection = _im.load() amplitude = [] # Iterate each column for x in range(im.size[0]): pxs = [] # Graph pixels # Iterate each row (pixel in column) for y in range(im.size[1]): # Convert read RGB pixel values and convert to HSV rgba = im.getpixel((x, y)) # Graph pixels are colored if np.mean(np.abs(line_color - rgba[:3])) < 15: pxs.append(float(y)) else: p = im.getpixel((x, y)) inspection[x, y] = (int(0.3 * p[0]), int(255 * 0.7 + 0.3 * p[1]), int(0 + 0.3 * p[2])) if not pxs: # No graph pixels found on this column amplitude.append(None) else: # Mean of recorded pixels v = np.mean(pxs) # Convert to dB value v = a_max - v * a_res amplitude.append(v) # Inspection image draw = ImageDraw.Draw(_im) x0 = np.log(30 / f_min) / np.log(f_step) x1 = np.log(10000 / f_min) / np.log(f_step) y_0 = px_a_max + 12 / a_res y_1 = px_a_min - 12 / a_res draw.rectangle(((x0, y_0), (x1, y_1)), outline='magenta') draw.rectangle(((x0 + 1, y_0 + 1), (x1 - 1, y_1 - 1)), outline='magenta') # Create frequency response fr = FrequencyResponse(model, f, amplitude) fr.interpolate() fr.center() return fr, _im
def main(): # Read name index name_index = dict() df = pd.read_csv(os.path.join(DIR_PATH, 'name_index.tsv'), sep='\t', header=None) # Replace empty cells with empty strings df = df.fillna('') df.columns = ['name', 'full_name', 'comment'] records = df.to_dict('records') full_names = set() for record in records: if record['full_name'] in full_names: warnings.warn( 'Duplicate entry in name index with full name: "{}".'.format( record['full_name'])) continue name_index[record['name']] = record data = dict() for file_path in glob(os.path.join(DIR_PATH, 'raw_data', '*.txt')): name = os.path.split(file_path)[1] # Remove ".txt" and " R" or " L" suffix name = re.sub('\.txt$', '', name) name = re.sub(' (L|R)', '', name) if name not in name_index: warnings.warn( '"{}" missing from name index, skipping.'.format(name)) continue if name_index[name]['comment'] in [ 'ignore', 'onear' ] or not name_index[name]['full_name']: warnings.warn('Skipping "{}".'.format(name)) continue name = name_index[name]['full_name'] if name not in data: data[name] = [] # Read file with open(file_path, 'r') as f: s = f.read() freq = [] raw = [] for line in s.split('\n'): if len(line) == 0 or line[0] == '*': # Skip empty lines and comments if 'C-weighting compensation: On' in line: warnings.warn('C-weighted measurement: ' + name) continue frp = line.split(' ') if len(frp) == 1: frp = line.split('\t') if len(frp) == 2: f, r = frp elif len(frp) == 3: f, r, p = frp else: # Must be comment line continue if f == '?' or r == '?': # Skip lines with missing data continue try: freq.append(float(f)) raw.append(float(r)) except ValueError as err: # Failed to convert values to floats, must be header or comment row, skip continue # Create standard fr object fr = FrequencyResponse(name=name, frequency=freq, raw=raw) fr.interpolate() fr.center() data[name].append(fr) if not os.path.isdir(os.path.join(DIR_PATH, 'inspection')): os.makedirs(os.path.join(DIR_PATH, 'inspection')) # Iterate all models for name, frs in data.items(): # Average SPL data from all measurements for this model (so Left and Right) raw = np.mean([fr.raw for fr in frs], axis=0) # Save as CSV fr = FrequencyResponse(name=name, frequency=frs[0].frequency, raw=raw) dir_path = os.path.join(DIR_PATH, 'data', 'inear', name) if not os.path.isdir(dir_path): os.makedirs(dir_path) fr.write_to_csv(os.path.join(dir_path, name + '.csv')) # Save inspection image fr.plot_graph(show=False, file_path=os.path.join(DIR_PATH, 'inspection', name + '.png')) plt.close()
def equalize(self, max_gain=DEFAULT_MAX_GAIN, limit=18, limit_decay=0.0, concha_interference=False, window_size=1 / 12, treble_window_size=2, treble_f_lower=DEFAULT_TREBLE_F_LOWER, treble_f_upper=DEFAULT_TREBLE_F_UPPER): """Creates equalization curve and equalized curve. Args: max_gain: Maximum positive gain in dB limit: Maximum slope in dB per octave limit_decay: Decay coefficient (per octave) for the limit. Value of 0.5 would reduce limit by 50% in an octave when traversing a single limitation zone. concha_interference: Do measurements include concha interference which produced a narrow dip around 9 kHz? window_size: Smoothing window size in octaves. treble_window_size: Smoothing window size in octaves in the treble region. treble_f_lower: Lower boundary of transition frequency region. In the transition region normal filter is \ switched to treble filter with sigmoid weighting function. treble_f_upper: Upper boundary of transition frequency reqion. In the transition region normal filter is \ switched to treble filter with sigmoid weighting function. Returns: """ fr = FrequencyResponse(name='fr', frequency=self.frequency, raw=self.error) # Smoothen data heavily in the treble region to avoid problems caused by peakiness fr.smoothen_fractional_octave(window_size=window_size, treble_window_size=treble_window_size, treble_f_lower=treble_f_lower, treble_f_upper=treble_f_upper) # Copy data x = np.array(fr.frequency) y = np.array(-fr.smoothed) # Inverse of the smoothed error # Find peaks and notches peak_inds, peak_props = find_peaks(y, prominence=1) dip_inds, dip_props = find_peaks(-y, prominence=1) if not len(peak_inds) and not len(dip_inds): self.equalization = y # Equalized self.equalized_raw = self.raw + self.equalization if len(self.smoothed): self.equalized_smoothed = self.smoothed + self.equalization return y, fr.smoothed.copy(), np.array([]), np.array([False] * len(y)), np.array([]),\ np.array([False] * len(y)), np.array([]), np.array([]), len(y) - 1, np.array([False] * len(y)) else: limit_free_mask = self.protection_mask(y, peak_inds, dip_inds) if concha_interference: # 8 kHz - 11.5 kHz should not be limit free zone limit_free_mask[np.logical_and(x >= 8000, x <= 11500)] = False # Find rtl start index rtl_start = self.find_rtl_start(y, peak_inds, dip_inds) # Find ltr and rtl limitations # limited_ltr is y but with slopes limited when traversing left to right # clipped_ltr is boolean mask for limited samples when traversing left to right # limited_rtl is found using ltr algorithm but with flipped data limited_ltr, clipped_ltr, regions_ltr = self.limited_ltr_slope( x, y, limit, limit_decay=limit_decay, start_index=0, peak_inds=peak_inds, limit_free_mask=limit_free_mask, concha_interference=concha_interference) limited_rtl, clipped_rtl, regions_rtl = self.limited_rtl_slope( x, y, limit, limit_decay=limit_decay, start_index=rtl_start, peak_inds=peak_inds, limit_free_mask=limit_free_mask, concha_interference=concha_interference) # ltr and rtl limited curves are combined with min function combined = self.__class__(name='limiter', frequency=x, raw=np.min(np.vstack( [limited_ltr, limited_rtl]), axis=0)) # Gain can be reduced in the treble region # Clip positive gain to max gain combined.raw = np.min(np.vstack( [combined.raw, np.ones(combined.raw.shape) * max_gain]), axis=0) # Smoothen the curve to get rid of hard kinks combined.smoothen_fractional_octave(window_size=1 / 5, treble_window_size=1 / 5) # TODO: Fix trend by comparing super heavy smoothed equalizer frequency responses: limited vs unlimited # Equalization curve self.equalization = combined.smoothed # Equalized self.equalized_raw = self.raw + self.equalization if len(self.smoothed): self.equalized_smoothed = self.smoothed + self.equalization return combined.smoothed.copy(), fr.smoothed.copy(), limited_ltr, clipped_ltr, limited_rtl,\ clipped_rtl, peak_inds, dip_inds, rtl_start, limit_free_mask
def parse_image(im, model, channel): """Parses graph image downloaded from innerfidelity.com""" # Find graph edges v_lines = ImageGraphParser.find_lines(im, 'vertical', line_color=(204, 204, 204)) h_lines = ImageGraphParser.find_lines(im, 'horizontal', line_color=(204, 204, 204)) h_lines = np.array(h_lines) # Crop by left and right edges box = (v_lines[0], h_lines[0], v_lines[-1], im.size[1]) im = im.crop(box) h_lines -= h_lines[ 0] # Cropped to first line, subtract distance from all lines # Add missing horizontal lines 115 - 55 deltas = [] for i in range(1, len(h_lines)): deltas.append(h_lines[i] - h_lines[i - 1]) delta = max(deltas, key=deltas.count) _h_lines = list(h_lines[:1]) for _ in range(12): # First line + 12 additional lines # Estimate where the line should be estimate = _h_lines[-1] + delta # Get original lines which are at most 1 pixel away from the estimate originals = h_lines[np.abs(np.array(h_lines) - estimate) <= 1] if len(originals): # Original line found, use it estimate = originals[0] # Add new line _h_lines.append(estimate) h_lines = _h_lines # Crop bottom box = (0, 0, im.size[0], h_lines[-1]) im = im.crop(box) px_a_max = 0 px_a_min = h_lines[-1] #im.show() # X axis f_min = 20 f_max = 20000 f_step = (f_max / f_min)**(1 / im.size[0]) f = [f_min] for _ in range(1, im.size[0]): f.append(f[-1] * f_step) # Y axis a_max = 115 a_min = 55 a_res = (a_max - a_min) / (px_a_min - px_a_max) # Colors color_left = (79, 129, 189) color_right = (119, 119, 119) _im = im.copy() inspection = _im.load() amplitude = [] # Iterate each column for x in range(im.size[0]): pxs = [] # Graph pixels # Iterate each row (pixel in column) for y in range(im.size[1]): # Convert read RGB pixel values and convert to HSV rgba = im.getpixel((x, y)) r, g, b = rgba[:3] # Graph pixels are colored if (channel == 'left' and (r, g, b) == color_left) or (channel == 'right' and (r, g, b) == color_right): pxs.append(float(y)) else: p = im.getpixel((x, y)) inspection[x, y] = (int(0.3 * p[0]), int(255 * 0.7 + 0.3 * p[1]), int(0 + 0.3 * p[2])) if not pxs: # No graph pixels found on this column amplitude.append(None) else: # Mean of recorded pixels v = np.mean(pxs) # Convert to dB value v = a_max - v * a_res amplitude.append(v) # Inspection image draw = ImageDraw.Draw(_im) x0 = np.log(30 / f_min) / np.log(f_step) x1 = np.log(10000 / f_min) / np.log(f_step) y_0 = px_a_max + 10 / a_res y_1 = px_a_min - 10 / a_res draw.rectangle(((x0, y_0), (x1, y_1)), outline='magenta') draw.rectangle(((x0 + 1, y_0 + 1), (x1 - 1, y_1 - 1)), outline='magenta') # Create frequency response fr = FrequencyResponse(model, f, amplitude) fr.interpolate() fr.center() return fr, _im