예제 #1
0
    def draw_trunc(D, P, ax):
        idx_img = info['show_reconstruction']

        sampler = D.sampler()
        S, _, I_prev = sampler.decode(D[idx_img])
        sky_model = D.ground_truth[idx_img]

        N_layer = int(P['N_layer'])
        A = phased_array.steering_operator(D.XYZ, D.R, D.wl)
        lambda_ = D.lambda_[idx_img]
        I_trunc = apgd.solve(S,
                             A,
                             lambda_=lambda_,
                             gamma=D.gamma,
                             x0=I_prev.copy(),
                             N_iter_max=N_layer)
        tts = I_trunc['time']
        N_iter = I_trunc['niter']
        I_trunc = I_trunc['sol']

        if info['interpolation_order'] is not None:
            N = info['interpolation_order']
            approximate_kernel = True if (N > 15) else False
            interp = interpolate.Interpolator(N, approximate_kernel)
            N_s = N_px = D.R.shape[1]
            I_trunc = interp.__call__(weight=np.ones((N_s, )),
                                      support=D.R,
                                      f=I_trunc.reshape((1, N_px)),
                                      r=D.R)
            I_trunc = np.clip(I_trunc, a_min=0, a_max=None)

        trunc_plot = s2image.Image(data=I_trunc, grid=D.R)
        trunc_plot.draw(catalog=sky_model.xyz,
                        projection=info['projection'],
                        use_contours=False,
                        catalog_kwargs=dict(edgecolor='g', ),
                        ax=ax)
        ax.set_title(f'APGD {N_iter:02d} iter, {tts:.02f} [s]')
