Usage
Overview
PyStarshade will compute fields and PSF files for a given starshade configuration and store these on disk for simulating imaging of new scenes. In general PyStarshade is used to compute:
Diffracted field at telescope: A single diffracted field is computed per wavelength in the specified band, assuming an on-axis source.
A PSF basis in the focal plane for a grid of source positions, enabling imaging simulations of exoplanetary scenes. The PSF basis is computed from the telescope field by masking regions with the chosen telescope aperture mask and propagating the field to the focal plane (described in Aime et al. 2024 and Taaki et al. 2024).
Imaging simulations: PSF’s are scaled by source flux of an astrophysical scene (input by the user) and co-added to simulate imaging.
Note - the optical propagation tools can be applied more generally for Fresnel/Fraunhofer diffractive simulations, or for use of 2D spectral sampling.
Dimensions
In PyStarshade, the classes and functions are generally designed to act on a spatial grid of size N * N, where N is an odd number and the origin is at (N/2, N/2). As an example, calling the function ‘source_field_to_ccd’, this function takes as input a 2D source-field of size (N_s, N_s) and spatial sampling ds and returns the 2D output field incident on a CCD of size (N_pix, N_pix) and pixel size dp.
Configuring the starshade
To start generating a PSF basis pick a starshade configuration, some options provided are (‘wfirst’, ‘hwo’, ‘habex’). The starshade configuration is termed a drm (design reference mission).
drm = 'hwo'
If you wanted to design your own drm (or modify one of these), go to the file ‘data/drm.py’ and change some the instrument parameters. The drm parameters define the size of the starshade, it’s flight distance, number of petals, wavelength bands, size of the telescope aperture and focal length. An example drm is:
'hwo': {
'focal_length_lens': 10,
'diameter_telescope_m': 6,
'radius_lens': 3,
'ss_radius': 30,
'dist_ss_t': [9.52e+07],
'num_pet': 24,
'wl_bands': [[500, 600]],
'dx_': [0.04, 0.001],
'grey_mask_dx': ['04m', '001m'],
'iwa': [30 / 9.52e+07 / mas_to_rad] # R_ss / dist_ss_t in MAS
}
The parameter grey_mask_dx describes the pixel sampling in physical units of a mask file. For example, 01m describes Δs = 0.01 m.
The easiest way to interface with PyStarshade is via the StarshadeProp class.
from pystarshade.propagator import StarshadeProp
drm = 'hwo'
hwo_starshade = StarshadeProp(drm = drm)
hwo_starshade.gen_pupil_field()
After running this code, a field incident on the telescope aperture will be stored on disk (and can be propagated with various different telescope apertures). There are optional parameters to change the samplings at the pupil, or wavelength sampling.
Note: there is an important setting called ‘chunk’, if this is invoked a chunked FFT will be used to compute the fields using a memory mapped starshade mask. This avoids hitting RAM bottlenecks, but you will need to have enough disk memory for the memmap starshade mask of floats. By default, this chunked FFT will process the starshade mask of size (N * N) in chunks of size (N/N_chunk * N/N_chunk). To modify the N_chunk parameter change it inside ‘pystarshade.simulate_field.source_field_to_pupil’, by default N_chunk is set to 4.
Data
The internal data directory is structured like so:
data
├── fields
├── masks
│ ├── starshade_edge_files
│ └── starshade_masks
├── psf
├── pupils
└── scenes
If you have new masks for the starshade, or telescope aperture masks, place them in the correct folders (starshade_masks and pupils respectively).
New data generated from examples is located at the top level: ‘PyStarshade/out’.
Location of Generated Files
The generated pupil fields and PSF basis files are stored in the data/fields and data/psf directories, respectively, allowing for reuse in future simulations without repeating the computationally intensive propagation steps.
Please see the examples folder for detailed examples!
Starshade Masks
The starshade mask itself is not generated by PyStarshade. Some masks are provided for the drm’s listed (stored in data/masks), collected from various sources (SISTERS/diffraq) and interpolated/upsampled these to grey-scale masks which are stored in the data/masks/starshade_masks directory. If you have a starshade mask defined by a set of edge-points (locii) and want to generate a grey-scale (antialiased) mask for use with PyStarshade, run the script mp_gen_mask.py to do so. If you want to generate diffracted fields using a chunked FFT (the diffraction from the mask is processed in chunks), then generate a memory mapped mask using make_memmap.py. See further down for more info.
Generating and using a PSF Basis
The simplest way to use PyStarshade is by using the precomputed pupil fields and the StarshadeProp class as described. The StarshadeProp class is designed to abstract away sampling calculations, as well as pre-compute data products and interface with them. Pystarshade utilizes a PSF basis to simulate imaging - however only a pre-computed PSF basis at a single wavelength is included.
Warning
Generating the diffracted pupil fields from scratch for different wavelengths can be quite compute intensive depending on the size of the starshade and may take several hours. However, this pupil field needs only be generated once and can be used to generate different PSF basis for different apertures.
A set of pre-generated pupil fields for the HWO starshade drm are available with the git-lfs install. Diffracted fields at the telescope aperture live in the data/fields` directory.
Warning
Computing the PSF basis itself may take several minutes or up to an hour, depending on the bandwidth, spectral sampling and source-field sampling.
PyStarshade does not assume any azimuthal symmetry and will compute a fully unique PSF for each source pixel. Once computed, the PSF basis can be used to simulate imaging for different scenes.
Choices for sampling and resolution in the optical pipeline can be modified, however we have generally chosen defaults that opt for precision over speed and memory usage.
Input data
To perform these computations, PyStarshade requires the following inputs:
Starshade configuration (Design Reference Mission, DRM): A dictionary specifying instrument parameters, including:
Starshade mask parameters: radius (
, e.g., 30 m for HWO), number of petals (e.g., 24), and pixel sampling (
, e.g., 1 mm to achieve
contrast).
Telescope parameters: aperture diameter (
, e.g., 6 m), focal length (
, e.g., 10 m), and aperture mask (e.g., segmented on-axis or off-axis designs).
Optical parameters: wavelength band (
, e.g., 500–1000 nm), flight distance between starshade and telescope (
, e.g.,
m), and Fresnel number (
, e.g., 9.5–18.9).
Sampling parameters: starshade mask sampling (
), telescope aperture sampling (
, e.g., 2 cm), and focal plane sampling (
, e.g., 2 mas).
Starshade mask file: a binary starshade mask on a grid.
Telescope aperture mask: A binary or grayscale mask representing the telescope aperture (e.g., HWO’s segmented on-axis or off-axis designs). These can be user-defined or loaded from HCIPy.
Exoplanet scene (optional for imaging): A flux distribution of the astrophysical scene. PyStarshade can take as input any pixelized source-field such as Haystacks model or an ExoVista model, or analytic descriptions of sources
(so far a point source and Gaussian source). If you wish to perform propagation using analytic descriptions, please use ‘pystarshade.simulate_field.point_source_to_ccd’.
Using Chunked FFT for Large Masks
Important: By default when chunk=1 when calling gen_pupil_field(chunk = 1), the mask will be propagated in chunks. To use this, the generated starshade mask must be a memmap .dat filetype. You can generate a memmap file by running the make_memmap script inside the masks directory on one of the existing masks. We do not include masks generated in this format as they occupy a large disk space. Set chunk=0 to use an npz file instead - beware you may run out of memory.
Chunked FFT processing avoids RAM limitations when working with very large masks. The computation processes the starshade mask of size (N × N) in chunks of size (N//N_chunk × N//N_chunk). By default, N_chunk is set to 4 in pystarshade.simulate_field.source_field_to_pupil. If this chunking factor is not sufficient and a RAM bottleneck is still met, it can be increased (recommend setting N_chunk to a power of 2).
Full PSF Generation Workflow
Here’s a complete example of generating and using a PSF basis:
from pystarshade.propagator import StarshadeProp
import numpy as np
# 1. Initialize with a DRM
drm = 'hwo'
starshade = StarshadeProp(drm=drm)
# 2. Generate the pupil field (this is computationally intensive)
# Optional parameters: wl_override, N_x_override, dx_override, chunk=1
starshade.gen_pupil_field()
# 3. Generate PSF basis for a specific telescope pupil
pupil_type = 'hex' # Can be 'hex', 'circular', or a custom pupil
starshade.gen_psf_basis(pupil_type=pupil_type)
# 4. Use the generated PSF basis to simulate a scene
# Load or create a toy model of a source field
source_field = np.zeros((251, 251), dtype=np.float32)
source_field[126, 126] = 1 #star
source_field[70, 126] = 1e-10 #toy planet
wavelength = 500e-9 # 500 nm
focal_intensity = starshade.gen_scene(pupil_type, source_field, wavelength)