def get(self, request): info = {} # Staff users by default should see all instruments, but can request only schedulable instruments. # Non-staff users are only allowed access to schedulable instruments. if request.user.is_staff: only_schedulable = request.query_params.get( 'only_schedulable', False) else: only_schedulable = True requested_instrument_type = request.query_params.get( 'instrument_type', '') location = { 'site': request.query_params.get('site', ''), 'enclosure': request.query_params.get('enclosure', ''), 'telescope_class': request.query_params.get('telescope_class', ''), 'telescope': request.query_params.get('telescope', ''), } for instrument_type in configdb.get_instrument_type_codes( location=location, only_schedulable=only_schedulable): if not requested_instrument_type or requested_instrument_type.lower( ) == instrument_type.lower(): ccd_size = configdb.get_ccd_size(instrument_type) info[instrument_type] = { 'type': configdb.get_instrument_type_category(instrument_type), 'class': configdb.get_instrument_type_telescope_class( instrument_type), 'name': configdb.get_instrument_type_full_name(instrument_type), 'optical_elements': configdb.get_optical_elements(instrument_type), 'modes': configdb.get_modes_by_type(instrument_type), 'default_acceptability_threshold': configdb.get_default_acceptability_threshold( instrument_type), 'configuration_types': configdb.get_configuration_types(instrument_type), 'default_configuration_type': configdb.get_default_configuration_type(instrument_type), 'camera_type': { 'science_field_of_view': configdb.get_diagonal_ccd_fov(instrument_type, autoguider=False), 'autoguider_field_of_view': configdb.get_diagonal_ccd_fov(instrument_type, autoguider=True), 'pixel_scale': configdb.get_pixel_scale(instrument_type), 'pixels_x': ccd_size['x'], 'pixels_y': ccd_size['y'], 'orientation': configdb.get_average_ccd_orientation(instrument_type) } } return Response(info)
def get(self, request): info = {} # Staff users by default should see all instruments, but can request only schedulable instruments. # Non-staff users are only allowed access to schedulable instruments. if request.user.is_staff: only_schedulable = request.query_params.get('only_schedulable', False) else: only_schedulable = True requested_instrument_type = request.query_params.get('instrument_type', '') location = { 'site': request.query_params.get('site', ''), 'enclosure': request.query_params.get('enclosure', ''), 'telescope_class': request.query_params.get('telescope_class', ''), 'telescope': request.query_params.get('telescope', ''), } for instrument_type in configdb.get_instrument_types(location=location, only_schedulable=only_schedulable): if not requested_instrument_type or requested_instrument_type.lower() == instrument_type.lower(): info[instrument_type] = { 'type': 'SPECTRA' if configdb.is_spectrograph(instrument_type) else 'IMAGE', 'class': configdb.get_instrument_type_telescope_class(instrument_type), 'name': configdb.get_instrument_type_full_name(instrument_type), 'optical_elements': configdb.get_optical_elements(instrument_type), 'modes': configdb.get_modes_by_type(instrument_type), 'default_acceptability_threshold': configdb.get_default_acceptability_threshold(instrument_type) } return Response(info)
def test_validate_mode_config_filled_in_when_missing(self): guiding_config = {} modes = configdb.get_modes_by_type(self.request_instrument_type) validation_helper = ModeValidationHelper('guiding', self.request_instrument_type, modes['guiding']) validated_config = validation_helper.validate(guiding_config) self.assertEqual(validated_config['mode'], 'OFF')
def test_validate_mode_config_bad_config(self): instrument_config = self.instrument_config.copy() instrument_config['mode'] = "1m0_sbig_2" instrument_config['extra_params'] = {'bin_x': 1, 'bin_y': 2} modes = configdb.get_modes_by_type(self.request_instrument_type) validation_helper = ModeValidationHelper('readout', self.request_instrument_type, modes['readout']) with self.assertRaises(ValidationError) as e: validation_helper.validate(instrument_config) self.assertIn('bin_x', str(e.exception))
def test_validate_mode_config_good_config(self): instrument_config = self.instrument_config.copy() instrument_config['mode'] = "1m0_sbig_1" instrument_config['extra_params'] = {'bin_x': 1, 'bin_y': 1} modes = configdb.get_modes_by_type(self.request_instrument_type) validation_helper = ModeValidationHelper('readout', self.request_instrument_type, modes['readout']) validated_config = validation_helper.validate(instrument_config) self.assertEqual(validated_config, instrument_config)
def _instrument_defaults(self, instrument_type): '''Get a set of default values for optical elements and modes for an instrument''' modes = configdb.get_modes_by_type(instrument_type) optical_elements = configdb.get_optical_elements(instrument_type) defaults = {'optical_elements': {}} for oe_type, elements in optical_elements.items(): for element in elements: if element['default']: defaults['optical_elements'][ oe_type[:-1]] = element['code'] for mode_type, mode_group in modes.items(): defaults[mode_type] = mode_group.get('default', '') return defaults
def test_validate_extra_param_mode_good_config(self): instrument_config = self.instrument_config.copy() instrument_type = "2M0-SCICAM-MUSCAT" instrument_config['instrument_type'] = instrument_type instrument_config['extra_params'] = self.muscat_extra_params modes = configdb.get_modes_by_type(instrument_type) validation_helper = ModeValidationHelper('exposure', instrument_type, modes['exposure'], is_extra_param_mode=True) validated_instrument_config = validation_helper.validate( instrument_config) self.assertEqual(instrument_config, validated_instrument_config)
def test_validate_extra_param_mode_bad_config(self): instrument_config = self.instrument_config.copy() instrument_type = "2M0-SCICAM-MUSCAT" instrument_config['instrument_type'] = instrument_type instrument_config['extra_params'] = self.muscat_extra_params.copy() del instrument_config['extra_params']['exposure_mode'] modes = configdb.get_modes_by_type(instrument_type) validation_helper = ModeValidationHelper('exposure', instrument_type, modes['exposure'], is_extra_param_mode=True) with self.assertRaises(ValidationError) as e: validation_helper.validate(instrument_config) self.assertIn('exposure_mode', str(e.exception))
def get(self, request): info = {} is_staff = request.user.is_staff for instrument_type in configdb.get_instrument_types( {}, only_schedulable=(not is_staff)): info[instrument_type] = { 'type': 'SPECTRA' if configdb.is_spectrograph(instrument_type) else 'IMAGE', 'class': configdb.get_instrument_type_telescope_class(instrument_type), 'name': configdb.get_instrument_type_full_name(instrument_type), 'optical_elements': configdb.get_optical_elements(instrument_type), 'modes': configdb.get_modes_by_type(instrument_type), 'default_acceptability_threshold': configdb.get_default_acceptability_threshold(instrument_type) } return Response(info)
def validate(self, data): # TODO: Validate the guiding optical elements on the guiding instrument types instrument_type = data['instrument_type'] modes = configdb.get_modes_by_type(instrument_type) default_modes = configdb.get_default_modes_by_type(instrument_type) guiding_config = data['guiding_config'] if len(data['instrument_configs']) > 1 and data['type'] in ['SCRIPT', 'SKY_FLAT']: raise serializers.ValidationError(_(f'Multiple instrument configs are not allowed for type {data["type"]}')) # Validate the guide mode guide_validation_helper = ModeValidationHelper('guiding', instrument_type, default_modes, modes) if guide_validation_helper.mode_is_not_set(guiding_config): guide_mode_to_set = guide_validation_helper.get_mode_to_set() if guide_mode_to_set['error']: raise serializers.ValidationError(_(guide_mode_to_set['error'])) if guide_mode_to_set['mode']: guiding_config['mode'] = guide_mode_to_set['mode']['code'] else: guiding_config['mode'] = GuidingConfig.OFF guide_mode_error_msg = guide_validation_helper.get_mode_error_msg(guiding_config) if guide_mode_error_msg: raise serializers.ValidationError(_(guide_mode_error_msg)) if configdb.is_spectrograph(instrument_type) and data['type'] not in ['LAMP_FLAT', 'ARC', 'NRES_BIAS', 'NRES_DARK']: if 'optional' in guiding_config and guiding_config['optional']: raise serializers.ValidationError(_( "Guiding cannot be optional on spectrograph instruments for types that are not ARC or LAMP_FLAT." )) guiding_config['optional'] = False if data['type'] in ['LAMP_FLAT', 'ARC', 'AUTO_FOCUS', 'NRES_BIAS', 'NRES_DARK', 'BIAS', 'DARK', 'SCRIPT']: # These types of observations should only ever be set to guiding mode OFF, but the acquisition modes for # spectrographs won't necessarily have that mode. Force OFF here. data['acquisition_config']['mode'] = AcquisitionConfig.OFF else: # Validate acquire modes acquisition_config = data['acquisition_config'] acquire_validation_helper = ModeValidationHelper('acquisition', instrument_type, default_modes, modes) if acquire_validation_helper.mode_is_not_set(acquisition_config): acquire_mode_to_set = acquire_validation_helper.get_mode_to_set() if acquire_mode_to_set['error']: raise serializers.ValidationError(_(acquire_mode_to_set['error'])) if acquire_mode_to_set['mode']: acquisition_config['mode'] = acquire_mode_to_set['mode']['code'] else: acquisition_config['mode'] = AcquisitionConfig.OFF acquire_mode_error_msg = acquire_validation_helper.get_mode_error_msg(acquisition_config) if acquire_mode_error_msg: raise serializers.ValidationError(_(acquire_mode_error_msg)) available_optical_elements = configdb.get_optical_elements(instrument_type) for instrument_config in data['instrument_configs']: # Validate the readout mode and the binning. Readout modes and binning are tied # together- If one is set, we can determine the other. # TODO: Remove the binning checks when binnings are removed entirely readout_validation_helper = ModeValidationHelper('readout', instrument_type, default_modes, modes) if readout_validation_helper.mode_is_not_set(instrument_config): if 'bin_x' not in instrument_config and 'bin_y' not in instrument_config: # Set the readout mode as well as the binning readout_mode_to_set = readout_validation_helper.get_mode_to_set() if readout_mode_to_set['error']: raise serializers.ValidationError(_(readout_mode_to_set['error'])) if readout_mode_to_set['mode']: instrument_config['mode'] = readout_mode_to_set['mode']['code'] instrument_config['bin_x'] = readout_mode_to_set['mode']['params']['binning'] instrument_config['bin_y'] = readout_mode_to_set['mode']['params']['binning'] elif 'bin_x' in instrument_config: # A binning is set already - figure out what the readout mode should be from that try: instrument_config['mode'] = configdb.get_readout_mode_with_binning( instrument_type, instrument_config['bin_x'] )['code'] except ConfigDBException as cdbe: raise serializers.ValidationError(_(str(cdbe))) else: # A readout mode is set - validate the mode readout_error_msg = readout_validation_helper.get_mode_error_msg(instrument_config) if readout_error_msg: raise serializers.ValidationError(_(readout_error_msg)) # At this point the readout mode that is set is valid. Now either set the binnings, or make # sure that those that are set are ok readout_mode = configdb.get_mode_with_code(instrument_type, instrument_config['mode'], 'readout') if 'bin_x' not in instrument_config: instrument_config['bin_x'] = readout_mode['params']['binning'] instrument_config['bin_y'] = readout_mode['params']['binning'] elif instrument_config['bin_x'] != readout_mode['params']['binning']: raise serializers.ValidationError(_( f'Binning {instrument_config["bin_x"]} is not a valid binning for readout mode ' f'{instrument_config["mode"]} for instrument type {instrument_type}' )) # Validate the rotator modes if 'rotator' in modes: rotator_mode_validation_helper = ModeValidationHelper('rotator', instrument_type, default_modes, modes) if rotator_mode_validation_helper.mode_is_not_set(instrument_config): rotator_mode_to_set = rotator_mode_validation_helper.get_mode_to_set() if rotator_mode_to_set['error']: raise serializers.ValidationError(_(rotator_mode_to_set['error'])) if rotator_mode_to_set['mode']: instrument_config['rotator_mode'] = rotator_mode_to_set['mode']['code'] rotator_error_msg = rotator_mode_validation_helper.get_mode_error_msg(instrument_config) if rotator_error_msg: raise serializers.ValidationError(_(rotator_error_msg)) # Check that the optical elements specified are valid in configdb for oe_type, value in instrument_config.get('optical_elements', {}).items(): plural_type = '{}s'.format(oe_type) if plural_type not in available_optical_elements: raise serializers.ValidationError(_("optical_element of type {} is not available on {} instruments" .format(oe_type, data['instrument_type']))) available_elements = {element['code'].lower(): element['code'] for element in available_optical_elements[plural_type]} if plural_type in available_optical_elements and value.lower() not in available_elements.keys(): raise serializers.ValidationError(_("optical element {} of type {} is not available".format( value, oe_type ))) else: instrument_config['optical_elements'][oe_type] = available_elements[value.lower()] # Also check that any optical element group in configdb is specified in the request unless we are a BIAS or # DARK or SCRIPT type observation observation_types_without_oe = ['BIAS', 'DARK', 'SCRIPT'] if data['type'].upper() not in observation_types_without_oe: for oe_type in available_optical_elements.keys(): singular_type = oe_type[:-1] if oe_type.endswith('s') else oe_type if singular_type not in instrument_config.get('optical_elements', {}): raise serializers.ValidationError(_( f'Must set optical element of type {singular_type} for instrument type {instrument_type}' )) # Validate any regions of interest if 'rois' in instrument_config: max_rois = configdb.get_max_rois(instrument_type) ccd_size = configdb.get_ccd_size(instrument_type) if len(instrument_config['rois']) > max_rois: raise serializers.ValidationError(_( f'Instrument type {instrument_type} supports up to {max_rois} regions of interest' )) for roi in instrument_config['rois']: if 'x1' not in roi and 'x2' not in roi and 'y1' not in roi and 'y2' not in roi: raise serializers.ValidationError(_('Must submit at least one bound for a region of interest')) if 'x1' not in roi: roi['x1'] = 0 if 'x2' not in roi: roi['x2'] = ccd_size['x'] if 'y1' not in roi: roi['y1'] = 0 if 'y2' not in roi: roi['y2'] = ccd_size['y'] if roi['x1'] >= roi['x2'] or roi['y1'] >= roi['y2']: raise serializers.ValidationError(_( 'Region of interest pixels start must be less than pixels end' )) if roi['x2'] > ccd_size['x'] or roi['y2'] > ccd_size['y']: raise serializers.ValidationError(_( 'Regions of interest for instrument type {} must be in range 0<=x<={} and 0<=y<={}'.format( instrument_type, ccd_size['x'], ccd_size['y'] )) ) if data['type'] == 'SCRIPT': if ( 'extra_params' not in data or 'script_name' not in data['extra_params'] or not data['extra_params']['script_name'] ): raise serializers.ValidationError(_( 'Must specify a script_name in extra_params for SCRIPT configuration type' )) # Validate duration is set if it's a REPEAT_* type configuration if 'REPEAT' in data['type']: if 'repeat_duration' not in data or data['repeat_duration'] is None: raise serializers.ValidationError(_( f'Must specify a configuration repeat_duration for {data["type"]} type configurations.' )) else: # Validate that the duration exceeds the minimum to run everything at least once min_duration = sum( [get_instrument_configuration_duration( ic, data['instrument_type']) for ic in data['instrument_configs']] ) if min_duration > data['repeat_duration']: raise serializers.ValidationError(_( f'Configuration repeat_duration of {data["repeat_duration"]} is less than the minimum of ' f'{min_duration} required to repeat at least once' )) else: if 'repeat_duration' in data and data['repeat_duration'] is not None: raise serializers.ValidationError(_( 'You may only specify a repeat_duration for REPEAT_* type configurations.' )) # Validate the configuration type is available for the instrument requested if data['type'] not in configdb.get_configuration_types(instrument_type): raise serializers.ValidationError(_( f'configuration type {data["type"]} is not valid for instrument type {instrument_type}' )) return data
def validate(self, data): # TODO: Validate the guiding optical elements on the guiding instrument types instrument_type = data['instrument_type'] modes = configdb.get_modes_by_type(instrument_type) guiding_config = data['guiding_config'] if len(data['instrument_configs']) > 1 and data['type'] in [ 'SCRIPT', 'SKY_FLAT' ]: raise serializers.ValidationError( _(f'Multiple instrument configs are not allowed for type {data["type"]}' )) # Validate the guide mode guide_validation_helper = ModeValidationHelper('guiding', instrument_type, modes['guiding']) guiding_config = guide_validation_helper.validate(guiding_config) if not guiding_config.get('mode'): # Guiding modes have an implicit default of OFF (we could just put this in all relevent validation_schema) guiding_config['mode'] = GuidingConfig.OFF data['guiding_config'] = guiding_config if configdb.is_spectrograph(instrument_type) and data['type'] not in [ 'LAMP_FLAT', 'ARC', 'NRES_BIAS', 'NRES_DARK' ]: if 'optional' in guiding_config and guiding_config['optional']: raise serializers.ValidationError( _("Guiding cannot be optional on spectrograph instruments for types that are not ARC or LAMP_FLAT." )) guiding_config['optional'] = False if data['type'] in [ 'LAMP_FLAT', 'ARC', 'AUTO_FOCUS', 'NRES_BIAS', 'NRES_DARK', 'BIAS', 'DARK', 'SCRIPT' ]: # These types of observations should only ever be set to guiding mode OFF, but the acquisition modes for # spectrographs won't necessarily have that mode. Force OFF here. data['acquisition_config']['mode'] = AcquisitionConfig.OFF else: # Validate acquire modes acquisition_config = data['acquisition_config'] acquire_validation_helper = ModeValidationHelper( 'acquisition', instrument_type, modes['acquisition']) acquisition_config = acquire_validation_helper.validate( acquisition_config) if not acquisition_config.get('mode'): # Acquisition modes have an implicit default of OFF (we could just put this in all relevent validation_schema) acquisition_config['mode'] = AcquisitionConfig.OFF data['acquisition_config'] = acquisition_config available_optical_elements = configdb.get_optical_elements( instrument_type) for i, instrument_config in enumerate(data['instrument_configs']): # Validate the readout mode and the binning. Readout modes and binning are tied # together- If one is set, we can determine the other. # TODO: Remove the binning checks when binnings are removed entirely readout_mode = instrument_config.get('mode', '') if not readout_mode and 'bin_x' in instrument_config: # A binning is set already - figure out what the readout mode should be from that try: readout_mode = instrument_config[ 'mode'] = configdb.get_readout_mode_with_binning( instrument_type, instrument_config['bin_x'])['code'] except ConfigDBException as cdbe: raise serializers.ValidationError(_(str(cdbe))) readout_validation_helper = ModeValidationHelper( 'readout', instrument_type, modes['readout']) instrument_config = readout_validation_helper.validate( instrument_config) data['instrument_configs'][i] = instrument_config # Validate the rotator modes if 'rotator' in modes: rotator_mode_validation_helper = ModeValidationHelper( 'rotator', instrument_type, modes['rotator'], mode_key='rotator_mode') instrument_config = rotator_mode_validation_helper.validate( instrument_config) data['instrument_configs'][i] = instrument_config # Check that the optical elements specified are valid in configdb for oe_type, value in instrument_config.get( 'optical_elements', {}).items(): plural_type = '{}s'.format(oe_type) if plural_type not in available_optical_elements: raise serializers.ValidationError( _("optical_element of type {} is not available on {} instruments" .format(oe_type, data['instrument_type']))) available_elements = { element['code'].lower(): element['code'] for element in available_optical_elements[plural_type] } if plural_type in available_optical_elements and value.lower( ) not in available_elements.keys(): raise serializers.ValidationError( _("optical element {} of type {} is not available". format(value, oe_type))) else: instrument_config['optical_elements'][ oe_type] = available_elements[value.lower()] # Also check that any optical element group in configdb is specified in the request unless we are a BIAS or # DARK or SCRIPT type observation observation_types_without_oe = ['BIAS', 'DARK', 'SCRIPT'] if data['type'].upper() not in observation_types_without_oe: for oe_type in available_optical_elements.keys(): singular_type = oe_type[:-1] if oe_type.endswith( 's') else oe_type if singular_type not in instrument_config.get( 'optical_elements', {}): raise serializers.ValidationError( _(f'Must set optical element of type {singular_type} for instrument type {instrument_type}' )) # Validate any regions of interest if 'rois' in instrument_config: max_rois = configdb.get_max_rois(instrument_type) ccd_size = configdb.get_ccd_size(instrument_type) if len(instrument_config['rois']) > max_rois: raise serializers.ValidationError( _(f'Instrument type {instrument_type} supports up to {max_rois} regions of interest' )) for roi in instrument_config['rois']: if 'x1' not in roi and 'x2' not in roi and 'y1' not in roi and 'y2' not in roi: raise serializers.ValidationError( _('Must submit at least one bound for a region of interest' )) if 'x1' not in roi: roi['x1'] = 0 if 'x2' not in roi: roi['x2'] = ccd_size['x'] if 'y1' not in roi: roi['y1'] = 0 if 'y2' not in roi: roi['y2'] = ccd_size['y'] if roi['x1'] >= roi['x2'] or roi['y1'] >= roi['y2']: raise serializers.ValidationError( _('Region of interest pixels start must be less than pixels end' )) if roi['x2'] > ccd_size['x'] or roi['y2'] > ccd_size['y']: raise serializers.ValidationError( _('Regions of interest for instrument type {} must be in range 0<=x<={} and 0<=y<={}' .format(instrument_type, ccd_size['x'], ccd_size['y']))) # Validate the exposure modes if 'exposure' in modes: exposure_mode_validation_helper = ModeValidationHelper( 'exposure', instrument_type, modes['exposure'], mode_key='exposure_mode', is_extra_param_mode=True) instrument_config = exposure_mode_validation_helper.validate( instrument_config) data['instrument_configs'][i] = instrument_config # This applies the exposure_time requirement only to non-muscat instruments. # I wish I could figure out a better way to do this, but it needs info about the instrument_type which is a level up. if instrument_type.upper() != '2M0-SCICAM-MUSCAT': if instrument_config.get('exposure_time', None) is None: raise serializers.ValidationError({ 'instrument_configs': [{ 'exposure_time': [_('This value cannot be null.')] }] }) if isnan(instrument_config.get('exposure_time')): raise serializers.ValidationError({ 'instrument_configs': [{ 'exposure_time': [_('A valid number is required.')] }] }) if instrument_config['exposure_time'] < 0: raise serializers.ValidationError({ 'instrument_configs': [{ 'exposure_time': [_('This value cannot be negative.')] }] }) if data['type'] == 'SCRIPT': if ('extra_params' not in data or 'script_name' not in data['extra_params'] or not data['extra_params']['script_name']): raise serializers.ValidationError( _('Must specify a script_name in extra_params for SCRIPT configuration type' )) # Validate duration is set if it's a REPEAT_* type configuration if 'REPEAT' in data['type']: if 'repeat_duration' not in data or data['repeat_duration'] is None: raise serializers.ValidationError( _(f'Must specify a configuration repeat_duration for {data["type"]} type configurations.' )) else: # Validate that the duration exceeds the minimum to run everything at least once min_duration = sum([ get_instrument_configuration_duration( ic, data['instrument_type']) for ic in data['instrument_configs'] ]) if min_duration > data['repeat_duration']: raise serializers.ValidationError( _(f'Configuration repeat_duration of {data["repeat_duration"]} is less than the minimum of ' f'{min_duration} required to repeat at least once')) else: if 'repeat_duration' in data and data[ 'repeat_duration'] is not None: raise serializers.ValidationError( _('You may only specify a repeat_duration for REPEAT_* type configurations.' )) # Validate the configuration type is available for the instrument requested if data['type'] not in configdb.get_configuration_types( instrument_type): raise serializers.ValidationError( _(f'configuration type {data["type"]} is not valid for instrument type {instrument_type}' )) return data