예제 #2
0
def train_network(args):
    """
    Parameters
    ----------
    args : :py:class:`~argparse.Namespace`

    Returns
    -------
    opt : dict
        p_opt : :py:class:`~numpy.ndarray`
            (N_epoch + 1, N_cell) optimized parameter per epoch.
            `p_opt[0] = p_apgd`
        iter_loss : :py:class:`~numpy.ndarray`
            (N_epoch, N_batch) loss function value per (epoch, batch) on
            training set.
        t_loss : :py:class:`~numpy.ndarray`
            (N_epoch + 1,) loss function value per epoch on training set.
        v_loss : :py:class:`~numpy.ndarray`
            (N_epoch + 1,) loss function value per epoch on validation set.
        t : :py:class:`~numpy.ndarray`
            (N_epoch,) execution time [s] per epoch.
            Includes time to compute training/validation loss.
        idx_t : :py:class:`~numpy.ndarray`
            (N_k1,) sample indices used for training set.
        idx_v : :py:class:`~numpy.ndarray`
            (N_k2,) sample indices used for validation set.
        K : int
            Order of polynomial filter.
        D_lambda : float
        tau_lambda : float
        N_layer : int
        psf_threshold : float
        tanh_lin_limit : float
        lr : float
        mu : float
        batch_size : int
    """
    if args.seed is not None:
        np.random.seed(args.seed)

    D = nn.DataSet.from_file(str(args.dataset))
    A = phased_array.steering_operator(D.XYZ, D.R, D.wl)
    N_antenna, N_px = A.shape
    sampler = nn.Sampler(N_antenna, N_px)

    # Set optimization initial point.
    p_apgd, K = crnn.APGD_Parameter(D.XYZ, D.R, D.wl,
                                    lambda_=np.median(D.lambda_),
                                    gamma=D.gamma,
                                    L=2 * pylinalg.eighMax(A),
                                    eps=args.psf_threshold)
    parameter = crnn.Parameter(N_antenna, N_px, K)
    p0 = p_apgd.copy()
    if args.random_initializer:
        p_mu, p_D, p_tau = parameter.decode(p0)
        if not args.fix_mu:
            mu_step = np.abs(p_mu[~np.isclose(p_mu, 0)]).min()
            p_mu[:] = mu_step * np.random.randn(K + 1)
        if not args.fix_tau:
            tau_step = np.abs(p_tau[~np.isclose(p_tau, 0)]).min()
            p_tau[:] = tau_step * np.random.randn(N_px)
        if not args.fix_D:
            D_step = np.abs(p_D[~np.isclose(p_D, 0)]).min() / 2  # because complex-valued.
            p_D[:] = D_step * (     np.random.randn(N_antenna, N_px) +
                               1j * np.random.randn(N_antenna, N_px))

    R_laplacian, _ = graph.laplacian_exp(D.R, normalized=True)

    afunc = (lambda x: func.retanh(args.tanh_lin_limit, x),
             lambda x: func.d_retanh(args.tanh_lin_limit, x))
    trainable_parameter = (('mu', not args.fix_mu),
                           ('D', not args.fix_D),
                           ('tau', not args.fix_tau))
    sample_loss = crnn.SampleLossFunction(args.N_layer, parameter, sampler, R_laplacian,
                                          args.loss, afunc, trainable_parameter)
    ridge_loss = crnn.D_RidgeLossFunction(args.D_lambda, parameter)
    laplacian_loss = crnn.LaplacianLossFunction(R_laplacian, args.tau_lambda, parameter)
    sgd_solver = optim.StochasticGradientDescent(func=[sample_loss, ridge_loss, laplacian_loss],
                                                 batch_size=args.batch_size,
                                                 N_epoch=args.N_epoch,
                                                 alpha=args.lr,
                                                 mu=args.mu,
                                                 verbosity='HIGH')

    log_fname = (pathlib.Path(args.parameter.parent) /
                 (args.parameter.stem + ".log"))
    logging.basicConfig(level=logging.DEBUG,
                        format='%(asctime)s | %(message)s',
                        filename=log_fname,
                        filemode='w')
    # Setup logging to stdout.
    console = logging.StreamHandler(sys.stdout)
    console.setLevel(logging.DEBUG)
    console_formatter = logging.Formatter('%(asctime)s | %(message)s')
    console.setFormatter(console_formatter)
    logging.getLogger(__name__).addHandler(console)
    logging.info(str(args))

    ### Dataset Preprocessing: drop all-0 samples + permutation
    _, I, _ = sampler.decode(D[:])
    sample_mask = ~np.isclose(I.sum(axis=1), 0)
    if args.tv_index is None:  # Random split
        idx_valid = np.flatnonzero(sample_mask)
        idx_sample = np.random.permutation(idx_valid)

        N_sample = len(idx_valid)
        idx_ts = idx_sample[int(N_sample * args.tv_ratio):]
        idx_vs = idx_sample[:int(N_sample * args.tv_ratio)]
    else:  # Deterministic split
        idx_tv = np.load(args.tv_index)
        if not (('idx_train' in idx_tv) and ('idx_test' in idx_tv)):
            raise ValueError('Parameter[tv_index] does not have keys "idx_train" and "idx_test".')
        idx_ts = idx_tv['idx_train']
        if not (argcheck.has_integers(idx_ts) and
                np.all((0 <= idx_ts) & (idx_ts < len(D)))):
            raise ValueError('Specified "idx_ts" values must be integer and in {0, ..., len(D) - 1}.')
        idx_vs = idx_tv['idx_test']
        if not (argcheck.has_integers(idx_vs) and
                np.all((0 <= idx_vs) & (idx_vs < len(D)))):
            raise ValueError('Specified "idx_vs" values must be integer and in {0, ..., len(D) - 1}.')

        idx_invalid = np.flatnonzero(~sample_mask)
        idx_ts = np.setdiff1d(idx_ts, idx_invalid)
        idx_vs = np.setdiff1d(idx_vs, idx_invalid)

    D_ts = nn.DataSet(D[idx_ts], D.XYZ, D.R, D.wl,
                      ground_truth=[D.ground_truth[idx] for idx in idx_ts],
                      lambda_=np.array([np.median(D.lambda_[idx_ts])] * len(idx_ts)),
                      gamma=D.gamma,
                      N_iter=D.N_iter[idx_ts],
                      tts=D.tts[idx_ts])
    D_vs = nn.DataSet(D[idx_vs], D.XYZ, D.R, D.wl,
                      ground_truth=[D.ground_truth[idx] for idx in idx_vs],
                      lambda_=np.array([np.median(D.lambda_[idx_vs])] * len(idx_vs)),
                      gamma=D.gamma,
                      N_iter=D.N_iter[idx_vs],
                      tts=D.tts[idx_vs])
    out = sgd_solver.fit(D_ts, D_vs, p0, file_name=args.parameter)

    # Augment output with extra information.
    out = dict(**out,
               D_lambda=args.D_lambda,
               tau_lambda=args.tau_lambda,
               N_layer=args.N_layer,
               psf_threshold=args.psf_threshold,
               tanh_lin_limit=args.tanh_lin_limit,
               lr=args.lr,
               mu=args.mu,
               batch_size=args.batch_size,
               K=K,
               idx_t=idx_ts,
               idx_v=idx_vs,
               loss=args.loss)
    return out
