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

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

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

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

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

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

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)