def test_nyquist_sampling_criterion(): pixels = 32 wavel = 3.8e-6 mask_coordinates = tf.random.normal((12, 2)) phys = PhysicalModel(pixels, mask_coordinates, wavel, oversampling_factor=2) # get frequency sampled in Fourier space B = Operators(mask_coordinates, wavel) uv = B.UVC / wavel rho = np.hypot(uv[:, 0], uv[:, 1]) freq_sampled = 1 / rad2mas(1 / rho) sampling_frequency = 1 / phys.plate_scale # 1/mas print(freq_sampled.max()) print(sampling_frequency) assert sampling_frequency > 2 * freq_sampled.max() phys = PhysicalModel(pixels, GOLAY9) # GOLAY9 mask # get frequency sampled in Fourier space B = Operators(GOLAY9, wavel) uv = B.UVC / wavel rho = np.hypot(uv[:, 0], uv[:, 1]) freq_sampled = 1 / rad2mas(1 / rho) sampling_frequency = 1 / phys.plate_scale # 1/mas print(freq_sampled.max()) print(sampling_frequency) assert np.round(sampling_frequency, 5) > np.round(2 * freq_sampled.max(), 5)
def compute_plate_scale(self, wavel, oversampling_factor=None) -> float: """ Compute the angular size of a pixel """ rho = np.sqrt(self.operators.UVC[:, 0]**2 + self.operators.UVC[:, 1]**2 ) / wavel # frequency in 1/RAD fov = rad2mas(1 / rho).max() resolution = (rad2mas( 1 / 2 / rho)).min() # Michelson criterion = lambda / 2b radians if oversampling_factor is None: oversampling_factor = self.pixels * resolution / fov plate_scale = resolution / oversampling_factor return plate_scale
def test_fov(): pixels = 32 wavel = 0.5e-6 mask_coordinates = tf.random.normal((12, 2)) phys = PhysicalModel(pixels, mask_coordinates, oversampling_factor=2) # get frequency sampled in Fourier space B = Operators(mask_coordinates, wavel) uv = B.UVC / wavel rho = np.hypot(uv[:, 0], uv[:, 1]) fov = rad2mas(1 / rho.min()) reconstruction_fov = pixels * phys.plate_scale assert np.round(fov, 5) <= np.round( reconstruction_fov, 5) # reconstruction should at least encapsulate largest scale
def freq2scale(x): return rad2mas(1/ x)#/plate_scale
def main(): # Defines physical variables np.random.seed(42) N = 21 L = 6 radius = 20 # pixels lam_circle = 4*radius pixels = 64 wavel = 0.5e-6 var = 5 # mask x = (L + np.random.normal(0, var, N)) * np.cos(2 * np.pi * np.arange(N) / N) y = (L + np.random.normal(0, var, N)) * np.sin(2 * np.pi * np.arange(N) / N) circle_mask = np.array([x, y]).T mask = np.random.normal(0, L, (N, 2)) # selected_mask = circle_mask selected_mask = mask B = Operators(selected_mask, wavel) rho = np.sqrt(B.UVC[:, 0]**2 + B.UVC[:, 1]**2)/wavel theta = rad2mas(1/rho) print(mode(theta)[0][0]) print(np.std(theta)) # this favors high frequencies over large ones. # plate_scale = (mode(theta)[0][0] + 2*np.std(theta))/pixels plate_scale = (np.median(theta) + 1*np.std(theta))/pixels # plate_scale = np.max(theta)/pixels # plate_scale = 5*np.std(theta)/pixels # plate_scale = np.min(theta)/10 print(plate_scale) d_theta = mas2rad(plate_scale) sampled_scales = rad2mas(1/rho) #/plate_scale print(f"Largest structure: {sampled_scales.max():.0f}") print(f"Smallest structure: {sampled_scales.min():.0f}") print(f"xi = {(rad2mas(1/2/rho.min())/pixels)}") sampling_frequency = 1/plate_scale signal_frequency = 1/(mas2rad(plate_scale*lam_circle)) nyquist_frequency = 0.5*signal_frequency # half of pixel/RAD, plate_scale is the sampling rate # Estimated sampling rate estimated_rate = np.diff(np.sort(rho), 1).mean() rho_th = np.linspace(rho.min(), rho.max(), 1000) image_coords = np.arange(pixels) - pixels / 2. xx, yy = np.meshgrid(image_coords, image_coords) image1 = np.zeros_like(xx) rho_squared = (xx) ** 2 + (yy) ** 2 a = radius * mas2rad(plate_scale) image1 += np.sqrt(rho_squared) < radius A = NDFTM(B.UVC, wavel, pixels, plate_scale) * d_theta**2 V = A.dot(image1.ravel()) x = 2*np.pi*a*rho_th V_th = j1(x)/x * 2*np.pi * a**2 V_th_f = interp1d(rho_th, np.abs(V_th)) def freq2scale(x): return rad2mas(1/ x)#/plate_scale def scale2freq(x): return 1/mas2rad(x) fig = plt.figure(figsize=(10, 8)) fig.suptitle(r"Comparison between analytic and discrete FT of circ($2\rho/a$)") frame1 = fig.add_axes((.1, .3, .8, .6)) _frame1 = frame1.twiny() _frame1.set_xlabel(r"$\theta$ [pixels]") _frame1.xaxis.set_major_formatter(FuncFormatter(lambda x, pos: f"{freq2scale(x)/plate_scale:.1f}")) frame1.yaxis.set_major_formatter(FuncFormatter(lambda x, pos: f"{x/0.5/a**2/2/np.pi:.1f}")) plt.plot(rho, np.abs(V), "ko", label=r"$\mathcal{F} X$") plt.plot(rho_th, np.abs(V_th), "r-", label=r"$a J_1(2\pi a \rho) / \rho$") # frame1.axvline(signal_frequency, color="b") plt.axvline(signal_frequency, color="b") plt.annotate(f"Circle radius = a = {rad2mas(a):.2f} mas", xy=(0.6, 0.8), xycoords="axes fraction", color="k") plt.annotate(r"Sampling frequency = %.2f mas$^{-1}$" % (sampling_frequency), xy=(0.6, 0.65), xycoords="axes fraction", color="b") plt.annotate(r"Nyquist frequency = %.2f mas$^{-1}$" % (nyquist_frequency/rad2mas(1)), xy=(0.6, 0.6), xycoords="axes fraction", color="k") frame1.set_ylabel(r"$|\gamma| = \frac{|V|}{a^2\pi }$") frame1.set_xticklabels([]) # Remove x-tic labels for the first frame # plt.annotate("") plt.xlim(rho.min(), rho.max()) plt.legend() frame2 = fig.add_axes((.1, .1, .8, .2)) frame2.xaxis.set_major_formatter(FuncFormatter(lambda x,pos: f"{x/rad2mas(1):.2f}")) plt.plot(rho, np.abs(np.abs(V) - V_th_f(rho))/V_th_f(rho) * 100, 'ok') plt.yscale("log") plt.ylabel("Error %") plt.xlabel(r"$\rho$ (mas$^{-1}$)") # plt.xlabel(r"Baseline (m)") plt.xlim(rho.min(), rho.max()) # plt.savefig(os.path.join(results_dir, "test_fourier_transform_circle.png"), bbox_inches="tight") # it is the baselines that are distributed with this pdf, so we change scale def rho_pdf(rho): x = rho*wavel return x/L * np.exp(-x**2/L**2/4) / 2 plt.figure() # theta = sampled_scales #/plate_scale plt.hist(np.sort(sampled_scales)[:-3], bins=50, density=True) # print(f"mean theta = {freq2scale(2*gamma(3/2)/wavel)/plate_scale:.2f}") # print(f"var theta = {freq2scale((4 - 4*gamma(3/2)**2)/wavel)/plate_scale:.2f}") # print(f"skewness theta = {freq2scale(8*gamma(5/2)/wavel)/plate_scale:.2f}") # plt.plot(theta, rho_pdf(rho/2), "ko") plt.axvline(rad2mas(2*a), color="r") plt.axvline(pixels*plate_scale, color="g") plt.axvline(plate_scale, color="g") plt.xlabel(r"$\Delta \theta$ sampled [mas]") plt.annotate(f"Circle diameter = {rad2mas(2*a):.2f} mas", xy=(0.65, 0.9), xycoords="axes fraction", color="r") plt.annotate(f"Image size = {pixels} pixels", xy=(0.65, 0.5), xycoords="axes fraction", color="g") plt.annotate(f"Median = {np.median(theta):.2f} mas", xy=(0.65, 0.8), xycoords="axes fraction", color="k") plt.annotate(f"Mean = {np.mean(theta):.2f} mas", xy=(0.65, 0.7), xycoords="axes fraction", color="k") plt.annotate(f"std = {np.std(theta):.2f} mas", xy=(0.65, 0.6), xycoords="axes fraction", color="k") plt.title("Baseline sampling of image dimensions in pixels") # plt.savefig(os.path.join(results_dir, "image_pixels_sampling.png")) plt.show()
from exorim.operators import Operators from exorim.definitions import rad2mas, mas2rad import time wavel = 3.8e-6 pixels = 128 x = np.arange(pixels) - pixels // 2 xx, yy = np.meshgrid(x, x) image = np.zeros_like(xx) rho = np.sqrt(xx**2 + yy**2) image = image + 1.0 * (rho < 10) B = Operators(JWST_NIRISS_MASK, wavel) rho = np.sqrt(B.UVC[:, 0]**2 + B.UVC[:, 1]**2) / wavel # frequency in 1/RAD fov = rad2mas(1 / rho).max() resolution = (rad2mas(1 / 2 / rho)).min() # Michelson criterion = lambda / 2b radians oversampling_factor = pixels * resolution / 10 / fov plate_scale = resolution / oversampling_factor A, A1, A2, A3 = B.build_operators(pixels, plate_scale) start = time.time() V = A @ image.ravel() V1 = A1 @ image.ravel() V2 = A2 @ image.ravel() V3 = A3 @ image.ravel() end = time.time() - start print(f"Took {end:.4f} seconds to compute all DFT") print(V)
pixels = 32 wavel = 3.8e-6 phys = PhysicalModel(pixels=pixels, mask_coordinates=JWST_NIRISS_MASK, wavelength=wavel, oversampling_factor=2) dataset = CenteredBinariesDataset(phys, total_items=1, batch_size=1, width=2) X, image = dataset.generate_batch(1) plt.imshow(image[0, ..., 0]) plt.show() plt.figure() fft = np.abs(np.fft.fftshift(np.fft.fft2(image[0, ..., 0]))) uv = phys.operators.UVC rho = np.hypot(uv[:, 0], uv[:, 1]) fftfreq = np.fft.fftshift(np.fft.fftfreq(pixels, phys.plate_scale)) im = plt.imshow(np.abs(fft), cmap="hot", extent=[fftfreq.min(), fftfreq.max()] * 2) ufreq = 1 / rad2mas(1 / uv[:, 0] * wavel) vfreq = 1 / rad2mas(1 / uv[:, 1] * wavel) plt.plot(ufreq, vfreq, "bo") plt.colorbar(im) plt.title("UV coverage") plt.show()