def smooth_ceil_cont(inspec1, smooth, percent_ceil = None, use_raw_arc=False,sigdetect = 10.0, fwhm = 4.0): """ Utility routine to smooth and apply a ceiling to spectra """ # ToDO can we improve the logic here. Technically if use_raw_arc = True and perecent_ceil=None # we don't need to peak find or continuum subtract, but this makes the code pretty uggly. # Run line detection to get the continuum subtracted arc tampl1, tampl1_cont, tcent1, twid1, centerr1, w1, arc1, nsig1 = arc.detect_lines(inspec1, sigdetect=sigdetect, fwhm=fwhm) if use_raw_arc == True: ampl = tampl1 use_arc = inspec1 else: ampl = tampl1_cont use_arc = arc1 if percent_ceil is not None and (ampl.size > 0): # If this is set, set a ceiling on the greater > 10sigma peaks ceil1 = np.percentile(ampl, percent_ceil) spec1 = np.fmin(use_arc, ceil1) else: spec1 = np.copy(use_arc) if smooth is not None: y1 = scipy.ndimage.filters.gaussian_filter(spec1, smooth) else: y1 = np.copy(spec1) return y1
def test_flex_shift(): # Dummy slf # Read spectra obj_spec = readspec(data_path('obj_lrisb_600_sky.fits')) arx_file = os.path.join(data.Paths.sky_spec, 'sky_LRISb_600.fits') arx_spec = readspec(arx_file) arx_lines = arc.detect_lines(arx_spec.flux.value) # Call flex_dict = flexure.spec_flex_shift(obj_spec, arx_spec, arx_lines, mxshft=60) # # Apply # from scipy import interpolate # print(flex_dict['shift']) # npix = len(obj_spec.wavelength) # x = np.linspace(0., 1., npix) # f = interpolate.interp1d(x, obj_spec.wavelength.value, bounds_error=False, # fill_value="extrapolate") # new_wave = f(x+flex_dict['shift']/(npix-1)) # # from matplotlib import pyplot # pyplot.plot(arx_spec.wavelength, arx_spec.flux) # pyplot.plot(obj_spec.wavelength, obj_spec.flux) # pyplot.plot(new_wave, obj_spec.flux) # pyplot.show() assert np.abs(flex_dict['shift'] - 43.7) < 0.1
def xcorr_shift(inspec1,inspec2,smooth = None,debug = False): if smooth is not None: y1 = scipy.ndimage.filters.gaussian_filter(inspec1, smooth) y2 = scipy.ndimage.filters.gaussian_filter(inspec2, smooth) else: y1 = inspec1 y2 = inspec2 nspec = y1.shape[0] lags = np.arange(-nspec + 1, nspec) corr = scipy.signal.correlate(y1, y2, mode='full') corr_denom = np.sqrt(np.sum(y1*y1)*np.sum(y2*y2)) corr_norm = corr/corr_denom output = arc.detect_lines(corr_norm, nfitpix=7, sigdetect=5.0, fwhm=20.0, mask_width = 10.0, cont_samp=30, nfind = 1) pix_max = output[1] corr_max = np.interp(pix_max, np.arange(lags.shape[0]),corr_norm) lag_max = np.interp(pix_max, np.arange(lags.shape[0]),lags) if debug: # Interpolate for bad lines since the fitting code often returns nan plt.figure(figsize=(14, 6)) plt.plot(lags, corr_norm, color='black', drawstyle = 'steps-mid', lw=3, label = 'x-corr', linewidth = 1.0) plt.plot(lag_max[0], corr_max[0],'g+', markersize =6.0, label = 'peak') plt.title('Best shift = {:5.3f}'.format(lag_max[0]) + ', corr_max = {:5.3f}'.format(corr_max[0])) plt.legend() plt.show() return lag_max[0], corr_max[0]
def test_detect_lines(): # Using Paranal night sky as an 'arc' sky_file = pkg_resources.resource_filename('pypeit', 'data/sky_spec/paranal_sky.fits') arx_sky = xspectrum1d.XSpectrum1D.from_file(sky_file) arx_amp_true, arx_amp, arx_cent, arx_wid, arx_centerr, arx_w, arx_yprep, _ \ = arc.detect_lines(arx_sky.flux.value) assert (len(arx_w[0]) > 3275)
def xcorr_shift(inspec1,inspec2, smooth=1.0, percent_ceil=80.0, use_raw_arc=False, sigdetect=10.0, fwhm=4.0, debug=False): """ Determine the shift inspec2 relative to inspec1. This routine computes the shift by finding the maximum of the the cross-correlation coefficient. The convention for the shift is that positive shift means inspec2 is shifted to the right (higher pixel values) relative to inspec1. Args: inspec1 : ndarray Reference spectrum inspec2 : ndarray Spectrum for which the shift and stretch are computed such that it will match inspec1 smooth: float, default=1.0 Gaussian smoothing in pixels applied to both spectra for the computations. Default is 5.0 percent_ceil: float, default=90.0 Apply a ceiling to the input spectra at the percent_ceil percentile level of the distribution of peak amplitudes. This prevents extremely strong lines from completely dominating the cross-correlation, which can causes the cross-correlation to have spurious noise spikes that are not the real maximum. use_raw_arc: bool, default = False If this parameter is True the raw arc will be used rather than the continuum subtracted arc debug: boolean, default = False Returns: tuple: Returns the following: - shift: float; the shift which was determined - cross_corr: float; the maximum of the cross-correlation coefficient at this shift """ y1 = smooth_ceil_cont(inspec1,smooth,percent_ceil=percent_ceil,use_raw_arc=use_raw_arc, sigdetect = sigdetect, fwhm = fwhm) y2 = smooth_ceil_cont(inspec2,smooth,percent_ceil=percent_ceil,use_raw_arc=use_raw_arc, sigdetect = sigdetect, fwhm = fwhm) nspec = y1.shape[0] lags = np.arange(-nspec + 1, nspec) corr = scipy.signal.correlate(y1, y2, mode='full') corr_denom = np.sqrt(np.sum(y1*y1)*np.sum(y2*y2)) corr_norm = corr/corr_denom tampl_true, tampl, pix_max, twid, centerr, ww, arc_cont, nsig = arc.detect_lines(corr_norm, sigdetect=3.0, fit_frac_fwhm=1.5, fwhm=5.0, cont_frac_fwhm=1.0, cont_samp=30, nfind=1) corr_max = np.interp(pix_max, np.arange(lags.shape[0]),corr_norm) lag_max = np.interp(pix_max, np.arange(lags.shape[0]),lags) if debug: # Interpolate for bad lines since the fitting code often returns nan plt.figure(figsize=(14, 6)) plt.plot(lags, corr_norm, color='black', drawstyle = 'steps-mid', lw=3, label = 'x-corr', linewidth = 1.0) plt.plot(lag_max[0], corr_max[0],'g+', markersize =6.0, label = 'peak') plt.title('Best shift = {:5.3f}'.format(lag_max[0]) + ', corr_max = {:5.3f}'.format(corr_max[0])) plt.legend() plt.show() return lag_max[0], corr_max[0]
def arc_lines_from_spec(spec, sigdetect=10.0, fwhm=4.0, fit_frac_fwhm=1.25, cont_frac_fwhm=1.0, max_frac_fwhm=2.0, cont_samp=30, niter_cont=3, nonlinear_counts=1e10, debug=False): """ Simple wrapper to arc.detect_lines. See that code for docs Args: spec: sigdetect: fwhm: fit_frac_fwhm: cont_frac_fwhm: max_frac_fwhm: cont_samp: niter_cont: nonlinear_counts: debug: Returns: tuple: all_tcent, all_ecent, cut_tcent, icut, arc_cont_sub. See arc.detect_lines """ # Find peaks tampl, tampl_cont, tcent, twid, centerr, w, arc_cont_sub, nsig = arc.detect_lines( spec, sigdetect=sigdetect, fwhm=fwhm, fit_frac_fwhm=fit_frac_fwhm, cont_frac_fwhm=cont_frac_fwhm, max_frac_fwhm=max_frac_fwhm, cont_samp=cont_samp, niter_cont=niter_cont, nonlinear_counts=nonlinear_counts, debug=debug) all_tcent = tcent[w] all_tampl = tampl[w] all_ecent = centerr[w] all_nsig = nsig[w] # Old code cut on amplitude # Cut on Amplitude #cut_amp = all_tampl > min_ampl # Cut on significance cut_sig = all_nsig > sigdetect cut_tcent = all_tcent[cut_sig] icut = np.where(cut_sig)[0] # Return return all_tcent, all_ecent, cut_tcent, icut, arc_cont_sub
def fit_shift_stretch_iter(inspec1, inspec2, smooth = 5.0, shift_mnmx = (-1.0,1.0), stretch_mnmx = (0.8,1.2), debug = True): shift_tot = 0.0 stretch_tot = 1.0 niter = 5 stretch_vec = np.linspace(stretch_mnmx[0],stretch_mnmx[1],endpoint=True, num = 100) nspec = inspec1.size y1 = scipy.ndimage.filters.gaussian_filter(inspec1, smooth) y2 = scipy.ndimage.filters.gaussian_filter(inspec2, smooth) bounds = [stretch_mnmx] guess = np.array([1.0]) this_y2 = np.copy(y2) for iter in range(niter): this_shift, corr_val = xcross_shift_nosm(y1,this_y2) shift_tot += this_shift #y2_trans = shift_and_stretch(y2,shift,1.0) corr_vec = np.zeros_like(stretch_vec) for ii in np.arange(stretch_vec.shape[0]): corr_vec[ii] = -xcorr_shift_stretch([this_shift, stretch_vec[ii]],y1,this_y2) output = arc.detect_lines(corr_vec, nfitpix=7, sigdetect=5.0, fwhm=10.0, mask_width = 3.0, cont_samp=30, nfind=1) pix_max = output[1][0] this_stretch = np.interp(pix_max, np.arange(stretch_vec.shape[0]), stretch_vec) stretch_tot *= this_stretch corr_max = -xcorr_shift_stretch([0.0, this_stretch], y1, this_y2) this_y2 = shift_and_stretch(this_y2,0.0,this_stretch) if debug: # Interpolate for bad lines since the fitting code often returns nan plt.figure(figsize=(14, 6)) plt.plot(stretch_vec, corr_vec, color='black', drawstyle='steps-mid', lw=3, label='x-corr', linewidth=1.0) plt.plot(this_stretch, corr_max, 'g+', markersize=6.0, label='peak') plt.title('Best stretch = {:5.3f}'.format(this_stretch) + ', corr_max = {:5.3f}'.format(corr_max)) plt.legend() plt.show() if debug and (iter == niter-1): x1 = np.arange(nspec) inspec2_trans = shift_and_stretch(inspec2, 0.0, this_stretch) plt.plot(x1, inspec1, 'k-', drawstyle='steps') plt.plot(x1, inspec2_trans, 'r-', drawstyle='steps') plt.title('Iteration # {:d}'.format(iter) + ': shift_tot = {:5.3f}'.format(shift_tot) + ', stretch_tot = {:5.3f}'.format(stretch_tot) + ', corr_tot = {:5.3f}'.format(corr_max)) plt.show() corr_tot = -xcorr_shift_stretch([shift_tot, stretch_tot], y1, y2) shift_cc, cc_val = xcross_shift_nosm(y1, y2) if debug: x1 = np.arange(nspec) inspec2_trans = shift_and_stretch(inspec2, shift_tot, stretch_tot) plt.plot(x1, inspec1, 'k-', drawstyle='steps') plt.plot(x1, inspec2_trans, 'g-', drawstyle='steps') plt.title('Iteration # {:d}'.format(iter) + ': shift_tot = {:5.3f}'.format(shift_tot) + ', stretch_tot = {:5.3f}'.format(stretch_tot) + ', corr_tot = {:5.3f}'.format(corr_tot)) plt.show() return True, shift_tot, stretch_tot, corr_tot, shift_cc, cc_val
def create_linelist(wavelength, spec, fwhm, sigdetec=2., cont_samp=10., line_name=None, file_root_name=None, iraf_frmt=False, debug=False): """ Create list of lines detected in a spectrum in a PypeIt compatible format. The name of the output file is file_root_name+'_lines.dat'. Parameters ---------- wavelength : np.array wavelength spec : np.array spectrum fwhm : float fwhm in pixels used for filtering out arc lines that are too wide and not considered in fits. Parameter of arc.detect_lines(). sigdetec : float sigma threshold above fluctuations for line detection. Parameter of arc.detect_lines(). Default = 2. cont_samp : float the number of samples across the spectrum used for continuum subtraction. Parameter of arc.detect_lines(). Default = 10. line_name : str name of the lines to listed in the file. file_root_name : str name of the file where the identified lines will be stored. The code automatically add '_lines.dat' at the end of the root name. iraf_frmt : bool if True, the file is written in the IRAF format (i.e. wavelength, ion name, amplitude). """ msgs.info("Searching for peaks {} sigma above background".format(sigdetec)) tampl_true, tampl, tcent, twid, centerr, ww, arcnorm, nsig = arc.detect_lines(spec, sigdetect=sigdetec, fwhm=fwhm, cont_samp=cont_samp, debug=debug) peaks_good = tcent[ww] ampl_good = tampl[ww] # convert from pixel location to wavelength pixvec = np.arange(spec.size) wave_peak = scipy.interpolate.interp1d(pixvec, wavelength, bounds_error=False, fill_value='extrapolate')(peaks_good) npeak = len(wave_peak) ion = npeak*[str(line_name)] NIST = npeak*[1] Instr = npeak*[32] Source = npeak*['wavemodel.py'] if iraf_frmt: msgs.info("Printing file in IRAF format: {}".format(file_root_name+'_iraf_lines.dat')) ion = np.array(ion) id_lines_iraf = np.vstack( (np.round(wave_peak,5), ion, np.round(ampl_good,5)) ).T np.savetxt(file_root_name+'_iraf_lines.dat', id_lines_iraf, fmt="%15s %6s %15s", delimiter=" ") else: msgs.info("Printing file: {}".format(file_root_name+'_lines.dat')) dat = Table([wave_peak, ion, NIST, Instr, ampl_good, Source], names=('wave', 'ion','NIST','Instr','amplitude','Source')) dat.write(file_root_name+'_lines.dat',format='ascii.fixed_width')
def arc_lines_from_spec(spec, sigdetect=10.0, fwhm=4.0, fit_frac_fwhm=1.25, cont_frac_fwhm=1.0, max_frac_fwhm=2.0, cont_samp=30, niter_cont=3, nonlinear_counts=1e10, debug=False): """ Parameters ---------- spec siglev min_ampl Returns ------- """ # Find peaks tampl, tampl_cont, tcent, twid, centerr, w, arc_cont_sub, nsig = arc.detect_lines( spec, sigdetect=sigdetect, fwhm=fwhm, fit_frac_fwhm=fit_frac_fwhm, cont_frac_fwhm=cont_frac_fwhm, max_frac_fwhm=max_frac_fwhm, cont_samp=cont_samp, niter_cont=niter_cont, nonlinear_counts=nonlinear_counts, debug=debug) all_tcent = tcent[w] all_tampl = tampl[w] all_ecent = centerr[w] all_nsig = nsig[w] # Old code cut on amplitude # Cut on Amplitude #cut_amp = all_tampl > min_ampl # Cut on significance cut_sig = all_nsig > sigdetect cut_tcent = all_tcent[cut_sig] icut = np.where(cut_sig)[0] # Return return all_tcent, all_ecent, cut_tcent, icut, arc_cont_sub
def xcorr_shift(inspec1, inspec2, smooth=None, debug=False): if smooth is not None: y1 = scipy.ndimage.filters.gaussian_filter(inspec1, smooth) y2 = scipy.ndimage.filters.gaussian_filter(inspec2, smooth) else: y1 = inspec1 y2 = inspec2 nspec = y1.shape[0] lags = np.arange(-nspec + 1, nspec) corr = scipy.signal.correlate(y1, y2, mode='full') corr_denom = np.sqrt(np.sum(y1 * y1) * np.sum(y2 * y2)) corr_norm = corr / corr_denom output = arc.detect_lines(corr_norm, nfitpix=7, sigdetect=5.0, fwhm=20.0, mask_width=10.0, cont_samp=30, nfind=1) pix_max = output[1] corr_max = np.interp(pix_max, np.arange(lags.shape[0]), corr_norm) lag_max = np.interp(pix_max, np.arange(lags.shape[0]), lags) if debug: # Interpolate for bad lines since the fitting code often returns nan plt.figure(figsize=(14, 6)) plt.plot(lags, corr_norm, color='black', drawstyle='steps-mid', lw=3, label='x-corr', linewidth=1.0) plt.plot(lag_max[0], corr_max[0], 'g+', markersize=6.0, label='peak') plt.title('Best shift = {:5.3f}'.format(lag_max[0]) + ', corr_max = {:5.3f}'.format(corr_max[0])) plt.legend() plt.show() return lag_max[0], corr_max[0]
def test_detect_lines(): # Using Paranal night sky as an 'arc' arx_sky = data.load_sky_spectrum('paranal_sky.fits') arx_amp_true, arx_amp, arx_cent, arx_wid, arx_centerr, arx_w, arx_yprep, _ \ = arc.detect_lines(arx_sky.flux.value) assert (len(arx_w[0]) > 3275)
def spec_flex_shift(obj_skyspec, arx_skyspec, arx_lines, mxshft=20): """ Calculate shift between object sky spectrum and archive sky spectrum Args: obj_skyspec (:class:`linetools.spectra.xspectrum1d.XSpectrum1d`): Spectrum of the sky related to our object arx_skyspec (:class:`linetools.spectra.xspectrum1d.XSpectrum1d`): Archived sky spectrum arx_lines (tuple): Line information returned by arc.detect_lines for the Archived sky spectrum mxshft (float, optional): Maximum allowed shift from flexure; note there are cases that have been known to exceed even 30 pixels.. Returns: dict: Contains flexure info """ # TODO None of these routines should have dependencies on XSpectrum1d! # Determine the brightest emission lines msgs.warn("If we use Paranal, cut down on wavelength early on") arx_amp, arx_amp_cont, arx_cent, arx_wid, _, arx_w, arx_yprep, nsig \ = arx_lines obj_amp, obj_amp_cont, obj_cent, obj_wid, _, obj_w, obj_yprep, nsig_obj \ = arc.detect_lines(obj_skyspec.flux.value) # Keep only 5 brightest amplitude lines (xxx_keep is array of # indices within arx_w of the 5 brightest) arx_keep = np.argsort(arx_amp[arx_w])[-5:] obj_keep = np.argsort(obj_amp[obj_w])[-5:] # Calculate wavelength (Angstrom per pixel) arx_disp = np.append( arx_skyspec.wavelength.value[1] - arx_skyspec.wavelength.value[0], arx_skyspec.wavelength.value[1:] - arx_skyspec.wavelength.value[:-1]) obj_disp = np.append( obj_skyspec.wavelength.value[1] - obj_skyspec.wavelength.value[0], obj_skyspec.wavelength.value[1:] - obj_skyspec.wavelength.value[:-1]) # Calculate resolution (lambda/delta lambda_FWHM)..maybe don't need # this? can just use sigmas arx_idx = (arx_cent + 0.5).astype( np.int)[arx_w][arx_keep] # The +0.5 is for rounding arx_res = arx_skyspec.wavelength.value[arx_idx]/\ (arx_disp[arx_idx]*(2*np.sqrt(2*np.log(2)))*arx_wid[arx_w][arx_keep]) obj_idx = (obj_cent + 0.5).astype( np.int)[obj_w][obj_keep] # The +0.5 is for rounding obj_res = obj_skyspec.wavelength.value[obj_idx]/ \ (obj_disp[obj_idx]*(2*np.sqrt(2*np.log(2)))*obj_wid[obj_w][obj_keep]) if not np.all(np.isfinite(obj_res)): msgs.warn( 'Failed to measure the resolution of the object spectrum, likely due to error ' 'in the wavelength image.') return None msgs.info("Resolution of Archive={0} and Observation={1}".format( np.median(arx_res), np.median(obj_res))) # Determine sigma of gaussian for smoothing arx_sig2 = np.power(arx_disp[arx_idx] * arx_wid[arx_w][arx_keep], 2) obj_sig2 = np.power(obj_disp[obj_idx] * obj_wid[obj_w][obj_keep], 2) arx_med_sig2 = np.median(arx_sig2) obj_med_sig2 = np.median(obj_sig2) if obj_med_sig2 >= arx_med_sig2: smooth_sig = np.sqrt(obj_med_sig2 - arx_med_sig2) # Ang smooth_sig_pix = smooth_sig / np.median(arx_disp[arx_idx]) arx_skyspec = arx_skyspec.gauss_smooth(smooth_sig_pix * 2 * np.sqrt(2 * np.log(2))) else: msgs.warn("Prefer archival sky spectrum to have higher resolution") smooth_sig_pix = 0. msgs.warn("New Sky has higher resolution than Archive. Not smoothing") #smooth_sig = np.sqrt(arx_med_sig**2-obj_med_sig**2) #Determine region of wavelength overlap min_wave = max(np.amin(arx_skyspec.wavelength.value), np.amin(obj_skyspec.wavelength.value)) max_wave = min(np.amax(arx_skyspec.wavelength.value), np.amax(obj_skyspec.wavelength.value)) #Smooth higher resolution spectrum by smooth_sig (flux is conserved!) # if np.median(obj_res) >= np.median(arx_res): # msgs.warn("New Sky has higher resolution than Archive. Not smoothing") #obj_sky_newflux = ndimage.gaussian_filter(obj_sky.flux, smooth_sig) # else: #tmp = ndimage.gaussian_filter(arx_sky.flux, smooth_sig) # arx_skyspec = arx_skyspec.gauss_smooth(smooth_sig_pix*2*np.sqrt(2*np.log(2))) #arx_sky.flux = ndimage.gaussian_filter(arx_sky.flux, smooth_sig) # Define wavelengths of overlapping spectra keep_idx = np.where((obj_skyspec.wavelength.value >= min_wave) & (obj_skyspec.wavelength.value <= max_wave))[0] #keep_wave = [i for i in obj_sky.wavelength.value if i>=min_wave if i<=max_wave] #Rebin both spectra onto overlapped wavelength range if len(keep_idx) <= 50: msgs.warn("Not enough overlap between sky spectra") return None # rebin onto object ALWAYS keep_wave = obj_skyspec.wavelength[keep_idx] arx_skyspec = arx_skyspec.rebin(keep_wave) obj_skyspec = obj_skyspec.rebin(keep_wave) # Trim edges (rebinning is junk there) arx_skyspec.data['flux'][0, :2] = 0. arx_skyspec.data['flux'][0, -2:] = 0. obj_skyspec.data['flux'][0, :2] = 0. obj_skyspec.data['flux'][0, -2:] = 0. # Set minimum to 0. For bad rebinning and for pernicious extractions obj_skyspec.data['flux'][0, :] = np.maximum(obj_skyspec.data['flux'][0, :], 0.) arx_skyspec.data['flux'][0, :] = np.maximum(arx_skyspec.data['flux'][0, :], 0.) # Normalize spectra to unit average sky count norm = np.sum(obj_skyspec.flux.value) / obj_skyspec.npix norm2 = np.sum(arx_skyspec.flux.value) / arx_skyspec.npix if norm <= 0: msgs.warn("Bad normalization of object in flexure algorithm") msgs.warn("Will try the median") norm = np.median(obj_skyspec.flux.value) if norm <= 0: msgs.warn("Improper sky spectrum for flexure. Is it too faint??") return None if norm2 <= 0: msgs.warn( 'Bad normalization of archive in flexure. You are probably using wavelengths ' 'well beyond the archive.') return None obj_skyspec.flux = obj_skyspec.flux / norm arx_skyspec.flux = arx_skyspec.flux / norm2 # Deal with bad pixels msgs.work("Need to mask bad pixels") # Deal with underlying continuum msgs.work("Consider taking median first [5 pixel]") everyn = obj_skyspec.npix // 20 pypeitFit_obj, _ = fitting.iterfit(obj_skyspec.wavelength.value, obj_skyspec.flux.value, nord=3, kwargs_bspline={'everyn': everyn}, kwargs_reject={ 'groupbadpix': True, 'maxrej': 1 }, maxiter=15, upper=3.0, lower=3.0) obj_sky_cont, _ = pypeitFit_obj.value(obj_skyspec.wavelength.value) obj_sky_flux = obj_skyspec.flux.value - obj_sky_cont pypeitFit_sky, _ = fitting.iterfit(arx_skyspec.wavelength.value, arx_skyspec.flux.value, nord=3, kwargs_bspline={'everyn': everyn}, kwargs_reject={ 'groupbadpix': True, 'maxrej': 1 }, maxiter=15, upper=3.0, lower=3.0) arx_sky_cont, _ = pypeitFit_sky.value(arx_skyspec.wavelength.value) arx_sky_flux = arx_skyspec.flux.value - arx_sky_cont # Consider sharpness filtering (e.g. LowRedux) msgs.work("Consider taking median first [5 pixel]") #Cross correlation of spectra #corr = np.correlate(arx_skyspec.flux, obj_skyspec.flux, "same") corr = np.correlate(arx_sky_flux, obj_sky_flux, "same") #Create array around the max of the correlation function for fitting for subpixel max # Restrict to pixels within maxshift of zero lag lag0 = corr.size // 2 #mxshft = settings.argflag['reduce']['flexure']['maxshift'] max_corr = np.argmax(corr[lag0 - mxshft:lag0 + mxshft]) + lag0 - mxshft subpix_grid = np.linspace(max_corr - 3., max_corr + 3., 7) #Fit a 2-degree polynomial to peak of correlation function. JFH added this if/else to not crash for bad slits if np.any(np.isfinite(corr[subpix_grid.astype(np.int)])): fit = fitting.PypeItFit(xval=subpix_grid, yval=corr[subpix_grid.astype(np.int)], func='polynomial', order=np.atleast_1d(2)) fit.fit() success = True max_fit = -0.5 * fit.fitc[1] / fit.fitc[2] else: fit = fitting.PypeItFit(xval=subpix_grid, yval=0.0 * subpix_grid, func='polynomial', order=np.atleast_1d(2)) fit.fit() success = False max_fit = 0.0 msgs.warn('Flexure compensation failed for one of your objects') #Calculate and apply shift in wavelength shift = float(max_fit) - lag0 msgs.info("Flexure correction of {:g} pixels".format(shift)) #model = (fit[2]*(subpix_grid**2.))+(fit[1]*subpix_grid)+fit[0] return dict(polyfit=fit, shift=shift, subpix=subpix_grid, corr=corr[subpix_grid.astype(np.int)], sky_spec=obj_skyspec, arx_spec=arx_skyspec, corr_cen=corr.size / 2, smooth=smooth_sig_pix, success=success)
def fit_shift_stretch_iter(inspec1, inspec2, smooth=5.0, shift_mnmx=(-1.0, 1.0), stretch_mnmx=(0.8, 1.2), debug=True): shift_tot = 0.0 stretch_tot = 1.0 niter = 5 stretch_vec = np.linspace(stretch_mnmx[0], stretch_mnmx[1], endpoint=True, num=100) nspec = inspec1.size y1 = scipy.ndimage.filters.gaussian_filter(inspec1, smooth) y2 = scipy.ndimage.filters.gaussian_filter(inspec2, smooth) bounds = [stretch_mnmx] guess = np.array([1.0]) this_y2 = np.copy(y2) for iter in range(niter): this_shift, corr_val = xcross_shift_nosm(y1, this_y2) shift_tot += this_shift #y2_trans = shift_and_stretch(y2,shift,1.0) corr_vec = np.zeros_like(stretch_vec) for ii in np.arange(stretch_vec.shape[0]): corr_vec[ii] = -xcorr_shift_stretch([this_shift, stretch_vec[ii]], y1, this_y2) output = arc.detect_lines(corr_vec, nfitpix=7, sigdetect=5.0, fwhm=10.0, mask_width=3.0, cont_samp=30, nfind=1) pix_max = output[1][0] this_stretch = np.interp(pix_max, np.arange(stretch_vec.shape[0]), stretch_vec) stretch_tot *= this_stretch corr_max = -xcorr_shift_stretch([0.0, this_stretch], y1, this_y2) this_y2 = shift_and_stretch(this_y2, 0.0, this_stretch) if debug: # Interpolate for bad lines since the fitting code often returns nan plt.figure(figsize=(14, 6)) plt.plot(stretch_vec, corr_vec, color='black', drawstyle='steps-mid', lw=3, label='x-corr', linewidth=1.0) plt.plot(this_stretch, corr_max, 'g+', markersize=6.0, label='peak') plt.title('Best stretch = {:5.3f}'.format(this_stretch) + ', corr_max = {:5.3f}'.format(corr_max)) plt.legend() plt.show() if debug and (iter == niter - 1): x1 = np.arange(nspec) inspec2_trans = shift_and_stretch(inspec2, 0.0, this_stretch) plt.plot(x1, inspec1, 'k-', drawstyle='steps') plt.plot(x1, inspec2_trans, 'r-', drawstyle='steps') plt.title('Iteration # {:d}'.format(iter) + ': shift_tot = {:5.3f}'.format(shift_tot) + ', stretch_tot = {:5.3f}'.format(stretch_tot) + ', corr_tot = {:5.3f}'.format(corr_max)) plt.show() corr_tot = -xcorr_shift_stretch([shift_tot, stretch_tot], y1, y2) shift_cc, cc_val = xcross_shift_nosm(y1, y2) if debug: x1 = np.arange(nspec) inspec2_trans = shift_and_stretch(inspec2, shift_tot, stretch_tot) plt.plot(x1, inspec1, 'k-', drawstyle='steps') plt.plot(x1, inspec2_trans, 'g-', drawstyle='steps') plt.title('Iteration # {:d}'.format(iter) + ': shift_tot = {:5.3f}'.format(shift_tot) + ', stretch_tot = {:5.3f}'.format(stretch_tot) + ', corr_tot = {:5.3f}'.format(corr_tot)) plt.show() return True, shift_tot, stretch_tot, corr_tot, shift_cc, cc_val
def tilts_find_lines(arc_spec, slit_cen, tracethresh=10.0, sig_neigh=5.0, nfwhm_neigh=3.0, only_these_lines=None, fwhm=4.0, nonlinear_counts=1e10, fit_frac_fwhm=1.25, cont_frac_fwhm=1.0, max_frac_fwhm=2.0, cont_samp=30, niter_cont=3, debug_lines=False, debug_peaks=False): """ I can't believe this method has no docs Args: arc_spec: slit_cen: tracethresh: sig_neigh: nfwhm_neigh: only_these_lines: fwhm: nonlinear_counts: fit_frac_fwhm: cont_frac_fwhm: max_frac_fwhm: cont_samp: niter_cont: debug_lines: debug_peaks: Returns: (np.ndarray, np.ndarray) or (None,None): """ nspec = arc_spec.size spec_vec = np.arange(nspec) # Find peaks with a liberal threshold of sigdetect = 5.0 tampl_tot, tampl_cont_tot, tcent_tot, twid_tot, _, wgood, arc_cont_sub, nsig_tot = arc.detect_lines( arc_spec, sigdetect=np.min([sig_neigh,tracethresh]), fwhm=fwhm, fit_frac_fwhm=fit_frac_fwhm, cont_frac_fwhm=cont_frac_fwhm, max_frac_fwhm=max_frac_fwhm, cont_samp=cont_samp, niter_cont=niter_cont, nonlinear_counts=nonlinear_counts, debug=debug_peaks) # Good lines arcdet = tcent_tot[wgood] arc_ampl = tampl_cont_tot[wgood] nsig = nsig_tot[wgood] npix_neigh = nfwhm_neigh*fwhm # Determine the best lines to use to trace the tilts aduse = np.zeros(arcdet.size, dtype=np.bool) # Which lines should be used to trace the tilts w = np.where(nsig >= tracethresh) aduse[w] = 1 # Remove lines that are within npix_neigh pixels. # #ToDO replce this with a near-neighbor based approach, where # we identify groups and take the brightest line in a given group? nuse = np.sum(aduse) detuse = arcdet[aduse] idxuse = np.arange(arcdet.size)[aduse] olduse = aduse.copy() for s in range(nuse): w = np.where((np.abs(arcdet - detuse[s]) <= npix_neigh) & (np.abs(arcdet - detuse[s]) >= 1.0) & (nsig > sig_neigh))[0] for u in range(w.size): if nsig[w[u]] > nsig[olduse][s]: aduse[idxuse[s]] = False break # Restricted to ID lines? [introduced to avoid LRIS ghosts] if only_these_lines is not None: ids_pix = np.array(only_these_lines) idxuse = np.arange(arcdet.size)[aduse] for s in idxuse: if np.min(np.abs(arcdet[s] - ids_pix)) > 2.0: msgs.info("Ignoring line at spectral position={:6.1f} which was not identified".format(arcdet[s])) aduse[s] = False # Final spectral positions of arc lines we will trace lines_spec = arcdet[aduse] nlines = len(lines_spec) if nlines == 0: msgs.warn('No arc lines were deemed usable on this slit. Cannot compute tilts. Try lowering tracethresh.') msgs.warn('Or, more likely, this was a bad slit (which you might remove)') return None, None else: msgs.info('Modelling arc line tilts with {:d} arc lines'.format(nlines)) if debug_lines: xrng = np.arange(nspec) plt.figure(figsize=(14, 6)) plt.plot(xrng, arc_cont_sub, color='black', drawstyle='steps-mid', lw=3, label='arc', linewidth=1.0) plt.plot(arcdet[~aduse], arc_ampl[~aduse], 'r+', markersize=6.0, label='bad for tilts') plt.plot(arcdet[aduse], arc_ampl[aduse], 'g+', markersize=6.0, label='good for tilts') if nonlinear_counts < 1e9: plt.hlines(nonlinear_counts, xrng.min(), xrng.max(), color='orange', linestyle='--', linewidth=2.0, label='nonlinear', zorder=10) plt.title('Good Lines = {:d}'.format(np.sum(aduse)) + ', Bad Lines = {:d}'.format(np.sum(~aduse))) plt.ylim(arc_cont_sub.min(), 1.5 * arc_cont_sub.max()) plt.legend() plt.show() # Spatial position of line, i.e. the central trace interpolated onto the spectral pixel of the line lines_spat = np.interp(lines_spec, spec_vec, slit_cen) return lines_spec, lines_spat
def spat_flexure_shift(sciimg, slits, debug=False, maxlag=20): """ Calculate a rigid flexure shift in the spatial dimension between the slitmask and the science image. It is *important* to use original=True when defining the slitmask as everything should be relative to the initial slits Otherwise, the WaveTilts could get out of sync with science images Args: sciimg (`numpy.ndarray`_): slits (:class:`pypeit.slittrace.SlitTraceSet`): maxlag (:obj:`int`, optional): Maximum flexure searched for Returns: float: The spatial flexure shift relative to the initial slits """ # Mask -- Includes short slits and those excluded by the user (e.g. ['rdx']['slitspatnum']) slitmask = slits.slit_img(initial=True, exclude_flag=slits.bitmask.exclude_for_flexure) _sciimg = sciimg if slitmask.shape == sciimg.shape \ else arc.resize_mask2arc(slitmask.shape, sciimg) onslits = slitmask > -1 corr_slits = onslits.astype(float).flatten() # Compute mean_sci, med_sci, stddev_sci = stats.sigma_clipped_stats(_sciimg[onslits]) thresh = med_sci + 5.0 * stddev_sci corr_sci = np.fmin(_sciimg.flatten(), thresh) lags, xcorr = utils.cross_correlate(corr_sci, corr_slits, maxlag) xcorr_denom = np.sqrt( np.sum(corr_sci * corr_sci) * np.sum(corr_slits * corr_slits)) xcorr_norm = xcorr / xcorr_denom # TODO -- Generate a QA plot tampl_true, tampl, pix_max, twid, centerr, ww, arc_cont, nsig \ = arc.detect_lines(xcorr_norm, sigdetect=3.0, fit_frac_fwhm=1.5, fwhm=5.0, cont_frac_fwhm=1.0, cont_samp=30, nfind=1, debug=debug) # No peak? -- e.g. data fills the entire detector if len(tampl) == 0: msgs.warn( 'No peak found in spatial flexure. Assuming there is none..') if debug: embed(header='68 of flexure') return 0. # Find the peak xcorr_max = np.interp(pix_max, np.arange(lags.shape[0]), xcorr_norm) lag_max = np.interp(pix_max, np.arange(lags.shape[0]), lags) msgs.info('Spatial flexure measured: {}'.format(lag_max[0])) if debug: plt.figure(figsize=(14, 6)) plt.plot(lags, xcorr_norm, color='black', drawstyle='steps-mid', lw=3, label='x-corr', linewidth=1.0) plt.plot(lag_max[0], xcorr_max[0], 'g+', markersize=6.0, label='peak') plt.title('Best shift = {:5.3f}'.format(lag_max[0]) + ', corr_max = {:5.3f}'.format(xcorr_max[0])) plt.legend() plt.show() #tslits_shift = trace_slits.shift_slits(tslits_dict, lag_max) # Now translate the tilts #slitmask_shift = pixels.tslits2mask(tslits_shift) #slitmask_shift = slits.slit_img(flexure=lag_max[0]) if debug: # Now translate the slits in the tslits_dict all_left_flexure, all_right_flexure, mask = slits.select_edges( flexure=lag_max[0]) gpm = mask == 0 viewer, ch = display.show_image(_sciimg) #display.show_slits(viewer, ch, left_flexure[:,gpm], right_flexure)[:,gpm]#, slits.id) #, args.det) embed(header='83 of flexure.py') return lag_max[0]
import scipy #infile = 'XSHOOTER_modelsky.fits' #outfile = 'OH_XSHOOTER_lines.dat' infile = 'GNIRS_modelsky.fits' outfile = 'OH_GNIRS_lines.dat' hdu = fits.open(infile) spec = hdu[0].data wave = 1e4 * hdu[1].data pixvec = np.arange(spec.size) tampl, tcent, twid, centerr, w, yprep, nsig = arc.detect_lines(spec, nfitpix=7, sigdetect=2.0, FWHM=12, cont_samp=10, debug=True) peaks_good = tcent[w] ampl_good = tampl[w] wave_peak = scipy.interpolate.interp1d(pixvec, wave, bounds_error=False, fill_value='extrapolate')(peaks_good) npeak = len(wave_peak) ion = npeak * ['OH'] NIST = npeak * [1] Instr = npeak * [32] Source = npeak * ['IDL code'] dat = Table([wave_peak, ion, NIST, Instr, ampl_good, Source],
def spec_flexure_slit(slits, slitord, slit_bpm, sky_file, method="boxcar", specobjs=None, slit_specs=None, mxshft=None): """Calculate the spectral flexure for every slit (global) or object (local) Args: slits (:class:`~pypeit.slittrace.SlitTraceSet`): Slit trace set slitord (`numpy.ndarray`_): Array of slit/order numbers slit_bpm (`numpy.ndarray`_): True = masked slit sky_file (str): Sky file method (:obj:`str`, optional): Two methods are available: - 'boxcar': Recommended for object extractions. This method uses the boxcar extracted sky and wavelength spectra from the input specobjs - 'slitcen': Recommended when no objects are being extracted. This method uses a spectrum (stored in slitspecs) that is extracted from the center of each slit. specobjs (:class:`~pypeit.specobjs.Specobjs`, optional): Spectral extractions slit_specs (list, optional): A list of linetools.xspectrum1d, one for each slit. The spectra stored in this list are sky spectra, extracted from the center of each slit. mxshft (int, optional): Passed to flex_shift() Returns: :obj:`list`: A list of :obj:`dict` objects containing flexure results of each slit. This is filled with a basically empty dict if the slit is skipped. """ sv_fdict = None msgs.work("Consider doing 2 passes in flexure as in LowRedux") # Determine the method slit_cen = True if (specobjs is None) or (method == "slitcen") else False # Load Archive. Save the line information to avoid the performance hit from calling it on the archive sky spectrum # multiple times sky_spectrum = load_sky_spectrum(sky_file) sky_lines = arc.detect_lines(sky_spectrum.flux.value) nslits = slits.nslits gpm = np.logical_not(slit_bpm) gdslits = np.where(gpm)[0] # Initialise the flexure list for each slit flex_list = [] # Slit/objects to come back to return_later_sobjs = [] # Loop over slits, and then over objects for islit in range(nslits): msgs.info("Working on spectral flexure of slit: {:d}".format(islit)) # Reset flex_dict = dict(polyfit=[], shift=[], subpix=[], corr=[], corr_cen=[], spec_file=sky_file, smooth=[], arx_spec=[], sky_spec=[], method=[]) # If no objects on this slit append an empty dictionary if islit not in gdslits: flex_list.append(flex_dict.copy()) continue if slit_cen: sky_wave = slit_specs[islit].wavelength.value sky_flux = slit_specs[islit].flux.value # Calculate the shift fdict = spec_flex_shift(slit_specs[islit], sky_spectrum, sky_lines, mxshft=mxshft) # Failed? if fdict is not None: # Update dict for key in [ 'polyfit', 'shift', 'subpix', 'corr', 'corr_cen', 'smooth', 'arx_spec' ]: flex_dict[key].append(fdict[key]) # Interpolate sky_wave_new = flexure_interp(fdict['shift'], sky_wave) flex_dict['sky_spec'].append( xspectrum1d.XSpectrum1D.from_tuple( (sky_wave_new, sky_flux))) flex_dict['method'].append("slitcen") else: i_slitord = slitord[islit] indx = specobjs.slitorder_indices(i_slitord) this_specobjs = specobjs[indx] # Loop through objects for ss, sobj in enumerate(this_specobjs): if sobj is None: continue if sobj['BOX_WAVE'] is None: #len(specobj._data.keys()) == 1: # Nothing extracted; only the trace exists continue msgs.info( "Working on flexure for object # {:d}".format(sobj.OBJID) + "in slit # {:d}".format(islit)) # Using boxcar sky_wave = sobj.BOX_WAVE sky_flux = sobj.BOX_COUNTS_SKY # Generate 1D spectrum for object obj_sky = xspectrum1d.XSpectrum1D.from_tuple( (sky_wave, sky_flux)) # Calculate the shift fdict = spec_flex_shift(obj_sky, sky_spectrum, sky_lines, mxshft=mxshft) punt = False if fdict is None: msgs.warn( "Flexure shift calculation failed for this spectrum.") if sv_fdict is not None: msgs.warn( "Will used saved estimate from a previous slit/object" ) fdict = copy.deepcopy(sv_fdict) else: # One does not exist yet # Save it for later return_later_sobjs.append([islit, ss]) punt = True else: sv_fdict = copy.deepcopy(fdict) # Punt? if punt: break # Update dict for key in [ 'polyfit', 'shift', 'subpix', 'corr', 'corr_cen', 'smooth', 'arx_spec', 'sky_spec' ]: flex_dict[key].append(fdict[key]) flex_dict['method'].append("boxcar") # Check if we need to go back # TODO :: This code just throws an error... probably need to delete or fix this "local" spectral flexure code if not slit_cen: # Do we need to go back? for items in return_later_sobjs: if sv_fdict is None: msgs.info("No flexure corrections could be made") break # Setup msgs.error("This probably needs to be updated") slit, ss = items flex_dict = flex_list[slit] sobj = specobjs[ss] # Copy me fdict = copy.deepcopy(sv_fdict) # Update dict for key in [ 'polyfit', 'shift', 'subpix', 'corr', 'corr_cen', 'smooth', 'arx_spec', 'sky_spec' ]: flex_dict[key].append(fdict[key]) flex_dict['method'].append("boxcar") # Append, this will be an empty dictionary if the flexure failed flex_list.append(flex_dict.copy()) return flex_list
def flex_shift(obj_skyspec, arx_skyspec, mxshft=20): """ Calculate shift between object sky spectrum and archive sky spectrum Parameters ---------- obj_skyspec arx_skyspec Returns ------- flex_dict: dict Contains flexure info """ # TODO None of these routines should have dependencies on XSpectrum1d! # Determine the brightest emission lines msgs.warn("If we use Paranal, cut down on wavelength early on") arx_amp, arx_amp_cont, arx_cent, arx_wid, _, arx_w, arx_yprep, nsig = arc.detect_lines( arx_skyspec.flux.value) obj_amp, obj_amp_cont, obj_cent, obj_wid, _, obj_w, obj_yprep, nsig_obj = arc.detect_lines( obj_skyspec.flux.value) # Keep only 5 brightest amplitude lines (xxx_keep is array of # indices within arx_w of the 5 brightest) arx_keep = np.argsort(arx_amp[arx_w])[-5:] obj_keep = np.argsort(obj_amp[obj_w])[-5:] # Calculate wavelength (Angstrom per pixel) arx_disp = np.append( arx_skyspec.wavelength.value[1] - arx_skyspec.wavelength.value[0], arx_skyspec.wavelength.value[1:] - arx_skyspec.wavelength.value[:-1]) #arx_disp = (np.amax(arx_sky.wavelength.value)-np.amin(arx_sky.wavelength.value))/arx_sky.wavelength.size obj_disp = np.append( obj_skyspec.wavelength.value[1] - obj_skyspec.wavelength.value[0], obj_skyspec.wavelength.value[1:] - obj_skyspec.wavelength.value[:-1]) #obj_disp = (np.amax(obj_sky.wavelength.value)-np.amin(obj_sky.wavelength.value))/obj_sky.wavelength.size # Calculate resolution (lambda/delta lambda_FWHM)..maybe don't need # this? can just use sigmas arx_idx = (arx_cent + 0.5).astype( np.int)[arx_w][arx_keep] # The +0.5 is for rounding arx_res = arx_skyspec.wavelength.value[arx_idx]/\ (arx_disp[arx_idx]*(2*np.sqrt(2*np.log(2)))*arx_wid[arx_w][arx_keep]) obj_idx = (obj_cent + 0.5).astype( np.int)[obj_w][obj_keep] # The +0.5 is for rounding obj_res = obj_skyspec.wavelength.value[obj_idx]/ \ (obj_disp[obj_idx]*(2*np.sqrt(2*np.log(2)))*obj_wid[obj_w][obj_keep]) #obj_res = (obj_sky.wavelength.value[0]+(obj_disp*obj_cent[obj_w][obj_keep]))/( # obj_disp*(2*np.sqrt(2*np.log(2)))*obj_wid[obj_w][obj_keep]) if not np.all(np.isfinite(obj_res)): msgs.warn( 'Failed to measure the resolution of the object spectrum, likely due to error ' 'in the wavelength image.') return None msgs.info("Resolution of Archive={0} and Observation={1}".format( np.median(arx_res), np.median(obj_res))) # Determine sigma of gaussian for smoothing arx_sig2 = np.power(arx_disp[arx_idx] * arx_wid[arx_w][arx_keep], 2) obj_sig2 = np.power(obj_disp[obj_idx] * obj_wid[obj_w][obj_keep], 2) arx_med_sig2 = np.median(arx_sig2) obj_med_sig2 = np.median(obj_sig2) if obj_med_sig2 >= arx_med_sig2: smooth_sig = np.sqrt(obj_med_sig2 - arx_med_sig2) # Ang smooth_sig_pix = smooth_sig / np.median(arx_disp[arx_idx]) arx_skyspec = arx_skyspec.gauss_smooth(smooth_sig_pix * 2 * np.sqrt(2 * np.log(2))) else: msgs.warn("Prefer archival sky spectrum to have higher resolution") smooth_sig_pix = 0. msgs.warn("New Sky has higher resolution than Archive. Not smoothing") #smooth_sig = np.sqrt(arx_med_sig**2-obj_med_sig**2) #Determine region of wavelength overlap min_wave = max(np.amin(arx_skyspec.wavelength.value), np.amin(obj_skyspec.wavelength.value)) max_wave = min(np.amax(arx_skyspec.wavelength.value), np.amax(obj_skyspec.wavelength.value)) #Smooth higher resolution spectrum by smooth_sig (flux is conserved!) # if np.median(obj_res) >= np.median(arx_res): # msgs.warn("New Sky has higher resolution than Archive. Not smoothing") #obj_sky_newflux = ndimage.gaussian_filter(obj_sky.flux, smooth_sig) # else: #tmp = ndimage.gaussian_filter(arx_sky.flux, smooth_sig) # arx_skyspec = arx_skyspec.gauss_smooth(smooth_sig_pix*2*np.sqrt(2*np.log(2))) #arx_sky.flux = ndimage.gaussian_filter(arx_sky.flux, smooth_sig) # Define wavelengths of overlapping spectra keep_idx = np.where((obj_skyspec.wavelength.value >= min_wave) & (obj_skyspec.wavelength.value <= max_wave))[0] #keep_wave = [i for i in obj_sky.wavelength.value if i>=min_wave if i<=max_wave] #Rebin both spectra onto overlapped wavelength range if len(keep_idx) <= 50: msgs.warn("Not enough overlap between sky spectra") return None else: #rebin onto object ALWAYS keep_wave = obj_skyspec.wavelength[keep_idx] arx_skyspec = arx_skyspec.rebin(keep_wave) obj_skyspec = obj_skyspec.rebin(keep_wave) # Trim edges (rebinning is junk there) arx_skyspec.data['flux'][0, :2] = 0. arx_skyspec.data['flux'][0, -2:] = 0. obj_skyspec.data['flux'][0, :2] = 0. obj_skyspec.data['flux'][0, -2:] = 0. # Normalize spectra to unit average sky count norm = np.sum(obj_skyspec.flux.value) / obj_skyspec.npix obj_skyspec.flux = obj_skyspec.flux / norm norm2 = np.sum(arx_skyspec.flux.value) / arx_skyspec.npix arx_skyspec.flux = arx_skyspec.flux / norm2 if (norm < 0.): msgs.warn("Bad normalization of object in flexure algorithm") msgs.warn("Will try the median") norm = np.median(obj_skyspec.flux.value) if (norm < 0.): msgs.warn("Improper sky spectrum for flexure. Is it too faint??") return None if (norm2 < 0.): msgs.warn( 'Bad normalization of archive in flexure. You are probably using wavelengths ' 'well beyond the archive.') return None # Deal with bad pixels msgs.work("Need to mask bad pixels") # Deal with underlying continuum msgs.work("Consider taking median first [5 pixel]") everyn = obj_skyspec.npix // 20 bspline_par = dict(everyn=everyn) mask, ct = utils.robust_polyfit(obj_skyspec.wavelength.value, obj_skyspec.flux.value, 3, function='bspline', sigma=3., bspline_par=bspline_par) obj_sky_cont = utils.func_val(ct, obj_skyspec.wavelength.value, 'bspline') obj_sky_flux = obj_skyspec.flux.value - obj_sky_cont mask, ct_arx = utils.robust_polyfit(arx_skyspec.wavelength.value, arx_skyspec.flux.value, 3, function='bspline', sigma=3., bspline_par=bspline_par) arx_sky_cont = utils.func_val(ct_arx, arx_skyspec.wavelength.value, 'bspline') arx_sky_flux = arx_skyspec.flux.value - arx_sky_cont # Consider sharpness filtering (e.g. LowRedux) msgs.work("Consider taking median first [5 pixel]") #Cross correlation of spectra #corr = np.correlate(arx_skyspec.flux, obj_skyspec.flux, "same") corr = np.correlate(arx_sky_flux, obj_sky_flux, "same") #Create array around the max of the correlation function for fitting for subpixel max # Restrict to pixels within maxshift of zero lag lag0 = corr.size // 2 #mxshft = settings.argflag['reduce']['flexure']['maxshift'] max_corr = np.argmax(corr[lag0 - mxshft:lag0 + mxshft]) + lag0 - mxshft subpix_grid = np.linspace(max_corr - 3., max_corr + 3., 7) #Fit a 2-degree polynomial to peak of correlation function. JFH added this if/else to not crash for bad slits if np.any(np.isfinite(corr[subpix_grid.astype(np.int)])): fit = utils.func_fit(subpix_grid, corr[subpix_grid.astype(np.int)], 'polynomial', 2) success = True max_fit = -0.5 * fit[1] / fit[2] else: fit = utils.func_fit(subpix_grid, 0.0 * subpix_grid, 'polynomial', 2) success = False max_fit = 0.0 msgs.warn('Flexure compensation failed for one of your objects') #Calculate and apply shift in wavelength shift = float(max_fit) - lag0 msgs.info("Flexure correction of {:g} pixels".format(shift)) #model = (fit[2]*(subpix_grid**2.))+(fit[1]*subpix_grid)+fit[0] flex_dict = dict(polyfit=fit, shift=shift, subpix=subpix_grid, corr=corr[subpix_grid.astype(np.int)], sky_spec=obj_skyspec, arx_spec=arx_skyspec, corr_cen=corr.size / 2, smooth=smooth_sig_pix, success=success) # Return return flex_dict
from astropy.table import Table from astropy.io import fits from pypeit.core import arc import numpy as np import scipy #infile = 'XSHOOTER_modelsky.fits' #outfile = 'OH_XSHOOTER_lines.dat' infile = 'GNIRS_modelsky.fits' outfile = 'OH_GNIRS_lines.dat' hdu = fits.open(infile) spec = hdu[0].data wave = 1e4*hdu[1].data pixvec = np.arange(spec.size) tampl, tcent, twid, centerr, w, yprep, nsig = arc.detect_lines(spec, nfitpix=7, sigdetect = 2.0, FWHM = 12, cont_samp = 10, debug = True) peaks_good = tcent[w] ampl_good = tampl[w] wave_peak = scipy.interpolate.interp1d(pixvec, wave, bounds_error=False, fill_value='extrapolate')(peaks_good) npeak = len(wave_peak) ion = npeak*['OH'] NIST = npeak*[1] Instr = npeak*[32] Source = npeak*['IDL code'] dat = Table([wave_peak, ion, NIST, Instr, ampl_good, Source], names=('wave', 'ion','NIST','Instr','amplitude','Source')) dat.write(outfile,format='ascii.fixed_width')