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]')
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
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
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()
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)
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