diff --git a/py/desispec/scripts/trace_shifts.py b/py/desispec/scripts/trace_shifts.py index e12a09ef1..58ace0d5d 100644 --- a/py/desispec/scripts/trace_shifts.py +++ b/py/desispec/scripts/trace_shifts.py @@ -148,12 +148,14 @@ def fit_trace_shifts(image, args): spectrum_filename = args.spectrum if args.sky : + continuum_subtract = True srch_file = "data/spec-sky.dat" if not resources.files('desispec').joinpath(srch_file).is_file(): log.error("Cannot find sky spectrum file {:s}".format(srch_file)) raise RuntimeError("Cannot find sky spectrum file {:s}".format(srch_file)) spectrum_filename = resources.files('desispec').joinpath(srch_file) elif args.arc_lamps : + continuum_subtract = False srch_file = "data/spec-arc-lamps.dat" if not resources.files('desispec').joinpath(srch_file).is_file(): log.error("Cannot find arc lamps spectrum file {:s}".format(srch_file)) @@ -196,7 +198,7 @@ def fit_trace_shifts(image, args): x_for_dx,y_for_dx,dx,ex,fiber_for_dx,wave_for_dx = compute_dx_from_cross_dispersion_profiles(xcoef,ycoef,wavemin,wavemax, image=image, fibers=fibers, width=args.width, deg=args.degxy,image_rebin=args.ccd_rows_rebin) if internal_wavelength_calib : # measure y shifts - x_for_dy,y_for_dy,dy,ey,fiber_for_dy,wave_for_dy = compute_dy_using_boxcar_extraction(tset, image=image, fibers=fibers, width=args.width) + x_for_dy,y_for_dy,dy,ey,fiber_for_dy,wave_for_dy = compute_dy_using_boxcar_extraction(tset, image=image, fibers=fibers, width=args.width, continuum_subtract=continuum_subtract) mdy = np.median(dy) log.info("Subtract median(dy)={}".format(mdy)) dy -= mdy # remove median, because this is an internal calibration @@ -216,17 +218,14 @@ def fit_trace_shifts(image, args): degyy=args.degyy # if any quadrant is masked, reduce to a single offset - hy=image.pix.shape[0]//2 - hx=image.pix.shape[1]//2 - allgood=True - allgood &= (np.sum((x_for_dx0) # some data in this quadrant - allgood &= (np.sum((x_for_dxhy))>0) # some data in this quadrant - allgood &= (np.sum((x_for_dx>hx)&(y_for_dx0) # some data in this quadrant - allgood &= (np.sum((x_for_dx>hx)&(y_for_dx>hy))>0) # some data in this quadrant - allgood &= (np.sum((x_for_dy0) # some data in this quadrant - allgood &= (np.sum((x_for_dyhy))>0) # some data in this quadrant - allgood &= (np.sum((x_for_dy>hx)&(y_for_dy0) # some data in this quadrant - allgood &= (np.sum((x_for_dy>hx)&(y_for_dy>hy))>0) # some data in this quadrant + hy = image.pix.shape[0] // 2 + hx = image.pix.shape[1] // 2 + allgood = True + for _curx, _cury in [(x_for_dx,y_for_dx),(x_for_dy, y_for_dy)]: + for curxop in [np.less, np.greater]: + for curyop in [np.less, np.greater]: + allgood &= np.any(curxop(_curx, hx) & curyop(_cury, hy)) + # some data in this quadrant if not allgood : log.warning("No shift data for at least one quadrant of the CCD, falls back to deg=0 shift") degxx=0 diff --git a/py/desispec/trace_shifts.py b/py/desispec/trace_shifts.py index 4ec99fe05..738a280f7 100644 --- a/py/desispec/trace_shifts.py +++ b/py/desispec/trace_shifts.py @@ -218,7 +218,7 @@ def compute_dy_from_spectral_cross_correlation(flux, wave, refflux, ivar=None, A relative flux calibration of the two spectra is done internally. Args: - flux : 1D array of spectral flux as a function of wavelenght + flux : 1D array of spectral flux as a function of wavelength wave : 1D array of wavelength (in Angstrom) refflux : 1D array of reference spectral flux @@ -360,16 +360,22 @@ def compute_dy_from_spectral_cross_correlations_of_frame(flux, ivar, wave , xcoe else : wmax=wave[-1] ok=(wave>=wmin)&(wave<=wmax) - sw=np.sum(ivar[fiber,ok]*flux[fiber,ok]*(flux[fiber,ok]>0)) - if sw<=0 : + flux_weights = ivar[fiber,ok] * flux[fiber,ok]**2 * (flux[fiber,ok]>0) + flux_weights_sum = np.sum(flux_weights) + if flux_weights_sum <= 0 : continue + block_wave = np.sum(flux_weights * wave[ok]) / flux_weights_sum - dwave,err = compute_dy_from_spectral_cross_correlation(flux[fiber,ok],wave[ok],reference_flux[ok],ivar=ivar[fiber,ok]*reference_flux[ok],hw=3., calibrate=True) + dwave,err = compute_dy_from_spectral_cross_correlation(flux[fiber,ok], wave[ok], + reference_flux[ok], + ivar=ivar[fiber,ok], + hw=3., calibrate=True) + if fiber % 10==0 : + log.info(f"Wavelength offset {dwave} +/- {err} for fiber {fiber:03d} at wave {block_wave}") if err > 1 : continue - block_wave = np.sum(ivar[fiber,ok]*flux[fiber,ok]*(flux[fiber,ok]>0)*wave[ok])/sw rw = legx(block_wave,wavemin,wavemax) tx = legval(rw,xcoef[fiber]) ty = legval(rw,ycoef[fiber]) @@ -388,7 +394,8 @@ def compute_dy_from_spectral_cross_correlations_of_frame(flux, ivar, wave , xcoe return x_for_dy,y_for_dy,dy,ey,fiber_for_dy,wave_for_dy -def compute_dy_using_boxcar_extraction(xytraceset, image, fibers, width=7, degyy=2) : +def compute_dy_using_boxcar_extraction(xytraceset, image, fibers, width=7, degyy=2, + continuum_subtract = False): """ Measures y offsets (internal wavelength calibration) from a preprocessed image and a trace set using a cross-correlation of boxcar extracted spectra. Uses boxcar_extraction , resample_boxcar_frame , compute_dy_from_spectral_cross_correlations_of_frame @@ -401,6 +408,7 @@ def compute_dy_using_boxcar_extraction(xytraceset, image, fibers, width=7, degyy fibers : 1D np.array of int (default is all fibers, the first fiber is always = 0) width : int, extraction boxcar width, default is 7 degyy : int, degree of polynomial fit of shifts as a function of y, used to reject outliers. + continuum_subtract : bool if true subtract continuum before cross-correlation Returns: x : 1D array of x coordinates on CCD (axis=1 in numpy image array, AXIS=0 in FITS, cross-dispersion axis = fiber number direction) @@ -415,18 +423,22 @@ def compute_dy_using_boxcar_extraction(xytraceset, image, fibers, width=7, degyy log=get_logger() # boxcar extraction - qframe = qproc_boxcar_extraction(xytraceset, image, fibers=fibers, width=7) # resampling on common finer wavelength grid - flux, ivar, wave = resample_boxcar_frame(qframe.flux, qframe.ivar, qframe.wave, oversampling=4) - - # boolean mask of fibers with good data - good_fibers = (np.sum(ivar>0, axis=1) > 0) - - # median flux of good fibers used as internal spectral reference - mflux=np.median(flux[good_fibers],axis=0) - + oversampling = 4 + flux, ivar, wave = resample_boxcar_frame(qframe.flux, qframe.ivar, qframe.wave, oversampling=oversampling) + flux0 = flux * 1 # for debugging + if continuum_subtract: + mflux, mivar, flux = _continuum_subtract_median(flux, ivar, continuum_win = oversampling * 9) + flux[flux<0] = 0 + else: + # boolean mask of fibers with good data + good_fibers = (np.sum(ivar>0, axis=1) > 0) + + # median flux of good fibers used as internal spectral reference + mflux=np.median(flux[good_fibers],axis=0) + # measure y shifts wavemin = xytraceset.wavemin wavemax = xytraceset.wavemax @@ -465,8 +477,8 @@ def compute_dx_from_cross_dispersion_profiles(xcoef,ycoef,wavemin,wavemax, image Measure x offsets from a preprocessed image and a trace set Args: - xcoef : 2D np.array of shape (nfibers,ncoef) containing Legendre coefficents for each fiber to convert wavelenght to XCCD - ycoef : 2D np.array of shape (nfibers,ncoef) containing Legendre coefficents for each fiber to convert wavelenght to YCCD + xcoef : 2D np.array of shape (nfibers,ncoef) containing Legendre coefficients for each fiber to convert wavelength to XCCD + ycoef : 2D np.array of shape (nfibers,ncoef) containing Legendre coefficients for each fiber to convert wavelength to YCCD wavemin : float wavemax : float. wavemin and wavemax are used to define a reduced variable legx(wave,wavemin,wavemax)=2*(wave-wavemin)/(wavemax-wavemin)-1 used to compute the traces, xccd=legval(legx(wave,wavemin,wavemax),xtrace[fiber]) @@ -618,57 +630,122 @@ def compute_dx_from_cross_dispersion_profiles(xcoef,ycoef,wavemin,wavemax, image return ox,oy,odx,oex,of,ol - -def shift_ycoef_using_external_spectrum(psf, xytraceset, image, fibers, - spectrum_filename, degyy=2, width=7, - prior_width_dy=0.1): +def _prepare_ref_spectrum(ref_wave, ref_spectrum, psf, wave, mflux, nfibers): """ - Measure y offsets (external wavelength calibration) from a preprocessed image , a PSF + trace set using a cross-correlation of boxcar extracted spectra - and an external well-calibrated spectrum. - The PSF shape is used to convolve the input spectrum. It could also be used to correct for the PSF asymetry (disabled for now). - A relative flux calibration of the spectra is performed internally. - - Args: - psf : specter PSF - xytraceset : XYTraceset object - image : DESI preprocessed image object - fibers : 1D np.array of fiber indices - spectrum_filename : path to input spectral file ( read with np.loadtxt , first column is wavelength (in vacuum and Angstrom) , second column in flux (arb. units) - - Optional: - width : int, extraction boxcar width, default is 7 - degyy : int, degree of polynomial fit of shifts as a function of y, used to reject outliers. - prior_width_dy: float with of the Gaussian prior on dy - + Prepare the reference spectrum to be used for wavelength offset + determination. Here we convolve it to the right LSF and rescale it + to match the measured flux. + + Arguments: + ref_wave: np.array of wavelengths + ref_spectrum: np.array of reference spectrum flux + psf: PSF object + wave: np.array wavelength of extracted spectra + mflux: np.array flux of extracted spectra + nfibers: int Returns: - ycoef : 2D np.array of same shape as input, with modified Legendre coefficents for each fiber to convert wavelenght to YCCD - + ref_wave, ref_spectrum: tuple of wavelength and flux arrays """ log = get_logger() + + # trim ref_spectrum + subset = (ref_wave >= wave[0]) & (ref_wave <= wave[-1]) + ref_wave = ref_wave[subset] + ref_spectrum = ref_spectrum[subset] - wavemin = xytraceset.wavemin - wavemax = xytraceset.wavemax - xcoef = xytraceset.x_vs_wave_traceset._coeff - ycoef = xytraceset.y_vs_wave_traceset._coeff - - tmp=np.loadtxt(spectrum_filename).T - ref_wave=tmp[0] - ref_spectrum=tmp[1] - log.info("read reference spectrum in %s with %d entries"%(spectrum_filename,ref_wave.size)) - - log.info("rextract spectra with boxcar") + # check wave is linear or make it linear + if (np.abs((ref_wave[1] - ref_wave[0]) - (ref_wave[-1] - ref_wave[-2])) > + 0.0001 * (ref_wave[1]-ref_wave[0])): + log.info("reference spectrum wavelength is not on a linear grid, resample it") + dwave = np.min(np.gradient(ref_wave)) + tmp_wave = np.linspace(ref_wave[0], ref_wave[-1], + int((ref_wave[-1]-ref_wave[0])/dwave)) + ref_spectrum = resample_flux(tmp_wave, ref_wave, ref_spectrum) + ref_wave = tmp_wave + + n_wave_bins = 20 # how many points along wavelength to use to get psf + n_representative_fibers = 20 # how many fibers to use to get psf + fiber_list = np.unique(np.linspace(0, nfibers - 1, + n_representative_fibers).astype(int)) + wave_bins = np.linspace(wave[0], wave[-1], n_wave_bins+1) + wave_bins = wave_bins[:-1] + .5 * np.diff(wave_bins) + spectra = [] + ref_spectrum0 = ref_spectrum * 1 + # original before convolution + angstrom_hwidth = 3 # psf half width + + for central_wave0 in wave_bins: + ipos = np.searchsorted(ref_wave, central_wave0) + central_wave = ref_wave[ipos] # actual value from the grid + dwave = ref_wave[ipos + 1] - ref_wave[ipos] + hw = int(angstrom_hwidth / dwave) + 1 + wave_range = ref_wave[ipos - hw:ipos + hw + 1] + kernels = [] + for fiber in fiber_list: + x, y = psf.xy(fiber, wave_range) + + x = x[:,None] + np.linspace(-hw, hw, 2*hw+1)[None,:] + # original code below but I don't understand y[-1]-y[0] part + # x=np.tile(x[hw]+np.arange(-hw,hw+1)*(y[-1]-y[0])/(2*hw+1),(y.size,1)) + y = np.tile(y, (2 * hw + 1, 1)).T + + kernel2d = psf._value(x, y, fiber, central_wave) + kernel1d = np.sum(kernel2d, axis=1) + kernels.append(kernel1d) + kernels = np.mean(kernels, axis=0) + # average across fibers + ref_spectrum = fftconvolve(ref_spectrum0, kernels, mode='same') + spectra.append(ref_spectrum) + log.info("convolve reference spectrum using PSF") + spectra = np.array(spectra) + spectra_pos = np.searchsorted(wave_bins, ref_wave) + result = ref_spectrum * 0. + # Edges of the spectrum + result[spectra_pos == 0] = spectra[0][spectra_pos==0] + result[spectra_pos == n_wave_bins] = spectra[-1][spectra_pos == n_wave_bins] + inside = (spectra_pos > 0) & (spectra_pos < n_wave_bins) + weight = (ref_wave[inside] - wave_bins[spectra_pos[inside] - 1])/( + wave_bins[spectra_pos[inside]] - wave_bins[spectra_pos[inside] - 1]) + # weight is zero on left edge one on right + + # linearly stitching the spectra convolved with different kernel + result[inside] = (1 - weight) * spectra[spectra_pos[inside] - 1, inside]+( + weight * spectra[spectra_pos[inside],inside]) + ref_spectrum = result - # boxcar extraction - qframe = qproc_boxcar_extraction(xytraceset, image, fibers=fibers, width=7) + # resample input spectrum + log.info("resample convolved reference spectrum") + ref_spectrum = resample_flux(wave, ref_wave , ref_spectrum) - # resampling on common finer wavelength grid + log.info("absorb difference of calibration") + x = (wave - wave[wave.size//2]) / 50. + kernel = np.exp(- x**2/2) + f1 = fftconvolve(mflux, kernel, mode='same') + f2 = fftconvolve(ref_spectrum, kernel, mode='same') + # We scale by a constant factor + scale = (f1 * f2).sum() / (f2 * f2).sum() + ref_spectrum *= scale + return ref_wave, ref_spectrum - flux, ivar, wave = resample_boxcar_frame(qframe.flux, qframe.ivar, qframe.wave, oversampling=2) +def _continuum_subtract_median(flux0, ivar, continuum_win = 17): + """ + Compute the median spectrum after continuum subtraction + + Arguments: + flux0: (nfibers, npix) np.array of fluxes + ivar: (nfibers, npix)-shaped np.array of inverser variances + continuum_win: integer. How-many pixels around are used to get continuum. + Here we use the 1d annulus from continuum_win/2 to continuum_win + + Returns: + mfux: npix np.array of median spectrum + mivar: npix np.array with ivar of the median spectrum + flux: (nfibers, npix) continuum subtracted original flux array + """ # here we get rid of continuum by applying a median filter - continuum_win = 17 - continuum_foot = np.abs(np.arange(-continuum_win,continuum_win))>continuum_win /2. - + continuum_foot = np.abs(np.arange(-continuum_win,continuum_win+1))>continuum_win /2. + flux = flux0 * 1 # we will modify flux # we only keep emission lines and get rid of continuum for ii in range(flux.shape[0]): flux[ii] = flux[ii] - median_filter(flux[ii], footprint=continuum_foot) @@ -698,86 +775,90 @@ def shift_ycoef_using_external_spectrum(psf, xytraceset, image, fibers, mivar = np.minimum(mivar, 1. / mad_factor**2 / np.median(np.abs(mflux))**2) # do not allow negatives mflux[mflux < 0] = 0 + return mflux, mivar, flux +def shift_ycoef_using_external_spectrum(psf, xytraceset, image, fibers, + spectrum_filename, degyy=2, width=7, + prior_width_dy=0.1): + """ + Measure y offsets (external wavelength calibration) from a preprocessed image , a PSF + trace set using a cross-correlation of boxcar extracted spectra + and an external well-calibrated spectrum. + The PSF shape is used to convolve the input spectrum. It could also be used to correct for the PSF asymetry (disabled for now). + A relative flux calibration of the spectra is performed internally. - # trim ref_spectrum - i=(ref_wave>=wave[0])&(ref_wave<=wave[-1]) - ref_wave=ref_wave[i] - ref_spectrum=ref_spectrum[i] + Args: + psf : specter PSF + xytraceset : XYTraceset object + image : DESI preprocessed image object + fibers : 1D np.array of fiber indices + spectrum_filename : path to input spectral file ( read with np.loadtxt , first column is wavelength (in vacuum and Angstrom) , second column in flux (arb. units) - # check wave is linear or make it linear - if np.abs((ref_wave[1]-ref_wave[0])-(ref_wave[-1]-ref_wave[-2]))>0.0001*(ref_wave[1]-ref_wave[0]) : - log.info("reference spectrum wavelength is not on a linear grid, resample it") - dwave = np.min(np.gradient(ref_wave)) - tmp_wave = np.linspace(ref_wave[0],ref_wave[-1],int((ref_wave[-1]-ref_wave[0])/dwave)) - ref_spectrum = resample_flux(tmp_wave, ref_wave , ref_spectrum) - ref_wave = tmp_wave + Optional: + width : int, extraction boxcar width, default is 7 + degyy : int, degree of polynomial fit of shifts as a function of y, used to reject outliers. + prior_width_dy: float with of the Gaussian prior on dy - i=np.argmax(ref_spectrum) - central_wave_for_psf_evaluation = ref_wave[i] - fiber_for_psf_evaluation = (flux.shape[0]//2) - try : - # compute psf at most significant line of ref_spectrum - dwave=ref_wave[i+1]-ref_wave[i] - hw=int(3./dwave)+1 # 3A half width - wave_range = ref_wave[i-hw:i+hw+1] - x,y=psf.xy(fiber_for_psf_evaluation,wave_range) - x=np.tile(x[hw]+np.arange(-hw,hw+1)*(y[-1]-y[0])/(2*hw+1),(y.size,1)) - y=np.tile(y,(2*hw+1,1)).T - kernel2d=psf._value(x,y,fiber_for_psf_evaluation,central_wave_for_psf_evaluation) - kernel1d=np.sum(kernel2d,axis=1) - log.info("convolve reference spectrum using PSF at fiber %d and wavelength %dA"%(fiber_for_psf_evaluation,central_wave_for_psf_evaluation)) - ref_spectrum=fftconvolve(ref_spectrum,kernel1d, mode='same') - except : - log.warning("couldn't convolve reference spectrum: %s %s"%(sys.exc_info()[0],sys.exc_info()[1])) + Returns: + ycoef : 2D np.array of same shape as input, with modified Legendre coefficients for each fiber to convert wavelength to YCCD + + """ + log = get_logger() + + wavemin = xytraceset.wavemin + wavemax = xytraceset.wavemax + xcoef = xytraceset.x_vs_wave_traceset._coeff + ycoef = xytraceset.y_vs_wave_traceset._coeff + tmp=np.loadtxt(spectrum_filename).T + ref_wave=tmp[0] + ref_spectrum=tmp[1] + log.info("read reference spectrum in %s with %d entries"%(spectrum_filename,ref_wave.size)) + log.info("rextract spectra with boxcar") - # resample input spectrum - log.info("resample convolved reference spectrum") - ref_spectrum = resample_flux(wave, ref_wave , ref_spectrum) + # boxcar extraction + qframe = qproc_boxcar_extraction(xytraceset, image, fibers=fibers, width=7) + + # resampling on common finer wavelength grid + oversampling = 2 # + flux, ivar, wave = resample_boxcar_frame(qframe.flux, qframe.ivar, qframe.wave, oversampling=oversampling) + + mflux, mivar, flux = _continuum_subtract_median(flux, ivar, continuum_win=oversampling*9) + + ref_wave, ref_spectrum = _prepare_ref_spectrum(ref_wave, ref_spectrum, psf, wave, mflux, len(ivar)) - log.info("absorb difference of calibration") - x = (wave - wave[wave.size//2]) / 50. - kernel = np.exp(- x**2/2) - f1 = fftconvolve(mflux, kernel, mode='same') - f2 = fftconvolve(ref_spectrum, kernel, mode='same') - # We scale by a constant factor - scale = (f1 * f2).sum() / (f2 * f2).sum() - ref_spectrum *= scale - log.info("fit shifts on wavelength bins") # define bins - n_wavelength_bins = degyy+4 - y_for_dy=np.array([]) - dy=np.array([]) - ey=np.array([]) - wave_for_dy=np.array([]) - + n_wavelength_bins = degyy + 4 + y_for_dy = np.array([]) + dy = np.array([]) + ey = np.array([]) + wave_for_dy = np.array([]) + fiber_for_psf_evaluation = flux.shape[0] //2 + wavelength_bins = np.linspace(wave[0], wave[-1], n_wavelength_bins+1) for b in range(n_wavelength_bins) : - wmin=wave[0]+((wave[-1]-wave[0])/n_wavelength_bins)*b - if b=wmin)&(wave<=wmax) - sw= np.sum(mflux[ok]*(mflux[ok]>0)) - if sw==0 : + wmin, wmax = [wavelength_bins[_] for _ in [b, b + 1]] + ok = (wave >= wmin) & (wave <= wmax) + flux_weights = mflux[ok]**2 * (mflux[ok] > 0) * mivar[ok] + flux_weights_sum = np.sum(flux_weights) + if flux_weights_sum == 0 : continue dwave,err = compute_dy_from_spectral_cross_correlation(mflux[ok], wave[ok], ref_spectrum[ok], ivar=mivar[ok], hw=10., prior_width_dy=prior_width_dy) - bin_wave = np.sum(mflux[ok]*(mflux[ok]>0)*wave[ok])/sw - x,y=psf.xy(fiber_for_psf_evaluation,bin_wave) - eps=0.1 - x,yp=psf.xy(fiber_for_psf_evaluation,bin_wave+eps) - dydw=(yp-y)/eps - if err*dydw<1 : - dy=np.append(dy,-dwave*dydw) - ey=np.append(ey,err*dydw) - wave_for_dy=np.append(wave_for_dy,bin_wave) + bin_wave = np.sum(flux_weights * wave[ok]) / flux_weights_sum + # flux weighted wavelength of the center + # Computing the derivative dy/dwavelength + x, y = psf.xy(fiber_for_psf_evaluation, bin_wave) + eps = 0.1 + x, yp = psf.xy(fiber_for_psf_evaluation, bin_wave+eps) + dydw = (yp - y) / eps + if err * dydw < 1 : + dy = np.append(dy, -dwave * dydw) + ey = np.append(ey, err*dydw) + wave_for_dy = np.append(wave_for_dy,bin_wave) y_for_dy=np.append(y_for_dy,y) - log.info("wave = %fA , y=%d, measured dwave = %f +- %f A"%(bin_wave,y,dwave,err)) + log.info(f"wave = {bin_wave}A , y={y}, measured dwave = {dwave} +- {err} A") if False : # we don't need this for now try : @@ -1210,11 +1291,11 @@ def polynomial_fit(z,ez,xx,yy,degx,degy) : def recompute_legendre_coefficients(xcoef,ycoef,wavemin,wavemax,degxx,degxy,degyx,degyy,dx_coeff,dy_coeff) : """ - Modifies legendre coefficients of an input trace set using polynomial coefficents (as defined by the routine monomials) + Modifies legendre coefficients of an input trace set using polynomial coefficients (as defined by the routine monomials) Args: - xcoef : 2D np.array of shape (nfibers,ncoef) containing Legendre coefficents for each fiber to convert wavelenght to XCCD - ycoef : 2D np.array of shape (nfibers,ncoef) containing Legendre coefficents for each fiber to convert wavelenght to YCCD + xcoef : 2D np.array of shape (nfibers,ncoef) containing Legendre coefficients for each fiber to convert wavelength to XCCD + ycoef : 2D np.array of shape (nfibers,ncoef) containing Legendre coefficients for each fiber to convert wavelength to YCCD wavemin : float wavemax : float. wavemin and wavemax are used to define a reduced variable legx(wave,wavemin,wavemax)=2*(wave-wavemin)/(wavemax-wavemin)-1 used to compute the traces, xccd=legval(legx(wave,wavemin,wavemax),xtrace[fiber]) @@ -1226,8 +1307,8 @@ def recompute_legendre_coefficients(xcoef,ycoef,wavemin,wavemax,degxx,degxy,degy dy_coeff : 1D np.array of polynomial coefficients of size (degyx*degyy) as defined by the routine monomials. Returns: - xcoef : 2D np.array of shape (nfibers,ncoef) with modified Legendre coefficents - ycoef : 2D np.array of shape (nfibers,ncoef) with modified Legendre coefficents + xcoef : 2D np.array of shape (nfibers,ncoef) with modified Legendre coefficients + ycoef : 2D np.array of shape (nfibers,ncoef) with modified Legendre coefficients """ wave=np.linspace(wavemin,wavemax,100) nfibers=xcoef.shape[0]