diff --git a/observation_portal/blocks/conversion.py b/observation_portal/blocks/conversion.py index 60bcf0d4..0ddd1b99 100644 --- a/observation_portal/blocks/conversion.py +++ b/observation_portal/blocks/conversion.py @@ -113,19 +113,19 @@ def convert_pond_block_to_observation(block): config_extra_params['self_guiding'] = True instrument_extra_params = {} - if 'rot_angle' in pointing and pointing['rot_angle'] is not None and float(pointing['rot_angle']): + if pointing.get('rot_angle') not in [None, ''] and float(pointing['rot_angle']): instrument_extra_params['rotator_angle'] = float(pointing['rot_angle']) - if 'defocus' in molecule and float(molecule['defocus']): + if molecule.get('defocus') not in [None, ''] and float(molecule['defocus']): instrument_extra_params['defocus'] = float(molecule['defocus']) if 'expmeter_mode' in molecule and not molecule['expmeter_mode'] in ['OFF', 'EXPMETER_OFF']: instrument_extra_params['expmeter_mode'] = molecule['expmeter_mode'] - if 'expmeter_snr' in molecule and float(molecule['expmeter_snr']): + if molecule.get('expmeter_snr') not in [None, ''] and float(molecule['expmeter_snr']): instrument_extra_params['expmeter_snr'] = float(molecule['expmeter_snr']) instrument_optical_elements = {} - if molecule.get('filter') and 'NRES' not in block.get('instrument_class').upper(): + if molecule.get('filter') and 'NRES' not in str(block.get('instrument_class')).upper(): instrument_optical_elements['filter'] = molecule['filter'] - if molecule.get('spectra_slit') and 'NRES' not in block.get('instrument_class').upper(): + if molecule.get('spectra_slit') and 'NRES' not in str(block.get('instrument_class')).upper(): instrument_optical_elements['slit'] = molecule['spectra_slit'] if molecule.get('spectra_lamp'): instrument_optical_elements['lamp'] = molecule['spectra_lamp'] @@ -133,7 +133,6 @@ def convert_pond_block_to_observation(block): instrument_configs = [{ 'mode': molecule.get('readout_mode', ''), 'rotator_mode': pointing.get('rot_mode', ''), - 'exposure_time': float(molecule.get('exposure_time', 0.01)), 'exposure_count': molecule.get('exposure_count', 1), 'bin_x': molecule.get('bin_x', 1), 'bin_y': molecule.get('bin_y', 1), @@ -141,6 +140,12 @@ def convert_pond_block_to_observation(block): 'extra_params': instrument_extra_params }] + if molecule.get('exposure_time') not in [None, '']: + exposure_time = float(molecule['exposure_time']) + else: + exposure_time = 0.01 + instrument_configs[0]['exposure_time'] = exposure_time + if molecule.get('sub_x1') and molecule.get('sub_x2') and molecule.get('sub_y1') and molecule.get('sub_y2'): instrument_configs[0]['rois'] = [{'x1': float(molecule['sub_x1']), 'x2': float(molecule['sub_x2']), @@ -156,18 +161,27 @@ def convert_pond_block_to_observation(block): ag_extra_params['ag_strategy'] = molecule['ag_strategy'] (guide_mode, guide_optional) = pond_ag_mode_to_guiding_mode(molecule.get('ag_mode', 'OPT')) - if block.get('instrument_class').upper() in ['1M0-NRES-SCICAM', '1M0-NRES-COMMISIONING', '2M0-FLOYDS-SCICAM']: - if molecule.get('type').upper() not in ['ARC', 'LAMP_FLAT']: + if str(block.get('instrument_class')).upper() in [ + '1M0-NRES-SCICAM', + '1M0-NRES-COMMISIONING', + '2M0-FLOYDS-SCICAM' + ]: + if str(molecule.get('type')).upper() not in ['ARC', 'LAMP_FLAT']: guide_optional = False guiding_config = { 'mode': guide_mode, 'optional': guide_optional, - 'exposure_time': float(molecule.get('ag_exp_time', 10)), 'optical_elements': ag_optical_elements, 'extra_params': ag_extra_params } + if molecule.get('ag_exp_time') not in [None, '']: + ag_exp_time = float(molecule['ag_exp_time']) + else: + ag_exp_time = 10 + guiding_config['exposure_time'] = ag_exp_time + acquire_mode = molecule.get('acquire_mode', 'OFF') acquire_extra_params = {} if not acquire_mode == 'OFF': @@ -183,7 +197,7 @@ def convert_pond_block_to_observation(block): 'extra_params': acquire_extra_params } - if float(molecule.get('acquire_exp_time', 0)): + if molecule.get('acquire_exp_time') not in [None, ''] and float(molecule['acquire_exp_time']): acquisition_config['exposure_time'] = float(molecule['acquire_exp_time']) configuration = { diff --git a/observation_portal/common/rise_set_utils.py b/observation_portal/common/rise_set_utils.py index 52b8d53c..c74172b0 100644 --- a/observation_portal/common/rise_set_utils.py +++ b/observation_portal/common/rise_set_utils.py @@ -151,6 +151,28 @@ def filter_out_downtime_from_intervalsets(intervalsets_by_telescope: dict) -> di return filtered_intervalsets_by_telescope +def get_proper_motion(target_dict): + # The proper motion fields are sometimes not present in HOUR_ANGLE targets + pm = {'pmra': None, 'pmdec': None} + if 'proper_motion_ra' in target_dict and target_dict['proper_motion_ra'] is not None: + pm['pmra'] = ProperMotion( + Angle( + degrees=(target_dict['proper_motion_ra'] / 1000.0 / cos(radians(target_dict['dec']))) / 3600.0, + units='arc' + ), + time='year' + ) + if 'proper_motion_dec' in target_dict and target_dict['proper_motion_dec'] is not None: + pm['pmdec'] = ProperMotion( + Angle( + degrees=(target_dict['proper_motion_dec'] / 1000.0) / 3600.0, + units='arc' + ), + time='year' + ) + return pm + + def get_rise_set_target(target_dict): # This is a hack to protect against poorly formatted or empty targets that are submitted directly. # TODO: Remove this check when target models are updated to handle when there is no target @@ -164,14 +186,13 @@ def get_rise_set_target(target_dict): dec=Angle(degrees=0), ) if target_dict['type'] in ['ICRS', 'HOUR_ANGLE']: - pmra = (target_dict['proper_motion_ra'] / 1000.0 / cos(radians(target_dict['dec']))) / 3600.0 - pmdec = (target_dict['proper_motion_dec'] / 1000.0) / 3600.0 + pm = get_proper_motion(target_dict) if target_dict['type'] == 'ICRS': return make_ra_dec_target( ra=Angle(degrees=target_dict['ra']), dec=Angle(degrees=target_dict['dec']), - ra_proper_motion=ProperMotion(Angle(degrees=pmra, units='arc'), time='year'), - dec_proper_motion=ProperMotion(Angle(degrees=pmdec, units='arc'), time='year'), + ra_proper_motion=pm['pmra'], + dec_proper_motion=pm['pmdec'], parallax=target_dict['parallax'], rad_vel=0.0, epoch=target_dict['epoch'] @@ -180,8 +201,8 @@ def get_rise_set_target(target_dict): return make_hour_angle_target( hour_angle=Angle(degrees=target_dict['hour_angle']), dec=Angle(degrees=target_dict['dec']), - ra_proper_motion=ProperMotion(Angle(degrees=pmra, units='arc'), time='year'), - dec_proper_motion=ProperMotion(Angle(degrees=pmdec, units='arc'), time='year'), + ra_proper_motion=pm['pmra'], + dec_proper_motion=pm['pmdec'], parallax=target_dict['parallax'], epoch=target_dict['epoch'] ) diff --git a/observation_portal/requestgroups/duration_utils.py b/observation_portal/requestgroups/duration_utils.py index 11c857db..89620689 100644 --- a/observation_portal/requestgroups/duration_utils.py +++ b/observation_portal/requestgroups/duration_utils.py @@ -169,7 +169,11 @@ def get_request_duration(request_dict): duration += change_overhead # Now add in the slew time between targets (configurations). Only Sidereal can be calculated based on position. - if not previous_target or 'ra' not in previous_target or 'ra' not in configuration['target']: + if ( + not previous_target + or previous_target['type'].upper() != 'ICRS' + or configuration['target']['type'].upper() != 'ICRS' + ): duration += request_overheads['maximum_slew_overhead'] elif previous_target != configuration['target']: duration += min(max(get_slew_distance(previous_target, configuration['target'], start_time) diff --git a/observation_portal/requestgroups/serializers.py b/observation_portal/requestgroups/serializers.py index 54d6c9ae..f4968cf5 100644 --- a/observation_portal/requestgroups/serializers.py +++ b/observation_portal/requestgroups/serializers.py @@ -510,17 +510,10 @@ def validate_configurations(self, value): if not value: raise serializers.ValidationError(_('You must specify at least 1 configuration')) - target = value[0]['target'] constraints = value[0]['constraints'] # Set the relative priority of molecules in order for i, configuration in enumerate(value): configuration['priority'] = i + 1 - # TODO: Remove this once we support multiple targets/constraints - if configuration['target'] != target: - raise serializers.ValidationError(_( - 'Currently only a single target per Request is supported. This restriction will be lifted in ' - 'the future.' - )) if configuration['constraints'] != constraints: raise serializers.ValidationError(_( 'Currently only a single constraints per Request is supported. This restriction will be ' @@ -726,12 +719,20 @@ def validate(self, data): # Don't do any time accounting stuff if it is a directly scheduled observation return data else: - # for non-DIRECT observations, don't allow HOUR_ANGLE targets for request in data['requests']: + target = request['configurations'][0]['target'] for config in request['configurations']: + # for non-DIRECT observations, don't allow HOUR_ANGLE targets if config['target']['type'] == 'HOUR_ANGLE': raise serializers.ValidationError(_('HOUR_ANGLE Target type not supported in scheduled observations')) + # For non-DIRECT observations, only allow a single target + # TODO: Remove this check once we support multiple targets/constraints + if config['target'] != target: + raise serializers.ValidationError(_( + 'Currently only a single target per Request is supported. This restriction will be lifted ' + 'in the future.' + )) try: total_duration_dict = get_total_duration_dict(data) for tak, duration in total_duration_dict.items():