-
Notifications
You must be signed in to change notification settings - Fork 106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable users to run and re-run one step of Calibrations #1877
base: develop
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @profxj . Thanks for doing this. I have a few comments (minor), but I also tested the code and works great.
We should run the test.
|
||
self.calib_one(grp_frames, self.det) | ||
|
||
# # Instantiate Calibrations class |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The commented lines can be removed
'remake' -- Force the frame to be remade. | ||
'reload' -- Reload the frame if it exists. | ||
None -- Load the existing frame if it exists and reuse_calibs=True | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like the force
if loop is missing here (line 499)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed.
@@ -793,7 +865,8 @@ def get_flats(self): | |||
calib_key = illum_calib_key if pixel_calib_key is None else pixel_calib_key | |||
setup = illum_setup if pixel_setup is None else pixel_setup | |||
calib_id = illum_calib_id if pixel_calib_id is None else pixel_calib_id | |||
if cal_file.exists() and self.reuse_calibs: | |||
|
|||
if cal_file.exists() and self.reuse_calibs and not force == 'remake': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the force=reload
option missing here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oof. I forgot how complicated the flat file is...
if stop_at_step is not None and step == stop_at_step: | ||
force = 'remake' | ||
else: | ||
force = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't seem force=reload
is actually an option. Should we remove it from the if loop in each step?
parser.add_argument('pypeit_file', type=str, | ||
help='PypeIt reduction file (must have .pypeit extension)') | ||
parser.add_argument('frame', type=str, help='Raw science frame to reduce as listed in your PypeIt file, e.g. b28.fits.gz') | ||
parser.add_argument('step', type=str, help='Calibration step to perform') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to provide the list of possible steps here. Or, we could add another parameter, e.g. --list_steps
, which will list all the default steps for that Calibrations class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good suggestion. I would define a class attribute:
valid_steps = ['align', 'arc', 'bias', 'bpm', 'dark', 'flats', 'scattlight', 'slits', 'tiltimg', 'tilts', 'wv_calib']
or a class method:
@classmethod
def valid_steps(cls):
steps = [method.replace('get_', '') for method in dir(cls) \
if callable(getattr(cls, method)) and method.startswith('get_')]
steps.remove('instance')
steps.remove('association')
return steps
Then here, you would just do:
from pypeit.calibrations import Calibrations
parser.add_argument('step', type=str, help=f'Calibration step to perform. Valid steps are {', '.join(Calibrations.valid_steps)}')
with or without the ()
for valid_steps
, depending on the implementation you choose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few relatively minor comments from me.
elif force == 'reload' and not cal_file.exists(): | ||
self.success = False | ||
return | ||
elif force == 'reload' or (self.reuse_calibs and cal_file.exists()): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recommend abstracting this a bit. I'd add this member function:
def process_load_selection(self, frame, cal_file, force):
"""
Process how pypeit should use any pre-existing calibration files.
If loading is requested but the calibration file (``cal_file``) does
not exist, ``self.success`` is set to False, and None is returned.
Args:
frame (:obj:`dict`):
A dictionary with two elements: ``type`` is the string
defining the frame type and ``class`` is the pypeit class
used to load the pre-existing calibration file.
cal_file (:obj:`str`, `Path`_):
Path to the calibration file.
force (:obj:`str`):
Defines how to treat a pre-existing calibration file. Must
be one of the following options:
- ``'remake'``: Force the calibration be remade.
- ``'reload'``: Reload the frame if it exists.
- ``None``: Load the existing frame if it exists and
``self.reuse_calibs=True``.
Returns:
:obj:`object`: Either the loaded calibration object or None.
"""
if force not in [None, 'remake', 'reload']:
msgs.error(f'`force` keyword must be None, remake, or reload, not {force}')
if force == 'remake':
return None
_cal_file = Path(cal_file).absolute()
if force == 'reload' and not _cal_file.exists():
msgs.warn(f"{_cal_file} does not exist; cannot reload "
f"{frame['class'].__name__} calibration.")
self.success = False
return None
if force == 'reload' or (self.reuse_calibs and _cal_file.exists()):
return = frame['class'].from_file(_cal_file, chk_version=self.chk_version)
And then the lines within each get_*
function would replace this block with:
self.?? = self.process_load_selection(frame, cal_file, force)
if not self.success or self.?? is not None:
return self.??
where ??
is msarc
in this function.
'remake' -- Force the frame to be remade. | ||
'reload' -- Reload the frame if it exists. | ||
None -- Load the existing frame if it exists and reuse_calibs=True | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed.
""" | ||
Load or generate the bad pixel mask. | ||
|
||
This is primarily a wrapper for | ||
:func:`~pypeit.spectrographs.spectrograph.Spectrograph.bpm`. | ||
|
||
Args: | ||
force (:obj:`str`, optional): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adding this. Also add a description of frame
?
@@ -793,7 +865,8 @@ def get_flats(self): | |||
calib_key = illum_calib_key if pixel_calib_key is None else pixel_calib_key | |||
setup = illum_setup if pixel_setup is None else pixel_setup | |||
calib_id = illum_calib_id if pixel_calib_id is None else pixel_calib_id | |||
if cal_file.exists() and self.reuse_calibs: | |||
|
|||
if cal_file.exists() and self.reuse_calibs and not force == 'remake': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oof. I forgot how complicated the flat file is...
@@ -1099,7 +1189,7 @@ def get_wv_calib(self): | |||
'calibration! Please generate a new one with a 2d fit.') | |||
|
|||
# Return | |||
if self.par['wavelengths']['redo_slits'] is None: | |||
if (self.par['wavelengths']['redo_slits'] is None) or self.try_reload_only: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is try_reload_only
defined?
""" | ||
Return the name of the executable. | ||
""" | ||
return 'pypeit_run_to_calibstep' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need to define name
. The default in the base class results in the same thing.
import argparse | ||
|
||
parser = super().get_parser(description='Run PypeIt to a single calibration step for an input frame', | ||
width=width, formatter=argparse.RawDescriptionHelpFormatter) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you're better off with the default formatter; the reason this is used in run_pypeit.py
is so that the parser can handle the usage string.
parser.add_argument('pypeit_file', type=str, | ||
help='PypeIt reduction file (must have .pypeit extension)') | ||
parser.add_argument('frame', type=str, help='Raw science frame to reduce as listed in your PypeIt file, e.g. b28.fits.gz') | ||
parser.add_argument('step', type=str, help='Calibration step to perform') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good suggestion. I would define a class attribute:
valid_steps = ['align', 'arc', 'bias', 'bpm', 'dark', 'flats', 'scattlight', 'slits', 'tiltimg', 'tilts', 'wv_calib']
or a class method:
@classmethod
def valid_steps(cls):
steps = [method.replace('get_', '') for method in dir(cls) \
if callable(getattr(cls, method)) and method.startswith('get_')]
steps.remove('instance')
steps.remove('association')
return steps
Then here, you would just do:
from pypeit.calibrations import Calibrations
parser.add_argument('step', type=str, help=f'Calibration step to perform. Valid steps are {', '.join(Calibrations.valid_steps)}')
with or without the ()
for valid_steps
, depending on the implementation you choose.
splitnm = os.path.splitext(args.pypeit_file) | ||
if splitnm[1] != '.pypeit': | ||
msgs.error('Input file must have a .pypeit extension!') | ||
logname = splitnm[0] + ".log" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recommend that we avoid using os.path
for path manipulation in new code. I would change this to:
from pathlib import Path
_pypeit_file = Path(args.pypeit_file).absolute()
if _pypeit_file.suffix != '.pypeit':
msgs.error(f'Input file {_pypeit_file} must have a .pypeit extension!')
logname = _pypeit_file.parent / f'{_pypeit_file.stem}.log'
width=width, formatter=argparse.RawDescriptionHelpFormatter) | ||
parser.add_argument('pypeit_file', type=str, | ||
help='PypeIt reduction file (must have .pypeit extension)') | ||
parser.add_argument('frame', type=str, help='Raw science frame to reduce as listed in your PypeIt file, e.g. b28.fits.gz') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to allow the users to provide a calibration group or a science file? I understand why you're using the science file (it defines the fitstbl row and therefore the calibration group), but can we just provide the calibration group directly? It feels odd that we're requiring users provide a specific science frame for this script.
Related, can we also allow the user to specify the detector or mosaic?
A script and related to code to run to one step of the calibrations and stop.
This is mainly useful to re-run a bad calibration step.
Docs included and a test in the DevSuite in an accompanying PR.
Part of the motivation here is to enable a future Dashboard.