"""
desispec.scripts.createoverride
=================================
"""
import yaml
import os
import time
import numpy as np
from desispec.io.meta import findfile
[docs]def valid_night(night):
"""
Test whether or not the input night is valid for a YEARMMDD DESI night or not.
Args:
night, str, 8 char YEARMMDD.
Returns:
bool, True if date is consistent.
NOTE: Theres a Y2K-esque bug here for dates before 2013 and after 2033
"""
isvalid = night.isnumeric() \
and np.abs(int(night[:4]) - 2023) < 10 \
and np.abs(int(night[4:6]) - 6.5) < 6 \
and np.abs(int(night[6:]) - 16) < 15
if not isvalid:
print(f"--> Received invalid response: '{night}'. Must be YEARMMDD.")
return isvalid
[docs]def valid_yes_no(string):
"""
Test whether or not the input is valid for a yes/no answer
Args:
string, str, string to test.
Returns:
bool, True if input string starts with 'y' or 'n'.
"""
string = string.lower()
isvalid = len(string) > 0 and string[0] in ['y', 'n']
if not isvalid:
print(f"--> Received invalid response: '{string}'. Must be 'y' or 'n'.")
return isvalid
[docs]def get_response(input_question, validation_func):
"""
Keep requesting inputs until a valid response is given
Args:
input_question, str, question to prompt to the user.
validation_func, function, function that takes a string as input and
returns a boolean representing whether the string is a valid response
or not.
Returns:
response, str, the string provided by the user that passes validation.
"""
good_format = False
response = None
while not good_format:
response = input(input_question)
good_format = validation_func(response)
print(f"--> Received valid response: {response}")
return response
[docs]def get_night(prompt):
"""
Prompt the user for a night and return the night as an integer
"""
night = get_response(prompt, valid_night)
return int(night)
[docs]def is_yes(prompt):
"""
Prompt the user with a yes/no question and return a boolean yes<->True
"""
response = get_response(prompt, valid_yes_no)
return str(response)[0] == 'y'
[docs]def create_override_file(args):
"""
Create an override file with the given inputs. If no inputs given, prompt
the user for all necessary information.
"""
if args.night is None:
night = get_night("What night is it? ")
else:
night = int(args.night)
outdict = {'calibration': dict()}
if args.linkcal is None:
linkcal = is_yes("Does this night require cal linking? ")
else:
linkcal = args.linkcal
if linkcal:
refnight = get_night("What is the reference night? ")
good_bias = is_yes("Are there valid zeros for biases? ")
good_cte = is_yes("Are there valid falts for cte corections? ")
good_badcol = is_yes("Is there a valid dark for badcolumn detection? ")
good_psf = is_yes("Are there valid arcs for psf generation? ")
good_flats = is_yes("Are there valid flats for "
+ "fiberflatnight generation? ")
if not good_psf and good_flats:
print("Since good_psf is False, we cannot use the flats. "
+ "Setting good_flats to False")
good_flats = False
if good_bias and good_cte and good_badcol and good_psf and good_flats:
print("WARNING: All calibrations given as good for the current "
+ "night, so *NOT* linking calibrations in the override file.")
linkcal = False
else:
outdict['calibration'] = {'linkcal': dict()}
linkcal_include_list, goods, bads = list(), list(), list()
if not good_bias:
linkcal_include_list.append('biasnight')
bads.append('bias')
else:
goods.append('bias')
if not good_cte:
linkcal_include_list.append('ctecorrnight')
bads.append('cte flats')
else:
goods.append('cte flats')
if not good_badcol:
linkcal_include_list.append('badcolumns')
bads.append('badcols')
else:
goods.append('badcols')
if not good_psf:
linkcal_include_list.append('psfnight')
bads.append('psfs')
else:
goods.append('psfs')
if not good_flats:
linkcal_include_list.append('fiberflatnight')
bads.append('flats')
else:
goods.append('flats')
linkcal_include = ','.join(linkcal_include_list)
outdict['calibration']['linkcal'] = {'refnight': refnight,
'include': linkcal_include}
if args.ff_solve_grad is None:
ff_solve_grad = is_yes("Do we need to set --solve-gradient in autocalib_fiberflat? ")
else:
ff_solve_grad = args.ff_solve_grad
if ff_solve_grad:
print("\nNOTE: Check that reference fiberflats in $DESI_SPECTRO_CALIB"
+ f" are valid for use in gradient correction on {night}.\n")
outdict['calibration']['nightlyflat'] = {'extra_cmd_args':
['--autocal-ff-solve-grad']}
if not linkcal and not ff_solve_grad:
print("There is nothing to be set in an override "
+ "file, so not creating one.")
else:
pathname = findfile('override', night=night)
dirname = os.path.dirname(pathname)
if not os.path.exists(dirname):
print(f"{dirname} doesn't exist. Creating directory.")
os.makedirs(dirname)
if os.path.exists(pathname):
timestamp = time.strftime('%Y%m%d_%Hh%Mm')
archivepathname = pathname.replace('.yaml', f".{timestamp}.yaml")
print(f"\nWARNING: {pathname} exists. Moving that to "
+ f"{archivepathname}.\n")
os.rename(pathname, archivepathname)
with open(pathname, 'w') as fil:
fil.write(f"# DESI override file for {night}\n")
if linkcal:
goodstr = ','.join(goods)
badstr = ','.join(bads)
fil.write(f"# linkcal notes: current night's {goodstr} were ok; {badstr} were bad\n")
if ff_solve_grad:
fil.write(f"# nightlyflat notes: using '--autocal-ff-solve-grad'\n")
fil.write("\n")
## Write out the yaml portion in proper format
## default_flow_style is set to None to get bracketed list ([]) but that also does
## curly brackets in linkcal, so avoid that if doing linkcal
if linkcal:
yaml.safe_dump(outdict, fil)
else:
yaml.safe_dump(outdict, fil, default_flow_style=None)
print(f"\nWrote override file to: {pathname}")