From cb52d46e34ac145bac15c5eed28dfdcf1ffdfa2b Mon Sep 17 00:00:00 2001 From: profxj Date: Mon, 24 Apr 2023 13:05:20 -0700 Subject: [PATCH 01/41] wip --- pypeit/core/wavecal/autoid.py | 115 ++++++++++++++++++++++++++++++-- pypeit/core/wavecal/patterns.py | 3 +- pypeit/wavecalib.py | 10 ++- 3 files changed, 120 insertions(+), 8 deletions(-) diff --git a/pypeit/core/wavecal/autoid.py b/pypeit/core/wavecal/autoid.py index 9ac51f6ac8..93e86e7bdd 100644 --- a/pypeit/core/wavecal/autoid.py +++ b/pypeit/core/wavecal/autoid.py @@ -284,9 +284,11 @@ def match_qa(arc_spec, tcent, line_list, IDs, scores, outfile = None, title=None -def reidentify(spec, spec_arxiv_in, wave_soln_arxiv_in, line_list, nreid_min, det_arxiv=None, detections=None, cc_thresh=0.8,cc_local_thresh = 0.8, - match_toler=2.0, nlocal_cc=11, nonlinear_counts=1e10,sigdetect=5.0,fwhm=4.0, - debug_xcorr=False, debug_reid=False, debug_peaks = False): +def reidentify(spec, spec_arxiv_in, wave_soln_arxiv_in, line_list, + nreid_min, det_arxiv=None, + detections=None, cc_thresh=0.8, cc_local_thresh=0.8, + match_toler=2.0, nlocal_cc=11, nonlinear_counts=1e10, + sigdetect=5.0, fwhm=4.0, debug_xcorr=False, debug_reid=False, debug_peaks = False): """ Determine a wavelength solution for a set of spectra based on archival wavelength solutions Parameters @@ -439,6 +441,7 @@ def reidentify(spec, spec_arxiv_in, wave_soln_arxiv_in, line_list, nreid_min, de wvc_arxiv = np.zeros(narxiv, dtype=float) disp_arxiv = np.zeros(narxiv, dtype=float) + # Determine the central wavelength and dispersion of wavelength arxiv for iarxiv in range(narxiv): wvc_arxiv[iarxiv] = wave_soln_arxiv[nspec//2, iarxiv] @@ -551,9 +554,13 @@ def reidentify(spec, spec_arxiv_in, wave_soln_arxiv_in, line_list, nreid_min, de patt_dict_slit['sigdetect'] = sigdetect return detections, spec_cont_sub, patt_dict_slit + if debug_reid: + embed(header='557 of autoid.py') # Finalize the best guess of each line - patt_dict_slit = patterns.solve_xcorr(detections, wvdata, det_indx, line_indx, line_cc,nreid_min=nreid_min,cc_local_thresh=cc_local_thresh) + patt_dict_slit = patterns.solve_xcorr( + detections, wvdata, det_indx, line_indx, line_cc, + nreid_min=nreid_min,cc_local_thresh=cc_local_thresh) patt_dict_slit['sign'] = sign # This is not used anywhere patt_dict_slit['bwv'] = np.median(wcen[wcen != 0.0]) patt_dict_slit['bdisp'] = np.median(disp[disp != 0.0]) @@ -592,6 +599,106 @@ def reidentify(spec, spec_arxiv_in, wave_soln_arxiv_in, line_list, nreid_min, de return detections, spec_cont_sub, patt_dict_slit +# TODO -- Move this to wvutils? +def match_to_arxiv(spec_arxiv:np.ndarray): + + nspec_arxiv, narxiv = spec_arxiv.shape + + # Cross-correlate with each arxiv spectrum to identify lines + line_indx = np.array([], dtype=int) + det_indx = np.array([], dtype=int) + line_cc = np.array([], dtype=float) + line_iarxiv = np.array([], dtype=int) + wcen = np.zeros(narxiv) + disp = np.zeros(narxiv) + shift_vec = np.zeros(narxiv) + stretch_vec = np.zeros(narxiv) + ccorr_vec = np.zeros(narxiv) + for iarxiv in range(narxiv): + msgs.info('Cross-correlating with arxiv slit # {:d}'.format(iarxiv)) + this_det_arxiv = det_arxiv[str(iarxiv)] + # Match the peaks between the two spectra. This code attempts to compute the stretch if cc > cc_thresh + success, shift_vec[iarxiv], stretch_vec[iarxiv], ccorr_vec[iarxiv], _, _ = \ + wvutils.xcorr_shift_stretch(spec_cont_sub, spec_arxiv[:, iarxiv], + cc_thresh=cc_thresh, fwhm=fwhm, seed=random_state, + debug=debug_xcorr) + # If cc < cc_thresh or if this optimization failed, don't reidentify from this arxiv spectrum + if success != 1: + continue + # Estimate wcen and disp for this slit based on its shift/stretch relative to the archive slit + disp[iarxiv] = disp_arxiv[iarxiv] / stretch_vec[iarxiv] + wcen[iarxiv] = wvc_arxiv[iarxiv] - shift_vec[iarxiv]*disp[iarxiv] + # For each peak in the arxiv spectrum, identify the corresponding peaks in the input spectrum. Do this by + # transforming these arxiv slit line pixel locations into the (shifted and stretched) input spectrum frame + det_arxiv_ss = this_det_arxiv*stretch_vec[iarxiv] + shift_vec[iarxiv] + spec_arxiv_ss = wvutils.shift_and_stretch(spec_arxiv[:, iarxiv], shift_vec[iarxiv], stretch_vec[iarxiv]) + + if debug_xcorr: + plt.figure(figsize=(14, 6)) + tampl_slit = np.interp(detections, xrng, spec_cont_sub) + plt.plot(xrng, spec_cont_sub, color='red', drawstyle='steps-mid', label='input arc',linewidth=1.0, zorder=10) + plt.plot(detections, tampl_slit, 'r.', markersize=10.0, label='input arc lines', zorder=10) + tampl_arxiv = np.interp(this_det_arxiv, xrng, spec_arxiv[:, iarxiv]) + plt.plot(xrng, spec_arxiv[:, iarxiv], color='black', drawstyle='steps-mid', linestyle=':', + label='arxiv arc', linewidth=0.5) + plt.plot(this_det_arxiv, tampl_arxiv, 'k+', markersize=8.0, label='arxiv arc lines') + # tampl_ss = np.interp(gsdet_ss, xrng, gdarc_ss) + for iline in range(det_arxiv_ss.size): + plt.plot([this_det_arxiv[iline], det_arxiv_ss[iline]], [tampl_arxiv[iline], tampl_arxiv[iline]], + color='cornflowerblue', linewidth=1.0) + plt.plot(xrng, spec_arxiv_ss, color='black', drawstyle='steps-mid', label='arxiv arc shift/stretch',linewidth=1.0) + plt.plot(det_arxiv_ss, tampl_arxiv, 'k.', markersize=10.0, label='predicted arxiv arc lines') + plt.title( + 'Cross-correlation of input slit and arxiv slit # {:d}'.format(iarxiv + 1) + + ': ccor = {:5.3f}'.format(ccorr_vec[iarxiv]) + + ', shift = {:6.1f}'.format(shift_vec[iarxiv]) + + ', stretch = {:5.4f}'.format(stretch_vec[iarxiv]) + + ', wv_cen = {:7.1f}'.format(wcen[iarxiv]) + + ', disp = {:5.3f}'.format(disp[iarxiv])) + plt.ylim(1.2*spec_cont_sub.min(), 1.5 *spec_cont_sub.max()) + plt.legend() + plt.show() + + + # Calculate wavelengths for all of the this_det_arxiv detections. This step could in principle be done more accurately + # with the polynomial solution itself, but the differences are 1e-12 of a pixel, and this interpolate of the tabulated + # solution makes the code more general. + wvval_arxiv = (scipy.interpolate.interp1d(xrng, wave_soln_arxiv[:, iarxiv], kind='cubic'))(this_det_arxiv) + + # Compute a "local" zero lag correlation of the slit spectrum and the shifted and stretch arxiv spectrum over a + # a nlocal_cc_odd long segment of spectrum. We will then uses spectral similarity as a further criteria to + # decide which lines are good matches + prod_smooth = scipy.ndimage.convolve1d(spec_cont_sub*spec_arxiv_ss, window) + spec2_smooth = scipy.ndimage.convolve1d(spec_cont_sub**2, window) + arxiv2_smooth = scipy.ndimage.convolve1d(spec_arxiv_ss**2, window) + denom = np.sqrt(spec2_smooth*arxiv2_smooth) + corr_local = np.zeros_like(denom) + corr_local[denom > 0] = prod_smooth[denom > 0]/denom[denom > 0] + corr_local[denom == 0.0] = -1.0 + + # Loop over the current slit line pixel detections and find the nearest arxiv spectrum line + # JFH added this if statement to prevent crashes for cases where no arc lines where found. This is because + # full_template keeps passing in tiny snippets of mostly junk padded spectra that cause all kind of crashes. + # A better approach would be to fix full_template so as to not enter reidentify unless the "arxiv_arcs" + # are not almost entirely zero padded snippets. + if det_arxiv_ss.size > 0: + for iline in range(detections.size): + # match to pixel in shifted/stretch arxiv spectrum + pdiff = np.abs(detections[iline] - det_arxiv_ss) + bstpx = np.argmin(pdiff) + # If a match is found within 2 pixels, consider this a successful match + if pdiff[bstpx] < match_toler: + # Using the arxiv arc wavelength solution, search for the nearest line in the line list + bstwv = np.abs(wvdata - wvval_arxiv[bstpx]) + # This is a good wavelength match if it is within match_toler disperion elements + if bstwv[np.argmin(bstwv)] < match_toler*disp_arxiv[iarxiv]: + line_indx = np.append(line_indx, np.argmin(bstwv)) # index in the line list array wvdata of this match + det_indx = np.append(det_indx, iline) # index of this line in the detected line array detections + line_cc = np.append(line_cc,np.interp(detections[iline],xrng,corr_local)) # local cross-correlation at this match + line_iarxiv = np.append(line_iarxiv,iarxiv) + + narxiv_used = np.sum(wcen != 0.0) + def measure_fwhm(spec, sigdetect=10., fwhm=5.): """ Measure the arc lines FWHM, i.e, approximate spectral resolution diff --git a/pypeit/core/wavecal/patterns.py b/pypeit/core/wavecal/patterns.py index 54afe12892..7350ca9a5a 100644 --- a/pypeit/core/wavecal/patterns.py +++ b/pypeit/core/wavecal/patterns.py @@ -620,7 +620,8 @@ def empty_patt_dict(nlines): return patt_dict -def solve_xcorr(detlines, linelist, dindex, lindex, line_cc, nreid_min = 4, cc_local_thresh = 0.8): +def solve_xcorr(detlines, linelist, dindex, lindex, line_cc, + nreid_min:int=4, cc_local_thresh:float=0.8): """ Given a starting solution, find the best match for all detlines Parameters diff --git a/pypeit/wavecalib.py b/pypeit/wavecalib.py index 9cc6fa9bc6..183daefdec 100644 --- a/pypeit/wavecalib.py +++ b/pypeit/wavecalib.py @@ -456,6 +456,7 @@ def __init__(self, msarc, slits, spectrograph, par, lamps, self.slits.mask[idx] = self.slits.bitmask.turn_off( self.slits.mask[idx], 'BADWVCALIB') else: + embed(header='459 of wavecalib') raise NotImplementedError("Not ready for multi-slit") # Load up slits @@ -878,10 +879,13 @@ def run(self, skip_QA=False, debug=False, # TODO -- just run solve_xcorr # Generate a better guess at wavelengths patt_dict, final_fit = autoid.echelle_wvcalib( - arccen, order_vec, arcspec_arxiv, wave_soln_arxiv, - self.lamps, self.par, ok_mask=ok_mask_idx, + self.arccen, self.slits.ech_order, + self.arcspec_arxiv, self.wave_soln_arxiv, + self.lamps, self.par, + #ok_mask=ok_mask_idx, nonlinear_counts=self.nonlinear_counts, - debug_all=False, redo_slit=self.slits.ech_order[bad_slit]) + debug_all=False, redo_slit=self.slits.ech_order[bad_slit], + debug_reid=True) # Deal with mask self.update_wvmask() From 25a3d363a402dab6f344a883012517dd42cf935d Mon Sep 17 00:00:00 2001 From: profxj Date: Mon, 29 May 2023 09:21:18 -0700 Subject: [PATCH 02/41] redo a single order well --- pypeit/calibrations.py | 2 +- pypeit/core/wavecal/autoid.py | 47 ++++++++++++++++++++++--------- pypeit/par/pypeitpar.py | 8 +++--- pypeit/wavecalib.py | 53 +++++++++++++++++++---------------- 4 files changed, 67 insertions(+), 43 deletions(-) diff --git a/pypeit/calibrations.py b/pypeit/calibrations.py index f3d86a8d58..d68265e800 100644 --- a/pypeit/calibrations.py +++ b/pypeit/calibrations.py @@ -837,7 +837,7 @@ def get_wv_calib(self): self.wv_calib.chk_synced(self.slits) self.slits.mask_wvcalib(self.wv_calib) # Return - if self.par['wavelengths']['redo_slit'] is None: + if self.par['wavelengths']['redo_slits'] is None: return self.wv_calib # Determine lamp list to use for wavecalib diff --git a/pypeit/core/wavecal/autoid.py b/pypeit/core/wavecal/autoid.py index 93e86e7bdd..51288ba9de 100644 --- a/pypeit/core/wavecal/autoid.py +++ b/pypeit/core/wavecal/autoid.py @@ -554,8 +554,8 @@ def reidentify(spec, spec_arxiv_in, wave_soln_arxiv_in, line_list, patt_dict_slit['sigdetect'] = sigdetect return detections, spec_cont_sub, patt_dict_slit - if debug_reid: - embed(header='557 of autoid.py') + #if debug_reid: + # embed(header='557 of autoid.py') # Finalize the best guess of each line patt_dict_slit = patterns.solve_xcorr( @@ -979,7 +979,7 @@ def echelle_wvcalib(spec, orders, spec_arxiv, wave_arxiv, lamps, par, ok_mask=None, use_unknowns=True, debug_all=False, debug_peaks=False, debug_xcorr=False, debug_reid=False, debug_fits=False, nonlinear_counts=1e10, - redo_slit:int=None): + redo_slits:list=None): r""" Algorithm to wavelength calibrate echelle data based on a predicted or archived wavelength solution @@ -1029,9 +1029,9 @@ def echelle_wvcalib(spec, orders, spec_arxiv, wave_arxiv, lamps, par, For arc line detection: Arc lines above this saturation threshold are not used in wavelength solution fits because they cannot be accurately centroided - redo_slit: int, optional + redo_slits: list, optional If provided, only perform the wavelength calibration for the - given slit. + given slit(s). Returns ------- @@ -1084,7 +1084,7 @@ def echelle_wvcalib(spec, orders, spec_arxiv, wave_arxiv, lamps, par, bad_orders = np.array([], dtype=int) # Reidentify each slit, and perform a fit for iord in range(norders): - if redo_slit is not None and orders[iord] != redo_slit: + if redo_slits is not None and orders[iord] not in redo_slits: continue # ToDO should we still be populating wave_calib with an empty dict here? if iord not in ok_mask: @@ -1104,18 +1104,33 @@ def echelle_wvcalib(spec, orders, spec_arxiv, wave_arxiv, lamps, par, debug_peaks=(debug_peaks or debug_all), debug_xcorr=(debug_xcorr or debug_all), debug_reid=(debug_reid or debug_all)) + # Perform the fit + if debug_fits or debug_all: + embed(header='1115 of autoid') # Check if an acceptable reidentification solution was found if not all_patt_dict[str(iord)]['acceptable']: wv_calib[str(iord)] = None bad_orders = np.append(bad_orders, iord) continue - # Perform the fit n_final = wvutils.parse_param(par, 'n_final', iord) - final_fit = wv_fitting.fit_slit(spec_cont_sub[:, iord], all_patt_dict[str(iord)], detections[str(iord)], - tot_line_list, match_toler=par['match_toler'], func=par['func'], - n_first=par['n_first'], - sigrej_first=par['sigrej_first'], n_final=n_final, sigrej_final=par['sigrej_final']) + final_fit = wv_fitting.fit_slit( + spec_cont_sub[:, iord], all_patt_dict[str(iord)], + detections[str(iord)], tot_line_list, + match_toler=par['match_toler'], + func=par['func'], n_first=par['n_first'], + sigrej_first=par['sigrej_first'], + n_final=n_final, + sigrej_final=par['sigrej_final']) + + #final_fit = wv_fitting.fit_slit( + # spec_cont_sub[:, iord], all_patt_dict[str(iord)], + # detections[str(iord)], tot_line_list, + # match_toler=par['match_toler'], + # func=par['func'], n_first=3, + # sigrej_first=2.0, + # n_final=n_final, + # sigrej_final=2.5) # Did the fit succeed? if final_fit is None: @@ -1141,14 +1156,14 @@ def echelle_wvcalib(spec, orders, spec_arxiv, wave_arxiv, lamps, par, # Print the final report of all lines report_final(norders, all_patt_dict, detections, wv_calib, ok_mask, bad_orders, - redo_slit=redo_slit, orders=orders) + redo_slits=redo_slits, orders=orders) return all_patt_dict, wv_calib def report_final(nslits, all_patt_dict, detections, wv_calib, ok_mask, bad_slits, - redo_slit:int=None, + redo_slits:list=None, orders:np.ndarray=None): """ Print out the final report for wavelength calibration @@ -1166,6 +1181,10 @@ def report_final(nslits, all_patt_dict, detections, Mask of indices of good slits bad_slits (ndarray, bool): List of slits that are bad + redo_slits (list, optional): + Report on only these slits + orders (np.ndarray, optional): + Echelle orders """ for slit in range(nslits): # Prepare a message for bad wavelength solutions @@ -1175,7 +1194,7 @@ def report_final(nslits, all_patt_dict, detections, badmsg += f' Order: {orders[slit]}' + msgs.newline() badmsg += ' Wavelength calibration not performed!' # Redo? - if redo_slit is not None and orders[slit] != redo_slit: + if redo_slits is not None and orders[slit] not in redo_slits: continue if slit not in ok_mask or slit in bad_slits or all_patt_dict[str(slit)] is None: msgs.warn(badmsg) diff --git a/pypeit/par/pypeitpar.py b/pypeit/par/pypeitpar.py index 604cbbb22d..4bff42ee83 100644 --- a/pypeit/par/pypeitpar.py +++ b/pypeit/par/pypeitpar.py @@ -2470,7 +2470,7 @@ def __init__(self, reference=None, method=None, echelle=None, ech_nspec_coeff=No sigrej_first=None, sigrej_final=None, numsearch=None, nfitpix=None, IDpixels=None, IDwaves=None, refframe=None, nsnippet=None, use_instr_flag=None, wvrng_arxiv=None, - ech_separate_2d=None, redo_slit=None): + ech_separate_2d=None, redo_slits=None): # Grab the parameter names and values from the function # arguments @@ -2712,8 +2712,8 @@ def __init__(self, reference=None, method=None, echelle=None, ech_nspec_coeff=No descr['refframe'] = 'Frame of reference for the wavelength calibration. ' \ 'Options are: {0}'.format(', '.join(options['refframe'])) - dtypes['redo_slit'] = int - descr['redo_slit'] = 'Redo the input slit (multslit) or order (echelle)' + dtypes['redo_slits'] = [int, list] + descr['redo_slits'] = 'Redo the input slit (multslit) or order (echelle)' # Instantiate the parameter set @@ -2734,7 +2734,7 @@ def from_dict(cls, cfg): 'nlocal_cc', 'rms_threshold', 'match_toler', 'func', 'n_first','n_final', 'sigrej_first', 'sigrej_final', 'numsearch', 'nfitpix', 'IDpixels', 'IDwaves', 'refframe', 'nsnippet', 'use_instr_flag', - 'wvrng_arxiv', 'redo_slit'] + 'wvrng_arxiv', 'redo_slits'] badkeys = np.array([pk not in parkeys for pk in k]) if np.any(badkeys): diff --git a/pypeit/wavecalib.py b/pypeit/wavecalib.py index 183daefdec..8b8a7052e3 100644 --- a/pypeit/wavecalib.py +++ b/pypeit/wavecalib.py @@ -449,9 +449,9 @@ def __init__(self, msarc, slits, spectrograph, par, lamps, # the slits if self.slits is not None and self.msarc is not None: # Redo? - if self.par['redo_slit'] is not None: + if self.par['redo_slits'] is not None: if self.par['echelle'] and self.slits.ech_order is not None: - idx = np.where(self.slits.ech_order == self.par['redo_slit'])[0][0] + idx = np.in1d(self.slits.ech_order, self.par['redo_slits']) # Turn off mask self.slits.mask[idx] = self.slits.bitmask.turn_off( self.slits.mask[idx], 'BADWVCALIB') @@ -560,7 +560,8 @@ def build_wv_calib(self, arccen, method, skip_QA=False, # Now preferred # Slit positions arcfitter = autoid.ArchiveReid(arccen, self.lamps, self.par, - ech_fixed_format=self.spectrograph.ech_fixed_format, ok_mask=ok_mask_idx, + ech_fixed_format=self.spectrograph.ech_fixed_format, + ok_mask=ok_mask_idx, measured_fwhms=measured_fwhms, orders=self.orders, nonlinear_counts=self.nonlinear_counts) @@ -597,7 +598,8 @@ def build_wv_calib(self, arccen, method, skip_QA=False, arccen, order_vec, arcspec_arxiv, wave_soln_arxiv, self.lamps, self.par, ok_mask=ok_mask_idx, nonlinear_counts=self.nonlinear_counts, - debug_all=False, redo_slit=self.par['redo_slit']) + debug_all=False, + redo_slits=np.atleast_1d(self.par['redo_slits'])) # Save as internals in case we need to redo self.wave_soln_arxiv = wave_soln_arxiv @@ -608,16 +610,17 @@ def build_wv_calib(self, arccen, method, skip_QA=False, msgs.error('Unrecognized wavelength calibration method: {:}'.format(method)) # Build the DataContainer - if self.par['redo_slit'] is not None: + if self.par['redo_slits'] is not None: self.wv_calib = prev_wvcalib # Update/reset items self.wv_calib.arc_spectra = arccen - # + # Save? for key in final_fit.keys(): - idx = int(key) - self.wv_calib.wv_fits[idx] = final_fit[key] - self.wv_calib.wv_fits[idx].spat_id = self.slits.spat_id[idx] - self.wv_calib.wv_fits[idx].fwhm = measured_fwhms[idx] + if final_fit[key]['rms'] < self.par['rms_threshold']: + idx = int(key) + self.wv_calib.wv_fits[idx] = final_fit[key] + self.wv_calib.wv_fits[idx].spat_id = self.slits.spat_id[idx] + self.wv_calib.wv_fits[idx].fwhm = measured_fwhms[idx] else: # Loop on WaveFit items tmp = [] @@ -849,8 +852,8 @@ def run(self, skip_QA=False, debug=False, self.arccen, self.wvc_bpm = self.extract_arcs() # Fill up the calibrations and generate QA - skip_QA = True # for debugging - msgs.warn("TURN QA BACK ON!!!") + #skip_QA = True # for debugging + #msgs.warn("TURN QA BACK ON!!!") self.wv_calib = self.build_wv_calib( self.arccen, self.par['method'], skip_QA=skip_QA, @@ -872,20 +875,22 @@ def run(self, skip_QA=False, debug=False, if self.par['ech_separate_2d']: self.wv_calib.det_img = self.msarc.det_img.copy() + # TODO -- Turn the following back on! # Try a second attempt with 1D, if needed if np.any(bad_rms): - for bad_slit in np.where(bad_rms)[0]: - embed(header='877 of wavecalib') - # TODO -- just run solve_xcorr - # Generate a better guess at wavelengths - patt_dict, final_fit = autoid.echelle_wvcalib( - self.arccen, self.slits.ech_order, - self.arcspec_arxiv, self.wave_soln_arxiv, - self.lamps, self.par, - #ok_mask=ok_mask_idx, - nonlinear_counts=self.nonlinear_counts, - debug_all=False, redo_slit=self.slits.ech_order[bad_slit], - debug_reid=True) + embed(header='877 of wavecalib') + # TODO -- just run solve_xcorr + # Generate a better guess at wavelengths + patt_dict, final_fit = autoid.echelle_wvcalib( + self.arccen, self.slits.ech_order, + self.arcspec_arxiv, self.wave_soln_arxiv, + self.lamps, self.par, + #ok_mask=ok_mask_idx, + nonlinear_counts=self.nonlinear_counts, + debug_all=False, + redo_slits=self.slits.ech_order[bad_rms], + debug_reid=True) + embed(header='893 of wavecalib') # Deal with mask self.update_wvmask() From 97615883c2f2d515f73d540ce346b5d8eb4bcbd0 Mon Sep 17 00:00:00 2001 From: profxj Date: Sun, 4 Jun 2023 17:14:56 -0700 Subject: [PATCH 03/41] fuss --- pypeit/wavecalib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pypeit/wavecalib.py b/pypeit/wavecalib.py index 8b8a7052e3..99d4eced68 100644 --- a/pypeit/wavecalib.py +++ b/pypeit/wavecalib.py @@ -875,7 +875,6 @@ def run(self, skip_QA=False, debug=False, if self.par['ech_separate_2d']: self.wv_calib.det_img = self.msarc.det_img.copy() - # TODO -- Turn the following back on! # Try a second attempt with 1D, if needed if np.any(bad_rms): embed(header='877 of wavecalib') From 01c87023ef94b86942182f2c4375871e03f57310 Mon Sep 17 00:00:00 2001 From: profxj Date: Mon, 3 Jul 2023 11:52:58 -0700 Subject: [PATCH 04/41] wip --- pypeit/core/wavecal/autoid.py | 183 +++++++++++++++----------------- pypeit/core/wavecal/patterns.py | 13 ++- pypeit/par/pypeitpar.py | 5 +- pypeit/wavecalib.py | 54 ++++++---- 4 files changed, 131 insertions(+), 124 deletions(-) diff --git a/pypeit/core/wavecal/autoid.py b/pypeit/core/wavecal/autoid.py index 8efd326ddc..052053faaa 100644 --- a/pypeit/core/wavecal/autoid.py +++ b/pypeit/core/wavecal/autoid.py @@ -373,9 +373,6 @@ def match_qa(arc_spec, tcent, line_list, IDs, scores, outfile = None, title=None return - - - def reidentify(spec, spec_arxiv_in, wave_soln_arxiv_in, line_list, nreid_min, det_arxiv=None, detections=None, cc_thresh=0.8, cc_local_thresh=0.8, @@ -646,9 +643,6 @@ def reidentify(spec, spec_arxiv_in, wave_soln_arxiv_in, line_list, patt_dict_slit['sigdetect'] = sigdetect return detections, spec_cont_sub, patt_dict_slit - #if debug_reid: - # embed(header='557 of autoid.py') - # Finalize the best guess of each line patt_dict_slit = patterns.solve_xcorr( detections, wvdata, det_indx, line_indx, line_cc, @@ -691,105 +685,98 @@ def reidentify(spec, spec_arxiv_in, wave_soln_arxiv_in, line_list, return detections, spec_cont_sub, patt_dict_slit -# TODO -- Move this to wvutils? -def match_to_arxiv(spec_arxiv:np.ndarray): +def match_to_arxiv(lamps:list, spec:np.ndarray, wv_guess:np.ndarray, + spec_arxiv:np.ndarray, wave_arxiv:np.ndarray, nreid_imin:int, + match_toler=2.0, nonlinear_counts=1e10, sigdetect=5.0, fwhm=4.0, + debug_peaks:bool=False, use_unknowns:bool=False): - nspec_arxiv, narxiv = spec_arxiv.shape - - # Cross-correlate with each arxiv spectrum to identify lines - line_indx = np.array([], dtype=int) - det_indx = np.array([], dtype=int) - line_cc = np.array([], dtype=float) - line_iarxiv = np.array([], dtype=int) - wcen = np.zeros(narxiv) - disp = np.zeros(narxiv) - shift_vec = np.zeros(narxiv) - stretch_vec = np.zeros(narxiv) - ccorr_vec = np.zeros(narxiv) - for iarxiv in range(narxiv): - msgs.info('Cross-correlating with arxiv slit # {:d}'.format(iarxiv)) - this_det_arxiv = det_arxiv[str(iarxiv)] - # Match the peaks between the two spectra. This code attempts to compute the stretch if cc > cc_thresh - success, shift_vec[iarxiv], stretch_vec[iarxiv], ccorr_vec[iarxiv], _, _ = \ - wvutils.xcorr_shift_stretch(spec_cont_sub, spec_arxiv[:, iarxiv], - cc_thresh=cc_thresh, fwhm=fwhm, seed=random_state, - debug=debug_xcorr) - # If cc < cc_thresh or if this optimization failed, don't reidentify from this arxiv spectrum - if success != 1: - continue - # Estimate wcen and disp for this slit based on its shift/stretch relative to the archive slit - disp[iarxiv] = disp_arxiv[iarxiv] / stretch_vec[iarxiv] - wcen[iarxiv] = wvc_arxiv[iarxiv] - shift_vec[iarxiv]*disp[iarxiv] - # For each peak in the arxiv spectrum, identify the corresponding peaks in the input spectrum. Do this by - # transforming these arxiv slit line pixel locations into the (shifted and stretched) input spectrum frame - det_arxiv_ss = this_det_arxiv*stretch_vec[iarxiv] + shift_vec[iarxiv] - spec_arxiv_ss = wvutils.shift_and_stretch(spec_arxiv[:, iarxiv], shift_vec[iarxiv], stretch_vec[iarxiv]) + # TODO -- This code is duplicated from echelle_wvcalib + # Load the line lists + if 'ThAr' in lamps: + line_lists_all = waveio.load_line_lists(lamps) + line_lists = line_lists_all[np.where(line_lists_all['ion'] != 'UNKNWN')] + unknwns = line_lists_all[np.where(line_lists_all['ion'] == 'UNKNWN')] + else: + line_lists = waveio.load_line_lists(lamps) + unknwns = waveio.load_unknown_list(lamps) - if debug_xcorr: - plt.figure(figsize=(14, 6)) - tampl_slit = np.interp(detections, xrng, spec_cont_sub) - plt.plot(xrng, spec_cont_sub, color='red', drawstyle='steps-mid', label='input arc',linewidth=1.0, zorder=10) - plt.plot(detections, tampl_slit, 'r.', markersize=10.0, label='input arc lines', zorder=10) - tampl_arxiv = np.interp(this_det_arxiv, xrng, spec_arxiv[:, iarxiv]) - plt.plot(xrng, spec_arxiv[:, iarxiv], color='black', drawstyle='steps-mid', linestyle=':', - label='arxiv arc', linewidth=0.5) - plt.plot(this_det_arxiv, tampl_arxiv, 'k+', markersize=8.0, label='arxiv arc lines') - # tampl_ss = np.interp(gsdet_ss, xrng, gdarc_ss) - for iline in range(det_arxiv_ss.size): - plt.plot([this_det_arxiv[iline], det_arxiv_ss[iline]], [tampl_arxiv[iline], tampl_arxiv[iline]], - color='cornflowerblue', linewidth=1.0) - plt.plot(xrng, spec_arxiv_ss, color='black', drawstyle='steps-mid', label='arxiv arc shift/stretch',linewidth=1.0) - plt.plot(det_arxiv_ss, tampl_arxiv, 'k.', markersize=10.0, label='predicted arxiv arc lines') - plt.title( - 'Cross-correlation of input slit and arxiv slit # {:d}'.format(iarxiv + 1) + - ': ccor = {:5.3f}'.format(ccorr_vec[iarxiv]) + - ', shift = {:6.1f}'.format(shift_vec[iarxiv]) + - ', stretch = {:5.4f}'.format(stretch_vec[iarxiv]) + - ', wv_cen = {:7.1f}'.format(wcen[iarxiv]) + - ', disp = {:5.3f}'.format(disp[iarxiv])) - plt.ylim(1.2*spec_cont_sub.min(), 1.5 *spec_cont_sub.max()) - plt.legend() - plt.show() + tot_line_list = astropy.table.vstack([line_lists, unknwns]) if use_unknowns else line_lists + # Generate the wavelengths from the line list and sort + wvdata = np.array(tot_line_list['wave'].data) # Removes mask if any + wvdata.sort() - # Calculate wavelengths for all of the this_det_arxiv detections. This step could in principle be done more accurately - # with the polynomial solution itself, but the differences are 1e-12 of a pixel, and this interpolate of the tabulated - # solution makes the code more general. - wvval_arxiv = (scipy.interpolate.interp1d(xrng, wave_soln_arxiv[:, iarxiv], kind='cubic'))(this_det_arxiv) + # Search for lines in the input arc + tcent, ecent, cut_tcent, icut, spec_cont_sub = wvutils.arc_lines_from_spec( + spec, sigdetect=sigdetect, + nonlinear_counts=nonlinear_counts, + fwhm=fwhm, debug = debug_peaks) + # If there are no lines in the input arc, return + if tcent.size == 0: + return None + + # Search for lines in the arxiv arc + tcent_arxiv, ecent_arxiv, cut_tcent_arxiv, icut_arxiv, spec_cont_sub_now = wvutils.arc_lines_from_spec( + spec_arxiv, sigdetect=sigdetect, + nonlinear_counts=nonlinear_counts, fwhm = fwhm, debug = debug_peaks) + # If there are no lines in the arxiv arc, return + if tcent_arxiv.size == 0: + return None + + # Interpolate the input wavelengths + fwv_guess = scipy.interpolate.interp1d(np.arange(len(wv_guess)), wv_guess, + kind='cubic', bounds_error=False, + fill_value='extrapolate') + # Interpolate the axiv both ways + fpix_arxiv = scipy.interpolate.interp1d(wave_arxiv, np.arange(len(wave_arxiv)), + kind='cubic', bounds_error=False, + fill_value='extrapolate') + fwv_arxiv = scipy.interpolate.interp1d(np.arange(len(wave_arxiv)), wave_arxiv, + kind='cubic', bounds_error=False, + fill_value='extrapolate') + # Find the wavelengths of the input arc lines and then the pixels + wv_cent = fwv_guess(tcent) + pix_arxiv = fpix_arxiv(wv_cent) + + # Other bits + wvc_arxiv = wave_arxiv[wave_arxiv.size//2] + igood = wave_arxiv > 1.0 + disp_arxiv = np.median(wave_arxiv[igood] - np.roll(wave_arxiv[igood], 1)) - # Compute a "local" zero lag correlation of the slit spectrum and the shifted and stretch arxiv spectrum over a - # a nlocal_cc_odd long segment of spectrum. We will then uses spectral similarity as a further criteria to - # decide which lines are good matches - prod_smooth = scipy.ndimage.convolve1d(spec_cont_sub*spec_arxiv_ss, window) - spec2_smooth = scipy.ndimage.convolve1d(spec_cont_sub**2, window) - arxiv2_smooth = scipy.ndimage.convolve1d(spec_arxiv_ss**2, window) - denom = np.sqrt(spec2_smooth*arxiv2_smooth) - corr_local = np.zeros_like(denom) - corr_local[denom > 0] = prod_smooth[denom > 0]/denom[denom > 0] - corr_local[denom == 0.0] = -1.0 + line_indx = np.array([], dtype=int) + det_indx = np.array([], dtype=int) + line_cc = np.array([], dtype=float) + #line_iarxiv = np.array([], dtype=int) + + # Match with tolerance + for ss, ipix_arxiv in enumerate(pix_arxiv): + pdiff = np.abs(pix_arxiv - tcent_arxiv) + bstpx = np.argmin(pdiff) + # If a match is found within 2 pixels, consider this a successful match + if pdiff[bstpx] < match_toler: + # Using the arxiv arc wavelength solution, search for the nearest line in the line list + bstwv = np.abs(wvdata - fwv_arxiv(tcent_arxiv[bstpx])) + # This is a good wavelength match if it is within match_toler disperion elements + if bstwv[np.argmin(bstwv)] < match_toler*disp_arxiv: + line_indx = np.append(line_indx, np.argmin(bstwv)) # index in the line list array wvdata of this match + det_indx = np.append(det_indx, ss) # index of this line in the detected line array detections + #line_iarxiv = np.append(line_iarxiv,iarxiv) + line_cc = np.append(line_cc,1.) # Fakery - # Loop over the current slit line pixel detections and find the nearest arxiv spectrum line - # JFH added this if statement to prevent crashes for cases where no arc lines where found. This is because - # full_template keeps passing in tiny snippets of mostly junk padded spectra that cause all kind of crashes. - # A better approach would be to fix full_template so as to not enter reidentify unless the "arxiv_arcs" - # are not almost entirely zero padded snippets. - if det_arxiv_ss.size > 0: - for iline in range(detections.size): - # match to pixel in shifted/stretch arxiv spectrum - pdiff = np.abs(detections[iline] - det_arxiv_ss) - bstpx = np.argmin(pdiff) - # If a match is found within 2 pixels, consider this a successful match - if pdiff[bstpx] < match_toler: - # Using the arxiv arc wavelength solution, search for the nearest line in the line list - bstwv = np.abs(wvdata - wvval_arxiv[bstpx]) - # This is a good wavelength match if it is within match_toler disperion elements - if bstwv[np.argmin(bstwv)] < match_toler*disp_arxiv[iarxiv]: - line_indx = np.append(line_indx, np.argmin(bstwv)) # index in the line list array wvdata of this match - det_indx = np.append(det_indx, iline) # index of this line in the detected line array detections - line_cc = np.append(line_cc,np.interp(detections[iline],xrng,corr_local)) # local cross-correlation at this match - line_iarxiv = np.append(line_iarxiv,iarxiv) + # Initialise the patterns dictionary, sigdetect not used anywhere + if (len(np.unique(line_indx)) < 3): + patt_dict_slit = patterns.empty_patt_dict(pix_arxiv.size) + patt_dict_slit['sigdetect'] = sigdetect + else: + # Finalize the best guess of each line + patt_dict_slit = patterns.solve_xcorr( + tcent, wvdata, det_indx, line_indx, line_cc, + nreid_min=nreid_min,cc_local_thresh=-1) + patt_dict_slit['bwv'] = wvc_arxiv + patt_dict_slit['bdisp'] = disp_arxiv + patt_dict_slit['sigdetect'] = sigdetect - narxiv_used = np.sum(wcen != 0.0) + return tcent, spec_cont_sub, patt_dict_slit def map_fwhm(image, imbpm, slits, npixel=None, nsample=None, sigdetect=10., specord=1, spatord=0, fwhm=5.): """ diff --git a/pypeit/core/wavecal/patterns.py b/pypeit/core/wavecal/patterns.py index 7350ca9a5a..46b16a9325 100644 --- a/pypeit/core/wavecal/patterns.py +++ b/pypeit/core/wavecal/patterns.py @@ -626,14 +626,17 @@ def solve_xcorr(detlines, linelist, dindex, lindex, line_cc, Parameters ---------- - detlines : ndarray + detlines : `numpy.ndarray`_ list of detected lines in pixels (sorted, increasing) - linelist : ndarray + linelist : `numpy.ndarray`_ list of lines that should be detected (sorted, increasing) - dindex : ndarray + dindex : `numpy.ndarray`_ Index array of all detlines (pixels) used in each triangle - lindex : ndarray + lindex : `numpy.ndarray`_ Index array of the assigned line (wavelengths)to each index in dindex + line_cc : `numpy.ndarray`_ + ?? + cc_local_thresh : float, default = 0.8, optional Returns ------- @@ -713,6 +716,8 @@ def score_xcorr(counts, cc_avg, nreid_min = 4, cc_local_thresh = -1.0): nmin_match: int, default = 4, optional Minimum number of slits/solutions that have to have been matched to receive a score of 'Perfect' or 'Very Good' + cc_local_thresh: float, default = -1.0, optional + What does this do?? Returns ------- diff --git a/pypeit/par/pypeitpar.py b/pypeit/par/pypeitpar.py index ddcd5416ee..b01e1071f8 100644 --- a/pypeit/par/pypeitpar.py +++ b/pypeit/par/pypeitpar.py @@ -2764,9 +2764,8 @@ def from_dict(cls, cfg): 'reid_arxiv', 'nreid_min', 'cc_thresh', 'cc_local_thresh', 'nlocal_cc', 'rms_threshold', 'match_toler', 'func', 'n_first','n_final', 'sigrej_first', 'sigrej_final', 'numsearch', 'nfitpix', - 'IDpixels', 'IDwaves', 'refframe', 'nsnippet', - 'use_instr_flag', 'wvrng_arxiv', 'redo_slits', - 'qa_log'] + 'refframe', 'nsnippet', 'use_instr_flag', 'wvrng_arxiv', + 'redo_slits', 'qa_log'] badkeys = np.array([pk not in parkeys for pk in k]) if np.any(badkeys): diff --git a/pypeit/wavecalib.py b/pypeit/wavecalib.py index d98f9c655b..c715f2bcee 100644 --- a/pypeit/wavecalib.py +++ b/pypeit/wavecalib.py @@ -659,7 +659,7 @@ def build_wv_calib(self, arccen, method, skip_QA=False, self.lamps, self.par, ok_mask=ok_mask_idx, nonlinear_counts=self.nonlinear_counts, debug_all=False, - redo_slits=np.atleast_1d(self.par['redo_slits'])) + redo_slits=np.atleast_1d(self.par['redo_slits']) if self.par['redo_slits'] is not None else None) # Save as internals in case we need to redo self.wave_soln_arxiv = wave_soln_arxiv @@ -922,11 +922,9 @@ def run(self, skip_QA=False, debug=False, self.arccen, self.wvc_bpm = self.extract_arcs() # Fill up the calibrations and generate QA -<<<<<<< HEAD #skip_QA = True # for debugging #msgs.warn("TURN QA BACK ON!!!") -======= ->>>>>>> origin/sensfunc_blaze_jwst_lists + self.wv_calib = self.build_wv_calib( self.arccen, self.par['method'], skip_QA=skip_QA, @@ -935,7 +933,10 @@ def run(self, skip_QA=False, debug=False, # Fit 2D? if self.par['echelle']: # Assess the fits - rms = np.array([wvfit.rms for wvfit in self.wv_calib.wv_fits]) + rms = [] + for wvfit in self.wv_calib.wv_fits: + rms.append(wvfit.rms if wvfit.rms is not None else 999.) + rms = np.array(rms) bad_rms = rms > self.par['rms_threshold'] self.wvc_bpm[bad_rms] = True if np.any(bad_rms): @@ -950,20 +951,35 @@ def run(self, skip_QA=False, debug=False, # TODO This is work in progress by ProfX # Try a second attempt with 1D, if needed - if np.any(bad_rms): - embed(header='877 of wavecalib') - # TODO -- just run solve_xcorr - # Generate a better guess at wavelengths - patt_dict, final_fit = autoid.echelle_wvcalib( - self.arccen, self.slits.ech_order, - self.arcspec_arxiv, self.wave_soln_arxiv, - self.lamps, self.par, - #ok_mask=ok_mask_idx, - nonlinear_counts=self.nonlinear_counts, - debug_all=False, - redo_slits=self.slits.ech_order[bad_rms], - debug_reid=True) - embed(header='893 of wavecalib') + embed(header='953 of wavecalib') + # Make this outside the for loop.. + if self.par['ech_separate_2d']: + slit_img = self.slits.slit_img() + for idx in np.where(bad_rms)[0]: + idx = 16 + order = self.slits.ech_order[idx] + # Which detector? + if self.par['ech_separate_2d']: + spat_id = self.wv_calib.spat_ids[idx] + ordr_det = self.slits.det_of_slit( + spat_id, self.msarc.det_img, + slit_img=slit_img) + else: + ordr_det = 1 + # Predict the wavelengths + nspec = self.arccen.shape[0] + spec_vec_norm = np.arange(nspec)/float(nspec-1) + wv_order_mod = fit2ds[ordr_det-1].eval(spec_vec_norm, + x2=np.ones_like(spec_vec_norm)*order) + + # Link me + + # Score me + + embed(header='893 of wavecalib') + + # Do another 2D + fit2ds = self.echelle_2dfit(self.wv_calib, skip_QA = skip_QA, debug=debug) # Deal with mask self.update_wvmask() From e738f9138e20ab08d8cd94bea0ee639c7334bc0c Mon Sep 17 00:00:00 2001 From: profxj Date: Mon, 3 Jul 2023 15:44:16 -0700 Subject: [PATCH 05/41] wip! --- pypeit/core/wavecal/autoid.py | 6 +++--- pypeit/wavecalib.py | 36 ++++++++++++++++++++++++++++------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/pypeit/core/wavecal/autoid.py b/pypeit/core/wavecal/autoid.py index 052053faaa..a3c50fca5f 100644 --- a/pypeit/core/wavecal/autoid.py +++ b/pypeit/core/wavecal/autoid.py @@ -686,7 +686,7 @@ def reidentify(spec, spec_arxiv_in, wave_soln_arxiv_in, line_list, def match_to_arxiv(lamps:list, spec:np.ndarray, wv_guess:np.ndarray, - spec_arxiv:np.ndarray, wave_arxiv:np.ndarray, nreid_imin:int, + spec_arxiv:np.ndarray, wave_arxiv:np.ndarray, nreid_min:int, match_toler=2.0, nonlinear_counts=1e10, sigdetect=5.0, fwhm=4.0, debug_peaks:bool=False, use_unknowns:bool=False): @@ -750,7 +750,7 @@ def match_to_arxiv(lamps:list, spec:np.ndarray, wv_guess:np.ndarray, # Match with tolerance for ss, ipix_arxiv in enumerate(pix_arxiv): - pdiff = np.abs(pix_arxiv - tcent_arxiv) + pdiff = np.abs(ipix_arxiv - tcent_arxiv) bstpx = np.argmin(pdiff) # If a match is found within 2 pixels, consider this a successful match if pdiff[bstpx] < match_toler: @@ -776,7 +776,7 @@ def match_to_arxiv(lamps:list, spec:np.ndarray, wv_guess:np.ndarray, patt_dict_slit['bdisp'] = disp_arxiv patt_dict_slit['sigdetect'] = sigdetect - return tcent, spec_cont_sub, patt_dict_slit + return tcent, spec_cont_sub, patt_dict_slit, tot_line_list def map_fwhm(image, imbpm, slits, npixel=None, nsample=None, sigdetect=10., specord=1, spatord=0, fwhm=5.): """ diff --git a/pypeit/wavecalib.py b/pypeit/wavecalib.py index c715f2bcee..401d4a3128 100644 --- a/pypeit/wavecalib.py +++ b/pypeit/wavecalib.py @@ -17,7 +17,7 @@ from pypeit.core import arc, qa from pypeit.core import fitting from pypeit.core import parse -from pypeit.core.wavecal import autoid, wv_fitting +from pypeit.core.wavecal import autoid, wv_fitting, wvutils from pypeit.core.gui.identify import Identify from pypeit import datamodel from pypeit import calibframe @@ -955,12 +955,11 @@ def run(self, skip_QA=False, debug=False, # Make this outside the for loop.. if self.par['ech_separate_2d']: slit_img = self.slits.slit_img() - for idx in np.where(bad_rms)[0]: - idx = 16 - order = self.slits.ech_order[idx] + for iord in np.where(bad_rms)[0]: + order = self.slits.ech_order[iord] # Which detector? if self.par['ech_separate_2d']: - spat_id = self.wv_calib.spat_ids[idx] + spat_id = self.wv_calib.spat_ids[iord] ordr_det = self.slits.det_of_slit( spat_id, self.msarc.det_img, slit_img=slit_img) @@ -970,11 +969,34 @@ def run(self, skip_QA=False, debug=False, nspec = self.arccen.shape[0] spec_vec_norm = np.arange(nspec)/float(nspec-1) wv_order_mod = fit2ds[ordr_det-1].eval(spec_vec_norm, - x2=np.ones_like(spec_vec_norm)*order) + x2=np.ones_like(spec_vec_norm)*order)/order # Link me + from importlib import reload + reload(autoid) + tcent, spec_cont_sub, patt_dict_slit, tot_llist = autoid.match_to_arxiv( + self.lamps, self.arccen[:,iord], wv_order_mod, + self.arcspec_arxiv[:, iord], self.wave_soln_arxiv[:,iord], + self.par['nreid_min'], + match_toler=self.par['match_toler'], + nonlinear_counts=self.nonlinear_counts, + sigdetect=wvutils.parse_param(self.par, 'sigdetect', iord), + fwhm=self.par['fwhm']) + + if not patt_dict_slit['acceptable']: + msgs.warn(f"Order {order} is still not acceptable after attempt to reidentify.") + continue - # Score me + # Fit me -- RMS may be too high again + n_final = wvutils.parse_param(self.par, 'n_final', iord) + final_fit = wv_fitting.fit_slit( + spec_cont_sub, patt_dict_slit, tcent, tot_llist, + match_toler=self.par['match_toler'], + func=self.par['func'], + n_first=self.par['n_first'], + sigrej_first=self.par['sigrej_first'], + n_final=n_final, + sigrej_final=self.par['sigrej_final']) embed(header='893 of wavecalib') From 85bc73ba32f1c7d573bbd774057b7a3d9f2db046 Mon Sep 17 00:00:00 2001 From: profxj Date: Mon, 3 Jul 2023 16:00:31 -0700 Subject: [PATCH 06/41] semi-successful --- pypeit/wavecalib.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/pypeit/wavecalib.py b/pypeit/wavecalib.py index 401d4a3128..e295a2db9e 100644 --- a/pypeit/wavecalib.py +++ b/pypeit/wavecalib.py @@ -994,11 +994,29 @@ def run(self, skip_QA=False, debug=False, match_toler=self.par['match_toler'], func=self.par['func'], n_first=self.par['n_first'], + #n_first=3, sigrej_first=self.par['sigrej_first'], + #sigrej_first=1.5, n_final=n_final, - sigrej_final=self.par['sigrej_final']) + sigrej_final=2.) + #sigrej_final=self.par['sigrej_final']) + print(final_fit['rms']) + + # Keep? + if final_fit['rms'] < self.par['rms_threshold']: + # TODO -- This is repeated from lines 718-725 + # QA + outfile = qa.set_qa_filename( + self.wv_calib.calib_key, 'arc_fit_qa', + slit=order, + out_dir=self.qa_path) + autoid.arc_fit_qa(final_fit, + title=f'Arc Fit QA for slit/order: {order}', + outfile=outfile) + # Save the wavelength solution fits + self.wv_calib.wv_fits[iord] = final_fit + self.wvc_bpm[iord] = False - embed(header='893 of wavecalib') # Do another 2D fit2ds = self.echelle_2dfit(self.wv_calib, skip_QA = skip_QA, debug=debug) From 73a91fa61cd7f55b01607197d3b17c0b0018bab5 Mon Sep 17 00:00:00 2001 From: profxj Date: Wed, 5 Jul 2023 12:14:10 -0700 Subject: [PATCH 07/41] good as it gets for now --- pypeit/wavecalib.py | 164 +++++++++++++++++++++++++------------------- 1 file changed, 92 insertions(+), 72 deletions(-) diff --git a/pypeit/wavecalib.py b/pypeit/wavecalib.py index e295a2db9e..72804b63d6 100644 --- a/pypeit/wavecalib.py +++ b/pypeit/wavecalib.py @@ -753,9 +753,12 @@ def echelle_2dfit(self, wv_calib, debug=False, skip_QA=False): Flag to skip construction of the nominal QA plots. Returns: - list: list of :class:`pypeit.fitting.PypeItFit`: objects containing information from 2-d fit. + tuple: + fit2ds -- list of :class:`pypeit.fitting.PypeItFit`: objects containing information from 2-d fit. Frequently a list of 1 fit. The main exception is for a mosaic when one sets echelle_separate_2d=True + dets -- list of int: List of the detectors + order_dets -- list of lists of int: List of the orders """ if self.spectrograph.pypeline != 'Echelle': msgs.error('Cannot execute echelle_2dfit for a non-echelle spectrograph.') @@ -779,7 +782,9 @@ def echelle_2dfit(self, wv_calib, debug=False, skip_QA=False): # Loop on detectors fit2ds = [] + save_order_dets = [] for idet in dets: + order_in_dets = [] msgs.info('Fitting detector {:d}'.format(idet)) # Init all_wave = np.array([], dtype=float) @@ -789,8 +794,6 @@ def echelle_2dfit(self, wv_calib, debug=False, skip_QA=False): # Loop to grab the good orders for ii in range(wv_calib.nslits): iorder = self.slits.ech_order[ii] - if iorder not in ok_mask_order: - continue # Separate detector analysis? if self.par['ech_separate_2d']: @@ -803,6 +806,13 @@ def echelle_2dfit(self, wv_calib, debug=False, skip_QA=False): if ordr_det != idet: continue + # Need to record this whether or not it is ok + order_in_dets.append(iorder) + + # Is it ok? + if iorder not in ok_mask_order: + continue + # Slurp mask_now = wv_calib.wv_fits[ii].pypeitfit.bool_gpm all_wave = np.append(all_wave, wv_calib.wv_fits[ii]['wave_fit'][mask_now]) @@ -815,6 +825,8 @@ def echelle_2dfit(self, wv_calib, debug=False, skip_QA=False): nspec_coeff=self.par['ech_nspec_coeff'], norder_coeff=self.par['ech_norder_coeff'], sigrej=self.par['ech_sigrej'], debug=debug) + # Save + save_order_dets.append(order_in_dets) fit2ds.append(fit2d) self.steps.append(inspect.stack()[0][3]) @@ -843,7 +855,7 @@ def echelle_2dfit(self, wv_calib, debug=False, skip_QA=False): out_dir=self.qa_path) arc.fit2darc_orders_qa(fit2d, nspec, outfile=outfile_orders) - return fit2ds + return fit2ds, dets, save_order_dets # TODO: JFH this method is identical to the code in wavetilts. @@ -942,7 +954,9 @@ def run(self, skip_QA=False, debug=False, if np.any(bad_rms): msgs.warn("Masking one or more bad orders (RMS)") # Fit - fit2ds = self.echelle_2dfit(self.wv_calib, skip_QA = skip_QA, debug=debug) + fit2ds, dets, order_dets = self.echelle_2dfit( + self.wv_calib, skip_QA = skip_QA, debug=debug) + # Save self.wv_calib.wv_fit2d = np.array(fit2ds) # Save det_img? @@ -951,75 +965,81 @@ def run(self, skip_QA=False, debug=False, # TODO This is work in progress by ProfX # Try a second attempt with 1D, if needed - embed(header='953 of wavecalib') - # Make this outside the for loop.. - if self.par['ech_separate_2d']: - slit_img = self.slits.slit_img() - for iord in np.where(bad_rms)[0]: - order = self.slits.ech_order[iord] - # Which detector? - if self.par['ech_separate_2d']: - spat_id = self.wv_calib.spat_ids[iord] - ordr_det = self.slits.det_of_slit( - spat_id, self.msarc.det_img, - slit_img=slit_img) - else: - ordr_det = 1 - # Predict the wavelengths - nspec = self.arccen.shape[0] - spec_vec_norm = np.arange(nspec)/float(nspec-1) - wv_order_mod = fit2ds[ordr_det-1].eval(spec_vec_norm, + if np.any(bad_rms): + # Make this outside the for loop.. + bad_orders = self.slits.ech_order[np.where(bad_rms)[0]] + for idet in range(len(dets)): + in_det = np.in1d(bad_orders, order_dets[idet]) + if np.sum(in_det) == 0: + continue + # Are there few enough? + # TODO -- make the scale a parameter + max_bad = int(0.1 * len(order_dets[idet])) + 1 + if np.sum(in_det) > max_bad: + msgs.warn(f"Too many bad orders in detector={dets[idet]} to attempt a refit.") + continue + # Loop + for order in bad_orders[in_det]: + iord = np.where(self.slits.ech_order == order)[0][0] + # Predict the wavelengths + nspec = self.arccen.shape[0] + spec_vec_norm = np.arange(nspec)/float(nspec-1) + wv_order_mod = fit2ds[idet].eval(spec_vec_norm, x2=np.ones_like(spec_vec_norm)*order)/order - # Link me - from importlib import reload - reload(autoid) - tcent, spec_cont_sub, patt_dict_slit, tot_llist = autoid.match_to_arxiv( - self.lamps, self.arccen[:,iord], wv_order_mod, - self.arcspec_arxiv[:, iord], self.wave_soln_arxiv[:,iord], - self.par['nreid_min'], - match_toler=self.par['match_toler'], - nonlinear_counts=self.nonlinear_counts, - sigdetect=wvutils.parse_param(self.par, 'sigdetect', iord), - fwhm=self.par['fwhm']) - - if not patt_dict_slit['acceptable']: - msgs.warn(f"Order {order} is still not acceptable after attempt to reidentify.") - continue - - # Fit me -- RMS may be too high again - n_final = wvutils.parse_param(self.par, 'n_final', iord) - final_fit = wv_fitting.fit_slit( - spec_cont_sub, patt_dict_slit, tcent, tot_llist, - match_toler=self.par['match_toler'], - func=self.par['func'], - n_first=self.par['n_first'], - #n_first=3, - sigrej_first=self.par['sigrej_first'], - #sigrej_first=1.5, - n_final=n_final, - sigrej_final=2.) - #sigrej_final=self.par['sigrej_final']) - print(final_fit['rms']) - - # Keep? - if final_fit['rms'] < self.par['rms_threshold']: - # TODO -- This is repeated from lines 718-725 - # QA - outfile = qa.set_qa_filename( - self.wv_calib.calib_key, 'arc_fit_qa', - slit=order, - out_dir=self.qa_path) - autoid.arc_fit_qa(final_fit, - title=f'Arc Fit QA for slit/order: {order}', - outfile=outfile) - # Save the wavelength solution fits - self.wv_calib.wv_fits[iord] = final_fit - self.wvc_bpm[iord] = False - - - # Do another 2D - fit2ds = self.echelle_2dfit(self.wv_calib, skip_QA = skip_QA, debug=debug) + # Link me + #from importlib import reload + #reload(autoid) + tcent, spec_cont_sub, patt_dict_slit, tot_llist = autoid.match_to_arxiv( + self.lamps, self.arccen[:,iord], wv_order_mod, + self.arcspec_arxiv[:, iord], self.wave_soln_arxiv[:,iord], + self.par['nreid_min'], + match_toler=self.par['match_toler'], + nonlinear_counts=self.nonlinear_counts, + sigdetect=wvutils.parse_param(self.par, 'sigdetect', iord), + fwhm=self.par['fwhm']) + + if not patt_dict_slit['acceptable']: + msgs.warn(f"Order {order} is still not acceptable after attempt to reidentify.") + continue + + # Fit me -- RMS may be too high again + n_final = wvutils.parse_param(self.par, 'n_final', iord) + # TODO - Make this cheaper + final_fit = wv_fitting.fit_slit( + spec_cont_sub, patt_dict_slit, tcent, tot_llist, + match_toler=self.par['match_toler'], + func=self.par['func'], + n_first=self.par['n_first'], + #n_first=3, + sigrej_first=self.par['sigrej_first'], + #sigrej_first=1.5, + n_final=n_final, + sigrej_final=2.) + #sigrej_final=self.par['sigrej_final']) + print(final_fit['rms']) + + # Keep? + # TODO -- Make this a parameter? + increase_rms = 1.5 + if final_fit['rms'] < increase_rms*self.par['rms_threshold']: + # TODO -- This is repeated from lines 718-725 + # QA + outfile = qa.set_qa_filename( + self.wv_calib.calib_key, 'arc_fit_qa', + slit=order, + out_dir=self.qa_path) + autoid.arc_fit_qa(final_fit, + title=f'Arc Fit QA for slit/order: {order}', + outfile=outfile) + # Save the wavelength solution fits + self.wv_calib.wv_fits[iord] = final_fit + self.wvc_bpm[iord] = False + + + # Do another full 2D + embed(header='1035 of wavecalib') + fit2ds = self.echelle_2dfit(self.wv_calib, skip_QA = skip_QA, debug=debug) # Deal with mask self.update_wvmask() From 9ef5fa069fdac8f3ea8373ee18224f8bb0e15b4f Mon Sep 17 00:00:00 2001 From: profxj Date: Thu, 6 Jul 2023 09:27:04 -0700 Subject: [PATCH 08/41] wip --- pypeit/wavecalib.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pypeit/wavecalib.py b/pypeit/wavecalib.py index 72804b63d6..58748d3e72 100644 --- a/pypeit/wavecalib.py +++ b/pypeit/wavecalib.py @@ -821,6 +821,12 @@ def echelle_2dfit(self, wv_calib, debug=False, skip_QA=False): float(iorder))) # Fit + if len(all_orders) < 2: + msgs.warn(f"Fewer than 2 orders to fit for detector {idet}. Skipping") + save_order_dets.append([]) + fit2ds.append(None) + continue + fit2d = arc.fit2darc(all_wave, all_pixel, all_order, nspec, nspec_coeff=self.par['ech_nspec_coeff'], norder_coeff=self.par['ech_norder_coeff'], @@ -1038,7 +1044,6 @@ def run(self, skip_QA=False, debug=False, # Do another full 2D - embed(header='1035 of wavecalib') fit2ds = self.echelle_2dfit(self.wv_calib, skip_QA = skip_QA, debug=debug) # Deal with mask From bd38ce188eb678aab5efa3a49cfb810db1d19253 Mon Sep 17 00:00:00 2001 From: profxj Date: Thu, 6 Jul 2023 15:41:07 -0700 Subject: [PATCH 09/41] closer! --- pypeit/core/arc.py | 2 -- pypeit/core/wavecal/echelle.py | 14 ++++++++++---- pypeit/wavecalib.py | 12 +++++++++--- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/pypeit/core/arc.py b/pypeit/core/arc.py index 830cdf58a4..bfbf34e2b1 100644 --- a/pypeit/core/arc.py +++ b/pypeit/core/arc.py @@ -18,8 +18,6 @@ from pypeit import msgs from pypeit import utils -from pypeit.core.wavecal import wvutils -from pypeit.core.wavecal import wv_fitting from pypeit.core import fitting from IPython import embed diff --git a/pypeit/core/wavecal/echelle.py b/pypeit/core/wavecal/echelle.py index d67c364da5..cac26e9e70 100644 --- a/pypeit/core/wavecal/echelle.py +++ b/pypeit/core/wavecal/echelle.py @@ -208,7 +208,8 @@ def identify_ech_orders(arcspec, echangle, xdangle, dispname, # Predict the echelle order coverage and wavelength solution order_vec_guess, wave_soln_guess_pad, arcspec_guess_pad = predict_ech_arcspec( - angle_fits_file, composite_arc_file, echangle, xdangle, dispname, nspec, norders, pad=pad) + angle_fits_file, composite_arc_file, echangle, xdangle, dispname, + nspec, norders, pad=pad) norders_guess = order_vec_guess.size # Since we padded the guess we need to pad the data to the same size @@ -222,15 +223,20 @@ def identify_ech_orders(arcspec, echangle, xdangle, dispname, percent_ceil=50.0, sigdetect=5.0, sig_ceil=10.0, fwhm=4.0, debug=debug) # Finish - x_ordr_shift = shift_cc / nspec ordr_shift = int(np.round(shift_cc / nspec)) spec_shift = int(np.round(shift_cc - ordr_shift * nspec)) - msgs.info('Shift in order number between prediction and reddest order: {:.3f}'.format(ordr_shift + pad)) + msgs.info('Shift in order number between prediction and reddest order: {:.3f}'.format( + ordr_shift + pad)) msgs.info('Shift in spectral pixels between prediction and data: {:.3f}'.format(spec_shift)) - order_vec = order_vec_guess[-1] - ordr_shift + np.arange(norders)[::-1] + # Assign + #order_vec = order_vec_guess[-1] - ordr_shift + np.arange(norders)[::-1] + order_vec = order_vec_guess[0] + ordr_shift - np.arange(norders) ind = np.isin(order_vec_guess, order_vec, assume_unique=True) + if debug: + embed(header='identify_ech_orders 232 of echelle.py') + # Return return order_vec, wave_soln_guess_pad[:, ind], arcspec_guess_pad[:, ind] diff --git a/pypeit/wavecalib.py b/pypeit/wavecalib.py index 58748d3e72..5cfac04eed 100644 --- a/pypeit/wavecalib.py +++ b/pypeit/wavecalib.py @@ -821,10 +821,11 @@ def echelle_2dfit(self, wv_calib, debug=False, skip_QA=False): float(iorder))) # Fit - if len(all_orders) < 2: + if len(all_order) < 2: msgs.warn(f"Fewer than 2 orders to fit for detector {idet}. Skipping") save_order_dets.append([]) - fit2ds.append(None) + # Add a dummy fit + fit2ds.append(fitting.PypeItFit()) continue fit2d = arc.fit2darc(all_wave, all_pixel, all_order, nspec, @@ -974,6 +975,7 @@ def run(self, skip_QA=False, debug=False, if np.any(bad_rms): # Make this outside the for loop.. bad_orders = self.slits.ech_order[np.where(bad_rms)[0]] + fixed = False for idet in range(len(dets)): in_det = np.in1d(bad_orders, order_dets[idet]) if np.sum(in_det) == 0: @@ -1041,10 +1043,14 @@ def run(self, skip_QA=False, debug=False, # Save the wavelength solution fits self.wv_calib.wv_fits[iord] = final_fit self.wvc_bpm[iord] = False + fixed = True # Do another full 2D - fit2ds = self.echelle_2dfit(self.wv_calib, skip_QA = skip_QA, debug=debug) + if fixed: + fit2ds, _, _ = self.echelle_2dfit(self.wv_calib, skip_QA = skip_QA, debug=debug) + # Save + self.wv_calib.wv_fit2d = np.array(fit2ds) # Deal with mask self.update_wvmask() From db75b5934eb3e252dddfc2aa09fa45858f1b8bab Mon Sep 17 00:00:00 2001 From: profxj Date: Thu, 6 Jul 2023 16:07:43 -0700 Subject: [PATCH 10/41] id fix (I hope) --- pypeit/wavecalib.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pypeit/wavecalib.py b/pypeit/wavecalib.py index 5cfac04eed..6851ef4322 100644 --- a/pypeit/wavecalib.py +++ b/pypeit/wavecalib.py @@ -195,6 +195,7 @@ def _parse(cls, hdu, **kwargs): parsed_hdus += ihdu.name # Check if spat_ids != _d['spat_ids'].tolist(): + embed(header="198 of wavecalib.py") msgs.error("Bad parsing of WaveCalib") # Finish _d['wv_fits'] = np.asarray(list_of_wave_fits) @@ -597,6 +598,9 @@ def build_wv_calib(self, arccen, method, skip_QA=False, # (i.e. the midpoint in both the spectral and spatial directions) measured_fwhms[islit] = fwhm_map[islit].eval(self.msarc.image.shape[0]//2, 0.5) + # Save for redo's + self.measured_fwhms = measured_fwhms + # Obtain calibration for all slits if method == 'holy-grail': # Sometimes works, sometimes fails @@ -1040,6 +1044,9 @@ def run(self, skip_QA=False, debug=False, autoid.arc_fit_qa(final_fit, title=f'Arc Fit QA for slit/order: {order}', outfile=outfile) + # This is for I/O naming + final_fit.spat_id = self.slits.spat_id[iord] + final_fit.fwhm = self.measured_fwhms[iord] # Save the wavelength solution fits self.wv_calib.wv_fits[iord] = final_fit self.wvc_bpm[iord] = False @@ -1066,6 +1073,8 @@ def run(self, skip_QA=False, debug=False, j_par = jsonify(sv_par) self.wv_calib['strpar'] = json.dumps(j_par)#, sort_keys=True, indent=4, separators=(',', ': ')) + embed(header='end of run 1070 wavecalib') + return self.wv_calib From 8a8217653e34ff33b57ee0a7245bbdd46955146e Mon Sep 17 00:00:00 2001 From: profxj Date: Fri, 7 Jul 2023 13:10:58 -0700 Subject: [PATCH 11/41] mo --- pypeit/wavecalib.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pypeit/wavecalib.py b/pypeit/wavecalib.py index 6851ef4322..cabe89a0b6 100644 --- a/pypeit/wavecalib.py +++ b/pypeit/wavecalib.py @@ -1073,8 +1073,6 @@ def run(self, skip_QA=False, debug=False, j_par = jsonify(sv_par) self.wv_calib['strpar'] = json.dumps(j_par)#, sort_keys=True, indent=4, separators=(',', ': ')) - embed(header='end of run 1070 wavecalib') - return self.wv_calib From e9ebbf917aa8067ce8d5d24d1e0206fd8f20b7a9 Mon Sep 17 00:00:00 2001 From: profxj Date: Mon, 10 Jul 2023 08:25:11 -0700 Subject: [PATCH 12/41] good progress --- pypeit/par/pypeitpar.py | 4 ++-- pypeit/spectrographs/keck_esi.py | 6 +++++- pypeit/spectrographs/magellan_mage.py | 6 +++++- pypeit/spectrographs/vlt_xshooter.py | 16 +++++++++------- pypeit/wavecalib.py | 4 +++- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/pypeit/par/pypeitpar.py b/pypeit/par/pypeitpar.py index b01e1071f8..98309a611c 100644 --- a/pypeit/par/pypeitpar.py +++ b/pypeit/par/pypeitpar.py @@ -2679,8 +2679,8 @@ def __init__(self, reference=None, method=None, echelle=None, ech_nspec_coeff=No defaults['rms_threshold'] = 0.15 dtypes['rms_threshold'] = [float, list, np.ndarray] descr['rms_threshold'] = 'Minimum RMS for keeping a slit/order solution. This can be a ' \ - 'single number or a list/array providing the value for each slit. ' \ - 'Only used if ``method`` is either \'holy-grail\' or \'reidentify\'' + 'single number or a list/array providing the value for each order. ' \ + 'Only used if ``method`` for \'reidentify\' and echelle spectrographs' defaults['match_toler'] = 2.0 dtypes['match_toler'] = float diff --git a/pypeit/spectrographs/keck_esi.py b/pypeit/spectrographs/keck_esi.py index 55a5f7f801..0ec218277f 100644 --- a/pypeit/spectrographs/keck_esi.py +++ b/pypeit/spectrographs/keck_esi.py @@ -90,7 +90,11 @@ def default_pypeit_par(cls): #par['calibrations']['biasframe']['useframe'] = 'overscan' # Wavelengths # 1D wavelength solution - par['calibrations']['wavelengths']['rms_threshold'] = 0.20 # Might be grating dependent.. + # This is for 1x1 + par['calibrations']['wavelengths']['rms_threshold'] = 0.30 + par['calibrations']['wavelengths']['fwhm'] = 2.9 + par['calibrations']['wavelengths']['fwhm_fromlines'] = True + # par['calibrations']['wavelengths']['sigdetect'] = 5.0 par['calibrations']['wavelengths']['lamps'] = ['CuI', 'ArI', 'NeI', 'HgI', 'XeI', 'ArII'] diff --git a/pypeit/spectrographs/magellan_mage.py b/pypeit/spectrographs/magellan_mage.py index c1edba2a3f..460a2cade0 100644 --- a/pypeit/spectrographs/magellan_mage.py +++ b/pypeit/spectrographs/magellan_mage.py @@ -94,7 +94,11 @@ def default_pypeit_par(cls): #par['calibrations']['biasframe']['useframe'] = 'overscan' # Wavelengths # 1D wavelength solution - par['calibrations']['wavelengths']['rms_threshold'] = 0.20 # Might be grating dependent.. + # The following is for 1x1 binning + par['calibrations']['wavelengths']['rms_threshold'] = 0.30 + par['calibrations']['wavelengths']['fwhm'] = 3.0 + par['calibrations']['wavelengths']['fwhm_fromlines'] = True + # par['calibrations']['wavelengths']['sigdetect'] = 5.0 par['calibrations']['wavelengths']['lamps'] = ['ThAr_MagE'] diff --git a/pypeit/spectrographs/vlt_xshooter.py b/pypeit/spectrographs/vlt_xshooter.py index 7c4fbc05e6..1b9c50234d 100644 --- a/pypeit/spectrographs/vlt_xshooter.py +++ b/pypeit/spectrographs/vlt_xshooter.py @@ -662,15 +662,14 @@ def default_pypeit_par(cls): # 1D wavelength solution par['calibrations']['wavelengths']['lamps'] = ['ThAr_XSHOOTER_VIS'] # The following is for 1x1 binning. TODO GET BINNING SORTED OUT!! - par['calibrations']['wavelengths']['rms_threshold'] = 0.50 + par['calibrations']['wavelengths']['rms_threshold'] = 1.2 + par['calibrations']['wavelengths']['fwhm'] = 8.0 + par['calibrations']['wavelengths']['fwhm_fromlines'] = True + # par['calibrations']['wavelengths']['sigdetect'] = 5.0 par['calibrations']['wavelengths']['n_final'] = [3] + 13*[4] + [3] - # This is for 1x1 binning. Needs to be divided by binning for binned data!! - par['calibrations']['wavelengths']['fwhm'] = 11.0 # Reidentification parameters par['calibrations']['wavelengths']['method'] = 'reidentify' - # TODO: the arxived solution is for 1x1 binning. It needs to be - # generalized for different binning! par['calibrations']['wavelengths']['reid_arxiv'] = 'vlt_xshooter_vis1x1.fits' par['calibrations']['wavelengths']['cc_thresh'] = 0.50 par['calibrations']['wavelengths']['cc_local_thresh'] = 0.50 @@ -680,7 +679,6 @@ def default_pypeit_par(cls): par['calibrations']['wavelengths']['ech_nspec_coeff'] = 4 par['calibrations']['wavelengths']['ech_norder_coeff'] = 4 par['calibrations']['wavelengths']['ech_sigrej'] = 3.0 - #par['calibrations']['wavelengths']['fwhm_fromlines'] = True par['calibrations']['wavelengths']['qa_log'] = True @@ -936,7 +934,11 @@ def default_pypeit_par(cls): # 1D wavelength solution par['calibrations']['wavelengths']['lamps'] = ['ThAr_XSHOOTER_UVB'] par['calibrations']['wavelengths']['n_final'] = [3] + 10*[4] - par['calibrations']['wavelengths']['rms_threshold'] = 0.60 + # This is for 1x1 + par['calibrations']['wavelengths']['rms_threshold'] = 0.70 + par['calibrations']['wavelengths']['fwhm'] = 3.8 + par['calibrations']['wavelengths']['fwhm_fromlines'] = True + # par['calibrations']['wavelengths']['sigdetect'] = 3.0 # Pretty faint lines in places # Reidentification parameters par['calibrations']['wavelengths']['method'] = 'reidentify' diff --git a/pypeit/wavecalib.py b/pypeit/wavecalib.py index e0d679ce1e..753a615e23 100644 --- a/pypeit/wavecalib.py +++ b/pypeit/wavecalib.py @@ -965,7 +965,9 @@ def run(self, skip_QA=False, debug=False, for wvfit in self.wv_calib.wv_fits: rms.append(wvfit.rms if wvfit.rms is not None else 999.) rms = np.array(rms) - bad_rms = rms > self.par['rms_threshold'] + # Test and scale by measured_fwhms + bad_rms = rms > (self.par['rms_threshold'] * np.median(self.measured_fwhms)/self.par['fwhm']) + embed(header='line 968 of wavecalib.py') self.wvc_bpm[bad_rms] = True if np.any(bad_rms): msgs.warn("Masking one or more bad orders (RMS)") From 7463cdddebc7b70627738a435dcfc5e45a990b5f Mon Sep 17 00:00:00 2001 From: profxj Date: Mon, 10 Jul 2023 08:30:38 -0700 Subject: [PATCH 13/41] polishing --- pypeit/core/wavecal/autoid.py | 11 +---------- pypeit/spectrographs/magellan_mage.py | 2 +- pypeit/wavecalib.py | 3 +-- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/pypeit/core/wavecal/autoid.py b/pypeit/core/wavecal/autoid.py index 1fc5961d56..e93bf48f12 100644 --- a/pypeit/core/wavecal/autoid.py +++ b/pypeit/core/wavecal/autoid.py @@ -447,7 +447,7 @@ def reidentify(spec, spec_arxiv_in, wave_soln_arxiv_in, line_list, sigdetect: float, default = 5.0 Threshold for detecting arcliens - fwfm: float, default = 4.0 + fwhm: float, default = 4.0 Full width at half maximum for the arc lines Returns @@ -1324,15 +1324,6 @@ def echelle_wvcalib(spec, orders, spec_arxiv, wave_arxiv, lamps, par, n_final=n_final, sigrej_final=par['sigrej_final']) - #final_fit = wv_fitting.fit_slit( - # spec_cont_sub[:, iord], all_patt_dict[str(iord)], - # detections[str(iord)], tot_line_list, - # match_toler=par['match_toler'], - # func=par['func'], n_first=3, - # sigrej_first=2.0, - # n_final=n_final, - # sigrej_final=2.5) - # Did the fit succeed? if final_fit is None: # This pattern wasn't good enough diff --git a/pypeit/spectrographs/magellan_mage.py b/pypeit/spectrographs/magellan_mage.py index 460a2cade0..ee239b28ce 100644 --- a/pypeit/spectrographs/magellan_mage.py +++ b/pypeit/spectrographs/magellan_mage.py @@ -95,7 +95,7 @@ def default_pypeit_par(cls): # Wavelengths # 1D wavelength solution # The following is for 1x1 binning - par['calibrations']['wavelengths']['rms_threshold'] = 0.30 + par['calibrations']['wavelengths']['rms_threshold'] = 0.40 par['calibrations']['wavelengths']['fwhm'] = 3.0 par['calibrations']['wavelengths']['fwhm_fromlines'] = True # diff --git a/pypeit/wavecalib.py b/pypeit/wavecalib.py index 753a615e23..32defab0b4 100644 --- a/pypeit/wavecalib.py +++ b/pypeit/wavecalib.py @@ -967,7 +967,7 @@ def run(self, skip_QA=False, debug=False, rms = np.array(rms) # Test and scale by measured_fwhms bad_rms = rms > (self.par['rms_threshold'] * np.median(self.measured_fwhms)/self.par['fwhm']) - embed(header='line 968 of wavecalib.py') + #embed(header='line 968 of wavecalib.py') self.wvc_bpm[bad_rms] = True if np.any(bad_rms): msgs.warn("Masking one or more bad orders (RMS)") @@ -981,7 +981,6 @@ def run(self, skip_QA=False, debug=False, if self.par['ech_separate_2d']: self.wv_calib.det_img = self.msarc.det_img.copy() - # TODO This is work in progress by ProfX # Try a second attempt with 1D, if needed if np.any(bad_rms): # Make this outside the for loop.. From d8e314f61ebecd35ea12c386f6a58783c1e5ee66 Mon Sep 17 00:00:00 2001 From: profxj Date: Mon, 10 Jul 2023 08:41:41 -0700 Subject: [PATCH 14/41] docs --- doc/calibrations/wave_calib.rst | 28 ++++++++++++++++++++++++++++ doc/spectrographs/keck_hires.rst | 18 ++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 doc/spectrographs/keck_hires.rst diff --git a/doc/calibrations/wave_calib.rst b/doc/calibrations/wave_calib.rst index 01130132b5..e12c02ac44 100644 --- a/doc/calibrations/wave_calib.rst +++ b/doc/calibrations/wave_calib.rst @@ -259,6 +259,34 @@ observations, long-slit observations where wavelengths vary (*e.g.*, grating tilts). We are likely to implement this for echelle observations (*e.g.*, HIRES). +.. _wvcalib-echelle: + +Echelle Spectrographs +===================== + +Echelle spectrographs are a special case for wavelength +solutions, primarily because the orders follow the +grating equation. + +In general, the approach is: + + 1. Identify the arc lines in each order + 2. Fit the arc lines in each order to a polynomial, individually + 3. Fit a 2D solution to the lines using the order number as a basis + 4. Reject orders where the RMS of the fit exceeds ``rms_threshold`` + 5. Attempt to recover the missing orders using the 2D fit and + a higher RMS threshold + 6. Refit the 2D solution + +One should always inspect the outputs, especially the 2D solution +(global and orders). One may then modify the ``rms_threshold`` +and/or hand-fit a few of the orders to improve the solution. + +For echelle spectrographs with multiple detectors *per* camera +that are mosaiced (e.g. Keck/HIRES), we fit the 2D solutions on a *per* detector +basis. Ths is because we have found the mosaic solutions to be +too difficult to make sufficiently accurate. + .. _wvcalib-byhand: By-Hand Approach diff --git a/doc/spectrographs/keck_hires.rst b/doc/spectrographs/keck_hires.rst new file mode 100644 index 0000000000..210b93e29c --- /dev/null +++ b/doc/spectrographs/keck_hires.rst @@ -0,0 +1,18 @@ +========== +Keck HIRES +========== + +Overview +======== + +This file summarizes several instrument specific settings that are related to the Keck/HIRES spectrograph. + + +Wavelengths +=========== + +See :ref:`wvcalib-echelle` for details on the wavelength calibration. + +We also note that Order 45 is frequently flagged +as bad in the wavelength solution. This is due to, in part, +to very bright ThAr line contamination. \ No newline at end of file From e1327b1ff26af08745435b237dc00d95c3e2879c Mon Sep 17 00:00:00 2001 From: profxj Date: Thu, 13 Jul 2023 05:43:45 -0700 Subject: [PATCH 15/41] 2d --- pypeit/wavecalib.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pypeit/wavecalib.py b/pypeit/wavecalib.py index 32defab0b4..9a9c42df1b 100644 --- a/pypeit/wavecalib.py +++ b/pypeit/wavecalib.py @@ -1065,6 +1065,9 @@ def run(self, skip_QA=False, debug=False, # Save self.wv_calib.wv_fit2d = np.array(fit2ds) + # Check that we have at least one good 2D fit + embed(header='line 1070 of wavecalib.py') + # Deal with mask self.update_wvmask() From 71f2cb3af2c2823eb44193b1b8ef8917d986c716 Mon Sep 17 00:00:00 2001 From: profxj Date: Thu, 13 Jul 2023 05:52:17 -0700 Subject: [PATCH 16/41] nires fixes --- pypeit/spectrographs/keck_nires.py | 5 +++-- pypeit/wavecalib.py | 7 ++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/pypeit/spectrographs/keck_nires.py b/pypeit/spectrographs/keck_nires.py index cf8fdb5c37..6d5bd374f2 100644 --- a/pypeit/spectrographs/keck_nires.py +++ b/pypeit/spectrographs/keck_nires.py @@ -80,9 +80,10 @@ def default_pypeit_par(cls): # Wavelengths # 1D wavelength solution - par['calibrations']['wavelengths']['rms_threshold'] = 0.20 #0.20 # Might be grating dependent.. + par['calibrations']['wavelengths']['rms_threshold'] = 0.30 par['calibrations']['wavelengths']['sigdetect']=5.0 - par['calibrations']['wavelengths']['fwhm']= 5.0 + par['calibrations']['wavelengths']['fwhm']= 2.2 # Measured + par['calibrations']['wavelengths']['fwhm_fromlines'] = True par['calibrations']['wavelengths']['n_final']= [3,4,4,4,4] par['calibrations']['wavelengths']['lamps'] = ['OH_NIRES'] #par['calibrations']['wavelengths']['nonlinear_counts'] = self.detector[0]['nonlinear'] * self.detector[0]['saturation'] diff --git a/pypeit/wavecalib.py b/pypeit/wavecalib.py index 9a9c42df1b..2d76b56e2b 100644 --- a/pypeit/wavecalib.py +++ b/pypeit/wavecalib.py @@ -1066,7 +1066,12 @@ def run(self, skip_QA=False, debug=False, self.wv_calib.wv_fit2d = np.array(fit2ds) # Check that we have at least one good 2D fit - embed(header='line 1070 of wavecalib.py') + chk_fit2d = False + for fit2d in self.wv_calib.wv_fit2d: + if fit2d.success: + chk_fit2d = True + if not chk_fit2d: + msgs.error("No successful 2D Wavelength fits. Cannot proceed.") # Deal with mask self.update_wvmask() From 5c2ae309d5986ba0d9f7df77c0e482b15e48c283 Mon Sep 17 00:00:00 2001 From: profxj Date: Thu, 13 Jul 2023 06:19:33 -0700 Subject: [PATCH 17/41] debuggin --- pypeit/wavecalib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pypeit/wavecalib.py b/pypeit/wavecalib.py index c983558c2f..ef4c9de80d 100644 --- a/pypeit/wavecalib.py +++ b/pypeit/wavecalib.py @@ -972,7 +972,7 @@ def run(self, skip_QA=False, debug=False, rms = np.array(rms) # Test and scale by measured_fwhms bad_rms = rms > (self.par['rms_threshold'] * np.median(self.measured_fwhms)/self.par['fwhm']) - #embed(header='line 968 of wavecalib.py') + embed(header='line 975 of wavecalib.py') self.wvc_bpm[bad_rms] = True if np.any(bad_rms): msgs.warn("Masking one or more bad orders (RMS)") From 97627dd5147f659c5787695540521b6ba143ae03 Mon Sep 17 00:00:00 2001 From: profxj Date: Thu, 13 Jul 2023 06:27:04 -0700 Subject: [PATCH 18/41] debuggin continues --- pypeit/spectrographs/p200_tspec.py | 3 ++- pypeit/wavecalib.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pypeit/spectrographs/p200_tspec.py b/pypeit/spectrographs/p200_tspec.py index 0100fc42ff..495f85472d 100644 --- a/pypeit/spectrographs/p200_tspec.py +++ b/pypeit/spectrographs/p200_tspec.py @@ -160,7 +160,8 @@ def default_pypeit_par(cls): # 1D wavelength solution par['calibrations']['wavelengths']['rms_threshold'] = 0.3 par['calibrations']['wavelengths']['sigdetect']=5.0 - par['calibrations']['wavelengths']['fwhm']= 5.0 + par['calibrations']['wavelengths']['fwhm']= 2.9 # As measured in DevSuite + par['calibrations']['wavelengths']['fwhm_fromlines'] = True par['calibrations']['wavelengths']['n_final']= [3,4,4,4,4] par['calibrations']['wavelengths']['lamps'] = ['OH_NIRES'] par['calibrations']['wavelengths']['method'] = 'reidentify' diff --git a/pypeit/wavecalib.py b/pypeit/wavecalib.py index ef4c9de80d..80509cb0be 100644 --- a/pypeit/wavecalib.py +++ b/pypeit/wavecalib.py @@ -972,7 +972,7 @@ def run(self, skip_QA=False, debug=False, rms = np.array(rms) # Test and scale by measured_fwhms bad_rms = rms > (self.par['rms_threshold'] * np.median(self.measured_fwhms)/self.par['fwhm']) - embed(header='line 975 of wavecalib.py') + #embed(header='line 975 of wavecalib.py') self.wvc_bpm[bad_rms] = True if np.any(bad_rms): msgs.warn("Masking one or more bad orders (RMS)") From 35c3d3a4cb3551d0fa176e3603b4924fc546984a Mon Sep 17 00:00:00 2001 From: "J. Xavier Prochaska" Date: Thu, 20 Jul 2023 16:15:01 -0700 Subject: [PATCH 19/41] PR edits --- CHANGES.rst | 3 ++ pypeit/calibrations.py | 4 +- pypeit/core/wavecal/autoid.py | 93 +++++++++++++-------------------- pypeit/core/wavecal/echelle.py | 4 +- pypeit/core/wavecal/patterns.py | 3 +- pypeit/core/wavecal/waveio.py | 35 ++++++++----- pypeit/par/pypeitpar.py | 2 +- pypeit/wavecalib.py | 54 +++++++++---------- 8 files changed, 94 insertions(+), 104 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 4fc3aaced0..b52259e880 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -32,6 +32,9 @@ - Now ``only_slits`` parameter in `pypeit_coadd_2dspec` includes the detector number (similar to ``slitspatnum``) - Added ``exclude_slits`` parameter in `pypeit_coadd_2dspec` to exclude specific slits - Fix wrong RA & Dec for 2D coadded serendips +- HIRES wavelength solution improvements galor +- Added `redo_slits` option +- Refactored ``load_line_lists()`` yet again! 1.13.0 (2 June 2023) diff --git a/pypeit/calibrations.py b/pypeit/calibrations.py index 0d42324aa9..a1ccf8ad91 100644 --- a/pypeit/calibrations.py +++ b/pypeit/calibrations.py @@ -863,7 +863,9 @@ def get_wv_calib(self): self.wv_calib = waveCalib.run(skip_QA=(not self.write_qa), prev_wvcalib=self.wv_calib) # If orders were found, save slits to disk - if self.spectrograph.pypeline == 'Echelle' and not self.spectrograph.ech_fixed_format: + # or if redo_slits + if (self.par['wavelengths']['redo_slits'] is not None) or ( + self.spectrograph.pypeline == 'Echelle' and not self.spectrograph.ech_fixed_format): self.slits.to_file() # Save calibration frame self.wv_calib.to_file() diff --git a/pypeit/core/wavecal/autoid.py b/pypeit/core/wavecal/autoid.py index 50a90642f9..fcbc64d1e7 100644 --- a/pypeit/core/wavecal/autoid.py +++ b/pypeit/core/wavecal/autoid.py @@ -699,9 +699,11 @@ def match_to_arxiv(lamps:list, spec:np.ndarray, wv_guess:np.ndarray, match_toler=2.0, nonlinear_counts=1e10, sigdetect=5.0, fwhm=4.0, debug_peaks:bool=False, use_unknowns:bool=False): """ Algorithm to match an input arc spectrum to an archival arc spectrum - using a set wavelength guess for the input + using a set wavelength guess for the input. This is an alternative to + shifting/stretching to match to the archival arc spectrum as we (hopefully) + have a good guess of the wavelength solution for the input spectrum. - Used only missing orders of echelle spectrographs (so far) + Used only for missing orders of echelle spectrographs (so far) Args: lamps (list): List of lamps used in the arc @@ -732,20 +734,12 @@ def match_to_arxiv(lamps:list, spec:np.ndarray, wv_guess:np.ndarray, If True, use the unknowns in the solution (not recommended) Returns: - _type_: _description_ + tuple: tcent (np.ndarray; centroid of lines), spec_cont_sub (np.ndarray; subtracted continuum), + patt_dict_slit (dict; dictionary on the lines), tot_line_list (astropy.table.Table; line list) """ + # Load line list + tot_line_list, _, _ = waveio.load_line_lists(lamps, include_unknown=use_unknowns) - # TODO -- The next 10 lines of code is duplicated from echelle_wvcalib - # Load the line lists - if 'ThAr' in lamps: - line_lists_all = waveio.load_line_lists(lamps) - line_lists = line_lists_all[np.where(line_lists_all['ion'] != 'UNKNWN')] - unknwns = line_lists_all[np.where(line_lists_all['ion'] == 'UNKNWN')] - else: - line_lists = waveio.load_line_lists(lamps) - unknwns = waveio.load_unknown_list(lamps) - - tot_line_list = astropy.table.vstack([line_lists, unknwns]) if use_unknowns else line_lists # Generate the wavelengths from the line list and sort wvdata = np.array(tot_line_list['wave'].data) # Removes mask if any @@ -772,7 +766,7 @@ def match_to_arxiv(lamps:list, spec:np.ndarray, wv_guess:np.ndarray, fwv_guess = scipy.interpolate.interp1d(np.arange(len(wv_guess)), wv_guess, kind='cubic', bounds_error=False, fill_value='extrapolate') - # Interpolate the axiv both ways + # Interpolate the arxiv both ways fpix_arxiv = scipy.interpolate.interp1d(wave_arxiv, np.arange(len(wave_arxiv)), kind='cubic', bounds_error=False, fill_value='extrapolate') @@ -1019,11 +1013,7 @@ def full_template(spec, lamps, par, ok_mask, det, binspectral, nsnippet=2, #debug_xcorr = True #debug_reid = True # Load line lists - if 'ThAr' in lamps: - line_lists_all = waveio.load_line_lists(lamps) - line_lists = line_lists_all[np.where(line_lists_all['ion'] != 'UNKNWN')] - else: - line_lists = waveio.load_line_lists(lamps) + line_lists, _, _ = waveio.load_line_lists(lamps, include_unknown=False) # Load template if template_dict is None: @@ -1265,15 +1255,7 @@ def echelle_wvcalib(spec, orders, spec_arxiv, wave_arxiv, lamps, par, 'spec array.') # Load the line lists - if 'ThAr' in lamps: - line_lists_all = waveio.load_line_lists(lamps) - line_lists = line_lists_all[np.where(line_lists_all['ion'] != 'UNKNWN')] - unknwns = line_lists_all[np.where(line_lists_all['ion'] == 'UNKNWN')] - else: - line_lists = waveio.load_line_lists(lamps) - unknwns = waveio.load_unknown_list(lamps) - - tot_line_list = astropy.table.vstack([line_lists, unknwns]) if use_unknowns else line_lists + tot_line_list, _, _ = waveio.load_line_lists(lamps, include_unknown=use_unknowns) # Array to hold continuum subtracted arcs spec_cont_sub = np.zeros_like(spec) @@ -1306,8 +1288,8 @@ def echelle_wvcalib(spec, orders, spec_arxiv, wave_arxiv, lamps, par, debug_xcorr=(debug_xcorr or debug_all), debug_reid=(debug_reid or debug_all)) # Perform the fit - if debug_fits or debug_all: - embed(header='1115 of autoid') + #if debug_fits or debug_all: + # embed(header='1115 of autoid') # Check if an acceptable reidentification solution was found if not all_patt_dict[str(iord)]['acceptable']: wv_calib[str(iord)] = None @@ -1547,16 +1529,19 @@ def __init__(self, spec, lamps, par, ech_fixed_format=False, ok_mask=None, meas self.sigrej_final= self.par['sigrej_final'] # Load the line lists - if 'ThAr' in self.lamps: - line_lists_all = waveio.load_line_lists(self.lamps) - self.line_lists = line_lists_all[np.where(line_lists_all['ion'] != 'UNKNWN')] - self.unknwns = line_lists_all[np.where(line_lists_all['ion'] == 'UNKNWN')] - else: - self.line_lists = waveio.load_line_lists(self.lamps) - self.unknwns = waveio.load_unknown_list(self.lamps) - - self.tot_line_list = astropy.table.vstack([self.line_lists, self.unknwns]) if self.use_unknowns \ - else self.line_lists + self.tot_line_list, self.line_lists, self.unknwns = waveio.load_line_lists( + lamps, include_unknown=self.use_unknowns) + + #if 'ThAr' in self.lamps: + # line_lists_all = waveio.load_line_lists(self.lamps) + # self.line_lists = line_lists_all[np.where(line_lists_all['ion'] != 'UNKNWN')] + # self.unknwns = line_lists_all[np.where(line_lists_all['ion'] == 'UNKNWN')] + #else: + # self.line_lists = waveio.load_line_lists(self.lamps) + # self.unknwns = waveio.load_unknown_list(self.lamps) + + #self.tot_line_list = astropy.table.vstack([self.line_lists, self.unknwns]) if self.use_unknowns \ + # else self.line_lists # Read in the wv_calib_arxiv and pull out some relevant quantities # ToDO deal with different binnings! @@ -1686,7 +1671,7 @@ class HolyGrail: ok_mask : ndarray, optional Array of good slits islinelist : bool, optional - Is lines a linelist (True), or a list of ions (False) + Is lamps a linelist (True), or a list of ions (False) outroot : str, optional Name of output file debug : bool, optional @@ -1758,25 +1743,21 @@ def __init__(self, spec, lamps, par=None, ok_mask=None, islinelist=False, self._debug = debug self._verbose = verbose - # Load the linelist to be used for pattern matching + # Line list provided? (not recommended) if self._islinelist: self._line_lists = self._lamps self._unknwns = self._lamps[:0].copy() - else: - if 'ThAr' in self._lamps: - line_lists_all = waveio.load_line_lists(self._lamps) - self._line_lists = line_lists_all[np.where(line_lists_all['ion'] != 'UNKNWN')] - self._unknwns = line_lists_all[np.where(line_lists_all['ion'] == 'UNKNWN')] + if self._use_unknowns: + self._tot_list = astropy.table.vstack([self._line_lists, self._unknwns]) else: - restrict = spectrograph if self._par['use_instr_flag'] else None - self._line_lists = waveio.load_line_lists( - self._lamps, restrict_on_instr=restrict) - self._unknwns = waveio.load_unknown_list(self._lamps) - - if self._use_unknowns: - self._tot_list = astropy.table.vstack([self._line_lists, self._unknwns]) + self._tot_list = self._line_lists else: - self._tot_list = self._line_lists + # Load the linelist to be used for pattern matching + restrict = spectrograph if self._par['use_instr_flag'] else None + self._tot_list, self._line_lists, self._unknwns = waveio.load_line_lists( + self._lamps, include_unknown=self._use_unknowns, + restrict_on_instr=restrict) + # Generate the final linelist and sort self._wvdata = np.array(self._tot_list['wave'].data) # Removes mask if any diff --git a/pypeit/core/wavecal/echelle.py b/pypeit/core/wavecal/echelle.py index cac26e9e70..54ac8ed78f 100644 --- a/pypeit/core/wavecal/echelle.py +++ b/pypeit/core/wavecal/echelle.py @@ -234,8 +234,8 @@ def identify_ech_orders(arcspec, echangle, xdangle, dispname, order_vec = order_vec_guess[0] + ordr_shift - np.arange(norders) ind = np.isin(order_vec_guess, order_vec, assume_unique=True) - if debug: - embed(header='identify_ech_orders 232 of echelle.py') + #if debug: + # embed(header='identify_ech_orders 232 of echelle.py') # Return return order_vec, wave_soln_guess_pad[:, ind], arcspec_guess_pad[:, ind] diff --git a/pypeit/core/wavecal/patterns.py b/pypeit/core/wavecal/patterns.py index 46b16a9325..d25f26ba8b 100644 --- a/pypeit/core/wavecal/patterns.py +++ b/pypeit/core/wavecal/patterns.py @@ -635,8 +635,9 @@ def solve_xcorr(detlines, linelist, dindex, lindex, line_cc, lindex : `numpy.ndarray`_ Index array of the assigned line (wavelengths)to each index in dindex line_cc : `numpy.ndarray`_ - ?? + local cross correlation coefficient computed for each line cc_local_thresh : float, default = 0.8, optional + Threshold to satisy for local cross-correlation Returns ------- diff --git a/pypeit/core/wavecal/waveio.py b/pypeit/core/wavecal/waveio.py index 2e524543cb..3ed2f2dc3c 100644 --- a/pypeit/core/wavecal/waveio.py +++ b/pypeit/core/wavecal/waveio.py @@ -156,7 +156,7 @@ def load_line_list(line_file, use_ion=False): return astropy.table.Table.read(line_file, format='ascii.fixed_width', comment='#') -def load_line_lists(lamps, unknown=False, all=False, restrict_on_instr=None): +def load_line_lists(lamps, all=False, include_unknown:bool=False, restrict_on_instr=None): """ Loads a series of line list files @@ -165,14 +165,18 @@ def load_line_lists(lamps, unknown=False, all=False, restrict_on_instr=None): lamps : list List of arc lamps to be used for wavelength calibration. E.g., ['ArI','NeI','KrI','XeI'] - unknown : bool, optional - Load the unknown list restrict_on_instr : str, optional Restrict according to the input spectrograph + all : bool, optional + Load all line lists, independent of the input lamps (not recommended) + include_unknown : bool, optional + If True, the tot_line_list includes the unknown lines Returns ------- - line_list : Table + tot_line_list : astropy Table of line lists (including unknown lines, if requested) + line_list : astropy Table of line lists + unkn_lines : astropy Table of unknown lines """ # All? @@ -194,23 +198,28 @@ def load_line_lists(lamps, unknown=False, all=False, restrict_on_instr=None): # Stack if len(lists) == 0: return None - line_lists = astropy.table.vstack(lists, join_type='exact') + line_lists_all = astropy.table.vstack(lists, join_type='exact') # Restrict on the spectrograph? if restrict_on_instr is not None: instr_dict = defs.instruments() - gdI = (line_lists['Instr'] & instr_dict[restrict_on_instr]) > 0 - line_lists = line_lists[gdI] + gdI = (line_lists_all['Instr'] & instr_dict[restrict_on_instr]) > 0 + line_lists_all = line_lists_all[gdI] - # Unknown - if unknown: + # Load Unknowns + if 'ThAr' in lamps: + line_lists = line_lists_all[np.where(line_lists_all['ion'] != 'UNKNWN')] + unkn_lines = line_lists_all[np.where(line_lists_all['ion'] == 'UNKNWN')] + else: + line_lists = line_lists_all unkn_lines = load_unknown_list(lamps) - unkn_lines.remove_column('line_flag') # may wish to have this info - # Stack - line_lists = astropy.table.vstack([line_lists, unkn_lines]) + #unkn_lines.remove_column('line_flag') # may wish to have this info + + # Stack? + tot_line_list = astropy.table.vstack([line_lists, unkn_lines]) if include_unknown else line_lists_all # Return - return line_lists + return tot_line_list, line_lists, unkn_lines def load_tree(polygon=4, numsearch=20): diff --git a/pypeit/par/pypeitpar.py b/pypeit/par/pypeitpar.py index 11d4b57943..b8234c581b 100644 --- a/pypeit/par/pypeitpar.py +++ b/pypeit/par/pypeitpar.py @@ -2743,7 +2743,7 @@ def __init__(self, reference=None, method=None, echelle=None, ech_nspec_coeff=No 'Options are: {0}'.format(', '.join(options['refframe'])) dtypes['redo_slits'] = [int, list] - descr['redo_slits'] = 'Redo the input slit (multslit) or order (echelle)' + descr['redo_slits'] = 'Redo the input slit(s) [multslit] or order(s) [echelle]' defaults['qa_log'] = True dtypes['qa_log'] = bool diff --git a/pypeit/wavecalib.py b/pypeit/wavecalib.py index 80509cb0be..db086ce6fd 100644 --- a/pypeit/wavecalib.py +++ b/pypeit/wavecalib.py @@ -200,7 +200,7 @@ def _parse(cls, hdu, **kwargs): parsed_hdus += ihdu.name # Check if spat_ids != _d['spat_ids'].tolist(): - embed(header="198 of wavecalib.py") + #embed(header="198 of wavecalib.py") msgs.error("Bad parsing of WaveCalib") # Finish _d['wv_fits'] = np.asarray(list_of_wave_fits) @@ -514,8 +514,9 @@ def __init__(self, msarc, slits, spectrograph, par, lamps, self.slits.mask[idx] = self.slits.bitmask.turn_off( self.slits.mask[idx], 'BADWVCALIB') else: - embed(header='459 of wavecalib') - raise NotImplementedError("Not ready for multi-slit") + idx = np.in1d(self.slits.spat_id, self.par['redo_slits']) + self.slits.mask[idx] = self.slits.bitmask.turn_off( + self.slits.mask[idx], 'BADWVCALIB') # Load up slits # TODO -- Allow for flexure @@ -613,7 +614,8 @@ def build_wv_calib(self, arccen, method, skip_QA=False, # Obtain calibration for all slits if method == 'holy-grail': # Sometimes works, sometimes fails - arcfitter = autoid.HolyGrail(arccen, self.lamps, par=self.par, ok_mask=ok_mask_idx, + arcfitter = autoid.HolyGrail(arccen, self.lamps, par=self.par, + ok_mask=ok_mask_idx, nonlinear_counts=self.nonlinear_counts, spectrograph=self.spectrograph.name) patt_dict, final_fit = arcfitter.get_results() @@ -635,7 +637,7 @@ def build_wv_calib(self, arccen, method, skip_QA=False, arcfitter = autoid.ArchiveReid(arccen, self.lamps, self.par, ech_fixed_format=self.spectrograph.ech_fixed_format, ok_mask=ok_mask_idx, - measured_fwhms=measured_fwhms, + measured_fwhms=self.measured_fwhms, orders=self.orders, nonlinear_counts=self.nonlinear_counts) patt_dict, final_fit = arcfitter.get_results() @@ -644,7 +646,8 @@ def build_wv_calib(self, arccen, method, skip_QA=False, if self.binspectral is None: msgs.error("You must specify binspectral for the full_template method!") final_fit = autoid.full_template(arccen, self.lamps, self.par, ok_mask_idx, self.det, - self.binspectral, measured_fwhms=measured_fwhms, + self.binspectral, + measured_fwhms=self.measured_fwhms, nonlinear_counts=self.nonlinear_counts, nsnippet=self.par['nsnippet']) #debug=True, debug_reid=True, debug_xcorr=True) @@ -693,7 +696,7 @@ def build_wv_calib(self, arccen, method, skip_QA=False, idx = int(key) self.wv_calib.wv_fits[idx] = final_fit[key] self.wv_calib.wv_fits[idx].spat_id = self.slits.spat_id[idx] - self.wv_calib.wv_fits[idx].fwhm = measured_fwhms[idx] + self.wv_calib.wv_fits[idx].fwhm = self.measured_fwhms[idx] else: # Loop on WaveFit items tmp = [] @@ -768,11 +771,13 @@ def echelle_2dfit(self, wv_calib, debug=False, skip_QA=False): Returns: tuple: - fit2ds -- list of :class:`pypeit.fitting.PypeItFit`: objects containing information from 2-d fit. - Frequently a list of 1 fit. The main exception is for - a mosaic when one sets echelle_separate_2d=True - dets -- list of int: List of the detectors - order_dets -- list of lists of int: List of the orders + - ``fit2ds``: a list of :class:`pypeit.fitting.PypeItFit`: objects + containing information from 2-d fit. Frequently a list of 1 fit. + The main exception is for a mosaic when one sets ``echelle_separate_2d=True``. + + - ``dets``: a list of integers for the detector numbers. + + - ``order_dets``: a list of integer lists providing list of the orders. """ if self.spectrograph.pypeline != 'Echelle': msgs.error('Cannot execute echelle_2dfit for a non-echelle spectrograph.') @@ -966,15 +971,12 @@ def run(self, skip_QA=False, debug=False, # Fit 2D? if self.par['echelle']: # Assess the fits - rms = [] - for wvfit in self.wv_calib.wv_fits: - rms.append(wvfit.rms if wvfit.rms is not None else 999.) - rms = np.array(rms) + rms = np.array([999. if wvfit.rms is None else wvfit.rms for wvfit in self.wv_calib.wv_fits]) # Test and scale by measured_fwhms bad_rms = rms > (self.par['rms_threshold'] * np.median(self.measured_fwhms)/self.par['fwhm']) #embed(header='line 975 of wavecalib.py') - self.wvc_bpm[bad_rms] = True if np.any(bad_rms): + self.wvc_bpm[bad_rms] = True msgs.warn("Masking one or more bad orders (RMS)") # Fit fit2ds, dets, order_dets = self.echelle_2dfit( @@ -993,11 +995,11 @@ def run(self, skip_QA=False, debug=False, fixed = False for idet in range(len(dets)): in_det = np.in1d(bad_orders, order_dets[idet]) - if np.sum(in_det) == 0: + if not np.any(in_det): continue # Are there few enough? # TODO -- make the scale a parameter - max_bad = int(0.1 * len(order_dets[idet])) + 1 + max_bad = len(order_dets[idet])//10 + 1 if np.sum(in_det) > max_bad: msgs.warn(f"Too many bad orders in detector={dets[idet]} to attempt a refit.") continue @@ -1006,13 +1008,11 @@ def run(self, skip_QA=False, debug=False, iord = np.where(self.slits.ech_order == order)[0][0] # Predict the wavelengths nspec = self.arccen.shape[0] - spec_vec_norm = np.arange(nspec)/float(nspec-1) + spec_vec_norm = np.linspace(0,1,nspec) wv_order_mod = fit2ds[idet].eval(spec_vec_norm, x2=np.ones_like(spec_vec_norm)*order)/order # Link me - #from importlib import reload - #reload(autoid) tcent, spec_cont_sub, patt_dict_slit, tot_llist = autoid.match_to_arxiv( self.lamps, self.arccen[:,iord], wv_order_mod, self.arcspec_arxiv[:, iord], self.wave_soln_arxiv[:,iord], @@ -1036,11 +1036,9 @@ def run(self, skip_QA=False, debug=False, n_first=self.par['n_first'], #n_first=3, sigrej_first=self.par['sigrej_first'], - #sigrej_first=1.5, n_final=n_final, sigrej_final=2.) - #sigrej_final=self.par['sigrej_final']) - print(final_fit['rms']) + msgs.info(f"New RMS: {final_fit['rms']}") # Keep? # TODO -- Make this a parameter? @@ -1071,11 +1069,7 @@ def run(self, skip_QA=False, debug=False, self.wv_calib.wv_fit2d = np.array(fit2ds) # Check that we have at least one good 2D fit - chk_fit2d = False - for fit2d in self.wv_calib.wv_fit2d: - if fit2d.success: - chk_fit2d = True - if not chk_fit2d: + if not np.any([fit2d.success for fit2d in self.wv_calib.wv_fit2d]): msgs.error("No successful 2D Wavelength fits. Cannot proceed.") # Deal with mask From 0939c936c02a61bc918b6e05d905a8960c31cefd Mon Sep 17 00:00:00 2001 From: profxj Date: Fri, 21 Jul 2023 06:10:03 -0700 Subject: [PATCH 20/41] fix --- pypeit/core/wavecal/autoid.py | 6 +++--- pypeit/wavecalib.py | 16 ++++++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/pypeit/core/wavecal/autoid.py b/pypeit/core/wavecal/autoid.py index fcbc64d1e7..59d7de6536 100644 --- a/pypeit/core/wavecal/autoid.py +++ b/pypeit/core/wavecal/autoid.py @@ -1573,9 +1573,9 @@ def __init__(self, spec, lamps, par, ech_fixed_format=False, ok_mask=None, meas self.wave_soln_arxiv[:, iarxiv] = self.wv_calib_arxiv[str(iarxiv)]['wave_soln'] # arxiv orders (echelle only) if ech_fixed_format: - arxiv_orders = [] + self.arxiv_orders = [] for iarxiv in range(narxiv): - arxiv_orders.append(self.wv_calib_arxiv[str(iarxiv)]['order']) + self.arxiv_orders.append(self.wv_calib_arxiv[str(iarxiv)]['order']) # orders, _ = self.spectrograph.slit2order(slit_spat_pos) ind_arxiv = np.arange(narxiv, dtype=int) @@ -1593,7 +1593,7 @@ def __init__(self, spec, lamps, par, ech_fixed_format=False, ok_mask=None, meas msgs.info('Reidentifying and fitting slit # {0:d}/{1:d}'.format(slit+1,self.nslits)) # If this is a fixed format echelle, arxiv has exactly the same orders as the data and so # we only pass in the relevant arxiv spectrum to make this much faster - ind_sp = arxiv_orders.index(orders[slit]) if ech_fixed_format else ind_arxiv + ind_sp = self.arxiv_orders.index(orders[slit]) if ech_fixed_format else ind_arxiv if ech_fixed_format: msgs.info(f'Order: {orders[slit]}') sigdetect = wvutils.parse_param(self.par, 'sigdetect', slit) diff --git a/pypeit/wavecalib.py b/pypeit/wavecalib.py index db086ce6fd..15b405f380 100644 --- a/pypeit/wavecalib.py +++ b/pypeit/wavecalib.py @@ -634,13 +634,17 @@ def build_wv_calib(self, arccen, method, skip_QA=False, elif method == 'reidentify': # Now preferred # Slit positions - arcfitter = autoid.ArchiveReid(arccen, self.lamps, self.par, - ech_fixed_format=self.spectrograph.ech_fixed_format, - ok_mask=ok_mask_idx, - measured_fwhms=self.measured_fwhms, - orders=self.orders, - nonlinear_counts=self.nonlinear_counts) + arcfitter = autoid.ArchiveReid( + arccen, self.lamps, self.par, + ech_fixed_format=self.spectrograph.ech_fixed_format, + ok_mask=ok_mask_idx, + measured_fwhms=self.measured_fwhms, + orders=self.orders, + nonlinear_counts=self.nonlinear_counts) patt_dict, final_fit = arcfitter.get_results() + # Save orders? + if self.par['echelle']: + embed(header='647 of wavecalib.py') elif method == 'full_template': # Now preferred if self.binspectral is None: From c11a0b5983958cfa739332b9dbbb527249e67d48 Mon Sep 17 00:00:00 2001 From: profxj Date: Fri, 21 Jul 2023 06:20:33 -0700 Subject: [PATCH 21/41] the fix is in --- pypeit/core/wavecal/autoid.py | 3 ++- pypeit/wavecalib.py | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/pypeit/core/wavecal/autoid.py b/pypeit/core/wavecal/autoid.py index 59d7de6536..503a15b253 100644 --- a/pypeit/core/wavecal/autoid.py +++ b/pypeit/core/wavecal/autoid.py @@ -1604,7 +1604,8 @@ def __init__(self, spec, lamps, par, ech_fixed_format=False, ok_mask=None, meas # get FWHM for this slit fwhm = set_fwhm(self.par, measured_fwhm=measured_fwhms[slit]) self.detections[str(slit)], self.spec_cont_sub[:,slit], self.all_patt_dict[str(slit)] = \ - reidentify(self.spec[:,slit], self.spec_arxiv[:,ind_sp], self.wave_soln_arxiv[:,ind_sp], + reidentify(self.spec[:,slit], self.spec_arxiv[:,ind_sp], + self.wave_soln_arxiv[:,ind_sp], self.tot_line_list, self.nreid_min, cc_thresh=cc_thresh, match_toler=self.match_toler, cc_local_thresh=self.cc_local_thresh, nlocal_cc=self.nlocal_cc, nonlinear_counts=self.nonlinear_counts, sigdetect=sigdetect, fwhm=fwhm, debug_peaks=self.debug_peaks, debug_xcorr=self.debug_xcorr, diff --git a/pypeit/wavecalib.py b/pypeit/wavecalib.py index 15b405f380..190f495d9e 100644 --- a/pypeit/wavecalib.py +++ b/pypeit/wavecalib.py @@ -642,9 +642,20 @@ def build_wv_calib(self, arccen, method, skip_QA=False, orders=self.orders, nonlinear_counts=self.nonlinear_counts) patt_dict, final_fit = arcfitter.get_results() - # Save orders? + + # Save arxiv for redo later? if self.par['echelle']: - embed(header='647 of wavecalib.py') + # Collate + wave_soln_arxiv = [] + arcspec_arxiv = [] + for order in self.orders: + ind_sp = arcfitter.arxiv_orders.index(order) + wave_soln_arxiv.append(arcfitter.wave_soln_arxiv[:,ind_sp]) + arcspec_arxiv.append(arcfitter.spec_arxiv[:,ind_sp]) + # Save + self.wave_soln_arxiv = np.stack(wave_soln_arxiv,axis=-1) + self.arcspec_arxiv = np.stack(arcspec_arxiv,axis=-1) + self.arccen = arccen elif method == 'full_template': # Now preferred if self.binspectral is None: From a3fd75fbae902b8812f8ba53ee245cd0c7b3dbe9 Mon Sep 17 00:00:00 2001 From: profxj Date: Fri, 21 Jul 2023 06:29:04 -0700 Subject: [PATCH 22/41] mo --- pypeit/wavecalib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pypeit/wavecalib.py b/pypeit/wavecalib.py index 190f495d9e..8999a2393e 100644 --- a/pypeit/wavecalib.py +++ b/pypeit/wavecalib.py @@ -1053,7 +1053,7 @@ def run(self, skip_QA=False, debug=False, sigrej_first=self.par['sigrej_first'], n_final=n_final, sigrej_final=2.) - msgs.info(f"New RMS: {final_fit['rms']}") + msgs.info(f"New RMS for redo of order={order}: {final_fit['rms']}") # Keep? # TODO -- Make this a parameter? From 569a7364c0609673e048c01b700759acab17acdd Mon Sep 17 00:00:00 2001 From: "J. Xavier Prochaska" Date: Fri, 21 Jul 2023 09:29:32 -0700 Subject: [PATCH 23/41] unknowns bug --- pypeit/core/wavecal/waveio.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pypeit/core/wavecal/waveio.py b/pypeit/core/wavecal/waveio.py index 3ed2f2dc3c..be337ad046 100644 --- a/pypeit/core/wavecal/waveio.py +++ b/pypeit/core/wavecal/waveio.py @@ -303,6 +303,10 @@ def load_unknown_list(lines, unknwn_file=None, all=False): # Otherwise msk = np.zeros(len(line_list), dtype=bool) for line in lines: + # Skip if the lines is not even in the line list + if line not in line_dict.keys(): + continue + # Else consider masking line_flag = line_dict[line] match = line_list['line_flag'] % (2*line_flag) >= line_flag msk[match] = True From c8479ad5da8b7d1b2ba8d37de3e2acf8a2b37427 Mon Sep 17 00:00:00 2001 From: "J. Xavier Prochaska" Date: Mon, 24 Jul 2023 09:34:59 -0700 Subject: [PATCH 24/41] xshooter doc --- doc/spectrographs/xshooter.rst | 45 ++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/doc/spectrographs/xshooter.rst b/doc/spectrographs/xshooter.rst index 06409ce672..51485d4808 100644 --- a/doc/spectrographs/xshooter.rst +++ b/doc/spectrographs/xshooter.rst @@ -10,3 +10,48 @@ This file summarizes several instrument specific settings that are related to the VLT/XShooter spectrograph. +Wavelengths +=========== + +As it is common for ESO to obtain calibrations with different +slit widths and binning, this can lead to various challenges +for PypeIt. + +As regards wavelengths, the varying binning and slit widths lead +to differing FWHM of the arc lines. And because the RMS threshold +for a good solution is scaled to FWHM, the default is to measure +the FWHM from the lines themselves. + +If too many orders are being rejected, you may wish to adjust things +in one or more ways. + +FWHM +---- + +For the UVB or the VIS, you may turn off measuring the FWHM from the arc lines +by adding this to your :doc:`pypeit_file`: + + +.. code-block:: ini + + [calibrations] + [[wavelengths]] + fwhm_fromlines = False + +This will set the FWHM to the default value for UVB/VIS which +may yield a better set of discovered arc lines. + +RMS +--- + +Another option is to increase the RMS threshold for a good solution. +This may be done in the :doc:`pypeit_file` as well: + +.. code-block:: ini + + [calibrations] + [[wavelengths]] + rms_threshold = 1.5 + +Note that this is scaled by the ratio of the measured FWHM value +to the default value. From 3d40489319c85396f6d2e519b1601fd3343f4f1c Mon Sep 17 00:00:00 2001 From: "J. Xavier Prochaska" Date: Mon, 24 Jul 2023 11:09:20 -0700 Subject: [PATCH 25/41] bump up rms --- pypeit/spectrographs/vlt_xshooter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pypeit/spectrographs/vlt_xshooter.py b/pypeit/spectrographs/vlt_xshooter.py index 447a05f0b7..b0d32ad57e 100644 --- a/pypeit/spectrographs/vlt_xshooter.py +++ b/pypeit/spectrographs/vlt_xshooter.py @@ -295,7 +295,7 @@ def default_pypeit_par(cls): # 1D wavelength solution par['calibrations']['wavelengths']['lamps'] = ['OH_XSHOOTER'] - par['calibrations']['wavelengths']['rms_threshold'] = 0.35 + par['calibrations']['wavelengths']['rms_threshold'] = 0.60 par['calibrations']['wavelengths']['sigdetect'] = 10.0 par['calibrations']['wavelengths']['fwhm'] = 5.0 par['calibrations']['wavelengths']['n_final'] = 4 From 2db1699525960ea01ff64aa70f39f4e84a464d37 Mon Sep 17 00:00:00 2001 From: "J. Xavier Prochaska" Date: Mon, 24 Jul 2023 14:23:58 -0700 Subject: [PATCH 26/41] identify fix --- pypeit/core/gui/identify.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pypeit/core/gui/identify.py b/pypeit/core/gui/identify.py index 46c3b26b0e..9ec147cfa8 100644 --- a/pypeit/core/gui/identify.py +++ b/pypeit/core/gui/identify.py @@ -272,11 +272,7 @@ def initialise(cls, arccen, lamps, slits, slit=0, par=None, wv_calib_all=None, detns = tdetns[icut] # Load line lists - if 'ThAr' in lamps: - line_lists_all = waveio.load_line_lists(lamps) - line_lists = line_lists_all[np.where(line_lists_all['ion'] != 'UNKNWN')] - else: - line_lists = waveio.load_line_lists(lamps) + line_lists, _, _ = waveio.load_line_lists(lamps, include_unknown=False) # Trim the wavelength scale if requested if wavelim is not None: From 98c0eb48e95c2799fee764c3a25a8fe34bba65d2 Mon Sep 17 00:00:00 2001 From: Kyle Westfall Date: Wed, 26 Jul 2023 10:05:09 -0700 Subject: [PATCH 27/41] calibration frame naming convention --- CHANGES.rst | 8 +++- pypeit/calibframe.py | 77 +++++++++++++++++++++++++++++++-- pypeit/tests/test_arcimage.py | 1 - pypeit/tests/test_calibframe.py | 31 ++++++++++++- 4 files changed, 109 insertions(+), 8 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 34bacf07aa..1038a6baef 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,5 @@ -1.13.1dev (6 June 2023) ------------------------- +1.13.1dev +--------- - Add support for Gemini/GNIRS (IFU) - Added a script to convert a wavelength solution into something that can be placed in the reid archive. @@ -14,6 +14,10 @@ - Adds Keck/ESI to PypeIt - Add MDM/Modspec spectrograph - Store user-generated wavelength solution in pypeit cache +- Changed calibration frame naming as an attempt to avoid very long names for + files with many calibration groups. Sequential numbers are reduced to a + range; e.g., ``'0-1-2-3-4'`` becomes ``'0:5'`` and + ``'3-5-6-10-11-12-15-18-19'`` becomes ``'3-5:7-10:13-15-18:20'`` 1.13.0 (2 June 2023) -------------------- diff --git a/pypeit/calibframe.py b/pypeit/calibframe.py index 7b648a8bdc..1b6e869096 100644 --- a/pypeit/calibframe.py +++ b/pypeit/calibframe.py @@ -302,6 +302,74 @@ def ingest_calib_id(calib_id): msgs.error(f'Invalid calibration group {c}; must be convertible to an integer.') return _calib_id.tolist() + @staticmethod + def construct_calib_id(calib_id, ingested=False): + """ + Use the calibration ID to construct a unique identifying string included + in output file names. + + Args: + calib_id (:obj:`str`, :obj:`list`, :obj:`int`): + Identifiers for one or more calibration groups for this + calibration frame. Strings (either as individually entered or + as elements of a provided list) can be single or comma-separated + integers. Otherwise, all strings must be convertible to + integers; the only exception is the string 'all'. + ingested (:obj:`bool`, optional): + Indicates that the ``calib_id`` object has already been + "ingested" (see :func:`ingest_calib_id`). If True, this will + skip the ingestion step. + + Returns: + :obj:`str`: A string identifier to include in output file names. + """ + # Ingest the calibration IDs, if necessary + _calib_id = calib_id if ingested else CalibFrame.ingest_calib_id(calib_id) + if len(_calib_id) == 1: + # There's only one calibration ID, so return it. This works both + # for 'all' and for single-integer calibration groupings. + return _calib_id[0] + + # Convert the IDs to integers and sort them + calibs = np.sort(np.array(_calib_id).astype(int)) + + # Find where the list is non-sequential + indx = np.diff(calibs) != 1 + if not np.any(indx): + # The full list is sequential, so give the starting and ending points + return f'{calibs[0]}:{calibs[-1]+1}' + + # Split the array into sequential subarrays (or single elements) and + # combine them into a single string + split_calibs = np.split(calibs, np.where(indx)[0]+1) + return '-'.join([f'{s[0]}:{s[-1]+1}' if len(s) > 1 else f'{s[0]}' for s in split_calibs]) + + @staticmethod + def parse_calib_id(calib_id_name): + """ + Parse the calibration ID(s) from the unique string identifier used in + file naming. I.e., this is the inverse of :func:`construct_calib_id`. + + Args: + calib_id_name (:obj:`str`): + The string identifier used in file naming constructed from a + list of calibration IDs using :func:`construct_calib_id`. + + Returns: + :obj:`list`: List of string representations of single calibration + group integer identifiers. + """ + # Name is all, so we're done + if calib_id_name == 'all': + return ['all'] + # Parse the name into slices and enumerate them + calib_id = [] + for slc in calib_id_name.split('-'): + split_slc = slc.split(':') + calib_id += split_slc if len(split_slc) == 1 \ + else np.arange(*np.array(split_slc).astype(int)).astype(str).tolist() + return calib_id + @staticmethod def construct_calib_key(setup, calib_id, detname): """ @@ -330,7 +398,8 @@ def construct_calib_key(setup, calib_id, detname): Returns: :obj:`str`: Calibration identifier. """ - return f'{setup}_{"-".join(CalibFrame.ingest_calib_id(calib_id))}_{detname}' +# return f'{setup}_{"-".join(CalibFrame.ingest_calib_id(calib_id))}_{detname}' + return f'{setup}_{CalibFrame.construct_calib_id(calib_id)}_{detname}' @staticmethod def parse_calib_key(calib_key): @@ -346,8 +415,10 @@ def parse_calib_key(calib_key): Returns: :obj:`tuple`: The three components of the calibration key. """ - setup, calib_id, detname = calib_key.split('_') - return setup, ','.join(calib_id.split('-')), detname +# setup, calib_id, detname = calib_key.split('_') +# return setup, ','.join(calib_id.split('-')), detname + setup, calib_id_name, detname = calib_key.split('_') + return setup, CalibFrame.parse_calib_id(calib_id_name), detname @classmethod def construct_file_name(cls, calib_key, calib_dir=None): diff --git a/pypeit/tests/test_arcimage.py b/pypeit/tests/test_arcimage.py index 62fb1af3d0..4eb52f0717 100644 --- a/pypeit/tests/test_arcimage.py +++ b/pypeit/tests/test_arcimage.py @@ -57,4 +57,3 @@ def test_io(): ofile.unlink() - diff --git a/pypeit/tests/test_calibframe.py b/pypeit/tests/test_calibframe.py index d8e12db330..b5b0407af8 100644 --- a/pypeit/tests/test_calibframe.py +++ b/pypeit/tests/test_calibframe.py @@ -5,6 +5,8 @@ from IPython import embed +import numpy as np + import pytest from pypeit.pypmsgs import PypeItError @@ -56,7 +58,7 @@ def test_init(): calib.set_paths(odir, 'A', ['1','2'], 'DET01') ofile = Path(calib.get_path()).name - assert ofile == 'Minimal_A_1-2_DET01.fits', 'Wrong file name' + assert ofile == 'Minimal_A_1:3_DET01.fits', 'Wrong file name' def test_io(): @@ -97,7 +99,7 @@ def test_construct_calib_key(): key = CalibFrame.construct_calib_key('A', '1', 'DET01') assert key == 'A_1_DET01', 'Key changed' key = CalibFrame.construct_calib_key('A', ['1','2'], 'DET01') - assert key == 'A_1-2_DET01', 'Key changed' + assert key == 'A_1:3_DET01', 'Key changed' key = CalibFrame.construct_calib_key('A', 'all', 'DET01') assert key == 'A_all_DET01', 'Key changed' @@ -115,6 +117,31 @@ def test_ingest_calib_id(): 'Bad ingest' +def test_construct_calib_id(): + assert CalibFrame.construct_calib_id(['all']) == 'all', 'Construction should simply return all' + assert CalibFrame.construct_calib_id(['1']) == '1', \ + 'Construction with one calib_id should just return it' + calib_id = np.arange(10).tolist() + assert CalibFrame.construct_calib_id(calib_id) == '0:10', 'Bad simple construction' + # rng = np.random.default_rng(99) + # calib_id = np.unique(rng.integers(20, size=15)).tolist() + calib_id = [3, 5, 6, 10, 11, 12, 15, 18, 19] + assert CalibFrame.construct_calib_id(calib_id) == '3-5:7-10:13-15-18:20', \ + 'Bad complex construction' + + +def test_parse_calib_id(): + assert CalibFrame.parse_calib_id('all') == ['all'], 'Parsing should simply return all' + assert CalibFrame.parse_calib_id('1') == ['1'], 'Parsing should simply return all' + assert np.array_equal(CalibFrame.parse_calib_id('0:10'), np.arange(10).astype(str).tolist()), \ + 'Bad simple construction' + # rng = np.random.default_rng(99) + # calib_id = np.unique(rng.integers(20, size=15)).tolist() + calib_id = np.sort(np.array([3, 5, 6, 10, 11, 12, 15, 18, 19]).astype(str)) + assert np.array_equal(np.sort(CalibFrame.parse_calib_id('3-5:7-10:13-15-18:20')), calib_id), \ + 'Bad complex construction' + + def test_parse_key_dir(): calib = MinimalCalibFrame() odir = Path(data_path('')).resolve() From 2a9b3aea21a8b59be4898ed8a83f81068fb945f2 Mon Sep 17 00:00:00 2001 From: Kyle Westfall Date: Wed, 26 Jul 2023 10:19:48 -0700 Subject: [PATCH 28/41] doc update --- doc/api/pypeit.scripts.arxiv_solution.rst | 8 + doc/api/pypeit.scripts.rst | 1 + doc/api/pypeit.spectrographs.keck_esi.rst | 8 + doc/api/pypeit.spectrographs.mdm_modspec.rst | 8 + doc/api/pypeit.spectrographs.rst | 2 + doc/help/pypeit_cache_github_data.rst | 15 +- doc/help/pypeit_chk_for_calibs.rst | 17 +- doc/help/pypeit_obslog.rst | 17 +- doc/help/pypeit_ql.rst | 17 +- doc/help/pypeit_ql_multislit.rst | 17 +- doc/help/pypeit_setup.rst | 17 +- doc/help/pypeit_setup_coadd2d.rst | 34 +- doc/help/pypeit_skysub_regions.rst | 4 +- doc/help/pypeit_trace_edges.rst | 13 +- doc/help/pypeit_view_fits.rst | 20 +- doc/help/run_pypeit.rst | 15 +- doc/include/class_datamodel_specobj.rst | 6 +- doc/include/class_datamodel_wavecalib.rst | 3 +- doc/include/datamodel_specobj.rst | 6 +- doc/include/datamodel_wavecalib.rst | 2 +- doc/include/dependencies_table.rst | 2 +- doc/include/dev_suite_readme.rst | 4 +- ....rst => gemini_gnirs_echelle_A.pypeit.rst} | 4 +- ...mini_gnirs_echelle_A_corrected.pypeit.rst} | 4 +- doc/include/inst_detector_table.rst | 7 +- doc/include/spectrographs_table.rst | 12 +- doc/pypeit_par.rst | 971 ++++++++++++------ doc/scripts/make_example_files.py | 17 +- pypeit/calibframe.py | 3 - pypeit/spectrographs/mdm_modspec.py | 23 +- 30 files changed, 843 insertions(+), 434 deletions(-) create mode 100644 doc/api/pypeit.scripts.arxiv_solution.rst create mode 100644 doc/api/pypeit.spectrographs.keck_esi.rst create mode 100644 doc/api/pypeit.spectrographs.mdm_modspec.rst rename doc/include/{gemini_gnirs_A.pypeit.rst => gemini_gnirs_echelle_A.pypeit.rst} (98%) rename doc/include/{gemini_gnirs_A_corrected.pypeit.rst => gemini_gnirs_echelle_A_corrected.pypeit.rst} (98%) diff --git a/doc/api/pypeit.scripts.arxiv_solution.rst b/doc/api/pypeit.scripts.arxiv_solution.rst new file mode 100644 index 0000000000..edae4df2cf --- /dev/null +++ b/doc/api/pypeit.scripts.arxiv_solution.rst @@ -0,0 +1,8 @@ +pypeit.scripts.arxiv\_solution module +===================================== + +.. automodule:: pypeit.scripts.arxiv_solution + :members: + :private-members: + :undoc-members: + :show-inheritance: diff --git a/doc/api/pypeit.scripts.rst b/doc/api/pypeit.scripts.rst index f6765004a8..39c559ed1b 100644 --- a/doc/api/pypeit.scripts.rst +++ b/doc/api/pypeit.scripts.rst @@ -7,6 +7,7 @@ Submodules .. toctree:: :maxdepth: 4 + pypeit.scripts.arxiv_solution pypeit.scripts.cache_github_data pypeit.scripts.chk_alignments pypeit.scripts.chk_edges diff --git a/doc/api/pypeit.spectrographs.keck_esi.rst b/doc/api/pypeit.spectrographs.keck_esi.rst new file mode 100644 index 0000000000..b87b500d22 --- /dev/null +++ b/doc/api/pypeit.spectrographs.keck_esi.rst @@ -0,0 +1,8 @@ +pypeit.spectrographs.keck\_esi module +===================================== + +.. automodule:: pypeit.spectrographs.keck_esi + :members: + :private-members: + :undoc-members: + :show-inheritance: diff --git a/doc/api/pypeit.spectrographs.mdm_modspec.rst b/doc/api/pypeit.spectrographs.mdm_modspec.rst new file mode 100644 index 0000000000..5e669a872b --- /dev/null +++ b/doc/api/pypeit.spectrographs.mdm_modspec.rst @@ -0,0 +1,8 @@ +pypeit.spectrographs.mdm\_modspec module +======================================== + +.. automodule:: pypeit.spectrographs.mdm_modspec + :members: + :private-members: + :undoc-members: + :show-inheritance: diff --git a/doc/api/pypeit.spectrographs.rst b/doc/api/pypeit.spectrographs.rst index dd574769a5..c53968bcbb 100644 --- a/doc/api/pypeit.spectrographs.rst +++ b/doc/api/pypeit.spectrographs.rst @@ -15,6 +15,7 @@ Submodules pypeit.spectrographs.jwst_nircam pypeit.spectrographs.jwst_nirspec pypeit.spectrographs.keck_deimos + pypeit.spectrographs.keck_esi pypeit.spectrographs.keck_hires pypeit.spectrographs.keck_kcwi pypeit.spectrographs.keck_lris @@ -26,6 +27,7 @@ Submodules pypeit.spectrographs.ldt_deveny pypeit.spectrographs.magellan_fire pypeit.spectrographs.magellan_mage + pypeit.spectrographs.mdm_modspec pypeit.spectrographs.mdm_osmos pypeit.spectrographs.mmt_binospec pypeit.spectrographs.mmt_bluechannel diff --git a/doc/help/pypeit_cache_github_data.rst b/doc/help/pypeit_cache_github_data.rst index 5948f93d00..b894405b84 100644 --- a/doc/help/pypeit_cache_github_data.rst +++ b/doc/help/pypeit_cache_github_data.rst @@ -10,13 +10,14 @@ spectrograph A valid spectrograph identifier: bok_bc, gemini_flamingos1, gemini_flamingos2, gemini_gmos_north_e2v, gemini_gmos_north_ham, gemini_gmos_north_ham_ns, - gemini_gmos_south_ham, gemini_gnirs, gtc_maat, gtc_osiris, - gtc_osiris_plus, jwst_nircam, jwst_nirspec, keck_deimos, - keck_hires, keck_kcwi, keck_lris_blue, keck_lris_blue_orig, - keck_lris_red, keck_lris_red_mark4, keck_lris_red_orig, - keck_mosfire, keck_nires, keck_nirspec_low, lbt_luci1, - lbt_luci2, lbt_mods1b, lbt_mods1r, lbt_mods2b, lbt_mods2r, - ldt_deveny, magellan_fire, magellan_fire_long, magellan_mage, + gemini_gmos_south_ham, gemini_gnirs_echelle, gemini_gnirs_ifu, + gtc_maat, gtc_osiris, gtc_osiris_plus, jwst_nircam, + jwst_nirspec, keck_deimos, keck_esi, keck_hires, keck_kcwi, + keck_lris_blue, keck_lris_blue_orig, keck_lris_red, + keck_lris_red_mark4, keck_lris_red_orig, keck_mosfire, + keck_nires, keck_nirspec_low, lbt_luci1, lbt_luci2, + lbt_mods1b, lbt_mods1r, lbt_mods2b, lbt_mods2r, ldt_deveny, + magellan_fire, magellan_fire_long, magellan_mage, mdm_modspec, mdm_osmos_mdm4k, mmt_binospec, mmt_bluechannel, mmt_mmirs, not_alfosc, not_alfosc_vert, ntt_efosc2, p200_dbsp_blue, p200_dbsp_red, p200_tspec, shane_kast_blue, shane_kast_red, diff --git a/doc/help/pypeit_chk_for_calibs.rst b/doc/help/pypeit_chk_for_calibs.rst index 34b2c930bc..14c727c21f 100644 --- a/doc/help/pypeit_chk_for_calibs.rst +++ b/doc/help/pypeit_chk_for_calibs.rst @@ -17,14 +17,15 @@ gemini_flamingos1, gemini_flamingos2, gemini_gmos_north_e2v, gemini_gmos_north_ham, gemini_gmos_north_ham_ns, gemini_gmos_south_ham, - gemini_gnirs, gtc_maat, gtc_osiris, gtc_osiris_plus, - jwst_nircam, jwst_nirspec, keck_deimos, keck_hires, - keck_kcwi, keck_lris_blue, keck_lris_blue_orig, - keck_lris_red, keck_lris_red_mark4, keck_lris_red_orig, - keck_mosfire, keck_nires, keck_nirspec_low, lbt_luci1, - lbt_luci2, lbt_mods1b, lbt_mods1r, lbt_mods2b, - lbt_mods2r, ldt_deveny, magellan_fire, - magellan_fire_long, magellan_mage, mdm_osmos_mdm4k, + gemini_gnirs_echelle, gemini_gnirs_ifu, gtc_maat, + gtc_osiris, gtc_osiris_plus, jwst_nircam, jwst_nirspec, + keck_deimos, keck_esi, keck_hires, keck_kcwi, + keck_lris_blue, keck_lris_blue_orig, keck_lris_red, + keck_lris_red_mark4, keck_lris_red_orig, keck_mosfire, + keck_nires, keck_nirspec_low, lbt_luci1, lbt_luci2, + lbt_mods1b, lbt_mods1r, lbt_mods2b, lbt_mods2r, + ldt_deveny, magellan_fire, magellan_fire_long, + magellan_mage, mdm_modspec, mdm_osmos_mdm4k, mmt_binospec, mmt_bluechannel, mmt_mmirs, not_alfosc, not_alfosc_vert, ntt_efosc2, p200_dbsp_blue, p200_dbsp_red, p200_tspec, shane_kast_blue, diff --git a/doc/help/pypeit_obslog.rst b/doc/help/pypeit_obslog.rst index 1f6e2b5290..d185e402ca 100644 --- a/doc/help/pypeit_obslog.rst +++ b/doc/help/pypeit_obslog.rst @@ -14,14 +14,15 @@ gemini_flamingos1, gemini_flamingos2, gemini_gmos_north_e2v, gemini_gmos_north_ham, gemini_gmos_north_ham_ns, gemini_gmos_south_ham, - gemini_gnirs, gtc_maat, gtc_osiris, gtc_osiris_plus, - jwst_nircam, jwst_nirspec, keck_deimos, keck_hires, - keck_kcwi, keck_lris_blue, keck_lris_blue_orig, - keck_lris_red, keck_lris_red_mark4, keck_lris_red_orig, - keck_mosfire, keck_nires, keck_nirspec_low, lbt_luci1, - lbt_luci2, lbt_mods1b, lbt_mods1r, lbt_mods2b, - lbt_mods2r, ldt_deveny, magellan_fire, - magellan_fire_long, magellan_mage, mdm_osmos_mdm4k, + gemini_gnirs_echelle, gemini_gnirs_ifu, gtc_maat, + gtc_osiris, gtc_osiris_plus, jwst_nircam, jwst_nirspec, + keck_deimos, keck_esi, keck_hires, keck_kcwi, + keck_lris_blue, keck_lris_blue_orig, keck_lris_red, + keck_lris_red_mark4, keck_lris_red_orig, keck_mosfire, + keck_nires, keck_nirspec_low, lbt_luci1, lbt_luci2, + lbt_mods1b, lbt_mods1r, lbt_mods2b, lbt_mods2r, + ldt_deveny, magellan_fire, magellan_fire_long, + magellan_mage, mdm_modspec, mdm_osmos_mdm4k, mmt_binospec, mmt_bluechannel, mmt_mmirs, not_alfosc, not_alfosc_vert, ntt_efosc2, p200_dbsp_blue, p200_dbsp_red, p200_tspec, shane_kast_blue, diff --git a/doc/help/pypeit_ql.rst b/doc/help/pypeit_ql.rst index f02e25d753..8c36c2c291 100644 --- a/doc/help/pypeit_ql.rst +++ b/doc/help/pypeit_ql.rst @@ -21,14 +21,15 @@ gemini_flamingos1, gemini_flamingos2, gemini_gmos_north_e2v, gemini_gmos_north_ham, gemini_gmos_north_ham_ns, gemini_gmos_south_ham, - gemini_gnirs, gtc_maat, gtc_osiris, gtc_osiris_plus, - jwst_nircam, jwst_nirspec, keck_deimos, keck_hires, - keck_kcwi, keck_lris_blue, keck_lris_blue_orig, - keck_lris_red, keck_lris_red_mark4, keck_lris_red_orig, - keck_mosfire, keck_nires, keck_nirspec_low, lbt_luci1, - lbt_luci2, lbt_mods1b, lbt_mods1r, lbt_mods2b, - lbt_mods2r, ldt_deveny, magellan_fire, - magellan_fire_long, magellan_mage, mdm_osmos_mdm4k, + gemini_gnirs_echelle, gemini_gnirs_ifu, gtc_maat, + gtc_osiris, gtc_osiris_plus, jwst_nircam, jwst_nirspec, + keck_deimos, keck_esi, keck_hires, keck_kcwi, + keck_lris_blue, keck_lris_blue_orig, keck_lris_red, + keck_lris_red_mark4, keck_lris_red_orig, keck_mosfire, + keck_nires, keck_nirspec_low, lbt_luci1, lbt_luci2, + lbt_mods1b, lbt_mods1r, lbt_mods2b, lbt_mods2r, + ldt_deveny, magellan_fire, magellan_fire_long, + magellan_mage, mdm_modspec, mdm_osmos_mdm4k, mmt_binospec, mmt_bluechannel, mmt_mmirs, not_alfosc, not_alfosc_vert, ntt_efosc2, p200_dbsp_blue, p200_dbsp_red, p200_tspec, shane_kast_blue, diff --git a/doc/help/pypeit_ql_multislit.rst b/doc/help/pypeit_ql_multislit.rst index ce83350892..72718fa74a 100644 --- a/doc/help/pypeit_ql_multislit.rst +++ b/doc/help/pypeit_ql_multislit.rst @@ -16,14 +16,15 @@ gemini_flamingos1, gemini_flamingos2, gemini_gmos_north_e2v, gemini_gmos_north_ham, gemini_gmos_north_ham_ns, gemini_gmos_south_ham, - gemini_gnirs, gtc_maat, gtc_osiris, gtc_osiris_plus, - jwst_nircam, jwst_nirspec, keck_deimos, keck_hires, - keck_kcwi, keck_lris_blue, keck_lris_blue_orig, - keck_lris_red, keck_lris_red_mark4, keck_lris_red_orig, - keck_mosfire, keck_nires, keck_nirspec_low, lbt_luci1, - lbt_luci2, lbt_mods1b, lbt_mods1r, lbt_mods2b, - lbt_mods2r, ldt_deveny, magellan_fire, - magellan_fire_long, magellan_mage, mdm_osmos_mdm4k, + gemini_gnirs_echelle, gemini_gnirs_ifu, gtc_maat, + gtc_osiris, gtc_osiris_plus, jwst_nircam, jwst_nirspec, + keck_deimos, keck_esi, keck_hires, keck_kcwi, + keck_lris_blue, keck_lris_blue_orig, keck_lris_red, + keck_lris_red_mark4, keck_lris_red_orig, keck_mosfire, + keck_nires, keck_nirspec_low, lbt_luci1, lbt_luci2, + lbt_mods1b, lbt_mods1r, lbt_mods2b, lbt_mods2r, + ldt_deveny, magellan_fire, magellan_fire_long, + magellan_mage, mdm_modspec, mdm_osmos_mdm4k, mmt_binospec, mmt_bluechannel, mmt_mmirs, not_alfosc, not_alfosc_vert, ntt_efosc2, p200_dbsp_blue, p200_dbsp_red, p200_tspec, shane_kast_blue, diff --git a/doc/help/pypeit_setup.rst b/doc/help/pypeit_setup.rst index 40dc9ceb1b..397014a124 100644 --- a/doc/help/pypeit_setup.rst +++ b/doc/help/pypeit_setup.rst @@ -15,14 +15,15 @@ gemini_flamingos1, gemini_flamingos2, gemini_gmos_north_e2v, gemini_gmos_north_ham, gemini_gmos_north_ham_ns, gemini_gmos_south_ham, - gemini_gnirs, gtc_maat, gtc_osiris, gtc_osiris_plus, - jwst_nircam, jwst_nirspec, keck_deimos, keck_hires, - keck_kcwi, keck_lris_blue, keck_lris_blue_orig, - keck_lris_red, keck_lris_red_mark4, keck_lris_red_orig, - keck_mosfire, keck_nires, keck_nirspec_low, lbt_luci1, - lbt_luci2, lbt_mods1b, lbt_mods1r, lbt_mods2b, - lbt_mods2r, ldt_deveny, magellan_fire, - magellan_fire_long, magellan_mage, mdm_osmos_mdm4k, + gemini_gnirs_echelle, gemini_gnirs_ifu, gtc_maat, + gtc_osiris, gtc_osiris_plus, jwst_nircam, jwst_nirspec, + keck_deimos, keck_esi, keck_hires, keck_kcwi, + keck_lris_blue, keck_lris_blue_orig, keck_lris_red, + keck_lris_red_mark4, keck_lris_red_orig, keck_mosfire, + keck_nires, keck_nirspec_low, lbt_luci1, lbt_luci2, + lbt_mods1b, lbt_mods1r, lbt_mods2b, lbt_mods2r, + ldt_deveny, magellan_fire, magellan_fire_long, + magellan_mage, mdm_modspec, mdm_osmos_mdm4k, mmt_binospec, mmt_bluechannel, mmt_mmirs, not_alfosc, not_alfosc_vert, ntt_efosc2, p200_dbsp_blue, p200_dbsp_red, p200_tspec, shane_kast_blue, diff --git a/doc/help/pypeit_setup_coadd2d.rst b/doc/help/pypeit_setup_coadd2d.rst index 505042b1ae..6f5de8bd3e 100644 --- a/doc/help/pypeit_setup_coadd2d.rst +++ b/doc/help/pypeit_setup_coadd2d.rst @@ -1,10 +1,14 @@ .. code-block:: console $ pypeit_setup_coadd2d -h - usage: pypeit_setup_coadd2d [-h] (-f PYPEIT_FILE | -d SCIENCE_DIR) [--keep_par] - [--obj OBJ [OBJ ...]] [--det DET [DET ...]] + usage: pypeit_setup_coadd2d [-h] + (-f PYPEIT_FILE | -d SCIENCE_DIR [SCIENCE_DIR ...]) + [--keep_par] [--obj OBJ [OBJ ...]] + [--det DET [DET ...]] [--only_slits ONLY_SLITS [ONLY_SLITS ...]] - [--offsets OFFSETS] [--weights WEIGHTS] + [--exclude_slits EXCLUDE_SLITS [EXCLUDE_SLITS ...]] + [--spat_toler SPAT_TOLER] [--offsets OFFSETS] + [--weights WEIGHTS] Prepare a configuration file for performing 2D coadds @@ -12,8 +16,10 @@ -h, --help show this help message and exit -f PYPEIT_FILE, --pypeit_file PYPEIT_FILE PypeIt reduction file (default: None) - -d SCIENCE_DIR, --science_dir SCIENCE_DIR - Directory with spec2d files to stack (default: None) + -d SCIENCE_DIR [SCIENCE_DIR ...], --science_dir SCIENCE_DIR [SCIENCE_DIR ...] + One or more directories with spec2d files to stack (use + wildcard to specify multiple directories). (default: + None) --keep_par Propagate all parameters from the pypeit file to the coadd2d file(s). If not set, only the required parameters and their default values are included in the @@ -35,8 +41,22 @@ mosaics made up of detectors 1,5 and 3,7, you would use --det 1,5 3,7 (default: None) --only_slits ONLY_SLITS [ONLY_SLITS ...] - A space-separated set of slits to coadd. If not - provided, all slits are coadded. (default: None) + A space-separated set of slits to coadd. Example syntax + -- --only_slits DET01:175,DET02:205 or MSC02:2234. If + not provided, all slits are coadded. If both --det and + --only_slits are provided, --det will be ignored. This + and --exclude_slits are mutually exclusive. If both are + provided, --only_slits takes precedence. (default: None) + --exclude_slits EXCLUDE_SLITS [EXCLUDE_SLITS ...] + A space-separated set of slits to exclude in the + coaddition. This and --only_slits are mutually + exclusive. If both are provided, --only_slits takes + precedence. (default: None) + --spat_toler SPAT_TOLER + Desired tolerance in spatial pixel used to identify + slits in different exposures. If not provided, the + default value for the specific instrument/configuration + is used. (default: None) --offsets OFFSETS Spatial offsets to apply to each image; see the [coadd2d][offsets] parameter. Options are restricted here to either maskdef_offsets or auto. If not diff --git a/doc/help/pypeit_skysub_regions.rst b/doc/help/pypeit_skysub_regions.rst index d5e887fde1..92b9b0ff8b 100644 --- a/doc/help/pypeit_skysub_regions.rst +++ b/doc/help/pypeit_skysub_regions.rst @@ -4,8 +4,8 @@ usage: pypeit_skysub_regions [-h] [--det DET] [-o] [-i] [-f] [-s] [-v VERBOSITY] file - Display a Raw science image and interactively define the sky regions using a - GUI. Run in the same folder as your .pypeit file + Display a spec2d frame and interactively define the sky regions using a GUI. Run + in the same folder as your .pypeit file positional arguments: file spec2d file diff --git a/doc/help/pypeit_trace_edges.rst b/doc/help/pypeit_trace_edges.rst index 8a15d76819..e631392121 100644 --- a/doc/help/pypeit_trace_edges.rst +++ b/doc/help/pypeit_trace_edges.rst @@ -29,17 +29,18 @@ providing files directly: bok_bc, gemini_flamingos1, gemini_flamingos2, gemini_gmos_north_e2v, gemini_gmos_north_ham, gemini_gmos_north_ham_ns, - gemini_gmos_south_ham, gemini_gnirs, gtc_maat, - gtc_osiris, gtc_osiris_plus, jwst_nircam, jwst_nirspec, - keck_deimos, keck_hires, keck_kcwi, keck_lris_blue, + gemini_gmos_south_ham, gemini_gnirs_echelle, + gemini_gnirs_ifu, gtc_maat, gtc_osiris, gtc_osiris_plus, + jwst_nircam, jwst_nirspec, keck_deimos, keck_esi, + keck_hires, keck_kcwi, keck_lris_blue, keck_lris_blue_orig, keck_lris_red, keck_lris_red_mark4, keck_lris_red_orig, keck_mosfire, keck_nires, keck_nirspec_low, lbt_luci1, lbt_luci2, lbt_mods1b, lbt_mods1r, lbt_mods2b, lbt_mods2r, ldt_deveny, magellan_fire, magellan_fire_long, magellan_mage, - mdm_osmos_mdm4k, mmt_binospec, mmt_bluechannel, - mmt_mmirs, not_alfosc, not_alfosc_vert, ntt_efosc2, - p200_dbsp_blue, p200_dbsp_red, p200_tspec, + mdm_modspec, mdm_osmos_mdm4k, mmt_binospec, + mmt_bluechannel, mmt_mmirs, not_alfosc, not_alfosc_vert, + ntt_efosc2, p200_dbsp_blue, p200_dbsp_red, p200_tspec, shane_kast_blue, shane_kast_red, shane_kast_red_ret, soar_goodman_blue, soar_goodman_red, tng_dolores, vlt_fors2, vlt_sinfoni, vlt_xshooter_nir, diff --git a/doc/help/pypeit_view_fits.rst b/doc/help/pypeit_view_fits.rst index 406eb833da..6958d5b646 100644 --- a/doc/help/pypeit_view_fits.rst +++ b/doc/help/pypeit_view_fits.rst @@ -3,7 +3,7 @@ $ pypeit_view_fits -h usage: pypeit_view_fits [-h] [--list] [--proc] [--bkg_file BKG_FILE] [--exten EXTEN] [--det [DET ...]] [--chname CHNAME] - [--embed] + [--showmask] [--embed] spectrograph file View FITS files with ginga @@ -13,14 +13,15 @@ gemini_flamingos1, gemini_flamingos2, gemini_gmos_north_e2v, gemini_gmos_north_ham, gemini_gmos_north_ham_ns, gemini_gmos_south_ham, - gemini_gnirs, gtc_maat, gtc_osiris, gtc_osiris_plus, - jwst_nircam, jwst_nirspec, keck_deimos, keck_hires, - keck_kcwi, keck_lris_blue, keck_lris_blue_orig, - keck_lris_red, keck_lris_red_mark4, keck_lris_red_orig, - keck_mosfire, keck_nires, keck_nirspec_low, lbt_luci1, - lbt_luci2, lbt_mods1b, lbt_mods1r, lbt_mods2b, - lbt_mods2r, ldt_deveny, magellan_fire, - magellan_fire_long, magellan_mage, mdm_osmos_mdm4k, + gemini_gnirs_echelle, gemini_gnirs_ifu, gtc_maat, + gtc_osiris, gtc_osiris_plus, jwst_nircam, jwst_nirspec, + keck_deimos, keck_esi, keck_hires, keck_kcwi, + keck_lris_blue, keck_lris_blue_orig, keck_lris_red, + keck_lris_red_mark4, keck_lris_red_orig, keck_mosfire, + keck_nires, keck_nirspec_low, lbt_luci1, lbt_luci2, + lbt_mods1b, lbt_mods1r, lbt_mods2b, lbt_mods2r, + ldt_deveny, magellan_fire, magellan_fire_long, + magellan_mage, mdm_modspec, mdm_osmos_mdm4k, mmt_binospec, mmt_bluechannel, mmt_mmirs, not_alfosc, not_alfosc_vert, ntt_efosc2, p200_dbsp_blue, p200_dbsp_red, p200_tspec, shane_kast_blue, @@ -49,5 +50,6 @@ gemini_gmos, keck_deimos, or keck_lris will show the mosaic of all detectors. (default: 1) --chname CHNAME Name of Ginga tab (default: Image) + --showmask Overplot masked pixels (default: False) --embed Upon completion embed in ipython shell (default: False) \ No newline at end of file diff --git a/doc/help/run_pypeit.rst b/doc/help/run_pypeit.rst index 5cdfda7e56..5a42c47fd0 100644 --- a/doc/help/run_pypeit.rst +++ b/doc/help/run_pypeit.rst @@ -9,13 +9,14 @@ ## Available spectrographs include: ## bok_bc, gemini_flamingos1, gemini_flamingos2, gemini_gmos_north_e2v, ## gemini_gmos_north_ham, gemini_gmos_north_ham_ns, - ## gemini_gmos_south_ham, gemini_gnirs, gtc_maat, gtc_osiris, - ## gtc_osiris_plus, jwst_nircam, jwst_nirspec, keck_deimos, keck_hires, - ## keck_kcwi, keck_lris_blue, keck_lris_blue_orig, keck_lris_red, - ## keck_lris_red_mark4, keck_lris_red_orig, keck_mosfire, keck_nires, - ## keck_nirspec_low, lbt_luci1, lbt_luci2, lbt_mods1b, lbt_mods1r, - ## lbt_mods2b, lbt_mods2r, ldt_deveny, magellan_fire, magellan_fire_long, - ## magellan_mage, mdm_osmos_mdm4k, mmt_binospec, mmt_bluechannel, + ## gemini_gmos_south_ham, gemini_gnirs_echelle, gemini_gnirs_ifu, + ## gtc_maat, gtc_osiris, gtc_osiris_plus, jwst_nircam, jwst_nirspec, + ## keck_deimos, keck_esi, keck_hires, keck_kcwi, keck_lris_blue, + ## keck_lris_blue_orig, keck_lris_red, keck_lris_red_mark4, + ## keck_lris_red_orig, keck_mosfire, keck_nires, keck_nirspec_low, + ## lbt_luci1, lbt_luci2, lbt_mods1b, lbt_mods1r, lbt_mods2b, lbt_mods2r, + ## ldt_deveny, magellan_fire, magellan_fire_long, magellan_mage, + ## mdm_modspec, mdm_osmos_mdm4k, mmt_binospec, mmt_bluechannel, ## mmt_mmirs, not_alfosc, not_alfosc_vert, ntt_efosc2, p200_dbsp_blue, ## p200_dbsp_red, p200_tspec, shane_kast_blue, shane_kast_red, ## shane_kast_red_ret, soar_goodman_blue, soar_goodman_red, tng_dolores, diff --git a/doc/include/class_datamodel_specobj.rst b/doc/include/class_datamodel_specobj.rst index c44849c9f0..4b03b76f55 100644 --- a/doc/include/class_datamodel_specobj.rst +++ b/doc/include/class_datamodel_specobj.rst @@ -1,5 +1,5 @@ -**Version**: 1.1.8 +**Version**: 1.1.9 ======================= =================================================================================================== ===================== ==================================================================================================================================================================================== Attribute Type Array Type Description @@ -15,6 +15,7 @@ Attribute Type ``BOX_FLAM_IVAR`` `numpy.ndarray`_ float Boxcar flux inverse variance (1e-17 erg/s/cm^2/Ang)^-2 ``BOX_FLAM_SIG`` `numpy.ndarray`_ float Boxcar flux uncertainty (1e-17 erg/s/cm^2/Ang) ``BOX_FRAC_USE`` `numpy.ndarray`_ float Fraction of pixels in the object profile subimage used for this extraction +``BOX_FWHM`` `numpy.ndarray`_ float Spectral FWHM (in Angstroms) at every pixel of the boxcar extracted flux. ``BOX_MASK`` `numpy.ndarray`_ `numpy.bool`_ Mask for boxcar extracted flux. True=good ``BOX_NPIX`` `numpy.ndarray`_ float Number of pixels used for the boxcar extraction; can be fractional ``BOX_RADIUS`` float Size of boxcar radius (pixels) @@ -51,6 +52,7 @@ Attribute Type ``OPT_FLAM_IVAR`` `numpy.ndarray`_ float Optimal flux inverse variance (1e-17 erg/s/cm^2/Ang)^-2 ``OPT_FLAM_SIG`` `numpy.ndarray`_ float Optimal flux uncertainty (1e-17 erg/s/cm^2/Ang) ``OPT_FRAC_USE`` `numpy.ndarray`_ float Fraction of pixels in the object profile subimage used for this extraction +``OPT_FWHM`` `numpy.ndarray`_ float Spectral FWHM (in Angstroms) at every pixel of the optimally extracted flux. ``OPT_MASK`` `numpy.ndarray`_ `numpy.bool`_ Mask for optimally extracted flux. True=good ``OPT_WAVE`` `numpy.ndarray`_ float Optimal Wavelengths in vacuum (Angstroms) ``PYPELINE`` str Name of the PypeIt pipeline mode @@ -64,7 +66,7 @@ Attribute Type ``VEL_TYPE`` str Type of heliocentric correction (if any) ``WAVE_RMS`` float, `numpy.floating`_ RMS (pix) for the wavelength solution for this slit. ``hand_extract_flag`` bool Boolean indicating if this is a forced extraction at the location provided by the user. -``maskwidth`` float, `numpy.floating`_ Size (in units of fwhm) of the region used for local sky subtraction +``maskwidth`` float, `numpy.floating`_ Size (in units of spatial fwhm) of the region used for local sky subtraction ``smash_peakflux`` float Peak value of the spectral direction collapsed spatial profile ``smash_snr`` float Peak S/N ratio of the spectral direction collapsed patial profile ``trace_spec`` `numpy.ndarray`_ int, `numpy.integer`_ Array of pixels along the spectral direction diff --git a/doc/include/class_datamodel_wavecalib.rst b/doc/include/class_datamodel_wavecalib.rst index abe9120c0c..eba69b1591 100644 --- a/doc/include/class_datamodel_wavecalib.rst +++ b/doc/include/class_datamodel_wavecalib.rst @@ -1,5 +1,5 @@ -**Version**: 1.1.0 +**Version**: 1.1.1 =============== ================ ================================================ =================================================================================================================================================================== Attribute Type Array Type Description @@ -7,6 +7,7 @@ Attribute Type Array Type ``PYP_SPEC`` str PypeIt spectrograph name ``arc_spectra`` `numpy.ndarray`_ `numpy.floating`_ 2D array: 1D extracted spectra, slit by slit (nspec, nslits) ``det_img`` `numpy.ndarray`_ `numpy.integer`_ Detector image which indicates which pixel in the mosaic corresponds to which detector; used occasionally by echelle. Currently only saved if ech_separate_2d=True +``fwhm_map`` `numpy.ndarray`_ :class:`~pypeit.core.fitting.PypeItFit` A fit that determines the spectral FWHM at every location of every slit ``lamps`` str List of arc lamps used for the wavelength calibration ``nslits`` int Total number of slits. This can include masked slits ``spat_ids`` `numpy.ndarray`_ `numpy.integer`_ Slit spat_ids. Named distinctly from that in WaveFit diff --git a/doc/include/datamodel_specobj.rst b/doc/include/datamodel_specobj.rst index eea96330c5..83ad68e14d 100644 --- a/doc/include/datamodel_specobj.rst +++ b/doc/include/datamodel_specobj.rst @@ -1,6 +1,6 @@ -Version: 1.1.8 +Version: 1.1.9 ======================= ========================= ================= ==================================================================================================================================================================================== Obj Key Obj Type Array Type Description @@ -16,6 +16,7 @@ Obj Key Obj Type Array Type Descripti ``BOX_FLAM_IVAR`` ndarray float Boxcar flux inverse variance (1e-17 erg/s/cm^2/Ang)^-2 ``BOX_FLAM_SIG`` ndarray float Boxcar flux uncertainty (1e-17 erg/s/cm^2/Ang) ``BOX_FRAC_USE`` ndarray float Fraction of pixels in the object profile subimage used for this extraction +``BOX_FWHM`` ndarray float Spectral FWHM (in Angstroms) at every pixel of the boxcar extracted flux. ``BOX_MASK`` ndarray bool Mask for boxcar extracted flux. True=good ``BOX_NPIX`` ndarray float Number of pixels used for the boxcar extraction; can be fractional ``BOX_RADIUS`` float Size of boxcar radius (pixels) @@ -52,6 +53,7 @@ Obj Key Obj Type Array Type Descripti ``OPT_FLAM_IVAR`` ndarray float Optimal flux inverse variance (1e-17 erg/s/cm^2/Ang)^-2 ``OPT_FLAM_SIG`` ndarray float Optimal flux uncertainty (1e-17 erg/s/cm^2/Ang) ``OPT_FRAC_USE`` ndarray float Fraction of pixels in the object profile subimage used for this extraction +``OPT_FWHM`` ndarray float Spectral FWHM (in Angstroms) at every pixel of the optimally extracted flux. ``OPT_MASK`` ndarray bool Mask for optimally extracted flux. True=good ``OPT_WAVE`` ndarray float Optimal Wavelengths in vacuum (Angstroms) ``PYPELINE`` str Name of the PypeIt pipeline mode @@ -65,7 +67,7 @@ Obj Key Obj Type Array Type Descripti ``VEL_TYPE`` str Type of heliocentric correction (if any) ``WAVE_RMS`` float, floating RMS (pix) for the wavelength solution for this slit. ``hand_extract_flag`` bool Boolean indicating if this is a forced extraction at the location provided by the user. -``maskwidth`` float, floating Size (in units of fwhm) of the region used for local sky subtraction +``maskwidth`` float, floating Size (in units of spatial fwhm) of the region used for local sky subtraction ``smash_peakflux`` float Peak value of the spectral direction collapsed spatial profile ``smash_snr`` float Peak S/N ratio of the spectral direction collapsed patial profile ``trace_spec`` ndarray int,numpy.integer Array of pixels along the spectral direction diff --git a/doc/include/datamodel_wavecalib.rst b/doc/include/datamodel_wavecalib.rst index b8e7eaef22..e9eea37072 100644 --- a/doc/include/datamodel_wavecalib.rst +++ b/doc/include/datamodel_wavecalib.rst @@ -1,5 +1,5 @@ -Version 1.1.0 +Version 1.1.1 ======================== ============================== ========= =================================================================================================================================== HDU Name HDU Type Data Type Description diff --git a/doc/include/dependencies_table.rst b/doc/include/dependencies_table.rst index 5412b694ef..28e333784a 100644 --- a/doc/include/dependencies_table.rst +++ b/doc/include/dependencies_table.rst @@ -1,5 +1,5 @@ ======================= ================================================================================================================================================================================================================================================================================================= Python Version ``>=3.9,<3.12`` -Required for users ``IPython>=7.10.0``, ``PyYAML>=5.1``, ``astropy>=4.3``, ``bottleneck``, ``configobj>=5.0.6``, ``extension-helpers>=0.1``, ``ginga>=3.2``, ``linetools``, ``matplotlib>=3.3``, ``numpy>=1.21``, ``packaging>=0.19``, ``pygithub``, ``pyqt6``, ``qtpy>=1.9``, ``scikit-learn>=1.0``, ``scipy>=1.7`` +Required for users ``IPython>=7.10.0``, ``PyYAML>=5.1``, ``astropy>=4.3``, ``bottleneck``, ``configobj>=5.0.6``, ``extension-helpers>=0.1``, ``ginga>=3.2``, ``linetools``, ``matplotlib>=3.7``, ``numpy>=1.22``, ``packaging>=0.19``, ``pygithub``, ``pyqt6``, ``qtpy>=1.9``, ``scikit-learn>=1.0``, ``scipy>=1.7`` Required for developers ``coverage``, ``docutils<0.18``, ``pytest-astropy``, ``pytest-cov``, ``pytest>=6.0.0``, ``sphinx-automodapi``, ``sphinx<6,>=1.6``, ``sphinx_rtd_theme==1.1.1``, ``tox`` ======================= ================================================================================================================================================================================================================================================================================================= diff --git a/doc/include/dev_suite_readme.rst b/doc/include/dev_suite_readme.rst index 57491ad4d6..f996477749 100644 --- a/doc/include/dev_suite_readme.rst +++ b/doc/include/dev_suite_readme.rst @@ -459,8 +459,8 @@ Additional Options --debug Debug using only blue setups (default: False) -p, --prep_only Only prepare to execute run_pypeit, but do not actually run it. (default: False) - -m, --do_not_reuse_masters - run pypeit without using any existing masters + -m, --do_not_reuse_calibs + run pypeit without using any existing calibrations (default: False) -t THREADS, --threads THREADS Run THREADS number of parallel tests. (default: 1) diff --git a/doc/include/gemini_gnirs_A.pypeit.rst b/doc/include/gemini_gnirs_echelle_A.pypeit.rst similarity index 98% rename from doc/include/gemini_gnirs_A.pypeit.rst rename to doc/include/gemini_gnirs_echelle_A.pypeit.rst index 06ca5ad954..71e0008845 100644 --- a/doc/include/gemini_gnirs_A.pypeit.rst +++ b/doc/include/gemini_gnirs_echelle_A.pypeit.rst @@ -5,7 +5,7 @@ # User-defined execution parameters [rdx] - spectrograph = gemini_gnirs + spectrograph = gemini_gnirs_echelle # Setup setup read @@ -17,7 +17,7 @@ # Data block data read - path /Users/westfall/Work/packages/PypeIt-development-suite/RAW_DATA/gemini_gnirs/32_SB_SXD + path /Users/westfall/Work/packages/PypeIt-development-suite/RAW_DATA/gemini_gnirs_echelle/32_SB_SXD filename | frametype | ra | dec | target | dispname | decker | binning | mjd | airmass | exptime | dispangle | dithoff | calib | comb_id | bkg_id cN20170331S0216.fits | arc,science,tilt | 205.53380833 | 9.47733611 | pisco | 32/mmSB_G5533 | 0.68arcsec_G5530 | 1,1 | 57843.3709743134 | 1.077 | 300.0 | 6.1887 | -0.34225501721318 | 0 | 5 | -1 cN20170331S0217.fits | arc,science,tilt | 205.53380833 | 9.47733611 | pisco | 32/mmSB_G5533 | 0.68arcsec_G5530 | 1,1 | 57843.3746886267 | 1.068 | 300.0 | 6.1887 | 2.65774498278682 | 0 | 6 | -1 diff --git a/doc/include/gemini_gnirs_A_corrected.pypeit.rst b/doc/include/gemini_gnirs_echelle_A_corrected.pypeit.rst similarity index 98% rename from doc/include/gemini_gnirs_A_corrected.pypeit.rst rename to doc/include/gemini_gnirs_echelle_A_corrected.pypeit.rst index e9b8cf8af5..aa7c8f103e 100644 --- a/doc/include/gemini_gnirs_A_corrected.pypeit.rst +++ b/doc/include/gemini_gnirs_echelle_A_corrected.pypeit.rst @@ -5,7 +5,7 @@ # User-defined execution parameters [rdx] - spectrograph = gemini_gnirs + spectrograph = gemini_gnirs_echelle [calibrations] [[wavelengths]] rms_threshold = 0.8, 0.4, 0.8, 0.8, 0.5, 0.9 @@ -18,7 +18,7 @@ # Read in the data data read - path /Users/joe/python/PypeIt-development-suite/RAW_DATA/gemini_gnirs/32_SB_SXD/ + path /Users/joe/python/PypeIt-development-suite/RAW_DATA/gemini_gnirs_echelle/32_SB_SXD/ | filename | date | frametype | target | exptime | dispname | decker | setup | calib | dispangle | idname | comb_id | bkg_id | | cN20170331S0206.fits | 2017-03-31 | standard | HIP62745 | 10.0 | 32/mmSB_G5533 | SCXD_G5531 | A | 0 | 6.1887 | OBJECT | 5 | 6 | | cN20170331S0207.fits | 2017-03-31 | standard | HIP62745 | 10.0 | 32/mmSB_G5533 | SCXD_G5531 | A | 0 | 6.1887 | OBJECT | 6 | 5 | diff --git a/doc/include/inst_detector_table.rst b/doc/include/inst_detector_table.rst index 5f18ef4af5..4e556258ec 100644 --- a/doc/include/inst_detector_table.rst +++ b/doc/include/inst_detector_table.rst @@ -16,7 +16,8 @@ Instrument Det specaxis specflip spatflip namp gain ``gemini_gmos_south_ham`` 1 1 False False 4 1.83, 1.83, 1.83, 1.83 3.98, 3.98, 3.98, 3.98 0.0 -1.0e+10 129000.0 0.9500 0.0800 ... 2 1 False False 4 1.83, 1.83, 1.83, 1.83 3.98, 3.98, 3.98, 3.98 0.0 -1.0e+10 123000.0 0.9500 0.0800 ... 3 1 False False 4 1.83, 1.83, 1.83, 1.83 3.98, 3.98, 3.98, 3.98 0.0 -1.0e+10 125000.0 0.9500 0.0800 -``gemini_gnirs`` 1 0 True True 1 13.5 7.0 0.15 -1.0e+10 150000.0 0.7100 0.1500 +``gemini_gnirs_echelle`` 1 0 True True 1 13.5 7.0 0.15 -1.0e+10 150000.0 0.7100 0.1500 +``gemini_gnirs_ifu`` 1 0 True True 1 13.5 7.0 0.15 -1.0e+10 150000.0 0.7100 0.1500 ``gtc_maat`` 1 1 True False 1 1.9 4.3 5.0 0.0e+00 65535.0 0.9500 0.1250 ``gtc_osiris`` 1 0 False False 1 0.95 4.5 0.0 0.0e+00 65535.0 0.9500 0.1270 ... 2 0 False False 1 0.95 4.5 0.0 0.0e+00 65535.0 0.9500 0.1270 @@ -33,6 +34,7 @@ Instrument Det specaxis specflip spatflip namp gain ... 6 0 False False 1 1.177 2.469 3.8 -1.0e+10 65535.0 0.9500 0.1185 ... 7 0 False False 1 1.201 2.518 3.3 -1.0e+10 65535.0 0.9500 0.1185 ... 8 0 False False 1 1.23 2.58 3.7 -1.0e+10 65535.0 0.9500 0.1185 +``keck_esi`` 1 0 False False 2 1.3, 1.3 2.5, 2.5 2.1 -1.0e+10 65535.0 0.9900 0.1542 ``keck_hires`` 1 0 False False 1 1.9 2.8 0.0 -1.0e+10 65535.0 0.7000 0.1350 ... 2 0 False False 1 2.1 3.1 0.0 -1.0e+10 65535.0 0.7000 0.1350 ... 3 0 False False 1 2.1 3.1 0.0 -1.0e+10 65535.0 0.7000 0.1350 @@ -43,7 +45,7 @@ Instrument Det specaxis specflip spatflip namp gain ... 2 0 True False 2 1.63, 1.7 3.6, 3.6 0.0 -1.0e+10 65535.0 0.8600 0.1350 ``keck_lris_red`` 1 0 False False 2 1.255, 1.18 4.64, 4.76 0.0 -1.0e+10 65535.0 0.7600 0.1350 ... 2 0 False False 2 1.191, 1.162 4.54, 4.62 0.0 -1.0e+10 65535.0 0.7600 0.1350 -``keck_lris_red_mark4`` 1 0 True False 2 1.61, 1.60153 3.65, 3.52 0.0 -1.0e+10 65535.0 0.7600 0.1230 +``keck_lris_red_mark4`` 1 0 True True 2 1.61, 1.60153 3.65, 3.52 0.0 -1.0e+10 65535.0 0.7600 0.1350 ``keck_lris_red_orig`` 1 1 False False 2 1.98, 2.17 6.1, 6.3 0.0 -1.0e+10 65535.0 0.7600 0.2100 ``keck_mosfire`` 1 1 False False 1 2.15 5.8 0.8 -1.0e+10 1000000000.0 1.0000 0.1798 ``keck_nires`` 1 1 True False 1 3.8 5.0 0.01 -1.0e+10 1000000.0 0.7600 0.1500 @@ -58,6 +60,7 @@ Instrument Det specaxis specflip spatflip namp gain ``magellan_fire`` 1 1 True False 1 1.2 5.0 0.01 -1.0e+10 100000.0 1.0000 0.1800 ``magellan_fire_long`` 1 0 False False 1 3.8 6.0 0.01 -1.0e+10 320000.0 0.8750 0.1500 ``magellan_mage`` 1 1 True False 1 1.02 2.9 1.0 -1.0e+10 65535.0 0.9900 0.3000 +``mdm_modspec`` 1 0 True False 1 1.3 7.9 0.0 -1.0e+10 65535.0 0.9700 0.2800 ``mdm_osmos_mdm4k`` 1 1 True False 4 2.2, 2.2, 2.2, 2.2 5.0, 5.0, 5.0, 5.0 0.0 -1.0e+10 65535.0 0.8600 0.2730 ``mmt_binospec`` 1 0 False False 4 1.085, 1.046, 1.042, 0.975 3.2, 3.2, 3.2, 3.2 3.0 -1.0e+10 65535.0 0.9500 0.2400 ... 2 0 False False 4 1.028, 1.115, 1.047, 1.045 3.6, 3.6, 3.6, 3.6 3.0 -1.0e+10 65535.0 0.9500 0.2400 diff --git a/doc/include/spectrographs_table.rst b/doc/include/spectrographs_table.rst index 82de9081ed..842a8efd29 100644 --- a/doc/include/spectrographs_table.rst +++ b/doc/include/spectrographs_table.rst @@ -8,16 +8,17 @@ gemini_gmos_north_e2v :class:`~pypeit.spectrographs.gemini_gmos.GeminiGMOSNE gemini_gmos_north_ham :class:`~pypeit.spectrographs.gemini_gmos.GeminiGMOSNHamSpectrograph` GEMINI-N GMOS-N `Link `__ MultiSlit True False Hamamatsu detector (R400, B600, R831); Used since Feb 2017; see :doc:`gemini_gmos` gemini_gmos_north_ham_ns :class:`~pypeit.spectrographs.gemini_gmos.GeminiGMOSNHamNSSpectrograph` GEMINI-N GMOS-N `Link `__ MultiSlit True False Same as gemini_gmos_north_ham when used in nod-and-shuffle mode; see :doc:`gemini_gmos` gemini_gmos_south_ham :class:`~pypeit.spectrographs.gemini_gmos.GeminiGMOSSHamSpectrograph` GEMINI-S GMOS-S `Link `__ MultiSlit True False Hamamatsu detector (R400, B600, R831); see :doc:`gemini_gmos` -gemini_gnirs_echelle :class:`~pypeit.spectrographs.gemini_gnirs.GeminiGNIRSEchelleSpectrograph` GEMINI-N GNIRS `Link `__ Echelle True False -gemini_gnirs_ifu :class:`~pypeit.spectrographs.gemini_gnirs.GNIRSIFUSpectrograph` GEMINI-N GNIRS `Link `__ IFU True False Support for LR-IFU and HR-IFU mode. Further testing still required... -gtc_maat :class:`~pypeit.spectrographs.gtc_osiris.GTCMAATSpectrograph` GTC OSIRIS `Link `__ IFU True False See :doc:`gtc_osiris` +gemini_gnirs_echelle :class:`~pypeit.spectrographs.gemini_gnirs.GeminiGNIRSEchelleSpectrograph` GEMINI-N GNIRS `Link `__ Echelle False False +gemini_gnirs_ifu :class:`~pypeit.spectrographs.gemini_gnirs.GNIRSIFUSpectrograph` GEMINI-N GNIRS `Link `__ IFU False False +gtc_maat :class:`~pypeit.spectrographs.gtc_osiris.GTCMAATSpectrograph` GTC OSIRIS `Link `__ IFU True False See :doc:`gtc_osiris` gtc_osiris :class:`~pypeit.spectrographs.gtc_osiris.GTCOSIRISSpectrograph` GTC OSIRIS `Link `__ MultiSlit True False See :doc:`gtc_osiris` gtc_osiris_plus :class:`~pypeit.spectrographs.gtc_osiris.GTCOSIRISPlusSpectrograph` GTC OSIRIS `Link `__ MultiSlit True False See :doc:`gtc_osiris` jwst_nircam :class:`~pypeit.spectrographs.jwst_nircam.JWSTNIRCamSpectrograph` JWST NIRCAM `Link `__ MultiSlit False False jwst_nirspec :class:`~pypeit.spectrographs.jwst_nirspec.JWSTNIRSpecSpectrograph` JWST NIRSPEC `Link `__ MultiSlit True False keck_deimos :class:`~pypeit.spectrographs.keck_deimos.KeckDEIMOSSpectrograph` KECK DEIMOS `Link `__ MultiSlit True True Supported gratings: 600ZD, 830G, 900ZD, 1200B, 1200G; see :doc:`deimos` +keck_esi :class:`~pypeit.spectrographs.keck_esi.KeckESISpectrograph` KECK ESI Echelle True False keck_hires :class:`~pypeit.spectrographs.keck_hires.KECKHIRESSpectrograph` KECK HIRES `Link `__ Echelle False False -keck_kcwi :class:`~pypeit.spectrographs.keck_kcwi.KeckKCWISpectrograph` KECK KCWI `Link `__ IFU True False Supported setups: BL, BM, BH2; see :doc:`keck_kcwi` +keck_kcwi :class:`~pypeit.spectrographs.keck_kcwi.KeckKCWISpectrograph` KECK KCWI `Link `__ IFU True False Supported setups: BM, BH2; see :doc:`keck_kcwi` keck_lris_blue :class:`~pypeit.spectrographs.keck_lris.KeckLRISBSpectrograph` KECK LRISb `Link `__ MultiSlit True False Blue camera; see :doc:`lris` keck_lris_blue_orig :class:`~pypeit.spectrographs.keck_lris.KeckLRISBOrigSpectrograph` KECK LRISb `Link `__ MultiSlit True False Original detector; replaced in 20??; see :doc:`lris` keck_lris_red :class:`~pypeit.spectrographs.keck_lris.KeckLRISRSpectrograph` KECK LRISr `Link `__ MultiSlit True True Red camera; LBNL detector, 2kx4k; see :doc:`lris` @@ -36,7 +37,8 @@ ldt_deveny :class:`~pypeit.spectrographs.ldt_deveny.LDTDeVenySpec magellan_fire :class:`~pypeit.spectrographs.magellan_fire.MagellanFIREEchelleSpectrograph` MAGELLAN FIRE `Link `__ Echelle True False Magellan/FIRE in echelle mode magellan_fire_long :class:`~pypeit.spectrographs.magellan_fire.MagellanFIRELONGSpectrograph` MAGELLAN FIRE `Link `__ MultiSlit True False Magellan/FIRE in long-slit/high-throughput mode magellan_mage :class:`~pypeit.spectrographs.magellan_mage.MagellanMAGESpectrograph` MAGELLAN MagE `Link `__ Echelle True False See :doc:`mage` -mdm_osmos_mdm4k :class:`~pypeit.spectrographs.mdm_osmos.MDMOSMOSMDM4KSpectrograph` KPNO MDM4K `Link `__ MultiSlit True False MDM OSMOS spectrometer +mdm_modspec :class:`~pypeit.spectrographs.mdm_modspec.MDMModspecEchelleSpectrograph` HILTNER Echelle MultiSlit True False MDM Modspec spectrometer; Only 1200l/mm disperser (so far) +mdm_osmos_mdm4k :class:`~pypeit.spectrographs.mdm_osmos.MDMOSMOSMDM4KSpectrograph` HILTNER MDM4K `Link `__ MultiSlit True False MDM OSMOS spectrometer mmt_binospec :class:`~pypeit.spectrographs.mmt_binospec.MMTBINOSPECSpectrograph` MMT BINOSPEC `Link `__ MultiSlit True False mmt_bluechannel :class:`~pypeit.spectrographs.mmt_bluechannel.MMTBlueChannelSpectrograph` MMT Blue_Channel `Link `__ MultiSlit True False mmt_mmirs :class:`~pypeit.spectrographs.mmt_mmirs.MMTMMIRSSpectrograph` MMT MMIRS `Link `__ MultiSlit True False diff --git a/doc/pypeit_par.rst b/doc/pypeit_par.rst index b1607074e3..f3daf02313 100644 --- a/doc/pypeit_par.rst +++ b/doc/pypeit_par.rst @@ -271,14 +271,14 @@ AlignPar Keywords Class Instantiation: :class:`~pypeit.par.pypeitpar.AlignPar` -=============== ============= ======= ============= ================================================================================================================================================================================================================================================================================ -Key Type Options Default Description -=============== ============= ======= ============= ================================================================================================================================================================================================================================================================================ -``locations`` list, ndarray .. 0.0, 0.5, 1.0 Locations of the bars, in a list, specified as a fraction of the slit width -``snr_thresh`` int, float .. 1.0 S/N ratio threshold for finding an alignment trace. This should be a low number to ensure that the algorithm finds all bars. The algorithm will then only use the N most significant detections, where N is the number of elements specified in the "locations" keyword argument -``trace_npoly`` int .. 4 Order of the polynomial to use when fitting the trace of a single bar -``trim_edge`` list .. 0, 0 Trim the slit by this number of pixels left/right before finding alignment bars -=============== ============= ======= ============= ================================================================================================================================================================================================================================================================================ +=============== ============= ======= ======== ================================================================================================================================================================================================================================================================================ +Key Type Options Default Description +=============== ============= ======= ======== ================================================================================================================================================================================================================================================================================ +``locations`` list, ndarray .. 0.0, 1.0 Locations of the bars, in a list, specified as a fraction of the slit width +``snr_thresh`` int, float .. 1.0 S/N ratio threshold for finding an alignment trace. This should be a low number to ensure that the algorithm finds all bars. The algorithm will then only use the N most significant detections, where N is the number of elements specified in the "locations" keyword argument +``trace_npoly`` int .. 4 Order of the polynomial to use when fitting the trace of a single bar +``trim_edge`` list .. 0, 0 Trim the slit by this number of pixels left/right before finding alignment bars +=============== ============= ======= ======== ================================================================================================================================================================================================================================================================================ ---- @@ -330,6 +330,7 @@ Class Instantiation: :class:`~pypeit.par.pypeitpar.EdgeTracePar` =========================== ================ =========================================== ============== ====================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================== Key Type Options Default Description =========================== ================ =========================================== ============== ====================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================== +``add_missed_orders`` bool .. False If orders are not detected by the automated edge tracing, attempt to add them based on their expected positions on on the detector. Echelle spectrographs only. ``add_predict`` str .. ``nearest`` Sets the method used to predict the shape of the left and right traces for a user-defined slit inserted. Options are (1) ``straight`` inserts traces with a constant spatial pixels position, (2) ``nearest`` inserts traces with a form identical to the automatically identified trace at the nearest spatial position to the inserted slit, or (3) ``pca`` uses the PCA decomposition to predict the shape of the traces. ``add_slits`` str, list .. .. Add one or more user-defined slits. The syntax to define a slit to add is: 'det:spec:spat_left:spat_right' where det=detector, spec=spectral pixel, spat_left=spatial pixel of left slit boundary, and spat_righ=spatial pixel of right slit boundary. For example, '2:2000:2121:2322,3:2000:1201:1500' will add a slit to detector 2 passing through spec=2000 extending spatially from 2121 to 2322 and another on detector 3 at spec=2000 extending from 1201 to 1500. ``auto_pca`` bool .. True During automated tracing, attempt to construct a PCA decomposition of the traces. When True, the edge traces resulting from the initial detection, centroid refinement, and polynomial fitting must meet a set of criteria for performing the pca; see :func:`pypeit.edgetrace.EdgeTraceSet.can_pca`. If False, the ``sync_predict`` parameter *cannot* be set to ``pca``; if it is not, the value is set to ``nearest`` and a warning is issued when validating the parameter set. @@ -417,7 +418,7 @@ Key Type Options Default Descrip ``sig_neigh`` int, float .. 10.0 Significance threshold for arcs to be used in line identification for the purpose of identifying neighboring lines. The tracethresh parameter above determines the significance threshold of lines that will be traced, but these lines must be at least nfwhm_neigh fwhm away from neighboring lines. This parameter determines the significance above which a line must be to be considered a possible colliding neighbor. A low value of sig_neigh will result in an overall larger number of lines, which will result in more lines above tracethresh getting rejected ``sigrej2d`` int, float .. 3.0 Outlier rejection significance determining which pixels on a fit to an arc line tilt are rejected by the global 2D fit ``sigrej_trace`` int, float .. 3.0 Outlier rejection significance to determine which traced arc lines should be included in the global fit -``spat_order`` int, float, list, ndarray .. 3 Order of the legendre polynomial to be fit to the the tilt of an arc line. This parameter determines both the orer of the *individual* arc line tilts, as well as the order of the spatial direction of the 2d legendre polynomial (spatial, spectral) that is fit to obtain a global solution for the tilts across the slit/order. This can be a single number or a list/array providing the value for each slit +``spat_order`` int, float, list, ndarray .. 3 Order of the legendre polynomial to be fit to the tilt of an arc line. This parameter determines both the order of the *individual* arc line tilts, as well as the order of the spatial direction of the 2d legendre polynomial (spatial, spectral) that is fit to obtain a global solution for the tilts across the slit/order. This can be a single number or a list/array providing the value for each slit ``spec_order`` int, float, list, ndarray .. 4 Order of the spectral direction of the 2d legendre polynomial (spatial, spectral) that is fit to obtain a global solution for the tilts across the slit/order. This can be a single number or a list/array providing the value for each slit ``tracethresh`` int, float, list, ndarray .. 20.0 Significance threshold for arcs to be used in tracing wavelength tilts. This can be a single number or a list/array providing the value for each slit/order. =================== ========================= ======= ============== ============================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================= @@ -445,6 +446,8 @@ Key Type Options ``func`` str .. ``legendre`` Function used for wavelength solution fits ``fwhm`` int, float .. 4.0 Spectral sampling of the arc lines. This is the FWHM of an arcline in binned pixels of the input arc image ``fwhm_fromlines`` bool .. False Estimate spectral resolution in each slit using the arc lines. If True, the estimated FWHM will override ``fwhm`` only in the determination of the wavelength solution (`i.e.`, not in WaveTilts). +``fwhm_spat_order`` int .. 0 This parameter determines the spatial polynomial order to use in the 2D polynomial fit to the FWHM of the arc lines. See also, fwhm_spec_order. +``fwhm_spec_order`` int .. 1 This parameter determines the spectral polynomial order to use in the 2D polynomial fit to the FWHM of the arc lines. See also, fwhm_spat_order. ``lamps`` list .. .. Name of one or more ions used for the wavelength calibration. Use ``None`` for no calibration. Choose ``use_header`` to use the list of lamps recorded in the header of the arc frames (this is currently available only for Keck DEIMOS and LDT DeVeny). ``match_toler`` float .. 2.0 Matching tolerance in pixels when searching for new lines. This is the difference in pixels between the wavlength assigned to an arc line by an iteration of the wavelength solution to the wavelength in the line list. This parameter is also used as the matching tolerance in pixels for a line reidentification. A good line match must match within this tolerance to the shifted and stretched archive spectrum, and the archive wavelength solution at this match must be within match_toler dispersion elements from the line in line list. ``method`` str ``holy-grail``, ``identify``, ``reidentify``, ``echelle``, ``full_template`` ``holy-grail`` Method to use to fit the individual arc lines. Note that some of the available methods should not be used; they are unstable and require significant parameter tweaking to succeed. You should use one of 'holy-grail', 'reidentify', or 'full_template'. 'holy-grail' attempts to get a first guess at line IDs by looking for patterns in the line locations. It is fully automated. When it works, it works well; however, it can fail catastrophically. Instead, 'reidentify' and 'full_template' are the preferred methods. They require an archived wavelength solution for your specific instrument/grating combination as a reference. This is used to anchor the wavelength solution for the data being reduced. All options are: holy-grail, identify, reidentify, echelle, full_template. @@ -522,9 +525,10 @@ Class Instantiation: :class:`~pypeit.par.pypeitpar.Coadd2DPar` ==================== ========= ======= ======== ======================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================== Key Type Options Default Description ==================== ========= ======= ======== ======================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================== +``exclude_slits`` str, list .. .. Exclude one or more slits from the coaddition. Example syntax -- DET01:175,DET02:205 or MSC02:2234. This and ``only_slits`` are mutually exclusive. If both are provided, ``only_slits`` takes precedence. ``manual`` str .. .. Manual extraction parameters. det:spat:spec:fwhm:boxcar_radius. Multiple manual extractions are semi-colon separated, and spat,spec are in the pseudo-image generated by COADD2D.boxcar_radius is optional and in pixels (not arcsec!). ``offsets`` str, list .. ``auto`` Offsets for the images being combined (spat pixels). Options are: ``maskdef_offsets``, ``header``, ``auto``, and a list of offsets. Use ``maskdef_offsets`` to use the offsets computed during the slitmask design matching (currently available for DEIMOS and MOSFIRE only). If equal to ``header``, the dither offsets recorded in the header, when available, will be used. If ``auto`` is chosen, PypeIt will try to compute the offsets using a reference object with the highest S/N, or an object selected by the user (see ``user_obj``). If a list of offsets is provided, PypeIt will use it. -``only_slits`` int, list .. .. Slit ID, or list of slit IDs that the user want to restrict the coadd to. I.e., only this/these slit/s will be coadded. +``only_slits`` str, list .. .. Restrict coaddition to one or more of slits. Example syntax -- DET01:175,DET02:205 or MSC02:2234. This and ``exclude_slits`` are mutually exclusive. If both are provided, ``only_slits`` takes precedence. ``spat_toler`` int .. 5 This parameter provides the desired tolerance in spatial pixel used to identify slits in different exposures ``use_slits4wvgrid`` bool .. False If True, use the slits to set the trace down the center ``user_obj`` int, list .. .. Object that the user wants to use to compute the weights and/or the offsets for coadding images. For slit spectroscopy, provide the ``SLITID`` and the ``OBJID``, separated by comma, of the selected object. For echelle spectroscopy, provide the ``ECH_OBJID`` of the selected object. See :doc:`out_spec1D` for more info about ``SLITID``, ``OBJID`` and ``ECH_OBJID``. If this parameter is not ``None``, it will be used to compute the offsets only if ``offsets = auto``, and it will used to compute the weights only if ``weights = auto``. @@ -656,6 +660,7 @@ Class Instantiation: :class:`~pypeit.par.pypeitpar.CubePar` ==================== ===== ======= ============ =========================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================== Key Type Options Default Description ==================== ===== ======= ============ =========================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================== +``align`` bool .. False If set to True, the input frames will be spatially aligned by cross-correlating the whitelight images with either a reference image (see `reference_image`) or the whitelight image that is generated using the first spec2d listed in the coadd3d file. ``astrometric`` bool .. True If true, an astrometric correction will be applied using the alignment frames. ``combine`` bool .. False If set to True, the input frames will be combined. Otherwise, a separate datacube will be generated for each input spec2d file, and will be saved as a spec3d file. ``dec_max`` float .. .. Maximum DEC to use when generating the WCS. If None, the default is maximum DEC based on the WCS of all spaxels. Units should be degrees. @@ -667,9 +672,9 @@ Key Type Options Default Description ``ra_min`` float .. .. Minimum RA to use when generating the WCS. If None, the default is minimum RA based on the WCS of all spaxels. Units should be degrees. ``reference_image`` str .. .. White light image of a previously combined datacube. The white light image will be used as a reference when calculating the offsets of the input spec2d files. Ideally, the reference image should have the same shape as the data to be combined (i.e. set the ra_min, ra_max etc. params so they are identical to the reference image). ``relative_weights`` bool .. False If set to True, the combined frames will use a relative weighting scheme. This only works well if there is a common continuum source in the field of view of all input observations, and is generally only required if high relative precision is desired. -``save_whitelight`` bool .. False Save a white light image of the combined datacube. The output filename will be given by the "output_filename" variable with a suffix "_whitelight". Note that the white light image collapses the flux along the wavelength axis, so some spaxels in the 2D white light image may have different wavelength ranges. If combine=False, the individual spec3d files will have a suffix "_whitelight". +``save_whitelight`` bool .. False Save a white light image of the combined datacube. The output filename will be given by the "output_filename" variable with a suffix "_whitelight". Note that the white light image collapses the flux along the wavelength axis, so some spaxels in the 2D white light image may have different wavelength ranges. To set the wavelength range, use the "whitelight_range" parameter. If combine=False, the individual spec3d files will have a suffix "_whitelight". ``scale_corr`` str .. .. This option performs a small correction for the relative spectral illumination scale of different spec2D files. Specify the relative path+file to the spec2D file that you would like to use for the relative scaling. If you want to perform this correction, it is best to use the spec2d file with the highest S/N sky spectrum. You should choose the same frame for both the standards and science frames. -``skysub_frame`` str .. ``image`` Set the sky subtraction to be implemented. The default behaviour is to subtract the sky using the model that is derived from each individual image (i.e. set this parameter to "image"). To turn off sky subtraction completely, set this parameter to "none" (all lowercase). Finally, if you want to use a different frame for the sky subtraction, specify the relative path+file to the spec2D file that you would like to use for the sky subtraction. The model fit to the sky of the specified frame will be used. Note, the sky and science frames do not need to have the same exposure time. +``skysub_frame`` str .. ``image`` Set the sky subtraction to be implemented. The default behaviour is to subtract the sky using the model that is derived from each individual image (i.e. set this parameter to "image"). To turn off sky subtraction completely, set this parameter to "none" (all lowercase). Finally, if you want to use a different frame for the sky subtraction, specify the relative path+file to the spec2D file that you would like to use for the sky subtraction. The model fit to the sky of the specified frame will be used. Note, the sky and science frames do not need to have the same exposure time; the sky model will be scaled to the science frame based on the relative exposure time. ``slit_spec`` bool .. True If the data use slits in one spatial direction, set this to True. If the data uses fibres for all spaxels, set this to False. ``spat_subpixel`` int .. 5 When method=subpixel, spat_subpixel sets the subpixellation scale of each detector pixel in the spatial direction. The total number of subpixels in each pixel is given by spec_subpixel x spat_subpixel. The default option is to divide each spec2d pixel into 25 subpixels during datacube creation. See also, spec_subpixel. ``spatial_delta`` float .. .. The spatial size of each spaxel to use when generating the WCS (in arcsec). If None, the default is set by the spectrograph file. @@ -678,6 +683,7 @@ Key Type Options Default Description ``wave_delta`` float .. .. The wavelength step to use when generating the WCS (in Angstroms). If None, the default is set by the wavelength solution. ``wave_max`` float .. .. Maximum wavelength to use when generating the WCS. If None, the default is maximum wavelength based on the WCS of all spaxels. Units should be Angstroms. ``wave_min`` float .. .. Minimum wavelength to use when generating the WCS. If None, the default is minimum wavelength based on the WCS of all spaxels. Units should be Angstroms. +``whitelight_range`` list .. None, None A two element list specifying the wavelength range over which to generate the white light image. The first (second) element is the minimum (maximum) wavelength to use. If either of these elements are None, PypeIt will automatically use a wavelength range that ensures all spaxels have the same wavelength coverage. Note, if you are using a reference_image to align all frames, it is preferable to use the same white light wavelength range for all white light images. For example, you may wish to use an emission line map to register two frames. ==================== ===== ======= ============ =========================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================== @@ -984,7 +990,7 @@ Alterations to the default parameters are: spectrograph = bok_bc [calibrations] [[biasframe]] - exprng = None, 1 + exprng = None, 0.001, [[[process]]] combine = median use_biasimage = False @@ -993,7 +999,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_biasimage = False @@ -1001,7 +1007,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[arcframe]] - exprng = None, 120 + exprng = None, 120, [[[process]]] use_biasimage = False use_overscan = False @@ -1022,7 +1028,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] use_biasimage = False use_overscan = False @@ -1062,7 +1068,7 @@ Alterations to the default parameters are: noise_floor = 0.01 use_illumflat = False [[standardframe]] - exprng = None, 120 + exprng = None, 120, [[[process]]] mask_cr = True use_biasimage = False @@ -1070,14 +1076,14 @@ Alterations to the default parameters are: noise_floor = 0.01 use_illumflat = False [[wavelengths]] - lamps = NeI, ArI, ArII, HeI + lamps = NeI, ArI, ArII, HeI, fwhm = 5.0 rms_threshold = 0.5 [[slitedges]] edge_thresh = 50.0 sync_predict = nearest [scienceframe] - exprng = 90, None + exprng = 90, None, [[process]] mask_cr = True sigclip = 5.0 @@ -1116,7 +1122,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 20, None + exprng = 20, None, [[[process]]] mask_cr = True use_biasimage = False @@ -1124,7 +1130,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[arcframe]] - exprng = 1, 50 + exprng = 1, 50, [[[process]]] use_biasimage = False use_overscan = False @@ -1183,7 +1189,7 @@ Alterations to the default parameters are: noise_floor = 0.01 use_illumflat = False [[standardframe]] - exprng = None, 60 + exprng = None, 60, [[[process]]] mask_cr = True use_biasimage = False @@ -1192,7 +1198,7 @@ Alterations to the default parameters are: use_illumflat = False [[wavelengths]] method = full_template - lamps = ArI, ArII, ThAr, NeI + lamps = ArI, ArII, ThAr, NeI, sigdetect = 3 fwhm = 20 reid_arxiv = magellan_fire_long.fits @@ -1204,7 +1210,7 @@ Alterations to the default parameters are: [[tilts]] tracethresh = 5 [scienceframe] - exprng = 20, None + exprng = 20, None, [[process]] mask_cr = True use_biasimage = False @@ -1214,7 +1220,7 @@ Alterations to the default parameters are: [reduce] [[findobj]] snr_thresh = 5.0 - find_trim_edge = 50, 50 + find_trim_edge = 50, 50, .. _instr_par-gemini_flamingos2: @@ -1236,7 +1242,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 20, None + exprng = 20, None, [[[process]]] mask_cr = True use_biasimage = False @@ -1244,14 +1250,14 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[arcframe]] - exprng = 50, None + exprng = 50, None, [[[process]]] use_biasimage = False use_overscan = False use_pixelflat = False use_illumflat = False [[tiltframe]] - exprng = 50, None + exprng = 50, None, [[[process]]] use_biasimage = False use_overscan = False @@ -1304,7 +1310,7 @@ Alterations to the default parameters are: noise_floor = 0.01 use_illumflat = False [[standardframe]] - exprng = None, 30 + exprng = None, 30, [[[process]]] mask_cr = True use_biasimage = False @@ -1312,7 +1318,7 @@ Alterations to the default parameters are: noise_floor = 0.01 use_illumflat = False [[wavelengths]] - lamps = OH_NIRES + lamps = OH_NIRES, fwhm = 5 rms_threshold = 0.5 match_toler = 5.0 @@ -1325,7 +1331,7 @@ Alterations to the default parameters are: tracethresh = 5 spat_order = 4 [scienceframe] - exprng = 20, None + exprng = 20, None, [[process]] mask_cr = True use_biasimage = False @@ -1335,7 +1341,7 @@ Alterations to the default parameters are: [reduce] [[findobj]] snr_thresh = 5.0 - find_trim_edge = 10, 10 + find_trim_edge = 10, 10, [[skysub]] sky_sigrej = 5.0 [sensfunc] @@ -1354,7 +1360,7 @@ Alterations to the default parameters are: [rdx] spectrograph = gemini_gmos_north_e2v - detnum = (1, 2, 3) + detnum = (1, 2, 3), [calibrations] [[biasframe]] [[[process]]] @@ -1411,7 +1417,7 @@ Alterations to the default parameters are: noise_floor = 0.01 [[wavelengths]] method = full_template - lamps = CuI, ArI, ArII + lamps = CuI, ArI, ArII, rms_threshold = 0.4 nsnippet = 1 [[slitedges]] @@ -1437,7 +1443,7 @@ Alterations to the default parameters are: [rdx] spectrograph = gemini_gmos_north_ham - detnum = (1, 2, 3) + detnum = (1, 2, 3), [calibrations] [[biasframe]] [[[process]]] @@ -1494,7 +1500,7 @@ Alterations to the default parameters are: noise_floor = 0.01 [[wavelengths]] method = full_template - lamps = CuI, ArI, ArII + lamps = CuI, ArI, ArII, rms_threshold = 0.4 nsnippet = 1 [[slitedges]] @@ -1520,7 +1526,7 @@ Alterations to the default parameters are: [rdx] spectrograph = gemini_gmos_north_ham_ns - detnum = (1, 2, 3) + detnum = (1, 2, 3), [calibrations] [[biasframe]] [[[process]]] @@ -1577,7 +1583,7 @@ Alterations to the default parameters are: noise_floor = 0.01 [[wavelengths]] method = full_template - lamps = CuI, ArI, ArII + lamps = CuI, ArI, ArII, rms_threshold = 0.4 nsnippet = 1 [[slitedges]] @@ -1603,7 +1609,7 @@ Alterations to the default parameters are: [rdx] spectrograph = gemini_gmos_south_ham - detnum = (1, 2, 3) + detnum = (1, 2, 3), [calibrations] [[biasframe]] [[[process]]] @@ -1660,7 +1666,7 @@ Alterations to the default parameters are: noise_floor = 0.01 [[wavelengths]] method = full_template - lamps = CuI, ArI, ArII + lamps = CuI, ArI, ArII, rms_threshold = 0.4 nsnippet = 1 [[slitedges]] @@ -1681,16 +1687,16 @@ Alterations to the default parameters are: [[IR]] telgridfile = TelFit_LasCampanas_3100_26100_R20000.fits -.. _instr_par-gemini_gnirs: +.. _instr_par-gemini_gnirs_echelle: -GEMINI-N GNIRS (``gemini_gnirs``) ---------------------------------- +GEMINI-N GNIRS (``gemini_gnirs_echelle``) +----------------------------------------- Alterations to the default parameters are: .. code-block:: ini [rdx] - spectrograph = gemini_gnirs + spectrograph = gemini_gnirs_echelle [calibrations] [[biasframe]] [[[process]]] @@ -1720,7 +1726,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pixelflatframe]] - exprng = None, 30 + exprng = None, 30, [[[process]]] satpix = nothing use_biasimage = False @@ -1740,7 +1746,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[traceframe]] - exprng = None, 30 + exprng = None, 30, [[[process]]] use_biasimage = False use_overscan = False @@ -1768,7 +1774,7 @@ Alterations to the default parameters are: noise_floor = 0.01 use_illumflat = False [[standardframe]] - exprng = None, 30 + exprng = None, 30, [[[process]]] use_biasimage = False use_overscan = False @@ -1776,8 +1782,10 @@ Alterations to the default parameters are: use_illumflat = False [[flatfield]] tweak_slits_thresh = 0.9 + [[tilts]] + spat_order = 1 [scienceframe] - exprng = 30, None + exprng = 30, None, [[process]] mask_cr = True use_biasimage = False @@ -1786,7 +1794,7 @@ Alterations to the default parameters are: use_illumflat = False [reduce] [[findobj]] - find_trim_edge = 2, 2 + find_trim_edge = 2, 2, maxnumber_sci = 2 maxnumber_std = 1 [[skysub]] @@ -1801,6 +1809,142 @@ Alterations to the default parameters are: [[IR]] telgridfile = TelFit_MaunaKea_3100_26100_R20000.fits +.. _instr_par-gemini_gnirs_ifu: + +GEMINI-N GNIRS (``gemini_gnirs_ifu``) +------------------------------------- +Alterations to the default parameters are: + +.. code-block:: ini + + [rdx] + spectrograph = gemini_gnirs_ifu + [calibrations] + [[biasframe]] + [[[process]]] + combine = median + use_biasimage = False + use_overscan = False + shot_noise = False + use_pixelflat = False + use_illumflat = False + [[darkframe]] + [[[process]]] + mask_cr = True + use_biasimage = False + use_overscan = False + use_pixelflat = False + use_illumflat = False + [[arcframe]] + [[[process]]] + use_biasimage = False + use_overscan = False + use_pixelflat = False + use_illumflat = False + [[tiltframe]] + [[[process]]] + use_biasimage = False + use_overscan = False + use_pixelflat = False + use_illumflat = False + [[pixelflatframe]] + exprng = None, 30, + [[[process]]] + satpix = nothing + use_biasimage = False + use_overscan = False + use_pixelflat = False + use_illumflat = False + [[pinholeframe]] + [[[process]]] + use_biasimage = False + use_overscan = False + use_illumflat = False + [[alignframe]] + [[[process]]] + satpix = nothing + use_biasimage = False + use_overscan = False + use_pixelflat = False + use_illumflat = False + [[traceframe]] + exprng = None, 30, + [[[process]]] + use_biasimage = False + use_overscan = False + use_pixelflat = False + use_illumflat = False + [[illumflatframe]] + [[[process]]] + satpix = nothing + use_biasimage = False + use_overscan = False + use_pixelflat = False + use_illumflat = False + [[lampoffflatsframe]] + [[[process]]] + satpix = nothing + use_biasimage = False + use_overscan = False + use_pixelflat = False + use_illumflat = False + [[skyframe]] + [[[process]]] + mask_cr = True + use_biasimage = False + use_overscan = False + noise_floor = 0.01 + use_illumflat = False + [[standardframe]] + exprng = None, 30, + [[[process]]] + use_biasimage = False + use_overscan = False + noise_floor = 0.01 + use_illumflat = False + [[flatfield]] + tweak_slits_thresh = 0.0 + tweak_slits_maxfrac = 0.0 + slit_trim = 2 + slit_illum_finecorr = False + [[slitedges]] + pad = 2 + [[tilts]] + spat_order = 1 + spec_order = 1 + [scienceframe] + exprng = 30, None, + [[process]] + mask_cr = True + sigclip = 4.0 + objlim = 1.5 + use_biasimage = False + use_overscan = False + noise_floor = 0.01 + use_illumflat = False + [reduce] + [[findobj]] + find_trim_edge = 2, 2, + maxnumber_sci = 2 + maxnumber_std = 1 + [[skysub]] + global_sky_std = False + no_poly = True + [[extraction]] + model_full_slit = True + skip_extraction = True + [[cube]] + grating_corr = False + [flexure] + spec_maxshift = 0 + [sensfunc] + algorithm = IR + polyorder = 6 + [[UVIS]] + extinct_correct = False + [[IR]] + telgridfile = TelFit_MaunaKea_3100_26100_R20000.fits + .. _instr_par-gtc_maat: GTC OSIRIS (``gtc_maat``) @@ -1813,7 +1957,7 @@ Alterations to the default parameters are: spectrograph = gtc_maat [calibrations] [[biasframe]] - exprng = None, 1 + exprng = None, 0.001, [[[process]]] combine = median use_biasimage = False @@ -1821,7 +1965,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_pixelflat = False @@ -1845,7 +1989,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[alignframe]] [[[process]]] satpix = nothing @@ -1870,7 +2014,7 @@ Alterations to the default parameters are: mask_cr = True noise_floor = 0.01 [[standardframe]] - exprng = None, 180 + exprng = None, 180, [[[process]]] mask_cr = True noise_floor = 0.01 @@ -1878,7 +2022,7 @@ Alterations to the default parameters are: slit_illum_finecorr = False [[wavelengths]] method = full_template - lamps = XeI,HgI,NeI,ArI + lamps = XeI, HgI, NeI, ArI, [[slitedges]] sync_predict = nearest bound_detector = True @@ -1886,7 +2030,7 @@ Alterations to the default parameters are: spat_order = 1 spec_order = 1 [scienceframe] - exprng = 90, None + exprng = 90, None, [[process]] mask_cr = True sigclip = 4.0 @@ -1921,7 +2065,7 @@ Alterations to the default parameters are: spectrograph = gtc_osiris [calibrations] [[biasframe]] - exprng = None, 1 + exprng = None, 0.001, [[[process]]] combine = median use_biasimage = False @@ -1929,7 +2073,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_pixelflat = False @@ -1953,7 +2097,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[alignframe]] [[[process]]] satpix = nothing @@ -1978,13 +2122,13 @@ Alterations to the default parameters are: mask_cr = True noise_floor = 0.01 [[standardframe]] - exprng = None, 180 + exprng = None, 180, [[[process]]] mask_cr = True noise_floor = 0.01 [[wavelengths]] method = full_template - lamps = XeI,HgI,NeI,ArI + lamps = XeI, HgI, NeI, ArI, [[slitedges]] sync_predict = nearest bound_detector = True @@ -1992,7 +2136,7 @@ Alterations to the default parameters are: spat_order = 5 spec_order = 5 [scienceframe] - exprng = 90, None + exprng = 90, None, [[process]] mask_cr = True noise_floor = 0.01 @@ -2014,7 +2158,7 @@ Alterations to the default parameters are: spectrograph = gtc_osiris_plus [calibrations] [[biasframe]] - exprng = None, 1 + exprng = None, 0.001, [[[process]]] combine = median use_biasimage = False @@ -2022,7 +2166,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_pixelflat = False @@ -2046,7 +2190,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[alignframe]] [[[process]]] satpix = nothing @@ -2071,13 +2215,13 @@ Alterations to the default parameters are: mask_cr = True noise_floor = 0.01 [[standardframe]] - exprng = None, 180 + exprng = None, 180, [[[process]]] mask_cr = True noise_floor = 0.01 [[wavelengths]] method = full_template - lamps = XeI,HgI,NeI,ArI + lamps = XeI, HgI, NeI, ArI, [[slitedges]] sync_predict = nearest bound_detector = True @@ -2085,7 +2229,7 @@ Alterations to the default parameters are: spat_order = 5 spec_order = 5 [scienceframe] - exprng = 90, None + exprng = 90, None, [[process]] mask_cr = True noise_floor = 0.01 @@ -2166,9 +2310,9 @@ Alterations to the default parameters are: objlim = 2.0 noise_floor = 0.01 [reduce] - trim_edge = 0, 0 + trim_edge = 0, 0, [[findobj]] - find_trim_edge = 0, 0 + find_trim_edge = 0, 0, maxnumber_sci = 2 find_fwhm = 2.0 [[skysub]] @@ -2252,9 +2396,9 @@ Alterations to the default parameters are: objlim = 2.0 noise_floor = 0.01 [reduce] - trim_edge = 0, 0 + trim_edge = 0, 0, [[findobj]] - find_trim_edge = 0, 0 + find_trim_edge = 0, 0, maxnumber_sci = 2 find_fwhm = 2.0 [[skysub]] @@ -2278,7 +2422,7 @@ Alterations to the default parameters are: [rdx] spectrograph = keck_deimos - detnum = (1, 5), (2, 6), (3, 7), (4, 8) + detnum = (1, 5), (2, 6), (3, 7), (4, 8), [calibrations] [[biasframe]] [[[process]]] @@ -2350,7 +2494,7 @@ Alterations to the default parameters are: use_biasimage = False noise_floor = 0.01 [[wavelengths]] - lamps = ArI, NeI, KrI, XeI + lamps = ArI, NeI, KrI, XeI, match_toler = 2.5 n_first = 3 [[slitedges]] @@ -2374,6 +2518,107 @@ Alterations to the default parameters are: [[IR]] telgridfile = TelFit_MaunaKea_3100_26100_R20000.fits +.. _instr_par-keck_esi: + +KECK ESI (``keck_esi``) +----------------------- +Alterations to the default parameters are: + +.. code-block:: ini + + [rdx] + spectrograph = keck_esi + [calibrations] + [[biasframe]] + [[[process]]] + combine = median + use_biasimage = False + shot_noise = False + use_pixelflat = False + use_illumflat = False + [[darkframe]] + exprng = 1, None, + [[[process]]] + mask_cr = True + use_pixelflat = False + use_illumflat = False + [[arcframe]] + exprng = 300, None, + [[[process]]] + use_pixelflat = False + use_illumflat = False + [[tiltframe]] + [[[process]]] + use_pixelflat = False + use_illumflat = False + [[pixelflatframe]] + [[[process]]] + satpix = nothing + use_pixelflat = False + use_illumflat = False + [[alignframe]] + [[[process]]] + satpix = nothing + use_pixelflat = False + use_illumflat = False + [[traceframe]] + [[[process]]] + use_pixelflat = False + use_illumflat = False + [[illumflatframe]] + [[[process]]] + satpix = nothing + use_pixelflat = False + use_illumflat = False + [[lampoffflatsframe]] + [[[process]]] + satpix = nothing + use_pixelflat = False + use_illumflat = False + [[skyframe]] + [[[process]]] + mask_cr = True + noise_floor = 0.01 + [[standardframe]] + exprng = None, 60, + [[[process]]] + mask_cr = True + noise_floor = 0.01 + [[wavelengths]] + method = reidentify + echelle = True + ech_sigrej = 3.0 + lamps = CuI, ArI, NeI, HgI, XeI, ArII, + reid_arxiv = keck_esi_ECH.fits + cc_thresh = 0.5 + cc_local_thresh = 0.5 + rms_threshold = 0.2 + [[slitedges]] + edge_thresh = 5.0 + det_min_spec_length = 0.2 + max_shift_adj = 3.0 + fit_min_spec_length = 0.4 + left_right_pca = True + pca_order = 3 + pca_sigrej = 1.5 + add_missed_orders = True + [[tilts]] + tracethresh = 10.0 + [scienceframe] + exprng = 60, None, + [[process]] + satpix = nothing + mask_cr = True + sigclip = 20.0 + noise_floor = 0.01 + [reduce] + [[findobj]] + find_trim_edge = 4, 4, + maxnumber_sci = 2 + maxnumber_std = 1 + [[extraction]] + model_full_slit = True + .. _instr_par-keck_hires: KECK HIRES (``keck_hires``) @@ -2384,7 +2629,7 @@ Alterations to the default parameters are: [rdx] spectrograph = keck_hires - detnum = (1, 2, 3) + detnum = (1, 2, 3), [calibrations] [[biasframe]] [[[process]]] @@ -2475,7 +2720,7 @@ Alterations to the default parameters are: method = echelle echelle = True ech_sigrej = 3.0 - lamps = ThAr + lamps = ThAr, fwhm = 8.0 cc_thresh = 0.5 cc_local_thresh = 0.5 @@ -2504,7 +2749,7 @@ Alterations to the default parameters are: use_illumflat = False [reduce] [[findobj]] - find_trim_edge = 3, 3 + find_trim_edge = 3, 3, [[skysub]] global_sky_std = False [[extraction]] @@ -2527,7 +2772,7 @@ Alterations to the default parameters are: spectrograph = keck_kcwi [calibrations] [[biasframe]] - exprng = None, 0.01 + exprng = None, 0.001, [[[process]]] combine = median use_biasimage = False @@ -2536,7 +2781,7 @@ Alterations to the default parameters are: use_illumflat = False use_pattern = True [[darkframe]] - exprng = 0.01, None + exprng = 0.01, None, [[[process]]] mask_cr = True use_pixelflat = False @@ -2562,7 +2807,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[alignment]] - locations = 0.1, 0.3, 0.5, 0.7, 0.9 + locations = 0.1, 0.3, 0.5, 0.7, 0.9, [[traceframe]] [[[process]]] use_pixelflat = False @@ -2594,7 +2839,10 @@ Alterations to the default parameters are: slit_illum_ref_idx = 14 slit_illum_smooth_npix = 5 fit_2d_det_response = True + [[wavelengths]] + fwhm_spat_order = 2 [[slitedges]] + edge_thresh = 5 fit_order = 4 pad = 2 [scienceframe] @@ -2604,6 +2852,7 @@ Alterations to the default parameters are: objlim = 1.5 use_biasimage = False noise_floor = 0.01 + use_specillum = True use_pattern = True [reduce] [[skysub]] @@ -2628,7 +2877,7 @@ Alterations to the default parameters are: spectrograph = keck_lris_blue [calibrations] [[biasframe]] - exprng = None, 1 + exprng = None, 0.001, [[[process]]] combine = median use_biasimage = False @@ -2636,7 +2885,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_pixelflat = False @@ -2650,20 +2899,20 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pixelflatframe]] - exprng = None, 300 + exprng = None, 300, [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[alignframe]] [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[traceframe]] - exprng = None, 300 + exprng = None, 300, [[[process]]] use_pixelflat = False use_illumflat = False @@ -2682,14 +2931,14 @@ Alterations to the default parameters are: mask_cr = True noise_floor = 0.01 [[standardframe]] - exprng = None, 30 + exprng = None, 30, [[[process]]] mask_cr = True noise_floor = 0.01 spat_flexure_correct = True [[wavelengths]] method = full_template - lamps = NeI, ArI, CdI, KrI, XeI, ZnI, HgI + lamps = NeI, ArI, CdI, KrI, XeI, ZnI, HgI, sigdetect = 10.0 rms_threshold = 0.2 match_toler = 2.5 @@ -2703,7 +2952,7 @@ Alterations to the default parameters are: minimum_slit_length = 4.0 minimum_slit_length_sci = 6 [scienceframe] - exprng = 60, None + exprng = 60, None, [[process]] mask_cr = True noise_floor = 0.01 @@ -2726,7 +2975,7 @@ Alterations to the default parameters are: spectrograph = keck_lris_blue_orig [calibrations] [[biasframe]] - exprng = None, 1 + exprng = None, 0.001, [[[process]]] combine = median use_biasimage = False @@ -2734,7 +2983,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_pixelflat = False @@ -2748,20 +2997,20 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pixelflatframe]] - exprng = None, 300 + exprng = None, 300, [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[alignframe]] [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[traceframe]] - exprng = None, 300 + exprng = None, 300, [[[process]]] use_pixelflat = False use_illumflat = False @@ -2780,14 +3029,14 @@ Alterations to the default parameters are: mask_cr = True noise_floor = 0.01 [[standardframe]] - exprng = None, 30 + exprng = None, 30, [[[process]]] mask_cr = True noise_floor = 0.01 spat_flexure_correct = True [[wavelengths]] method = full_template - lamps = NeI, ArI, CdI, KrI, XeI, ZnI, HgI + lamps = NeI, ArI, CdI, KrI, XeI, ZnI, HgI, sigdetect = 10.0 rms_threshold = 0.2 match_toler = 2.5 @@ -2801,7 +3050,7 @@ Alterations to the default parameters are: minimum_slit_length = 4.0 minimum_slit_length_sci = 6 [scienceframe] - exprng = 60, None + exprng = 60, None, [[process]] mask_cr = True noise_floor = 0.01 @@ -2824,7 +3073,7 @@ Alterations to the default parameters are: spectrograph = keck_lris_red [calibrations] [[biasframe]] - exprng = None, 1 + exprng = None, 0.001, [[[process]]] combine = median use_biasimage = False @@ -2832,7 +3081,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_pixelflat = False @@ -2846,20 +3095,20 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pixelflatframe]] - exprng = None, 60 + exprng = None, 60, [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[alignframe]] [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[traceframe]] - exprng = None, 60 + exprng = None, 60, [[[process]]] use_pixelflat = False use_illumflat = False @@ -2878,13 +3127,13 @@ Alterations to the default parameters are: mask_cr = True noise_floor = 0.01 [[standardframe]] - exprng = None, 30 + exprng = None, 30, [[[process]]] mask_cr = True noise_floor = 0.01 spat_flexure_correct = True [[wavelengths]] - lamps = NeI, ArI, CdI, KrI, XeI, ZnI, HgI + lamps = NeI, ArI, CdI, KrI, XeI, ZnI, HgI, sigdetect = 10.0 rms_threshold = 0.2 [[slitedges]] @@ -2900,7 +3149,7 @@ Alterations to the default parameters are: maxdev2d = 1.0 sigrej2d = 5.0 [scienceframe] - exprng = 60, None + exprng = 60, None, [[process]] mask_cr = True sigclip = 5.0 @@ -2930,7 +3179,7 @@ Alterations to the default parameters are: spectrograph = keck_lris_red_mark4 [calibrations] [[biasframe]] - exprng = None, 1 + exprng = None, 0.001, [[[process]]] combine = median use_biasimage = False @@ -2938,7 +3187,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_pixelflat = False @@ -2952,20 +3201,20 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pixelflatframe]] - exprng = None, 60 + exprng = None, 60, [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[alignframe]] [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[traceframe]] - exprng = None, 60 + exprng = None, 60, [[[process]]] use_pixelflat = False use_illumflat = False @@ -2984,13 +3233,13 @@ Alterations to the default parameters are: mask_cr = True noise_floor = 0.01 [[standardframe]] - exprng = None, 30 + exprng = None, 30, [[[process]]] mask_cr = True noise_floor = 0.01 spat_flexure_correct = True [[wavelengths]] - lamps = NeI, ArI, CdI, KrI, XeI, ZnI, HgI + lamps = NeI, ArI, CdI, KrI, XeI, ZnI, HgI, sigdetect = 10.0 rms_threshold = 0.2 [[slitedges]] @@ -3006,7 +3255,7 @@ Alterations to the default parameters are: maxdev2d = 1.0 sigrej2d = 5.0 [scienceframe] - exprng = 60, None + exprng = 60, None, [[process]] mask_cr = True sigclip = 5.0 @@ -3036,7 +3285,7 @@ Alterations to the default parameters are: spectrograph = keck_lris_red_orig [calibrations] [[biasframe]] - exprng = None, 1 + exprng = None, 0.001, [[[process]]] combine = median use_biasimage = False @@ -3044,7 +3293,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_pixelflat = False @@ -3058,20 +3307,20 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pixelflatframe]] - exprng = None, 60 + exprng = None, 60, [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[alignframe]] [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[traceframe]] - exprng = None, 60 + exprng = None, 60, [[[process]]] use_pixelflat = False use_illumflat = False @@ -3090,13 +3339,13 @@ Alterations to the default parameters are: mask_cr = True noise_floor = 0.01 [[standardframe]] - exprng = None, 30 + exprng = None, 30, [[[process]]] mask_cr = True noise_floor = 0.01 spat_flexure_correct = True [[wavelengths]] - lamps = NeI, ArI, KrI, XeI, HgI + lamps = NeI, ArI, KrI, XeI, HgI, sigdetect = 10.0 rms_threshold = 0.2 [[slitedges]] @@ -3112,7 +3361,7 @@ Alterations to the default parameters are: maxdev2d = 1.0 sigrej2d = 5.0 [scienceframe] - exprng = 60, None + exprng = 60, None, [[process]] mask_cr = True sigclip = 5.0 @@ -3150,7 +3399,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 1, None + exprng = 1, None, [[[process]]] mask_cr = True use_biasimage = False @@ -3158,7 +3407,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[arcframe]] - exprng = 1, None + exprng = 1, None, [[[process]]] use_biasimage = False use_overscan = False @@ -3215,21 +3464,21 @@ Alterations to the default parameters are: use_overscan = False noise_floor = 0.01 [[standardframe]] - exprng = None, 20 + exprng = None, 20, [[[process]]] mask_cr = True use_biasimage = False use_overscan = False noise_floor = 0.01 [[wavelengths]] - lamps = OH_NIRES + lamps = OH_NIRES, fwhm = 5.0 rms_threshold = 0.3 [[slitedges]] edge_thresh = 50.0 sync_predict = nearest [scienceframe] - exprng = 20, None + exprng = 20, None, [[process]] satpix = nothing mask_cr = True @@ -3277,14 +3526,14 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[arcframe]] - exprng = 61, None + exprng = 61, None, [[[process]]] use_biasimage = False use_overscan = False use_pixelflat = False use_illumflat = False [[tiltframe]] - exprng = 61, None + exprng = 61, None, [[[process]]] use_biasimage = False use_overscan = False @@ -3337,7 +3586,7 @@ Alterations to the default parameters are: noise_floor = 0.01 use_illumflat = False [[standardframe]] - exprng = None, 60 + exprng = None, 60, [[[process]]] mask_cr = True use_biasimage = False @@ -3349,11 +3598,11 @@ Alterations to the default parameters are: echelle = True ech_norder_coeff = 6 ech_sigrej = 3.0 - lamps = OH_NIRES + lamps = OH_NIRES, fwhm = 5.0 reid_arxiv = keck_nires.fits rms_threshold = 0.2 - n_final = 3, 4, 4, 4, 4 + n_final = 3, 4, 4, 4, 4, [[slitedges]] fit_min_spec_length = 0.4 left_right_pca = True @@ -3362,7 +3611,7 @@ Alterations to the default parameters are: [[tilts]] tracethresh = 10.0 [scienceframe] - exprng = 61, None + exprng = 61, None, [[process]] satpix = nothing mask_cr = True @@ -3404,7 +3653,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 20, None + exprng = 20, None, [[[process]]] mask_cr = True use_biasimage = False @@ -3412,7 +3661,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[arcframe]] - exprng = 20, None + exprng = 20, None, [[[process]]] use_biasimage = False use_overscan = False @@ -3471,7 +3720,7 @@ Alterations to the default parameters are: noise_floor = 0.01 use_illumflat = False [[standardframe]] - exprng = None, 20 + exprng = None, 20, [[[process]]] mask_cr = True use_biasimage = False @@ -3481,14 +3730,14 @@ Alterations to the default parameters are: [[flatfield]] tweak_slits_thresh = 0.8 [[wavelengths]] - lamps = OH_NIRES + lamps = OH_NIRES, fwhm = 5.0 rms_threshold = 0.2 [[slitedges]] edge_thresh = 200.0 sync_predict = nearest [scienceframe] - exprng = 20, None + exprng = 20, None, [[process]] satpix = nothing mask_cr = True @@ -3598,7 +3847,7 @@ Alterations to the default parameters are: noise_floor = 0.01 use_illumflat = False [[wavelengths]] - lamps = OH_NIRES + lamps = OH_NIRES, fwhm = 5.0 rms_threshold = 0.2 [[slitedges]] @@ -3711,7 +3960,7 @@ Alterations to the default parameters are: noise_floor = 0.01 use_illumflat = False [[wavelengths]] - lamps = OH_NIRES + lamps = OH_NIRES, fwhm = 5.0 rms_threshold = 0.2 [[slitedges]] @@ -3747,7 +3996,7 @@ Alterations to the default parameters are: spectrograph = lbt_mods1b [calibrations] [[biasframe]] - exprng = None, 1 + exprng = None, 0.001, [[[process]]] combine = median use_biasimage = False @@ -3755,7 +4004,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_pixelflat = False @@ -3771,20 +4020,20 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pixelflatframe]] - exprng = 0, None + exprng = 0, None, [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[alignframe]] [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[traceframe]] - exprng = 0, None + exprng = 0, None, [[[process]]] use_pixelflat = False use_illumflat = False @@ -3803,12 +4052,12 @@ Alterations to the default parameters are: mask_cr = True noise_floor = 0.01 [[standardframe]] - exprng = 1, 200 + exprng = 1, 200, [[[process]]] mask_cr = True noise_floor = 0.01 [[wavelengths]] - lamps = XeI, KrI, ArI, HgI + lamps = XeI, KrI, ArI, HgI, sigdetect = 10.0 rms_threshold = 0.4 [[slitedges]] @@ -3820,7 +4069,7 @@ Alterations to the default parameters are: spec_order = 5 maxdev2d = 0.02 [scienceframe] - exprng = 200, None + exprng = 200, None, [[process]] mask_cr = True noise_floor = 0.01 @@ -3839,7 +4088,7 @@ Alterations to the default parameters are: spectrograph = lbt_mods1r [calibrations] [[biasframe]] - exprng = None, 1 + exprng = None, 0.001, [[[process]]] combine = median use_biasimage = False @@ -3847,7 +4096,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_pixelflat = False @@ -3863,20 +4112,20 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pixelflatframe]] - exprng = 0, None + exprng = 0, None, [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[alignframe]] [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[traceframe]] - exprng = 0, None + exprng = 0, None, [[[process]]] use_pixelflat = False use_illumflat = False @@ -3895,12 +4144,12 @@ Alterations to the default parameters are: mask_cr = True noise_floor = 0.01 [[standardframe]] - exprng = 1, 200 + exprng = 1, 200, [[[process]]] mask_cr = True noise_floor = 0.01 [[wavelengths]] - lamps = ArI, NeI, KrI, XeI + lamps = ArI, NeI, KrI, XeI, fwhm = 10.0 rms_threshold = 0.4 match_toler = 2.5 @@ -3914,7 +4163,7 @@ Alterations to the default parameters are: spec_order = 5 maxdev2d = 0.02 [scienceframe] - exprng = 200, None + exprng = 200, None, [[process]] mask_cr = True noise_floor = 0.01 @@ -3933,7 +4182,7 @@ Alterations to the default parameters are: spectrograph = lbt_mods2b [calibrations] [[biasframe]] - exprng = None, 1 + exprng = None, 0.001, [[[process]]] combine = median use_biasimage = False @@ -3941,7 +4190,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_pixelflat = False @@ -3957,20 +4206,20 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pixelflatframe]] - exprng = 0, None + exprng = 0, None, [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[alignframe]] [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[traceframe]] - exprng = 0, None + exprng = 0, None, [[[process]]] use_pixelflat = False use_illumflat = False @@ -3989,12 +4238,12 @@ Alterations to the default parameters are: mask_cr = True noise_floor = 0.01 [[standardframe]] - exprng = 1, 200 + exprng = 1, 200, [[[process]]] mask_cr = True noise_floor = 0.01 [[wavelengths]] - lamps = XeI, KrI, ArI, HgI + lamps = XeI, KrI, ArI, HgI, sigdetect = 10.0 rms_threshold = 0.4 [[slitedges]] @@ -4006,7 +4255,7 @@ Alterations to the default parameters are: spec_order = 5 maxdev2d = 0.02 [scienceframe] - exprng = 200, None + exprng = 200, None, [[process]] mask_cr = True noise_floor = 0.01 @@ -4025,7 +4274,7 @@ Alterations to the default parameters are: spectrograph = lbt_mods2r [calibrations] [[biasframe]] - exprng = None, 1 + exprng = None, 0.001, [[[process]]] combine = median use_biasimage = False @@ -4033,7 +4282,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_pixelflat = False @@ -4049,20 +4298,20 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pixelflatframe]] - exprng = 0, None + exprng = 0, None, [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[alignframe]] [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[traceframe]] - exprng = 0, None + exprng = 0, None, [[[process]]] use_pixelflat = False use_illumflat = False @@ -4081,12 +4330,12 @@ Alterations to the default parameters are: mask_cr = True noise_floor = 0.01 [[standardframe]] - exprng = 1, 200 + exprng = 1, 200, [[[process]]] mask_cr = True noise_floor = 0.01 [[wavelengths]] - lamps = ArI, NeI, KrI, XeI + lamps = ArI, NeI, KrI, XeI, fwhm = 10.0 rms_threshold = 1.0 match_toler = 2.5 @@ -4100,7 +4349,7 @@ Alterations to the default parameters are: spec_order = 5 maxdev2d = 0.02 [scienceframe] - exprng = 200, None + exprng = 200, None, [[process]] mask_cr = True noise_floor = 0.01 @@ -4186,7 +4435,7 @@ Alterations to the default parameters are: slit_illum_finecorr = False [[wavelengths]] method = full_template - lamps = use_header + lamps = use_header, fwhm_fromlines = True n_first = 3 n_final = 5 @@ -4245,7 +4494,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 20, None + exprng = 20, None, [[[process]]] mask_cr = True use_biasimage = False @@ -4253,7 +4502,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[arcframe]] - exprng = 20, None + exprng = 20, None, [[[process]]] use_biasimage = False use_overscan = False @@ -4312,7 +4561,7 @@ Alterations to the default parameters are: noise_floor = 0.01 use_illumflat = False [[standardframe]] - exprng = None, 60 + exprng = None, 60, [[[process]]] mask_cr = True use_biasimage = False @@ -4324,13 +4573,13 @@ Alterations to the default parameters are: echelle = True ech_norder_coeff = 6 ech_sigrej = 3.0 - lamps = OH_FIRE_Echelle - sigdetect = 5, 10, 10, 10, 10, 20, 30, 30, 30, 30, 30, 10, 30, 30, 60, 30, 30, 10, 20, 30, 10 + lamps = OH_FIRE_Echelle, + sigdetect = 5, 10, 10, 10, 10, 20, 30, 30, 30, 30, 30, 10, 30, 30, 60, 30, 30, 10, 20, 30, 10, reid_arxiv = magellan_fire_echelle.fits cc_thresh = 0.35 rms_threshold = 1.0 match_toler = 30.0 - n_final = 3, 3, 3, 2, 4, 4, 4, 3, 4, 4, 4, 3, 4, 4, 4, 4, 4, 4, 6, 6, 4 + n_final = 3, 3, 3, 2, 4, 4, 4, 3, 4, 4, 4, 3, 4, 4, 4, 4, 4, 4, 6, 6, 4, [[slitedges]] edge_thresh = 3.0 max_shift_adj = 0.5 @@ -4341,7 +4590,7 @@ Alterations to the default parameters are: [[tilts]] tracethresh = 5 [scienceframe] - exprng = 20, None + exprng = 20, None, [[process]] satpix = nothing mask_cr = True @@ -4381,7 +4630,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 20, None + exprng = 20, None, [[[process]]] mask_cr = True use_biasimage = False @@ -4389,7 +4638,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[arcframe]] - exprng = 1, 50 + exprng = 1, 50, [[[process]]] use_biasimage = False use_overscan = False @@ -4448,7 +4697,7 @@ Alterations to the default parameters are: noise_floor = 0.01 use_illumflat = False [[standardframe]] - exprng = None, 60 + exprng = None, 60, [[[process]]] mask_cr = True use_biasimage = False @@ -4457,7 +4706,7 @@ Alterations to the default parameters are: use_illumflat = False [[wavelengths]] method = full_template - lamps = ArI, ArII, ThAr, NeI + lamps = ArI, ArII, ThAr, NeI, sigdetect = 3 fwhm = 20 reid_arxiv = magellan_fire_long.fits @@ -4469,7 +4718,7 @@ Alterations to the default parameters are: [[tilts]] tracethresh = 5 [scienceframe] - exprng = 20, None + exprng = 20, None, [[process]] mask_cr = True use_biasimage = False @@ -4479,7 +4728,7 @@ Alterations to the default parameters are: [reduce] [[findobj]] snr_thresh = 5 - find_trim_edge = 50, 50 + find_trim_edge = 50, 50, [sensfunc] [[IR]] telgridfile = TelFit_LasCampanas_3100_26100_R20000.fits @@ -4503,13 +4752,13 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 20, None + exprng = 20, None, [[[process]]] mask_cr = True use_pixelflat = False use_illumflat = False [[arcframe]] - exprng = 20, None + exprng = 20, None, [[[process]]] use_pixelflat = False use_illumflat = False @@ -4546,7 +4795,7 @@ Alterations to the default parameters are: mask_cr = True noise_floor = 0.01 [[standardframe]] - exprng = None, 20 + exprng = None, 20, [[[process]]] mask_cr = True noise_floor = 0.01 @@ -4554,7 +4803,7 @@ Alterations to the default parameters are: method = reidentify echelle = True ech_sigrej = 3.0 - lamps = ThAr_MagE + lamps = ThAr_MagE, reid_arxiv = magellan_mage.fits cc_thresh = 0.5 cc_local_thresh = 0.5 @@ -4567,7 +4816,7 @@ Alterations to the default parameters are: [[tilts]] tracethresh = 10.0 [scienceframe] - exprng = 20, None + exprng = 20, None, [[process]] satpix = nothing mask_cr = True @@ -4575,16 +4824,106 @@ Alterations to the default parameters are: noise_floor = 0.01 [reduce] [[findobj]] - find_trim_edge = 4, 4 + find_trim_edge = 4, 4, maxnumber_sci = 2 maxnumber_std = 1 [[extraction]] model_full_slit = True +.. _instr_par-mdm_modspec: + +HILTNER Echelle (``mdm_modspec``) +--------------------------------- +Alterations to the default parameters are: + +.. code-block:: ini + + [rdx] + spectrograph = mdm_modspec + [calibrations] + [[biasframe]] + exprng = None, 0.001, + [[[process]]] + overscan_method = median + combine = median + use_biasimage = False + shot_noise = False + use_pixelflat = False + use_illumflat = False + [[darkframe]] + exprng = 999999, None, + [[[process]]] + mask_cr = True + use_pixelflat = False + use_illumflat = False + [[arcframe]] + [[[process]]] + clip = False + use_pixelflat = False + use_illumflat = False + subtract_continuum = True + [[tiltframe]] + [[[process]]] + clip = False + use_pixelflat = False + use_illumflat = False + subtract_continuum = True + [[pixelflatframe]] + [[[process]]] + satpix = nothing + n_lohi = 1, 1, + comb_sigrej = 3.0 + use_pixelflat = False + use_illumflat = False + [[pinholeframe]] + exprng = 999999, None, + [[alignframe]] + [[[process]]] + satpix = nothing + use_pixelflat = False + use_illumflat = False + [[traceframe]] + [[[process]]] + use_pixelflat = False + use_illumflat = False + [[illumflatframe]] + [[[process]]] + satpix = nothing + use_pixelflat = False + use_illumflat = False + [[lampoffflatsframe]] + [[[process]]] + satpix = nothing + use_pixelflat = False + use_illumflat = False + [[skyframe]] + [[[process]]] + mask_cr = True + noise_floor = 0.01 + [[standardframe]] + [[[process]]] + mask_cr = True + noise_floor = 0.01 + [[flatfield]] + slit_illum_finecorr = False + [[wavelengths]] + method = full_template + lamps = ArI, XeI, NeI, + reid_arxiv = mdm_modspec_1200_5100.fits + n_final = 9 + [[slitedges]] + sync_predict = nearest + bound_detector = True + [scienceframe] + exprng = 10, 600, + [[process]] + mask_cr = True + noise_floor = 0.01 + .. _instr_par-mdm_osmos_mdm4k: -KPNO MDM4K (``mdm_osmos_mdm4k``) --------------------------------- +HILTNER MDM4K (``mdm_osmos_mdm4k``) +----------------------------------- Alterations to the default parameters are: .. code-block:: ini @@ -4593,7 +4932,7 @@ Alterations to the default parameters are: spectrograph = mdm_osmos_mdm4k [calibrations] [[biasframe]] - exprng = None, 1 + exprng = None, 0.001, [[[process]]] combine = median use_biasimage = False @@ -4601,7 +4940,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_pixelflat = False @@ -4621,7 +4960,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[alignframe]] [[[process]]] satpix = nothing @@ -4646,19 +4985,19 @@ Alterations to the default parameters are: mask_cr = True noise_floor = 0.01 [[standardframe]] - exprng = None, 120 + exprng = None, 120, [[[process]]] mask_cr = True noise_floor = 0.01 [[wavelengths]] method = full_template - lamps = ArI, XeI + lamps = ArI, XeI, sigdetect = 10.0 reid_arxiv = mdm_osmos_mdm4k.fits [[slitedges]] sync_predict = nearest [scienceframe] - exprng = 90, None + exprng = 90, None, [[process]] mask_cr = True noise_floor = 0.01 @@ -4682,14 +5021,14 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 20, None + exprng = 20, None, [[[process]]] mask_cr = True use_biasimage = False use_pixelflat = False use_illumflat = False [[arcframe]] - exprng = 20, None + exprng = 20, None, [[[process]]] use_biasimage = False use_pixelflat = False @@ -4737,14 +5076,14 @@ Alterations to the default parameters are: use_biasimage = False noise_floor = 0.01 [[standardframe]] - exprng = None, 100 + exprng = None, 100, [[[process]]] mask_cr = True use_biasimage = False noise_floor = 0.01 [[wavelengths]] method = full_template - lamps = HeI, NeI, ArI, ArII + lamps = HeI, NeI, ArI, ArII, fwhm = 5.0 rms_threshold = 0.5 [[slitedges]] @@ -4754,7 +5093,7 @@ Alterations to the default parameters are: spat_order = 6 spec_order = 6 [scienceframe] - exprng = 20, None + exprng = 20, None, [[process]] mask_cr = True sigclip = 5.0 @@ -4791,14 +5130,14 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 300, None + exprng = 300, None, [[[process]]] mask_cr = True use_biasimage = False use_pixelflat = False use_illumflat = False [[arcframe]] - exprng = 1, None + exprng = 1, None, [[[process]]] use_biasimage = False use_pixelflat = False @@ -4809,7 +5148,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pixelflatframe]] - exprng = None, 600 + exprng = None, 600, [[[process]]] satpix = nothing use_biasimage = False @@ -4826,13 +5165,13 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[traceframe]] - exprng = None, 600 + exprng = None, 600, [[[process]]] use_biasimage = False use_pixelflat = False use_illumflat = False [[illumflatframe]] - exprng = 1, None + exprng = 1, None, [[[process]]] satpix = nothing use_biasimage = False @@ -4851,14 +5190,14 @@ Alterations to the default parameters are: noise_floor = 0.01 use_illumflat = False [[standardframe]] - exprng = None, 600 + exprng = None, 600, [[[process]]] mask_cr = True use_biasimage = False noise_floor = 0.01 use_illumflat = False [[wavelengths]] - lamps = use_header + lamps = use_header, fwhm_fromlines = True rms_threshold = 0.5 [[slitedges]] @@ -4899,7 +5238,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 30, None + exprng = 30, None, [[[process]]] mask_cr = True use_biasimage = False @@ -4907,14 +5246,14 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[arcframe]] - exprng = 60, None + exprng = 60, None, [[[process]]] use_biasimage = False use_overscan = False use_pixelflat = False use_illumflat = False [[tiltframe]] - exprng = 60, None + exprng = 60, None, [[[process]]] use_biasimage = False use_overscan = False @@ -4967,7 +5306,7 @@ Alterations to the default parameters are: noise_floor = 0.01 use_illumflat = False [[standardframe]] - exprng = None, 60 + exprng = None, 60, [[[process]]] mask_cr = True use_biasimage = False @@ -4975,7 +5314,7 @@ Alterations to the default parameters are: noise_floor = 0.01 use_illumflat = False [[wavelengths]] - lamps = OH_NIRES + lamps = OH_NIRES, fwhm = 5 rms_threshold = 0.5 match_toler = 5.0 @@ -4990,7 +5329,7 @@ Alterations to the default parameters are: spat_order = 7 spec_order = 5 [scienceframe] - exprng = 30, None + exprng = 30, None, [[process]] mask_cr = True grow = 0.5 @@ -5022,7 +5361,7 @@ Alterations to the default parameters are: spectrograph = not_alfosc [calibrations] [[biasframe]] - exprng = None, 1 + exprng = None, 1, [[[process]]] combine = median use_biasimage = False @@ -5031,7 +5370,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_overscan = False @@ -5059,7 +5398,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] use_overscan = False [[alignframe]] @@ -5091,14 +5430,14 @@ Alterations to the default parameters are: use_overscan = False noise_floor = 0.01 [[standardframe]] - exprng = None, 120 + exprng = None, 120, [[[process]]] mask_cr = True use_overscan = False noise_floor = 0.01 [[wavelengths]] method = full_template - lamps = HeI, NeI, ArI + lamps = HeI, NeI, ArI, sigdetect = 10.0 [[slitedges]] edge_thresh = 30 @@ -5106,7 +5445,7 @@ Alterations to the default parameters are: bound_detector = True minimum_slit_gap = 15 [scienceframe] - exprng = 10, None + exprng = 10, None, [[process]] mask_cr = True use_overscan = False @@ -5124,7 +5463,7 @@ Alterations to the default parameters are: spectrograph = not_alfosc_vert [calibrations] [[biasframe]] - exprng = None, 1 + exprng = None, 1, [[[process]]] combine = median use_biasimage = False @@ -5133,7 +5472,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_overscan = False @@ -5161,7 +5500,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] use_overscan = False [[alignframe]] @@ -5193,14 +5532,14 @@ Alterations to the default parameters are: use_overscan = False noise_floor = 0.01 [[standardframe]] - exprng = None, 120 + exprng = None, 120, [[[process]]] mask_cr = True use_overscan = False noise_floor = 0.01 [[wavelengths]] method = full_template - lamps = HeI, NeI, ArI + lamps = HeI, NeI, ArI, sigdetect = 10.0 [[slitedges]] edge_thresh = 30 @@ -5208,7 +5547,7 @@ Alterations to the default parameters are: bound_detector = True minimum_slit_gap = 15 [scienceframe] - exprng = 10, None + exprng = 10, None, [[process]] mask_cr = True use_overscan = False @@ -5281,7 +5620,7 @@ Alterations to the default parameters are: tweak_slits_thresh = 0.9 [[wavelengths]] method = full_template - lamps = HeI, ArI + lamps = HeI, ArI, sigdetect = 10.0 rms_threshold = 0.25 [[slitedges]] @@ -5314,7 +5653,7 @@ Alterations to the default parameters are: [calibrations] bpm_usebias = True [[biasframe]] - exprng = None, 1 + exprng = None, 0.001, [[[process]]] combine = median use_biasimage = False @@ -5322,13 +5661,13 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_pixelflat = False use_illumflat = False [[arcframe]] - exprng = None, 120 + exprng = None, 120, [[[process]]] use_pixelflat = False use_illumflat = False @@ -5343,7 +5682,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[alignframe]] [[[process]]] satpix = nothing @@ -5368,19 +5707,19 @@ Alterations to the default parameters are: mask_cr = True noise_floor = 0.01 [[standardframe]] - exprng = None, 120 + exprng = None, 120, [[[process]]] combine = median mask_cr = True noise_floor = 0.01 [[wavelengths]] method = full_template - lamps = FeI, ArI, ArII + lamps = FeI, ArI, ArII, [[slitedges]] fit_min_spec_length = 0.55 sync_predict = nearest [scienceframe] - exprng = 90, None + exprng = 90, None, [[process]] combine = median mask_cr = True @@ -5402,7 +5741,7 @@ Alterations to the default parameters are: [calibrations] bpm_usebias = True [[biasframe]] - exprng = None, 1 + exprng = None, 0.001, [[[process]]] combine = median use_biasimage = False @@ -5410,13 +5749,13 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_pixelflat = False use_illumflat = False [[arcframe]] - exprng = None, 120 + exprng = None, 120, [[[process]]] use_pixelflat = False use_illumflat = False @@ -5431,7 +5770,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[alignframe]] [[[process]]] satpix = nothing @@ -5456,18 +5795,18 @@ Alterations to the default parameters are: mask_cr = True noise_floor = 0.01 [[standardframe]] - exprng = None, 120 + exprng = None, 120, [[[process]]] combine = median mask_cr = True noise_floor = 0.01 [[wavelengths]] method = full_template - lamps = ArI, ArII, NeI, HeI + lamps = ArI, ArII, NeI, HeI, [[slitedges]] sync_predict = nearest [scienceframe] - exprng = 90, None + exprng = 90, None, [[process]] combine = median mask_cr = True @@ -5500,7 +5839,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 0, None + exprng = 0, None, [[[process]]] mask_cr = True use_biasimage = False @@ -5508,14 +5847,14 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[arcframe]] - exprng = 100, None + exprng = 100, None, [[[process]]] use_biasimage = False use_overscan = False use_pixelflat = False use_illumflat = False [[tiltframe]] - exprng = 100, None + exprng = 100, None, [[[process]]] use_biasimage = False use_overscan = False @@ -5568,7 +5907,7 @@ Alterations to the default parameters are: noise_floor = 0.01 use_illumflat = False [[standardframe]] - exprng = None, 60 + exprng = None, 60, [[[process]]] mask_cr = True use_biasimage = False @@ -5580,11 +5919,11 @@ Alterations to the default parameters are: echelle = True ech_norder_coeff = 6 ech_sigrej = 3.0 - lamps = OH_NIRES + lamps = OH_NIRES, fwhm = 5.0 reid_arxiv = p200_triplespec.fits rms_threshold = 0.3 - n_final = 3, 4, 4, 4, 4 + n_final = 3, 4, 4, 4, 4, [[slitedges]] fit_min_spec_length = 0.3 left_right_pca = True @@ -5593,7 +5932,7 @@ Alterations to the default parameters are: [[tilts]] tracethresh = 10.0 [scienceframe] - exprng = 60, None + exprng = 60, None, [[process]] satpix = nothing mask_cr = True @@ -5629,7 +5968,7 @@ Alterations to the default parameters are: spectrograph = shane_kast_blue [calibrations] [[biasframe]] - exprng = None, 1 + exprng = None, 0.001, [[[process]]] combine = median use_biasimage = False @@ -5637,13 +5976,13 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_pixelflat = False use_illumflat = False [[arcframe]] - exprng = None, 61 + exprng = None, 61, [[[process]]] use_pixelflat = False use_illumflat = False @@ -5652,20 +5991,20 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pixelflatframe]] - exprng = 0, None + exprng = 0, None, [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[alignframe]] [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[traceframe]] - exprng = 0, None + exprng = 0, None, [[[process]]] use_pixelflat = False use_illumflat = False @@ -5684,13 +6023,13 @@ Alterations to the default parameters are: mask_cr = True noise_floor = 0.01 [[standardframe]] - exprng = 1, 61 + exprng = 1, 61, [[[process]]] mask_cr = True noise_floor = 0.01 [[wavelengths]] method = full_template - lamps = CdI, HgI, HeI + lamps = CdI, HgI, HeI, rms_threshold = 0.2 match_toler = 2.5 n_first = 3 @@ -5702,7 +6041,7 @@ Alterations to the default parameters are: spec_order = 5 maxdev2d = 0.02 [scienceframe] - exprng = 61, None + exprng = 61, None, [[process]] mask_cr = True noise_floor = 0.01 @@ -5722,7 +6061,7 @@ Alterations to the default parameters are: spectrograph = shane_kast_red [calibrations] [[biasframe]] - exprng = None, 1 + exprng = None, 0.001, [[[process]]] combine = median use_biasimage = False @@ -5730,13 +6069,13 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_pixelflat = False use_illumflat = False [[arcframe]] - exprng = None, 61 + exprng = None, 61, [[[process]]] use_pixelflat = False use_illumflat = False @@ -5745,20 +6084,20 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pixelflatframe]] - exprng = 0, None + exprng = 0, None, [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[alignframe]] [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[traceframe]] - exprng = 0, None + exprng = 0, None, [[[process]]] use_pixelflat = False use_illumflat = False @@ -5777,17 +6116,17 @@ Alterations to the default parameters are: mask_cr = True noise_floor = 0.01 [[standardframe]] - exprng = 1, 61 + exprng = 1, 61, [[[process]]] mask_cr = True noise_floor = 0.01 [[wavelengths]] - lamps = NeI, HgI, HeI, ArI + lamps = NeI, HgI, HeI, ArI, [[slitedges]] sync_predict = nearest bound_detector = True [scienceframe] - exprng = 61, None + exprng = 61, None, [[process]] mask_cr = True noise_floor = 0.01 @@ -5809,7 +6148,7 @@ Alterations to the default parameters are: spectrograph = shane_kast_red_ret [calibrations] [[biasframe]] - exprng = None, 1 + exprng = None, 0.001, [[[process]]] combine = median use_biasimage = False @@ -5817,13 +6156,13 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_pixelflat = False use_illumflat = False [[arcframe]] - exprng = None, 61 + exprng = None, 61, [[[process]]] use_pixelflat = False use_illumflat = False @@ -5832,20 +6171,20 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pixelflatframe]] - exprng = 0, None + exprng = 0, None, [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[alignframe]] [[[process]]] satpix = nothing use_pixelflat = False use_illumflat = False [[traceframe]] - exprng = 0, None + exprng = 0, None, [[[process]]] use_pixelflat = False use_illumflat = False @@ -5864,19 +6203,19 @@ Alterations to the default parameters are: mask_cr = True noise_floor = 0.01 [[standardframe]] - exprng = 1, 61 + exprng = 1, 61, [[[process]]] mask_cr = True noise_floor = 0.01 [[wavelengths]] - lamps = NeI, HgI, HeI, ArI + lamps = NeI, HgI, HeI, ArI, rms_threshold = 0.2 use_instr_flag = True [[slitedges]] sync_predict = nearest bound_detector = True [scienceframe] - exprng = 61, None + exprng = 61, None, [[process]] mask_cr = True noise_floor = 0.01 @@ -5908,7 +6247,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[arcframe]] - exprng = None, 30 + exprng = None, 30, [[[process]]] use_biasimage = False use_pixelflat = False @@ -5956,20 +6295,20 @@ Alterations to the default parameters are: use_biasimage = False noise_floor = 0.01 [[standardframe]] - exprng = None, 120 + exprng = None, 120, [[[process]]] mask_cr = True use_biasimage = False noise_floor = 0.01 [[wavelengths]] - lamps = NeI, ArI, HgI + lamps = NeI, ArI, HgI, fwhm = 5.0 rms_threshold = 0.5 [[slitedges]] sync_predict = nearest bound_detector = True [scienceframe] - exprng = 90, None + exprng = 90, None, [[process]] mask_cr = True use_biasimage = False @@ -6005,7 +6344,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[arcframe]] - exprng = None, 30 + exprng = None, 30, [[[process]]] use_biasimage = False use_pixelflat = False @@ -6053,7 +6392,7 @@ Alterations to the default parameters are: use_biasimage = False noise_floor = 0.01 [[standardframe]] - exprng = None, 120 + exprng = None, 120, [[[process]]] mask_cr = True use_biasimage = False @@ -6061,14 +6400,14 @@ Alterations to the default parameters are: [[flatfield]] slit_illum_finecorr = False [[wavelengths]] - lamps = NeI, ArI, HgI + lamps = NeI, ArI, HgI, fwhm = 5.0 rms_threshold = 0.5 [[slitedges]] sync_predict = nearest bound_detector = True [scienceframe] - exprng = 90, None + exprng = 90, None, [[process]] mask_cr = True use_biasimage = False @@ -6091,7 +6430,7 @@ Alterations to the default parameters are: spectrograph = tng_dolores [calibrations] [[biasframe]] - exprng = None, 0.1 + exprng = None, 0.001, [[[process]]] combine = median use_biasimage = False @@ -6099,7 +6438,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_pixelflat = False @@ -6122,7 +6461,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[alignframe]] [[[process]]] satpix = nothing @@ -6153,7 +6492,7 @@ Alterations to the default parameters are: [[slitedges]] sync_predict = nearest [scienceframe] - exprng = 1, None + exprng = 1, None, [[process]] mask_cr = True noise_floor = 0.01 @@ -6238,7 +6577,7 @@ Alterations to the default parameters are: [[flatfield]] tweak_slits_thresh = 0.9 [[wavelengths]] - lamps = HeI, ArI + lamps = HeI, ArI, sigdetect = 10.0 rms_threshold = 0.25 [[slitedges]] @@ -6278,7 +6617,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 20, None + exprng = 20, None, [[[process]]] mask_cr = True use_biasimage = False @@ -6286,7 +6625,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[arcframe]] - exprng = 20, None + exprng = 20, None, [[[process]]] mask_cr = True sigclip = 20.0 @@ -6348,7 +6687,7 @@ Alterations to the default parameters are: use_overscan = False noise_floor = 0.01 [[standardframe]] - exprng = None, 20 + exprng = None, 20, [[[process]]] mask_cr = True use_biasimage = False @@ -6356,7 +6695,7 @@ Alterations to the default parameters are: noise_floor = 0.01 [[wavelengths]] method = full_template - lamps = OH_FIRE_Echelle + lamps = OH_FIRE_Echelle, fwhm = 5.0 reid_arxiv = vlt_sinfoni_K.fits rms_threshold = 0.3 @@ -6368,7 +6707,7 @@ Alterations to the default parameters are: [[tilts]] tracethresh = 5.0 [scienceframe] - exprng = 20, None + exprng = 20, None, [[process]] satpix = nothing mask_cr = True @@ -6490,7 +6829,7 @@ Alterations to the default parameters are: ech_nspec_coeff = 5 ech_norder_coeff = 5 ech_sigrej = 3.0 - lamps = OH_XSHOOTER + lamps = OH_XSHOOTER, sigdetect = 10.0 fwhm = 5.0 reid_arxiv = vlt_xshooter_nir.fits @@ -6631,13 +6970,13 @@ Alterations to the default parameters are: method = reidentify echelle = True ech_sigrej = 3.0 - lamps = ThAr_XSHOOTER_UVB + lamps = ThAr_XSHOOTER_UVB, sigdetect = 3.0 reid_arxiv = vlt_xshooter_uvb1x1.fits cc_thresh = 0.5 cc_local_thresh = 0.5 rms_threshold = 0.6 - n_final = 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 + n_final = 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, [[slitedges]] edge_thresh = 8.0 max_shift_adj = 0.5 @@ -6651,7 +6990,7 @@ Alterations to the default parameters are: noise_floor = 0.01 [reduce] [[findobj]] - find_trim_edge = 3, 3 + find_trim_edge = 3, 3, maxnumber_sci = 2 maxnumber_std = 1 [[skysub]] @@ -6758,13 +7097,13 @@ Alterations to the default parameters are: method = reidentify echelle = True ech_sigrej = 3.0 - lamps = ThAr_XSHOOTER_VIS + lamps = ThAr_XSHOOTER_VIS, fwhm = 11.0 reid_arxiv = vlt_xshooter_vis1x1.fits cc_thresh = 0.5 cc_local_thresh = 0.5 rms_threshold = 0.5 - n_final = 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3 + n_final = 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, [[slitedges]] edge_thresh = 8.0 max_shift_adj = 0.5 @@ -6782,7 +7121,7 @@ Alterations to the default parameters are: noise_floor = 0.01 [reduce] [[findobj]] - find_trim_edge = 3, 3 + find_trim_edge = 3, 3, maxnumber_sci = 2 maxnumber_std = 1 [[skysub]] @@ -6792,7 +7131,7 @@ Alterations to the default parameters are: model_full_slit = True [sensfunc] algorithm = IR - polyorder = 9, 11, 11, 9, 9, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7 + polyorder = 9, 11, 11, 9, 9, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, [[IR]] telgridfile = TelFit_Paranal_VIS_4900_11100_R25000.fits @@ -6809,7 +7148,7 @@ Alterations to the default parameters are: [calibrations] bpm_usebias = True [[biasframe]] - exprng = None, 1 + exprng = None, 0.001, [[[process]]] combine = median use_biasimage = False @@ -6818,14 +7157,14 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_overscan = False use_pixelflat = False use_illumflat = False [[arcframe]] - exprng = None, 120 + exprng = None, 120, [[[process]]] use_overscan = False use_pixelflat = False @@ -6843,7 +7182,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] use_overscan = False [[alignframe]] @@ -6875,21 +7214,21 @@ Alterations to the default parameters are: use_overscan = False noise_floor = 0.01 [[standardframe]] - exprng = None, 120 + exprng = None, 120, [[[process]]] mask_cr = True use_overscan = False noise_floor = 0.01 [[wavelengths]] method = full_template - lamps = NeI, ArI, ArII, CuI + lamps = NeI, ArI, ArII, CuI, sigdetect = 10.0 n_first = 3 n_final = 5 [[slitedges]] sync_predict = nearest [scienceframe] - exprng = 90, None + exprng = 90, None, [[process]] mask_cr = True use_overscan = False @@ -6908,7 +7247,7 @@ Alterations to the default parameters are: [calibrations] bpm_usebias = True [[biasframe]] - exprng = None, 1 + exprng = None, 0.001, [[[process]]] combine = median use_biasimage = False @@ -6917,14 +7256,14 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[darkframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] mask_cr = True use_overscan = False use_pixelflat = False use_illumflat = False [[arcframe]] - exprng = None, 120 + exprng = None, 120, [[[process]]] use_overscan = False use_pixelflat = False @@ -6942,7 +7281,7 @@ Alterations to the default parameters are: use_pixelflat = False use_illumflat = False [[pinholeframe]] - exprng = 999999, None + exprng = 999999, None, [[[process]]] use_overscan = False [[alignframe]] @@ -6974,19 +7313,19 @@ Alterations to the default parameters are: use_overscan = False noise_floor = 0.01 [[standardframe]] - exprng = None, 120 + exprng = None, 120, [[[process]]] mask_cr = True use_overscan = False noise_floor = 0.01 [[wavelengths]] method = full_template - lamps = NeI, ArI, ArII, CuI + lamps = NeI, ArI, ArII, CuI, sigdetect = 10.0 [[slitedges]] sync_predict = nearest [scienceframe] - exprng = 90, None + exprng = 90, None, [[process]] mask_cr = True use_overscan = False diff --git a/doc/scripts/make_example_files.py b/doc/scripts/make_example_files.py index 015381c8e9..c39996e411 100644 --- a/doc/scripts/make_example_files.py +++ b/doc/scripts/make_example_files.py @@ -75,17 +75,18 @@ def make_example_gnirs_pypeit_files(version, date): oroot = Path(resource_filename('pypeit', '')).resolve().parent / 'doc' / 'include' # Create the default pypeit file - droot = Path(os.getenv('PYPEIT_DEV')).resolve() / 'RAW_DATA' / 'gemini_gnirs' / '32_SB_SXD' + droot = Path(os.getenv('PYPEIT_DEV')).resolve() / 'RAW_DATA' / 'gemini_gnirs_echelle' \ + / '32_SB_SXD' - pargs = setup.Setup.parse_args(['-r', str(droot), '-s', 'gemini_gnirs', '-b', '-c', 'A', - '-d', str(oroot), + pargs = setup.Setup.parse_args(['-r', str(droot), '-s', 'gemini_gnirs_echelle', '-b', + '-c', 'A', '-d', str(oroot), '--version_override', version, '--date_override', date]) setup.Setup.main(pargs) - ofile = oroot / 'gemini_gnirs_A.pypeit.rst' + ofile = oroot / 'gemini_gnirs_echelle_A.pypeit.rst' with open(ofile, 'w') as f: - with open(oroot / 'gemini_gnirs_A' / 'gemini_gnirs_A.pypeit', 'r') as p: + with open(oroot / 'gemini_gnirs_echelle_A' / 'gemini_gnirs_echelle_A.pypeit', 'r') as p: lines = p.readlines() f.write('.. code-block:: console\n') f.write('\n') @@ -93,13 +94,13 @@ def make_example_gnirs_pypeit_files(version, date): f.write(' '+l) f.write('\n\n') - shutil.rmtree(oroot / 'gemini_gnirs_A') + shutil.rmtree(oroot / 'gemini_gnirs_echelle_A') # Copy over the one that is actually used by the dev-suite dev = Path(os.getenv('PYPEIT_DEV')).resolve() \ - / 'pypeit_files' / 'gemini_gnirs_32_sb_sxd.pypeit' + / 'pypeit_files' / 'gemini_gnirs_echelle_32_sb_sxd.pypeit' - ofile = oroot / 'gemini_gnirs_A_corrected.pypeit.rst' + ofile = oroot / 'gemini_gnirs_echelle_A_corrected.pypeit.rst' with open(ofile, 'w') as f: with open(dev, 'r') as p: lines = p.readlines() diff --git a/pypeit/calibframe.py b/pypeit/calibframe.py index 1b6e869096..6c46b006fb 100644 --- a/pypeit/calibframe.py +++ b/pypeit/calibframe.py @@ -398,7 +398,6 @@ def construct_calib_key(setup, calib_id, detname): Returns: :obj:`str`: Calibration identifier. """ -# return f'{setup}_{"-".join(CalibFrame.ingest_calib_id(calib_id))}_{detname}' return f'{setup}_{CalibFrame.construct_calib_id(calib_id)}_{detname}' @staticmethod @@ -415,8 +414,6 @@ def parse_calib_key(calib_key): Returns: :obj:`tuple`: The three components of the calibration key. """ -# setup, calib_id, detname = calib_key.split('_') -# return setup, ','.join(calib_id.split('-')), detname setup, calib_id_name, detname = calib_key.split('_') return setup, CalibFrame.parse_calib_id(calib_id_name), detname diff --git a/pypeit/spectrographs/mdm_modspec.py b/pypeit/spectrographs/mdm_modspec.py index 0ef36386bd..c5a8f01186 100644 --- a/pypeit/spectrographs/mdm_modspec.py +++ b/pypeit/spectrographs/mdm_modspec.py @@ -50,19 +50,24 @@ def get_detector_par(self, det, hdu=None): # See Echelle at 2.4m f/7.5 scale : http://mdm.kpno.noirlab.edu/mdm-ccds.html gain = np.atleast_1d([1.3]) # Hardcoded in the header ronoise = np.atleast_1d([7.90]) # Hardcoded in the header - lenSpat = hdu[0].header['NAXIS1'] # length of spatial axis, including overscan. Horizontal axis of original .fits files - lenSpec = hdu[0].header['NAXIS2'] # length of spectral axis. Vertical axis of original .fits files - - datasec = np.atleast_1d([ - '[{0:d}:{1:d},{2:d}:{3:d}]'.format(1, lenSpec, 1, 300) - ]) - oscansec = np.atleast_1d([ - '[{0:d}:{1:d},{2:d}:{3:d}]'.format(1, lenSpec, 308, lenSpat) - ]) + # Allowing hdu=None is only needed for the automated documentation. + # TODO: See if there's a better way to automatically create the detector + # table for the docs. if hdu is None: + lenSpat = None + lenSpec = None + datasec = None + oscansec = None binning = '1,1' # Most common use mode else: + # length of spatial axis, including overscan. Horizontal axis of + # original .fits files + lenSpat = hdu[0].header['NAXIS1'] + # length of spectral axis. Vertical axis of original .fits files + lenSpec = hdu[0].header['NAXIS2'] + datasec = np.atleast_1d([f'[1:{lenSpec},1:3002]']) + oscansec = np.atleast_1d([f'[1:{lenSpec},308:{lenSpat}]']) binning = self.compound_meta(self.get_headarr(hdu), 'binning') if binning != '1,1': From 7a7eb4c16859bc65988bd61eab0d64dda1146c04 Mon Sep 17 00:00:00 2001 From: Kyle Westfall Date: Wed, 26 Jul 2023 10:23:01 -0700 Subject: [PATCH 29/41] doc fix --- doc/tutorials/gnirs_howto.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/tutorials/gnirs_howto.rst b/doc/tutorials/gnirs_howto.rst index 4a8e49d1a5..7f4cc376fa 100644 --- a/doc/tutorials/gnirs_howto.rst +++ b/doc/tutorials/gnirs_howto.rst @@ -24,14 +24,14 @@ To setup the pypeit file, first run :ref:`pypeit_setup`: .. code-block:: bash - pypeit_setup -r absolute_path -s gemini_gnirs -b -c A + pypeit_setup -r absolute_path -s gemini_gnirs_echelle -b -c A where ``-b`` indicates that the data uses background images and includes the ``calib``, ``comb_id``, ``bkg_id`` in the pypeit file. The resulting pypeit file looks like: -.. include:: ../include/gemini_gnirs_A.pypeit.rst +.. include:: ../include/gemini_gnirs_echelle_A.pypeit.rst Reliable image typing and sequence generation based on header cards is not yet implemented for GNIRS. Hence, several modifications to the PypeIt file need to be made before executing :ref:`run-pypeit`. @@ -93,7 +93,7 @@ entire ABBA sequence into set of calibration frames. For this reason, we set the The edited pypeit file (exactly the one to reduce our example Gemini/GNIRS data set in the `PypeIt Development Suite`_) is: -.. include:: ../include/gemini_gnirs_A_corrected.pypeit.rst +.. include:: ../include/gemini_gnirs_echelle_A_corrected.pypeit.rst Note that the telluric standard has its ``calib`` IDs set to all 0s, which corresponds to the ``calib`` ID of the nearest science ABBA sequence in time. From 33b95e4f2c34602cd935ba3f4ed61d8dcbc4df54 Mon Sep 17 00:00:00 2001 From: Kyle Westfall Date: Thu, 27 Jul 2023 09:27:32 -0700 Subject: [PATCH 30/41] bug fix --- pypeit/calibframe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pypeit/calibframe.py b/pypeit/calibframe.py index 6c46b006fb..a99d17e90f 100644 --- a/pypeit/calibframe.py +++ b/pypeit/calibframe.py @@ -415,7 +415,7 @@ def parse_calib_key(calib_key): :obj:`tuple`: The three components of the calibration key. """ setup, calib_id_name, detname = calib_key.split('_') - return setup, CalibFrame.parse_calib_id(calib_id_name), detname + return setup, ','.join(CalibFrame.parse_calib_id(calib_id_name)), detname @classmethod def construct_file_name(cls, calib_key, calib_dir=None): From ae4813b366ac6530b9539eb508f998cd221037c4 Mon Sep 17 00:00:00 2001 From: Kyle Westfall Date: Tue, 1 Aug 2023 08:27:25 -0700 Subject: [PATCH 31/41] change : to + for ranges --- CHANGES.rst | 4 ++-- pypeit/calibframe.py | 6 +++--- pypeit/tests/test_calibframe.py | 12 ++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index a21f6df6d7..7326dc96ce 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -24,8 +24,8 @@ - Fix wrong RA & Dec for 2D coadded serendips - Changed calibration frame naming as an attempt to avoid very long names for files with many calibration groups. Sequential numbers are reduced to a - range; e.g., ``'0-1-2-3-4'`` becomes ``'0:5'`` and - ``'3-5-6-10-11-12-15-18-19'`` becomes ``'3-5:7-10:13-15-18:20'`` + range; e.g., ``'0-1-2-3-4'`` becomes ``'0+5'`` and + ``'3-5-6-10-11-12-15-18-19'`` becomes ``'3-5+7-10+13-15-18+20'`` 1.13.0 (2 June 2023) -------------------- diff --git a/pypeit/calibframe.py b/pypeit/calibframe.py index a99d17e90f..49f5d0a6b3 100644 --- a/pypeit/calibframe.py +++ b/pypeit/calibframe.py @@ -337,12 +337,12 @@ def construct_calib_id(calib_id, ingested=False): indx = np.diff(calibs) != 1 if not np.any(indx): # The full list is sequential, so give the starting and ending points - return f'{calibs[0]}:{calibs[-1]+1}' + return f'{calibs[0]}+{calibs[-1]+1}' # Split the array into sequential subarrays (or single elements) and # combine them into a single string split_calibs = np.split(calibs, np.where(indx)[0]+1) - return '-'.join([f'{s[0]}:{s[-1]+1}' if len(s) > 1 else f'{s[0]}' for s in split_calibs]) + return '-'.join([f'{s[0]}+{s[-1]+1}' if len(s) > 1 else f'{s[0]}' for s in split_calibs]) @staticmethod def parse_calib_id(calib_id_name): @@ -365,7 +365,7 @@ def parse_calib_id(calib_id_name): # Parse the name into slices and enumerate them calib_id = [] for slc in calib_id_name.split('-'): - split_slc = slc.split(':') + split_slc = slc.split('+') calib_id += split_slc if len(split_slc) == 1 \ else np.arange(*np.array(split_slc).astype(int)).astype(str).tolist() return calib_id diff --git a/pypeit/tests/test_calibframe.py b/pypeit/tests/test_calibframe.py index b5b0407af8..def98bd437 100644 --- a/pypeit/tests/test_calibframe.py +++ b/pypeit/tests/test_calibframe.py @@ -58,7 +58,7 @@ def test_init(): calib.set_paths(odir, 'A', ['1','2'], 'DET01') ofile = Path(calib.get_path()).name - assert ofile == 'Minimal_A_1:3_DET01.fits', 'Wrong file name' + assert ofile == 'Minimal_A_1+3_DET01.fits', 'Wrong file name' def test_io(): @@ -99,7 +99,7 @@ def test_construct_calib_key(): key = CalibFrame.construct_calib_key('A', '1', 'DET01') assert key == 'A_1_DET01', 'Key changed' key = CalibFrame.construct_calib_key('A', ['1','2'], 'DET01') - assert key == 'A_1:3_DET01', 'Key changed' + assert key == 'A_1+3_DET01', 'Key changed' key = CalibFrame.construct_calib_key('A', 'all', 'DET01') assert key == 'A_all_DET01', 'Key changed' @@ -122,23 +122,23 @@ def test_construct_calib_id(): assert CalibFrame.construct_calib_id(['1']) == '1', \ 'Construction with one calib_id should just return it' calib_id = np.arange(10).tolist() - assert CalibFrame.construct_calib_id(calib_id) == '0:10', 'Bad simple construction' + assert CalibFrame.construct_calib_id(calib_id) == '0+10', 'Bad simple construction' # rng = np.random.default_rng(99) # calib_id = np.unique(rng.integers(20, size=15)).tolist() calib_id = [3, 5, 6, 10, 11, 12, 15, 18, 19] - assert CalibFrame.construct_calib_id(calib_id) == '3-5:7-10:13-15-18:20', \ + assert CalibFrame.construct_calib_id(calib_id) == '3-5+7-10+13-15-18+20', \ 'Bad complex construction' def test_parse_calib_id(): assert CalibFrame.parse_calib_id('all') == ['all'], 'Parsing should simply return all' assert CalibFrame.parse_calib_id('1') == ['1'], 'Parsing should simply return all' - assert np.array_equal(CalibFrame.parse_calib_id('0:10'), np.arange(10).astype(str).tolist()), \ + assert np.array_equal(CalibFrame.parse_calib_id('0+10'), np.arange(10).astype(str).tolist()), \ 'Bad simple construction' # rng = np.random.default_rng(99) # calib_id = np.unique(rng.integers(20, size=15)).tolist() calib_id = np.sort(np.array([3, 5, 6, 10, 11, 12, 15, 18, 19]).astype(str)) - assert np.array_equal(np.sort(CalibFrame.parse_calib_id('3-5:7-10:13-15-18:20')), calib_id), \ + assert np.array_equal(np.sort(CalibFrame.parse_calib_id('3-5+7-10+13-15-18+20')), calib_id), \ 'Bad complex construction' From c8aee53dc924a1e3553b259b26529d2117622e02 Mon Sep 17 00:00:00 2001 From: Kyle Westfall Date: Tue, 1 Aug 2023 11:43:30 -0700 Subject: [PATCH 32/41] kast howto update --- doc/cookbook.rst | 9 +- doc/figures/kastb_ginga_tilts.png | Bin 0 -> 106884 bytes doc/figures/kastb_sens.png | Bin 0 -> 121913 bytes doc/tutorials/kast_howto.rst | 135 ++++++++++++++++++++++-------- doc/tutorials/tutorials.rst | 12 +-- 5 files changed, 114 insertions(+), 42 deletions(-) create mode 100644 doc/figures/kastb_ginga_tilts.png create mode 100644 doc/figures/kastb_sens.png diff --git a/doc/cookbook.rst b/doc/cookbook.rst index ce8a1be670..f13f806696 100644 --- a/doc/cookbook.rst +++ b/doc/cookbook.rst @@ -13,6 +13,10 @@ should adhere to the following approach. Note that: + - Essentially all PypeIt reduction steps are done via executing command-line + scripts using a terminal window. We provide specific commands below and + throughout our documentation. + - This cookbook provides minimal detail, but serves as a basic introduction to the primary steps used to reduce your data with PypeIt. We guide you to other parts of our documentation that explain specific functionality in more @@ -27,8 +31,9 @@ Note that: - Specific advice on :doc:`spectrographs/spectrographs` is provided in their own doc page (although not every supported spectrograph has stand-alone documentation). - - Invariably something will be out of date. When you see an egregious - example, please holler on GitHub or Slack. + - Invariably something will be out of date in our doc pages. When you see an + egregious example, please holler on `GitHub + `__ or `Slack `__. Finally, note that before you keep going, you should have already done the following: diff --git a/doc/figures/kastb_ginga_tilts.png b/doc/figures/kastb_ginga_tilts.png new file mode 100644 index 0000000000000000000000000000000000000000..facf3e99ba07087099a44141d83e0aefe00eb421 GIT binary patch literal 106884 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 literal 0 HcmV?d00001 diff --git a/doc/figures/kastb_sens.png b/doc/figures/kastb_sens.png new file mode 100644 index 0000000000000000000000000000000000000000..a7cdb426751939b3105c96c5e8be00af5f0f0413 GIT binary patch literal 121913 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 z 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≶ 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 literal 0 HcmV?d00001 diff --git a/doc/tutorials/kast_howto.rst b/doc/tutorials/kast_howto.rst index f0e593b481..4456893eae 100644 --- a/doc/tutorials/kast_howto.rst +++ b/doc/tutorials/kast_howto.rst @@ -11,7 +11,7 @@ Overview ======== This doc goes through a full run of PypeIt on one of the Shane Kast *blue* -datasets in the `PypeIt Development Suite`_. +datasets in the :ref:`dev-suite`. Setup ===== @@ -48,10 +48,12 @@ Here is my call for these data: cd folder_for_reducing # this is usually *not* the raw data folder pypeit_setup -r ${RAW_PATH}/b -s shane_kast_blue -c A -This creates a :doc:`../pypeit_file` in the folder named -``shane_kast_blue_A`` beneath where the script was run. -Note that ``$RAW_PATH`` should be the *full* path, i.e. including a / -at the start. +This creates a :doc:`../pypeit_file` in the folder named ``shane_kast_blue_A`` +beneath where the script was run. Note that ``$RAW_PATH`` should be the *full* +path (i.e., as given in the example above). Note that I have selected a single +configuration (using the ``-c A``) option. There is only one instrument +configuration for this dataset, meaning using ``--c A`` and ``-c all`` are +equivalent; see :doc:`../setup`. The ``shane_kast_blue_A.pypeit`` files looks like this: @@ -60,7 +62,21 @@ The ``shane_kast_blue_A.pypeit`` files looks like this: For some instruments (especially Kast), it is common for frametypes to be incorrectly assigned owing to limited or erroneous headers. However, in this example, all of the frametypes were accurately assigned in the -:doc:`../pypeit_file`, so there are no edits to be made. +:doc:`../pypeit_file`. + +A couple of notes: + + #. One of the dome-flat images (``b2.fits.gz``) has an exposure time of 30s, + whereas the others are 15s. Inspection of the image shows that the flat + was saturated, so the observer decreased the exposure time. It's best to + comment out this line from the pypeit file (add a ``#`` character at the + beginning of the line) so that PypeIt will ignore the file. + + #. This is the rare case when the observation of a standard star is + correctly typed. Generally, it will be difficult for the automatic + frame-typing code to distinguish standard-star observations from science + targets, meaning that you'll need to edit the pypeit file directly to + designate standard-star observations as such. Main Run ======== @@ -71,10 +87,13 @@ simply: .. code-block:: bash cd shane_kast_blue_A - run_pypeit shane_kast_blue_A.pypeit -o + run_pypeit shane_kast_blue_A.pypeit -The ``-o`` indicates that any existing output files should be overwritten. As -there are none, it is superfluous but we recommend (almost) always using it. +If you find you need to re-run the code, you can use the ``-o`` option to ensure +the code overwrites any existing output files (excluding processed calibration +frames). If you find you need to re-build the calibrations, it's best to remove +the relevant (or all) files from the ``Calibrations/`` directory **instead** of +using the ``-m`` option. The :doc:`../running` doc describes the process in some more detail. @@ -100,12 +119,13 @@ with `ginga`_: .. code-block:: bash - ginga Calibrations/Bias_A_1_01.fits + ginga Calibrations/Bias_A_0_DET01.fits As typical of most bias images, it is featureless (effectively noise from the readout). -.. image:: ../figures/kastb_bias_image.png +.. figure:: ../figures/kastb_bias_image.png + :width: 40% See :doc:`../calibrations/bias` for further details. @@ -117,12 +137,13 @@ with `ginga`_: .. code-block:: bash - ginga Calibrations/Arc_A_1_01.fits + ginga Calibrations/Arc_A_0_DET01.fits As typical of most arc images, one sees a series of arc lines, here oriented horizontally (as always in PypeIt). -.. image:: ../figures/kastb_arc_image.png +.. figure:: ../figures/kastb_arc_image.png + :width: 30% See :doc:`../calibrations/arc` for further details. @@ -140,9 +161,10 @@ the :ref:`pypeit_chk_edges` script, with this explicit call: .. code-block:: bash - pypeit_chk_edges Calibrations/Edges_A_1_01.fits.gz + pypeit_chk_edges Calibrations/Edges_A_0_DET01.fits.gz -.. image:: ../figures/kastb_edges_image.png +.. figure:: ../figures/kastb_edges_image.png + :width: 40% The data is the combined flat images and the green/red lines indicate the left/right slit edges (green/magenta in more recent versions). The S174 label @@ -161,13 +183,15 @@ calibration. These are PNGs in the ``QA/PNG/`` folder. :: Here is an example of the 1D fits, written to -the ``QA/PNGs/Arc_1dfit_A_1_01_S0175.png`` file: +the ``QA/PNGs/Arc_1dfit_A_0_DET01_S0175.png`` file: -.. image:: ../figures/kastb_arc1d.png +.. figure:: ../figures/kastb_arc1d.png + :width: 90% What you hope to see in this QA is: - - On the left, many of the blue arc lines marked with green IDs + - On the left, many of the blue arc lines marked with *green* IDs + - That the green IDs span the full spectral range. - In the upper right, an RMS < 0.1 pixels - In the lower right, a random scatter about 0 residuals @@ -177,9 +201,10 @@ See :doc:`../calibrations/wvcalib` for further details. :: There are several QA files written for the 2D fits. -Here is ``QA/PNGs/Arc_tilts_2d_A_1_01_S0175.png``: +Here is ``QA/PNGs/Arc_tilts_2d_A_0_DET01_S0175.png``: -.. image:: ../figures/kastb_arc2d.png +.. figure:: ../figures/kastb_arc2d.png + :width: 50% Each horizontal line of circles traces the arc line centroid as a function of spatial position along the slit length. These data are used to fit the tilt in @@ -187,6 +212,21 @@ the spectral position. "Good" measurements included in the parametric trace are shown as black points; rejected points are shown in red. Provided most were not rejected, the fit should be good. An RMS<0.1 is also desired. +We also provide a script so that the arcline traces can be assessed against the +image using `ginga`_, similar to checking the slit edge tracing. + +.. code-block:: bash + + pypeit_chk_tilts Calibrations/Tilts_A_0_DET01.fits.gz + +.. figure:: ../figures/kastb_ginga_tilts.png + :width: 40% + + Main `ginga`_ window produced by ``pypeit_chk_tilts``. The arc image is + shown in gray scale, the slit edges are shown in green/magenta, masked pixels + are highlighted in red, good centroids are shown in blue, and centroids + rejected during the fit are shown in yellow. + See :doc:`../calibrations/wvcalib` for further details. Flatfield @@ -201,25 +241,29 @@ window (``pixflat_norm``) after using .. code-block:: bash - pypeit_chk_flats Calibrations/Flat_A_1_01.fits + pypeit_chk_flats Calibrations/Flat_A_0_DET01.fits -.. image:: ../figures/kastb_flat.png +.. figure:: ../figures/kastb_flat.png + :width: 40% One notes the pixel-to-pixel variations; these are at the percent level. The slit edges defined by the code are also plotted (green/red lines; green/magenta in more recent versions). The region of the detector beyond these images -has been set to unit value. +has been set to unity. See :doc:`../calibrations/flat` for further details. Spectra ------- -Eventually (be patient), the code will start -generating 2D and 1D spectra outputs. One per standard -and science frame, located in the ``Science/`` folder. +Eventually (be patient), the code will start generating 2D and 1D spectra +outputs. One per standard and science frame, located in the ``Science/`` +folder. + +For reference, full processing of this dataset on my circa 2020 Macbook +Pro took a little more than 2 minutes. Spec2D ++++++ @@ -230,9 +274,10 @@ window (``sky_resid-det01``) after using .. code-block:: bash - pypeit_show_2dspec Science/spec2d_b27-J1217p3905_KASTb_2015may20T045733.560.fits + pypeit_show_2dspec Science/spec2d_b27-J1217p3905_KASTb_20150520T045733.560.fits -.. image:: ../figures/kastb_spec2d.png +.. figure:: ../figures/kastb_spec2d.png + :width: 40% The green/red lines are the slit edges (green/magenta in more recent versions). The brighter pixels down the center of the slit is the object. The orange line @@ -250,26 +295,46 @@ Here is a screen shot from the GUI showing the .. code-block:: bash - pypeit_show_1dspec Science/spec1d_b27-J1217p3905_KASTb_2015may20T045733.560.fits + pypeit_show_1dspec Science/spec1d_b27-J1217p3905_KASTb_20150520T045733.560.fits -.. image:: ../figures/kastb_spec1d.png +.. figure:: ../figures/kastb_spec1d.png + :width: 70% -This uses the -`XSpecGUI `_ -from the `linetools`_ package. +This uses the `XSpecGUI +`_ from the +`linetools`_ package. With your mouse hovering over the window, type ``?`` to +open a webpage with the set of available commands used to interact with the +plot. The spectrum can also be ingested into a `specutils.Spectrum1D`_ object +using our :ref:`spec1D-specutils`. See :doc:`../out_spec1D` for further details. Fluxing ======= +This dataset includes observations of a spectrophotometric standard, Feige 66, +which is reduced alongside the science target observations. + Now that we have a reduced standard star spectrum, we can use that to generate a sensitivity file. Here is the -call for this example, which I run in the ``Science/`` folder: +call for this example: .. code-block:: bash - pypeit_sensfunc spec1d_b24-Feige66_KASTb_2015may20T041246.960.fits -o Kastb_feige66_sens.fits + pypeit_sensfunc Science/spec1d_b24-Feige66_KASTb_20150520T041246.960.fits -o Kastb_feige66_sens.fits + +This produces the sensitivity function (saved to ``Kastb_feige66_sens.fits``) +and three QA (pdf) plots. The main QA plot looks like this: + +.. figure:: ../figures/kastb_sens.png + :width: 60% + + QA plot from the sensitivity calculation. Black is the observed zeropoints, + red is the best-fit model, orange are points masked *before* fitting, blue + are points masked *during* fitting. + +The other two plots show the flux-calibrated standard-star spectrum against the +archived spectrum and the full system (top of atmosphere) throughput. See :doc:`../fluxing` for further details. diff --git a/doc/tutorials/tutorials.rst b/doc/tutorials/tutorials.rst index d706d328eb..585b52e6f4 100644 --- a/doc/tutorials/tutorials.rst +++ b/doc/tutorials/tutorials.rst @@ -5,14 +5,16 @@ Tutorials ========= +If you've landed here without first reading through the :ref:`cookbook`, you're +encouraged to start there and come back. + If this is your **first time using PypeIt**, you're encouraged to read through the :doc:`Shane Kast` tutorial as a general example of how to use -PypeIt; see also the :ref:`cookbook`. **You are also encouraged to pull example -data from the DevSuite for your instrument when learning how to use the -software**; see :ref:`dev-suite`. +PypeIt. **You are also encouraged to pull example data from the DevSuite for +your instrument when learning how to use the software**; see :ref:`dev-suite`. -For examples of reductions for different types of data (long-slit, echelle, -etc), we recommend the following starting points: +For examples of reductions for different types of data, we recommend the +following starting points: - **Long-slit data**: :doc:`Shane Kast` From 86331a4a8c4dd6f263a1905ff3c6e2d5998fa88d Mon Sep 17 00:00:00 2001 From: Kyle Westfall Date: Wed, 2 Aug 2023 16:25:26 -0700 Subject: [PATCH 33/41] doc update --- doc/pypeit_par.rst | 90 ---------------------------------------------- 1 file changed, 90 deletions(-) diff --git a/doc/pypeit_par.rst b/doc/pypeit_par.rst index 8e618a3f6a..62105a340a 100644 --- a/doc/pypeit_par.rst +++ b/doc/pypeit_par.rst @@ -4848,96 +4848,6 @@ HILTNER Echelle (``mdm_modspec``) --------------------------------- Alterations to the default parameters are: -.. code-block:: ini - - [rdx] - spectrograph = mdm_modspec - [calibrations] - [[biasframe]] - exprng = None, 0.001, - [[[process]]] - overscan_method = median - combine = median - use_biasimage = False - shot_noise = False - use_pixelflat = False - use_illumflat = False - [[darkframe]] - exprng = 999999, None, - [[[process]]] - mask_cr = True - use_pixelflat = False - use_illumflat = False - [[arcframe]] - [[[process]]] - clip = False - use_pixelflat = False - use_illumflat = False - subtract_continuum = True - [[tiltframe]] - [[[process]]] - clip = False - use_pixelflat = False - use_illumflat = False - subtract_continuum = True - [[pixelflatframe]] - [[[process]]] - satpix = nothing - n_lohi = 1, 1, - comb_sigrej = 3.0 - use_pixelflat = False - use_illumflat = False - [[pinholeframe]] - exprng = 999999, None, - [[alignframe]] - [[[process]]] - satpix = nothing - use_pixelflat = False - use_illumflat = False - [[traceframe]] - [[[process]]] - use_pixelflat = False - use_illumflat = False - [[illumflatframe]] - [[[process]]] - satpix = nothing - use_pixelflat = False - use_illumflat = False - [[lampoffflatsframe]] - [[[process]]] - satpix = nothing - use_pixelflat = False - use_illumflat = False - [[skyframe]] - [[[process]]] - mask_cr = True - noise_floor = 0.01 - [[standardframe]] - [[[process]]] - mask_cr = True - noise_floor = 0.01 - [[flatfield]] - slit_illum_finecorr = False - [[wavelengths]] - method = full_template - lamps = ArI, XeI, NeI, - reid_arxiv = mdm_modspec_1200_5100.fits - n_final = 9 - [[slitedges]] - sync_predict = nearest - bound_detector = True - [scienceframe] - exprng = 10, 600, - [[process]] - mask_cr = True - noise_floor = 0.01 - -.. _instr_par-mdm_modspec: - -HILTNER Echelle (``mdm_modspec``) ---------------------------------- -Alterations to the default parameters are: - .. code-block:: ini [rdx] From 100111a977c68347afdd977211c886a051a795df Mon Sep 17 00:00:00 2001 From: Kyle Westfall Date: Thu, 3 Aug 2023 07:57:07 -0700 Subject: [PATCH 34/41] inclusive range --- CHANGES.rst | 4 ++-- doc/calibrations/calibrations.rst | 17 ++++++++++++----- pypeit/calibframe.py | 6 +++--- pypeit/tests/test_calibframe.py | 12 ++++++------ 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 2b2c6717cf..58bbc42425 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -35,8 +35,8 @@ - Fix wrong RA & Dec for 2D coadded serendips - Changed calibration frame naming as an attempt to avoid very long names for files with many calibration groups. Sequential numbers are reduced to a - range; e.g., ``'0-1-2-3-4'`` becomes ``'0+5'`` and - ``'3-5-6-10-11-12-15-18-19'`` becomes ``'3-5+7-10+13-15-18+20'`` + range; e.g., ``'0-1-2-3-4'`` becomes ``'0+4'`` and + ``'3-5-6-10-11-12-15-18-19'`` becomes ``'3-5+6-10+12-15-18+19'`` 1.13.0 (2 June 2023) diff --git a/doc/calibrations/calibrations.rst b/doc/calibrations/calibrations.rst index bc7d175a08..aeb6cb39d6 100644 --- a/doc/calibrations/calibrations.rst +++ b/doc/calibrations/calibrations.rst @@ -175,15 +175,22 @@ All reduced calibration frames are named according to their primary calibration type (e.g., ``Arc``). They are also assigned a unique identifier that is a combination of: - - the instrument configuration (setup) identifier (e.g., ``A``), + #. the instrument configuration (setup) identifier (e.g., ``A``), - - the list of associated calibration groups (e.g., ``1-2`` or ``all``), and + #. a compressed list of associated calibration groups (e.g., ``1+2`` or ``all``), and - - the detector or mosaic identifier (e.g., ``DET01`` or ``MSC01``). + #. the detector or mosaic identifier (e.g., ``DET01`` or ``MSC01``). -.. note:: +For the second component, sequential numbers are reduced to a range; e.g., +``'0-1-2-3-4'`` becomes ``'0+4'`` and ``'3-5-6-10-11-12-15-18-19'`` becomes +``'3-5+6-10+12-15-18+19'``. + +.. warning:: If you have a lot of calibration groups in your pypeit file, you may end up - with very long file names! + with very long file names! This may cause a fault when the file name is + included in the header of the output fits files. If using the calibration + group ``all`` doesn't solve the problem or isn't possible given your + application, please `Submit an issue`_. diff --git a/pypeit/calibframe.py b/pypeit/calibframe.py index 6d065199ab..30826d8683 100644 --- a/pypeit/calibframe.py +++ b/pypeit/calibframe.py @@ -337,12 +337,12 @@ def construct_calib_id(calib_id, ingested=False): indx = np.diff(calibs) != 1 if not np.any(indx): # The full list is sequential, so give the starting and ending points - return f'{calibs[0]}+{calibs[-1]+1}' + return f'{calibs[0]}+{calibs[-1]}' # Split the array into sequential subarrays (or single elements) and # combine them into a single string split_calibs = np.split(calibs, np.where(indx)[0]+1) - return '-'.join([f'{s[0]}+{s[-1]+1}' if len(s) > 1 else f'{s[0]}' for s in split_calibs]) + return '-'.join([f'{s[0]}+{s[-1]}' if len(s) > 1 else f'{s[0]}' for s in split_calibs]) @staticmethod def parse_calib_id(calib_id_name): @@ -367,7 +367,7 @@ def parse_calib_id(calib_id_name): for slc in calib_id_name.split('-'): split_slc = slc.split('+') calib_id += split_slc if len(split_slc) == 1 \ - else np.arange(*np.array(split_slc).astype(int)).astype(str).tolist() + else np.arange(int(split_slc[0]), int(split_slc[1])+1).astype(str).tolist() return calib_id @staticmethod diff --git a/pypeit/tests/test_calibframe.py b/pypeit/tests/test_calibframe.py index def98bd437..ebfa57a646 100644 --- a/pypeit/tests/test_calibframe.py +++ b/pypeit/tests/test_calibframe.py @@ -58,7 +58,7 @@ def test_init(): calib.set_paths(odir, 'A', ['1','2'], 'DET01') ofile = Path(calib.get_path()).name - assert ofile == 'Minimal_A_1+3_DET01.fits', 'Wrong file name' + assert ofile == 'Minimal_A_1+2_DET01.fits', 'Wrong file name' def test_io(): @@ -99,7 +99,7 @@ def test_construct_calib_key(): key = CalibFrame.construct_calib_key('A', '1', 'DET01') assert key == 'A_1_DET01', 'Key changed' key = CalibFrame.construct_calib_key('A', ['1','2'], 'DET01') - assert key == 'A_1+3_DET01', 'Key changed' + assert key == 'A_1+2_DET01', 'Key changed' key = CalibFrame.construct_calib_key('A', 'all', 'DET01') assert key == 'A_all_DET01', 'Key changed' @@ -122,23 +122,23 @@ def test_construct_calib_id(): assert CalibFrame.construct_calib_id(['1']) == '1', \ 'Construction with one calib_id should just return it' calib_id = np.arange(10).tolist() - assert CalibFrame.construct_calib_id(calib_id) == '0+10', 'Bad simple construction' + assert CalibFrame.construct_calib_id(calib_id) == '0+9', 'Bad simple construction' # rng = np.random.default_rng(99) # calib_id = np.unique(rng.integers(20, size=15)).tolist() calib_id = [3, 5, 6, 10, 11, 12, 15, 18, 19] - assert CalibFrame.construct_calib_id(calib_id) == '3-5+7-10+13-15-18+20', \ + assert CalibFrame.construct_calib_id(calib_id) == '3-5+6-10+12-15-18+19', \ 'Bad complex construction' def test_parse_calib_id(): assert CalibFrame.parse_calib_id('all') == ['all'], 'Parsing should simply return all' assert CalibFrame.parse_calib_id('1') == ['1'], 'Parsing should simply return all' - assert np.array_equal(CalibFrame.parse_calib_id('0+10'), np.arange(10).astype(str).tolist()), \ + assert np.array_equal(CalibFrame.parse_calib_id('0+9'), np.arange(10).astype(str).tolist()), \ 'Bad simple construction' # rng = np.random.default_rng(99) # calib_id = np.unique(rng.integers(20, size=15)).tolist() calib_id = np.sort(np.array([3, 5, 6, 10, 11, 12, 15, 18, 19]).astype(str)) - assert np.array_equal(np.sort(CalibFrame.parse_calib_id('3-5+7-10+13-15-18+20')), calib_id), \ + assert np.array_equal(np.sort(CalibFrame.parse_calib_id('3-5+6-10+12-15-18+19')), calib_id), \ 'Bad complex construction' From 18b40a914a1b99e8dc91456fe08fe424dcc93702 Mon Sep 17 00:00:00 2001 From: deborape Date: Thu, 3 Aug 2023 13:38:37 -1000 Subject: [PATCH 35/41] fix it --- pypeit/core/wavecal/autoid.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pypeit/core/wavecal/autoid.py b/pypeit/core/wavecal/autoid.py index 887315cc30..b429708b36 100644 --- a/pypeit/core/wavecal/autoid.py +++ b/pypeit/core/wavecal/autoid.py @@ -1736,10 +1736,12 @@ def run_brute(self, min_nlines=10): self._det_weak = {} self._det_stro = {} for slit in range(self._nslit): - msgs.info("Working on slit: {}".format(slit)) if slit not in self._ok_mask: self._all_final_fit[str(slit)] = None + msgs.info('Ignoring masked slit {}'.format(slit)) continue + else: + msgs.info("Working on slit: {}".format(slit)) # TODO Pass in all the possible params for detect_lines to arc_lines_from_spec, and update the parset # Detect lines, and decide which tcent to use sigdetect = wvutils.parse_param(self._par, 'sigdetect', slit) @@ -1772,11 +1774,10 @@ def run_brute(self, min_nlines=10): # Now that all slits have been inspected, cross match to generate a # list of all lines in every slit, and refit all spectra - if self._nslit > 1: + obad_slits = self.cross_match(good_fit, self._det_weak) if self._ok_mask.size > 1 else np.array([]) + if obad_slits.size > 1: msgs.info('Checking wavelength solution by cross-correlating with all slits') - msgs.info('Cross-correlation iteration #1') - obad_slits = self.cross_match(good_fit, self._det_weak) cntr = 2 while obad_slits.size > 0: msgs.info('Cross-correlation iteration #{:d}'.format(cntr)) @@ -2034,7 +2035,7 @@ def cross_match(self, good_fit, detections): # to be classified as a bad slit here. Is this the behavior we want?? Maybe we should be more # conservative and call a bad any slit which results in an outlier here? good_slits = np.sort(sort_idx[np.unique(slit_ids[gdmsk, :].flatten())]) - bad_slits = np.setdiff1d(np.arange(self._nslit), good_slits, assume_unique=True) + bad_slits = np.setdiff1d(np.arange(self._nslit)[self._ok_mask], good_slits, assume_unique=True) nbad = bad_slits.size if nbad > 0: msgs.info('Working on {:d}'.format(nbad) + ' bad slits: {:}'.format(bad_slits + 1)) From 2e9f2ecabc7607e776351947ffee4dbc016b5b66 Mon Sep 17 00:00:00 2001 From: Kyle Westfall Date: Fri, 4 Aug 2023 06:47:11 -0700 Subject: [PATCH 36/41] clean up removal of b2.fits.gz --- doc/include/shane_kast_blue_A.pypeit.rst | 1 - doc/tutorials/kast_howto.rst | 28 ++++++++++-------------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/doc/include/shane_kast_blue_A.pypeit.rst b/doc/include/shane_kast_blue_A.pypeit.rst index 260bb5d36f..b206d00094 100644 --- a/doc/include/shane_kast_blue_A.pypeit.rst +++ b/doc/include/shane_kast_blue_A.pypeit.rst @@ -33,7 +33,6 @@ b11.fits.gz | pixelflat,illumflat,trace | 144.955 | 37.43222222222222 | Dome Flat | 600/4310 | 2.0 arcsec | 1,1 | 57162.07897476852 | 1.0 | 15.0 | d55 | 0 b12.fits.gz | pixelflat,illumflat,trace | 145.0908333333333 | 37.43222222222222 | Dome Flat | 600/4310 | 2.0 arcsec | 1,1 | 57162.079351388886 | 1.0 | 15.0 | d55 | 0 b13.fits.gz | pixelflat,illumflat,trace | 145.22791666666666 | 37.43222222222222 | Dome Flat | 600/4310 | 2.0 arcsec | 1,1 | 57162.079728240744 | 1.0 | 15.0 | d55 | 0 - b2.fits.gz | pixelflat,illumflat,trace | 143.36208333333335 | 37.43222222222222 | Dome Flat | 600/4310 | 2.0 arcsec | 1,1 | 57162.07473645834 | 1.0 | 30.0 | d55 | 0 b3.fits.gz | pixelflat,illumflat,trace | 143.86791666666667 | 37.43222222222222 | Dome Flat | 600/4310 | 2.0 arcsec | 1,1 | 57162.07596400463 | 1.0 | 15.0 | d55 | 0 b4.fits.gz | pixelflat,illumflat,trace | 144.00458333333333 | 37.43222222222222 | Dome Flat | 600/4310 | 2.0 arcsec | 1,1 | 57162.076341782406 | 1.0 | 15.0 | d55 | 0 b5.fits.gz | pixelflat,illumflat,trace | 144.14041666666665 | 37.43222222222222 | Dome Flat | 600/4310 | 2.0 arcsec | 1,1 | 57162.07671956019 | 1.0 | 15.0 | d55 | 0 diff --git a/doc/tutorials/kast_howto.rst b/doc/tutorials/kast_howto.rst index 4456893eae..1abdd1a58b 100644 --- a/doc/tutorials/kast_howto.rst +++ b/doc/tutorials/kast_howto.rst @@ -26,11 +26,11 @@ Place all of the files in a single folder. Mine is named .. code-block:: bash $ ls - b10.fits.gz b15.fits.gz b1.fits.gz b24.fits.gz b4.fits.gz b9.fits.gz - b11.fits.gz b16.fits.gz b20.fits.gz b27.fits.gz b5.fits.gz - b12.fits.gz b17.fits.gz b21.fits.gz b28.fits.gz b6.fits.gz - b13.fits.gz b18.fits.gz b22.fits.gz b2.fits.gz b7.fits.gz - b14.fits.gz b19.fits.gz b23.fits.gz b3.fits.gz b8.fits.gz + b1.fits.gz b14.fits.gz b19.fits.gz b24.fits.gz b5.fits.gz + b10.fits.gz b15.fits.gz b20.fits.gz b27.fits.gz b6.fits.gz + b11.fits.gz b16.fits.gz b21.fits.gz b28.fits.gz b7.fits.gz + b12.fits.gz b17.fits.gz b22.fits.gz b3.fits.gz b8.fits.gz + b13.fits.gz b18.fits.gz b23.fits.gz b4.fits.gz b9.fits.gz Run ``pypeit_setup`` -------------------- @@ -64,19 +64,13 @@ incorrectly assigned owing to limited or erroneous headers. However, in this example, all of the frametypes were accurately assigned in the :doc:`../pypeit_file`. -A couple of notes: +.. note:: - #. One of the dome-flat images (``b2.fits.gz``) has an exposure time of 30s, - whereas the others are 15s. Inspection of the image shows that the flat - was saturated, so the observer decreased the exposure time. It's best to - comment out this line from the pypeit file (add a ``#`` character at the - beginning of the line) so that PypeIt will ignore the file. - - #. This is the rare case when the observation of a standard star is - correctly typed. Generally, it will be difficult for the automatic - frame-typing code to distinguish standard-star observations from science - targets, meaning that you'll need to edit the pypeit file directly to - designate standard-star observations as such. + This is the rare case when the observation of a standard star is correctly + typed. Generally, it will be difficult for the automatic frame-typing code + to distinguish standard-star observations from science targets, meaning that + you'll need to edit the pypeit file directly to designate standard-star + observations as such. Main Run ======== From 74cd1a0b2c969968ca9ea65dc897489f6aa77aba Mon Sep 17 00:00:00 2001 From: profxj Date: Fri, 4 Aug 2023 10:18:10 -0700 Subject: [PATCH 37/41] edits for PR --- doc/calibrations/wave_calib.rst | 26 ++++- doc/spectrographs/xshooter.rst | 7 +- pypeit/core/wavecal/autoid.py | 23 +++- pypeit/core/wavecal/echelle.py | 1 - pypeit/par/pypeitpar.py | 7 +- pypeit/wavecalib.py | 199 +++++++++++++++++--------------- 6 files changed, 161 insertions(+), 102 deletions(-) diff --git a/doc/calibrations/wave_calib.rst b/doc/calibrations/wave_calib.rst index e12c02ac44..4bf0123713 100644 --- a/doc/calibrations/wave_calib.rst +++ b/doc/calibrations/wave_calib.rst @@ -273,14 +273,34 @@ In general, the approach is: 1. Identify the arc lines in each order 2. Fit the arc lines in each order to a polynomial, individually 3. Fit a 2D solution to the lines using the order number as a basis - 4. Reject orders where the RMS of the fit exceeds ``rms_threshold`` + 4. Reject orders where the RMS of the fit (measured in binned pixels) exceeds ``rms_threshold`` 5. Attempt to recover the missing orders using the 2D fit and a higher RMS threshold 6. Refit the 2D solution One should always inspect the outputs, especially the 2D solution -(global and orders). One may then modify the ``rms_threshold`` -and/or hand-fit a few of the orders to improve the solution. +(global and orders). One may then need to modify the ``rms_threshold`` +parameter and/or hand-fit a few of the orders to improve the solution. + +.. _wvcalib-rms-threshold: + +rms_threshold +------------- + +All of the echelle spectrographs have a default ``rms_threshold`` +matched to a default ``FWHM`` parameter (also measured in binned pixels). +The ``rms_threshold`` adopted in the analysis is one +scaled by the measured FWHM from the arc lines +(again, binned pixels) of the adopted calibration files + +That is, each order must satisfy the following: + +.. code-block:: ini + + RMS < rms_threshold * (measured_FWHM/default_FWHM) + +Mosaics +------- For echelle spectrographs with multiple detectors *per* camera that are mosaiced (e.g. Keck/HIRES), we fit the 2D solutions on a *per* detector diff --git a/doc/spectrographs/xshooter.rst b/doc/spectrographs/xshooter.rst index 51485d4808..6247303a59 100644 --- a/doc/spectrographs/xshooter.rst +++ b/doc/spectrographs/xshooter.rst @@ -28,7 +28,8 @@ in one or more ways. FWHM ---- -For the UVB or the VIS, you may turn off measuring the FWHM from the arc lines +For the UVB or the VIS, you may turn off measuring the FWHM (in units +of binned pixdels) from the arc lines by adding this to your :doc:`pypeit_file`: @@ -53,5 +54,7 @@ This may be done in the :doc:`pypeit_file` as well: [[wavelengths]] rms_threshold = 1.5 + Note that this is scaled by the ratio of the measured FWHM value -to the default value. +to the default value. See :ref:`_wvcalib-echelle` for +further details. diff --git a/pypeit/core/wavecal/autoid.py b/pypeit/core/wavecal/autoid.py index ac23615f6d..0cf7f41bcd 100644 --- a/pypeit/core/wavecal/autoid.py +++ b/pypeit/core/wavecal/autoid.py @@ -709,8 +709,8 @@ def match_to_arxiv(lamps:list, spec:np.ndarray, wv_guess:np.ndarray, Args: lamps (list): List of lamps used in the arc spec (np.ndarray): Spectrum to match - wv_guess (np.ndarray): Wavelength guess for the input spectrum - spec_arxiv (np.ndarray): Spectrum to match to + wv_guess (np.ndarray): Wavelength solution guess for the input arc spectrum + spec_arxiv (np.ndarray): Archival spectrum to match to wave_arxiv (np.ndarray): Wavelegnth solution for the archival spectrum nreid_min (int): Minimum number of times that a given candidate reidentified line must be properly matched with a line in the arxiv @@ -1653,6 +1653,25 @@ def __init__(self, spec, lamps, par, ech_fixed_format=False, ok_mask=None, meas def get_results(self): return copy.deepcopy(self.all_patt_dict), copy.deepcopy(self.wv_calib) + def get_arxiv(self, orders): + """ Grab the arxiv spectrum and wavelength solution for the provided orders + + Args: + orders (list, `numpy.ndarray`_): Orders to retrieve + + Returns: + tuple: wavelengths arrays, spec arrays aligned with orders + """ + # Collate + wave_soln_arxiv = [] + arcspec_arxiv = [] + for order in orders: + ind_sp = self.arxiv_orders.index(order) + wave_soln_arxiv.append(self.wave_soln_arxiv[:,ind_sp]) + arcspec_arxiv.append(self.spec_arxiv[:,ind_sp]) + + # Return + return np.stack(wave_soln_arxiv,axis=-1), np.stack(arcspec_arxiv,axis=-1) diff --git a/pypeit/core/wavecal/echelle.py b/pypeit/core/wavecal/echelle.py index 27451e3698..3880e3ef7c 100644 --- a/pypeit/core/wavecal/echelle.py +++ b/pypeit/core/wavecal/echelle.py @@ -230,7 +230,6 @@ def identify_ech_orders(arcspec, echangle, xdangle, dispname, msgs.info('Shift in spectral pixels between prediction and data: {:.3f}'.format(spec_shift)) # Assign - #order_vec = order_vec_guess[-1] - ordr_shift + np.arange(norders)[::-1] order_vec = order_vec_guess[0] + ordr_shift - np.arange(norders) ind = np.isin(order_vec_guess, order_vec, assume_unique=True) diff --git a/pypeit/par/pypeitpar.py b/pypeit/par/pypeitpar.py index a250bcfa09..6e8470900a 100644 --- a/pypeit/par/pypeitpar.py +++ b/pypeit/par/pypeitpar.py @@ -2696,10 +2696,9 @@ def __init__(self, reference=None, method=None, echelle=None, ech_nspec_coeff=No # These are the parameters used for the iterative fitting of the arc lines defaults['rms_threshold'] = 0.15 - dtypes['rms_threshold'] = [float, list, np.ndarray] - descr['rms_threshold'] = 'Minimum RMS for keeping a slit/order solution. This can be a ' \ - 'single number or a list/array providing the value for each order. ' \ - 'Only used if ``method`` for \'reidentify\' and echelle spectrographs' + dtypes['rms_threshold'] = float + descr['rms_threshold'] = 'Maximum RMS (in binned pixels) for keeping a slit/order solution. ' \ + 'Used for echelle spectrographs, the \'reidentify\' method, and when re-analyzing a slit with the redo_slits parameter' defaults['match_toler'] = 2.0 dtypes['match_toler'] = float diff --git a/pypeit/wavecalib.py b/pypeit/wavecalib.py index b172a14859..e734a8063f 100644 --- a/pypeit/wavecalib.py +++ b/pypeit/wavecalib.py @@ -646,16 +646,8 @@ def build_wv_calib(self, arccen, method, skip_QA=False, # Save arxiv for redo later? if self.par['echelle']: - # Collate - wave_soln_arxiv = [] - arcspec_arxiv = [] - for order in self.orders: - ind_sp = arcfitter.arxiv_orders.index(order) - wave_soln_arxiv.append(arcfitter.wave_soln_arxiv[:,ind_sp]) - arcspec_arxiv.append(arcfitter.spec_arxiv[:,ind_sp]) - # Save - self.wave_soln_arxiv = np.stack(wave_soln_arxiv,axis=-1) - self.arcspec_arxiv = np.stack(arcspec_arxiv,axis=-1) + # Save for later usage + self.wave_soln_arxiv, self.arcspec_arxiv = arcfitter.get_arxiv(self.orders) self.arccen = arccen elif method == 'full_template': # Now preferred @@ -703,17 +695,19 @@ def build_wv_calib(self, arccen, method, skip_QA=False, # Build the DataContainer if self.par['redo_slits'] is not None: + # If we are only redoing slits, we start from the + # previous wv_calib and update only the (good) redone slits self.wv_calib = prev_wvcalib # Update/reset items self.wv_calib.arc_spectra = arccen - # Save? + # Save the new fits (if they meet tolerance) for key in final_fit.keys(): if final_fit[key]['rms'] < self.par['rms_threshold']: idx = int(key) self.wv_calib.wv_fits[idx] = final_fit[key] self.wv_calib.wv_fits[idx].spat_id = self.slits.spat_id[idx] self.wv_calib.wv_fits[idx].fwhm = self.measured_fwhms[idx] - else: + else: # Generate the DataContainer from scratch # Loop on WaveFit items tmp = [] for idx in range(self.slits.nslits): @@ -770,6 +764,97 @@ def build_wv_calib(self, arccen, method, skip_QA=False, self.steps.append(inspect.stack()[0][3]) return self.wv_calib + def redo_echelle_orders(self, bad_orders:np.ndarray, dets:np.ndarray, order_dets:np.ndarray): + """ Attempt to redo the wavelength calibration for a set + of bad echelle orders + + Args: + bad_orders (np.ndarray): Array of bad order numbers + dets (np.ndarray): detectors of the spectrograph + Multiple numbers for mosaic (typically) + order_dets (np.ndarray): + Orders on the each detector + + Returns: + bool: True if any of the echelle orders were + successfully redone + """ + + # Make this outside the for loop.. + #bad_orders = self.slits.ech_order[np.where(bad_rms)[0]] + fixed = False + + for idet in range(len(dets)): + in_det = np.in1d(bad_orders, order_dets[idet]) + if not np.any(in_det): + continue + # Are there few enough? + # TODO -- make max_bad a parameter + max_bad = len(order_dets[idet])//10 + 1 + if np.sum(in_det) > max_bad: + msgs.warn(f"Too many bad orders in detector={dets[idet]} to attempt a refit.") + continue + # Loop + for order in bad_orders[in_det]: + iord = np.where(self.slits.ech_order == order)[0][0] + # Predict the wavelengths + nspec = self.arccen.shape[0] + spec_vec_norm = np.linspace(0,1,nspec) + wv_order_mod = self.wv_calib.wv_fit2d[idet].eval(spec_vec_norm, + x2=np.ones_like(spec_vec_norm)*order)/order + + # Link me + tcent, spec_cont_sub, patt_dict_slit, tot_llist = autoid.match_to_arxiv( + self.lamps, self.arccen[:,iord], wv_order_mod, + self.arcspec_arxiv[:, iord], self.wave_soln_arxiv[:,iord], + self.par['nreid_min'], + match_toler=self.par['match_toler'], + nonlinear_counts=self.nonlinear_counts, + sigdetect=wvutils.parse_param(self.par, 'sigdetect', iord), + fwhm=self.par['fwhm']) + + if not patt_dict_slit['acceptable']: + msgs.warn(f"Order {order} is still not acceptable after attempt to reidentify.") + continue + + # Fit me -- RMS may be too high again + n_final = wvutils.parse_param(self.par, 'n_final', iord) + # TODO - Make this cheaper + final_fit = wv_fitting.fit_slit( + spec_cont_sub, patt_dict_slit, tcent, tot_llist, + match_toler=self.par['match_toler'], + func=self.par['func'], + n_first=self.par['n_first'], + #n_first=3, + sigrej_first=self.par['sigrej_first'], + n_final=n_final, + sigrej_final=2.) + msgs.info(f"New RMS for redo of order={order}: {final_fit['rms']}") + + # Keep? + # TODO -- Make this a parameter? + increase_rms = 1.5 + if final_fit['rms'] < increase_rms*self.par['rms_threshold']* np.median(self.measured_fwhms)/self.par['fwhm']: + # TODO -- This is repeated from lines 718-725 + # QA + outfile = qa.set_qa_filename( + self.wv_calib.calib_key, 'arc_fit_qa', + slit=order, + out_dir=self.qa_path) + autoid.arc_fit_qa(final_fit, + title=f'Arc Fit QA for slit/order: {order}', + outfile=outfile) + # This is for I/O naming + final_fit.spat_id = self.slits.spat_id[iord] + final_fit.fwhm = self.measured_fwhms[iord] + # Save the wavelength solution fits + self.wv_calib.wv_fits[iord] = final_fit + self.wvc_bpm[iord] = False + fixed = True + # + return fixed + + def echelle_2dfit(self, wv_calib, debug=False, skip_QA=False): """ Fit a two-dimensional wavelength solution for echelle data. @@ -957,19 +1042,22 @@ def update_wvmask(self): def run(self, skip_QA=False, debug=False, prev_wvcalib=None): """ - Main driver for wavelength calibration + Main method for wavelength calibration Code flow: 1. Extract 1D arc spectra down the center of each unmasked slit/order - 2. Load the parameters guiding wavelength calibration - 3. Generate the 1D wavelength fits - 4. Generate a mask + 2. Generate the 1D wavelength fits + 3. If echelle, perform 2D fit(s). + 4. Deal with masking + 5. Return a WaveCalib object Args: skip_QA : bool, optional + prev_wvcalib (WaveCalib, optional): + Previous wavelength calibration object (from disk, typically) Returns: - dict: wv_calib dict + WaveCalib: wavelength calibration object """ ############### @@ -1007,80 +1095,11 @@ def run(self, skip_QA=False, debug=False, # Try a second attempt with 1D, if needed if np.any(bad_rms): - # Make this outside the for loop.. bad_orders = self.slits.ech_order[np.where(bad_rms)[0]] - fixed = False - for idet in range(len(dets)): - in_det = np.in1d(bad_orders, order_dets[idet]) - if not np.any(in_det): - continue - # Are there few enough? - # TODO -- make the scale a parameter - max_bad = len(order_dets[idet])//10 + 1 - if np.sum(in_det) > max_bad: - msgs.warn(f"Too many bad orders in detector={dets[idet]} to attempt a refit.") - continue - # Loop - for order in bad_orders[in_det]: - iord = np.where(self.slits.ech_order == order)[0][0] - # Predict the wavelengths - nspec = self.arccen.shape[0] - spec_vec_norm = np.linspace(0,1,nspec) - wv_order_mod = fit2ds[idet].eval(spec_vec_norm, - x2=np.ones_like(spec_vec_norm)*order)/order - - # Link me - tcent, spec_cont_sub, patt_dict_slit, tot_llist = autoid.match_to_arxiv( - self.lamps, self.arccen[:,iord], wv_order_mod, - self.arcspec_arxiv[:, iord], self.wave_soln_arxiv[:,iord], - self.par['nreid_min'], - match_toler=self.par['match_toler'], - nonlinear_counts=self.nonlinear_counts, - sigdetect=wvutils.parse_param(self.par, 'sigdetect', iord), - fwhm=self.par['fwhm']) - - if not patt_dict_slit['acceptable']: - msgs.warn(f"Order {order} is still not acceptable after attempt to reidentify.") - continue - - # Fit me -- RMS may be too high again - n_final = wvutils.parse_param(self.par, 'n_final', iord) - # TODO - Make this cheaper - final_fit = wv_fitting.fit_slit( - spec_cont_sub, patt_dict_slit, tcent, tot_llist, - match_toler=self.par['match_toler'], - func=self.par['func'], - n_first=self.par['n_first'], - #n_first=3, - sigrej_first=self.par['sigrej_first'], - n_final=n_final, - sigrej_final=2.) - msgs.info(f"New RMS for redo of order={order}: {final_fit['rms']}") - - # Keep? - # TODO -- Make this a parameter? - increase_rms = 1.5 - if final_fit['rms'] < increase_rms*self.par['rms_threshold']: - # TODO -- This is repeated from lines 718-725 - # QA - outfile = qa.set_qa_filename( - self.wv_calib.calib_key, 'arc_fit_qa', - slit=order, - out_dir=self.qa_path) - autoid.arc_fit_qa(final_fit, - title=f'Arc Fit QA for slit/order: {order}', - outfile=outfile) - # This is for I/O naming - final_fit.spat_id = self.slits.spat_id[iord] - final_fit.fwhm = self.measured_fwhms[iord] - # Save the wavelength solution fits - self.wv_calib.wv_fits[iord] = final_fit - self.wvc_bpm[iord] = False - fixed = True - - - # Do another full 2D - if fixed: + any_fixed = self.redo_echelle_orders(bad_orders, dets, order_dets) + + # Do another full 2D? + if any_fixed: fit2ds, _, _ = self.echelle_2dfit(self.wv_calib, skip_QA = skip_QA, debug=debug) # Save self.wv_calib.wv_fit2d = np.array(fit2ds) From f8a0ff84381588566412d11f42f295b32ea6f612 Mon Sep 17 00:00:00 2001 From: profxj Date: Sun, 6 Aug 2023 07:36:49 -0700 Subject: [PATCH 38/41] RC comments --- doc/spectrographs/keck_hires.rst | 6 +++--- pypeit/core/wavecal/autoid.py | 12 +----------- pypeit/core/wavecal/patterns.py | 8 ++++++-- pypeit/core/wavecal/waveio.py | 4 ++-- pypeit/par/pypeitpar.py | 2 +- pypeit/wavecalib.py | 15 ++++++--------- 6 files changed, 19 insertions(+), 28 deletions(-) diff --git a/doc/spectrographs/keck_hires.rst b/doc/spectrographs/keck_hires.rst index 210b93e29c..f8d3277554 100644 --- a/doc/spectrographs/keck_hires.rst +++ b/doc/spectrographs/keck_hires.rst @@ -13,6 +13,6 @@ Wavelengths See :ref:`wvcalib-echelle` for details on the wavelength calibration. -We also note that Order 45 is frequently flagged -as bad in the wavelength solution. This is due to, in part, -to very bright ThAr line contamination. \ No newline at end of file +We also note that several Orders from 40-45 are +frequently flagged as bad in the wavelength solution. +This is due, in part, to very bright ThAr line contamination. \ No newline at end of file diff --git a/pypeit/core/wavecal/autoid.py b/pypeit/core/wavecal/autoid.py index 0cf7f41bcd..298ce3cbf5 100644 --- a/pypeit/core/wavecal/autoid.py +++ b/pypeit/core/wavecal/autoid.py @@ -1533,17 +1533,6 @@ def __init__(self, spec, lamps, par, ech_fixed_format=False, ok_mask=None, meas self.tot_line_list, self.line_lists, self.unknwns = waveio.load_line_lists( lamps, include_unknown=self.use_unknowns) - #if 'ThAr' in self.lamps: - # line_lists_all = waveio.load_line_lists(self.lamps) - # self.line_lists = line_lists_all[np.where(line_lists_all['ion'] != 'UNKNWN')] - # self.unknwns = line_lists_all[np.where(line_lists_all['ion'] == 'UNKNWN')] - #else: - # self.line_lists = waveio.load_line_lists(self.lamps) - # self.unknwns = waveio.load_unknown_list(self.lamps) - - #self.tot_line_list = astropy.table.vstack([self.line_lists, self.unknwns]) if self.use_unknowns \ - # else self.line_lists - # Read in the wv_calib_arxiv and pull out some relevant quantities # ToDO deal with different binnings! self.wv_calib_arxiv, self.par_arxiv = waveio.load_reid_arxiv(self.reid_arxiv) @@ -1693,6 +1682,7 @@ class HolyGrail: Array of good slits islinelist : bool, optional Is lamps a linelist (True), or a list of ions (False) + The former is not recommended except by expert users/developers outroot : str, optional Name of output file debug : bool, optional diff --git a/pypeit/core/wavecal/patterns.py b/pypeit/core/wavecal/patterns.py index d25f26ba8b..1ea0d39287 100644 --- a/pypeit/core/wavecal/patterns.py +++ b/pypeit/core/wavecal/patterns.py @@ -638,6 +638,10 @@ def solve_xcorr(detlines, linelist, dindex, lindex, line_cc, local cross correlation coefficient computed for each line cc_local_thresh : float, default = 0.8, optional Threshold to satisy for local cross-correlation + nreid_min: int, default = 4, optional + Minimum number of matches + to receive a score of 'Perfect' or 'Very Good' + Passed to score_xcorr() Returns ------- @@ -714,8 +718,8 @@ def score_xcorr(counts, cc_avg, nreid_min = 4, cc_local_thresh = -1.0): counts. The more different wavelengths that are attributed to the same detected line (i.e. not ideal) the longer the counts list will be. - nmin_match: int, default = 4, optional - Minimum number of slits/solutions that have to have been matched + nreid_min: int, default = 4, optional + Minimum number of matches to receive a score of 'Perfect' or 'Very Good' cc_local_thresh: float, default = -1.0, optional What does this do?? diff --git a/pypeit/core/wavecal/waveio.py b/pypeit/core/wavecal/waveio.py index b37faf628f..f105799800 100644 --- a/pypeit/core/wavecal/waveio.py +++ b/pypeit/core/wavecal/waveio.py @@ -215,8 +215,8 @@ def load_line_lists(lamps, all=False, include_unknown:bool=False, restrict_on_in # Load Unknowns if 'ThAr' in lamps: - line_lists = line_lists_all[np.where(line_lists_all['ion'] != 'UNKNWN')] - unkn_lines = line_lists_all[np.where(line_lists_all['ion'] == 'UNKNWN')] + line_lists = line_lists_all[line_lists_all['ion'] != 'UNKNWN'] + unkn_lines = line_lists_all[line_lists_all['ion'] == 'UNKNWN'] else: line_lists = line_lists_all unkn_lines = load_unknown_list(lamps) diff --git a/pypeit/par/pypeitpar.py b/pypeit/par/pypeitpar.py index 6e8470900a..010079704b 100644 --- a/pypeit/par/pypeitpar.py +++ b/pypeit/par/pypeitpar.py @@ -2754,7 +2754,7 @@ def __init__(self, reference=None, method=None, echelle=None, ech_nspec_coeff=No 'Options are: {0}'.format(', '.join(options['refframe'])) dtypes['redo_slits'] = [int, list] - descr['redo_slits'] = 'Redo the input slit(s) [multslit] or order(s) [echelle]' + descr['redo_slits'] = 'Redo the input slit(s) [multislit] or order(s) [echelle]' defaults['qa_log'] = True dtypes['qa_log'] = bool diff --git a/pypeit/wavecalib.py b/pypeit/wavecalib.py index e734a8063f..3b64ebbefe 100644 --- a/pypeit/wavecalib.py +++ b/pypeit/wavecalib.py @@ -644,9 +644,9 @@ def build_wv_calib(self, arccen, method, skip_QA=False, nonlinear_counts=self.nonlinear_counts) patt_dict, final_fit = arcfitter.get_results() - # Save arxiv for redo later? + # Grab arxiv for redo later? if self.par['echelle']: - # Save for later usage + # Hold for later usage self.wave_soln_arxiv, self.arcspec_arxiv = arcfitter.get_arxiv(self.orders) self.arccen = arccen elif method == 'full_template': @@ -772,8 +772,7 @@ def redo_echelle_orders(self, bad_orders:np.ndarray, dets:np.ndarray, order_dets bad_orders (np.ndarray): Array of bad order numbers dets (np.ndarray): detectors of the spectrograph Multiple numbers for mosaic (typically) - order_dets (np.ndarray): - Orders on the each detector + order_dets (np.ndarray): Orders on the each detector Returns: bool: True if any of the echelle orders were @@ -835,7 +834,8 @@ def redo_echelle_orders(self, bad_orders:np.ndarray, dets:np.ndarray, order_dets # TODO -- Make this a parameter? increase_rms = 1.5 if final_fit['rms'] < increase_rms*self.par['rms_threshold']* np.median(self.measured_fwhms)/self.par['fwhm']: - # TODO -- This is repeated from lines 718-725 + # TODO -- This is repeated from build_wv_calib() + # Would be nice to consolidate # QA outfile = qa.set_qa_filename( self.wv_calib.calib_key, 'arc_fit_qa', @@ -1052,7 +1052,7 @@ def run(self, skip_QA=False, debug=False, 5. Return a WaveCalib object Args: - skip_QA : bool, optional + skip_QA (bool, optional): Skip QA? prev_wvcalib (WaveCalib, optional): Previous wavelength calibration object (from disk, typically) @@ -1065,9 +1065,6 @@ def run(self, skip_QA=False, debug=False, self.arccen, self.wvc_bpm = self.extract_arcs() # Fill up the calibrations and generate QA - #skip_QA = True # for debugging - #msgs.warn("TURN QA BACK ON!!!") - self.wv_calib = self.build_wv_calib( self.arccen, self.par['method'], skip_QA=skip_QA, From 826a5040ed504b9924dd8ea2df817f72338040de Mon Sep 17 00:00:00 2001 From: Debora Pelliccia Date: Tue, 8 Aug 2023 15:24:17 -1000 Subject: [PATCH 39/41] more fixes --- pypeit/core/arc.py | 4 ++-- pypeit/core/wavecal/autoid.py | 21 +++++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/pypeit/core/arc.py b/pypeit/core/arc.py index aceec8e935..2404fa5d07 100644 --- a/pypeit/core/arc.py +++ b/pypeit/core/arc.py @@ -485,12 +485,12 @@ def get_censpec(slit_cen, slitmask, arcimg, gpm=None, box_rad=3.0, continue # Check if this slit is masked if slit_bpm is not None and slit_bpm[islit]: - msgs.info('Ignoring masked slit {}'.format(islit)) + msgs.info('Ignoring masked slit {}'.format(islit+1)) # TODO -- Avoid using NaNs arc_spec[:,islit] = np.nan continue if verbose: - msgs.info('Extracting approximate arc spectrum along the center of slit {0}'.format(islit)) + msgs.info('Extracting approximate arc spectrum along the center of slit {0}'.format(islit+1)) # Create a mask for the pixels that will contribue to the arc arcmask = _gpm & (np.absolute(spat[None,:] - slit_cen[:,islit,None]) < box_rad) # Trimming the image makes this much faster diff --git a/pypeit/core/wavecal/autoid.py b/pypeit/core/wavecal/autoid.py index b429708b36..2ac2f41995 100644 --- a/pypeit/core/wavecal/autoid.py +++ b/pypeit/core/wavecal/autoid.py @@ -1738,10 +1738,10 @@ def run_brute(self, min_nlines=10): for slit in range(self._nslit): if slit not in self._ok_mask: self._all_final_fit[str(slit)] = None - msgs.info('Ignoring masked slit {}'.format(slit)) + msgs.info('Ignoring masked slit {}'.format(slit+1)) continue else: - msgs.info("Working on slit: {}".format(slit)) + msgs.info("Working on slit: {}".format(slit+1)) # TODO Pass in all the possible params for detect_lines to arc_lines_from_spec, and update the parset # Detect lines, and decide which tcent to use sigdetect = wvutils.parse_param(self._par, 'sigdetect', slit) @@ -1753,7 +1753,7 @@ def run_brute(self, min_nlines=10): # Were there enough lines? This mainly deals with junk slits if self._all_tcent.size < min_nlines: - msgs.warn("Not enough lines to identify in slit {0:d}!".format(slit)) + msgs.warn("Not enough lines to identify in slit {0:d}!".format(slit+1)) self._det_weak[str(slit)] = [None,None] self._det_stro[str(slit)] = [None,None] # Remove from ok mask @@ -1772,12 +1772,14 @@ def run_brute(self, min_nlines=10): # Print preliminary report good_fit[slit] = self.report_prelim(slit, best_patt_dict, best_final_fit) - # Now that all slits have been inspected, cross match to generate a + # Now that all slits have been inspected, cross match (if there are bad fit) to generate a # list of all lines in every slit, and refit all spectra - obad_slits = self.cross_match(good_fit, self._det_weak) if self._ok_mask.size > 1 else np.array([]) - if obad_slits.size > 1: + # in self.cross_match() good fits are cross correlate with each other, so we need to have at least 2 good fits + if np.where(good_fit[self._ok_mask])[0].size > 1 and np.any(np.logical_not(good_fit[self._ok_mask])): msgs.info('Checking wavelength solution by cross-correlating with all slits') + msgs.info('Cross-correlation iteration #1') + obad_slits = self.cross_match(good_fit, self._det_weak) cntr = 2 while obad_slits.size > 0: msgs.info('Cross-correlation iteration #{:d}'.format(cntr)) @@ -2859,13 +2861,12 @@ def report_final(self): for slit in range(self._nslit): # Prepare a message for bad wavelength solutions badmsg = '---------------------------------------------------' + msgs.newline() +\ - 'Final report for slit {0:d}/{1:d}:'.format(slit+1, self._nslit) + msgs.newline() +\ - ' Wavelength calibration not performed!' + 'Final report for slit {0:d}/{1:d}:'.format(slit+1, self._nslit) + msgs.newline() if slit not in self._ok_mask: - msgs.warn(badmsg) + msgs.warn(badmsg + 'Masked slit ignored') continue if self._all_patt_dict[str(slit)] is None: - msgs.warn(badmsg) + msgs.warn(badmsg + ' Wavelength calibration not performed!') continue st = str(slit) if self._all_patt_dict[st]['sign'] == +1: From ea55d546afff7cc0a35409c2b17c13b8e21a6839 Mon Sep 17 00:00:00 2001 From: profxj Date: Fri, 11 Aug 2023 06:22:13 -0700 Subject: [PATCH 40/41] docs --- doc/calibrations/wave_calib.rst | 3 +++ pypeit/par/pypeitpar.py | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/calibrations/wave_calib.rst b/doc/calibrations/wave_calib.rst index 4bf0123713..cd8e62e96e 100644 --- a/doc/calibrations/wave_calib.rst +++ b/doc/calibrations/wave_calib.rst @@ -299,6 +299,9 @@ That is, each order must satisfy the following: RMS < rms_threshold * (measured_FWHM/default_FWHM) +Note: in a future release, we will re-define ``rms_threshold`` to be +in units of the measured FWHM. + Mosaics ------- diff --git a/pypeit/par/pypeitpar.py b/pypeit/par/pypeitpar.py index 010079704b..fa6339c4d9 100644 --- a/pypeit/par/pypeitpar.py +++ b/pypeit/par/pypeitpar.py @@ -2698,7 +2698,9 @@ def __init__(self, reference=None, method=None, echelle=None, ech_nspec_coeff=No defaults['rms_threshold'] = 0.15 dtypes['rms_threshold'] = float descr['rms_threshold'] = 'Maximum RMS (in binned pixels) for keeping a slit/order solution. ' \ - 'Used for echelle spectrographs, the \'reidentify\' method, and when re-analyzing a slit with the redo_slits parameter' + 'Used for echelle spectrographs, the \'reidentify\' method, and when re-analyzing a slit with the redo_slits parameter.' \ + 'In a future PR, we will refactor the code to always scale this threshold off the measured FWHM of the arc lines.' + defaults['match_toler'] = 2.0 dtypes['match_toler'] = float From 3198a88b9bf88206fdcdb824e787ab61923e4bfb Mon Sep 17 00:00:00 2001 From: profxj Date: Fri, 11 Aug 2023 06:48:26 -0700 Subject: [PATCH 41/41] load_object --- pypeit/io.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/pypeit/io.py b/pypeit/io.py index 2c4ddbae25..15e7380269 100644 --- a/pypeit/io.py +++ b/pypeit/io.py @@ -9,6 +9,7 @@ """ import os from pathlib import Path +import importlib import glob import sys import warnings @@ -880,3 +881,45 @@ def files_from_extension(raw_path, return numpy.concatenate([files_from_extension(p, extension=extension) for p in raw_path]).tolist() msgs.error(f"Incorrect type {type(raw_path)} for raw_path (must be str or list)") + + + +def load_object(module, obj=None): + """ + Load an abstracted module and object. + + Thanks to: https://stackoverflow.com/questions/67631/how-to-import-a-module-given-the-full-path?rq=1 + + Args: + module (:obj:`str`): + The name of a global python module, the root name of a local file + with the object to import, or the full module + object type. If + ``obj`` is None, this *must* be the latter. + obj (:obj:`str`, optional): + The name of the object to import. If None, ``module`` must be the + full module + object type name. + + Return: + :obj:`type`: The imported object. + + Raises: + ImportError: + Raised if unable to import ``module``. + """ + if obj is None: + _module = '.'.join(module.split('.')[:-1]) + obj = module.split('.')[-1] + else: + _module = module + + try: + Module = importlib.import_module(_module) + except (ModuleNotFoundError, ImportError, TypeError) as e: + p = Path(module + '.py').resolve() + if not p.exists(): + raise ImportError(f'Unable to load module {_module}!') from e + spec = importlib.util.spec_from_file_location(_module, str(p)) + Module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(Module) + + return getattr(Module, obj) \ No newline at end of file