def __init__(self, ra0, dec0, pa, x0, y0, x_scale=-1., y_scale=1., sys_rot=1.07, fplane_file=None, kind='fplane'): self.setup_logging() self.ra0 = ra0 self.dec0 = dec0 self.dra = 0. self.ddec = 0. self.dx = 0. self.dy = 0. self.x0 = x0 self.y0 = y0 self.pa = pa self.x_scale = x_scale self.y_scale = y_scale self.sys_rot = sys_rot self.fplane_file = fplane_file if self.fplane_file is None: self.log.info('No fplane file given.') self.log.info('Some functions will be unavailable until' 'an fplane is given.') else: if not pyhetdex_flag: self.log.warning('Cannot load fplane because pyhetdex ' 'is not available') else: self.fplane = FPlane(self.fplane_file) self.kind = kind self.set_effective_rotation() # Building tangent plane projection with scale 1" self.tp = self.setup_TP(self.ra0, self.dec0, self.rot, self.x0, self.y0) self.tp_ifuslot = None
def __init__(self, fplane_file, shot, dither_positions=[ ['000', 0.000, -1.270, -1.270, 0.000, 0.730, -0.730], ]): self.ifu_dxs = {} self.ifu_dys = {} self.shot = shot self.fplane = FPlane(fplane_file) self._store_dither_positions(dither_positions)
def generate_sencube_hdf(datevshot, ra, dec, pa, fplane_output_dir, nx, ny, nz, ifusize, skip_ifus=[ "000", "600", "555", "601", "602", "603", "604", "610", "611", "612", "613", "614", "615", "616" ], hdf_filename=None): """ Generate an empty real or mock sensitivity HDF5 container, with the proper astrometry in the cubes. Real containters are of the SensitivityCubeHDF5Container class and are written to a file. The mock containers are of HDF5MockContainer class and do not have a real HDF5 file - useful for simulations. Parameters ---------- datevshot : str the 8 digit YYYYMMDDvSSS date of the shot and the shot, used to get the correct focal plane file ra, dec, pa : float the astrometry of the shot fplane_output_dir : str directory to output fplane files to hdf_filename : str (optional) if passed, generate a real SensitivityCubeHDF5Container with this filename. If None generate a mock container. ifusize : float size of x,y of IFU in arcsec skip_ifus : list (optional) the IFUSLOTS to skip Returns ------- hdfcont : SensitivityCubeHDF5Container or HDF5MockContainer a real or mock sensivity cube container depending on the ``hdf_filename`` parameter """ if hdf_filename: hdfcont = SensitivityCubeHDF5Container(hdf_filename, mode="w") else: hdfcont = HDF5MockContainer() # Generate the shot astrometry rot = 360.0 - (pa + 90.) tp = TangentPlane(ra, dec, rot) date = datevshot[:8] fplane_bn = "{:s}_fplane.txt".format(date) fplane_fn = join(fplane_output_dir, fplane_bn) if not isfile(fplane_fn): get_fplane(fplane_fn, datestr=str(date)) fplane = FPlane(fplane_fn) else: fplane = FPlane(fplane_fn) for ifuslot, ifu in iteritems(fplane.difus_ifuslot): if ifuslot in skip_ifus: continue ifuslot_str = "ifuslot_" + ifuslot # Note x, y swapped in focal fplane ra_ifu, dec_ifu = tp.xy2raDec(ifu.y, ifu.x) scube = create_sensitivity_cube_from_astrom(ra_ifu.item(), dec_ifu.item(), pa, nx, ny, nz, ifusize) hdfcont.add_sensitivity_cube(datevshot, ifuslot_str, scube) return hdfcont
def split_into_ifus(table, unique_fields, unique_ifus, outdir, NMAX=1000, nsplit_start=0, nsplit=1): """ Split a catalogue into input catalogues and commands on single IFUs. Outputs catalogues and files full of calls to Karl's HETDEX simulation code Parameters ---------- unique_fields, unique_ifus : array fields and IFUs to split up outdir : str output directory NMAX : int (optional) maximum source per IFU, further splits if bigger than this """ # Maximum sources per ifu in one sim run # Second part of the filename fn2s = [] ras = [] decs = [] for field in unique_fields: table_field = table[table["field"] == field] for ifuslot in unique_ifus: table_ifu = table_field[table_field["ifuslot"] == ifuslot] # Split into sub sets if too big to simulate at once nsplit = int(ceil(len(table_ifu) / NMAX)) for isplit in range(nsplit): ttable = table_ifu[isplit * NMAX:(isplit + 1) * NMAX] date = field[:-4] fplane_fn = "{:s}_fplane.txt".format(date) if not isfile(fplane_fn): get_fplane(fplane_fn, datestr=date) fplane = FPlane(fplane_fn) else: fplane = FPlane(fplane_fn) ifu = fplane.by_id(ifuslot[-3:], "ifuslot") fn2 = "{:03d}_{:s}_{:s}".format(ifu.specid, ifu.ifuslot, ifu.ifuid) if isplit + nsplit_start > 0: fn2 = fn2 + "_{:d}".format(isplit + nsplit_start) outfn = join(outdir, "{:s}_{:s}.input".format(field, fn2)) savetxt( outfn, transpose([ ttable["ra"], ttable["dec"], ttable["wave"], ttable["flux_extincted"] ])) ras.append(ttable["ifu_ra"][0]) decs.append(ttable["ifu_dec"][0]) fn2s.append(fn2) produce_rs1_runfile( join(outdir, "{:s}_{:d}.run".format(field, nsplit_start)), fn2s, ras, decs, field, nsplit)
class Astrometry: ''' Astrometry Class This class holds basic astrometric solutions for fits imaging and has a suite of functions designed to manage the conversion from on sky coordinates to on image coordinates (and vice versa) as well as take updates to the solution. Other functionaility has a specific design for serving as the HET's fplane astrometry and building astrometric solutions for given ifuslot's based on the fplane astrometry. This is useful for reconstructed VIRUS images. ''' def __init__(self, ra0, dec0, pa, x0, y0, x_scale=-1., y_scale=1., sys_rot=1.07, fplane_file=None, kind='fplane'): self.setup_logging() self.ra0 = ra0 self.dec0 = dec0 self.dra = 0. self.ddec = 0. self.dx = 0. self.dy = 0. self.x0 = x0 self.y0 = y0 self.pa = pa self.x_scale = x_scale self.y_scale = y_scale self.sys_rot = sys_rot self.fplane_file = fplane_file if self.fplane_file is None: self.log.info('No fplane file given.') self.log.info('Some functions will be unavailable until' 'an fplane is given.') else: if not pyhetdex_flag: self.log.warning('Cannot load fplane because pyhetdex ' 'is not available') else: self.fplane = FPlane(self.fplane_file) self.kind = kind self.set_effective_rotation() # Building tangent plane projection with scale 1" self.tp = self.setup_TP(self.ra0, self.dec0, self.rot, self.x0, self.y0) self.tp_ifuslot = None def setup_TP(self, ra0, dec0, rot, x0=0.0, y0=0.0): ''' TP is tangent plane ''' ARCSECPERDEG = 1.0/3600.0 # make a WCS object with appropriate FITS headers tp = wcs.WCS(naxis=2) tp.wcs.crpix = [x0, y0] # central "pixel" tp.wcs.crval = [ra0, dec0] # tangent point tp.wcs.ctype = ['RA---TAN', 'DEC--TAN'] # pixel scale in deg. tp.wcs.cdelt = [ARCSECPERDEG * self.x_scale, ARCSECPERDEG * self.y_scale] # Deal with PA rotation by adding rotation matrix to header rrot = deg2rad(rot) # clockwise rotation matrix tp.wcs.pc = [[cos(rrot), sin(rrot)], [-1.0*sin(rrot), cos(rrot)]] return tp def set_polynomial_platescale(self): ''' This has not been tested ''' self.tp.wcs.a_0_0 = 0.177311 self.tp.wcs.a_1_0 = -8.29099e-06 self.tp.wcs.a_2_0 = -2.37318e-05 self.tp.wcs.b_0_0 = 0.177311 self.tp.wcs.b_0_1 = -8.29099e-06 self.tp.wcs.b_0_2 = -2.37318e-05 self.tp.wcs.a_order = 2 self.tp.wcs.b_order = 2 def setup_logging(self): '''Set up a logger for analysis with a name ``astrometry``. Use a StreamHandler to write to stdout and set the level to DEBUG if verbose is set from the command line ''' self.log = logging.getLogger('astrometry') if not len(self.log.handlers): fmt = '[%(levelname)s - %(asctime)s] %(message)s' level = logging.INFO fmt = logging.Formatter(fmt) handler = logging.StreamHandler() handler.setFormatter(fmt) handler.setLevel(level) self.log = logging.getLogger('astrometry') self.log.setLevel(logging.DEBUG) self.log.addHandler(handler) def set_effective_rotation(self): ''' The rotation for the acam is correct ''' if self.kind == 'fplane': self.rot = 360. - (90. + self.pa + self.sys_rot) elif self.kind == 'acam': self.rot = self.pa + self.sys_rot + 90. else: self.log.error('"kind" was not set to available options.') self.log.info('Available options are: %s and %s' % ('fplane', 'acam')) self.log.info('Next time please choose one of the options above.') self.log.info('Exiting due to error.') sys.exit(1) def update_projection(self): ''' Use this for a new projection with small adjustments ''' self.set_effective_rotation() self.tp = self.setup_TP(self.ra0 + self.dra, self.dec0 + self.ddec, self.rot, self.x0 + self.dx, self.y0 + self.dy) def get_ifuslot_ra_dec(self, ifuslot): ''' Fplane functionality required for this ''' if self.fplane is None: return None ifu = self.fplane.by_ifuslot(ifuslot) # remember to flip x,y return self.tp.xy2raDec(ifu.y, ifu.x) def get_ifuspos_ra_dec(self, ifuslot, x, y): ''' Fplane functionality required for this ''' if self.fplane is None or not pyhetdex_flag: return None ifu = self.fplane.by_ifuslot(ifuslot) # remember to flip x,y return self.tp.wcs_pix2world(ifu.y + x, ifu.x + y, 1) def get_ifuslot_projection(self, ifuslot, imscale, crx, cry): ''' Fplane functionality required for this ''' if self.fplane is None or not pyhetdex_flag: return None ra, dec = self.get_ifuslot_ra_dec(ifuslot) self.tp_ifuslot = self.setup_TP(ra, dec, self.rot, crx, cry, x_scale=-imscale, y_scale=imscale) def convert_ifuslot_xy_to_new_xy(self, x, y, wcs): ''' Fplane functionality required for this ''' if self.tp_ifuslot is None or not pyhetdex_flag: self.log.error('You have not setup the ifuslot projection yet.') self.log.error('To do so, call ' '"get_ifuslot_projection(ifuslot, imscale') return None ra, dec = self.tp_ifuslot.wcs.wcs_pix2world(x, y, 1) return wcs.wcs_world2pix(ra, dec, 1)
class DitherCreator(object): """Class to create dither files Initialize the dither file creator for this shot Read in the locations of the dithers for each IFU from dither_positions. Parameters ---------- fplane_file : str path the focal plane file; it is parsed using :class:`~pyhetdex.het.fplane.FPlane` shot : :class:`pyhetdex.het.telescope.Shot` instance a ``shot`` instance that contains info on the image quality and normalization dither_positions : list of lists, optional list of lists with 2*n+1 elements: the first element is the ``id_``, used in, e.g., :meth:`create_dither`, then there are the n x-positions of the dithers and finally their n y-positions Attributes ---------- ifu_dxs, ifu_dys : dictionaries key: ihmpid; key: x and y shifts for the dithers shot : :class:`pyhetdex.het.telescope.Shot` instance fplane : :class:`~pyhetdex.het.fplane.FPlane` instance Raises ------ DitherPositionError if the number of x and y dither shifts for one id does not match """ def __init__(self, fplane_file, shot, dither_positions=[ ['000', 0.000, -1.270, -1.270, 0.000, 0.730, -0.730], ]): self.ifu_dxs = {} self.ifu_dys = {} self.shot = shot self.fplane = FPlane(fplane_file) self._store_dither_positions(dither_positions) @classmethod def from_file(cls, fplane_file, shot, dither_positions_file): """Create an instance of the class reading the dither positions from a file. Parameters ---------- fplane_file : str path the focal plane file; it is parsed using :class:`~pyhetdex.het.fplane.FPlane` shot : :class:`pyhetdex.het.telescope.Shot` instance a ``shot`` instance that contains info on the image quality and normalization dither_positions_file : str path to file containing the positions of the dithers for each IHMPID; the file must have the following format:: ihmpid x1 x2 ... xn y1 y2 ... yn """ dither_positions = [] with open(dither_positions_file, 'r') as f: for line in f: els = line.strip().split() if '#' in els[0]: continue else: dither_positions.append(els) return cls(fplane_file, shot, dither_positions=dither_positions) def _store_dither_positions(self, dither_positions): '''Store the dither positions into the ``ifu_dxs`` and ``ifu_dys`` dictionaries''' for dp in dither_positions: key = dp[0] if len(dp[1:]) % 2 == 1: msg = ("The line '{}' has a miss-matching" " number of x and y entries") raise DitherPositionError(msg.format(dp)) else: n_x = len(dp[1:]) // 2 self.ifu_dxs[key] = array(dp[1:n_x + 1], dtype=float) self.ifu_dys[key] = array(dp[n_x + 1:], dtype=float) def dxs(self, id_, idtype='ifuslot'): """Returns the x shifts for the given ``id_`` Parameters ---------- id_ : str the id of the IFU idtype : str, optional type of the id; must be one of ``'ifuid'``, ``'ihmpid'``, ``'specid'`` Returns ------- ndarray x shifts """ ifu = self.fplane.by_id(id_, idtype=idtype) return self.ifu_dxs[ifu.ifuslot] def dys(self, id_, idtype='ifuslot'): """Returns the y shifts for the given ``id_`` Parameters ---------- id_ : str the id of the IFU idtype : str, optional type of the id; must be one of ``'ifuid'``, ``'ihmpid'``, ``'specid'`` Returns ------- ndarray y shifts """ ifu = self.fplane.by_id(id_, idtype=idtype) return self.ifu_dys[ifu.ifuslot] def create_dither(self, id_, basenames, modelbases, outfile, idtype='ifuid'): """ Create a dither file Parameters ---------- id_ : str the id of the IFU basenames, modelnames : list of strings the root of the file and model (distortion, fiber etc); their lengths must be the same as :attr:`ifu_dxs` outfile : str the output filename idtype : str, optional type of the id; must be one of ``'ifuid'``, ``'ifuslot'``, ``'specid'`` """ ifu = self.fplane.by_id(id_, idtype=idtype) dxs = self.dxs(id_, idtype=idtype) dys = self.dys(id_, idtype=idtype) if len(basenames) != len(dxs): msg = ("The number of elements in 'basenames' ({}) doesn't agree" " with the expected number of dithers" " ({})".format(len(basenames), len(dxs))) raise DitherCreationError(msg) if len(modelbases) != len(dxs): msg = ("The number of elements in 'modelbases' ({}) doesn't agree" " with the expected number of dithers" " ({})".format(len(modelbases), len(dxs))) raise DitherCreationError(msg) s = "# basename modelbase ditherx dithery\ seeing norm airmass\n" line = "{:s} {:s} {:f} {:f} {:4.3f} {:5.4f} {:5.4f}\n" for dither, bn, mb, dx, dy in zip(it.count(start=1), basenames, modelbases, dxs, dys): seeing = self.shot.fwhm(ifu.x, ifu.y, dither) norm = self.shot.normalisation(ifu.x, ifu.y, dither) # TODO replace with something airmass = 1.22 s += line.format(bn, mb, dx, dy, seeing, norm, airmass) with open(outfile, 'w') as f: f.write(s)
def generate_mangle_polyfile(args=None): """ Command line call to generate a Mangle polygon file in vertices format Mangle reference: http://space.mit.edu/~molly/mangle/ Parameters ---------- args : list (optional) list of arguments to parse. If None, grab from command line """ parser = argparse.ArgumentParser(description="""Generate a polygon file suitable for use in the Mangle mask software in vertices format. A line contains the four corners of an IFU in ra, dec. You can pass this to suitable Mangle commands, like poly2poly, with -iv4d input-type flag.""") parser.add_argument("shot_file", help="""An ascii file containing the header 'SHOTID RACEN DECCEN PARANGLE FPLANE' and appropriate entries. Coordinates should be given in degrees.""") parser.add_argument("out_file", help="""File name for the Mangle compatible polygon file""") parser.add_argument("rot_offset", help="Rotation difference to add to PARANGLE", default=0.0, type=float) opts = parser.parse_args(args=args) tables = [] try: table_shots = Table.read(opts.shot_file, format='ascii') except IOError as e: print("Problem opening input file {:s}".format(opts.shot_file)) raise e fplane_name_last = "" for row in table_shots: if row['FPLANE'] != fplane_name_last or not fplane: fplane = FPlane(row['FPLANE']) fplane_name_last = row['FPLANE'] # Carry out required changes to astrometry rot = 360.0 - (row['PARANGLE'] + 90.0 + opts.rot_offset) tp = TangentPlane(row['RACEN'], row['DECCEN'], rot) table = generate_ifu_corner_ra_decs(tp, fplane) print(row['SHOTID']) table['shotid'] = row['SHOTID'] tables.append(table) table_out = vstack(tables) table_out.write(opts.out_file, format='ascii.commented_header', comment='#')
def generate_ifu_mask(output_fn, survey_hdf, badshots_file, ramin, ramax, decmin, decmax, specific_shot=None, xsize=25.0, ysize=25.0, other_cuts={}, specific_field=None): """ Generate a mask of IFU corners from the survey HDF Parameters ---------- output_fn : str a file to output the ra/dec corners to survey_hdf : str path to the survey HDF badshots_file : str path to the bad shots file ramin, ramax, decmin, decmax : float restrict the mask to a subregion specific_shot : str (Optional) overides the ra and dec range and instead only outputs a bad mask only for the given shotid xsize, ysize : float half of the size of the IFU in x and y in arcseconds. Vertices are produced a +/-xsize and +/-ysize. Optional, default xsize=ysize=25.0 other_cuts : dict dictionary of shot property and a 2 element list of minimum and maximum allowed value """ # Read in the survey file survey_hdf = tb.open_file(survey_hdf) # Read bad shots file table_bad_shots = Table.read(badshots_file, format="ascii", names = ["shotid"]) bad_shots = array(table_bad_shots["shotid"]) if specific_shot is not None: survey_ttable = survey_hdf.root.Survey.read_where('shotid == specific_shot') elif specific_field is not None: survey_ttable = survey_hdf.root.Survey.read_where('field == specific_field') else: query = '(ra < ramax) & (ra > ramin) & (dec < decmax) & (dec > decmin)' for param, lims in iteritems(other_cuts): query += ' & ({:s} > {:f}) & ({:s} < {:f})'.format(param, lims[0], param, lims[1]) print(query) survey_ttable = survey_hdf.root.Survey.read_where(query) # Loop over the datevshots and see if there are bad amps polys = [] shids = [] for line in survey_ttable: print(line) # skip bad shots if line["shotid"] in bad_shots: continue date = line["date"] rot = 360.0 - (line["pa"] + 90.) tp = TangentPlane(line["ra"], line["dec"], rot) fplane_fn = "fplanes/{:d}_fplane.txt".format(date) if not isfile(fplane_fn): get_fplane(fplane_fn, datestr=str(date)) fplane = FPlane(fplane_fn) else: fplane = FPlane(fplane_fn) rect = [[-1.0*xsize, -1.0*xsize, xsize, xsize], [-1.0*ysize, ysize, ysize, -1.0*ysize]] for ifu in fplane.ifus: x = array(rect[0]) + ifu.y y = array(rect[1]) + ifu.x ra, dec = tp.xy2raDec(x, y) polys.append([ra[0], dec[0], ra[1], dec[1], ra[2], dec[2], ra[3], dec[3]]) shids.append(line["shotid"]) # Should now have a list of polygons to output with open(output_fn, "w") as fp: for poly, shid in zip(polys, shids): fp.write("{:7.6f} {:7.6f} {:7.6f} {:7.6f} {:7.6f} {:7.6f} {:7.6f} {:7.6f} {:d}\n".format(*poly, shid))
def generate_bad_amp_mask(output_fn, survey_hdf, badamps_file, badshots_file, ramin, ramax, decmin, decmax, specific_shot=None): """ Generate a Mangle-compatible list of ra/dec pairs corresponding to the corners of the bad amplifiers on the sky. The amplifiers are split into squares and rectangles to better follow their shape. Parameters ---------- output_fn : str a file to output the ra/dec corners to survey_hdf : str path to the survey HDF badamps_file : str path to the bad amps file ramin, ramax, decmin, decmax : float restrict the mask to a subregion specific_shot : str (Optional) overides the ra and dec range and instead only outputs a bad mask only for te given shotid """ # Read in the survey file survey_hdf = tb.open_file(survey_hdf) if specific_shot is not None: # 20190209027 survey_ttable = survey_hdf.root.Survey.read_where('shotid == specific_shot') else: survey_ttable = survey_hdf.root.Survey.read_where('(ra < ramax) & (ra > ramin) & (dec < decmax) & (dec > decmin)') # Read in the bad amps table_bad_amps = Table.read(badamps_file, format="ascii", names = ["IFUSLOT", "AMP", "multiframe", "start", "end"]) # Read bad shots file table_bad_shots = Table.read(badshots_file, format="ascii", names = ["shotid"]) bad_shots = array(table_bad_shots["shotid"]) # Loop over the datevshots and see if there are bad amps polys = [] amps = [] for line in survey_ttable: date = line["date"] fplane_fn = "fplanes/{:d}_fplane.txt".format(date) if not isfile(fplane_fn): get_fplane(fplane_fn, datestr=str(date)) else: fplane = FPlane(fplane_fn) if line["shotid"] in bad_shots: # if shot bad mask all IFUS print("Masking whole bad shot found {:d}".format(line["shotid"])) bad_amps_here = Table() bad_amps_here["IFUSLOT"] = [int(x) for x in fplane.ifuslots] bad_amps_here["AMP"] = "AA" else: # otherwise just mask bad amps sel = (table_bad_amps["start"] <= date) & (table_bad_amps["end"] >= date) bad_amps_here = table_bad_amps[sel] # If any, grab the focal plane and generate # a tangent plane for the astrometry if len(bad_amps_here) > 0: print("{:d} has bad amps. Adding to mask".format(line["shotid"])) rot = 360.0 - (line["pa"] + 90.) tp = TangentPlane(line["ra"], line["dec"], rot) for bad_amp in bad_amps_here: try: ifu = fplane.by_ifuslot("{:03d}".format(bad_amp["IFUSLOT"])) except NoIFUError: print("Warning. IFU {:d} not found for dateobs {:d}".format(bad_amp["IFUSLOT"], line["shotid"])) continue if bad_amp["AMP"] == "AA": # whole IFU with a little extra border rects_to_mask = [[[-30.0, -30.0, 30.0, 30.0], [-30.0, 30.0, 30.0, -30.0]]] else: # Check if the amps in this IFU are swapped around ampkey = "{:03d}{:s}".format(bad_amp["IFUSLOT"], bad_amp["AMP"]) if ampkey in swapped_around_amps: amp = swapped_around_amps[ampkey] else: amp = bad_amp["AMP"] # coordinates of amplifier for default dither and IFU cen rects_to_mask = amp_corners[amp] for rect in rects_to_mask: # Flip is correct x = array(rect[0]) + ifu.y y = array(rect[1]) + ifu.x ra, dec = tp.xy2raDec(x, y) polys.append([ra[0], dec[0], ra[1], dec[1], ra[2], dec[2], ra[3], dec[3]]) amps.append(bad_amp["AMP"]) # Should now have a list of polygons to output with open(output_fn, "w") as fp: for poly, amp in zip(polys, amps): fp.write("{:7.6f} {:7.6f} {:7.6f} {:7.6f} {:7.6f} {:7.6f} {:7.6f} {:7.6f} {:s}\n".format(*poly, amp))
def generate_bad_amp_mask_fits(output_fn, survey_hdf, badamps_fits, ramin, ramax, decmin, decmax, specific_shot = None): """ Generate a Mangle-compatible list of ra/dec pairs corresponding to the corners of the bad amplifiers on the sky. The amplifiers are split into squares and rectangles to better follow their shape. Parameters ---------- output_fn : str a file to output the ra/dec corners to survey_hdf : str path to the survey HDF badamps_file : str path to the bad amps file ramin, ramax, decmin, decmax : float restrict the mask to a subregion specific_shot : str (Optional) overides the ra and dec range and instead only outputs a bad mask only for te given shotid """ # Read in the survey file survey_hdf = tb.open_file(survey_hdf) if specific_shot is not None: # 20190209027 survey_ttable = survey_hdf.root.Survey.read_where('shotid == specific_shot') else: survey_ttable = survey_hdf.root.Survey.read_where('(ra < ramax) & (ra > ramin) & (dec < decmax) & (dec > decmin)') # Read in the bad amps table_bad_amps = Table.read(badamps_fits) table_bad_amps = table_bad_amps[table_bad_amps["flag"] == 0] pattern = re.compile("multi_[0-9]{3}_([0-9]{3})_[0-9]{3}_([RLU]{2})") table_bad_amps["AMP"] = [pattern.findall(x)[0][1] for x in table_bad_amps["multiframe"]] table_bad_amps["IFUSLOT"] = [pattern.findall(x)[0][0] for x in table_bad_amps["multiframe"]] # Loop over the datevshots and see if there are bad amps polys = [] amps = [] for line in survey_ttable: bad_amps_here = table_bad_amps[table_bad_amps["shotid"] == line["shotid"]] # If any, grab the focal plane and generate # a tangent plane for the astrometry if len(bad_amps_here) > 0: #print("{:d} has bad amps. Adding to mask".format(line["shotid"])) date = line["date"] fplane_fn = "fplanes/{:d}_fplane.txt".format(date) if not isfile(fplane_fn): get_fplane(fplane_fn, datestr=str(date)) else: fplane = FPlane(fplane_fn) rot = 360.0 - (line["pa"] + 90.) tp = TangentPlane(line["ra"], line["dec"], rot) for bad_amp in bad_amps_here: try: ifu = fplane.by_ifuslot("{:s}".format(bad_amp["IFUSLOT"])) except NoIFUError: print("Warning. IFU {:s} not found for dateobs {:d}".format(bad_amp["IFUSLOT"], line["shotid"])) continue # Check if the amps in this IFU are swapped around ampkey = "{:s}{:s}".format(bad_amp["IFUSLOT"], bad_amp["AMP"]) if ampkey in swapped_around_amps: amp = swapped_around_amps[ampkey] else: amp = bad_amp["AMP"] # coordinates of amplifier for default dither and IFU cen rects_to_mask = amp_corners[amp] for rect in rects_to_mask: # Flip is correct x = array(rect[0]) + ifu.y y = array(rect[1]) + ifu.x ra, dec = tp.xy2raDec(x, y) polys.append([ra[0], dec[0], ra[1], dec[1], ra[2], dec[2], ra[3], dec[3]]) amps.append(bad_amp["AMP"]) # Should now have a list of polygons to output with open(output_fn, "w") as fp: for poly, amp in zip(polys, amps): fp.write("{:7.6f} {:7.6f} {:7.6f} {:7.6f} {:7.6f} {:7.6f} {:7.6f} {:7.6f} {:s}\n".format(*poly, amp))
def __init__(self, datevshot, release=None, flim_model=None, rad=3.5, ffsky=False, wavenpix=3, d25scale=3.0, verbose=False, sclean_bad=True, log_level="WARNING"): self.conf = HDRconfig() self.extractor = Extract() self.shotid = int(datevshot.replace("v", "")) self.date = datevshot[:8] self.rad = rad self.ffsky = ffsky self.wavenpix = wavenpix self.sclean_bad = sclean_bad logger = logging.getLogger(name="ShotSensitivity") logger.setLevel(log_level) if verbose: raise DeprecationWarning( "Using verbose is deprecated, set log_level instead") logger.setLevel("DEBUG") logger.info("shotid: {:d}".format(self.shotid)) if not release: self.release = self.conf.LATEST_HDR_NAME else: self.release = release logger.info("Data release: {:s}".format(self.release)) self.survey = Survey(survey=self.release) # Set up flux limit model self.f50_from_noise, self.sinterp, interp_sigmas \ = return_flux_limit_model(flim_model, cache_sim_interp=False, verbose=verbose) # Generate astrometry for this shot survey_sel = (self.survey.shotid == self.shotid) self.shot_pa = self.survey.pa[survey_sel][0] self.shot_ra = self.survey.ra[survey_sel][0] self.shot_dec = self.survey.dec[survey_sel][0] rot = 360.0 - (self.shot_pa + 90.) self.tp = TangentPlane(self.shot_ra, self.shot_dec, rot) #Set up masking logger.info("Using d25scale {:f}".format(d25scale)) self.setup_mask(d25scale) # Set up spectral extraction if release == "hdr1": fwhm = self.survey.fwhm_moffat[survey_sel][0] else: fwhm = self.survey.fwhm_virus[survey_sel][0] logger.info("Using Moffat PSF with FWHM {:f}".format(fwhm)) self.moffat = self.extractor.moffat_psf(fwhm, 3. * rad, 0.25) self.extractor.load_shot(self.shotid, fibers=True, survey=self.release) # Set up the focal plane astrometry fplane_table = self.extractor.shoth5.root.Astrometry.fplane # Bit of a hack to avoid changing pyhetdex with NamedTemporaryFile(mode='w') as tpf: for row in fplane_table.iterrows(): tpf.write( "{:03d} {:8.5f} {:8.5f} {:03d} {:03d} {:03d} {:8.5f} {:8.5f}\n" .format(row['ifuslot'], row['fpx'], row['fpy'], row['specid'], row['specslot'], row['ifuid'], row['ifurot'], row['platesc'])) tpf.seek(0) self.fplane = FPlane(tpf.name)