Skip to content

Commit

Permalink
Merge 1aef103 into e3a58a2
Browse files Browse the repository at this point in the history
  • Loading branch information
jnation3406 committed Dec 12, 2019
2 parents e3a58a2 + 1aef103 commit 2d1fee9
Show file tree
Hide file tree
Showing 13 changed files with 224 additions and 43 deletions.
8 changes: 8 additions & 0 deletions observation_portal/common/test_data/configdb.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
},
"configuration_types": [
"EXPOSE",
"REPEAT_EXPOSE",
"BIAS",
"DARK",
"SCRIPT",
Expand Down Expand Up @@ -171,6 +172,7 @@
},
"configuration_types": [
"EXPOSE",
"REPEAT_EXPOSE",
"BIAS",
"DARK",
"SCRIPT",
Expand Down Expand Up @@ -296,6 +298,7 @@
},
"configuration_types": [
"EXPOSE",
"REPEAT_EXPOSE",
"BIAS",
"DARK",
"SCRIPT",
Expand Down Expand Up @@ -443,6 +446,7 @@
},
"configuration_types": [
"SPECTRUM",
"REPEAT_SPECTRUM",
"LAMP_FLAT",
"ARC",
"SCRIPT"
Expand Down Expand Up @@ -599,6 +603,7 @@
},
"configuration_types": [
"EXPOSE",
"REPEAT_EXPOSE",
"BIAS",
"DARK",
"SCRIPT",
Expand Down Expand Up @@ -724,6 +729,7 @@
},
"configuration_types": [
"NRES_SPECTRUM",
"REPEAT_NRES_SPECTRUM",
"NRES_EXPOSE",
"NRES_TEST",
"LAMP_FLAT",
Expand Down Expand Up @@ -855,6 +861,7 @@
},
"configuration_types": [
"SPECTRUM",
"REPEAT_SPECTRUM",
"LAMP_FLAT",
"ARC",
"SCRIPT"
Expand Down Expand Up @@ -1010,6 +1017,7 @@
},
"configuration_types": [
"EXPOSE",
"REPEAT_EXPOSE",
"BIAS",
"DARK",
"SCRIPT",
Expand Down
6 changes: 5 additions & 1 deletion observation_portal/requestgroups/duration_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ def get_configuration_duration(configuration_dict):
)} for ic in configuration_dict['instrument_configs']
]
conf_duration['instrument_configs'] = instrumentconf_durations
conf_duration['duration'] = sum([icd['duration'] for icd in instrumentconf_durations])
if ('REPEAT' in configuration_dict['type'] and 'repeat_duration' in configuration_dict
and configuration_dict['repeat_duration'] is not None):
conf_duration['duration'] = configuration_dict['repeat_duration']
else:
conf_duration['duration'] = sum([icd['duration'] for icd in instrumentconf_durations])
conf_duration['duration'] += (PER_CONFIGURATION_GAP + PER_CONFIGURATION_STARTUP_TIME)
return conf_duration

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 2.2.4 on 2019-11-23 02:33

import django.core.validators
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('requestgroups', '0011_auto_20190702_2014'),
]