예제 #3
0
def simulate_dataset(N_sample, N_src, XYZ, R, wl, src_mask, intensity=None, rate=None):
    """
    Generate APGD dataset.

    Parameters
    ----------
    N_sample : int
        Number of images to generate.
    N_src : int
        Number of sources present per image.
    XYZ : :py:class:`~numpy.ndarray`
        (3, N_antenna) microphone positions.
    R : :py:class:`~numpy.ndarray`
        (3, N_px) pixel grid.
    wl : float
        Wavelength [m] of plane wave.
    src_mask : :py:class:`~numpy.ndarray`
        (N_px,) boolean mask saying near which pixels it is possible to place sources.
    intensity : float
        If present, generate equi-amplitude sources.
    rate : float
        If present, generate rayleigh-amplitude sources.

    Returns
    -------
    D : :py:class:`~acoustic_camera.nn.DataSet`
        (N_sample,) dataset

    Note
    ----
    Either `intensity` or `rate` must be specified, not both.
    """
    if not (N_sample > 0):
        raise ValueError('Paremeter[N_sample] must be positive.')

    if not (N_src > 0):
        raise ValueError('Paremeter[N_src] must be positive.')

    if not ((XYZ.ndim == 2) and (XYZ.shape[0] == 3)):
        raise ValueError('Parameter[XYZ]: expected (3, N_antenna) array.')
    N_antenna = XYZ.shape[1]

    if not ((R.ndim == 2) and (R.shape[0] == 3)):
        raise ValueError('Parameter[R]: expected (3, N_px) array.')
    N_px = R.shape[1]

    if wl < 0:
        raise ValueError('Parameter[wl] is out of bounds.')

    if not ((src_mask.ndim == 1) and (src_mask.size == N_px)):
        raise ValueError('Parameter[src_mask]: expected (N_px,) boolean array.')

    if (((intensity is None) and (rate is None)) or
            ((intensity is not None) and (rate is not None))):
        raise ValueError('One of Parameters[intensity, rate] must be specified.')

    if (intensity is not None) and (intensity <= 0):
        raise ValueError('Parameter[intensity] must be positive.')

    if (rate is not None) and (rate <= 0):
        raise ValueError('Parameter[rate] must be positive.')

    vis_gen = statistics.VisibilityGenerator(T=50e-3, fs=48000, SNR=10)
    A = phased_array.steering_operator(XYZ, R, wl)

    sampler = nn.Sampler(N_antenna, N_px)
    N_data = sampler._N_cell
    data = np.zeros((N_sample, N_data))
    ground_truth = [None] * N_sample
    apgd_gamma = 0.5
    apgd_lambda_ = np.zeros(N_sample)
    apgd_N_iter = np.zeros(N_sample)
    apgd_tts = np.zeros(N_sample)

    for i in range(N_sample):
        logging.info(f'Generate APGD image {i + 1}/{N_sample}.')

        ### Create synthetic sky
        if (intensity is not None):
            sky_I = intensity * np.ones(N_src)
        elif (rate is not None):
            sky_I = stats.rayleigh.rvs(scale=rate, size=N_src)
        sky_XYZ = R[:, src_mask][:, np.random.randint(0, np.sum(src_mask), size=N_src)]
        ## Randomize positions slightly to not fall straight onto grid.
        _, sky_colat, sky_lon = transform.cart2pol(*sky_XYZ)
        px_pitch = np.arccos(np.clip(R[:, 0] @ R[:, 1:], -1, 1)).min()
        colat_noise, lon_noise = 0.1 * px_pitch * np.random.randn(2, N_src)
        sky_XYZ = np.stack(transform.pol2cart(1, sky_colat + colat_noise, sky_lon + lon_noise), axis=0)
        sky_model = source.SkyModel(sky_XYZ, sky_I)

        S = vis_gen(XYZ, wl, sky_model)
        # Normalize `S` spectrum for scale invariance.
        S_D, S_V = linalg.eigh(S)
        if S_D.max() <= 0:
            S_D[:] = 0
        else:
            S_D = np.clip(S_D / S_D.max(), 0, None)
        S = (S_V * S_D) @ S_V.conj().T

        I_apgd = apgd.solve(S, A,
                            lambda_=None,
                            gamma=apgd_gamma,
                            L=None,
                            d=50,
                            x0=None,
                            eps=1e-3,
                            N_iter_max=200,
                            verbosity='NONE',  # 'LOW',
                            momentum=True)

        data[i] = sampler.encode(S=S, I=I_apgd['sol'])
        ground_truth[i] = sky_model
        apgd_lambda_[i] = I_apgd['lambda_']
        apgd_N_iter[i] = I_apgd['niter']
        apgd_tts[i] = I_apgd['time']

    D = nn.DataSet(data, XYZ, R, wl, ground_truth,
                   apgd_lambda_, apgd_gamma, apgd_N_iter, apgd_tts)
    return D
