Source code for tom_observations.facilities.soar
import requests
from django import forms
from crispy_forms.bootstrap import Tab, Alert
from crispy_forms.layout import Div
from django.forms.widgets import HiddenInput
from tom_observations.facilities.lco import LCOFacility, LCOSettings, SpectralInstrumentConfigLayout
from tom_observations.facilities.lco import LCOImagingObservationForm, LCOSpectroscopyObservationForm
from tom_observations.facilities.lco import SpectralConfigurationLayout
from tom_common.exceptions import ImproperCredentialsException
[docs]
class SOARSettings(LCOSettings):
instrument_type_help = """
<a href="https://noirlab.edu/science/programs/ctio/telescopes/soar-telescope/instruments" target="_blank">
More information about SOAR instruments.
</a>
"""
exposure_time_help = """
"""
rotator_mode_help = """
"""
rotator_angle_help = """
"""
[docs]
def get_sites(self):
"""
Returns location informatino for the SOAR observatory
"""
return {
'Cerro Pachón': {
'sitecode': 'sor',
'latitude': -30.237892,
'longitude': -70.733642,
'elevation': 2000
}
}
[docs]
def get_weather_urls(self):
"""
Returns the URL providing weather information from the SOAR observatory site
"""
return {
'code': 'SOAR',
'sites': [
{
'code': site['sitecode'],
'weather_url': """'https://noirlab.edu/science/observing-noirlab/weather-webcams/
cerro-pachon/environmental-conditions'"""
}
for site in self.get_sites().values()]
}
def __init__(self, facility_name='SOAR'):
super().__init__(facility_name=facility_name)
def make_request(*args, **kwargs):
response = requests.request(*args, **kwargs)
if 400 <= response.status_code < 500:
raise ImproperCredentialsException('SOAR: ' + str(response.content))
response.raise_for_status()
return response
[docs]
class SOARImagingObservationForm(LCOImagingObservationForm):
def __init__(self, *args, **kwargs):
# Set the facility settings to the SOAR settings
if 'facility_settings' not in kwargs:
kwargs['facility_settings'] = SOARSettings("SOAR")
super().__init__(*args, **kwargs)
def get_instruments(self):
instruments = super()._get_instruments()
return {
code: instrument for (code, instrument) in instruments.items() if (
'IMAGE' == instrument['type'] and 'SOAR' in code)
}
def configuration_type_choices(self):
return [('EXPOSE', 'Exposure')]
[docs]
class SOARSpectroscopyObservationForm(LCOSpectroscopyObservationForm):
def __init__(self, *args, **kwargs):
# Set the facility settings to the SOAR settings
if 'facility_settings' not in kwargs:
kwargs['facility_settings'] = SOARSettings("SOAR")
super().__init__(*args, **kwargs)
# Add readout mode field for each instrument configuration since LCO doesn't have this field for spectroscopy
for j in range(self.facility_settings.get_setting('max_configurations')):
for i in range(self.facility_settings.get_setting('max_instrument_configs')):
self.fields[f'c_{j + 1}_ic_{i + 1}_readout_mode'] = forms.ChoiceField(
choices=self.mode_choices('readout'), required=False, label='Readout Mode')
def get_instruments(self):
instruments = super()._get_instruments()
return {
code: instrument for (code, instrument) in instruments.items() if (
'SPECTRA' == instrument['type'] and 'SOAR' in code)
}
def configuration_type_choices(self):
return [('SPECTRUM', 'Spectrum'), ('ARC', 'Arc'), ('LAMP_FLAT', 'Lamp Flat')]
def instrument_config_layout_class(self):
return SoarSpectralInstrumentConfigLayout
[docs]
class SOARSimpleGoodmanSpectroscopyObservationForm(SOARSpectroscopyObservationForm):
def __init__(self, *args, **kwargs):
if 'facility_settings' not in kwargs:
kwargs['facility_settings'] = SOARSettings("SOAR")
super().__init__(*args, **kwargs)
# Set default values for Arcs/Flats
self.fields['c_2_configuration_type'].initial = "ARC"
self.fields['c_2_ic_1_exposure_time'].initial = 0.5
self.fields['c_2_target_override'].widget = HiddenInput()
self.fields['c_3_configuration_type'].initial = "LAMP_FLAT"
self.fields['c_3_ic_1_exposure_time'].initial = 0.5
self.fields['c_3_target_override'].widget = HiddenInput()
for j in range(2, 4):
for i in range(self.facility_settings.get_setting('max_instrument_configs')):
self.fields[f'c_{j}_ic_{i+1}_readout_mode'].widget = HiddenInput()
self.fields[f'c_{j}_ic_{i+1}_exposure_time'].help_text = "Exposure time is hard-coded, " \
"and the value is ignored for Flats/Arcs. " \
"Any value will cause the calibration to be " \
"scheduled."
def form_name(self):
if 'BLUE' in self.initial.get('observation_type', ''):
return 'BlueCam'
return 'RedCam'
def get_instruments(self):
instruments = super()._get_instruments()
return {
code: instrument for (code, instrument) in instruments.items() if ('SPECTRA' == instrument['type'] and
'SOAR' in code and
self.form_name() in instrument['name'])
}
def _build_instrument_configs(self, instrument_type, configuration_id):
ics = super()._build_instrument_configs(instrument_type, configuration_id)
# Overwrite the Lamp/Arc readout mode to be the same mode as the initial spectrum
if configuration_id != 1:
for j, ic in enumerate(ics):
ic['mode'] = self._build_instrument_config(instrument_type, 1, j + 1)['mode']
return ics
def configuration_layout_class(self):
return SOARSimpleConfigurationLayout
[docs]
class SOARSimpleConfigurationLayout(SpectralConfigurationLayout):
def _get_config_tabs(self, oe_groups, num_tabs):
tabs = [Tab('Spectrum',
*self._get_config_layout(1, oe_groups),
css_id=f'{self.form_name}_config_{1}'
),
Tab('Arc',
*self._get_config_layout(2, oe_groups),
css_id=f'{self.form_name}_config_{2}'
),
Tab('Lamp Flat',
*self._get_config_layout(3, oe_groups),
css_id=f'{self.form_name}_config_{3}'
)
]
return tuple(tabs)
def _get_basic_config_layout(self, instance):
return (
Alert(
content="""Make sure the instrument and readout match the current load-out for SOAR.
""",
css_class='alert-warning'
),
Alert(
content="""An Arc and a Lamp FLat will be automatically generated for this observation.
""",
css_class='alert-success'
),
Div(
Div(
f'c_{instance}_instrument_type',
css_class='col'
),
Div(
f'c_{instance}_configuration_type',
css_class='col'
),
css_class='row'
),
)
[docs]
class SoarSpectralInstrumentConfigLayout(SpectralInstrumentConfigLayout):
def _get_ic_layout(self, config_instance, instance, oe_groups):
return (
Div(
Div(
f'c_{config_instance}_ic_{instance}_readout_mode',
css_class='col'
),
css_class='row'
),
Div(
Div(
f'c_{config_instance}_ic_{instance}_exposure_time',
css_class='col'
),
Div(
f'c_{config_instance}_ic_{instance}_exposure_count',
css_class='col'
),
css_class='row'
),
Div(
Div(
f'c_{config_instance}_ic_{instance}_rotator_mode',
css_class='col'
),
Div(
f'c_{config_instance}_ic_{instance}_rotator_angle',
css_class='col'
),
css_class='row'
),
*self._get_oe_groups_layout(config_instance, instance, oe_groups)
)
[docs]
class SOARFacility(LCOFacility):
"""
The ``SOARFacility`` is the interface to the SOAR Telescope. For information regarding SOAR observing and the
available parameters, please see https://noirlab.edu/science/observing-noirlab/observing-ctio/observing-soar.
Please note that SOAR is only available in AEON-mode. It also uses the LCO API key, so to use this module, the
LCO dictionary in FACILITIES in `settings.py` will need to be completed.
.. code-block:: python
:caption: settings.py
FACILITIES = {
'SOAR': {
'portal_url': 'https://observe.lco.global',
'api_key': os.getenv('LCO_API_KEY'),
},
}
"""
name = 'SOAR'
observation_forms = {
'IMAGING': SOARImagingObservationForm,
'Goodman_BLUE_Spectra': SOARSimpleGoodmanSpectroscopyObservationForm,
'Goodman_RED_Spectra': SOARSimpleGoodmanSpectroscopyObservationForm,
'SPECTRA_Advanced': SOARSpectroscopyObservationForm,
}
def __init__(self, facility_settings=SOARSettings("SOAR")):
super().__init__(facility_settings=facility_settings)
[docs]
def get_form(self, observation_type):
return self.observation_forms.get(observation_type, SOARImagingObservationForm)