예제 #1
0
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')
예제 #2
0
    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}"')
예제 #3
0
    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
예제 #4
0
    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}"')
예제 #5
0
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)
예제 #6
0
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
예제 #7
0
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()
예제 #9
0
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')
예제 #10
0
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)
예제 #11
0
파일: average.py 프로젝트: yu45020/AutoEq
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
예제 #13
0
    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)
예제 #14
0
    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
예제 #15
0
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
예제 #16
0
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()
예제 #17
0
    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
예제 #18
0
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
예제 #19
0
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)
예제 #20
0
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)
예제 #21
0
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'))
예제 #22
0
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
예제 #23
0
파일: bass.py 프로젝트: taegyeonga/AutoEq
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()
예제 #24
0
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)
예제 #25
0
    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
예제 #26
0
    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
예제 #27
0
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
예제 #28
0
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
예제 #30
0
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