예제 #4
0
def process(args):
    file = pathlib.Path(args.dataset).expanduser().absolute()
    if not (file.exists() and (file.suffix == '.npz')):
        raise ValueError('Dataset is non-conformant.')
    D = nn.DataSet.from_file(str(file))
    N_sample = len(D)

    if not (0 <= args.img_idx < N_sample):
        raise ValueError('Parameter[img_idx] is out-of-bounds.')

    S, I_apgd, I_prev = D.sampler().decode(D[args.img_idx])
    I_das = spectral.DAS(D.XYZ, S, D.wl, D.R)

    # Rescale DAS to lie on same range as APGD
    A = phased_array.steering_operator(D.XYZ, D.R, D.wl)
    alpha = 1 / (2 * pylinalg.eighMax(A))
    beta = 2 * D.lambda_[args.img_idx] * alpha * (1 - D.gamma) + 1
    I_das *= (2 * alpha) / beta

    if args.interpolation_order is not None:
        N = args.interpolation_order
        approximate_kernel = True if (N > 15) else False
        interp = interpolate.Interpolator(N, approximate_kernel)
        N_s = N_px = D.R.shape[1]

        I_prev = interp.__call__(weight=np.ones((N_s, )),
                                 support=D.R,
                                 f=I_prev.reshape((1, N_px)),
                                 r=D.R)
        I_prev = np.clip(I_prev, a_min=0, a_max=None)

        I_apgd = interp.__call__(weight=np.ones((N_s, )),
                                 support=D.R,
                                 f=I_apgd.reshape((1, N_px)),
                                 r=D.R)
        I_apgd = np.clip(I_apgd, a_min=0, a_max=None)

        I_das = interp.__call__(weight=np.ones((N_s, )),
                                support=D.R,
                                f=I_das.reshape((1, N_px)),
                                r=D.R)
        I_das = np.clip(I_das, a_min=0, a_max=None)

    fig = plt.figure()
    ax_prev = fig.add_subplot(131)
    ax_apgd = fig.add_subplot(132)
    ax_das = fig.add_subplot(133)

    s2image.Image(I_prev, D.R).draw(catalog=D.ground_truth[args.img_idx].xyz,
                                    projection=args.projection,
                                    catalog_kwargs=dict(edgecolor='g', ),
                                    ax=ax_prev)
    ax_prev.set_title(r'$APGD_{init}$')

    s2image.Image(I_apgd, D.R).draw(catalog=D.ground_truth[args.img_idx].xyz,
                                    projection=args.projection,
                                    catalog_kwargs=dict(edgecolor='g', ),
                                    ax=ax_apgd)
    ax_apgd.set_title('APGD')

    s2image.Image(I_das, D.R).draw(catalog=D.ground_truth[args.img_idx].xyz,
                                   projection=args.projection,
                                   catalog_kwargs=dict(edgecolor='g', ),
                                   ax=ax_das)
    ax_das.set_title('DAS')

    fig.show()
