import logging
import matplotlib
import astropy.io.fits as fits
import poppy
from . import webbpsf_core
from . import roman
from . import utils
_log = logging.getLogger('webbpsf')
[docs]
def show_notebook_interface(instrumentname):
"""
Show Jupyter notebook widget interface
Parameters
-----------
instrumentname : string
one of 'NIRCam','NIRSpec','NIRISS','FGS','MIRI'
or 'WFI'
"""
if instrumentname.upper()=='WFI':
instrument = roman.WFI()
show_notebook_interface_wfi(instrument)
else:
instrument = webbpsf_core.instrument(instrumentname)
show_notebook_interface_jwst(instrument)
def show_notebook_interface_wfi(instrument):
# Widget related imports.
# (Currently not a hard dependency for the full webbpsf package, so we import
# within the function.)
import ipywidgets as widgets
from IPython.display import display, clear_output
from matplotlib import pyplot as plt
try:
import synphot
except ImportError:
raise ImportError(f"For now, synphot must be installed to use the notebook interface")
# Clean up some warnings we know about so as not to scare the users
import warnings
from matplotlib.cbook import MatplotlibDeprecationWarning
warnings.simplefilter('ignore', MatplotlibDeprecationWarning)
warnings.simplefilter('ignore', fits.verify.VerifyWarning)
def make_binding_for_attribute(attribute):
def callback(trait_name, new_value):
setattr(instrument, attribute, new_value)
return callback
filter_selection = widgets.ToggleButtons(
options=instrument.filter_list,
value=instrument.filter,
description='Filter:'
)
filter_selection.on_trait_change(
make_binding_for_attribute('filter'),
name='selected_label'
)
display(filter_selection)
monochromatic_wavelength = widgets.BoundedFloatText(
value=0.76,
min=0.6,
max=2.0,
)
monochromatic_wavelength.disabled = True
monochromatic_toggle = widgets.Checkbox(description='Monochromatic calculation?')
def update_monochromatic(trait_name, new_value):
filter_selection.disabled = new_value
monochromatic_wavelength.disabled = not new_value
monochromatic_toggle.on_trait_change(update_monochromatic, name='value')
display(widgets.HTML(value='''<p style="padding: 1em 0;">
<span style="font-style:italic; font-size:1.0em">
Monochromatic calculations can be performed for any wavelength in the 0.6 to 2.0 µm range.
</span></p>''')) # kludge
monochromatic_controls = widgets.HBox(children=(
monochromatic_toggle,
widgets.HTML(value='<span style="display: inline-block; width: 0.6em;"></span>'),
monochromatic_wavelength,
widgets.HTML(value='<span style="display: inline-block; width: 0.25em;"></span> µm '),
))
display(monochromatic_controls)
display(widgets.HTML(value="<hr>"))
source_selection = widgets.Select(
options=poppy.specFromSpectralType('', return_list=True),
value='G0V',
description="Source spectrum"
)
display(source_selection)
display(widgets.HTML(value="<hr>"))
sca_selection = widgets.Dropdown(
options=instrument.detector_list,
value=instrument.detector,
description='Detector:'
)
sca_selection.on_trait_change(
make_binding_for_attribute('detector'),
name='selected_label'
)
display(sca_selection)
detector_field_points = [
('Top left', (4.0, 4092.0)),
('Bottom left', (4.0, 4.0)),
('Center', (2048.0, 2048.0)),
('Top right', (4092.0, 4092.0)),
('Bottom right', (4092.0, 4.0)),
]
# enforce ordering of buttons
detector_field_point_labels = [a[0] for a in detector_field_points]
detector_field_points = dict(detector_field_points)
def set_field_position(trait_name, new_value):
instrument.detector_position = detector_field_points[new_value]
field_position = widgets.ToggleButtons(options=detector_field_point_labels, value='Center', description='Detector field point:')
field_position.on_trait_change(set_field_position, name='selected_label')
display(field_position)
calculate_button = widgets.Button(
description="Calculate PSF",
width='10em',
color='white',
background_color='#00c403',
border_color='#318732'
)
display_osys_button = widgets.Button(
description="Display Optical System",
width='13em',
color='white',
background_color='#005fc4',
border_color='#224A75'
)
clear_button = widgets.Button(
description="Clear Output",
width='10em',
color='white',
background_color='#ed4747',
border_color='#911C1C'
)
progress = widgets.HTML(value='<progress>')
OUTPUT_FILENAME = 'psf.fits'
DOWNLOAD_BUTTON_HTML = """
<a class="btn btn-info" href="files/{}" target="_blank">
Download FITS image from last calculation
</a>
"""
download_link = widgets.HTML(value=DOWNLOAD_BUTTON_HTML.format(OUTPUT_FILENAME))
def disp(*args):
progress.visible = True
plt.figure(figsize=(12, 8))
instrument.display()
progress.visible = None
def calc(*args):
progress.visible = True
if monochromatic_toggle.value is True:
psf = instrument.calc_psf(
monochromatic=monochromatic_wavelength.value * 1e-6,
display=True,
outfile=OUTPUT_FILENAME,
overwrite=True
)
else:
source = poppy.specFromSpectralType(source_selection.value)
_log.debug("Got source type {}: {}".format(source_selection.value, source))
psf = instrument.calc_psf(
source=source,
display=True,
outfile=OUTPUT_FILENAME,
overwrite=True
)
fig, (ax_oversamp, ax_detsamp) = plt.subplots(1, 2)
poppy.display_psf(psf, ax=ax_oversamp)
poppy.display_psf(psf, ax=ax_detsamp, ext='DET_SAMP')
progress.visible = None
download_link.visible = True
def clear(*args):
clear_output()
progress.visible = None
download_link.visible = None
calculate_button.on_click(calc)
display_osys_button.on_click(disp)
clear_button.on_click(clear)
display(widgets.HTML(value="<br/>")) # kludge
buttons = widgets.HBox(children=[calculate_button, display_osys_button, clear_button])
display(buttons)
# Insert the progress bar, hidden by default
display(progress)
progress.visible = None
# and the download link
display(download_link)
download_link.visible = None
def show_notebook_interface_jwst(instrument):
""" Show Jupyter Notebook interface, for a JWST instrument
Parameters
-------------
instrument : string or object
either a webbpsf instrument object, e.g. `NIRCam()`
or the string name of an instrument.
Example
--------
nc = webbpsf.NIRCam()
webbpsf.show_notebook_interface(nc)
"""
# Widget related imports.
# (Currently not a hard dependency for the full webbpsf package, so we import
# within the function.)
import ipywidgets as widgets
from IPython.display import display, clear_output
from matplotlib import pyplot as plt
if isinstance(instrument, str):
instrument = instrument(instrument)
try:
import synphot
except ImportError:
raise ImportError(f"For now, synphot must be installed to use the notebook interface")
# Clean up some warnings we know about so as not to scare the users
import warnings
from matplotlib.cbook import MatplotlibDeprecationWarning
warnings.simplefilter('ignore', MatplotlibDeprecationWarning)
warnings.simplefilter('ignore', fits.verify.VerifyWarning)
def make_binding_for_attribute(attribute):
def callback(trait_name, new_value):
if new_value == 'None':
setattr(instrument, attribute, None)
else:
setattr(instrument, attribute, new_value)
return callback
display(widgets.HTML(value='''<p style="padding: 1em 0;">
<span style="font-weight:bold; font-size:1.0em">
Notebook Interface for {} PSF sims
</span></p>'''.format(instrument.name)))
filter_selection = widgets.Dropdown(
options=instrument.filter_list,
value=instrument.filter,
description='Filter:')
filter_selection.on_trait_change(
make_binding_for_attribute('filter'),
name='selected_label'
)
display(filter_selection)
wl_bounds = (5., 30., 10.0) if instrument.name=='MIRI' else (0.6, 5.3, 2.0)
monochromatic_wavelength = widgets.BoundedFloatText(
value=wl_bounds[2],
min=wl_bounds[0],
max=wl_bounds[1],
)
monochromatic_wavelength.disabled = True
monochromatic_toggle = widgets.Checkbox(description='Monochromatic calculation?')
def update_monochromatic(trait_name, new_value):
filter_selection.disabled = new_value
monochromatic_wavelength.disabled = not new_value
monochromatic_toggle.on_trait_change(update_monochromatic, name='value')
display(widgets.HTML(value='''<p style="padding: 1em 0;">
<span style="font-style:italic; font-size:1.0em">
Monochromatic calculations can be performed for any wavelength in the {} to {} µm range.
</span></p>'''.format(*wl_bounds))) # kludge
monochromatic_controls = widgets.HBox(children=(
monochromatic_toggle,
widgets.HTML(value='<span style="display: inline-block; width: 0.6em;"></span>'),
monochromatic_wavelength,
widgets.HTML(value='<span style="display: inline-block; width: 0.25em;"></span> µm '),
))
display(monochromatic_controls)
display(widgets.HTML(value="<hr>"))
if instrument.name != 'FGS':
image_selection = widgets.Dropdown(
options=['None'] + instrument.image_mask_list,
value=str(instrument.image_mask),
description='Image Mask:')
image_selection.on_trait_change(
make_binding_for_attribute('image_mask'),
name='selected_label'
)
display(image_selection)
pupil_selection = widgets.Dropdown(
options=['None'] + instrument.pupil_mask_list,
value=str(instrument.pupil_mask),
description='Pupil Mask: ')
pupil_selection.on_trait_change(
make_binding_for_attribute('pupil_mask'),
name='selected_label'
)
display(pupil_selection)
display(widgets.HTML(value="<hr>"))
source_selection = widgets.Dropdown(
options=poppy.specFromSpectralType('', return_list=True),
value='G0V',
description="Source spectrum"
)
display(source_selection)
display(widgets.HTML(value="<hr>"))
calculate_button = widgets.Button(
description="Calculate PSF",
width='10em',
color='white',
background_color='#00c403',
border_color='#318732'
)
display_osys_button = widgets.Button(
description="Display Optical System",
width='13em',
color='white',
background_color='#005fc4',
border_color='#224A75'
)
clear_button = widgets.Button(
description="Clear Output",
width='10em',
color='white',
background_color='#ed4747',
border_color='#911C1C'
)
progress = widgets.HTML(value='<progress>')
OUTPUT_FILENAME = 'psf.fits'
DOWNLOAD_BUTTON_HTML = """
<a class="btn btn-info" href="files/{}" target="_blank">
Download FITS image from last calculation
</a>
"""
download_link = widgets.HTML(value=DOWNLOAD_BUTTON_HTML.format(OUTPUT_FILENAME))
def disp(*args):
progress.visible = True
plt.figure(figsize=(12, 8))
instrument.display()
progress.visible = None
def calc(*args):
progress.visible = True
if monochromatic_toggle.value is True:
psf = instrument.calc_psf(
monochromatic=monochromatic_wavelength.value * 1e-6,
display=True,
outfile=OUTPUT_FILENAME,
overwrite=True
)
else:
source = poppy.specFromSpectralType(source_selection.value)
_log.debug("Got source type {}: {}".format(source_selection.value, source))
psf = instrument.calc_psf(
source=source,
display=True,
outfile=OUTPUT_FILENAME,
overwrite=True
)
fig, (ax_oversamp, ax_detsamp) = plt.subplots(1, 2,figsize=(12, 4))
title1 = "PSF sim for {}, {}\n".format(instrument.name, instrument.filter)
poppy.display_psf(psf, ax=ax_oversamp,
title=title1+"Oversampled PSF")
poppy.display_psf(psf, ax=ax_detsamp, ext='DET_SAMP',
title=title1+'Detector pixel sampled PSF')
progress.visible = None
download_link.visible = True
def clear(*args):
clear_output()
progress.visible = None
download_link.visible = None
calculate_button.on_click(calc)
display_osys_button.on_click(disp)
clear_button.on_click(clear)
display(widgets.HTML(value="<br/>")) # kludge
buttons = widgets.HBox(children=[calculate_button, display_osys_button, clear_button])
display(buttons)
# Insert the progress bar, hidden by default
display(progress)
progress.visible = None
# and the download link
display(download_link)
download_link.visible = None