Skip to content

Commit

Permalink
Improve NIRCam options for coron. mask shifts (see #191) and also pup…
Browse files Browse the repository at this point in the history
…il shifts too
  • Loading branch information
mperrin committed Feb 20, 2018
1 parent 79364f1 commit 5770740
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 56 deletions.
78 changes: 40 additions & 38 deletions webbpsf/optics.py
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,11 @@ class NIRCam_BandLimitedCoron(poppy.BandLimitedCoron):
Set to a NIRCam filter name to automatically offset to the nominal
position along the bar for that filter. See bar_offset if you want to set
to some arbitrary position.
shift_x, shift_y : floats or None
X and Y offset shifts applied to the occulter, via the standard mechanism for
poppy.AnalyticOpticalElements. Like bar_offset but allows for 2D offets, and
applies to both bar and wedge coronagraphs. This is IN ADDITION TO any offset
from bar_offset.
"""
allowable_kinds = ['nircamcircular', 'nircamwedge']
""" Allowable types of BLC supported by this class"""
Expand Down Expand Up @@ -799,7 +803,9 @@ def get_transmission(self, wave):
# the scale fact should depend on X coord in arcsec, scaling across a 20 arcsec FOV.
# map flat regions to 2.5 arcsec each
# map -7.5 to 2, +7.5 to 6. slope is 4/15, offset is +9.5
scalefact = (2 + (-x + 7.5) * 4 / 15).clip(2, 6)
wedgesign = 1 if self.name=='MASKSWB' else -1 # wide ends opposite for SW and LW

scalefact = (2 + (x*wedgesign + 7.5) * 4 / 15).clip(2, 6)

# Working out the sigma parameter vs. wavelength to get that wedge pattern is non trivial
# This is NOT a linear relationship. See calc_blc_wedge helper fn below.
Expand All @@ -808,7 +814,6 @@ def get_transmission(self, wave):
polyfitcoeffs = np.array([2.01210737e-04, -7.18758337e-03, 1.12381516e-01,
-1.00877701e+00, 5.72538509e+00, -2.12943497e+01,
5.18745152e+01, -7.97815606e+01, 7.02728734e+01])
scalefact = scalefact[:, ::-1] # flip orientation left/right for SWB mask
elif self.name == 'MASKLWB': #elif np.abs(self.wavelength - 4.6e-6) < 0.1e-6:
polyfitcoeffs = np.array([9.16195583e-05, -3.27354831e-03, 5.11960734e-02,
-4.59674047e-01, 2.60963397e+00, -9.70881273e+00,
Expand All @@ -834,75 +839,71 @@ def get_transmission(self, wave):
# See the figures in Krist et al. of how the 6 ND squares are spaced among the 5
# corongraph regions
# Note: 180 deg rotation needed relative to Krist's figures for the flight SCI orientation:
# We flip the signs of X and Y here as a shortcut to avoid recoding all of the below...
x *= -1
y *= -1

if ((self.module=='A' and self.name=='MASKLWB') or
(self.module=='B' and self.name=='MASK210R')):
# left edge:
# has one fully in the corner and one half in the other corner, half outside the 10x10 box
wnd_5 = np.where(
((y > 5)&(y<10)) &
((y < -5)&(y>-10)) &
(
((x < -5) & (x > -10)) |
((x > 7.5) & (x < 12.5))
((x > 5) & (x < 10)) |
((x < -7.5) & (x > -12.5))
)
)
wnd_2 = np.where(
((y > -10)&(y<-8)) &
((y < 10)&(y> 8)) &
(
((x < -8) & (x > -10)) |
((x > 9) & (x < 11))
((x > 8) & (x < 10)) |
((x < -9) & (x > -11))
)
)
elif ((self.module=='A' and self.name=='MASK210R') or
(self.module=='B' and self.name=='MASKSWB')):
# right edge
wnd_5 = np.where(
((y > 5)&(y<10)) &
((y < -5)&(y>-10)) &
(
((x > -12.5) & (x < -7.5)) |
((x > 5) & (x <10))
((x < 12.5) & (x > 7.5)) |
((x < -5) & (x > -10))
)
)
wnd_2 = np.where(
((y > -10)&(y<-8)) &
((y < 10)&(y>8)) &
(
((x > -11) & (x < -9)) |
((x > 8) & (x<10))
((x < 11) & (x > 9)) |
((x < -8) & (x > -10))
)
)
else:
# the others have two, one in each corner, both halfway out of the 10x10 box.
wnd_5 = np.where(
((y > 5)&(y<10)) &
((y < -5)&(y > -10)) &
(np.abs(x) > 7.5) &
(np.abs(x) < 12.5)
)
wnd_2 = np.where(
((y > -10)&(y<-8)) &
((y < 10)&(y > 8)) &
(np.abs(x) > 9) &
(np.abs(x) < 11)
)

self.transmission[wnd_5] = np.sqrt(1e-3)
self.transmission[wnd_2] = np.sqrt(1e-3)



# Add in the opaque border of the coronagraph mask holder.
if ((self.module=='A' and self.name=='MASKLWB') or
(self.module=='B' and self.name=='MASK210R')):
# left edge
woutside = np.where((x < -10) & (y < 11.5 ))
woutside = np.where((x > 10) & (y > -11.5 ))
self.transmission[woutside] = 0.0
elif ((self.module=='A' and self.name=='MASK210R') or
(self.module=='B' and self.name=='MASKSWB')):
# right edge
woutside = np.where((x > 10) & (y < 11.5))
woutside = np.where((x < -10) & (y > -11.5))
self.transmission[woutside] = 0.0
# mask holder edge
woutside = np.where(y < -10)
woutside = np.where(y > 10)
self.transmission[woutside] = 0.0

# edge of mask itself
Expand All @@ -911,28 +912,25 @@ def get_transmission(self, wave):
# The following is just a temporary placeholder with no quantitative accuracy.
# but this is outside the coronagraph FOV so that's fine - this only would matter in
# modeling atypical/nonstandard calibration exposures.

wedge = np.where(( y > 11.5) & (y < 13))
wedge = np.where(( y < -11.5) & (y > -13))
self.transmission[wedge] = 0.7







if not np.isfinite(self.transmission.sum()):
#stop()
_log.warn("There are NaNs in the BLC mask - correcting to zero. (DEBUG LATER?)")
self.transmission[np.where(np.isfinite(self.transmission) == False)] = 0
return self.transmission


def display(self, annotate=False, annotate_color='cyan', annotate_text_color=None, *args, **kwargs):
def display(self, annotate=False, annotate_color='cyan', annotate_text_color=None, grid_size=20, *args, **kwargs):
"""Same as regular display for any other optical element, except adds annotate option
for the LWB offsets """
poppy.AnalyticOpticalElement.display(self,*args, **kwargs)
poppy.AnalyticOpticalElement.display(self, grid_size=grid_size, *args, **kwargs)
if annotate:

shift_dx = getattr(self, 'shift_x', 0) - getattr(self, 'bar_offset', 0)
shift_dy = getattr(self, 'shift_y', 0)

if annotate_text_color is None:
annotate_text_color = annotate_color
if self.name.lower()=='maskswb' or self.name.lower() =='masklwb':
Expand All @@ -942,9 +940,13 @@ def display(self, annotate=False, annotate_color='cyan', annotate_text_color=Non
horiz, vert, voffset = 'right', 'top', -0.5
else:
horiz, vert, voffset = 'left', 'bottom', +0.5
matplotlib.pyplot.plot(offset,0,marker='+', color=annotate_color)
matplotlib.pyplot.text(offset,voffset, filt, color=annotate_text_color, rotation=75,
horizontalalignment=horiz, verticalalignment=vert)
matplotlib.pyplot.plot(offset+shift_dx, shift_dy, marker='+', color=annotate_color, clip_on=True)
matplotlib.pyplot.text(offset+shift_dx, voffset+shift_dy, filt, color=annotate_text_color, rotation=75,
horizontalalignment=horiz, verticalalignment=vert, clip_on=True)
ax = matplotlib.pyplot.gca()
# Fix the axis scaling if any of the overplots exceeded it
ax.set_xlim(-grid_size/2, grid_size/2)
ax.set_ylim(-grid_size/2, grid_size/2)


# Helper functions for NIRcam occulters.
Expand Down
58 changes: 40 additions & 18 deletions webbpsf/webbpsf_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1079,10 +1079,19 @@ def _addAdditionalOptics(self,optsys, oversample=2):

nd_squares = self.options.get('nd_squares', True)

SAM_box_size = None # default

# Allow arbitrary offsets of the focal plane masks with respect to the pixel grid origin;
# In most use cases it's better to offset the star away from the mask instead, using
# options['source_offset_*'], but doing it this way instead is helpful when generating
# the Pandeia ETC reference PSF library.
shifts = {'shift_x': self.options.get('coron_shift_x', None),
'shift_y': self.options.get('coron_shift_y', None)}

if ((self.image_mask == 'MASK210R') or (self.image_mask == 'MASK335R') or
(self.image_mask == 'MASK430R')):
optsys.add_image( NIRCam_BandLimitedCoron( name=self.image_mask, module=self.module,
nd_squares=nd_squares),
nd_squares=nd_squares, **shifts),
index=2)
trySAM = False # FIXME was True - see https://github.com/mperrin/poppy/issues/169
SAM_box_size = 5.0
Expand All @@ -1105,74 +1114,81 @@ def _addAdditionalOptics(self,optsys, oversample=2):
bar_offset = None

optsys.add_image( NIRCam_BandLimitedCoron(name=self.image_mask, module=self.module,
nd_squares=nd_squares, bar_offset=bar_offset, auto_offset=auto_offset),
nd_squares=nd_squares, bar_offset=bar_offset, auto_offset=auto_offset, **shifts),
index=2)
trySAM = False #True FIXME
SAM_box_size = [5,20]
#elif ((self.pupil_mask is not None) and (self.pupil_mask.startswith('MASK'))):
else:
elif ((self.pupil_mask is not None) and ('LENS' not in self.pupil_mask.upper() )):
# no occulter selected but coronagraphic mode anyway. E.g. off-axis PSF
# but don't add this image plane for weak lens calculations
#optsys.add_image(poppy.ScalarTransmission(name='No Image Mask Selected!'), index=1)
optsys.add_image(poppy.ScalarTransmission(name='No Image Mask Selected!'), index=1)
trySAM = False
SAM_box_size = 1.0 # irrelevant but variable still needs to be set.
#SAM_box_size = 1.0 # irrelevant but variable still needs to be set.
else:
trySAM = False
#SAM_box_size = 1.0 # irrelevant but variable still needs to be set.

# add pupil plane mask
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):
shift = (self.options['pupil_shift_x'], self.options['pupil_shift_y'])
shift = (self.options.get('pupil_shift_x', 0),
self.options.get('pupil_shift_y', 0))
else: shift = None

rotation =self.options.get('pupil_rotation', None)

#NIRCam as-built weak lenses, from WSS config file
WLP4_diversity = 8.27309 # microns
WLP8_diversity = 16.4554 # microns
WLM8_diversity =-16.4143 # microns
WL_wavelength = 2.12 # microns

#optsys.add_pupil( name='null for debugging NIRcam _addCoron') # debugging
if self.pupil_mask == 'CIRCLYOT' or self.pupil_mask=='MASKRND':
optsys.add_pupil(transmission=self._datapath+"/optics/NIRCam_Lyot_Somb.fits.gz", name=self.pupil_mask,
flip_y=True, shift=shift, index=3)
flip_y=True, shift=shift, rotation=rotation, index=3)
optsys.planes[-1].wavefront_display_hint='intensity'
elif self.pupil_mask == 'WEDGELYOT' or self.pupil_mask=='MASKSWB' or self.pupil_mask=='MASKLWB':
optsys.add_pupil(transmission=self._datapath+"/optics/NIRCam_Lyot_Sinc.fits.gz", name=self.pupil_mask,
flip_y=True, shift=shift, index=3)
flip_y=True, shift=shift, rotation=rotation, index=3)
optsys.planes[-1].wavefront_display_hint='intensity'
elif self.pupil_mask == 'WEAK LENS +4' or self.pupil_mask == 'WLP4':
optsys.add_pupil(poppy.ThinLens(
name='Weak Lens +4',
nwaves=WLP4_diversity / WL_wavelength,
reference_wavelength=WL_wavelength*1e-6, #convert microns to meters
radius=self.pupil_radius
radius=self.pupil_radius,
shift=shift, rotation=rotation,
), index=3)
elif self.pupil_mask == 'WEAK LENS +8' or self.pupil_mask == 'WLP8':
optsys.add_pupil(poppy.ThinLens(
name='Weak Lens +8',
nwaves=WLP8_diversity / WL_wavelength,
reference_wavelength=WL_wavelength*1e-6,
radius=self.pupil_radius
radius=self.pupil_radius,
shift=shift,rotation=rotation,
), index=3)
elif self.pupil_mask == 'WEAK LENS -8' or self.pupil_mask == 'WLM8':
optsys.add_pupil(poppy.ThinLens(
name='Weak Lens -8',
nwaves=WLM8_diversity / WL_wavelength,
reference_wavelength=WL_wavelength*1e-6,
radius=self.pupil_radius
radius=self.pupil_radius,
shift=shift,rotation=rotation,
), index=3)
elif self.pupil_mask == 'WEAK LENS +12 (=4+8)' or self.pupil_mask == 'WLP12':
stack = poppy.CompoundAnalyticOptic(name='Weak Lens Pair +12', opticslist=[
poppy.ThinLens(
name='Weak Lens +4',
nwaves=WLP4_diversity / WL_wavelength,
reference_wavelength=WL_wavelength*1e-6,
radius=self.pupil_radius
radius=self.pupil_radius,
shift=shift, rotation=rotation,
),
poppy.ThinLens(
name='Weak Lens +8',
nwaves=WLP8_diversity / WL_wavelength,
reference_wavelength=WL_wavelength*1e-6,
radius=self.pupil_radius
radius=self.pupil_radius,
shift=shift, rotation=rotation,
)]
)
optsys.add_pupil(stack, index=3)
Expand All @@ -1182,20 +1198,26 @@ def _addAdditionalOptics(self,optsys, oversample=2):
name='Weak Lens +4',
nwaves=WLP4_diversity / WL_wavelength,
reference_wavelength=WL_wavelength*1e-6,
radius=self.pupil_radius
radius=self.pupil_radius,
shift=shift, rotation=rotation,
),
poppy.ThinLens(
name='Weak Lens -8',
nwaves=WLM8_diversity / WL_wavelength,
reference_wavelength=WL_wavelength*1e-6,
radius=self.pupil_radius
radius=self.pupil_radius,
shift=shift, rotation=rotation,
)]
)
optsys.add_pupil(stack, index=3)


elif (self.pupil_mask is None and self.image_mask is not None):
optsys.add_pupil(poppy.ScalarTransmission(name='No Lyot Mask Selected!'), index=3)
else:
optsys.add_pupil(transmission=self._WebbPSF_basepath+"/tricontagon_oversized_4pct.fits.gz",
name = 'filter stop', shift=shift, rotation=rotation)


return (optsys, trySAM, SAM_box_size)

Expand Down

0 comments on commit 5770740

Please sign in to comment.