예제 #5
0
def process(file, warm_start):
    """
    Parameters
    ----------
    file : :py:class:`~pathlib.Path`
        File to process.
    warm_start : bool
        Perform warm-starts for APGD.

    Returns
    -------
    Saved APGD datasets on disk.
    """
    from ground_truth import dev_xyz, src_label, src_xyz

    # Ground truth: average twitter/woofer positions.
    src_xyz = (src_xyz['twitter'] + src_xyz['woofer']) / 2
    src_xyz /= linalg.norm(src_xyz, axis=0)
    N_antenna = dev_xyz.shape[1]

    src_map = {k: v for (k, v) in zip(src_label, src_xyz.T)}
    freq, bw = (
        skutil  # Center frequencies to form images
        .view_as_windows(np.linspace(1500, 4500, 10), (2, ),
                         1).mean(axis=-1)), 50.0  # [Hz]
    T_sti = 12.5e-3
    T_stationarity = 8 * T_sti  # Choose to have frame_rate = 10.
    N_freq = len(freq)

    wl_min = constants.speed_of_sound / (freq.max() + 500)
    sh_order = phased_array.nyquist_rate(dev_xyz, wl_min)
    R = grid.fibonacci(sh_order)
    R_mask = np.abs(R[2, :]) < np.sin(np.deg2rad(50))
    R = R[:, R_mask]  # Shrink visible view to avoid border effects.
    N_px = R.shape[1]

    sampler = nn.Sampler(N_antenna, N_px)
    rate, data, sky_model = get_data(file, src_map)
    for idx_freq in range(N_freq):
        wl = constants.speed_of_sound / freq[idx_freq]
        A = phased_array.steering_operator(dev_xyz, R, wl)

        S = form_visibility(data, rate, freq[idx_freq], bw, T_sti,
                            T_stationarity)
        N_sample = S.shape[0]

        apgd_gamma = 0.5
        apgd_lambda_ = np.zeros((N_sample, ))
        apgd_N_iter = np.zeros((N_sample, ), dtype=int)
        apgd_tts = np.zeros((N_sample, ))
        apgd_data = np.zeros((N_sample, sampler._N_cell))
        I_prev = np.zeros((N_px, ))
        for idx_s in range(N_sample):
            logging.info(
                f'Processing freq_idx={idx_freq + 1:02d}/{N_freq:02d}, '
                f'sample_idx={idx_s + 1:03d}/{N_sample:03d}')

            # Normalize visibilities
            S_D, S_V = linalg.eigh(S[idx_s])
            if S_D.max() <= 0:
                S_D[:] = 0
            else:
                S_D = np.clip(S_D / S_D.max(), 0, None)
            S_norm = (S_V * S_D) @ S_V.conj().T

            I_apgd = apgd.solve(S_norm,
                                A,
                                gamma=apgd_gamma,
                                x0=I_prev.copy(),
                                verbosity='NONE')
            apgd_data[idx_s] = sampler.encode(S=S_norm,
                                              I=I_apgd['backtrace'][-1],
                                              I_prev=I_apgd['backtrace'][0])
            apgd_lambda_[idx_s] = I_apgd['lambda_']
            apgd_N_iter[idx_s] = I_apgd['niter']
            apgd_tts[idx_s] = I_apgd['time']

            if warm_start:
                I_prev = I_apgd['sol'].copy()

        D = nn.DataSet(data=apgd_data,
                       XYZ=dev_xyz,
                       R=R,
                       wl=wl,
                       ground_truth=[sky_model] * N_sample,
                       lambda_=apgd_lambda_,
                       gamma=apgd_gamma,
                       N_iter=apgd_N_iter,
                       tts=apgd_tts)

        warm_suffix = 'warm' if warm_start else 'cold'
        d_name = f'./dataset/D_{file.stem}_freq{idx_freq}_{warm_suffix}.npz'
        logging.info(f'Storing dataset to {d_name}')
        D.to_file(d_name)
