def test_translate_ft(self): """ Test translate_ft() function :return: """ img = np.random.rand(100, 100) img_ft = fft.fftshift(fft.fft2(fft.ifftshift(img))) dx = 0.065 fx = tools.get_fft_frqs(img.shape[1], dx) fy = tools.get_fft_frqs(img.shape[0], dx) df = fx[1] - fx[0] # x-shifting for n in range(1, 20): img_ft_shifted = tools.translate_ft(img_ft, [n * df, 0], dx) max_err = np.abs(img_ft_shifted[:, :-n] - img_ft[:, n:]).max() self.assertTrue(max_err < 1e-7) # y-shifting for n in range(1, 20): img_ft_shifted = tools.translate_ft(img_ft, [0, n * df], dx) max_err = np.abs(img_ft_shifted[:-n, :] - img_ft[n:, :]).max() self.assertTrue(max_err < 1e-7) # x+y shifting for n in range(1, 20): img_ft_shifted = tools.translate_ft(img_ft, [n * df, n * df], dx) max_err = np.abs(img_ft_shifted[:-n, :-n] - img_ft[n:, n:]).max() self.assertTrue(max_err < 1e-7)
def test_otf2psf(self): """ Test otf2psf() by verifying that the ideal circular aperture otf obtained with circ_aperture_otf() produces the correct psf, obtained by airy_fn() :return: """ na = 1.3 wavelength = 0.465 dx = 0.061 nx = 101 dy = dx ny = nx fxs = tools.get_fft_frqs(nx, dx) fys = tools.get_fft_frqs(ny, dy) dfx = fxs[1] - fxs[0] dfy = fys[1] - fys[0] otf = fit_psf.circ_aperture_otf(fxs[None, :], fys[:, None], na, wavelength) psf, (ys, xs) = fit_psf.otf2psf(otf, (dfy, dfx)) psf = psf / psf.max() psf_true = fit_psf.airy_fn(xs[None, :], ys[:, None], [1, 0, 0, na, 0], wavelength) self.assertAlmostEqual(np.max(np.abs(psf - psf_true)), 0, 4)
def test_fft_frqs(self): dt = 0.46436 for n in [2, 3, 4, 3634, 581]: frqs_np = numpy.fft.fftfreq(n, dt) # positive and negative frequencies, with f=0 at edge frqs_e = tools.get_fft_frqs(n, dt, centered=False, mode="symmetric") self.assertAlmostEqual(np.abs(np.max(frqs_e - frqs_np)), 0, places=14) # positive and negative frequencies, with f=0 at center frqs_c = tools.get_fft_frqs(n, dt, centered=True, mode="symmetric") self.assertAlmostEqual(np.abs( np.max(numpy.fft.fftshift(frqs_np) - frqs_c)), 0, places=14) # positive frequencies with f=0 at edge frqs_e_pos = tools.get_fft_frqs(n, dt, centered=False, mode="positive") frqs_np_pos = np.array(frqs_np, copy=True) frqs_np_pos[ frqs_np_pos < 0] = frqs_np_pos[frqs_np_pos < 0] + 1 / dt self.assertAlmostEqual(np.abs(np.max(frqs_e_pos - frqs_np_pos)), 0, places=14)
def test_xform_sinusoid_params_roi(self): """ Test function xform_sinusoid_params_roi() by constructing sinusoid pattern and passing through an affine transformation. Compare the resulting frequency determined numerically with the resulting frequency determined from the initial frequency + affine parameters :return: """ # define object space parameters # roi_img = [0, 2048, 0, 2048] roi_img = [512, 788, 390, 871] fobj = np.array([0.08333333, 0.08333333]) phase_obj = 5.497787143782089 fn = lambda x, y: 1 + np.cos(2 * np.pi * (fobj[0] * x + fobj[1] * y) + phase_obj) # define affine transform xform = affine.params2xform([1.4296003114502853, 2.3693263411981396, 2671.39109, 1.4270495211450602, 2.3144621088632635, 790.402632]) # sinusoid parameter transformation fxi, fyi, phase_roi = affine.xform_sinusoid_params_roi(fobj[0], fobj[1], phase_obj, None, roi_img, xform, input_origin="edge", output_origin="edge") fimg = np.array([fxi, fyi]) # FFT phase _, _, phase_roi_ft = affine.xform_sinusoid_params_roi(fobj[0], fobj[1], phase_obj, None, roi_img, xform, input_origin="edge", output_origin="fft") # compared with phase from fitting image directly out_coords = np.meshgrid(range(roi_img[2], roi_img[3]), range(roi_img[0], roi_img[1])) img = affine.xform_fn(fn, xform, out_coords) phase_fit_roi = float(sim.get_phase_realspace(img, fimg, 1, phase_guess=phase_roi, origin="edge")) # phase FFT ny, nx = img.shape window = scipy.signal.windows.hann(nx)[None, :] * scipy.signal.windows.hann(ny)[:, None] img_ft = fft.fftshift(fft.fft2(fft.ifftshift(img * window))) fx = tools.get_fft_frqs(nx, 1) fy = tools.get_fft_frqs(ny, 1) peak = tools.get_peak_value(img_ft, fx, fy, fimg, 2) phase_fit_roi_ft = np.mod(np.angle(peak), 2*np.pi) # accuracy is limited by frequency fitting routine... self.assertAlmostEqual(phase_roi, phase_fit_roi, 1) # probably limited by peak height finding routine self.assertAlmostEqual(phase_roi_ft, phase_fit_roi_ft, 3)
def test_pattern_main_phase_vs_phase_index(self): """ Test get_sim_phase() on the main frequency component for one SIM pattern. Ensure works for all phase indices. Tests only a single pattern vector :return: """ nx = 1920 ny = 1080 nphases = 3 fx = tools.get_fft_frqs(nx) fy = tools.get_fft_frqs(ny) window = scipy.signal.windows.hann(nx)[ None, :] * scipy.signal.windows.hann(ny)[:, None] va = [-3, 11] vb = [3, 12] # va = [-1, 117] # vb = [-6, 117] rva, rvb = dmd.get_reciprocal_vects(va, vb) for jj in range(nphases): phase = dmd.get_sim_phase(va, vb, nphases, jj, [nx, ny], origin="fft") pattern, _ = dmd.get_sim_pattern([nx, ny], va, vb, nphases, jj) pattern_ft = fft.fftshift(fft.fft2(fft.ifftshift(pattern * window))) pattern_phase_est = np.mod( np.angle(tools.get_peak_value(pattern_ft, fx, fy, rvb, 2)), 2 * np.pi) # assert np.round(np.abs(pattern_phase_est - float(phase)), 3) == 0 self.assertAlmostEqual(pattern_phase_est, float(phase), 3)
ppath = dmd_pattern_data_fpath[kk] xform = affine_xforms[kk] with open(ppath, 'rb') as f: pattern_data = pickle.load(f) # DMD intensity frequency and phase (twice electric field frq/phase) frqs_dmd[kk] = 2 * pattern_data['frqs'] phases_dmd[kk] = 2 * pattern_data['phases'] dmd_nx = pattern_data['nx'] dmd_ny = pattern_data['ny'] for kk in range(ncolors): # otf matrix fmax = 1 / (0.5 * emission_wavelengths[kk] / na) fx = tools.get_fft_frqs(nx_roi, pixel_size) fy = tools.get_fft_frqs(ny_roi, pixel_size) ff = np.sqrt(fx[None, :]**2 + fy[:, None]**2) otf = otf_fn(ff, fmax) otf[ff >= fmax] = 0 # guess frequencies/phases frqs_guess = np.zeros((nangles, 2)) phases_guess = np.zeros((nangles, nphases)) for ii in range(nangles): for jj in range(nphases): # estimate frequencies based on affine_xform frqs_guess[ii, 0], frqs_guess[ii, 1], phases_guess[ii, jj] = \ affine.xform_sinusoid_params_roi(frqs_dmd[kk, ii, 0], frqs_dmd[kk, ii, 1], phases_dmd[kk, ii, jj], [dmd_ny, dmd_nx], roi, xform)
def simulated_img(ground_truth, max_photons, cam_gains, cam_offsets, cam_readout_noise_sds, pix_size=None, otf=None, na=1.3, wavelength=0.5, photon_shot_noise=True, bin_size=1, use_otf=False): """ Convert ground truth image (with values between 0-1) to simulated camera image, including the effects of photon shot noise and camera readout noise. :param use_otf: :param ground_truth: Relative intensity values of image :param max_photons: Mean photons emitted by ber of photons will be different than expected. Furthermore, due to the "blurring" of the point spread function and possible binning of the image, no point in the image may realize "max_photons" :param cam_gains: gains at each camera pixel :param cam_offsets: offsets of each camera pixel :param cam_readout_noise_sds: standard deviation characterizing readout noise at each camera pixel :param pix_size: pixel size of ground truth image in ums. Note that the pixel size of the output image will be pix_size * bin_size :param otf: optical transfer function. If None, use na and wavelength to set values :param na: numerical aperture. Only used if otf=None :param wavelength: wavelength in microns. Only used if otf=None :param photon_shot_noise: turn on/off photon shot-noise :param bin_size: bin pixels before applying Poisson/camera noise. This is to allow defining a pattern on a finer pixel grid. :return img: :return snr: :return max_photons_real: """ if np.any(ground_truth > 1) or np.any(ground_truth < 0): warnings.warn( 'ground_truth image values should be in the range [0, 1] for max_photons to be correct' ) img_size = ground_truth.shape if use_otf: # get OTF if otf is None: fx = tools.get_fft_frqs(img_size[1], pix_size) fy = tools.get_fft_frqs(img_size[0], pix_size) otf = psf.circ_aperture_otf(fx[None, :], fy[:, None], na, wavelength) # blur image with otf/psf # todo: maybe should add an "imaging forward model" function to fit_psf.py and call it here. gt_ft = fft.fftshift(fft.fft2(fft.ifftshift(ground_truth))) img_blurred = max_photons * fft.fftshift( fft.ifft2(fft.ifftshift(gt_ft * otf))).real img_blurred[img_blurred < 0] = 0 else: img_blurred = max_photons * ground_truth # resample image by binning img_blurred = tools.bin(img_blurred, (bin_size, bin_size), mode='sum') max_photons_real = img_blurred.max() # add shot noise if photon_shot_noise: img_shot_noise = np.random.poisson(img_blurred) else: img_shot_noise = img_blurred # add camera noise and convert from photons to ADU readout_noise = np.random.standard_normal( img_shot_noise.shape) * cam_readout_noise_sds img = cam_gains * img_shot_noise + readout_noise + cam_offsets # signal to noise ratio sig = cam_gains * img_blurred # assuming photon number large enough ~gaussian noise = np.sqrt(cam_readout_noise_sds**2 + cam_gains**2 * img_blurred) snr = sig / noise return img, snr, max_photons_real
def plot_pattern(img, va, vb, frq_vects, fmax_img, pixel_size_um, dmd_size, affine_xform, roi, nphases, phase_index, fmax_in=None, peak_int_exp=None, peak_int_exp_unc=None, peak_int_theory=None, otf=None, otf_unc=None, to_use=None, figsize=(20, 10)): """ plot image and affine xformed pattern it corresponds to :param img: image ny x nx :param va: [vx, vy] :param vb: [vx, vy] :param frq_vects: vecs1 x nvecs2 x 2. in 1/um :param float fmax_img: in 1/um :param float pixel_size_um: :param dmd_size: [] :param affine_xform: affine transformation between DMD space and camera space :param roi: [ystart, yend, xstart, xend] region of interest in image :param nphases: number of phaseshifts used to generate the DMD pattern. Needed in addition to va/vb to specify pattern :param phase_index: index of phaseshift. Needed in addition to va, vb, nphases to specify pattern :return fig_handle: """ if to_use is None: to_use = np.ones(peak_int_exp.shape, dtype=np.bool) fmags = np.linalg.norm(frq_vects, axis=-1) n1max = int(np.round(0.5 * (fmags.shape[0] - 1))) n2max = int(np.round(0.5 * (fmags.shape[1] - 1))) # generate DMD pattern pattern, _ = dmd_patterns.get_sim_pattern(dmd_size, va, vb, nphases, phase_index) # crop image img_roi = img[roi[0]:roi[1], roi[2]:roi[3]] ny, nx = img_roi.shape # transform pattern using affine transformation xform_roi = affine.xform_shift_center(affine_xform, cimg_new=(roi[2], roi[0])) img_coords = np.meshgrid(range(nx), range(ny)) pattern_xformed = affine.affine_xform_mat(pattern, xform_roi, img_coords, mode="interp") # pattern_xformed_ft = fft.fftshift(fft.fft2(fft.ifftshift(pattern_xformed))) # get fourier transform of image fxs = tools.get_fft_frqs(nx, pixel_size_um) dfx = fxs[1] - fxs[0] fys = tools.get_fft_frqs(ny, pixel_size_um) dfy = fys[1] - fys[0] extent = [ fxs[0] - 0.5 * dfx, fxs[-1] + 0.5 * dfx, fys[-1] + 0.5 * dfy, fys[0] - 0.5 * dfy ] window = scipy.signal.windows.hann(nx)[ None, :] * scipy.signal.windows.hann(ny)[:, None] img_ft = fft.fftshift(fft.fft2(fft.ifftshift(img_roi * window))) # plot results figh = plt.figure(figsize=figsize) grid = plt.GridSpec(2, 6) period = dmd_patterns.get_sim_period(va, vb) angle = dmd_patterns.get_sim_angle(va, vb) frq_main_um = frq_vects[n1max, n2max + 1] plt.suptitle( "DMD period=%0.3f mirrors, angle=%0.2fdeg\n" "Camera period=%0.1fnm = 1/%0.3f um, angle=%0.2fdeg\n" "va=(%d, %d); vb=(%d, %d)" % (period, angle * 180 / np.pi, 1 / np.linalg.norm(frq_main_um) * 1e3, np.linalg.norm(frq_main_um), np.mod(np.angle(frq_main_um[0] + 1j * frq_main_um[1]), 2 * np.pi) * 180 / np.pi, va[0], va[1], vb[0], vb[1])) plt.subplot(grid[0, 0:2]) plt.imshow(img_roi) plt.title('image') plt.subplot(grid[0, 2:4]) plt.imshow(pattern_xformed) plt.title('pattern after affine xform') ax = plt.subplot(grid[0, 4:6]) plt.title('image FFT') plt.imshow(np.abs(img_ft)**2, norm=PowerNorm(gamma=0.1), extent=extent) # to_plot = np.logical_not(np.isnan(intensity_exp_norm)) nmax = int(np.round((frq_vects.shape[1] - 1) * 0.5)) plt.scatter(frq_vects[to_use, 0].ravel(), frq_vects[to_use, 1].ravel(), facecolor='none', edgecolor='r') plt.scatter(-frq_vects[to_use, 0].ravel(), -frq_vects[to_use, 1].ravel(), facecolor='none', edgecolor='m') # plt.scatter(frq_vects[nmax, nmax + 1, 0], frq_vects[nmax, nmax + 1, 1], facecolor="none", edgecolor='k') # plt.scatter(frq_vects[nmax, nmax - 1, 0], frq_vects[nmax, nmax - 1, 1], facecolor="none", edgecolor='k') # plt.scatter(frq_vects[nmax, nmax + 2, 0], frq_vects[nmax, nmax + 2, 1], facecolor="none", edgecolor='k') # plt.scatter(frq_vects[nmax, nmax - 2, 0], frq_vects[nmax, nmax - 2, 1], facecolor="none", edgecolor='k') circ = matplotlib.patches.Circle((0, 0), radius=fmax_img, color='k', fill=0, ls='--') ax.add_artist(circ) if fmax_in is not None: circ2 = matplotlib.patches.Circle((0, 0), radius=(fmax_in / 2), color='r', fill=0, ls='--') ax.add_artist(circ2) plt.xlim([-fmax_img, fmax_img]) plt.ylim([fmax_img, -fmax_img]) ax = plt.subplot(grid[1, :2]) plt.title("peaks amp expt/theory") plt.xlabel("Frequency (1/um)") plt.ylabel("Intensity") plt.plot([fmax_img, fmax_img], [0, 1], 'k') phs = [] legend_entries = [] if peak_int_theory is not None: ph, = ax.plot( fmags[to_use], np.abs(peak_int_theory[to_use]).ravel() / np.nanmax(np.abs(peak_int_theory[to_use])), '.') phs.append(ph) legend_entries.append("theory") if peak_int_exp is not None: if peak_int_exp_unc is None: peak_int_exp_unc = np.zeros(peak_int_exp.shape) ph = ax.errorbar(fmags[to_use], np.abs(peak_int_exp[to_use]).ravel() / np.nanmax(np.abs(peak_int_exp)), yerr=peak_int_exp_unc[to_use].ravel() / np.nanmax(np.abs(peak_int_exp)), fmt='x') phs.append(ph) legend_entries.append("experiment") ax.set_ylim([1e-4, 1.2]) ax.set_xlim([-0.1 * fmax_img, 1.1 * fmax_img]) ax.set_yscale('log') plt.legend(phs, legend_entries) # plot phase ax = plt.subplot(grid[1, 2:4]) plt.title("peaks phase expt/theory") plt.xlabel("Frequency (1/um)") plt.ylabel("phase") plt.plot([fmax_img, fmax_img], [-np.pi, np.pi], 'k') phs = [] legend_entries = [] if peak_int_theory is not None: ph, = ax.plot(fmags[to_use], np.angle(peak_int_theory[to_use]).ravel(), '.') phs.append(ph) legend_entries.append("theory") if peak_int_exp is not None: ph, = ax.plot(fmags[to_use], np.angle(peak_int_exp[to_use]).ravel(), 'x') phs.append(ph) legend_entries.append("experiment") ax.set_xlim([-0.1 * fmax_img, 1.1 * fmax_img]) plt.legend(phs, legend_entries) # plot otf ax = plt.subplot(grid[1, 4:]) plt.title("otf") plt.xlabel("Frequency (1/um)") plt.ylabel("otf") ax.plot([0, fmax_img], [0, 0], 'k') ax.plot([fmax_img, fmax_img], [0, 1], 'k') if otf is not None: if otf_unc is None: otf_unc = np.zeros(otf.shape) ax.errorbar(fmags[to_use], np.abs(otf[to_use]).ravel(), yerr=otf_unc[to_use].ravel(), fmt='.') ax.set_ylim([-0.05, 1.2]) ax.set_xlim([-0.1 * fmax_img, 1.1 * fmax_img]) return figh
def get_all_fourier_exp(imgs, frq_vects_theory, roi, pixel_size_um, fmax_img, to_use=None, use_guess_frqs=True, max_frq_shift_pix=1.5, force_start_from_guess=True, peak_pix=2, bg=100): """ Calculate Fourier components from a set of images. :param imgs: nimgs x ny x nx :param vects: nimgs x nvecs1 x nvecs2 x 2 :param float pixel_size_um: :param bool use_guess_frqs: if True, use guess frequencies computed from frq_vects_theory, if False use fitting procedure to find peak :param int peak_pix: number of pixels to use when calculating peak. Typically 2. :param float bg: :return intensity: :return intensity_unc: :return frq_vects_expt: """ if to_use is None: to_use = np.ones(frq_vects_theory[:, :, :, 0].shape, dtype=np.int) nimgs = frq_vects_theory.shape[0] n1_vecs = frq_vects_theory.shape[1] n2_vecs = frq_vects_theory.shape[2] intensity = np.zeros(frq_vects_theory.shape[:-1], dtype=np.complex) * np.nan intensity_unc = np.zeros(intensity.shape) * np.nan # apodization, 2D window from broadcasting nx_roi = roi[3] - roi[2] ny_roi = roi[1] - roi[0] window = scipy.signal.windows.hann(nx_roi)[ None, :] * scipy.signal.windows.hann(ny_roi)[:, None] # generate frequency data for image FT's fxs = tools.get_fft_frqs(nx_roi, pixel_size_um) dfx = fxs[1] - fxs[0] fys = tools.get_fft_frqs(ny_roi, pixel_size_um) dfy = fys[1] - fys[0] if imgs.shape[0] == nimgs: multiple_images = True elif imgs.shape[0] == 1: multiple_images = False icrop = imgs[0, roi[0]:roi[1], roi[2]:roi[3]] img = icrop - bg img[img < 0] = 1e-6 img_ft = fft.fftshift(fft.fft2(fft.ifftshift(img * window))) noise_power = sim_reconstruction.get_noise_power( img_ft, fxs, fys, fmax_img) else: raise Exception() frq_vects_expt = np.zeros(frq_vects_theory.shape) tstart = time.process_time() for ii in range(nimgs): tnow = time.process_time() print("%d/%d, %d peaks, elapsed time = %0.2fs" % (ii + 1, nimgs, np.sum(to_use[ii]), tnow - tstart)) if multiple_images: # subtract background and crop to ROI # img = img[0, roi[0]:roi[1], roi[2]:roi[3]] - bg # img[img < 0] = 1e-6 icrop = imgs[ii, roi[0]:roi[1], roi[2]:roi[3]] img = icrop - bg img[img < 0] = 1e-6 # fft img_ft = fft.fftshift(fft.fft2(fft.ifftshift(img * window))) # get noise noise_power = sim_reconstruction.get_noise_power( img_ft, fxs, fys, fmax_img) # minimimum separation between reciprocal lattice vectors vnorms = np.linalg.norm(frq_vects_theory[ii], axis=2) min_sep = np.min(vnorms[vnorms > 0]) # get experimental weights of fourier components for aa in range(n1_vecs): for bb in range(n2_vecs): frq_vects_expt[ii, aa, bb] = frq_vects_theory[ii, aa, bb] # only do fitting if peak size exceeds tolerance, and only fit one of a peak and its compliment if not to_use[ii, aa, bb]: continue max_frq_shift = np.min([ max_frq_shift_pix * dfx, 0.5 * vnorms[aa, bb], 0.5 * min_sep ]) # get experimental frequency if (max_frq_shift / dfx) < 1 or use_guess_frqs or np.linalg.norm( frq_vects_expt[ii, aa, bb]) == 0: # if can't get large enough ROI, then use our guess pass else: # fit real fourier component in image space # only need wavelength and na to get fmax frq_vects_expt[ ii, aa, bb], mask = sim_reconstruction.fit_modulation_frq( img_ft, img_ft, pixel_size_um, fmax_img, frq_guess=frq_vects_theory[ii, aa, bb], max_frq_shift=max_frq_shift, force_start_from_guess=force_start_from_guess) sim_reconstruction.plot_correlation_fit( img_ft, img_ft, frq_vects_expt[ii, aa, bb], pixel_size_um, fmax_img, frqs_guess=frq_vects_theory[ii, aa, bb], roi_size=3) try: # get peak value and phase intensity[ii, aa, bb] = tools.get_peak_value( img_ft, fxs, fys, frq_vects_expt[ii, aa, bb], peak_pixel_size=peak_pix) intensity_unc[ii, aa, bb] = np.sqrt(noise_power) * peak_pix**2 # handle complimentary point with aa > bb aa_neg = n1_vecs - 1 - aa bb_neg = n2_vecs - 1 - bb intensity[ii, aa_neg, bb_neg] = intensity[ii, aa, bb].conj() intensity_unc[ii, aa_neg, bb_neg] = intensity_unc[ii, aa, bb] except: pass return intensity, intensity_unc, frq_vects_expt
def test_patterns_main_phase(self): """ Test determination of DMD pattern phase by comparing with FFT phase for dominant frequency component of given pattern. This compares get_sim_phase() with directly producing a pattern via get_sim_pattern() and numerically determining the phase. Unlike test_phase_vs_phase_index(), test many different lattice vectors, but only a single phase index :return: """ # dmd_size = [1920, 1080] dmd_size = [192, 108] nphases = 3 phase_index = 0 nx, ny = dmd_size # va_comps = np.arange(-15, 15) # vb_comps = np.arange(-30, 30, 3) va_comps = np.array([-15, -7, -3, 0, 4, 8, 17]) vb_comps = np.array([-30, -15, -6, -3, 12, 18]) # va_comps = np.array([1, 3, 10]) # vb_comps = np.array([-3, 0, 3]) # generate all lattice vectors vas_x, vas_y = np.meshgrid(va_comps, va_comps) vas = np.concatenate((vas_x.ravel()[:, None], vas_y.ravel()[:, None]), axis=1) vas = vas[np.linalg.norm(vas, axis=1) != 0] vbs_x, vbs_y = np.meshgrid(vb_comps, vb_comps) vbs = np.concatenate((vbs_x.ravel()[:, None], vbs_y.ravel()[:, None]), axis=1) vbs = vbs[np.linalg.norm(vbs, axis=1) != 0] for ii in range(len(vas)): for jj in range(len(vbs)): print("pattern %d/%d" % (len(vbs) * ii + jj + 1, len(vas) * len(vbs))) vec_a = vas[ii] vec_b = vbs[jj] try: recp_va, recp_vb = dmd.get_reciprocal_vects(vec_a, vec_b) except: continue # normal phase function phase = dmd.get_sim_phase(vec_a, vec_b, nphases, phase_index, dmd_size) # estimate phase from FFT pattern, _ = dmd.get_sim_pattern(dmd_size, vec_a, vec_b, nphases, phase_index) window = scipy.signal.windows.hann(nx)[ None, :] * scipy.signal.windows.hann(ny)[:, None] pattern_ft = fft.fftshift( fft.fft2(fft.ifftshift(pattern * window))) fx = tools.get_fft_frqs(nx, 1) fy = tools.get_fft_frqs(ny, 1) try: phase_direct = np.angle( tools.get_peak_value(pattern_ft, fx, fy, recp_vb, peak_pixel_size=2)) except: # recp_vb too close to edge of pattern continue phase_diff = np.min([ np.abs(phase - phase_direct), np.abs(phase - phase_direct - 2 * np.pi) ]) # assert np.round(phase_diff, 1) == 0 self.assertAlmostEqual(phase_diff, 0, 1)
def test_affine_phase_xform(self): """ Test transforming pattern and phases through affine xform :return: """ # get pattern dmd_size = [1920, 1080] #[nx, ny] nphases = 3 phase_index = 0 nmax = 40 # roi = tools.get_centered_roi([1024, 1024], [600, 800]) roi = tools.get_centered_roi([1024, 1024], [500, 500]) nx = roi[3] - roi[2] ny = roi[1] - roi[0] # vec_a = np.array([8, 17]) # vec_b = np.array([3, -6]) vec_a = [1, -117] vec_b = [6, -117] pattern, _ = dmd.get_sim_pattern(dmd_size, vec_a, vec_b, nphases, phase_index) unit_cell, xc, yc = dmd.get_sim_unit_cell(vec_a, vec_b, nphases) # get affine matrix # affine_mat = affine.params2xform([2, 15 * np.pi/180, 15, 1.7, -13 * np.pi/180, 3]) affine_mat = np.array( [[-1.01788979e+00, -1.04522661e+00, 2.66353915e+03], [9.92641451e-01, -9.58516962e-01, 7.83771959e+02], [0.00000000e+00, 0.00000000e+00, 1.00000000e+00]]) # get affine matrix for our ROI affine_xform_roi = affine.xform_shift_center(affine_mat, cimg_new=(roi[2], roi[0])) # get transformed phase from affine xform # transform pattern phase efields, ns, ms, vecs = dmd.get_efield_fourier_components(unit_cell, xc, yc, vec_a, vec_b, nphases, phase_index, dmd_size, nmax=nmax, origin="fft") efields = efields / np.max(np.abs(efields)) vecs_xformed = np.zeros(vecs.shape) vecs_xformed[..., 0], vecs_xformed[ ..., 1], phases = affine.xform_sinusoid_params_roi(vecs[..., 0], vecs[..., 1], np.angle(efields), pattern.shape, roi, affine_mat, input_origin="fft", output_origin="fft") efields_xformed = np.abs(efields) * np.exp(1j * phases) # get pattern phase directly from fft # transform pattern to image space img_coords = np.meshgrid(range(nx), range(ny)) # interpolation preserves phases but can distort Fourier components pattern_xform = affine.affine_xform_mat(pattern, affine_xform_roi, img_coords, mode="interp") # taking nearest pixel does a better job with amplitudes, but can introduce fourier components that did not exist before # pattern_xform_nearest = affine.affine_xform_mat(pattern, affine_xform_roi, img_coords, mode="nearest") window = scipy.signal.windows.hann(nx)[ None, :] * scipy.signal.windows.hann(ny)[:, None] pattern_ft = fft.fftshift( fft.fft2(fft.ifftshift(pattern_xform * window))) fx = tools.get_fft_frqs(nx, 1) fy = tools.get_fft_frqs(ny, 1) efields_direct = np.zeros(efields_xformed.shape, dtype=np.complex) for ii in range(efields.shape[0]): for jj in range(efields.shape[1]): if np.abs(vecs_xformed[ii, jj][0]) > 0.5 or np.abs( vecs_xformed[ii, jj][1]) > 0.5: efields_direct[ii, jj] = np.nan else: try: efields_direct[ii, jj] = tools.get_peak_value( pattern_ft, fx, fy, vecs_xformed[ii, jj], peak_pixel_size=2) except: efields_direct[ii, jj] = np.nan efields_direct = efields_direct / np.nanmax(np.abs(efields_direct)) to_compare = np.abs(efields_xformed) > 0.05 # check phases self.assertTrue( np.max( np.abs( np.angle(efields_xformed[to_compare]) - np.angle(efields_direct[to_compare]))) < 0.003) # check amplitudes self.assertTrue( np.nanmax( np.abs(efields_xformed[to_compare] - efields_direct[to_compare])) < 0.07) # check relative amplitudes self.assertTrue( np.nanmax( np.abs(efields_xformed[to_compare] - efields_direct[to_compare]) / np.abs(efields_xformed[to_compare])) < 0.25)
def test_pattern_all_phases(self): """ Test that can predict phases for ALL reciprocal lattice vectors in pattern using the get_efield_fourier_components() function. Do this for a few lattice vectors :return: """ dmd_size = [1920, 1080] nx, ny = dmd_size nphases = 3 phase_index = 0 # list of vectors to test vec_as = [[-7, 7], [-5, 5]] vec_bs = [[3, 9], [3, 9]] # vec_as = [[-1, 117]] # vec_bs = [[-6, 117]] # other unit vectors with larger unit cells that don't perfectly tile DMD do not work as well. # close, but larger errors # [12, -12] # [0, 18] for vec_a, vec_b in zip(vec_as, vec_bs): pattern, _, _, angles, frqs, periods, phases, recp_vects_a, recp_vects_b, min_leakage_angle = \ dmd.vects2pattern_data(dmd_size, [vec_a], [vec_b], nphases=nphases) pattern = pattern[0, 0] unit_cell, xc, yc = dmd.get_sim_unit_cell(vec_a, vec_b, nphases) # get ft window = scipy.signal.windows.hann(nx)[ None, :] * scipy.signal.windows.hann(ny)[:, None] pattern_ft = fft.fftshift(fft.fft2(fft.ifftshift(pattern * window))) fxs = tools.get_fft_frqs(nx) dfx = fxs[1] - fxs[0] fys = tools.get_fft_frqs(ny) dfy = fys[1] - fys[0] # get expected pattern components efield, ns, ms, vecs = dmd.get_efield_fourier_components( unit_cell, xc, yc, vec_a, vec_b, nphases, phase_index, dmd_size, nmax=40) # divide by size of DC component efield = efield / np.max(np.abs(efield)) # get phase from fft efield_img = np.zeros(efield.shape, dtype=np.complex) for ii in range(len(ns)): for jj in range(len(ms)): if np.abs(vecs[ii, jj][0]) > 0.5 or np.abs( vecs[ii, jj][1]) > 0.5: efield_img[ii, jj] = np.nan continue try: efield_img[ii, jj] = tools.get_peak_value( pattern_ft, fxs, fys, vecs[ii, jj], 2) except: efield_img[ii, jj] = np.nan # divide by size of DC component efield_img = efield_img / np.nanmax(np.abs(efield_img)) # import matplotlib.pyplot as plt # from matplotlib.colors import PowerNorm # plt.figure() # fs = np.linalg.norm(vecs, axis=2) # # xlim = [-0.05, 1.2*np.max([fxs.max(), fys.max()])] # to_use = np.logical_and(np.logical_not(np.isnan(efield_img)), np.abs(efield) > 1e-8) # # plt.subplot(2, 2, 1) # plt.semilogy(fs[to_use], np.abs(efield_img[to_use]), 'r.') # plt.semilogy(fs[to_use], np.abs(efield[to_use]), 'bx') # plt.xlim(xlim) # plt.ylabel('amplitude') # plt.xlabel('Frq 1/mirrors') # plt.legend(['FFT', 'Prediction']) # # plt.subplot(2, 2, 2) # plt.plot(fs[to_use], np.abs(efield_img[to_use]), 'r.') # plt.plot(fs[to_use], np.abs(efield[to_use]), 'bx') # plt.xlim(xlim) # plt.ylabel('amplitude') # plt.xlabel('Frq 1/mirrors') # # plt.subplot(2, 2, 3) # plt.plot(fs[to_use], np.mod(np.angle(efield_img[to_use]), 2*np.pi), 'r.') # plt.plot(fs[to_use], np.mod(np.angle(efield[to_use]), 2*np.pi), 'bx') # plt.xlim(xlim) # plt.ylabel('phases') # plt.xlabel('Frq 1/mirrors') # # plt.subplot(2, 2, 4) # extent = [fxs[0] - 0.5 * dfx, fxs[-1] + 0.5 * dfx, fys[-1] + 0.5 * dfy, fys[0] - 0.5 * dfy] # plt.imshow(np.abs(pattern_ft), extent=extent, norm=PowerNorm(gamma=0.1)) # # assert np.round(np.nanmax(np.abs(efield_img - efield)), 12) == 0 self.assertAlmostEqual(np.nanmax(np.abs(efield_img - efield)), 0, 12)