Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

For coronagraph Lyot masks, add reference information on alignment per each mask #51

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion stpsf/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,6 @@

JWST_TYPICAL_LOS_JITTER_PER_AXIS = 0.0008 # milliarcseconds jitter, 1 sigma per axis. = approx 1 mas rms radial, typically


# ad hoc, highly simplified models for charge diffusion within detectors
# These values are PLACEHOLDERS and should be updated based on comparisons with data and ePSFs (ongoing)
# Note, these are parameterized as arcseconds for convenience (and consistency with the jitter paramater)
Expand Down Expand Up @@ -407,3 +406,21 @@
'NIRSPEC': {'sigma': 0.05},
'MIRI': {'sigma': 0.05},
}

# Alignment information about instrument internal pupil masks (
INSTRUMENT_PUPIL_MASK_DEFAULT_POSITIONS = {
'NIRCam_SWA_MASKSWB': {'pupil_shift_x': None, 'pupil_shift_y': None, 'pupil_rotation': None},
'NIRCam_SWA_MASKLWB': {'pupil_shift_x': None, 'pupil_shift_y': None, 'pupil_rotation': None},
'NIRCam_SWA_MASK210R': {'pupil_shift_x': None, 'pupil_shift_y': None, 'pupil_rotation': None},
'NIRCam_SWA_MASK335R': {'pupil_shift_x': None, 'pupil_shift_y': None, 'pupil_rotation': None},
'NIRCam_SWA_MASK430R': {'pupil_shift_x': None, 'pupil_shift_y': None, 'pupil_rotation': None},
'NIRCam_LWA_MASKSWB': {'pupil_shift_x': None, 'pupil_shift_y': None, 'pupil_rotation': None},
'NIRCam_LWA_MASKLWB': {'pupil_shift_x': None, 'pupil_shift_y': None, 'pupil_rotation': None},
'NIRCam_LWA_MASK210R': {'pupil_shift_x': None, 'pupil_shift_y': None, 'pupil_rotation': None},
'NIRCam_LWA_MASK335R': {'pupil_shift_x': -0.012, 'pupil_shift_y': -0.023, 'pupil_rotation': -0.60}, # from K. Lawson, fits to ERS progid 1386 data
'NIRCam_LWA_MASK430R': {'pupil_shift_x': None, 'pupil_shift_y': None, 'pupil_rotation': None},
'MIRI_MASKFQPM_F1065C': {'pupil_shift_x': None, 'pupil_shift_y': None, 'pupil_rotation': None},
'MIRI_MASKFQPM_F11140': {'pupil_shift_x': None, 'pupil_shift_y': None, 'pupil_rotation': None},
'MIRI_MASKFQPM_F1550C': {'pupil_shift_x': None, 'pupil_shift_y': None, 'pupil_rotation': None},
'MIRI_MASKLYOT': {'pupil_shift_x': None, 'pupil_shift_y': None, 'pupil_rotation': None},
}
76 changes: 45 additions & 31 deletions stpsf/stpsf_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1358,41 +1358,58 @@ def interpolate_was_opd(self, array, newdim):

return newopd

def _get_pupil_shift(self):
"""Return a tuple of pupil shifts, for passing to OpticalElement constructors
This is a minor utility function that gets used in most of the subclass optical
def _get_pupil_mask_alignment(self, lookup_key=None):
"""Return a tuple of pupil shifts and rotation, for passing to OpticalElement constructors
This is a utility function that gets used in most of the subclass optical
system construction.

This has two main parts:
1. Determine values for the pupil mask shift X, Y, and rotation, which can either be from:
1a) Explicitly provided by the user in self.options
1b) Or else, (optional) default positions per each mask, set in constants.py
1c) Otherwise return None
2. Convert any pupil mask shift X, Y from fractions of the pupil to offsets in meters
projected into the primary aperture.
For historical reasons, the pupil_shift_x and pupil_shift_y options are expressed
in fractions of the pupil. The parameters to poppy should now be expressed in
meters of shift. So the translation of that happens here.

