def plot_psf_vs_time(outdir,cam,exps,fiber,dicLines,xaxis): dic = copy.deepcopy(exps) for k,v in dic.items(): dic[k][cam] = {} idx = str(k).zfill(8) p = '{}/psf-{}-{}.fits'.format(outdir,cam,idx) try: psf = read_xytraceset(p) except: print('INFO: did not find: ',p) continue dic[k][cam]['SIGMAX'] = psf.xsig_vs_wave(fiber,dicLines[cam[0]]['LINE']) dic[k][cam]['SIGMAY'] = psf.ysig_vs_wave(fiber,dicLines[cam[0]]['LINE']) dic[k][cam]['X'] = psf.x_vs_wave(fiber,dicLines[cam[0]]['LINE']) dic[k][cam]['Y'] = psf.y_vs_wave(fiber,dicLines[cam[0]]['LINE']) ### for exptime in sorted(set([ dic[tk]['EXPTIME'] for tk in dic.keys() ])): f, ax = plt.subplots(nrows=4, ncols=1, figsize=(10,10)) plt.subplots_adjust(top=0.95,hspace=0.,wspace=0.) plt.suptitle(r'$\mathrm{SP'+str(cam[-1])+', cam = '+cam+',\, exptime='+str(exptime)+'}$',fontsize=20) for i,k in enumerate(['SIGMAX','SIGMAY','X','Y']): y = sp.array([ dic[tk][cam][k] for tk in dic.keys() if dic[tk]['EXPTIME']==exptime and k in dic[tk][cam].keys() ]) if y.size==0: continue if xaxis=='DATE_OBS': x = sp.array([ dic[tk]['DATE_OBS'] for tk in dic.keys() if dic[tk]['EXPTIME']==exptime and k in dic[tk][cam].keys() ]) w = sp.argsort(x) x = x[w] y = y[w] x *= 24.*3600. x -= x[0] linemarker = 'o-' else: x = sp.array([ dic[tk]['CAM'][cam][xaxis] for tk in dic.keys() if dic[tk]['EXPTIME']==exptime and k in dic[tk][cam].keys() ]) linemarker = 'o' y -= y[0] ax[i].plot(x,y,linemarker) if i==len(['SIGMAX','SIGMAY','X','Y'])-1: ax[i].set_xlabel(r'$\mathrm{'+xaxis.replace('_','')+'}$') else: ax[i].set_xticklabels([]) ax[i].grid() ax[i].set_ylabel(r'$\mathrm{'+k+'} \, [\mathrm{pix}]$') #plt.show() plt.savefig('{}/psf-vs-{}-cam-{}-exptime-{}.png'.format(outdir,xaxis,cam,exptime)) plt.clf() return
def __init__(self, filename): print("desispec.psf is DEPRECATED, PLEASE USE desispec.xytraceset") self.traceset = read_xytraceset(filename) # all in traceset now. # psf kept to ease transition self.npix_y = self.traceset.npix_y self.xcoeff = self.traceset.x_vs_wave_traceset._coeff # in traceset self.ycoeff = self.traceset.y_vs_wave_traceset._coeff # in traceset self.wmin = self.traceset.wavemin # in traceset self.wmax = self.traceset.wavemax # in traceset self.nspec = self.traceset.nspec # in traceset self.ncoeff = self.traceset.x_vs_wave_traceset._coeff.shape[1] # self.traceset.wave_vs_y( 0, 100. ) # call wave_vs_y for creation of wave_vs_y_traceset and consistent inversion self.icoeff = self.traceset.wave_vs_y_traceset._coeff # in traceset self.ymin = self.traceset.wave_vs_y_traceset._xmin # in traceset self.ymax = self.traceset.wave_vs_y_traceset._xmax # in traceset
def fit_trace_shifts(image, args): global psfs log = get_logger() log.info("starting") tset = read_xytraceset(args.psf) wavemin = tset.wavemin wavemax = tset.wavemax xcoef = tset.x_vs_wave_traceset._coeff ycoef = tset.y_vs_wave_traceset._coeff nfibers = xcoef.shape[0] log.info( "read PSF trace with xcoef.shape = {} , ycoef.shape = {} , and wavelength range {}:{}" .format(xcoef.shape, ycoef.shape, int(wavemin), int(wavemax))) lines = None if args.lines is not None: log.info("We will fit the image using the psf model and lines") # read lines lines = np.loadtxt(args.lines, usecols=[0]) ok = (lines > wavemin) & (lines < wavemax) log.info( "read {} lines in {}, with {} of them in traces wavelength range". format(len(lines), args.lines, np.sum(ok))) lines = lines[ok] else: log.info( "We will do an internal calibration of trace coordinates without using the psf shape in a first step" ) internal_wavelength_calib = (not args.continuum) if args.auto: log.debug("read flavor of input image {}".format(args.image)) hdus = pyfits.open(args.image) if "FLAVOR" not in hdus[0].header: log.error( "no FLAVOR keyword in image header, cannot run with --auto option" ) raise KeyError( "no FLAVOR keyword in image header, cannot run with --auto option" ) flavor = hdus[0].header["FLAVOR"].strip().lower() hdus.close() log.info("Input is a '{}' image".format(flavor)) if flavor == "flat": internal_wavelength_calib = False elif flavor == "arc": internal_wavelength_calib = True args.arc_lamps = True else: internal_wavelength_calib = True args.sky = True log.info("wavelength calib, internal={}, sky={} , arc_lamps={}".format( internal_wavelength_calib, args.sky, args.arc_lamps)) spectrum_filename = args.spectrum if args.sky: srch_file = "data/spec-sky.dat" if not resource_exists('desispec', srch_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 = resource_filename('desispec', srch_file) elif args.arc_lamps: srch_file = "data/spec-arc-lamps.dat" if not resource_exists('desispec', srch_file): log.error( "Cannot find arc lamps spectrum file {:s}".format(srch_file)) raise RuntimeError( "Cannot find arc lamps spectrum file {:s}".format(srch_file)) spectrum_filename = resource_filename('desispec', srch_file) if spectrum_filename is not None: log.info( "Use external calibration from cross-correlation with {}".format( spectrum_filename)) if args.nfibers is not None: nfibers = args.nfibers # FOR DEBUGGING fibers = np.arange(nfibers) if lines is not None: # use a forward modeling of the image # it's slower and works only for individual lines # it's in principle more accurate # but gives systematic residuals for complex spectra like the sky psf = read_specter_psf(args.psf) x, y, dx, ex, dy, ey, fiber_xy, wave_xy = compute_dx_dy_using_psf( psf, image, fibers, lines) x_for_dx = x y_for_dx = y fiber_for_dx = fiber_xy wave_for_dx = wave_xy x_for_dy = x y_for_dy = y fiber_for_dy = fiber_xy wave_for_dy = wave_xy else: # internal calibration method that does not use the psf # nor a prior set of lines. this method is much faster # measure x shifts 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) mdy = np.median(dy) log.info("Subtract median(dy)={}".format(mdy)) dy -= mdy # remove median, because this is an internal calibration else: # duplicate dx results with zero shift to avoid write special case code below x_for_dy = x_for_dx.copy() y_for_dy = y_for_dx.copy() dy = np.zeros(dx.shape) ey = 1.e-6 * np.ones(ex.shape) fiber_for_dy = fiber_for_dx.copy() wave_for_dy = wave_for_dx.copy() degxx = args.degxx degxy = args.degxy degyx = args.degyx degyy = args.degyy while (True): # loop because polynomial degrees could be reduced log.info( "polynomial fit of measured offsets with degx=(%d,%d) degy=(%d,%d)" % (degxx, degxy, degyx, degyy)) try: dx_coeff, dx_coeff_covariance, dx_errorfloor, dx_mod, dx_mask = polynomial_fit( z=dx, ez=ex, xx=x_for_dx, yy=y_for_dx, degx=degxx, degy=degxy) dy_coeff, dy_coeff_covariance, dy_errorfloor, dy_mod, dy_mask = polynomial_fit( z=dy, ez=ey, xx=x_for_dy, yy=y_for_dy, degx=degyx, degy=degyy) log.info("dx dy error floor = %4.3f %4.3f pixels" % (dx_errorfloor, dy_errorfloor)) log.info("check fit uncertainties are ok on edge of CCD") merr = 0. for fiber in [0, nfibers - 1]: for rw in [-1, 1]: tx = legval(rw, xcoef[fiber]) ty = legval(rw, ycoef[fiber]) m = monomials(tx, ty, degxx, degxy) tdx = np.inner(dx_coeff, m) tsx = np.sqrt(np.inner(m, dx_coeff_covariance.dot(m))) m = monomials(tx, ty, degyx, degyy) tdy = np.inner(dy_coeff, m) tsy = np.sqrt(np.inner(m, dy_coeff_covariance.dot(m))) merr = max(merr, tsx) merr = max(merr, tsy) log.info("max edge shift error = %4.3f pixels" % merr) if degxx == 0 and degxy == 0 and degyx == 0 and degyy == 0: break except (LinAlgError, ValueError): log.warning( "polynomial fit failed with degx=(%d,%d) degy=(%d,%d)" % (degxx, degxy, degyx, degyy)) if degxx == 0 and degxy == 0 and degyx == 0 and degyy == 0: log.error( "polynomial degrees are already 0. we can fit the offsets") raise RuntimeError( "polynomial degrees are already 0. we can fit the offsets") merr = 100000. # this will lower the pol. degree. if merr > args.max_error: if merr != 100000.: log.warning( "max edge shift error = %4.3f pixels is too large, reducing degrees" % merr) if degxy > 0 and degyy > 0 and degxy > degxx and degyy > degyx: # first along wavelength if degxy > 0: degxy -= 1 if degyy > 0: degyy -= 1 else: # then along fiber if degxx > 0: degxx -= 1 if degyx > 0: degyx -= 1 else: # error is ok, so we quit the loop break # write this for debugging if args.outoffsets: file = open(args.outoffsets, "w") file.write( "# axis wave fiber x y delta error polval (axis 0=y axis1=x)\n") for e in range(dy.size): file.write("0 %f %d %f %f %f %f %f\n" % (wave_for_dy[e], fiber_for_dy[e], x_for_dy[e], y_for_dy[e], dy[e], ey[e], dy_mod[e])) for e in range(dx.size): file.write("1 %f %d %f %f %f %f %f\n" % (wave_for_dx[e], fiber_for_dx[e], x_for_dx[e], y_for_dx[e], dx[e], ex[e], dx_mod[e])) file.close() log.info("wrote offsets in ASCII file %s" % args.outoffsets) # print central shift mx = np.median(x_for_dx) my = np.median(y_for_dx) m = monomials(mx, my, degxx, degxy) mdx = np.inner(dx_coeff, m) mex = np.sqrt(np.inner(m, dx_coeff_covariance.dot(m))) mx = np.median(x_for_dy) my = np.median(y_for_dy) m = monomials(mx, my, degyx, degyy) mdy = np.inner(dy_coeff, m) mey = np.sqrt(np.inner(m, dy_coeff_covariance.dot(m))) log.info("central shifts dx = %4.3f +- %4.3f dy = %4.3f +- %4.3f " % (mdx, mex, mdy, mey)) # for each fiber, apply offsets and recompute legendre polynomial log.info("for each fiber, apply offsets and recompute legendre polynomial") # compute x y to record max deviations wave = np.linspace(tset.wavemin, tset.wavemax, 5) x0 = np.zeros((tset.nspec, wave.size)) y0 = np.zeros((tset.nspec, wave.size)) for s in range(tset.nspec): x0[s] = tset.x_vs_wave(s, wave) y0[s] = tset.y_vs_wave(s, wave) tset.x_vs_wave_traceset._coeff, tset.y_vs_wave_traceset._coeff = recompute_legendre_coefficients( xcoef=tset.x_vs_wave_traceset._coeff, ycoef=tset.y_vs_wave_traceset._coeff, wavemin=tset.wavemin, wavemax=tset.wavemax, degxx=degxx, degxy=degxy, degyx=degyx, degyy=degyy, dx_coeff=dx_coeff, dy_coeff=dy_coeff) # use an input spectrum as an external calibration of wavelength if spectrum_filename is not None: # the psf is used only to convolve the input spectrum # the traceset of the psf is not used here psf = read_specter_psf(args.psf) tset.y_vs_wave_traceset._coeff = shift_ycoef_using_external_spectrum( psf=psf, xytraceset=tset, image=image, fibers=fibers, spectrum_filename=spectrum_filename, degyy=args.degyy, width=7) x = np.zeros(x0.shape) y = np.zeros(x0.shape) for s in range(tset.nspec): x[s] = tset.x_vs_wave(s, wave) y[s] = tset.y_vs_wave(s, wave) dx = x - x0 dy = y - y0 if tset.meta is None: tset.meta = dict() tset.meta["MEANDX"] = np.mean(dx) tset.meta["MINDX"] = np.min(dx) tset.meta["MAXDX"] = np.max(dx) tset.meta["MEANDY"] = np.mean(dy) tset.meta["MINDY"] = np.min(dy) tset.meta["MAXDY"] = np.max(dy) return tset
h = fitsio.FITS(dic[cam]['PATH']) head = h['IMAGE'].read_header() camName = head['CAMERA'].strip() d = h['IMAGE'].read() w = h['MASK'].read()==0. td = d.copy() td[~w] = sp.nan h.close() if getattr(args,'{}_psf_path'.format(cam)) is None: cfinder = CalibFinder([head]) p = cfinder.findfile('PSF') else: p = getattr(args,'{}_psf_path'.format(cam)) dic[cam]['PSF'] = read_xytraceset(p) ### image of the PSF xmin = min( dic[cam]['PSF'].x_vs_wave(fmin,dic[cam]['LINE']['LINE']), dic[cam]['PSF'].x_vs_wave(fmax,dic[cam]['LINE']['LINE']) ) xmax = max( dic[cam]['PSF'].x_vs_wave(fmin,dic[cam]['LINE']['LINE']), dic[cam]['PSF'].x_vs_wave(fmax,dic[cam]['LINE']['LINE']) ) ymin = min( dic[cam]['PSF'].y_vs_wave(fmin,dic[cam]['LINE']['LINE']), dic[cam]['PSF'].y_vs_wave(fmax,dic[cam]['LINE']['LINE']) ) ymax = max( dic[cam]['PSF'].y_vs_wave(fmin,dic[cam]['LINE']['LINE']), dic[cam]['PSF'].y_vs_wave(fmax,dic[cam]['LINE']['LINE']) ) ax[2*i].imshow(td,interpolation='nearest',origin='lower',cmap='hot') ax[2*i].set_xlim(sp.floor(xmin-offset),sp.floor(xmax+offset)) ax[2*i].set_ylim(sp.floor(ymin-offset),sp.floor(ymax+offset)) ax[2*i].set_ylabel(r'$\mathrm{y-axis}$') x = sp.array([ dic[cam]['PSF'].x_vs_wave(f,dic[cam]['LINE']['LINE']) for f in range(fmin-1,fmax+2) ]) y = sp.array([ dic[cam]['PSF'].y_vs_wave(f,dic[cam]['LINE']['LINE']) for f in range(fmin-1,fmax+2) ]) fxy = sp.interpolate.interp1d(x,y) ax[2*i].plot(x,y,color='white',linestyle='--',linewidth=1)
def preproc(rawimage, header, primary_header, bias=True, dark=True, pixflat=True, mask=True, bkgsub=False, nocosmic=False, cosmics_nsig=6, cosmics_cfudge=3., cosmics_c2fudge=0.5, ccd_calibration_filename=None, nocrosstalk=False, nogain=False, overscan_per_row=False, use_overscan_row=False, use_savgol=None, nodarktrail=False, remove_scattered_light=False, psf_filename=None, bias_img=None): ''' preprocess image using metadata in header image = ((rawimage-bias-overscan)*gain)/pixflat Args: rawimage : 2D numpy array directly from raw data file header : dict-like metadata, e.g. from FITS header, with keywords CAMERA, BIASSECx, DATASECx, CCDSECx where x = A, B, C, D for each of the 4 amplifiers (also supports old naming convention 1, 2, 3, 4). primary_header: dict-like metadata fit keywords EXPTIME, DOSVER DATE-OBS is also required if bias, pixflat, or mask=True Optional bias, pixflat, and mask can each be: False: don't apply that step True: use default calibration data for that night ndarray: use that array filename (str or unicode): read HDU 0 and use that Optional overscan features: overscan_per_row : bool, Subtract the overscan_col values row by row from the data. use_overscan_row : bool, Subtract off the overscan_row from the data (default: False). Requires ORSEC in the Header use_savgol : bool, Specify whether to use Savitsky-Golay filter for the overscan. (default: False). Requires use_overscan_row=True to have any effect. Optional background subtraction with median filtering if bkgsub=True Optional disabling of cosmic ray rejection if nocosmic=True Optional disabling of dark trail correction if nodarktrail=True Optional bias image (testing only) may be provided by bias_img= Optional tuning of cosmic ray rejection parameters: cosmics_nsig: number of sigma above background required cosmics_cfudge: number of sigma inconsistent with PSF required cosmics_c2fudge: fudge factor applied to PSF Optional fit and subtraction of scattered light Returns Image object with member variables: pix : 2D preprocessed image in units of electrons per pixel ivar : 2D inverse variance of image mask : 2D mask of image (0=good) readnoise : 2D per-pixel readnoise of image meta : metadata dictionary TODO: define what keywords are included preprocessing includes the following steps: - bias image subtraction - overscan subtraction (from BIASSEC* keyword defined regions) - readnoise estimation (from BIASSEC* keyword defined regions) - gain correction (from GAIN* keywords) - pixel flat correction - cosmic ray masking - propagation of input known bad pixel mask - inverse variance estimation Notes: The bias image is subtracted before any other calculation to remove any non-uniformities in the overscan regions prior to calculating overscan levels and readnoise. The readnoise is an image not just one number per amp, because the pixflat image also affects the interpreted readnoise. The inverse variance is estimated from the readnoise and the image itself, and thus is biased. ''' log = get_logger() header = header.copy() cfinder = None if ccd_calibration_filename is not False: cfinder = CalibFinder([header, primary_header], yaml_file=ccd_calibration_filename) #- TODO: Check for required keywords first #- Subtract bias image camera = header['CAMERA'].lower() #- convert rawimage to float64 : this is the output format of read_image rawimage = rawimage.astype(np.float64) # Savgol if cfinder and cfinder.haskey("USE_ORSEC"): use_overscan_row = cfinder.value("USE_ORSEC") if cfinder and cfinder.haskey("SAVGOL"): use_savgol = cfinder.value("SAVGOL") # Set bias image, as desired if bias_img is None: bias = get_calibration_image(cfinder, "BIAS", bias) else: bias = bias_img if bias is not False: #- it's an array if bias.shape == rawimage.shape: log.info("subtracting bias") rawimage = rawimage - bias else: raise ValueError('shape mismatch bias {} != rawimage {}'.format( bias.shape, rawimage.shape)) #- Check if this file uses amp names 1,2,3,4 (old) or A,B,C,D (new) amp_ids = get_amp_ids(header) #- Double check that we have the necessary keywords missing_keywords = list() for prefix in ['CCDSEC', 'BIASSEC']: for amp in amp_ids: key = prefix + amp if not key in header: log.error('No {} keyword in header'.format(key)) missing_keywords.append(key) if len(missing_keywords) > 0: raise KeyError("Missing keywords {}".format( ' '.join(missing_keywords))) #- Output arrays ny = 0 nx = 0 for amp in amp_ids: yy, xx = parse_sec_keyword(header['CCDSEC%s' % amp]) ny = max(ny, yy.stop) nx = max(nx, xx.stop) image = np.zeros((ny, nx)) readnoise = np.zeros_like(image) #- Load mask mask = get_calibration_image(cfinder, "MASK", mask) if mask is False: mask = np.zeros(image.shape, dtype=np.int32) else: if mask.shape != image.shape: raise ValueError('shape mismatch mask {} != image {}'.format( mask.shape, image.shape)) #- Load dark dark = get_calibration_image(cfinder, "DARK", dark) if dark is not False: if dark.shape != image.shape: log.error('shape mismatch dark {} != image {}'.format( dark.shape, image.shape)) raise ValueError('shape mismatch dark {} != image {}'.format( dark.shape, image.shape)) if cfinder and cfinder.haskey("EXPTIMEKEY"): exptime_key = cfinder.value("EXPTIMEKEY") log.info("Using exposure time keyword %s for dark normalization" % exptime_key) else: exptime_key = "EXPTIME" exptime = primary_header[exptime_key] log.info("Multiplying dark by exptime %f" % (exptime)) dark *= exptime for amp in amp_ids: # Grab the sections ov_col = parse_sec_keyword(header['BIASSEC' + amp]) if 'ORSEC' + amp in header.keys(): ov_row = parse_sec_keyword(header['ORSEC' + amp]) elif use_overscan_row: log.error('No ORSEC{} keyword; not using overscan_row'.format(amp)) use_overscan_row = False if nogain: gain = 1. else: #- Initial teststand data may be missing GAIN* keywords; don't crash if 'GAIN' + amp in header: gain = header['GAIN' + amp] #- gain = electrons / ADU else: if cfinder and cfinder.haskey('GAIN' + amp): gain = float(cfinder.value('GAIN' + amp)) log.info('Using GAIN{}={} from calibration data'.format( amp, gain)) else: gain = 1.0 log.warning( 'Missing keyword GAIN{} in header and nothing in calib data; using {}' .format(amp, gain)) #- Add saturation level if 'SATURLEV' + amp in header: saturlev = header['SATURLEV' + amp] # in electrons else: if cfinder and cfinder.haskey('SATURLEV' + amp): saturlev = float(cfinder.value('SATURLEV' + amp)) log.info('Using SATURLEV{}={} from calibration data'.format( amp, saturlev)) else: saturlev = 200000 log.warning( 'Missing keyword SATURLEV{} in header and nothing in calib data; using 200000' .format(amp, saturlev)) # Generate the overscan images raw_overscan_col = rawimage[ov_col].copy() if use_overscan_row: raw_overscan_row = rawimage[ov_row].copy() overscan_row = np.zeros_like(raw_overscan_row) # Remove overscan_col from overscan_row raw_overscan_squared = rawimage[ov_row[0], ov_col[1]].copy() for row in range(raw_overscan_row.shape[0]): o, r = _overscan(raw_overscan_squared[row]) overscan_row[row] = raw_overscan_row[row] - o # Now remove the overscan_col nrows = raw_overscan_col.shape[0] log.info("nrows in overscan=%d" % nrows) overscan_col = np.zeros(nrows) rdnoise = np.zeros(nrows) if (cfinder and cfinder.haskey('OVERSCAN' + amp) and cfinder.value("OVERSCAN" + amp).upper() == "PER_ROW") or overscan_per_row: log.info( "Subtracting overscan per row for amplifier %s of camera %s" % (amp, camera)) for j in range(nrows): if np.isnan(np.sum(overscan_col[j])): log.warning( "NaN values in row %d of overscan of amplifier %s of camera %s" % (j, amp, camera)) continue o, r = _overscan(raw_overscan_col[j]) #log.info("%d %f %f"%(j,o,r)) overscan_col[j] = o rdnoise[j] = r else: log.info( "Subtracting average overscan for amplifier %s of camera %s" % (amp, camera)) o, r = _overscan(raw_overscan_col) overscan_col += o rdnoise += r rdnoise *= gain median_rdnoise = np.median(rdnoise) median_overscan = np.median(overscan_col) log.info("Median rdnoise and overscan= %f %f" % (median_rdnoise, median_overscan)) kk = parse_sec_keyword(header['CCDSEC' + amp]) for j in range(nrows): readnoise[kk][j] = rdnoise[j] header['OVERSCN' + amp] = (median_overscan, 'ADUs (gain not applied)') if gain != 1: rdnoise_message = 'electrons (gain is applied)' gain_message = 'e/ADU (gain applied to image)' else: rdnoise_message = 'ADUs (gain not applied)' gain_message = 'gain not applied to image' header['OBSRDN' + amp] = (median_rdnoise, rdnoise_message) header['GAIN' + amp] = (gain, gain_message) #- Warn/error if measured readnoise is very different from expected if exists if 'RDNOISE' + amp in header: expected_readnoise = header['RDNOISE' + amp] if median_rdnoise < 0.5 * expected_readnoise: log.error( 'Amp {} measured readnoise {:.2f} < 0.5 * expected readnoise {:.2f}' .format(amp, median_rdnoise, expected_readnoise)) elif median_rdnoise < 0.9 * expected_readnoise: log.warning( 'Amp {} measured readnoise {:.2f} < 0.9 * expected readnoise {:.2f}' .format(amp, median_rdnoise, expected_readnoise)) elif median_rdnoise > 2.0 * expected_readnoise: log.error( 'Amp {} measured readnoise {:.2f} > 2 * expected readnoise {:.2f}' .format(amp, median_rdnoise, expected_readnoise)) elif median_rdnoise > 1.2 * expected_readnoise: log.warning( 'Amp {} measured readnoise {:.2f} > 1.2 * expected readnoise {:.2f}' .format(amp, median_rdnoise, expected_readnoise)) #else: # log.warning('Expected readnoise keyword {} missing'.format('RDNOISE'+amp)) log.info("Measured readnoise for AMP %s = %f" % (amp, median_rdnoise)) #- subtract overscan from data region and apply gain jj = parse_sec_keyword(header['DATASEC' + amp]) data = rawimage[jj].copy() # Subtract columns for k in range(nrows): data[k] -= overscan_col[k] # And now the rows if use_overscan_row: # Savgol? if use_savgol: log.info("Using savgol") collapse_oscan_row = np.zeros(overscan_row.shape[1]) for col in range(overscan_row.shape[1]): o, _ = _overscan(overscan_row[:, col]) collapse_oscan_row[col] = o oscan_row = _savgol_clipped(collapse_oscan_row, niter=0) oimg_row = np.outer(np.ones(data.shape[0]), oscan_row) data -= oimg_row else: o, r = _overscan(overscan_row) data -= o #- apply saturlev (defined in ADU), prior to multiplication by gain saturated = (rawimage[jj] >= saturlev) mask[kk][saturated] |= ccdmask.SATURATED #- ADC to electrons image[kk] = data * gain if not nocrosstalk: #- apply cross-talk # the ccd looks like : # C D # A B # for cross talk, we need a symmetric 4x4 flip_matrix # of coordinates ABCD giving flip of both axis # when computing crosstalk of # A B C D # # A AA AB AC AD # B BA BB BC BD # C CA CB CC CD # D DA DB DC BB # orientation_matrix_defines change of orientation # fip_axis_0 = np.array([[1, 1, -1, -1], [1, 1, -1, -1], [-1, -1, 1, 1], [-1, -1, 1, 1]]) fip_axis_1 = np.array([[1, -1, 1, -1], [-1, 1, -1, 1], [1, -1, 1, -1], [-1, 1, -1, 1]]) for a1 in range(len(amp_ids)): amp1 = amp_ids[a1] ii1 = parse_sec_keyword(header['CCDSEC' + amp1]) a1flux = image[ii1] #a1mask=mask[ii1] for a2 in range(len(amp_ids)): if a1 == a2: continue amp2 = amp_ids[a2] if cfinder is None: continue if not cfinder.haskey("CROSSTALK%s%s" % (amp1, amp2)): continue crosstalk = cfinder.value("CROSSTALK%s%s" % (amp1, amp2)) if crosstalk == 0.: continue log.info("Correct for crosstalk=%f from AMP %s into %s" % (crosstalk, amp1, amp2)) a12flux = crosstalk * a1flux.copy() #a12mask=a1mask.copy() if fip_axis_0[a1, a2] == -1: a12flux = a12flux[::-1] #a12mask=a12mask[::-1] if fip_axis_1[a1, a2] == -1: a12flux = a12flux[:, ::-1] #a12mask=a12mask[:,::-1] ii2 = parse_sec_keyword(header['CCDSEC' + amp2]) image[ii2] -= a12flux # mask[ii2] |= a12mask (not sure we really need to propagate the mask) #- Poisson noise variance (prior to dark subtraction and prior to pixel flat field) #- This is biasing, but that's what we have for now poisson_var = image.clip(0) #- subtract dark after multiplication by gain if dark is not False: log.info("subtracting dark for amp %s" % amp) image -= dark #- Correct for dark trails if any if not nodarktrail and cfinder is not None: for amp in amp_ids: if cfinder.haskey("DARKTRAILAMP%s" % amp): amplitude = cfinder.value("DARKTRAILAMP%s" % amp) width = cfinder.value("DARKTRAILWIDTH%s" % amp) ii = _parse_sec_keyword(header["CCDSEC" + amp]) log.info( "Removing dark trails for amplifier %s with width=%3.1f and amplitude=%5.4f" % (amp, width, amplitude)) correct_dark_trail(image, ii, left=((amp == "B") | (amp == "D")), width=width, amplitude=amplitude) #- Divide by pixflat image pixflat = get_calibration_image(cfinder, "PIXFLAT", pixflat) if pixflat is not False: if pixflat.shape != image.shape: raise ValueError('shape mismatch pixflat {} != image {}'.format( pixflat.shape, image.shape)) almost_zero = 0.001 if np.all(pixflat > almost_zero): image /= pixflat readnoise /= pixflat poisson_var /= pixflat**2 else: good = (pixflat > almost_zero) image[good] /= pixflat[good] readnoise[good] /= pixflat[good] poisson_var[good] /= pixflat[good]**2 mask[~good] |= ccdmask.PIXFLATZERO lowpixflat = (0 < pixflat) & (pixflat < 0.1) if np.any(lowpixflat): mask[lowpixflat] |= ccdmask.PIXFLATLOW #- Inverse variance, estimated directly from the data (BEWARE: biased!) var = poisson_var + readnoise**2 ivar = np.zeros(var.shape) ivar[var > 0] = 1.0 / var[var > 0] #- Ridiculously high readnoise is bad mask[readnoise > 100] |= ccdmask.BADREADNOISE if bkgsub: bkg = _background(image, header) image -= bkg img = Image(image, ivar=ivar, mask=mask, meta=header, readnoise=readnoise, camera=camera) #- update img.mask to mask cosmic rays if not nocosmic: cosmics.reject_cosmic_rays(img, nsig=cosmics_nsig, cfudge=cosmics_cfudge, c2fudge=cosmics_c2fudge) if remove_scattered_light: if psf_filename is None: psf_filename = cfinder.findfile("PSF") xyset = read_xytraceset(psf_filename) img.pix -= model_scattered_light(img, xyset) return img
def main(args=None): if args is None: args = parse() elif isinstance(args, (list, tuple)): args = parse(args) t0 = time.time() log = get_logger() # guess if it is a preprocessed or a raw image hdulist = fits.open(args.image) is_input_preprocessed = ("IMAGE" in hdulist) & ("IVAR" in hdulist) primary_header = hdulist[0].header hdulist.close() if is_input_preprocessed: image = read_image(args.image) else: if args.camera is None: print( "ERROR: Need to specify camera to open a raw fits image (with all cameras in different fits HDUs)" ) print( "Try adding the option '--camera xx', with xx in {brz}{0-9}, like r7, or type 'desi_qproc --help' for more options" ) sys.exit(12) image = read_raw(args.image, args.camera, fill_header=[ 1, ]) if args.auto: log.debug("AUTOMATIC MODE") try: night = image.meta['NIGHT'] if not 'EXPID' in image.meta: if 'EXPNUM' in image.meta: log.warning('using EXPNUM {} for EXPID'.format( image.meta['EXPNUM'])) image.meta['EXPID'] = image.meta['EXPNUM'] expid = image.meta['EXPID'] except KeyError as e: log.error( "Need at least NIGHT and EXPID (or EXPNUM) to run in auto mode. Retry without the --auto option." ) log.error(str(e)) sys.exit(12) indir = os.path.dirname(args.image) if args.fibermap is None: filename = '{}/fibermap-{:08d}.fits'.format(indir, expid) if os.path.isfile(filename): log.debug("auto-mode: found a fibermap, {}, using it!".format( filename)) args.fibermap = filename if args.output_preproc is None: if not is_input_preprocessed: args.output_preproc = '{}/preproc-{}-{:08d}.fits'.format( args.auto_output_dir, args.camera.lower(), expid) log.debug("auto-mode: will write preproc in " + args.output_preproc) else: log.debug( "auto-mode: will not write preproc because input is a preprocessed image" ) if args.auto_output_dir != '.': if not os.path.isdir(args.auto_output_dir): log.debug("auto-mode: creating directory " + args.auto_output_dir) os.makedirs(args.auto_output_dir) if args.output_preproc is not None: write_image(args.output_preproc, image) cfinder = None if args.psf is None: if cfinder is None: cfinder = CalibFinder([image.meta, primary_header]) args.psf = cfinder.findfile("PSF") log.info(" Using PSF {}".format(args.psf)) tset = read_xytraceset(args.psf) # add fibermap if args.fibermap: if os.path.isfile(args.fibermap): fibermap = read_fibermap(args.fibermap) else: log.error("no fibermap file {}".format(args.fibermap)) fibermap = None else: fibermap = None if "OBSTYPE" in image.meta: obstype = image.meta["OBSTYPE"].upper() image.meta["OBSTYPE"] = obstype # make sure it's upper case qframe = None else: log.warning("No OBSTYPE keyword, trying to guess ...") qframe = qproc_boxcar_extraction(tset, image, width=args.width, fibermap=fibermap) obstype = check_qframe_flavor( qframe, input_flavor=image.meta["FLAVOR"]).upper() image.meta["OBSTYPE"] = obstype log.info("OBSTYPE = '{}'".format(obstype)) if args.auto: # now set the things to do if obstype == "SKY" or obstype == "TWILIGHT" or obstype == "SCIENCE": args.shift_psf = True args.output_psf = '{}/psf-{}-{:08d}.fits'.format( args.auto_output_dir, args.camera, expid) args.output_rawframe = '{}/qframe-{}-{:08d}.fits'.format( args.auto_output_dir, args.camera, expid) args.apply_fiberflat = True args.skysub = True args.output_skyframe = '{}/qsky-{}-{:08d}.fits'.format( args.auto_output_dir, args.camera, expid) args.fluxcalib = True args.outframe = '{}/qcframe-{}-{:08d}.fits'.format( args.auto_output_dir, args.camera, expid) elif obstype == "ARC" or obstype == "TESTARC": args.shift_psf = True args.output_psf = '{}/psf-{}-{:08d}.fits'.format( args.auto_output_dir, args.camera, expid) args.output_rawframe = '{}/qframe-{}-{:08d}.fits'.format( args.auto_output_dir, args.camera, expid) args.compute_lsf_sigma = True elif obstype == "FLAT" or obstype == "TESTFLAT": args.shift_psf = True args.output_psf = '{}/psf-{}-{:08d}.fits'.format( args.auto_output_dir, args.camera, expid) args.output_rawframe = '{}/qframe-{}-{:08d}.fits'.format( args.auto_output_dir, args.camera, expid) args.compute_fiberflat = '{}/qfiberflat-{}-{:08d}.fits'.format( args.auto_output_dir, args.camera, expid) if args.shift_psf: # using the trace shift script if args.auto: options = option_list({ "psf": args.psf, "image": "dummy", "outpsf": "dummy", "continuum": ((obstype == "FLAT") | (obstype == "TESTFLAT")), "sky": ((obstype == "SCIENCE") | (obstype == "SKY")) }) else: options = option_list({ "psf": args.psf, "image": "dummy", "outpsf": "dummy" }) tmp_args = trace_shifts_script.parse(options=options) tset = trace_shifts_script.fit_trace_shifts(image=image, args=tmp_args) qframe = qproc_boxcar_extraction(tset, image, width=args.width, fibermap=fibermap) if tset.meta is not None: # add traceshift info in the qframe, this will be saved in the qframe header if qframe.meta is None: qframe.meta = dict() for k in tset.meta.keys(): qframe.meta[k] = tset.meta[k] if args.output_rawframe is not None: write_qframe(args.output_rawframe, qframe) log.info("wrote raw extracted frame in {}".format( args.output_rawframe)) if args.compute_lsf_sigma: tset = process_arc(qframe, tset, linelist=None, npoly=2, nbins=2) if args.output_psf is not None: for k in qframe.meta: if k not in tset.meta: tset.meta[k] = qframe.meta[k] write_xytraceset(args.output_psf, tset) if args.compute_fiberflat is not None: fiberflat = qproc_compute_fiberflat(qframe) #write_qframe(args.compute_fiberflat,qflat) write_fiberflat(args.compute_fiberflat, fiberflat, header=qframe.meta) log.info("wrote fiberflat in {}".format(args.compute_fiberflat)) if args.apply_fiberflat or args.input_fiberflat: if args.input_fiberflat is None: if cfinder is None: cfinder = CalibFinder([image.meta, primary_header]) try: args.input_fiberflat = cfinder.findfile("FIBERFLAT") except KeyError as e: log.error("no FIBERFLAT for this spectro config") sys.exit(12) log.info("applying fiber flat {}".format(args.input_fiberflat)) flat = read_fiberflat(args.input_fiberflat) qproc_apply_fiberflat(qframe, flat) if args.skysub: log.info("sky subtraction") if args.output_skyframe is not None: skyflux = qproc_sky_subtraction(qframe, return_skymodel=True) sqframe = QFrame(qframe.wave, skyflux, np.ones(skyflux.shape)) write_qframe(args.output_skyframe, sqframe) log.info("wrote sky model in {}".format(args.output_skyframe)) else: qproc_sky_subtraction(qframe) if args.fluxcalib: if cfinder is None: cfinder = CalibFinder([image.meta, primary_header]) # check for flux calib if cfinder.haskey("FLUXCALIB"): fluxcalib_filename = cfinder.findfile("FLUXCALIB") fluxcalib = read_average_flux_calibration(fluxcalib_filename) log.info("read average calib in {}".format(fluxcalib_filename)) seeing = qframe.meta["SEEING"] airmass = qframe.meta["AIRMASS"] exptime = qframe.meta["EXPTIME"] exposure_calib = fluxcalib.value(seeing=seeing, airmass=airmass) for q in range(qframe.nspec): fiber_calib = np.interp(qframe.wave[q], fluxcalib.wave, exposure_calib) * exptime inv_calib = (fiber_calib > 0) / (fiber_calib + (fiber_calib == 0)) qframe.flux[q] *= inv_calib qframe.ivar[q] *= fiber_calib**2 * (fiber_calib > 0) # add keyword in header giving the calibration factor applied at a reference wavelength band = qframe.meta["CAMERA"].upper()[0] if band == "B": refwave = 4500 elif band == "R": refwave = 6500 else: refwave = 8500 calvalue = np.interp(refwave, fluxcalib.wave, exposure_calib) * exptime qframe.meta["CALWAVE"] = refwave qframe.meta["CALVALUE"] = calvalue else: log.error( "Cannot calibrate fluxes because no FLUXCALIB keywork in calibration files" ) fibers = parse_fibers(args.fibers) if fibers is None: fibers = qframe.flux.shape[0] else: ii = np.arange(qframe.fibers.size)[np.in1d(qframe.fibers, fibers)] if ii.size == 0: log.error("no such fibers in frame,") log.error("fibers are in range [{}:{}]".format( qframe.fibers[0], qframe.fibers[-1] + 1)) sys.exit(12) qframe = qframe[ii] if args.outframe is not None: write_qframe(args.outframe, qframe) log.info("wrote {}".format(args.outframe)) t1 = time.time() log.info("all done in {:3.1f} sec".format(t1 - t0)) if args.plot: log.info("plotting {} spectra".format(qframe.wave.shape[0])) import matplotlib.pyplot as plt fig = plt.figure() for i in range(qframe.wave.shape[0]): j = (qframe.ivar[i] > 0) plt.plot(qframe.wave[i, j], qframe.flux[i, j]) plt.grid() plt.xlabel("wavelength") plt.ylabel("flux") plt.show()
def main(args) : global psfs log=get_logger() log.info("starting") # read preprocessed image image=read_image(args.image) log.info("read image {}".format(args.image)) if image.mask is not None : image.ivar *= (image.mask==0) xytraceset = read_xytraceset(args.psf) wavemin = xytraceset.wavemin wavemax = xytraceset.wavemax xcoef = xytraceset.x_vs_wave_traceset._coeff ycoef = xytraceset.y_vs_wave_traceset._coeff nfibers=xcoef.shape[0] log.info("read PSF trace with xcoef.shape = {} , ycoef.shape = {} , and wavelength range {}:{}".format(xcoef.shape,ycoef.shape,int(wavemin),int(wavemax))) lines=None if args.lines is not None : log.info("We will fit the image using the psf model and lines") # read lines lines=np.loadtxt(args.lines,usecols=[0]) ok=(lines>wavemin)&(lines<wavemax) log.info("read {} lines in {}, with {} of them in traces wavelength range".format(len(lines),args.lines,np.sum(ok))) lines=lines[ok] else : log.info("We will do an internal calibration of trace coordinates without using the psf shape in a first step") internal_wavelength_calib = (not args.continuum) external_wavelength_calib = args.sky | ( args.spectrum is not None ) if args.auto : log.debug("read flavor of input image {}".format(args.image)) hdus = pyfits.open(args.image) if "FLAVOR" not in hdus[0].header : log.error("no FLAVOR keyword in image header, cannot run with --auto option") raise KeyError("no FLAVOR keyword in image header, cannot run with --auto option") flavor = hdus[0].header["FLAVOR"].strip().lower() hdus.close() log.info("Input is a '{}' image".format(flavor)) if flavor == "flat" : internal_wavelength_calib = False external_wavelength_calib = False elif flavor == "arc" : internal_wavelength_calib = True external_wavelength_calib = False else : internal_wavelength_calib = True external_wavelength_calib = True log.info("wavelength calib, internal={}, external={}".format(internal_wavelength_calib,external_wavelength_calib)) spectrum_filename = args.spectrum if external_wavelength_calib and spectrum_filename is None : srch_file = "data/spec-sky.dat" if not resource_exists('desispec', srch_file): log.error("Cannot find sky spectrum file {:s}".format(srch_file)) raise RuntimeError("Cannot find sky spectrum file {:s}".format(srch_file)) else : spectrum_filename=resource_filename('desispec', srch_file) log.info("Use external calibration from cross-correlation with {}".format(spectrum_filename)) if args.nfibers is not None : nfibers = args.nfibers # FOR DEBUGGING fibers=np.arange(nfibers) if lines is not None : # use a forward modeling of the image # it's slower and works only for individual lines # it's in principle more accurate # but gives systematic residuals for complex spectra like the sky psf = read_specter_psf(args.psf) x,y,dx,ex,dy,ey,fiber_xy,wave_xy=compute_dx_dy_using_psf(psf,image,fibers,lines) x_for_dx=x y_for_dx=y fiber_for_dx=fiber_xy wave_for_dx=wave_xy x_for_dy=x y_for_dy=y fiber_for_dy=fiber_xy wave_for_dy=wave_xy else : # internal calibration method that does not use the psf # nor a prior set of lines. this method is much faster # measure x shifts 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(xytraceset, image=image, fibers=fibers, width=args.width) mdy = np.median(dy) log.info("Subtract median(dy)={}".format(mdy)) dy -= mdy # remove median, because this is an internal calibration else : # duplicate dx results with zero shift to avoid write special case code below x_for_dy = x_for_dx.copy() y_for_dy = y_for_dx.copy() dy = np.zeros(dx.shape) ey = 1.e-6*np.ones(ex.shape) fiber_for_dy = fiber_for_dx.copy() wave_for_dy = wave_for_dx.copy() degxx=args.degxx degxy=args.degxy degyx=args.degyx degyy=args.degyy while(True) : # loop because polynomial degrees could be reduced log.info("polynomial fit of measured offsets with degx=(%d,%d) degy=(%d,%d)"%(degxx,degxy,degyx,degyy)) try : dx_coeff,dx_coeff_covariance,dx_errorfloor,dx_mod,dx_mask=polynomial_fit(z=dx,ez=ex,xx=x_for_dx,yy=y_for_dx,degx=degxx,degy=degxy) dy_coeff,dy_coeff_covariance,dy_errorfloor,dy_mod,dy_mask=polynomial_fit(z=dy,ez=ey,xx=x_for_dy,yy=y_for_dy,degx=degyx,degy=degyy) log.info("dx dy error floor = %4.3f %4.3f pixels"%(dx_errorfloor,dy_errorfloor)) log.info("check fit uncertainties are ok on edge of CCD") merr=0. for fiber in [0,nfibers-1] : for rw in [-1,1] : tx = legval(rw,xcoef[fiber]) ty = legval(rw,ycoef[fiber]) m=monomials(tx,ty,degxx,degxy) tdx=np.inner(dx_coeff,m) tsx=np.sqrt(np.inner(m,dx_coeff_covariance.dot(m))) m=monomials(tx,ty,degyx,degyy) tdy=np.inner(dy_coeff,m) tsy=np.sqrt(np.inner(m,dy_coeff_covariance.dot(m))) merr=max(merr,tsx) merr=max(merr,tsy) log.info("max edge shift error = %4.3f pixels"%merr) if degxx==0 and degxy==0 and degyx==0 and degyy==0 : break except ( LinAlgError , ValueError ) : log.warning("polynomial fit failed with degx=(%d,%d) degy=(%d,%d)"%(degxx,degxy,degyx,degyy)) if degxx==0 and degxy==0 and degyx==0 and degyy==0 : log.error("polynomial degrees are already 0. we can fit the offsets") raise RuntimeError("polynomial degrees are already 0. we can fit the offsets") merr = 100000. # this will lower the pol. degree. if merr > args.max_error : if merr != 100000. : log.warning("max edge shift error = %4.3f pixels is too large, reducing degrees"%merr) if degxy>0 and degyy>0 and degxy>degxx and degyy>degyx : # first along wavelength if degxy>0 : degxy-=1 if degyy>0 : degyy-=1 else : # then along fiber if degxx>0 : degxx-=1 if degyx>0 : degyx-=1 else : # error is ok, so we quit the loop break # write this for debugging if args.outoffsets : file=open(args.outoffsets,"w") file.write("# axis wave fiber x y delta error polval (axis 0=y axis1=x)\n") for e in range(dy.size) : file.write("0 %f %d %f %f %f %f %f\n"%(wave_for_dy[e],fiber_for_dy[e],x_for_dy[e],y_for_dy[e],dy[e],ey[e],dy_mod[e])) for e in range(dx.size) : file.write("1 %f %d %f %f %f %f %f\n"%(wave_for_dx[e],fiber_for_dx[e],x_for_dx[e],y_for_dx[e],dx[e],ex[e],dx_mod[e])) file.close() log.info("wrote offsets in ASCII file %s"%args.outoffsets) # print central shift mx=np.median(x_for_dx) my=np.median(y_for_dx) m=monomials(mx,my,degxx,degxy) mdx=np.inner(dx_coeff,m) mex=np.sqrt(np.inner(m,dx_coeff_covariance.dot(m))) mx=np.median(x_for_dy) my=np.median(y_for_dy) m=monomials(mx,my,degyx,degyy) mdy=np.inner(dy_coeff,m) mey=np.sqrt(np.inner(m,dy_coeff_covariance.dot(m))) log.info("central shifts dx = %4.3f +- %4.3f dy = %4.3f +- %4.3f "%(mdx,mex,mdy,mey)) # for each fiber, apply offsets and recompute legendre polynomial log.info("for each fiber, apply offsets and recompute legendre polynomial") # compute x y to record max deviations u = np.linspace(-1,1,5) x0 = np.zeros((xcoef.shape[0],u.size)) y0 = np.zeros((ycoef.shape[0],u.size)) for f in range(xcoef.shape[0]) : x0[f]=legval(u,xcoef[f]) y0[f]=legval(u,ycoef[f]) xcoef,ycoef = recompute_legendre_coefficients(xcoef=xcoef,ycoef=ycoef,wavemin=wavemin,wavemax=wavemax,degxx=degxx,degxy=degxy,degyx=degyx,degyy=degyy,dx_coeff=dx_coeff,dy_coeff=dy_coeff) # use an input spectrum as an external calibration of wavelength if spectrum_filename : log.info("write and reread PSF to be sure predetermined shifts were propagated") write_traces_in_psf(args.psf,args.outpsf,xcoef,ycoef,wavemin,wavemax) #psf,xcoef,ycoef,wavemin,wavemax = read_psf_and_traces(args.outpsf) xytraceset = read_xytraceset(args.outpsf) wavemin = xytraceset.wavemin wavemax = xytraceset.wavemax xcoef = xytraceset.x_vs_wave_traceset._coeff ycoef = xytraceset.y_vs_wave_traceset._coeff psf = read_specter_psf(args.outpsf) ycoef=shift_ycoef_using_external_spectrum(psf=psf,xytraceset=xytraceset, image=image,fibers=fibers,spectrum_filename=spectrum_filename,degyy=args.degyy,width=7) x = np.zeros((xcoef.shape[0],u.size)) y = np.zeros((ycoef.shape[0],u.size)) for f in range(xcoef.shape[0]) : x[f]=legval(u,xcoef[f]) y[f]=legval(u,ycoef[f]) dx = x-x0 dy = y-y0 header_keywords = {} header_keywords["MEANDX"]=np.mean(dx) header_keywords["MINDX"]=np.min(dx) header_keywords["MAXDX"]=np.max(dx) header_keywords["MEANDY"]=np.mean(dy) header_keywords["MINDY"]=np.min(dy) header_keywords["MAXDY"]=np.max(dy) write_traces_in_psf(args.psf,args.outpsf,xcoef,ycoef,wavemin,wavemax,header_keywords=header_keywords) log.info("wrote modified PSF in %s"%args.outpsf) else : x = np.zeros((xcoef.shape[0],u.size)) y = np.zeros((ycoef.shape[0],u.size)) for f in range(xcoef.shape[0]) : x[f]=legval(u,xcoef[f]) y[f]=legval(u,ycoef[f]) dx = x-x0 dy = y-y0 header_keywords = {} header_keywords["MEANDX"]=np.mean(dx) header_keywords["MINDX"]=np.min(dx) header_keywords["MAXDX"]=np.max(dx) header_keywords["MEANDY"]=np.mean(dy) header_keywords["MINDY"]=np.min(dy) header_keywords["MAXDY"]=np.max(dy) write_traces_in_psf(args.psf,args.outpsf,xcoef,ycoef,wavemin,wavemax,header_keywords=header_keywords) log.info("wrote modified PSF in %s"%args.outpsf)