operations = [
migrations.AddField(
model_name='configuration',
name='repeat_duration',
field=models.FloatField(blank=True, help_text='The requested duration for this configuration to be repeated within. Only applicable to REPEAT_* type configurations.', null=True, validators=[django.core.validators.MinValueValidator(0.0)], verbose_name='configuration duration'),
),
migrations.AlterField(
model_name='configuration',
name='type',
field=models.CharField(choices=[('EXPOSE', 'EXPOSE'), ('REPEAT_EXPOSE', 'REPEAT_EXPOSE'), ('SKY_FLAT', 'SKY_FLAT'), ('STANDARD', 'STANDARD'), ('ARC', 'ARC'), ('LAMP_FLAT', 'LAMP_FLAT'), ('SPECTRUM', 'SPECTRUM'), ('REPEAT_SPECTRUM', 'REPEAT_SPECTRUM'), ('AUTO_FOCUS', 'AUTO_FOCUS'), ('TRIPLE', 'TRIPLE'), ('NRES_TEST', 'NRES_TEST'), ('NRES_SPECTRUM', 'NRES_SPECTRUM'), ('REPEAT_NRES_SPECTRUM', 'REPEAT_NRES_SPECTRUM'), ('NRES_EXPOSE', 'NRES_EXPOSE'), ('NRES_DARK', 'NRES_DARK'), ('NRES_BIAS', 'NRES_BIAS'), ('ENGINEERING', 'ENGINEERING'), ('SCRIPT', 'SCRIPT'), ('BIAS', 'BIAS'), ('DARK', 'DARK')], help_text='The type of exposures for the observations under this Configuration', max_length=50),
),
]
13 changes: 13 additions & 0 deletions observation_portal/requestgroups/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,15 +325,18 @@ def __str__(self):
class Configuration(models.Model):
TYPES = (
('EXPOSE', 'EXPOSE'),
('REPEAT_EXPOSE', 'REPEAT_EXPOSE'),
('SKY_FLAT', 'SKY_FLAT'),
('STANDARD', 'STANDARD'),
('ARC', 'ARC'),
('LAMP_FLAT', 'LAMP_FLAT'),
('SPECTRUM', 'SPECTRUM'),
('REPEAT_SPECTRUM', 'REPEAT_SPECTRUM'),
('AUTO_FOCUS', 'AUTO_FOCUS'),
('TRIPLE', 'TRIPLE'),
('NRES_TEST', 'NRES_TEST'),
('NRES_SPECTRUM', 'NRES_SPECTRUM'),
('REPEAT_NRES_SPECTRUM', 'REPEAT_NRES_SPECTRUM'),
('NRES_EXPOSE', 'NRES_EXPOSE'),
('NRES_DARK', 'NRES_DARK'),
('NRES_BIAS', 'NRES_BIAS'),
Expand All @@ -360,6 +363,16 @@ class Configuration(models.Model):
max_length=50, choices=TYPES,
help_text='The type of exposures for the observations under this Configuration'
)

repeat_duration = models.FloatField(
verbose_name='configuration duration',
blank=True,
null=True,
validators=[MinValueValidator(0.0)],
help_text='The requested duration for this configuration to be repeated within. '
'Only applicable to REPEAT_* type configurations.'
)

extra_params = JSONField(
default=dict,
blank=True,
Expand Down
3 changes: 3 additions & 0 deletions observation_portal/requestgroups/request_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,18 @@

MOLECULE_TYPE_DISPLAY = {
'EXPOSE': 'Imaging',
'REPEAT_EXPOSE': 'Repeat Imaging',
'SKY_FLAT': 'Sky Flat',
'STANDARD': 'Standard',
'ARC': 'Arc',
'LAMP_FLAT': 'Lamp Flat',
'SPECTRUM': 'Spectrum',
'REPEAT_SPECTRUM': 'Repeat Spectrum',
'AUTO_FOCUS': 'Auto Focus',
'TRIPLE': 'Triple',
'NRES_TEST': 'NRES Test',
'NRES_SPECTRUM': 'NRES Spectrum',
'REPEAT_NRES_SPECTRUM': 'Repeat NRES Spectrum',
'NRES_EXPOSE': 'NRES Expose',
'ENGINEERING': 'Engineering',
'SCRIPT': 'Script'
Expand Down
31 changes: 31 additions & 0 deletions observation_portal/requestgroups/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,14 @@ class Meta:
exclude = Configuration.SERIALIZER_EXCLUDE
read_only_fields = ('priority',)

def to_representation(self, instance):
data = super().to_representation(instance)
# Only return the repeat duration if its a REPEAT type configuration
if 'REPEAT' not in data.get('type') and 'repeat_duration' in data:
del data['repeat_duration']

return data

def validate_instrument_configs(self, value):
if [instrument_config.get('fill_window', False) for instrument_config in value].count(True) > 1:
raise serializers.ValidationError(_('Only one instrument_config can have `fill_window` set'))
Expand Down Expand Up @@ -423,6 +431,29 @@ def validate(self, data):
'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(_(
Expand Down
51 changes: 50 additions & 1 deletion observation_portal/requestgroups/test/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,12 @@ def setUp(self):
self.configuration_spectrum_2 = mixer.blend(Configuration, instrument_type='2M0-FLOYDS-SCICAM', type='SPECTRUM')
self.configuration_arc = mixer.blend(Configuration, instrument_type='2M0-FLOYDS-SCICAM', type='ARC')
self.configuration_lampflat = mixer.blend(Configuration, instrument_type='2M0-FLOYDS-SCICAM', type='LAMP_FLAT')
self.configuration_repeat_expose = mixer.blend(Configuration, instrument_type='1M0-SCICAM-SBIG',
type='REPEAT_EXPOSE', repeat_duration=500)

configurations = [self.configuration_expose, self.configuration_spectrum, self.configuration_arc,
self.configuration_lampflat, self.configuration_expose_2, self.configuration_expose_3,
self.configuration_spectrum_2]
self.configuration_spectrum_2, self.configuration_repeat_expose]

mixer.cycle(len(configurations)).blend(AcquisitionConfig, configuration=(c for c in configurations))
mixer.cycle(len(configurations)).blend(GuidingConfig, configuration=(c for c in configurations))
Expand Down Expand Up @@ -149,6 +151,10 @@ def setUp(self):
InstrumentConfig, bin_x=1, bin_y=1, exposure_time=60, exposure_count=1,
optical_elements={'slit': 'slit_1.6as'}, configuration=self.configuration_lampflat
)
self.instrument_config_repeat_expose = mixer.blend(
InstrumentConfig, bin_x=2, bin_y=2, exposure_time=30, exposure_count=1,
optical_elements={'filter': 'b'}, mode='1m0_sbig_2', configuration=self.configuration_repeat_expose
)

