def apply_flat(scifilename, maskname, band): ''' Divides the contents of scifilename by the flat field and overwrites scifilename with the same file divided by the flat Args: scifilename: Path to science file name. maskname: The mask name band: The filter bands Results: Overwrites scifilename where the data contents of the file are divided by the pixel flat ''' flat = IO.load_flat(maskname, band, {}) flat_data = flat[1].filled(1.0) header, data = IO.readfits(scifilename) print("Applying flat to file {0}".format(scifilename)) IO.writefits(data / flat_data, maskname, scifilename, {}, header=header, overwrite=True)
def imcombine(filelist, maskname, fname, options, sum_type): """ combine the images in file list into fname. Sum type: rate -- filelist is in cnt/s ivar-rate -- filelist is in s/cnt snr-rate -- filelist is in SNR """ ARR = None hdr = None i = 1 itime = 0 for file in filelist: this_hdr, img = IO.readfits(file) cards = this_hdr.ascardlist() thisitime = this_hdr["truitime"] itime += thisitime if ARR is None: ARR = np.zeros(img.shape) if sum_type == "rate": ARR += img * thisitime if sum_type == "ivar-rate": ARR += thisitime / img if sum_type == "snr-rate": ARR += img * thisitime if hdr is None: hdr = this_hdr hdr.update("fno%2.2i" % i, file, "--") for card in cards: key, value, comment = (card.key, card.value, card.comment) if hdr.has_key(key) and hdr[key] != value: key = key + ("_img%2.2i" % i) if len(key) > 8: key = "HIERARCH " + key try: hdr.update(key, value, comment) except ValueError: pass hdr.update("itime", itime, "Itime for %i rectified images" % len(filelist)) if sum_type == "rate": ARR /= itime if sum_type == "ivar-rate": ARR = itime / ARR if sum_type == "snr-rate": ARR /= itime IO.writefits(ARR, maskname, fname, options, header=hdr, overwrite=True, lossy_compress=True)
def imcombine(filelist, maskname, fname, options, sum_type): ''' combine the images in file list into fname. Sum type: rate -- filelist is in cnt/s ivar-rate -- filelist is in s/cnt snr-rate -- filelist is in SNR ''' ARR = None hdr = None i = 1 itime = 0 for file in filelist: this_hdr, img = IO.readfits(file) cards = this_hdr.ascardlist() thisitime = this_hdr['truitime'] itime += thisitime if ARR is None: ARR = np.zeros(img.shape) if sum_type == 'rate': ARR += img * thisitime if sum_type == 'ivar-rate': ARR += thisitime / img if sum_type == 'snr-rate': ARR += img * thisitime if hdr is None: hdr = this_hdr hdr["fno%2.2i" % i] = (file, "--") for card in cards: key, value, comment = (card.key, card.value, card.comment) if hdr.has_key(key) and hdr[key] != value: key = key + ("_img%2.2i" % i) if len(key) > 8: key = 'HIERARCH ' + key try: hdr[key] = (value, comment) except ValueError: pass hdr['itime'] = (itime, 'Itime for %i rectified images' % len(filelist)) if sum_type == 'rate': ARR /= itime if sum_type == 'ivar-rate': ARR = itime / ARR if sum_type == 'snr-rate': ARR /= itime IO.writefits(ARR, maskname, fname, options, header=hdr, overwrite=True, lossy_compress=True)
def rectify(dname, lamdat, A, B, maskname, band, wavoptions, longoptions): header, data = IO.readfits(dname) raw_img = data * Detector.gain / header['TRUITIME'] dlam = Wavelength.grating_results(band) hpp = np.array(Filters.hpp[band]) ll_fid = np.arange(hpp[0], hpp[1], dlam) rectified = np.zeros((2048, len(ll_fid))) from scipy.interpolate import interp1d for i in xrange(2048): ll = lamdat[i, :] ss = raw_img[i, :] ok = np.isfinite(ll) & np.isfinite(ss) & (ll < hpp[1]) & (ll > hpp[0]) if len(np.where(ok)[0]) < 30: continue f = interp1d(ll[ok], ss[ok], bounds_error=False) rectified[i, :] = f(ll_fid) header.update("wat0_001", "system=world") header.update("wat1_001", "wtype=linear") header.update("wat2_001", "wtype=linear") header.update("dispaxis", 1) header.update("dclog1", "Transform") header.update("dc-flag", 0) header.update("ctype1", "AWAV") header.update("cunit1", "Angstrom") header.update("crval1", ll_fid[0]) header.update("crval2", 0) header.update("crpix1", 1) header.update("crpix2", 1) header.update("cdelt1", 1) header.update("cdelt2", 1) header.update("cname1", "angstrom") header.update("cname2", "pixel") header.update("cd1_1", dlam) header.update("cd1_2", 0) header.update("cd2_1", 0) header.update("cd2_2", 1) header.update("object", "rectified [eps]") IO.writefits(rectified, maskname, "rectified_%s" % (dname), wavoptions, header=header, overwrite=True, lossy_compress=True)
def rectify(dname, lamdat, A, B, maskname, band, wavoptions, longoptions): header, data = IO.readfits(dname) raw_img = data * Detector.gain / header['TRUITIME'] dlam = Wavelength.grating_results(band) hpp = np.array(Filters.hpp[band]) ll_fid = np.arange(hpp[0], hpp[1], dlam) rectified = np.zeros((2048, len(ll_fid))) from scipy.interpolate import interp1d for i in xrange(2048): ll = lamdat[i,:] ss = raw_img[i,:] ok = np.isfinite(ll) & np.isfinite(ss) & (ll < hpp[1]) & (ll > hpp[0]) if len(np.where(ok)[0]) < 30: continue f = interp1d(ll[ok], ss[ok], bounds_error=False) rectified[i,:] = f(ll_fid) header.update("wat0_001", "system=world") header.update("wat1_001", "wtype=linear") header.update("wat2_001", "wtype=linear") header.update("dispaxis", 1) header.update("dclog1", "Transform") header.update("dc-flag", 0) header.update("ctype1", "AWAV") header.update("cunit1", "Angstrom") header.update("crval1", ll_fid[0]) header.update("crval2", 0) header.update("crpix1", 1) header.update("crpix2", 1) header.update("cdelt1", 1) header.update("cdelt2", 1) header.update("cname1", "angstrom") header.update("cname2", "pixel") header.update("cd1_1", dlam) header.update("cd1_2", 0) header.update("cd2_1", 0) header.update("cd2_2", 1) header.update("object", "rectified [eps]") IO.writefits(rectified, maskname, "rectified_%s" % (dname), wavoptions, header=header, overwrite=True, lossy_compress=True)
def apply_flat(scifilename, maskname, band): ''' Divides the contents of scifilename by the flat field and overwrites scifilename with the same file divided by the flat Args: scifilename: Path to science file name. maskname: The mask name band: The filter bands Results: Overwrites scifilename where the data contents of the file are divided by the pixel flat ''' flat = IO.load_flat(maskname, band, {}) flat_data = flat[1].filled(1.0) header, data = IO.readfits(scifilename) print("Applying flat to file {0}".format(scifilename)) IO.writefits(data/flat_data, maskname, scifilename, {}, header=header, overwrite=True)
def imcombine(files, maskname, options, flat, outname=None, shifts=None, extension=None): ''' From a list of files it imcombine returns the imcombine of several values. The imcombine code also estimates the readnoise ad RN/sqrt(numreads) so that the variance per frame is equal to (ADU + RN^2) where RN is computed in ADUs. Arguments: files[]: list of full path to files to combine maskname: Name of mask options: Options dictionary flat[2048x2048]: Flat field (values should all be ~ 1.0) outname: If set, will write (see notes below for details) eps_[outname].fits: electron/sec file itimes_[outname].fits: integration time var_[outname].fits: Variance files shifts[len(files)]: If set, will "roll" each file by the amount in the shifts vector in pixels. This argument is used when telescope tracking is poor. If you need to use this, please notify Keck staff about poor telescope tracking. Returns 6-element tuple: header: The combined header electrons [2048x2048]: e- (in e- units) var [2048x2048]: electrons + RN**2 (in e-^2 units) bs: The MOSFIRE.Barset instance itimes [2048x2048]: itimes (in s units) Nframe: The number of frames that contribute to the summed arrays above. If Nframe > 5 I use the sigma-clipping Cosmic Ray Rejection tool. If Nframe < 5 then I drop the max/min elements. Notes: header -- fits header ADUs -- The mean # of ADUs per frame var -- the Variance [in adu] per frame. bs -- Barset itimes -- The _total_ integration time in second Nframe -- The number of frames in a stack. Thus the number of electron per second is derived as: e-/sec = (ADUs * Gain / Flat) * (Nframe/itimes) The total number of electrons is: el = ADUs * Gain * Nframe ''' ADUs = np.zeros((len(files), 2048, 2048)) itimes = np.zeros((len(files), 2048, 2048)) prevssl = None prevmn = None patternid = None maskname = None header = None if shifts is None: shifts = np.zeros(len(files)) warnings.filterwarnings('ignore') for i in xrange(len(files)): fname = files[i] thishdr, data, bs = IO.readmosfits(fname, options, extension=extension) itimes[i,:,:] = thishdr["truitime"] base = os.path.basename(fname).rstrip(".fits") fnum = int(base.split("_")[1]) if shifts[i] == 0: ADUs[i,:,:] = data.filled(0.0) / flat else: ADUs[i,:,:] = np.roll(data.filled(0.0) / flat, np.int(shifts[i]), axis=0) ''' Construct Header''' if header is None: header = thishdr header["imfno%3.3i" % (fnum)] = (fname, "img%3.3i file name" % fnum) map(lambda x: rem_header_key(header, x), ["CTYPE1", "CTYPE2", "WCSDIM", "CD1_1", "CD1_2", "CD2_1", "CD2_2", "LTM1_1", "LTM2_2", "WAT0_001", "WAT1_001", "WAT2_001", "CRVAL1", "CRVAL2", "CRPIX1", "CRPIX2", "RADECSYS"]) for card in header.cards: if card == '': continue key,val,comment = card if key in thishdr: if val != thishdr[key]: newkey = key + ("_img%2.2i" % fnum) try: header[newkey.rstrip()] = (thishdr[key], comment) except: pass ''' Now handle error checking''' if maskname is not None: if thishdr["maskname"] != maskname: raise Exception("File %s uses mask '%s' but the stack is of '%s'" % (fname, thishdr["maskname"], maskname)) maskname = thishdr["maskname"] if thishdr["aborted"]: raise Exception("Img '%s' was aborted and should not be used" % fname) if prevssl is not None: if len(prevssl) != len(bs.ssl): # todo Improve these checks raise Exception("The stack of input files seems to be of " "different masks") prevssl = bs.ssl if patternid is not None: if patternid != thishdr["frameid"]: raise Exception("The stack should be of '%s' frames only, but " "the current image is a '%s' frame." % (patternid, thishdr["frameid"])) patternid = thishdr["frameid"] if maskname is not None: if maskname != thishdr["maskname"]: raise Exception("The stack should be of CSU mask '%s' frames " "only but contains a frame of '%s'." % (maskname, thishdr["maskname"])) maskname = thishdr["maskname"] if thishdr["BUNIT"] != "ADU per coadd": raise Exception("The units of '%s' are not in ADU per coadd and " "this violates an assumption of the DRP. Some new code " "is needed in the DRP to handle the new units of " "'%s'." % (fname, thishdr["BUNIT"])) ''' Error checking is complete''' print "%s %s[%s]/%s: %5.1f s, Shift: %i px" % (fname, maskname, patternid, header['filter'], np.mean(itimes[i]), shifts[i]) warnings.filterwarnings('always') # the electrons and el_per_sec arrays are: # [2048, 2048, len(files)] and contain values for # each individual frame that is being combined. # These need to be kept here for CRR reasons. electrons = np.array(ADUs) * Detector.gain el_per_sec = electrons / itimes output = np.zeros((2048, 2048)) exptime = np.zeros((2048, 2048)) numreads = header["READS0"] RN_adu = Detector.RN / np.sqrt(numreads) / Detector.gain RN = Detector.RN / np.sqrt(numreads) # Cosmic ray rejection code begins here. This code construction the # electrons and itimes arrays. if len(files) >= 9: print "Sigclip CRR" srt = np.argsort(electrons, axis=0, kind='quicksort') shp = el_per_sec.shape sti = np.ogrid[0:shp[0], 0:shp[1], 0:shp[2]] electrons = electrons[srt, sti[1], sti[2]] el_per_sec = el_per_sec[srt, sti[1], sti[2]] itimes = itimes[srt, sti[1], sti[2]] # Construct the mean and standard deviation by dropping the top and bottom two # electron fluxes. This is temporary. mean = np.mean(el_per_sec[2:-2,:,:], axis = 0) std = np.std(el_per_sec[2:-2,:,:], axis = 0) drop = np.where( (el_per_sec > (mean+std*4)) | (el_per_sec < (mean-std*4)) ) print "dropping: ", len(drop[0]) electrons[drop] = 0.0 itimes[drop] = 0.0 electrons = np.sum(electrons, axis=0) itimes = np.sum(itimes, axis=0) Nframe = len(files) elif len(files) > 5: print "WARNING: Drop min/max CRR" srt = np.argsort(el_per_sec,axis=0) shp = el_per_sec.shape sti = np.ogrid[0:shp[0], 0:shp[1], 0:shp[2]] electrons = electrons[srt, sti[1], sti[2]] itimes = itimes[srt, sti[1], sti[2]] electrons = np.sum(electrons[1:-1,:,:], axis=0) itimes = np.sum(itimes[1:-1,:,:], axis=0) Nframe = len(files) - 2 else: print "WARNING: CRR through median filtering" for i in xrange(len(files)): el = electrons[i,:,:] it = itimes[i,:,:] el_mf = scipy.signal.medfilt(el, 5) bad = np.abs(el - el_mf) / np.abs(el) > 10.0 el[bad] = 0.0 it[bad] = 0.0 electrons[i,:,:] = el itimes[i,:,:] = it electrons = np.sum(electrons, axis=0) itimes = np.sum(itimes, axis=0) Nframe = len(files) ''' Now handle variance ''' numreads = header["READS0"] RN_adu = Detector.RN / np.sqrt(numreads) / Detector.gain RN = Detector.RN / np.sqrt(numreads) var = (electrons + RN**2) ''' Now mask out bad pixels ''' electrons[data.mask] = np.nan var[data.mask] = np.inf if "RN" in header: raise Exception("RN Already populated in header") header['RN'] = ("%1.3f" , "Read noise in e-") header['NUMFRM'] = (Nframe, 'Typical number of frames in stack') if outname is not None: header['BUNIT'] = 'ELECTRONS/SECOND' IO.writefits(np.float32(electrons/itimes), maskname, "eps_%s" % (outname), options, header=header, overwrite=True) # Update itimes after division in order to not introduce nans itimes[data.mask] = 0.0 header['BUNIT'] = 'ELECTRONS^2' IO.writefits(var, maskname, "var_%s" % (outname), options, header=header, overwrite=True, lossy_compress=True) header['BUNIT'] = 'SECOND' IO.writefits(np.float32(itimes), maskname, "itimes_%s" % (outname), options, header=header, overwrite=True, lossy_compress=True) return header, electrons, var, bs, itimes, Nframe
def write_outputs(solutions, itime, header, maskname, band_name, plan, options): sky_sub_out = np.zeros((2048, 2048), dtype=np.float) sky_model_out = np.zeros((2048, 2048), dtype=np.float) p0 = plan[0].replace("'", "p") p1 = plan[1].replace("'", "p") suffix = "%s-%s" % (p0,p1) xroi = slice(0,2048) for sol in solutions: if not sol["ok"]: continue yroi = slice(sol["bottom"], sol["top"]) sky_sub_out[yroi, xroi] = sol["output"] sky_model_out[yroi, xroi] = sol["model"] header['BUNIT'] = 'SECOND' IO.writefits(itime, maskname, "itime_%s_%s_%s.fits" % (maskname, band, suffix), options, header=header, overwrite=True, lossy_compress=True) header['BUNIT'] = 'ELECTRONS/SECOND' IO.writefits(data, maskname, "sub_%s_%s_%s.fits" % (maskname, band, suffix), options, header=header, overwrite=True, lossy_compress=True) header['BUNIT'] = 'ELECTRONS/SECOND' IO.writefits(sky_sub_out, maskname, "bsub_%s_%s_%s.fits" % (maskname, band, suffix), options, header=header, overwrite=True) header['BUNIT'] = 'ELECTRONS' IO.writefits(Var, maskname, "var_%s_%s_%s.fits" % (maskname, band, suffix), options, header=header, overwrite=True, lossy_compress=True) header['BUNIT'] = 'ELECTRONS/SECOND' IO.writefits(sky_model_out, maskname, "bmod_%s_%s_%s.fits" % (maskname, band, suffix), options, header=header, overwrite=True, lossy_compress=True) '''Now create rectified solutions''' dlam = Wavelength.grating_results(band) hpp = np.array(Filters.hpp[band]) ll_fid = np.arange(hpp[0], hpp[1], dlam) nspec = len(ll_fid) rectified = np.zeros((2048, nspec), dtype=np.float32) rectified_var = np.zeros((2048, nspec), dtype=np.float32) rectified_itime = np.zeros((2048, nspec), dtype=np.float32) from scipy.interpolate import interp1d for i in xrange(2048): ll = lam[1][i,:] ss = sky_sub_out[i,:] ok = np.isfinite(ll) & np.isfinite(ss) & (ll < hpp[1]) & (ll > hpp[0]) if len(np.where(ok)[0]) < 100: continue f = interp1d(ll[ok], ss[ok], bounds_error=False) rectified[i,:] = f(ll_fid) f = interp1d(ll, Var[i,:], bounds_error=False) rectified_var[i,:] = f(ll_fid) f = interp1d(ll, itime[i,:], bounds_error=False) rectified_itime[i,:] = f(ll_fid) header["wat0_001"] = "system=world" header["wat1_001"] = "type=linear" header["wat2_001"] = "type=linear" header["dispaxis"] = 1 header["dclog1"] = "Transform" header["dc-flag"] = 0 header["type1"] = "AWAV" header["cunit1"] = "Angstrom" header["crval1"] = (ll_fid[0], "Starting wavelength Angstrom") header["crval2"] = 0 header["crpix1"] = 1 header["crpix2"] = 1 header["cdelt1"] = 1 header["cdelt2"] = 1 header["cname1"] = "angstrom" header["cname2"] = "pixel" header["cd1_1"] = (dlam, "Angstrom/pixel") header["cd1_2"] = 0 header["cd2_1"] = 0 header["cd2_2"] = (1, "pixel/pixel") IO.writefits(rectified_itime, maskname, "%s_rectified_itime_%s_%s.fits" % (maskname, band_name, suffix), options, header=header, overwrite=True, lossy_compress=True) IO.writefits(rectified, maskname, "%s_rectified_%s_%s.fits" % (maskname, band_name, suffix), options, header=header, overwrite=True, lossy_compress=True) IO.writefits(rectified_var, maskname, "%s_rectified_var_%s_%s.fits" % (maskname, band_name, suffix), options, header=header, overwrite=True, lossy_compress=True) IO.writefits(rectified*rectified_itime/np.sqrt(rectified_var), maskname, "%s_rectified_sn_%s_%s.fits" % (maskname, band_name, suffix), options, header=header, overwrite=True, lossy_compress=True)
def handle_rectification(maskname, in_files, wavename, band_pass, barset_file, options, commissioning_shift=3.0): '''Handle slit rectification and coaddition. Args: maskname: The mask name string in_files: List of stacked spectra in electron per second. Will look like ['electrons_Offset_1.5.txt.fits', 'electrons_Offset_-1.5.txt.fits'] wavename: path (relative or full) to the wavelength stack file, string band_pass: Band pass name, string barset_file: Path to a mosfire fits file containing the full set of FITS extensions for the barset. It can be any file in the list of science files. Returns: None Writes files: [maskname]_[band]_[object name]_eps.fits -- The rectified, background subtracted, stacked eps spectrum [maskname]_[band]_[object name]_sig.fits -- Rectified, background subtracted, stacked weight spectrum (STD/itime) [maskname]_[band]_[object_name]_itime.fits Rectified, CRR stacked integration time spectrum [maskname]_[band]_[object_name]_snrs.fits Rectified signal to noise spectrum ''' global edges, dats, vars, itimes, shifts, lambdas, band, fidl, all_shifts band = band_pass dlambda = Wavelength.grating_results(band) hpp = Filters.hpp[band] fidl = np.arange(hpp[0], hpp[1], dlambda) lambdas = IO.readfits(wavename, options) if np.any(lambdas[1].data < 0) or np.any(lambdas[1].data > 29000): print "***********WARNING ***********" print "The file {0} may not be a wavelength file.".format(wavename) print "Check before proceeding." print "***********WARNING ***********" edges, meta = IO.load_edges(maskname, band, options) shifts = [] posnames = [] postoshift = {} for file in in_files: print ":: ", file II = IO.read_drpfits(maskname, file, options) off = np.array((II[0]["decoff"], II[0]["raoff"]),dtype=np.float64) if "yoffset" in II[0]: off = -II[0]["yoffset"] else: # Deal with data taken during commissioning if II[0]["frameid"] == 'A': off = 0.0 else: off = commissioning_shift try: off0 except: off0 = off shift = off - off0 shifts.append(shift) posnames.append(II[0]["frameid"]) postoshift[II[0]['frameid']] = shift print "Position {0} shift: {1:2.2f} as".format(off, shift) plans = Background.guess_plan_from_positions(set(posnames)) all_shifts = [] for plan in plans: to_append = [] for pos in plan: to_append.append(postoshift[pos]) all_shifts.append(to_append) # Reverse the elements in all_shifts to deal with an inversion all_shifts.reverse() theBPM = IO.badpixelmask() all_solutions = [] cntr = 0 for plan in plans: p0 = plan[0].replace("'", "p") p1 = plan[1].replace("'", "p") suffix = "%s-%s" % (p0,p1) print "Handling plan %s" % suffix fname = "bsub_{0}_{1}_{2}.fits".format(maskname,band,suffix) EPS = IO.read_drpfits(maskname, fname, options) EPS[1] = np.ma.masked_array(EPS[1], theBPM, fill_value=0) fname = "var_{0}_{1}_{2}.fits".format(maskname, band, suffix) VAR = IO.read_drpfits(maskname, fname, options) VAR[1] = np.ma.masked_array(VAR[1], theBPM, fill_value=np.inf) fname = "itime_{0}_{1}_{2}.fits".format(maskname, band, suffix) ITIME = IO.read_drpfits(maskname, fname, options) ITIME[1] = np.ma.masked_array(ITIME[1], theBPM, fill_value=0) dats = EPS vars = VAR itimes = ITIME EPS[0]["ORIGFILE"] = fname tock = time.time() sols = range(len(edges)-1,-1,-1) shifts = all_shifts[cntr] cntr += 1 p = Pool() solutions = p.map(handle_rectification_helper, sols) #solutions = map(handle_rectification_helper, [15]) p.close() all_solutions.append(solutions) tick = time.time() print "-----> Mask took %i. Writing to disk." % (tick-tock) output = np.zeros((1, len(fidl))) snrs = np.zeros((1, len(fidl))) sdout= np.zeros((1, len(fidl))) itout= np.zeros((1, len(fidl))) # the barset [bs] is used for determining object position x, x, bs = IO.readmosfits(barset_file, options) for i_slit in xrange(len(solutions)): solution = all_solutions[0][i_slit] header = EPS[0].copy() obj = header['OBJECT'] target_name = bs.ssl[-(i_slit+1)]['Target_Name'] header['OBJECT'] = target_name pixel_dist = np.float(bs.ssl[-(i_slit+1)]['Target_to_center_of_slit_distance'])/0.18 pixel_dist -= solution['offset'] ll = solution["lambda"] header["wat0_001"] = "system=world" header["wat1_001"] = "wtype=linear" header["wat2_001"] = "wtype=linear" header["dispaxis"] = 1 header["dclog1"] = "Transform" header["dc-flag"] = 0 header["ctype1"] = "AWAV" header["cunit1"] = "Angstrom" header["crval1"] = ll[0] header["crval2"] = -solution["eps_img"].shape[0]/2 - pixel_dist header["crpix1"] = 1 header["crpix2"] = 1 header["cdelt1"] = 1 header["cdelt2"] = 1 header["cname1"] = "angstrom" header["cname2"] = "pixel" header["cd1_1"] = ll[1]-ll[0] header["cd1_2"] = 0 header["cd2_1"] = 0 header["cd2_2"] = 1 S = output.shape img = solution["eps_img"] std = solution["sd_img"] tms = solution["itime_img"] for i_solution in xrange(1,len(all_solutions)): print "Combining solution %i" %i_solution solution = all_solutions[i_solution][i_slit] img += solution["eps_img"] std += solution["sd_img"] tms += solution["itime_img"] output = np.append(output, img, 0) output = np.append(output, np.nan*np.zeros((3,S[1])), 0) snrs = np.append(snrs, img*tms/std, 0) snrs = np.append(snrs, np.nan*np.zeros((3,S[1])), 0) sdout = np.append(sdout, std, 0) sdout = np.append(sdout, np.nan*np.zeros((3,S[1])), 0) itout = np.append(itout, tms, 0) itout = np.append(itout, np.nan*np.zeros((3,S[1])), 0) header['bunit'] = ('electron/second', 'electron power') IO.writefits(img, maskname, "{0}_{1}_{2}_eps.fits".format(maskname, band, target_name), options, overwrite=True, header=header, lossy_compress=False) header['bunit'] = ('electron/second', 'sigma/itime') IO.writefits(std/tms, maskname, "{0}_{1}_{2}_sig.fits".format(maskname, band, target_name), options, overwrite=True, header=header, lossy_compress=False) header['bunit'] = ('second', 'exposure time') IO.writefits(tms, maskname, "{0}_{1}_{2}_itime.fits".format(maskname, band, target_name), options, overwrite=True, header=header, lossy_compress=False) header['bunit'] = ('', 'SNR') IO.writefits(img*tms/std, maskname, "{0}_{1}_{2}_snrs.fits".format(maskname, band, target_name), options, overwrite=True, header=header, lossy_compress=False) header = EPS[0].copy() header["wat0_001"] = "system=world" header["wat1_001"] = "wtype=linear" header["wat2_001"] = "wtype=linear" header["dispaxis"] = 1 header["dclog1"] = "Transform" header["dc-flag"] = 0 header["ctype1"] = "AWAV" header["cunit1"] = ("Angstrom", 'Start wavelength') header["crval1"] = ll[0] header["crval2"] = 1 header["crpix1"] = 1 header["crpix2"] = 1 header["cdelt1"] = 1 header["cdelt2"] = 1 header["cname1"] = "angstrom" header["cname2"] = "pixel" header["cd1_1"] = (ll[1]-ll[0], 'Angstrom/pixel') header["cd1_2"] = 0 header["cd2_1"] = 0 header["cd2_2"] = 1 header["bunit"] = "ELECTRONS/SECOND" IO.writefits(output, maskname, "{0}_{1}_eps.fits".format(maskname, band), options, overwrite=True, header=header, lossy_compress=False) header["bunit"] = "" IO.writefits(snrs, maskname, "{0}_{1}_snrs.fits".format(maskname, band), options, overwrite=True, header=header, lossy_compress=False) header["bunit"] = "ELECTRONS/SECOND" IO.writefits(sdout/itout, maskname, "{0}_{1}_sig.fits".format(maskname, band), options, overwrite=True, header=header, lossy_compress=False) header["bunit"] = "SECOND" IO.writefits(itout, maskname, "{0}_{1}_itime.fits".format(maskname, band), options, overwrite=True, header=header, lossy_compress=False)
def write_outputs(solutions, itime, header, maskname, band_name, plan, options): sky_sub_out = np.zeros((2048, 2048), dtype=np.float) sky_model_out = np.zeros((2048, 2048), dtype=np.float) p0 = plan[0].replace("'", "p") p1 = plan[1].replace("'", "p") suffix = "%s-%s" % (p0, p1) xroi = slice(0, 2048) for sol in solutions: if not sol["ok"]: continue yroi = slice(sol["bottom"], sol["top"]) sky_sub_out[yroi, xroi] = sol["output"] sky_model_out[yroi, xroi] = sol["model"] header['BUNIT'] = 'SECOND' IO.writefits(itime, maskname, "itime_%s_%s_%s.fits" % (maskname, band, suffix), options, header=header, overwrite=True, lossy_compress=True) header['BUNIT'] = 'ELECTRONS/SECOND' IO.writefits(data, maskname, "sub_%s_%s_%s.fits" % (maskname, band, suffix), options, header=header, overwrite=True, lossy_compress=True) header['BUNIT'] = 'ELECTRONS/SECOND' IO.writefits(sky_sub_out, maskname, "bsub_%s_%s_%s.fits" % (maskname, band, suffix), options, header=header, overwrite=True) header['BUNIT'] = 'ELECTRONS' IO.writefits(Var, maskname, "var_%s_%s_%s.fits" % (maskname, band, suffix), options, header=header, overwrite=True, lossy_compress=True) header['BUNIT'] = 'ELECTRONS/SECOND' IO.writefits(sky_model_out, maskname, "bmod_%s_%s_%s.fits" % (maskname, band, suffix), options, header=header, overwrite=True, lossy_compress=True) '''Now create rectified solutions''' dlam = Wavelength.grating_results(band) hpp = np.array(Filters.hpp[band]) ll_fid = np.arange(hpp[0], hpp[1], dlam) nspec = len(ll_fid) rectified = np.zeros((2048, nspec), dtype=np.float32) rectified_var = np.zeros((2048, nspec), dtype=np.float32) rectified_itime = np.zeros((2048, nspec), dtype=np.float32) from scipy.interpolate import interp1d for i in xrange(2048): ll = lam[1][i, :] ss = sky_sub_out[i, :] ok = np.isfinite(ll) & np.isfinite(ss) & (ll < hpp[1]) & (ll > hpp[0]) if len(np.where(ok)[0]) < 100: continue f = interp1d(ll[ok], ss[ok], bounds_error=False) rectified[i, :] = f(ll_fid) f = interp1d(ll, Var[i, :], bounds_error=False) rectified_var[i, :] = f(ll_fid) f = interp1d(ll, itime[i, :], bounds_error=False) rectified_itime[i, :] = f(ll_fid) header["wat0_001"] = "system=world" header["wat1_001"] = "type=linear" header["wat2_001"] = "type=linear" header["dispaxis"] = 1 header["dclog1"] = "Transform" header["dc-flag"] = 0 header["type1"] = "AWAV" header["cunit1"] = "Angstrom" header["crval1"] = (ll_fid[0], "Starting wavelength Angstrom") header["crval2"] = 0 header["crpix1"] = 1 header["crpix2"] = 1 header["cdelt1"] = 1 header["cdelt2"] = 1 header["cname1"] = "angstrom" header["cname2"] = "pixel" header["cd1_1"] = (dlam, "Angstrom/pixel") header["cd1_2"] = 0 header["cd2_1"] = 0 header["cd2_2"] = (1, "pixel/pixel") IO.writefits(rectified_itime, maskname, "%s_rectified_itime_%s_%s.fits" % (maskname, band_name, suffix), options, header=header, overwrite=True, lossy_compress=True) IO.writefits(rectified, maskname, "%s_rectified_%s_%s.fits" % (maskname, band_name, suffix), options, header=header, overwrite=True, lossy_compress=True) IO.writefits(rectified_var, maskname, "%s_rectified_var_%s_%s.fits" % (maskname, band_name, suffix), options, header=header, overwrite=True, lossy_compress=True) IO.writefits(rectified * rectified_itime / np.sqrt(rectified_var), maskname, "%s_rectified_sn_%s_%s.fits" % (maskname, band_name, suffix), options, header=header, overwrite=True, lossy_compress=True)
def imcombine(files, maskname, options, flat, outname=None, shifts=None, extension=None): ''' From a list of files it imcombine returns the imcombine of several values. The imcombine code also estimates the readnoise ad RN/sqrt(numreads) so that the variance per frame is equal to (ADU + RN^2) where RN is computed in ADUs. Arguments: files[]: list of full path to files to combine maskname: Name of mask options: Options dictionary flat[2048x2048]: Flat field (values should all be ~ 1.0) outname: If set, will write (see notes below for details) eps_[outname].fits: electron/sec file itimes_[outname].fits: integration time var_[outname].fits: Variance files shifts[len(files)]: If set, will "roll" each file by the amount in the shifts vector in pixels. This argument is used when telescope tracking is poor. If you need to use this, please notify Keck staff about poor telescope tracking. Returns 6-element tuple: header: The combined header electrons [2048x2048]: e- (in e- units) var [2048x2048]: electrons + RN**2 (in e-^2 units) bs: The MOSFIRE.Barset instance itimes [2048x2048]: itimes (in s units) Nframe: The number of frames that contribute to the summed arrays above. If Nframe > 5 I use the sigma-clipping Cosmic Ray Rejection tool. If Nframe < 5 then I drop the max/min elements. Notes: header -- fits header ADUs -- The mean # of ADUs per frame var -- the Variance [in adu] per frame. bs -- Barset itimes -- The _total_ integration time in second Nframe -- The number of frames in a stack. Thus the number of electron per second is derived as: e-/sec = (ADUs * Gain / Flat) * (Nframe/itimes) The total number of electrons is: el = ADUs * Gain * Nframe ''' ADUs = np.zeros((len(files), 2048, 2048)) itimes = np.zeros((len(files), 2048, 2048)) prevssl = None prevmn = None patternid = None maskname = None header = None if shifts is None: shifts = np.zeros(len(files)) warnings.filterwarnings('ignore') for i in xrange(len(files)): fname = files[i] thishdr, data, bs = IO.readmosfits(fname, options, extension=extension) itimes[i, :, :] = thishdr["truitime"] base = os.path.basename(fname).rstrip(".fits") fnum = int(base.split("_")[1]) if shifts[i] == 0: ADUs[i, :, :] = data.filled(0.0) / flat else: ADUs[i, :, :] = np.roll(data.filled(0.0) / flat, np.int(shifts[i]), axis=0) ''' Construct Header''' if header is None: header = thishdr header["imfno%3.3i" % (fnum)] = (fname, "img%3.3i file name" % fnum) map(lambda x: rem_header_key(header, x), [ "CTYPE1", "CTYPE2", "WCSDIM", "CD1_1", "CD1_2", "CD2_1", "CD2_2", "LTM1_1", "LTM2_2", "WAT0_001", "WAT1_001", "WAT2_001", "CRVAL1", "CRVAL2", "CRPIX1", "CRPIX2", "RADECSYS" ]) for card in header.cards: if card == '': continue key, val, comment = card if key in thishdr: if val != thishdr[key]: newkey = key + ("_img%2.2i" % fnum) try: header[newkey.rstrip()] = (thishdr[key], comment) except: pass ''' Now handle error checking''' if maskname is not None: if thishdr["maskname"] != maskname: raise Exception( "File %s uses mask '%s' but the stack is of '%s'" % (fname, thishdr["maskname"], maskname)) maskname = thishdr["maskname"] if thishdr["aborted"]: raise Exception("Img '%s' was aborted and should not be used" % fname) if prevssl is not None: if len(prevssl) != len(bs.ssl): # todo Improve these checks raise Exception("The stack of input files seems to be of " "different masks") prevssl = bs.ssl if patternid is not None: if patternid != thishdr["frameid"]: raise Exception("The stack should be of '%s' frames only, but " "the current image is a '%s' frame." % (patternid, thishdr["frameid"])) patternid = thishdr["frameid"] if maskname is not None: if maskname != thishdr["maskname"]: raise Exception("The stack should be of CSU mask '%s' frames " "only but contains a frame of '%s'." % (maskname, thishdr["maskname"])) maskname = thishdr["maskname"] if thishdr["BUNIT"] != "ADU per coadd": raise Exception( "The units of '%s' are not in ADU per coadd and " "this violates an assumption of the DRP. Some new code " "is needed in the DRP to handle the new units of " "'%s'." % (fname, thishdr["BUNIT"])) ''' Error checking is complete''' print "%s %s[%s]/%s: %5.1f s, Shift: %i px" % ( fname, maskname, patternid, header['filter'], np.mean( itimes[i]), shifts[i]) warnings.filterwarnings('always') # the electrons and el_per_sec arrays are: # [2048, 2048, len(files)] and contain values for # each individual frame that is being combined. # These need to be kept here for CRR reasons. electrons = np.array(ADUs) * Detector.gain el_per_sec = electrons / itimes output = np.zeros((2048, 2048)) exptime = np.zeros((2048, 2048)) numreads = header["READS0"] RN_adu = Detector.RN / np.sqrt(numreads) / Detector.gain RN = Detector.RN / np.sqrt(numreads) # Cosmic ray rejection code begins here. This code construction the # electrons and itimes arrays. if len(files) >= 9: print "Sigclip CRR" srt = np.argsort(electrons, axis=0, kind='quicksort') shp = el_per_sec.shape sti = np.ogrid[0:shp[0], 0:shp[1], 0:shp[2]] electrons = electrons[srt, sti[1], sti[2]] el_per_sec = el_per_sec[srt, sti[1], sti[2]] itimes = itimes[srt, sti[1], sti[2]] # Construct the mean and standard deviation by dropping the top and bottom two # electron fluxes. This is temporary. mean = np.mean(el_per_sec[2:-2, :, :], axis=0) std = np.std(el_per_sec[2:-2, :, :], axis=0) drop = np.where((el_per_sec > (mean + std * 4)) | (el_per_sec < (mean - std * 4))) print "dropping: ", len(drop[0]) electrons[drop] = 0.0 itimes[drop] = 0.0 electrons = np.sum(electrons, axis=0) itimes = np.sum(itimes, axis=0) Nframe = len(files) elif len(files) > 5: print "WARNING: Drop min/max CRR" srt = np.argsort(el_per_sec, axis=0) shp = el_per_sec.shape sti = np.ogrid[0:shp[0], 0:shp[1], 0:shp[2]] electrons = electrons[srt, sti[1], sti[2]] itimes = itimes[srt, sti[1], sti[2]] electrons = np.sum(electrons[1:-1, :, :], axis=0) itimes = np.sum(itimes[1:-1, :, :], axis=0) Nframe = len(files) - 2 else: print "WARNING: CRR through median filtering" for i in xrange(len(files)): el = electrons[i, :, :] it = itimes[i, :, :] el_mf = scipy.signal.medfilt(el, 5) bad = np.abs(el - el_mf) / np.abs(el) > 10.0 el[bad] = 0.0 it[bad] = 0.0 electrons[i, :, :] = el itimes[i, :, :] = it electrons = np.sum(electrons, axis=0) itimes = np.sum(itimes, axis=0) Nframe = len(files) ''' Now handle variance ''' numreads = header["READS0"] RN_adu = Detector.RN / np.sqrt(numreads) / Detector.gain RN = Detector.RN / np.sqrt(numreads) var = (electrons + RN**2) ''' Now mask out bad pixels ''' electrons[data.mask] = np.nan var[data.mask] = np.inf if "RN" in header: raise Exception("RN Already populated in header") header['RN'] = ("%1.3f", "Read noise in e-") header['NUMFRM'] = (Nframe, 'Typical number of frames in stack') if outname is not None: header['BUNIT'] = 'ELECTRONS/SECOND' IO.writefits(np.float32(electrons / itimes), maskname, "eps_%s" % (outname), options, header=header, overwrite=True) # Update itimes after division in order to not introduce nans itimes[data.mask] = 0.0 header['BUNIT'] = 'ELECTRONS^2' IO.writefits(var, maskname, "var_%s" % (outname), options, header=header, overwrite=True, lossy_compress=True) header['BUNIT'] = 'SECOND' IO.writefits(np.float32(itimes), maskname, "itimes_%s" % (outname), options, header=header, overwrite=True, lossy_compress=True) return header, electrons, var, bs, itimes, Nframe
def handle_rectification(maskname, in_files, wavename, band_pass, files, options, commissioning_shift=3.0, target='default'): '''Handle slit rectification and coaddition. Args: maskname: The mask name string in_files: List of stacked spectra in electron per second. Will look like ['electrons_Offset_1.5.txt.fits', 'electrons_Offset_-1.5.txt.fits'] wavename: path (relative or full) to the wavelength stack file, string band_pass: Band pass name, string barset_file: Path to a mosfire fits file containing the full set of FITS extensions for the barset. It can be any file in the list of science files. Returns: None Writes files: [maskname]_[band]_[object name]_eps.fits -- The rectified, background subtracted, stacked eps spectrum [maskname]_[band]_[object name]_sig.fits -- Rectified, background subtracted, stacked weight spectrum (STD/itime) [maskname]_[band]_[object_name]_itime.fits Rectified, CRR stacked integration time spectrum [maskname]_[band]_[object_name]_snrs.fits Rectified signal to noise spectrum ''' global edges, dats, vars, itimes, shifts, lambdas, band, fidl, all_shifts band = band_pass dlambda = Wavelength.grating_results(band) hpp = Filters.hpp[band] fidl = np.arange(hpp[0], hpp[1], dlambda) lambdas = IO.readfits(wavename, options) if np.any(lambdas[1].data < 0) or np.any(lambdas[1].data > 29000): info("***********WARNING ***********") info("The file {0} may not be a wavelength file.".format(wavename)) info("Check before proceeding.") info("***********WARNING ***********") edges, meta = IO.load_edges(maskname, band, options) shifts = [] posnames = [] postoshift = {} for file in in_files: info(":: "+str(file)) II = IO.read_drpfits(maskname, file, options) off = np.array((II[0]["decoff"], II[0]["raoff"]),dtype=np.float64) if "yoffset" in II[0]: off = -II[0]["yoffset"] else: # Deal with data taken during commissioning if II[0]["frameid"] == 'A': off = 0.0 else: off = commissioning_shift try: off0 except: off0 = off shift = off - off0 shifts.append(shift) posnames.append(II[0]["frameid"]) postoshift[II[0]['frameid']] = shift info("Position {0} shift: {1:2.2f} as".format(off, shift)) # this is to deal with cases in which we want to rectify one single file if len(set(posnames)) is 1: plans = [['A']] else: plans = Background.guess_plan_from_positions(set(posnames)) all_shifts = [] for plan in plans: to_append = [] for pos in plan: to_append.append(postoshift[pos]) all_shifts.append(to_append) # Reverse the elements in all_shifts to deal with an inversion all_shifts.reverse() theBPM = IO.badpixelmask() all_solutions = [] cntr = 0 if target is 'default': outname = maskname else: outname = target for plan in plans: if len(plan) is 1: p0 = 'A' p1 = 'B' else: p0 = plan[0].replace("'", "p") p1 = plan[1].replace("'", "p") suffix = "%s-%s" % (p0,p1) info("Handling plan %s" % suffix) fname = "bsub_{0}_{1}_{2}.fits".format(outname,band,suffix) EPS = IO.read_drpfits(maskname, fname, options) EPS[1] = np.ma.masked_array(EPS[1], theBPM, fill_value=0) fname = "var_{0}_{1}_{2}.fits".format(outname, band, suffix) VAR = IO.read_drpfits(maskname, fname, options) VAR[1] = np.ma.masked_array(VAR[1], theBPM, fill_value=np.inf) fname = "itime_{0}_{1}_{2}.fits".format(outname, band, suffix) ITIME = IO.read_drpfits(maskname, fname, options) ITIME[1] = np.ma.masked_array(ITIME[1], theBPM, fill_value=0) dats = EPS vars = VAR itimes = ITIME EPS[0]["ORIGFILE"] = fname tock = time.time() sols = range(len(edges)-1,-1,-1) shifts = all_shifts[cntr] cntr += 1 p = Pool() solutions = p.map(handle_rectification_helper, sols) p.close() all_solutions.append(solutions) tick = time.time() info("-----> Mask took %i. Writing to disk." % (tick-tock)) output = np.zeros((1, len(fidl))) snrs = np.zeros((1, len(fidl))) sdout= np.zeros((1, len(fidl))) itout= np.zeros((1, len(fidl))) # the barset [bs] is used for determining object position files = IO.list_file_to_strings(files) info("Using "+str(files[0])+" for slit configuration.") x, x, bs = IO.readmosfits(files[0], options) for i_slit in xrange(len(solutions)): solution = all_solutions[0][i_slit] header = EPS[0].copy() obj = header['OBJECT'] target_name = bs.ssl[-(i_slit+1)]['Target_Name'] header['OBJECT'] = target_name pixel_dist = np.float(bs.ssl[-(i_slit+1)]['Target_to_center_of_slit_distance'])/0.18 pixel_dist -= solution['offset'] ll = solution["lambda"] header["wat0_001"] = "system=world" header["wat1_001"] = "wtype=linear" header["wat2_001"] = "wtype=linear" header["dispaxis"] = 1 header["dclog1"] = "Transform" header["dc-flag"] = 0 header["ctype1"] = "AWAV" header["cunit1"] = "Angstrom" header["crval1"] = ll[0] header["crval2"] = -solution["eps_img"].shape[0]/2 - pixel_dist header["crpix1"] = 1 header["crpix2"] = 1 #remove redundant CDELTi due to wavelength issues with ds9 #see: https://github.com/Keck-DataReductionPipelines/MosfireDRP/issues/44 #header["cdelt1"] = 1 #header["cdelt2"] = 1 header["cname1"] = "angstrom" header["cname2"] = "pixel" header["cd1_1"] = ll[1]-ll[0] header["cd1_2"] = 0 header["cd2_1"] = 0 header["cd2_2"] = 1 try: header["BARYCORR"]= (lambdas[0]['BARYCORR'],lambdas[0].comments['BARYCORR']) except KeyError: warning( "Barycentric corrections not applied to the wavelength solution") pass S = output.shape img = solution["eps_img"] std = solution["sd_img"] tms = solution["itime_img"] for i_solution in xrange(1,len(all_solutions)): info("Combining solution %i" %i_solution) solution = all_solutions[i_solution][i_slit] img += solution["eps_img"] std += solution["sd_img"] tms += solution["itime_img"] #print "adding in quadrature" output = np.append(output, img, 0) output = np.append(output, np.nan*np.zeros((3,S[1])), 0) snrs = np.append(snrs, img*tms/std, 0) snrs = np.append(snrs, np.nan*np.zeros((3,S[1])), 0) sdout = np.append(sdout, std, 0) sdout = np.append(sdout, np.nan*np.zeros((3,S[1])), 0) itout = np.append(itout, tms, 0) itout = np.append(itout, np.nan*np.zeros((3,S[1])), 0) header['bunit'] = ('electron/second', 'electron power') IO.writefits(img, maskname, "{0}_{1}_{2}_eps.fits".format(outname, band, target_name), options, overwrite=True, header=header, lossy_compress=False) header['bunit'] = ('electron/second', 'sigma/itime') IO.writefits(std/tms, maskname, "{0}_{1}_{2}_sig.fits".format(outname, band, target_name), options, overwrite=True, header=header, lossy_compress=False) header['bunit'] = ('second', 'exposure time') IO.writefits(tms, maskname, "{0}_{1}_{2}_itime.fits".format(outname, band, target_name), options, overwrite=True, header=header, lossy_compress=False) header['bunit'] = ('', 'SNR') IO.writefits(img*tms/std, maskname, "{0}_{1}_{2}_snrs.fits".format(outname, band, target_name), options, overwrite=True, header=header, lossy_compress=False) header = EPS[0].copy() header["wat0_001"] = "system=world" header["wat1_001"] = "wtype=linear" header["wat2_001"] = "wtype=linear" header["dispaxis"] = 1 header["dclog1"] = "Transform" header["dc-flag"] = 0 header["ctype1"] = "AWAV" header["cunit1"] = ("Angstrom", 'Start wavelength') header["crval1"] = ll[0] header["crval2"] = 1 header["crpix1"] = 1 header["crpix2"] = 1 #remove redundant CDELTi due to wavelength issues with ds9 #see: https://github.com/Keck-DataReductionPipelines/MosfireDRP/issues/44 #header["cdelt1"] = 1 #header["cdelt2"] = 1 header["cname1"] = "angstrom" header["cname2"] = "pixel" header["cd1_1"] = (ll[1]-ll[0], 'Angstrom/pixel') header["cd1_2"] = 0 header["cd2_1"] = 0 header["cd2_2"] = 1 try: header["BARYCORR"]= (lambdas[0]['BARYCORR'],lambdas[0].comments['BARYCORR']) except KeyError: warning( "Barycentric corrections not applied to the wavelength solution") pass header["bunit"] = "ELECTRONS/SECOND" info("############ Final reduced file: {0}_{1}_eps.fits".format(outname,band)) IO.writefits(output, maskname, "{0}_{1}_eps.fits".format(outname, band), options, overwrite=True, header=header, lossy_compress=False) header["bunit"] = "" IO.writefits(snrs, maskname, "{0}_{1}_snrs.fits".format(outname, band), options, overwrite=True, header=header, lossy_compress=False) header["bunit"] = "ELECTRONS/SECOND" IO.writefits(sdout/itout, maskname, "{0}_{1}_sig.fits".format(outname, band), options, overwrite=True, header=header, lossy_compress=False) header["bunit"] = "SECOND" IO.writefits(itout, maskname, "{0}_{1}_itime.fits".format(outname, band), options, overwrite=True, header=header, lossy_compress=False)