def plot_performance_LF(M_lf, M_hf, Su, sh_l, sh_m, title=""):

    T = sg.az_el()
    Y_test_dirs = rsh.real_sph_harm_transform(sh_l, sh_m, T.az, T.el)

    P, rVxyz, _, _ = compute_rVrE_fast(M_lf, Su, Y_test_dirs)
    rVaz, rVel, rVr, rVu = xyz2aeru(rVxyz)

    _, _, E, rExyz = compute_rVrE_fast(M_hf, Su, Y_test_dirs)
    rEaz, rEel, rEr, rEu = xyz2aeru(rExyz)

    out_figs=[]

    fig = plot_rX(rVr.reshape(T.shape),
                  title=f"{title}\nMagnitude of rV vs. test direction",
                  clim=(0.7, 1.1),
                  show=False)
    out_figs.append(fig)
    plt.show()

    ev_dot = np.sum(rEu * rVu, axis=0)
    dir_diff = np.arccos(np.clip(ev_dot, -1, 1)) * 180/np.pi
    fig = plot_rX(dir_diff.reshape(T.shape),
                  title=f"{title}\nrE vs. rV direction difference (degrees)",
                  clim=(0, 20),
                  show=False)
    out_figs.append(fig)
    plt.show()

    return out_figs
示例#2
0
def projection(degree, order, speakers_azimuth, speakers_elevation):
    """Compute basic decoder matrix by projection method.

    Parameters
    ----------
        degree: int or array-like collection of ints
            degree (l) of each spherical harmonic.
        order: int or array-like collection of ints
            order (m) of each spherical harmonic.
        speakers_azimuth, speakers_elevation: array-like collection of floats:
            speaker azimuths and elevations in radians.

    Returns
    -------
        numpy.ndarray
            Basic decoder matrix

    Note
    ----
        Optimal for platonic solids, more generally spherical designs only.
        This is here mostly for comparison to other methods.
    """
    if np.isscalar(degree):
        sh_l, sh_m, *_ = zip(*channel_spec(degree, order))
    else:
        sh_l, sh_m = degree, order

    M = rsh.real_sph_harm_transform(sh_l, sh_m,
                                    np.array(speakers_azimuth).ravel(),
                                    np.array(speakers_elevation).ravel())
    return M.transpose()
def compute_rVrE(sh_l, sh_m, M, Su, test_dirs=sg.az_el()):
    """Compute rV and rE, single call interface."""
    Y_test_dirs = rsh.real_sph_harm_transform(sh_l, sh_m,
                                              test_dirs.az.ravel(),
                                              test_dirs.el.ravel())

    P, rVxyz, E, rExyz = compute_rVrE_fast(M, Su, Y_test_dirs)

    rVaz, rVel, rVr, rVu = xyz2aeru(rVxyz)
    rEaz, rEel, rEr, rEu = xyz2aeru(rExyz)

    return rVr.reshape(test_dirs.shape), rEr.reshape(test_dirs.shape)
def compute_rVrE_dict(sh_l, sh_m, M, Su, test_dirs=sg.az_el()):
    """Compute rV and rE, single call interface."""
    Y_test_dirs = rsh.real_sph_harm_transform(sh_l, sh_m,
                                              test_dirs.az.ravel(),
                                              test_dirs.el.ravel())

    P, rVxyz, E, rExyz = compute_rVrE_fast(M, Su, Y_test_dirs)

    P = P.reshape(test_dirs.shape)
    #rVxyz = rVxyz.reshape(3, *test_dirs.shape)
    E = E.reshape(test_dirs.shape)
    #rExyz = rExyz.reshape(3, *test_dirs.shape)

    rVaz, rVel, rVr, rVu = xyz2aeru(rVxyz)
    rEaz, rEel, rEr, rEu = xyz2aeru(rExyz)

    rEaz = rEaz.reshape(test_dirs.shape)
    rEel = rEel.reshape(test_dirs.shape)

    return dict(P=P, rVxyz=rVxyz, E=E, rExyz=rExyz,
                rVaz=rVaz, rVel=rVel, rVr=rVr, rVu=rVu,
                rEaz=rEaz, rEel=rEel, rEr=rEr, rEu=rEu,
                M=M, sh_l=sh_l, sh_m=sh_m, test_dirs=test_dirs)