self.instrument_change_overhead_1m = 0
self.minimum_slew_time = 2
Expand Down Expand Up @@ -230,6 +236,49 @@ def test_ccd_multiple_instrument_configuration_request_duration(self):

self.assertEqual(duration, math.ceil(exp_1_duration + exp_2_duration + exp_3_duration + self.sbig_front_padding + num_filter_changes*self.sbig_filter_optical_element_change_overhead + (PER_CONFIGURATION_GAP + PER_CONFIGURATION_STARTUP_TIME + self.minimum_slew_time)))

def test_single_repeat_configuration_duration(self):
self.configuration_repeat_expose.request = self.request
self.configuration_repeat_expose.save()

configuration_duration = self.configuration_repeat_expose.repeat_duration + PER_CONFIGURATION_GAP + PER_CONFIGURATION_STARTUP_TIME
self.assertEqual(configuration_duration, self.configuration_repeat_expose.duration)

mixer.blend(
InstrumentConfig, bin_x=2, bin_y=2, exposure_time=30, exposure_count=1,
optical_elements={'filter': 'g'}, mode='1m0_sbig_2', configuration=self.configuration_repeat_expose
)
mixer.blend(
InstrumentConfig, bin_x=2, bin_y=2, exposure_time=30, exposure_count=1,
optical_elements={'filter': 'r'}, mode='1m0_sbig_2'
)
# configuration duration is unchanged after adding instrument configs
self.assertEqual(configuration_duration, self.configuration_repeat_expose.duration)

def test_repeat_configuration_multi_config_request_duration(self):
self.configuration_repeat_expose.request = self.request
self.configuration_repeat_expose.save()
self.configuration_expose.request = self.request
self.configuration_expose.save()
self.configuration_expose_2.request = self.request
self.configuration_expose_2.save()
self.instrument_config_expose_1.configuration = self.configuration_expose
self.instrument_config_expose_1.save()
self.instrument_config_expose_2.configuration = self.configuration_expose_2
self.instrument_config_expose_2.save()

exp_time1 = self.instrument_config_expose_1.exposure_time
exp_count1 = self.instrument_config_expose_1.exposure_count
exp_time2 = self.instrument_config_expose_2.exposure_time
exp_count2 = self.instrument_config_expose_2.exposure_count
exp_1_duration = exp_count1 * (exp_time1 + self.sbig_readout_time2 + self.sbig_fixed_overhead_per_exposure)
exp_2_duration = exp_count2 * (exp_time2 + self.sbig_readout_time2 + self.sbig_fixed_overhead_per_exposure)
repeat_config_duration = self.configuration_repeat_expose.repeat_duration
num_configurations = 3
num_filter_changes = 2
duration = self.request.duration

