{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Getting Started\n", "\n", "Welcome to HCIPy! This guide will help you get oriented with the framework.\n", "\n", "HCIPy is a framework written in Python for high contrast imaging simulation work. It implements adaptive optics simulation, coronagraphy and optical diffraction calculations.\n", "\n", "## Installation\n", "\n", "You can install HCIPy using pip:\n", "\n", "```bash\n", "pip install hcipy\n", "```\n", "\n", "or using conda via conda-forge:\n", "\n", "```bash\n", "conda install conda-forge::hcipy\n", "```\n", "\n", "For more detailed instructions, see the [installation](installation.rst) guide." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Your First Simulation\n", "\n", "Run the code below to simulate a circular telescope aperture and calculate its PSF. Don't worry about the details yet; we'll explain each step in the next section." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Step 1: Import HCIPy and other libraries\n", "import hcipy\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "# Step 2: Create a computational grid\n", "# This defines where we calculate the optical field\n", "pupil_diameter = 8.2 # meter\n", "grid = hcipy.make_pupil_grid(256, pupil_diameter) # 256x256 grid spanning 8.2 meters (VLT diameter)\n", "\n", "# Step 3: Create a circular aperture\n", "# This represents the telescope pupil\n", "aperture = hcipy.make_circular_aperture(pupil_diameter)(grid)\n", "\n", "# Step 4: Create a wavefront\n", "# This represents starlight entering the telescope\n", "wavelength = 2.2e-6 # 2.2 microns (K-band) in meters\n", "wf = hcipy.Wavefront(aperture, wavelength)\n", "\n", "# Step 5: Create a focal plane grid for the PSF\n", "# q=8 gives 8 pixels per resolution element (λ/D)\n", "spatial_resolution = wavelength / pupil_diameter\n", "focal_grid = hcipy.make_focal_grid(q=8, num_airy=16, spatial_resolution=spatial_resolution)\n", "\n", "# Step 6: Propagate to the focal plane\n", "prop = hcipy.FraunhoferPropagator(grid, focal_grid)\n", "psf = prop(wf).intensity\n", "\n", "# Step 7: Visualize the results\n", "fig, axes = plt.subplots(1, 2, figsize=(8, 4))\n", "\n", "# Plot the aperture\n", "hcipy.imshow_field(aperture, grid=grid, ax=axes[0], cmap='gray')\n", "axes[0].set_title('Telescope Aperture (8.2m diameter)')\n", "axes[0].set_xlabel('x [m]')\n", "axes[0].set_ylabel('y [m]')\n", "\n", "# Plot the PSF (log scale to see faint features)\n", "hcipy.imshow_field(np.log10(psf / psf.max()), grid=focal_grid, ax=axes[1], vmin=-5, cmap='inferno', grid_units=spatial_resolution)\n", "axes[1].set_title('Point Spread Function (K-band, 2.2 μm)')\n", "axes[1].set_xlabel('x [λ/D]')\n", "axes[1].set_ylabel('y [λ/D]')\n", "\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## What Just Happened?\n", "\n", "Let's break down the code step by step:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step 2: The Grid\n", "\n", "```python\n", "grid = make_pupil_grid(256, pupil_diameter)\n", "```\n", "\n", "A **Grid** defines where in space we calculate optical quantities. Think of it as the \"pixels\" of your simulation. Here we create a 256x256 grid spanning 8.2 meters, the diameter of the VLT's primary mirror. Grids are the workhorse of HCIPy and you'll use them to define your sampling in every optical plane in your simulations." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step 3: The Aperture\n", "\n", "```python\n", "aperture = make_circular_aperture(pupil_diameter)(grid)\n", "```\n", "\n", "An **aperture** represents the physical opening through which light enters the telescope. For a simple circular mirror, it's 1 inside the mirror and 0 outside. Note the double parentheses here: `make_circular_aperture(pupil_diameter)` returns a *function* that takes a grid as input and returns an aperture mask. We then call (i.e. evaluate) that function with the grid we defined above." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step 4: The Wavefront\n", "\n", "```python\n", "wf = Wavefront(aperture, wavelength)\n", "```\n", "\n", "A **Wavefront** represents the electromagnetic field of light. It stores:\n", "- **Electric Field**: The electric field amplitude and phase at each grid point.\n", "- **Wavelength**: The color of light (2.2 μm = K-band near-infrared)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step 5-6: Propagation and the PSF\n", "\n", "```python\n", "prop = FraunhoferPropagator(grid, focal_grid)\n", "psf = prop(wf).intensity\n", "```\n", "\n", "**Fraunhofer propagation** calculates how light diffracts as it travels from the pupil to the focal plane. Internally, this uses Fourier transforms to compute the resulting diffraction pattern.\n", "\n", "The result is the **Point Spread Function (PSF)**—the image of a point source (star). The PSF shows:\n", "- **Airy disk**: The central bright spot (diffraction-limited resolution)\n", "- **Diffraction rings**: Concentric rings from wave interference" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step 7: Visualization\n", "\n", "```python\n", "imshow_field(np.log10(psf / psf.max()), grid=focal_grid, grid_units=spatial_resolution)\n", "```\n", "\n", "`grid_units` controls the axis scaling: here `spatial_resolution = \\lambda / D` (the diffraction limit), so tick labels are in units of $\\lambda/D$." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Experiment: Wavelength Dependence\n", "\n", "Let's see how the PSF changes with wavelength. In astronomy, shorter wavelengths (blue) give better angular resolution than longer wavelengths (red)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Compare PSFs at different wavelengths\n", "wavelengths = [0.5e-6, 1.0e-6, 2.2e-6] # V, J, and K bands in meters\n", "labels = ['V-band (0.5 μm)', 'J-band (1.0 μm)', 'K-band (2.2 μm)']\n", "\n", "fig, axes = plt.subplots(1, 3, figsize=(10, 4))\n", "\n", "for i, (wl, label, ax) in enumerate(zip(wavelengths, labels, axes)):\n", " # Create wavefront at this wavelength\n", " wf = hcipy.Wavefront(aperture, wl)\n", "\n", " # Propagate\n", " psf = prop(wf).intensity\n", "\n", " # Plot\n", " hcipy.imshow_field(np.log10(psf/psf.max()), ax=ax, vmin=-5, cmap='inferno', grid_units=spatial_resolution)\n", " axes[i].set_title(label)\n", " axes[i].set_xlabel(r'x [$\\lambda_K / D$]')\n", " axes[i].set_ylabel(r'y [$\\lambda_K / D$]')\n", "\n", "plt.suptitle('PSF vs Wavelength')\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Using a Real Telescope Pupil\n", "\n", "Let's replace the simple circular aperture with the actual VLT pupil, which includes the central obscuration (secondary mirror) and spiders. The `make_vlt_aperture()` function defaults to the VLT's actual 8.2 m diameter." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create the actual VLT pupil\n", "vlt_pupil = hcipy.make_vlt_aperture()(grid)\n", "\n", "# Create wavefront\n", "wf_vlt = hcipy.Wavefront(vlt_pupil, wavelength)\n", "\n", "# Propagate\n", "psf_vlt = prop(wf_vlt).intensity\n", "\n", "# Compare\n", "fig, axes = plt.subplots(1, 2, figsize=(8, 5))\n", "\n", "# Simple circular aperture\n", "wf_circ = hcipy.Wavefront(aperture, wavelength)\n", "psf_circ = prop(wf_circ).intensity\n", "\n", "hcipy.imshow_field(np.log10(psf_circ/psf_circ.max()), ax=axes[0], vmin=-5, cmap='inferno', grid_units=spatial_resolution)\n", "axes[0].set_title('Circular Aperture (no obscuration)')\n", "axes[0].set_xlabel('x [λ/D]')\n", "axes[0].set_ylabel('y [λ/D]')\n", "\n", "# VLT pupil\n", "hcipy.imshow_field(np.log10(psf_vlt/psf_vlt.max()), ax=axes[1], vmin=-5, cmap='inferno', grid_units=spatial_resolution)\n", "axes[1].set_title('VLT Pupil (with obscuration and spiders)')\n", "axes[1].set_xlabel('x [λ/D]')\n", "axes[1].set_ylabel('y [λ/D]')\n", "\n", "plt.tight_layout()\n", "plt.show()\n", "\n", "# Show the pupils themselves\n", "fig, axes = plt.subplots(1, 2, figsize=(8, 5))\n", "hcipy.imshow_field(aperture, ax=axes[0], cmap='gray')\n", "axes[0].set_title('Circular Aperture')\n", "axes[0].set_xlabel('x [m]')\n", "axes[0].set_ylabel('y [m]')\n", "hcipy.imshow_field(vlt_pupil, ax=axes[1], cmap='gray')\n", "axes[1].set_title('VLT Pupil')\n", "axes[1].set_xlabel('x [m]')\n", "axes[1].set_ylabel('y [m]')\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice the differences in the PSFs:\n", "\n", "- The VLT PSF has more complex diffraction structure\n", "- Spiders create the cross-shaped pattern\n", "- Central obscuration redistributes light in the Airy rings" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Next Steps\n", "\n", "Now that you understand the basics, continue with these tutorials:\n", "\n", "1. [Core Concepts: Grids and Fields](tutorials/CoreConceptsGridsFields/CoreConceptsGridsFields.rst): deeper dive into HCIPy's fundamental data structures.\n", "\n", "2. [Core Concepts: Wavefronts and Optical Elements](tutorials/CoreConceptsWavefrontsOpticalElements/CoreConceptsWavefrontsOpticalElements.rst): learn about propagation and aberrations.\n", "\n", "3. [Telescope Pupils](tutorials/TelescopePupils/TelescopePupils.rst): explore pupils for other major telescopes (JWST, ELT, etc.).\n", "\n", "4. [Atmospheric Simulation](tutorials/AtmosphericSimulation/AtmosphericSimulation.rst): add realistic turbulence to your simulations.\n", "\n", "For a complete list of tutorials, see the [tutorials page](tutorials/index.rst)." ] } ], "metadata": { "kernelspec": { "display_name": "dev", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.5" }, "level": "beginner" }, "nbformat": 4, "nbformat_minor": 4 }