def plot_performance(M, Su, sh_l, sh_m, # /,  # / instroduced in 3.8
                     mask_matrix=None,
                     title="",
                     plot_spkrs=True,
                     test_dirs=None):
    """Compute and plot basic performance metrics of a decoder matrix."""
    # fill in defaults
    if test_dirs is None:
        test_dirs = sg.az_el()

    ambisonic_order = np.max(sh_l)  # FIXME: is this correct for mixed orders?

    # we want to return a list of all the figures to make the HTML report.
    # accumulate them in out_figs
    out_figs = []

    Y_test_dirs = rsh.real_sph_harm_transform(sh_l, sh_m,
                                              test_dirs.az.ravel(),
                                              test_dirs.el.ravel())
    if mask_matrix is not None:
        Y_test_dirs = mask_matrix @ Y_test_dirs

    P, rVxyz, E, rExyz = compute_rVrE_fast(M, Su, Y_test_dirs)

    rVaz, rVel, rVr, rVu = xyz2aeru(rVxyz)
    rEaz, rEel, rEr, rEu = xyz2aeru(rExyz)

    # the arg to arccos can get epsilon larger than 1 due to round off,
    # which produces NaNs, so clip to [-1, 1]
    rE_dir_err = np.arccos(np.clip(np.sum(rEu * test_dirs.u, axis=0),
                                   -1, 1)) * 180 / π

    # magnitude of rE
    if True:
        fig = plot_rX(rEr.reshape(test_dirs.shape),
                      title=(f'{title}\n' +
                             'magnitude of rE vs. test direction'),
                      clim=(0.5, 1),
                      show=False)
        out_figs.append(fig)
        plt.show()

    # plot of ambisonic order
    if True:
        fig = plot_rX(
            (shelf.rE_to_ambisonic_order_3d(
                rEr.reshape(test_dirs.shape)) - ambisonic_order).round(),
            title=(f'{title}\n' +
                   f'relative ambisonic order ({ambisonic_order}) vs. test direction'),
            clim=(-3, +3),
            cmap='Spectral_r',
            show=False)

        if plot_spkrs:
            plot_loudspeakers(Su)

        out_figs.append(fig)
        plt.show()

    # E vs td
    if True:
        E_dB = 10 * np.log10(E.reshape(test_dirs.shape))
        E_dB_ceil = np.ceil(E_dB.max())
        fig = plot_rX(E_dB,
                      title=(f'{title}\n' +
                             'E (dB) vs. test_direction'),
                      clim=(E_dB_ceil - 20, E_dB_ceil),
                      show=False,
                      )
        out_figs.append(fig)
        plt.show()

    # direction error
    if True:
        fig = plot_rX(rE_dir_err.reshape(test_dirs.shape),
                      title=f'{title}\n' +
                            'direction error (deg)',
                      clim=(0, 20),
                      show=False)
        out_figs.append(fig)
        plt.show()

        fig = plot_rX(((rE_dir_err.reshape(test_dirs.shape) / 3).round()) * 3,
                      title=f'{title}\n' +
                            'direction error (deg)',
                      clim=(0, 20),
                      show=False)

        # overlay loudspeaker positions
        if plot_spkrs:
            plot_loudspeakers(Su)

        out_figs.append(fig)
        plt.show()

    if True:
        fig = plt.figure(figsize=(10,4))
        plot_az_el_grid(sh_l, sh_m, M, Su,
                        title=f"{title}\nrE directions",
                        show=False)
        if plot_spkrs:
            plot_loudspeakers(Su)

        out_figs.append(fig)
        plt.show()

    return out_figs
def optimize_LF(M, Su, sh_l, sh_m, W=1, raise_error_on_failure=False):

    M_shape = M.shape
    g_spkr, g_total = lm.diffuse_field_gain(M)

    # the test directions
    T = sg.t_design5200()
    cap = sg.spherical_cap(T.u, (0, 0, 1), 5 * np.pi / 6)[0]
    W = np.where(cap, 1, 1)

    Y_test = rsh.real_sph_harm_transform(sh_l, sh_m, T.az, T.el)
    rExyz, E = rE(M, Su, Y_test)
    rEu, rEr = xyz2ur(rExyz)

    # define the loss function
    def o(x):
        M = x.reshape(M_shape)
        rVxyz, P = rV(M, Su, Y_test)

        df_gain = np.sum(M * M)

        df_gain_loss = (g_total - df_gain)**2

        # Tikhonov regularization term - typical value = 1e-3
        tikhonov_regularization_term = np.sum(M**2) * 1e-2  #tikhonov_lambda

        # dir loss mag(rVxyz) should be 1
        direction_loss = np.sum(W * ((rVxyz - rEu)**2))
        P_loss = np.sum(W * ((P - 1)**2))
        return (direction_loss + df_gain_loss + P_loss / 100000 +
                tikhonov_regularization_term)

    val_and_grad_fn = jax.value_and_grad(o)
    val_and_grad_fn = jax.jit(val_and_grad_fn)

    def objective_and_gradient(x):
        v, g = val_and_grad_fn(x)
        g = onp.array(g, order='F')
        return v, g

    x0 = M.ravel()
    with Timer() as t:
        res = opt.minimize(
            objective_and_gradient,
            x0,
            bounds=opt.Bounds(-1, 1),
            method='L-BFGS-B',
            jac=True,
            options=dict(disp=50,
                         # maxls=50,
                         # maxcor=30,
                         # gtol=1e-8,
                         # ftol=1e-12
                         ),
            # callback=callback,
        )
    if True:
        print()
        print(f"Execution time: {t.interval:0.3f} sec.")
        print(res.message)
        print(res)
        print()

    if res.status != 0 and raise_error_on_failure:
        print('bummer:', res.message)
        raise RuntimeError(res.message)

    M_opt = res.x.reshape(M_shape)
    return M_opt, res