self.assertEqual(duration, math.ceil(exp_1_duration + exp_2_duration + repeat_config_duration + self.sbig_front_padding + num_configurations*(PER_CONFIGURATION_GAP + PER_CONFIGURATION_STARTUP_TIME + self.minimum_slew_time) + num_filter_changes*self.sbig_filter_optical_element_change_overhead))

def test_ccd_multiple_configuration_request_duration(self):
self.instrument_config_expose_1.configuration = self.configuration_expose
self.instrument_config_expose_1.save()
Expand Down
32 changes: 32 additions & 0 deletions observation_portal/requestgroups/test/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1379,6 +1379,38 @@ def test_readout_mode_not_set_and_no_default_but_single_option_set(self):
# The only readout mode for floyds is 2m0_floyds_1
self.assertEqual(response.json()['requests'][0]['configurations'][0]['instrument_configs'][0]['mode'], '2m0_floyds_1')

def test_repeat_exposure_rejected_without_configuration_duration_set(self):
bad_data = self.generic_payload.copy()
bad_data['requests'][0]['configurations'][0]['type'] = 'REPEAT_EXPOSE'
response = self.client.post(reverse('api:request_groups-list'), data=bad_data)
self.assertEqual(response.status_code, 400)
self.assertIn('Must specify a configuration repeat_duration for REPEAT_EXPOSE type configurations.',
str(response.content))

def test_repeat_duration_rejected_for_non_repeat_exposure(self):
bad_data = self.generic_payload.copy()
bad_data['requests'][0]['configurations'][0]['repeat_duration'] = 500.0
response = self.client.post(reverse('api:request_groups-list'), data=bad_data)
self.assertEqual(response.status_code, 400)
self.assertIn('You may only specify a repeat_duration for REPEAT_* type configurations.',
str(response.content))

def test_repeat_exposure_accepted_when_configuration_duration_fits_instrument_configs(self):
good_data = self.generic_payload.copy()
good_data['requests'][0]['configurations'][0]['type'] = 'REPEAT_EXPOSE'
good_data['requests'][0]['configurations'][0]['repeat_duration'] = 500.0
response = self.client.post(reverse('api:request_groups-list'), data=good_data)
self.assertEqual(response.status_code, 201)

def test_repeat_exposure_rejected_when_configuration_duration_too_small(self):
bad_data = self.generic_payload.copy()
bad_data['requests'][0]['configurations'][0]['type'] = 'REPEAT_EXPOSE'
bad_data['requests'][0]['configurations'][0]['repeat_duration'] = 1.0
response = self.client.post(reverse('api:request_groups-list'), data=bad_data)
self.assertEqual(response.status_code, 400)
self.assertIn('Configuration repeat_duration of 1.0 is less than the minimum',
str(response.content))

def test_many_missing_required_fields(self):
bad_data = self.generic_payload.copy()
bad_data['requests'][0]['configurations'][0]['instrument_type'] = '1M0-NRES-SCICAM'
Expand Down
4 changes: 2 additions & 2 deletions static/js/components/archive.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@
},
computed: {
guiLink: function() {
return archiveUIRoot + '?OBSTYPE=EXPOSE&start=2014-05-01&covers=POINT(' + this.ra + ' ' + this.dec +')';
return archiveUIRoot + '?OBSTYPE=EXPOSE&OBSTYPE=REPEAT_EXPOSE&start=2014-05-01&covers=POINT(' + this.ra + ' ' + this.dec +')';
}
},
methods:{
setResultCount: _.debounce(function() {
let that = this;
$.getJSON(archiveRoot + 'frames/?OBSTYPE=EXPOSE&covers=POINT(' + that.ra + ' ' + that.dec +')', function(data) {
$.getJSON(archiveRoot + 'frames/?OBSTYPE=EXPOSE&OBSTYPE=REPEAT_EXPOSE&covers=POINT(' + that.ra + ' ' + that.dec +')', function(data) {
that.resultCount = data.count;
});
}, 500)
Expand Down

0 comments on commit 2d1fee9

Please sign in to comment.