예제 #1
0
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)
예제 #2
0
 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
예제 #3
0
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()
예제 #6
0
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)
예제 #7
0
    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()