예제 #6
0
def process_track(rate, data, sky_model):
    """
    Parameters
    ----------
    rate : int
        Sample rate [Hz].
    data : :py:class:`~numpy.ndarray`
        (N_sample, N_channel) samples. (float64)
    sky_model : :py:class:`~deepwave.tools.data_gen.source.SkyModel`
        (N_src,) ground truth for synthesized data stream.

    Returns
    -------
    D : dict(freq_idx -> :py:class:`~deepwave.nn.DataSet`)
    """
    dev_xyz = helpers.mic_xyz()
    N_antenna = dev_xyz.shape[1]

    freq, bw = (
        skutil  # Center frequencies to form images
        .view_as_windows(np.linspace(1500, 4500, 10), (2, ),
                         1).mean(axis=-1)), 50.0  # [Hz]
    T_sti = 12.5e-3
    T_stationarity = 8 * T_sti  # Choose to have frame_rate = 10.
    N_freq = len(freq)

    wl_min = helpers.speed_of_sound() / (freq.max() + 500)
    sh_order = phased_array.nyquist_rate(dev_xyz, wl_min)
    R = grid.fibonacci(sh_order)
    R_mask = np.abs(R[2, :]) < np.sin(np.deg2rad(30))
    R = R[:, R_mask]  # Shrink visible view to avoid border effects.
    N_px = R.shape[1]

    D = dict()
    sampler = nn.Sampler(N_antenna, N_px)
    for idx_freq in range(N_freq):
        wl = helpers.speed_of_sound() / freq[idx_freq]
        A = phased_array.steering_operator(dev_xyz, R, wl)

        S = form_visibility(data, rate, freq[idx_freq], bw, T_sti,
                            T_stationarity)
        N_sample = S.shape[0]

        apgd_gamma = 0.5
        apgd_lambda_ = np.zeros((N_sample, ))
        apgd_N_iter = np.zeros((N_sample, ), dtype=int)
        apgd_tts = np.zeros((N_sample, ))
        apgd_data = np.zeros((N_sample, sampler._N_cell))
        I_prev = np.zeros((N_px, ))
        for idx_s in range(N_sample):
            logging.info(
                f'Processing freq_idx={idx_freq + 1:02d}/{N_freq:02d}, '
                f'sample_idx={idx_s + 1:03d}/{N_sample:03d}')

            # Normalize visibilities
            S_D, S_V = linalg.eigh(S[idx_s])
            if S_D.max() <= 0:
                S_D[:] = 0
            else:
                S_D = np.clip(S_D / S_D.max(), 0, None)
            S_norm = (S_V * S_D) @ S_V.conj().T

            I_apgd = apgd.solve(S_norm,
                                A,
                                gamma=apgd_gamma,
                                x0=I_prev.copy(),
                                verbosity='NONE')
            apgd_data[idx_s] = sampler.encode(S=S_norm,
                                              I=I_apgd['backtrace'][-1],
                                              I_prev=I_apgd['backtrace'][0])
            apgd_lambda_[idx_s] = I_apgd['lambda_']
            apgd_N_iter[idx_s] = I_apgd['niter']
            apgd_tts[idx_s] = I_apgd['time']

        D_ = nn.DataSet(data=apgd_data,
                        XYZ=dev_xyz,
                        R=R,
                        wl=wl,
                        ground_truth=[sky_model] * N_sample,
                        lambda_=apgd_lambda_,
                        gamma=apgd_gamma,
                        N_iter=apgd_N_iter,
                        tts=apgd_tts)

        D[idx_freq] = D_
    return D