Imaging with a vortex coronagraph

We will simulate on-axis and off-axis images of stars through a (ring-apodized) vortex coronagraph.

We’ll start by importing all relevant libraries and setting up our pupil and focal grids. We’ll slightly oversize our pupil grid to more clearly see the effects of the vortex coronagraph in the Lyot plane.

from hcipy import *
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt

# For notebook animations
from matplotlib import animation
from IPython.display import HTML
mpl.rcParams['figure.dpi'] = 100

pupil_grid = make_pupil_grid(256, 1.5)
focal_grid = make_focal_grid(8, 12)
prop = FraunhoferPropagator(pupil_grid, focal_grid)

We start of by creating a circular aperture. A vortex coronagraph works perfectly for a circular aperture. We’ll use supersampling to evaluate this aperture to partially suppress sampling artefacts. We’ll also use a slightly-undersized circular Lyot stop.

aperture = evaluate_supersampled(circular_aperture(1), pupil_grid, 4)
lyot_mask = evaluate_supersampled(circular_aperture(0.95), pupil_grid, 4)

plt.subplot(1,2,1)
plt.title('Aperture')
imshow_field(aperture, cmap='gray')
plt.subplot(1,2,2)
plt.title('Lyot stop')
imshow_field(lyot_mask, cmap='gray')
plt.show()
../../_images/output_4_0.png

We can perform non-coronagraphic imaging by just using the Fraunhofer propagation defined above, to propagate the light from the pupil to the focal plane of our telescope.

wf = Wavefront(aperture)
img_ref = prop(wf).intensity

imshow_field(np.log10(img_ref / img_ref.max()), vmin=-5, cmap='inferno')
plt.show()
../../_images/output_6_01.png

This shows the usual Airy pattern. We’ll now generate the vortex coronagraph itself. It requires a pupil grid and the charge of the vortex. The vortex coronagraph object propagates light from the pupil plane to the Lyot plane.

charge = 2
coro = VortexCoronagraph(pupil_grid, charge)
lyot_stop = Apodizer(lyot_mask)

We can now propagate light through the vortex coronagraph. Internally the vortex coronagraph performs many propagations through the vortex with successively higher resolutions. This is done to adequately sample the vortex singularity.

wf = Wavefront(aperture)
lyot_plane = coro(wf)

imshow_field(lyot_plane.intensity, cmap='inferno')
plt.show()
../../_images/output_10_01.png

We can now block this light with a Lyot stop.

post_lyot_mask = lyot_stop(lyot_plane)
img = prop(post_lyot_mask).intensity

imshow_field(np.log10(img / img_ref.max()), vmin=-5, vmax=0, cmap='inferno')
plt.show()
../../_images/output_12_03.png

The star has completely been suppressed. We can see the star appear again, when we look at an off-axis object:

wf = Wavefront(aperture * np.exp(2j * np.pi * pupil_grid.x * 1.5))
img = prop(lyot_stop(coro(wf))).intensity

imshow_field(np.log10(img / img_ref.max()), vmin=-5, vmax=0, cmap='inferno')
plt.show()
../../_images/output_14_05.png

And the Lyot plane image looks totally different for an off-axis star:

lyot = coro(wf)

imshow_field(lyot.intensity, vmax=2, cmap='inferno')
plt.show()
../../_images/output_16_03.png

Unintuitively, the light in the Lyot stop is offset in the vertical direction, while the star is offset in the horizontal direction. We can see this effect clearer in an animation.

def create_offaxis_animation(coro):
    fig = plt.figure()
    plt.subplot(1,2,1)
    plt.title('Lyot plane')
    im1 = imshow_field(lyot_plane.intensity, vmax=1, cmap='inferno')
    plt.subplot(1,2,2)
    plt.title('Science image plane')
    im2 = imshow_field(np.log10(img / img_ref.max()), vmin=-5, vmax=0, cmap='inferno')
    plt.close(fig)

    def animate(angular_separation):
        wf = Wavefront(aperture * np.exp(2j * np.pi * pupil_grid.x * angular_separation))
        lyot = coro(wf)
        img = prop(lyot_stop(lyot))

        im1.set_data(*pupil_grid.separated_coords, lyot.intensity.shaped)
        im2.set_data(*focal_grid.separated_coords, np.log10(img.intensity.shaped / img_ref.max()))

        return [im1, im2]

    angular_separations = np.linspace(-5, 5, 51)
    anim = animation.FuncAnimation(fig, animate, angular_separations, interval=160, blit=True)
    return HTML(anim.to_html5_video())

create_offaxis_animation(coro)

We can also simulate vortex coronagraphs with other charges:

vortex4 = VortexCoronagraph(pupil_grid, charge=4)
create_offaxis_animation(vortex4)