Returns
-------
shift_x, shift_y : floats or Nones
Pupil shifts, expressed in meters.
shift_x, shift_y, rotation : floats or Nones
Pupil shifts, expressed in meters. And rotation in degrees.

"""
if ('pupil_shift_x' in self.options and self.options['pupil_shift_x'] != 0) or (
'pupil_shift_y' in self.options and self.options['pupil_shift_y'] != 0
):
from .constants import JWST_CIRCUMSCRIBED_DIAMETER

# missing values are treated as 0's
shift_x = self.options.get('pupil_shift_x', 0)
shift_y = self.options.get('pupil_shift_y', 0)
# nones are likewise treated as 0's
if shift_x is None:
shift_x = 0
if shift_y is None:
shift_y = 0
# Apply pupil scale
shift_x *= JWST_CIRCUMSCRIBED_DIAMETER
shift_y *= JWST_CIRCUMSCRIBED_DIAMETER
_log.info('Setting Lyot pupil shift to ({}, {})'.format(shift_x, shift_y))
else:
shift_x, shift_y = None, None
return shift_x, shift_y
if not self.pupil_mask:
# if there is no pupil stop mask, these have no effect, so no need to do anything more to find values
return 0, 0, None

if not lookup_key:
if self.name == 'NIRCam':
# This is complicated. Depends on the channel, and the image plane mask used...
# TODO more work needed here.
lookup_key = f'{self.name}_{self.channel[0].upper()}W{self.module}_' + self.pupil_mask
else:
lookup_key = self.name + '_' + self.pupil_mask

values = []
for param in ('pupil_shift_x', 'pupil_shift_y', 'pupil_rotation'):
val = self.options.get(param) # has user directly provided a value?
# if not, check if we have a default for this instrument + mask
if (val is None) and (lookup_key in constants.INSTRUMENT_PUPIL_MASK_DEFAULT_POSITIONS):
val = constants.INSTRUMENT_PUPIL_MASK_DEFAULT_POSITIONS[lookup_key].get(param)
_log.debug(f' Found default {lookup_key} {param} = {val}')

if val is not None and param.startswith('pupil_shift'):
val *= constants.JWST_CIRCUMSCRIBED_DIAMETER
values.append(val)

shift_x, shift_y, rotation = values

if any(values):
_log.info(f"Setting instrument pupil mask shift to ({shift_x}, {shift_y}), rotation={rotation}")

return shift_x, shift_y, rotation

def _apply_jitter(self, result, local_options=None):
"""Modify a PSF to account for the blurring effects of image jitter.
Expand Down Expand Up @@ -2297,8 +2314,7 @@ def make_fqpm_wrapper(name, wavelength):
optsys.add_pupil(poppy.FQPM_FFT_aligner(direction='backward'))

# add pupil plane mask
shift_x, shift_y = self._get_pupil_shift()
rotation = self.options.get('pupil_rotation', None)
shift_x, shift_y, rotation = self._get_pupil_mask_alignment()

if self.options.get('coron_include_pre_lyot_plane', False) and self.pupil_mask.startswith('MASK'):
optsys.add_pupil(poppy.ScalarTransmission(name='Pre Lyot Stop'))
Expand Down Expand Up @@ -3009,8 +3025,7 @@ def _addAdditionalOptics(self, optsys, oversample=2):
trySAM = False

# add pupil plane mask
shift_x, shift_y = self._get_pupil_shift()
rotation = self.options.get('pupil_rotation', None)
shift_x, shift_y, rotation = self._get_pupil_mask_alignment()

if self.pupil_mask == 'CIRCLYOT' or self.pupil_mask == 'MASKRND':
optsys.add_pupil(
Expand Down Expand Up @@ -3537,8 +3552,7 @@ def _addAdditionalOptics(self, optsys, oversample=2):
radius = 0.0 # irrelevant but variable needs to be initialized

# add pupil plane mask
shift_x, shift_y = self._get_pupil_shift()
rotation = self.options.get('pupil_rotation', None)
shift_x, shift_y, rotation = self._get_pupil_mask_alignment()

# Note - the syntax for specifying shifts is different between FITS files and
# AnalyticOpticalElement instances. Annoying but historical.
Expand Down
Loading