Source code for tom_observations.facilities.blanco
import requests
from django import forms
from crispy_forms.layout import Div, HTML
from tom_observations.facilities.ocs import OCSInstrumentConfigLayout, OCSConfigurationLayout
from tom_observations.facilities.ocs import OCSFullObservationForm, OCSAdvancedExpansionsLayout
from tom_observations.facilities.lco import LCOFacility, LCOSettings
from tom_common.exceptions import ImproperCredentialsException
[docs]
class BLANCOSettings(LCOSettings):
instrument_type_help = '<a href="https://noirlab.edu/science/programs/ctio/telescopes/victor-blanco-4m-telescope/' \
'Instruments-Available-Blanco-Telescope" target="_blank">' \
'More information about BLANCO instruments.' \
'</a>'
exposure_time_help = """
"""
rotator_mode_help = """
"""
rotator_angle_help = """
"""
[docs]
def get_sites(self):
"""
Returns location information for observing sites.
"""
return {
'Cerro Tololo': {
'sitecode': 'bco',
'latitude': -30.16541667,
'longitude': -70.81463889,
'elevation': 2000
}
}
[docs]
def get_weather_urls(self):
"""
Returns weather URLs for the BLANCO facility.
"""
return {
'code': 'BLANCO',
'sites': [
{
'code': site['sitecode'],
'weather_url': """'https://noirlab.edu/science/observing-noirlab/weather-webcams/
cerro-tololo/environmental-conditions'"""
}
for site in self.get_sites().values()
]
}
def __init__(self, facility_name='BLANCO'):
super().__init__(facility_name=facility_name)
def make_request(*args, **kwargs):
response = requests.request(*args, **kwargs)
if 400 <= response.status_code < 500:
raise ImproperCredentialsException('BLANCO: ' + str(response.content))
response.raise_for_status()
return response
[docs]
class BLANCOAdvancedExpansionLayout(OCSAdvancedExpansionsLayout):
def _get_dithering_tab(self):
return (
Div(
HTML('''<br/><p>Dithering settings are a part of the BLANCO configuration above</p>'''),
)
)
[docs]
class BLANCOConfigurationLayout(OCSConfigurationLayout):
[docs]
def get_initial_accordion_items(self, instance):
return (
Div(
Div(
f'c_{instance}_dither_value',
css_class='col'
),
Div(
f'c_{instance}_dither_sequence',
css_class='col'
),
css_class='row'
),
Div(
Div(
f'c_{instance}_detector_centering',
css_class='col'
),
Div(
f'c_{instance}_dither_sequence_random_offset',
css_class='col'
),
css_class='row'
),
)
[docs]
class BLANCOInstrumentConfigLayout(OCSInstrumentConfigLayout):
[docs]
def get_final_ic_items(self, config_instance, instance):
return (
Div(
Div(
f'c_{config_instance}_ic_{instance}_coadds',
css_class='col'
),
Div(
f'c_{config_instance}_ic_{instance}_sequence_repeats',
css_class='col'
),
css_class='row'
)
)
[docs]
class BLANCOImagingObservationForm(OCSFullObservationForm):
"""
The BLANCOImagingObservationForm allows selection of blanco specific instrument parameters
"""
DETECTOR_CENTERING_CHOICES = [('none', 'none'), ('det_1', 'det_1'), ('det_2', 'det_2'),
('det_3', 'det_3'), ('det_4', 'det_4')]
DITHER_SEQUENCE_CHOICES = [('2x2', '2x2'), ('3x3', '3x3'), ('4x4', '4x4'), ('5-point', '5-point')]
def __init__(self, *args, **kwargs):
if 'facility_settings' not in kwargs:
kwargs['facility_settings'] = BLANCOSettings("BLANCO")
super().__init__(*args, **kwargs)
# Need to add the blanco specific fields to this form
for j in range(self.facility_settings.get_setting('max_configurations')):
self.fields[f'c_{j+1}_dither_value'] = forms.IntegerField(
min_value=0, max_value=1600, initial=80, label='Dither Value',
help_text="The amount in arc seconds between dither points",
widget=forms.TextInput(attrs={'placeholder': 'Arc Seconds'}), required=True)
self.fields[f'c_{j+1}_dither_sequence'] = forms.ChoiceField(
choices=self.DITHER_SEQUENCE_CHOICES, required=True, label='Dither Sequence', initial='2x2',
help_text="The pattern to execute your dither points with")
self.fields[f'c_{j+1}_detector_centering'] = forms.ChoiceField(
choices=self.DETECTOR_CENTERING_CHOICES, required=True, label='Detector Centering', initial='det_1',
help_text="Place my target in the center of this detector")
self.fields[f'c_{j+1}_dither_sequence_random_offset'] = forms.BooleanField(
required=True, label='Dither Sequence Random Offset', initial=True,
help_text="Implements a random offset between dither patterns if repeating the dither pattern,"
" i.e. when sequence repeats > 1")
self.fields[f'c_{j+1}_repeat_duration'].widget = forms.HiddenInput()
for i in range(self.facility_settings.get_setting('max_instrument_configs')):
self.fields[f'c_{j+1}_ic_{i+1}_coadds'] = forms.IntegerField(
min_value=1, max_value=100, label='Coadds', initial=1,
help_text="This reduces data volume with short integration times necessary for broadband H and Ks"
" observations. Coadding is digital summation of the images to avoid long integrations that could"
" cause saturation of the detector.",
widget=forms.TextInput(attrs={'placeholder': 'Number'}), required=True)
self.fields[f'c_{j+1}_ic_{i+1}_sequence_repeats'] = forms.IntegerField(
min_value=1, max_value=100, label='Sequence Repeats', initial=1,
help_text="The number of times to repeat the dither sequence",
widget=forms.TextInput(attrs={'placeholder': 'Number'}), required=True)
def form_name(self):
return 'blanco'
def instrument_config_layout_class(self):
return BLANCOInstrumentConfigLayout
def configuration_layout_class(self):
return BLANCOConfigurationLayout
def advanced_expansions_layout_class(self):
return BLANCOAdvancedExpansionLayout
def get_instruments(self):
instruments = super()._get_instruments()
return {
code: instrument for (code, instrument) in instruments.items() if (
'IMAGE' == instrument['type'] and 'BLANCO' in code)
}
def configuration_type_choices(self):
return [('EXPOSE', 'Exposure'), ('STANDARD', 'Standard')]
def _build_configuration(self, build_id):
configuration = super()._build_configuration(build_id)
# Now parse out the fields that need to be in extra params here
if configuration:
configuration['extra_params'] = {
'dither_value': self.cleaned_data[f'c_{build_id}_dither_value'],
'dither_sequence': self.cleaned_data[f'c_{build_id}_dither_sequence'],
'detector_centering': self.cleaned_data[f'c_{build_id}_detector_centering'],
'dither_sequence_random_offset': self.cleaned_data[f'c_{build_id}_dither_sequence_random_offset']
}
return configuration
def _build_instrument_config(self, instrument_type, configuration_id, instrument_config_id):
instrument_config = super()._build_instrument_config(instrument_type, configuration_id, instrument_config_id)
# Now fill in the extra_params fields here
if instrument_config:
instrument_config['extra_params'] = {
'coadds': self.cleaned_data[f'c_{configuration_id}_ic_{instrument_config_id}_coadds'],
'sequence_repeats': self.cleaned_data[
f'c_{configuration_id}_ic_{instrument_config_id}_sequence_repeats'
]
}
return instrument_config
[docs]
class BLANCOFacility(LCOFacility):
"""
The ``BLANCOFacility`` is the interface to the BLANCO Telescope. For information regarding BLANCO observing and the
available parameters, please see:
https://noirlab.edu/science/observing-noirlab/observing-ctio/cerro-tololo/observing-blanco.
Please note that BLANCO 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 = {
'BLANCO': {
'portal_url': 'https://observe.lco.global',
'api_key': os.getenv('LCO_API_KEY'),
},
}
"""
name = 'BLANCO'
observation_forms = {
'IMAGING': BLANCOImagingObservationForm
}
def __init__(self, facility_settings=BLANCOSettings("BLANCO")):
super().__init__(facility_settings=facility_settings)
[docs]
def get_form(self, observation_type):
return self.observation_forms.get(observation_type, BLANCOImagingObservationForm)