def optimize(
    M,
    Su,
    sh_l,
    sh_m,
    E_goal=None,
    iprint=50,
    tikhonov_lambda=1.0e-3,
    sparseness_penalty=1,
    uniform_loudness_penalty=0.1,  #0.01
    rE_goal=1.0,
    maxcor=100,  # how accurate is the Hessian, more is better but slower
    raise_error_on_failure=True):
    """Optimize psychoacoustic criteria."""
    #
    # handle defaults
    if E_goal is None:
        E_goal = 1

    if rE_goal == 'auto' or rE_goal is None:
        #FIXME This assumes 3D arrays
        rE_goal = shelf.max_rE_3d(np.max(sh_l) + 2)

    print(f"rE_goal min={np.min(rE_goal)} max={np.max(rE_goal)}")

    # the test directions
    T = sg.t_design5200()
    Y_test = rsh.real_sph_harm_transform(sh_l, sh_m, T.az, T.el)

    if M is None:
        # infer M_shape from Su and Y
        M_shape = (
            Su.shape[1],  # number of loudspeakers
            Y_test.shape[0],  # number of program channels
        )
        M = random.uniform(key, shape=M_shape, minval=-1.0, maxval=1.0)
    else:
        M_shape = M.shape

    # the loss function
    def o(x) -> float:
        M = x.reshape(M_shape)
        rExyz, E = rE(M, Su, Y_test)

        # truncation loss due to finite order
        truncation_loss = np.sum((rExyz - T.u * rE_goal)**2)

        # uniform loudness loss
        uniform_loudness_loss = (np.sum(
            (E - E_goal)**2) * uniform_loudness_penalty)  # was 10

        # Tikhonov regularization term - typical value = 1e-3
        tikhonov_regularization_term = np.sum(M**2) * tikhonov_lambda

        # don't turn off speakers
        # pull diffuse-field gain for each speaker away from zero
        sparsness_term = (np.sum(np.abs(1 - np.sum(M**2, axis=1))) * 100 *
                          sparseness_penalty)

        # the entire loss function
        f = (truncation_loss + uniform_loudness_loss +
             tikhonov_regularization_term + sparsness_term)
        return f

    # consult the automatic differentiation oracle
    val_and_grad_fn = jax.value_and_grad(o)
    val_and_grad_fn = jax.jit(val_and_grad_fn)

    def objective_and_gradient(x, *args):
        v, g = val_and_grad_fn(x, *args)
        # I'm not to happy about having to copy g but L-BGFS needs it in
        # Fortran order and JAX only does C order.  Check with g.flags
        # NOTE: asarray() and asfortranarray() don't work correctly here
        g = onp.array(g, order='F')
        return v, g

    x0 = M.ravel()  # initial guess
    with Timer() as t:
        # https://docs.scipy.org/doc/scipy/reference/optimize.minimize-lbfgsb.html
        res = opt.minimize(
            objective_and_gradient,
            x0,
            bounds=opt.Bounds(-1, 1),
            method='L-BFGS-B',
            jac=True,
            options=dict(
                maxcor=maxcor,
                disp=iprint,
                maxls=500,
                # maxcor=30,
                gtol=1e-8,
                #ftol=1e-12
            ),
            # callback=callback,
        )
    if True:
        print()
        print(f"Execution time: {t.interval:0.3f} sec.")
        print(res.message)
        print(res)
        print()

    if res.status != 0 and raise_error_on_failure:
        print('bummer:', res.message)
        raise RuntimeError(res.message)

    M_opt = res.x.reshape(M_shape)
    return M_opt, res