def compute_sregion(image, extname='SCI'): """Compute the S_REGION keyword for a given WCS. Parameters ----------- image : Astropy io.fits HDUList object Image to update with the S_REGION keyword in each of the SCI extensions. extname : str, optional EXTNAME value for extension containing the WCS(s) to be updated """ # This function could, conceivably, be called directly... hdu, closefits = _process_input(image) # Find all extensions to be updated numext = countExtn(hdu, extname=extname) for extnum in range(1, numext + 1): sregion_str = 'POLYGON ICRS ' sciext = (extname, extnum) extwcs = wcsutil.HSTWCS(hdu, ext=sciext) footprint = extwcs.calc_footprint(center=True) for corner in footprint: sregion_str += '{} {} '.format(corner[0], corner[1]) hdu[sciext].header['s_region'] = sregion_str # close file if opened by this functions if closefits: hdu.close()
def archive_prefix_OPUS_WCS(fobj, extname='SCI'): """ Identifies WCS keywords which were generated by OPUS and archived using a prefix of 'O' for all 'SCI' extensions in the file Parameters ---------- fobj : str or `astropy.io.fits.HDUList` Filename or fits object of a file """ if stwcs is None: print('=====================') print( 'The STWCS package is needed to convert an old-style OPUS WCS to an alternate WCS' ) print('=====================') raise ImportError closefits = False if isinstance(fobj, str): # A filename was provided as input fobj = fits.open(fobj, mode='update') closefits = True # Define the header ext = ('sci', 1) hdr = fobj[ext].header numextn = fileutil.countExtn(fobj) extlist = [] for e in range(1, numextn + 1): extlist.append(('sci', e)) # Insure that the 'O' alternate WCS is present if 'O' not in wcsutil.wcskeys(hdr): # if not, archive the Primary WCS as the default OPUS WCS wcsutil.archiveWCS(fobj, extlist, wcskey='O', wcsname='OPUS') # find out how many SCI extensions are in the image numextn = fileutil.countExtn(fobj, extname=extname) if numextn == 0: extname = 'PRIMARY' # create HSTWCS object from PRIMARY WCS wcsobj = wcsutil.HSTWCS(fobj, ext=ext, wcskey='O') # get list of WCS keywords wcskeys = list(wcsobj.wcs2header().keys()) # For each SCI extension... for e in range(1, numextn + 1): # Now, look for any WCS keywords with a prefix of 'O' for key in wcskeys: okey = 'O' + key[:7] hdr = fobj[(extname, e)].header if okey in hdr: # Update alternate WCS keyword with prefix-O OPUS keyword value hdr[key] = hdr[okey] if closefits: fobj.close()
def get_hstwcs(filename, hdulist, extnum): """ Return the HSTWCS object for a given chip. """ hdrwcs = wcsutil.HSTWCS(hdulist, ext=extnum) hdrwcs.filename = filename hdrwcs.expname = hdulist[extnum].header['expname'] hdrwcs.extver = hdulist[extnum].header['extver'] return hdrwcs
def get_hstwcs(filename, extnum): """ Return the HSTWCS object for a given chip. """ hdrwcs = wcsutil.HSTWCS(filename, ext=extnum) hdrwcs.filename = filename hdrwcs.expname = pyfits.getval(filename, 'expname', ext=extnum) hdrwcs.extver = pyfits.getval(filename, 'extver', ext=extnum) return hdrwcs
def findWCSExtn(filename): """ Return new filename with extension that points to an extension with a valid WCS. Returns ======= extnum : str, None Value of extension name as a string either as provided by the user or based on the extension number for the first extension which contains a valid HSTWCS object. Returns None if no extension can be found with a valid WCS. Notes ===== The return value from this function can be used as input to create another HSTWCS with the syntax:: `HSTWCS('{}[{}]'.format(filename,extnum)) """ rootname, extroot = fileutil.parseFilename(filename) extnum = None if extroot is None: fimg = fits.open(rootname, memmap=False) for i, extn in enumerate(fimg): if 'crval1' in extn.header: refwcs = wcsutil.HSTWCS('{}[{}]'.format(rootname, i)) if refwcs.wcs.has_cd(): extnum = '{}'.format(i) break fimg.close() else: try: refwcs = wcsutil.HSTWCS(filename) if refwcs.wcs.has_cd(): extnum = extroot except: extnum = None return extnum
def read_hlet_wcs(filename, ext): """Insure HSTWCS includes all attributes of a full image WCS. For headerlets, the WCS does not contain information about the size of the image, as the image array is not present in the headerlet. """ hstwcs = wcsutil.HSTWCS(filename, ext=ext) if hstwcs.naxis1 is None: hstwcs.naxis1 = int(hstwcs.wcs.crpix[0] * 2.) # Assume crpix is center of chip hstwcs.naxis2 = int(hstwcs.wcs.crpix[1] * 2.) return hstwcs
def create_image_footprint(image, refwcs, border=0.): """ Create the footprint of the image in the reference WCS frame Parameters ---------- image : HDUList or filename Image to extract sources for matching to the external astrometric catalog refwcs : object Reference WCS for coordinate frame of image border : float Buffer (in arcseconds) around edge of image to exclude astrometric sources. Default: 0. """ # Interpret input image to generate initial source catalog and WCS if isinstance(image, str): image = pf.open(image) numSci = countExtn(image, extname='SCI') ref_x = refwcs._naxis1 ref_y = refwcs._naxis2 # convert border value into pixels border_pixels = int(border / refwcs.pscale) mask_arr = np.zeros((ref_y, ref_x), dtype=int) for chip in range(numSci): chip += 1 # Build arrays of pixel positions for all edges of chip chip_y, chip_x = image['sci', chip].data.shape chipwcs = wcsutil.HSTWCS(image, ext=('sci', chip)) xpix = np.arange(chip_x) + 1 ypix = np.arange(chip_y) + 1 edge_x = np.hstack([[1] * chip_y, xpix, [chip_x] * chip_y, xpix]) edge_y = np.hstack([ypix, [1] * chip_x, ypix, [chip_y] * chip_x]) edge_ra, edge_dec = chipwcs.all_pix2world(edge_x, edge_y, 1) edge_x_out, edge_y_out = refwcs.all_world2pix(edge_ra, edge_dec, 0) edge_x_out = np.clip(edge_x_out.astype(np.int32), 0, ref_x - 1) edge_y_out = np.clip(edge_y_out.astype(np.int32), 0, ref_y - 1) mask_arr[edge_y_out, edge_x_out] = 1 # Fill in outline of each chip mask_arr = ndimage.binary_fill_holes( ndimage.binary_dilation(mask_arr, iterations=2)) if border > 0.: mask_arr = ndimage.binary_erosion(mask_arr, iterations=border_pixels) return mask_arr
def compute_sregion(image, extname='SCI'): """Compute the S_REGION keyword for a given WCS. Parameters ----------- image : Astropy io.fits HDUList object Image to update with the S_REGION keyword in each of the SCI extensions. extname : str, optional EXTNAME value for extension containing the WCS(s) to be updated """ # This function could, conceivably, be called directly... hdu, closefits = _process_input(image) # Find all extensions to be updated numext = countExtn(hdu, extname=extname) for extnum in range(1, numext + 1): sciext = (extname, extnum) if 'd001data' not in hdu[0].header: sregion_str = 'POLYGON ICRS ' # Working with FLT/FLC file, so simply use # the array corners directly extwcs = wcsutil.HSTWCS(hdu, ext=sciext) footprint = extwcs.calc_footprint(center=True) for corner in footprint: sregion_str += '{} {} '.format(corner[0], corner[1]) else: if hdu[(extname, extnum)].data.min() == 0 and hdu[(extname, extnum)].data.max() == 0: continue # Working with a drizzled image, so we need to # get all the corners from each of the input files footprint = find_footprint(hdu, extname=extname, extnum=extnum) sregion_str = '' for region in footprint.corners: # S_REGION string should contain a separate POLYGON # for each region or chip in the SCI array sregion_str += 'POLYGON ICRS ' for corner in region: sregion_str += '{} {} '.format(corner[0], corner[1]) hdu[sciext].header['s_region'] = sregion_str # close file if opened by this functions if closefits: hdu.close()
def build_hstwcs(crval1, crval2, crpix1, crpix2, naxis1, naxis2, pscale, orientat): """ Create an HSTWCS object for a default instrument without distortion based on user provided parameter values. """ wcsout = wcsutil.HSTWCS() wcsout.wcs.crval = np.array([crval1, crval2]) wcsout.wcs.crpix = np.array([crpix1, crpix2]) wcsout.naxis1 = naxis1 wcsout.naxis2 = naxis2 wcsout.wcs.cd = fileutil.buildRotMatrix(orientat) * [-1, 1] * pscale / 3600.0 # Synchronize updates with astropy.wcs/WCSLIB objects wcsout.wcs.set() wcsout.setPscale() wcsout.setOrient() wcsout.wcs.ctype = ['RA---TAN', 'DEC--TAN'] return wcsout
def build_hstwcs(crval, crpix, naxis1, naxis2, pscale, orientat): """ Create an `~stwcs.wcsutil.HSTWCS` object for a default instrument without distortion based on user provided parameter values. """ wcsout = wcsutil.HSTWCS() wcsout.wcs.crval = crval.copy() wcsout.wcs.crpix = crpix.copy() wcsout.naxis1 = naxis1 wcsout.naxis2 = naxis2 wcsout.wcs.cd = fu.buildRotMatrix(orientat) * [-1, 1] * pscale / 3600.0 # Synchronize updates with astropy.wcs objects wcsout.wcs.set() wcsout.setPscale() wcsout.setOrient() wcsout.wcs.ctype = ['RA---TAN', 'DEC--TAN'] return wcsout
def verifyRefimage(refimage): """ Verify that the value of refimage specified by the user points to an extension with a proper WCS defined. It starts by making sure an extension gets specified by the user when using a MEF file. The final check comes by looking for a CD matrix in the WCS object itself. If either test fails, it returns a value of False. """ valid = True # start by trying to see whether the code can even find the file if is_blank(refimage): return True if isinstance(refimage, astropy.wcs.WCS): return True refroot, extroot = fileutil.parseFilename(refimage) if not os.path.exists(refroot): return False # if a MEF has been specified, make sure extension contains a valid WCS if valid: if extroot is None: extn = findWCSExtn(refimage) if extn is None: valid = False else: valid = True else: # check for CD matrix in WCS object refwcs = wcsutil.HSTWCS(refimage) if not refwcs.wcs.has_cd(): valid = False else: valid = True del refwcs return valid
def build_reference_wcs(inputs, sciname='sci'): """Create the reference WCS based on all the inputs for a field""" # start by creating a composite field-of-view for all inputs wcslist = [] for img in inputs: nsci = countExtn(img) for num in range(nsci): extname = (sciname, num + 1) if sciname == 'sci': extwcs = wcsutil.HSTWCS(img, ext=extname) else: # Working with HDRLET as input and do the best we can... extwcs = read_hlet_wcs(img, ext=extname) wcslist.append(extwcs) # This default output WCS will have the same plate-scale and orientation # as the first chip in the list, which for WFPC2 data means the PC. # Fortunately, for alignment, this doesn't matter since no resampling of # data will be performed outwcs = utils.output_wcs(wcslist) return outwcs
def find_footprint(hdu, extname='SCI', extnum=1): """Extract the footprints from each input file Determine the composite of all the input chip's corners as the footprint for the entire set of overlapping images that went into creating this drizzled image. Parameters =========== hdu : str or `fits.HDUList` Filename or HDUList for a drizzled image extname : str, optional Name of science array extension (extname value) extnum : int, optional EXTVER value for science array extension Returns ======== footprint : ndarray Array of RA/Dec for the 4 corners that comprise the footprint on the sky for this mosaic. Values were determined from northern-most corner counter-clockwise to the rest. """ # extract WCS from this product meta_wcs = wcsutil.HSTWCS(hdu, ext=(extname, extnum)) # create SkyFootprint object for all input_files to determine footprint footprint = SkyFootprint(meta_wcs=meta_wcs) # create mask of all input chips as they overlap on the product WCS footprint.extract_mask(hdu.filename()) # Now, find the corners from this mask footprint.find_corners() return footprint
def run_sourcelist_flagging(filter_product_obj, filter_product_catalogs, log_level, diagnostic_mode=False): """ Super-basic and profoundly inelegant interface to hla_flag_filter.py. Execute haputils.hla_flag_filter.run_source_list_flaging() to populate the "Flags" column in the catalog tables generated by HAPcatalogs.measure(). Parameters ---------- filter_product_obj : drizzlepac.hlautils.product.FilterProduct object object containing all the relevant info for the drizzled filter product filter_product_catalogs : drizzlepac.hlautils.catalog_utils.HAPCatalogs object drizzled filter product catalog object log_level : int The desired level of verboseness in the log statements displayed on the screen and written to the .log file. diagnostic_mode : Boolean, optional. create intermediate diagnostic files? Default value is False. Returns ------- filter_product_catalogs : drizzlepac.hlautils.catalog_utils.HAPCatalogs object updated version of filter_product_catalogs object with fully populated source flags """ drizzled_image = filter_product_obj.drizzle_filename flt_list = [] for edp_obj in filter_product_obj.edp_list: flt_list.append(edp_obj.full_filename) param_dict = filter_product_obj.configobj_pars.as_single_giant_dict() plate_scale = wcsutil.HSTWCS(drizzled_image, ext=('sci', 1)).pscale median_sky = filter_product_catalogs.image.bkg_median # Create mask array that will be used by hla_flag_filter.hla_nexp_flags() for both point and segment catalogs. if not hasattr(filter_product_obj, 'hla_flag_msk'): filter_product_obj.hla_flag_msk = hla_flag_filter.make_mask_array( drizzled_image) if filter_product_obj.configobj_pars.use_defaults: ci_lookup_file_path = "default_parameters/any" else: ci_lookup_file_path = "user_parameters/any" output_custom_pars_file = filter_product_obj.configobj_pars.output_custom_pars_file for cat_type in filter_product_catalogs.catalogs.keys(): exptime = filter_product_catalogs.catalogs[cat_type].image.imghdu[ 0].header[ 'exptime'] # TODO: This works for ACS. Make sure that it also works for WFC3. Look at "TEXPTIME" catalog_name = filter_product_catalogs.catalogs[ cat_type].sourcelist_filename catalog_data = filter_product_catalogs.catalogs[cat_type].source_cat drz_root_dir = os.getcwd() log.info("Run source list flagging on catalog file {}.".format( catalog_name)) filter_product_catalogs.catalogs[ cat_type].source_cat = hla_flag_filter.run_source_list_flagging( drizzled_image, flt_list, param_dict, exptime, plate_scale, median_sky, catalog_name, catalog_data, cat_type, drz_root_dir, filter_product_obj.hla_flag_msk, ci_lookup_file_path, output_custom_pars_file, log_level, diagnostic_mode) return filter_product_catalogs
def rd2xy(input, ra=None, dec=None, coordfile=None, colnames=None, precision=6, output=None, verbose=True): """ Primary interface to perform coordinate transformations from pixel to sky coordinates using STWCS and full distortion models read from the input image header. """ single_coord = False if coordfile is not None: if colnames in blank_list: colnames = ['c1', 'c2'] elif isinstance(colnames, type('a')): colnames = colnames.split(',') # convert input file coordinates to lists of decimal degrees values xlist, ylist = tweakutils.readcols(coordfile, cols=colnames) else: if isinstance(ra, np.ndarray): ralist = ra.tolist() declist = dec.tolist() elif not isinstance(ra, list): ralist = [ra] declist = [dec] else: ralist = ra declist = dec xlist = [0] * len(ralist) ylist = [0] * len(ralist) if len(xlist) == 1: single_coord = True for i, (r, d) in enumerate(zip(ralist, declist)): # convert input value into decimal degrees value xval, yval = tweakutils.parse_skypos(r, d) xlist[i] = xval ylist[i] = yval # start by reading in WCS+distortion info for input image inwcs = wcsutil.HSTWCS(input) if inwcs.wcs.is_unity(): print( "####\nNo valid WCS found in {}.\n Results may be invalid.\n####\n" .format(input)) # Now, convert pixel coordinates into sky coordinates try: outx, outy = inwcs.all_world2pix(xlist, ylist, 1) except RuntimeError: outx, outy = inwcs.wcs_world2pix(xlist, ylist, 1) # add formatting based on precision here... xstr = [] ystr = [] fmt = "%." + repr(precision) + "f" for x, y in zip(outx, outy): xstr.append(fmt % x) ystr.append(fmt % y) if verbose or (not verbose and util.is_blank(output)): print('# Coordinate transformations for ', input) print('# X Y RA Dec\n') for x, y, r, d in zip(xstr, ystr, xlist, ylist): print("%s %s %s %s" % (x, y, r, d)) # Create output file, if specified if output: f = open(output, mode='w') f.write("# Coordinates converted from %s\n" % input) for x, y in zip(xstr, ystr): f.write('%s %s\n' % (x, y)) f.close() print('Wrote out results to: ', output) if single_coord: outx = outx[0] outy = outy[0] return outx, outy """ Convert colnames input into list of column numbers """ cols = [] if not isinstance(colnames, list): colnames = colnames.split(',') # parse column names from coords file and match to input values if coordfile is not None and fileutil.isFits(coordfile)[0]: # Open FITS file with table ftab = fits.open(coordfile) # determine which extension has the table for extn in ftab: if isinstance(extn, fits.BinTableHDU): # parse column names from table and match to inputs cnames = extn.columns.names if colnames is not None: for c in colnames: for name, i in zip(cnames, list(range(len(cnames)))): if c == name.lower(): cols.append(i) if len(cols) < len(colnames): errmsg = "Not all input columns found in table..." ftab.close() raise ValueError(errmsg) else: cols = cnames[:2] break ftab.close() else: for c in colnames: if isinstance(c, str): if c[0].lower() == 'c': cols.append(int(c[1:]) - 1) else: cols.append(int(c)) else: if isinstance(c, int): cols.append(c) else: errmsg = "Unsupported column names..." raise ValueError(errmsg) return cols
def create_product_page(prodname, zoom_size=128, wcsname="", gcolor='magenta', fsize=8): """Create a matplotlib Figure() object which summarizes this product FITS file.""" # obtain image data to display with fits.open(prodname) as prod: data = prod[1].data phdr = prod[0].header if 'texptime' not in phdr: return None, None targname = phdr['targname'] inst = phdr['instrume'] det = phdr['detector'] texptime = phdr['texptime'] inexp = phdr['d001data'].split('[')[0] wcstype = prod[1].header['wcstype'] wcs = wcsutil.HSTWCS(prod, ext=1) hdrtab = prod['hdrtab'].data filters = ';'.join([phdr[f] for f in phdr['filter*']]) dateobs = phdr['date-obs'] # human-readable date expstart = phdr['expstart'] # MJD float value asnid = phdr.get('asn_id', '') center = (data.shape[0] // 2, data.shape[1] // 2) prod_path = os.path.split(prodname)[0] data = np.nan_to_num(data, 0.0) # Get GAIA catalog refcat = amutils.create_astrometric_catalog(prodname, existing_wcs=wcs, output=None) refx, refy = wcs.all_world2pix(refcat['RA'], refcat['DEC'], 0) # Remove points outside the full-size image area rx = [] ry = [] zx = [] zy = [] for x, y in zip(refx, refy): if 0 < x < data.shape[1] and 0 < y < data.shape[0]: rx.append(x) ry.append(y) if -zoom_size < x - center[1] < zoom_size and -zoom_size < y - center[ 0] < zoom_size: zx.append(x - center[1] + zoom_size) zy.append(y - center[0] + zoom_size) # Define subplot regions on page fig = plt.figure(constrained_layout=True, figsize=(8.5, 11)) gs = fig.add_gridspec(ncols=4, nrows=5) # title plots rootname = os.path.basename(prodname) img_title = "{} image of {} with WCSNAME={}".format( rootname, targname, wcsname) fig.suptitle(img_title, ha='center', va='top', fontsize=fsize) # Define image display fig_img = fig.add_subplot(gs[:3, :]) fig_zoom = fig.add_subplot(gs[3:, 2:]) fig_summary = fig.add_subplot(gs[3:, :2]) # Compute display range dmax = (data.max() // 10) if data.max() <= 1000. else 100 dscaled = np.log10(np.clip(data, -0.1, dmax) + 0.10001) # identify zoom region around center of data zoom = dscaled[center[0] - zoom_size:center[0] + zoom_size, center[1] - zoom_size:center[1] + zoom_size] # display full image fig_img.imshow(dscaled, cmap='gray', origin='lower') # display zoomed section fig_zoom.imshow(zoom, cmap='gray', origin='lower') # define markerstyle mstyle = markers.MarkerStyle(marker='o') mstyle.set_fillstyle('none') # plot GAIA sources onto full size image fig_img.scatter(rx, ry, marker=mstyle, alpha=0.35, c=gcolor, s=3) fig_zoom.scatter(zx, zy, marker=mstyle, alpha=0.35, c=gcolor) # Print summary info pname = os.path.split(prodname)[1] fig_summary.text(0.01, 0.95, "Summary for {}".format(pname), fontsize=fsize) fig_summary.text(0.01, 0.9, "WCSNAME: {}".format(wcsname), fontsize=fsize) fig_summary.text(0.01, 0.85, "TARGET: {}".format(targname), fontsize=fsize) fig_summary.text(0.01, 0.8, "Instrument: {}/{}".format(inst, det), fontsize=fsize) fig_summary.text(0.01, 0.75, "Filters: {}".format(filters), fontsize=fsize) fig_summary.text(0.01, 0.7, "Total Exptime: {}".format(texptime), fontsize=fsize) fig_summary.text(0.01, 0.65, "WCSTYPE: {}".format(wcstype), fontsize=fsize) fig_summary.text(0.01, 0.5, "Total # of GAIA sources: {}".format(len(refx)), fontsize=fsize) fig_summary.text(0.01, 0.45, "# of GAIA matches: {}".format(len(rx)), fontsize=fsize) # Get extended information about observation hdrtab_cols = hdrtab.columns.names mtflag = get_col_val(hdrtab, 'mtflag', default="") gyromode = get_col_val(hdrtab, 'gyromode', default='N/A') # populate JSON summary info summary = dict(wcsname=wcsname, targname=targname, asnid=asnid, dateobs=dateobs, expstart=expstart, instrument=(inst, det), exptime=texptime, wcstype=wcstype, num_gaia=len(refx), filters=filters, rms_ra=-1, rms_dec=-1, nmatch=-1, catalog="") obs_kws = [ 'gyromode', 'fgslock', 'aperture', 'mtflag', 'subarray', 'obstype', 'obsmode', 'scan_typ', 'photmode' ] for kw in obs_kws: summary[kw] = get_col_val(hdrtab, kw, default="") if 'FIT' in wcsname: # Look for FIT RMS and other stats from headerlet exp = fits.open(os.path.join(prod_path, inexp)) for ext in exp: if 'extname' in ext.header and ext.header['extname'] == 'HDRLET' \ and ext.header['wcsname'] == wcsname: hdrlet = ext.headerlet rms_ra = hdrlet[0].header['rms_ra'] rms_dec = hdrlet[0].header['rms_dec'] nmatch = hdrlet[0].header['nmatch'] catalog = hdrlet[0].header['catalog'] fit_vals = dict(rms_ra=rms_ra, rms_dec=rms_dec, nmatch=nmatch, catalog=catalog) summary.update(fit_vals) break exp.close() try: fig_summary.text(0.01, 0.4, "RMS: RA={:0.3f}mas, DEC={:0.3f}mas".format( rms_ra, rms_dec), fontsize=fsize) fig_summary.text(0.01, 0.35, "# matches: {}".format(nmatch), fontsize=fsize) fig_summary.text(0.01, 0.3, "Matched to {} catalog".format(catalog), fontsize=fsize) except: fig_summary.text(0.01, 0.35, "No MATCH to GAIA") print("Data without a match to GAIA: {},{}".format(inexp, wcsname)) return fig, summary
def generate_sky_catalog(image, refwcs, **kwargs): """Build source catalog from input image using photutils. This script borrows heavily from build_source_catalog The catalog returned by this function includes sources found in all chips of the input image with the positions translated to the coordinate frame defined by the reference WCS `refwcs`. The sources will be - identified using photutils segmentation-based source finding code - ignore any input pixel which has been flagged as 'bad' in the DQ array, should a DQ array be found in the input HDUList. - classified as probable cosmic-rays (if enabled) using central_moments properties of each source, with these sources being removed from the catalog. Parameters ----------- image : HDUList object Input image as an astropy.io.fits HDUList object refwcs : HSTWCS object Definition of the reference frame WCS. dqname : string EXTNAME for the DQ array, if present, in the input image HDUList. output : boolean Specify whether or not to write out a separate catalog file for all the sources found in each chip. Default: None (False) Optional Parameters -------------------- threshold : float, optional This parameter controls the S/N threshold used for identifying sources in the image relative to the background RMS in much the same way that the 'threshold' parameter in 'tweakreg' works. Default: 1000. fwhm : float, optional FWHM (in pixels) of the expected sources from the image, comparable to the 'conv_width' parameter from 'tweakreg'. Objects with FWHM closest to this value will be identified as sources in the catalog. Default: 3.0. Returns -------- master_cat : astropy.Table object Source catalog for all 'valid' sources identified from all chips of the input image with positions translated to the reference WCS coordinate frame. """ # Extract source catalogs for each chip source_cats = generate_source_catalog(image, **kwargs) # Build source catalog for entire image master_cat = None numSci = countExtn(image, extname='SCI') # if no refwcs specified, build one now... if refwcs is None: refwcs = build_reference_wcs([image]) for chip in range(numSci): chip += 1 # work with sources identified from this specific chip seg_tab_phot = source_cats[chip] # Convert pixel coordinates from this chip to sky coordinates chip_wcs = wcsutil.HSTWCS(image, ext=('sci', chip)) seg_ra, seg_dec = chip_wcs.all_pix2world(seg_tab_phot['xcentroid'], seg_tab_phot['ycentroid'], 1) # Convert sky positions to pixel positions in the reference WCS frame seg_xy_out = refwcs.all_world2pix(seg_ra, seg_dec, 1) seg_tab_phot['xcentroid'] = seg_xy_out[0] seg_tab_phot['ycentroid'] = seg_xy_out[1] if master_cat is None: master_cat = seg_tab_phot else: master_cat = vstack([master_cat, seg_tab_phot]) return master_cat
def rd2xy(input, ra=None, dec=None, coordfile=None, colnames=None, precision=6, output=None, verbose=True): """ Primary interface to perform coordinate transformations from pixel to sky coordinates using STWCS and full distortion models read from the input image header. """ single_coord = False if coordfile is not None: if colnames in blank_list: colnames = ['c1', 'c2'] elif isinstance(colnames, type('a')): colnames = colnames.split(',') # convert input file coordinates to lists of decimal degrees values xlist, ylist = tweakutils.readcols(coordfile, cols=colnames) else: if isinstance(ra, np.ndarray): ralist = ra.tolist() declist = dec.tolist() elif not isinstance(ra, list): ralist = [ra] declist = [dec] else: ralist = ra declist = dec xlist = [0] * len(ralist) ylist = [0] * len(ralist) if len(xlist) == 1: single_coord = True for i, (r, d) in enumerate(zip(ralist, declist)): # convert input value into decimal degrees value xval, yval = tweakutils.parse_skypos(r, d) xlist[i] = xval ylist[i] = yval # start by reading in WCS+distortion info for input image inwcs = wcsutil.HSTWCS(input) if inwcs.wcs.is_unity(): print( "####\nNo valid WCS found in {}.\n Results may be invalid.\n####\n" .format(input)) # Now, convert pixel coordinates into sky coordinates try: outx, outy = inwcs.all_world2pix(xlist, ylist, 1) except RuntimeError: outx, outy = inwcs.wcs_world2pix(xlist, ylist, 1) # add formatting based on precision here... xstr = [] ystr = [] fmt = "%." + repr(precision) + "f" for x, y in zip(outx, outy): xstr.append(fmt % x) ystr.append(fmt % y) if verbose or (not verbose and util.is_blank(output)): print('# Coordinate transformations for ', input) print('# X Y RA Dec\n') for x, y, r, d in zip(xstr, ystr, xlist, ylist): print("%s %s %s %s" % (x, y, r, d)) # Create output file, if specified if output: f = open(output, mode='w') f.write("# Coordinates converted from %s\n" % input) for x, y in zip(xstr, ystr): f.write('%s %s\n' % (x, y)) f.close() print('Wrote out results to: ', output) if single_coord: outx = outx[0] outy = outy[0] return outx, outy
def build(outname, wcsname, refimage, undistort=False, applycoeffs=False, coeffsfile=None, **wcspars): """ Core functionality to create a WCS instance from a reference image WCS, user supplied parameters or user adjusted reference WCS. The distortion information can either be read in as part of the reference image WCS or given in 'coeffsfile'. Parameters ---------- outname : string filename of output WCS wcsname : string WCSNAME ID for generated WCS refimage : string filename of image with source WCS used as basis for output WCS undistort : bool Create an undistorted WCS? applycoeffs : bool Apply coefficients from refimage to generate undistorted WCS? coeffsfile : string If specified, read distortion coeffs from separate file """ # Insure that the User WCS parameters have values for all the parameters, # even if that value is 'None' user_wcs_pars = convert_user_pars(wcspars) userwcs = wcspars['userwcs'] """ Use cases to document the logic required to interpret the parameters WCS generation based on refimage/userwcs parameters ------------------------------------------------------------- refimage == None, userwcs == False: *NO WCS specified* => print a WARNING message and return without doing anything refimage == None, userwcs == True: => Create WCS without a distortion model entirely from user parameters* refimage != None, userwcs == False: => No user WCS parameters specified => Simply use refimage WCS as specified refimage != None, userwcs == True: => Update refimage WCS with user specified values* Apply distortion and generate final headerlet using processed WCS ----------------------------------------------------------------- refimage == None, userwcs == True: *Output WCS generated entirely from user supplied parameters* Case 1: applycoeffs == False, undistort == True/False (ignored) => no distortion model to interpret => generate undistorted headerlet with no distortion model Case 2: applycoeffs == True/False, undistort == True => ignore any user specified distortion model => generate undistorted headerlet with no distortion model Case 3: applycoeffs == True, undistort == False => WCS from scratch combined with distortion model from another image => generate headerlet with distortion model refimage != None, userwcs == True/False: *Output WCS generated from reference image possibly modified by user parameters* Case 4: applycoeffs == False, undistort == True => If refimage has distortion, remove it => generate undistorted headerlet with no distortion model Case 5: applycoeffs == False, undistort == False => Leave refimage distortion model (if any) unmodified => generate a headerlet using same distortion model (if any) as refimage Case 6: applycoeffs == True, undistort == False => Update refimage with distortion model with user-specified model => generate a headerlet with a distortion model Case 7: applycoeffs == True, undistort == True => ignore user specified distortion model and undistort WCS => generate a headerlet without a distortion model """ ### Build WCS from refimage and/or user pars if util.is_blank(refimage) and not userwcs: print('WARNING: No WCS specified... No WCS created!') return customwcs = None if util.is_blank(refimage) and userwcs: # create HSTWCS object from user parameters complete_wcs = True for key in user_wcs_pars: if util.is_blank(user_wcs_pars[key]): complete_wcs = False break if complete_wcs: customwcs = wcs_functions.build_hstwcs( user_wcs_pars['crval1'], user_wcs_pars['crval2'], user_wcs_pars['crpix1'], user_wcs_pars['crpix2'], user_wcs_pars['naxis1'], user_wcs_pars['naxis2'], user_wcs_pars['pscale'], user_wcs_pars['orientat']) else: print('WARNING: Not enough WCS information provided by user!') raise ValueError if not util.is_blank(refimage): refwcs = stwcs.wcsutil.HSTWCS(refimage) else: refwcs = customwcs ### Apply distortion model (if any) to update WCS if applycoeffs and not util.is_blank(coeffsfile): if not util.is_blank(refimage): replace_model(refwcs, coeffsfile) else: if not undistort: add_model(refwcs, coeffsfile) # Only working with custom WCS from user, no distortion # so apply model to WCS, including modifying the CD matrix apply_model(refwcs) ### Create undistorted WCS, if requested if undistort: outwcs = undistortWCS(refwcs) else: outwcs = refwcs if userwcs: # replace (some/all?) WCS values from refimage with user WCS values # by running 'updatewcs' functions on input WCS outwcs = mergewcs(outwcs, customwcs, user_wcs_pars) ### Create the final headerlet and write it out, if specified if not util.is_blank(refimage): template = refimage elif not util.is_blank(coeffsfile): template = coeffsfile else: template = None # create default WCSNAME if None was given wcsname = create_WCSname(wcsname) print('Creating final headerlet with name ', wcsname, ' using template ', template) outhdr = generate_headerlet(outwcs, template, wcsname, outname=outname) # synchronize this new WCS with the rest of the chips in the image for ext in outhdr: if 'extname' in ext.header and ext.header['extname'] == 'SIPWCS': ext_wcs = wcsutil.HSTWCS(ext) stwcs.updatewcs.makewcs.MakeWCS.updateWCS(ext_wcs, outwcs) return outwcs
def build_wcscat(image, group_id, source_catalog): """ Return a list of `~tweakwcs.tpwcs.FITSWCS` objects for all chips in an image. Parameters ---------- image : str, `~astropy.io.fits.HDUList` Either filename or HDUList of a single HST observation. group_id : int Integer ID for group this image should be associated with; primarily used when separate chips are in separate files to treat them all as one exposure. source_catalog : dict If provided, these catalogs will be attached as `catalog` entries in each chip's ``FITSWCS`` object. It should be provided as a dict of astropy Tables identified by chip number with each table containing sources from image extension ``('sci', chip)`` as generated by `generate_source_catalog()`. Returns ------- wcs_catalogs : list of `~tweakwcs.tpwcs.FITSWCS` List of `~tweakwcs.tpwcs.FITSWCS` objects defined for all chips in input image. """ open_file = False if isinstance(image, str): hdulist = fits.open(image) open_file = True elif isinstance(image, fits.HDUList): hdulist = image else: log.info("Wrong type of input, {}, for build_wcscat...".format( type(image))) raise ValueError wcs_catalogs = [] numsci = countExtn(hdulist) for chip in range(1, numsci + 1): w = wcsutil.HSTWCS(hdulist, ('SCI', chip)) imcat = source_catalog[chip] # rename xcentroid/ycentroid columns, if necessary, to be consistent with tweakwcs if 'xcentroid' in imcat.colnames: imcat.rename_column('xcentroid', 'x') imcat.rename_column('ycentroid', 'y') wcscat = FITSWCS(w, meta={ 'chip': chip, 'group_id': group_id, 'filename': image, 'catalog': imcat, 'name': image }) wcs_catalogs.append(wcscat) if open_file: hdulist.close() return wcs_catalogs
def generate_gaia_catalog(hap_obj, columns_to_remove=None): """Uses astrometric_utils.create_astrometric_catalog() to create a catalog of all GAIA sources in the image footprint. This catalog contains right ascension, declination, and magnitude values, and is sorted in descending order by brightness. Parameters ---------- hap_obj : drizzlepac.hlautils.Product.TotalProduct, drizzlepac.hlautils.Product.FilterProduct, or drizzlepac.hlautils.Product.ExposureProduct, depending on input. hap product object to process Returns ------- gaia_table : astropy table table containing right ascension, declination, and magnitude of all GAIA sources identified in the image footprint, sorted in descending order by brightness. """ # Gather list of input flc/flt images img_list = [] log.debug("GAIA catalog will be created using the following input images:") # Create a list of the input flc.fits/flt.fits that were drizzled to create the final HAP product being # processed here. edp_item.info and hap_obj.info are both structured as follows: # <proposal id>_<visit #>_<instrument>_<detector>_<input filename>_<filter>_<drizzled product # image filetype> # Example: '10265_01_acs_wfc_j92c01b9q_flc.fits_f606w_drc' # what is being extracted here is just the input filename, which in this case is 'j92c01b9q_flc.fits'. if hasattr(hap_obj, "edp_list"): # for total and filter product objects for edp_item in hap_obj.edp_list: parse_info = edp_item.info.split("_") imgname = "{}_{}".format(parse_info[4], parse_info[5]) log.debug(imgname) img_list.append(imgname) else: # For single-exposure product objects parse_info = hap_obj.info.split("_") imgname = "{}_{}".format(parse_info[4], parse_info[5]) log.debug(imgname) img_list.append(imgname) # generate catalog of GAIA sources gaia_table = au.create_astrometric_catalog(img_list, gaia_only=True, use_footprint=True) # trim off specified columns if columns_to_remove: gaia_table.remove_columns(columns_to_remove) # remove sources outside image footprint outwcs = wcsutil.HSTWCS(hap_obj.drizzle_filename, ext=1) x, y = outwcs.all_world2pix(gaia_table['RA'], gaia_table['DEC'], 1) imghdu = fits.open(hap_obj.drizzle_filename) in_img_data = imghdu['WHT'].data.copy() in_img_data = np.where(in_img_data == 0, np.nan, in_img_data) mask = au.within_footprint(in_img_data, outwcs, x, y) gaia_table = gaia_table[mask] # Report results to log if len(gaia_table) == 0: log.warning("No GAIA sources were found!") elif len(gaia_table) == 1: log.info("1 GAIA source was found.") else: log.info("{} GAIA sources were found.".format(len(gaia_table))) return gaia_table
def generate_sky_catalog(image, refwcs, dqname="DQ", output=False): """Build source catalog from input image using photutils. This script borrows heavily from build_source_catalog. The catalog returned by this function includes sources found in all chips of the input image with the positions translated to the coordinate frame defined by the reference WCS `refwcs`. The sources will be - identified using photutils segmentation-based source finding code - ignore any input pixel which has been flagged as 'bad' in the DQ array, should a DQ array be found in the input HDUList. - classified as probable cosmic-rays (if enabled) using central_moments properties of each source, with these sources being removed from the catalog. Parameters ---------- image : `~astropy.io.fits.HDUList` Input image. refwcs : `~stwcs.wcsutil.HSTWCS` Definition of the reference frame WCS. dqname : str, optional EXTNAME for the DQ array, if present, in the input image. output : bool, optional Specify whether or not to write out a separate catalog file for all the sources found in each chip. Returns -------- master_cat : `~astropy.table.Table` Source catalog for all 'valid' sources identified from all chips of the input image with positions translated to the reference WCS coordinate frame. """ # Extract source catalogs for each chip source_cats = generate_source_catalog(image, dqname=dqname, output=output) # Build source catalog for entire image master_cat = None numSci = countExtn(image, extname='SCI') # if no refwcs specified, build one now... if refwcs is None: refwcs = build_reference_wcs([image]) for chip in range(numSci): chip += 1 # work with sources identified from this specific chip seg_tab_phot = source_cats[chip] if seg_tab_phot is None: continue # Convert pixel coordinates from this chip to sky coordinates chip_wcs = wcsutil.HSTWCS(image, ext=('sci', chip)) seg_ra, seg_dec = chip_wcs.all_pix2world(seg_tab_phot['xcentroid'], seg_tab_phot['ycentroid'], 1) # Convert sky positions to pixel positions in the reference WCS frame seg_xy_out = refwcs.all_world2pix(seg_ra, seg_dec, 1) seg_tab_phot['xcentroid'] = seg_xy_out[0] seg_tab_phot['ycentroid'] = seg_xy_out[1] if master_cat is None: master_cat = seg_tab_phot else: master_cat = vstack([master_cat, seg_tab_phot]) return master_cat
def create_product_page(prodname, zoom_size=128, wcsname=""): # obtain image data to display with fits.open(prodname) as prod: data = prod[1].data phdr = prod[0].header targname = phdr['targname'] inst = phdr['instrume'] det = phdr['detector'] texptime = phdr['texptime'] inexp = phdr['d001data'].split('[')[0] wcstype = prod[1].header['wcstype'] wcs = wcsutil.HSTWCS(prod, ext=1) center = (data.shape[0] // 2, data.shape[1] // 2) prod_path = os.path.split(prodname)[0] data = np.nan_to_num(data, 0.0) # Get GAIA catalog refcat = amutils.create_astrometric_catalog(prodname, existing_wcs=wcs, output=None) refx, refy = wcs.all_world2pix(refcat['RA'], refcat['DEC'], 0) # Remove points outside the full-size image area rx = [] ry = [] zx = [] zy = [] for x, y in zip(refx, refy): if 0 < x < data.shape[1] and 0 < y < data.shape[0]: rx.append(x) ry.append(y) if -zoom_size < x - center[1] < zoom_size and -zoom_size < y - center[ 0] < zoom_size: zx.append(x - center[1] + zoom_size) zy.append(y - center[0] + zoom_size) # Define subplot regions on page fig = plt.figure(constrained_layout=True, figsize=(7, 10)) gs = fig.add_gridspec(ncols=4, nrows=5) # title plots img_title = "{} image of {} with WCSNAME={}".format( prodname, targname, wcsname) plt.title(img_title, loc='center', fontsize=8) # Define image display fig_img = fig.add_subplot(gs[:3, :]) fig_zoom = fig.add_subplot(gs[3:, 2:]) fig_summary = fig.add_subplot(gs[3:, :2]) # Compute display range dmax = (data.max() // 10) dscaled = np.log10(np.clip(data, -0.9, dmax) + 1) # identify zoom region around center of data zoom = dscaled[center[0] - zoom_size:center[0] + zoom_size, center[1] - zoom_size:center[1] + zoom_size] # display full image fig_img.imshow(dscaled, cmap='gray', origin='lower') # display zoomed section fig_zoom.imshow(zoom, cmap='gray', origin='lower') # define markerstyle mstyle = markers.MarkerStyle(marker='o') mstyle.set_fillstyle('none') # plot GAIA sources onto full size image fig_img.scatter(rx, ry, marker=mstyle, alpha=0.25, c='cyan', s=3) fig_zoom.scatter(zx, zy, marker=mstyle, alpha=0.25, c='cyan') # Print summary info fsize = 8 pname = os.path.split(prodname)[1] fig_summary.text(0.01, 0.95, "Summary for {}".format(pname), fontsize=fsize) fig_summary.text(0.01, 0.9, "WCSNAME: {}".format(wcsname), fontsize=fsize) fig_summary.text(0.01, 0.85, "TARGET: {}".format(targname), fontsize=fsize) fig_summary.text(0.01, 0.8, "Instrument: {}/{}".format(inst, det), fontsize=fsize) fig_summary.text(0.01, 0.7, "Total Exptime: {}".format(texptime), fontsize=fsize) fig_summary.text(0.01, 0.65, "WCSTYPE: {}".format(wcstype), fontsize=fsize) fig_summary.text(0.01, 0.5, "# of GAIA sources: {}".format(len(rx)), fontsize=fsize) if 'FIT' in wcsname: # Look for FIT RMS and other stats from headerlet exp = fits.open(os.path.join(prod_path, inexp)) for ext in exp: if 'extname' in ext.header and ext.header['extname'] == 'HDRLET' \ and ext.header['wcsname'] == wcsname: hdrlet = ext.headerlet rms_ra = hdrlet[0].header['rms_ra'] rms_dec = hdrlet[0].header['rms_dec'] nmatch = hdrlet[0].header['nmatch'] catalog = hdrlet[0].header['catalog'] break exp.close() fig_summary.text(0.01, 0.4, "RMS: RA={:0.3}mas, DEC={:0.3}mas".format( rms_ra, rms_dec), fontsize=fsize) fig_summary.text(0.01, 0.35, "# matches: {}".format(nmatch), fontsize=fsize) fig_summary.text(0.01, 0.3, "Matched to {} catalog".format(catalog), fontsize=fsize) return fig
def make_outputwcs(imageObjectList, output, configObj=None, perfect=False): """ Computes the full output WCS based on the set of input imageObjects provided as input, along with the pre-determined output name from process_input. The user specified output parameters are then used to modify the default WCS to produce the final desired output frame. The input imageObjectList has the outputValues dictionary updated with the information from the computed output WCS. It then returns this WCS as a WCSObject(imageObject) instance. """ if not isinstance(imageObjectList, list): imageObjectList = [imageObjectList] # Compute default output WCS, replace later if user specifies a refimage hstwcs_list = [] undistort = True for img in imageObjectList: chip_wcs = copy.deepcopy(img.getKeywordList('wcs')) # IF the user turned off use of coeffs (coeffs==False) if not configObj['coeffs']: for cw in chip_wcs: # Turn off distortion model for each input cw.sip = None cw.cpdis1 = None cw.cpdis2 = None cw.det2im = None undistort = False hstwcs_list += chip_wcs if not undistort and len(hstwcs_list) == 1: default_wcs = hstwcs_list[0].deepcopy() else: default_wcs = utils.output_wcs(hstwcs_list, undistort=undistort) if perfect: default_wcs.wcs.cd = make_perfect_cd(default_wcs) # Turn WCS instances into WCSObject instances outwcs = createWCSObject(output, default_wcs, imageObjectList) # Merge in user-specified attributes for the output WCS # as recorded in the input configObj object. final_pars = DEFAULT_WCS_PARS.copy() # More interpretation of the configObj needs to be done here to translate # the input parameter names to those understood by 'mergeWCS' as defined # by the DEFAULT_WCS_PARS dictionary. single_step = configObj[util.getSectionName(configObj, 3)] singleParDict = configObj[util.getSectionName(configObj, '3a')].copy() if single_step['driz_separate'] and singleParDict['driz_sep_wcs']: single_pars = DEFAULT_WCS_PARS.copy() del singleParDict['driz_sep_wcs'] keyname = 'driz_sep_' for key in singleParDict: k = key[len(keyname):] if k != 'refimage': single_pars[k] = singleParDict[key] # Now, account for any user-specified reference image def_wcs = default_wcs.deepcopy() single_ref = singleParDict[keyname + 'refimage'] if single_ref: if isinstance(single_ref, wcs.WCS): default_wcs = single_ref else: default_wcs = wcsutil.HSTWCS(singleParDict[keyname + 'refimage']) # ## Create single_wcs instance based on user parameters outwcs.single_wcs = mergeWCS(default_wcs, single_pars) # restore global default WCS to original value so single_drizzle WCS does not # influence final_drizzle WCS default_wcs = def_wcs.deepcopy() final_step = configObj[util.getSectionName(configObj, 7)] finalParDict = configObj[util.getSectionName(configObj, '7a')].copy() if final_step['driz_combine'] and finalParDict['final_wcs']: del finalParDict['final_wcs'] keyname = 'final_' for key in finalParDict: k = key[len(keyname):] if k != 'refimage': final_pars[k] = finalParDict[key] # Now, account for any user-specified reference image final_ref = finalParDict[keyname + 'refimage'] if final_ref: if isinstance(final_ref, wcs.WCS): default_wcs = final_ref if hasattr(final_ref, 'filename'): rootname = final_ref.filename else: rootname = "" print('Creating OUTPUT WCS from WCS object based on {}'.format(rootname)) else: rootname, extnum = fileutil.parseFilename(finalParDict[keyname + 'refimage']) extnum = util.findWCSExtn(finalParDict[keyname + 'refimage']) print('Creating OUTPUT WCS from {}[{}]'.format(rootname, extnum)) default_wcs = wcsutil.HSTWCS('{}[{}]'.format(rootname, extnum)) # ## Create single_wcs instance based on user parameters outwcs.final_wcs = mergeWCS(default_wcs, final_pars) outwcs.wcs = outwcs.final_wcs.copy() # Apply user settings to create custom output_wcs instances # for each drizzle step updateImageWCS(imageObjectList, outwcs) return outwcs
def tweakback(drzfile, input=None, origwcs=None, newname=None, wcsname=None, extname='SCI', force=False, verbose=False): """ Apply WCS solution recorded in drizzled file to distorted input images (``_flt.fits`` files) used to create the drizzled file. This task relies on the original WCS and updated WCS to be recorded in the drizzled image's header as the last 2 alternate WCSs. Parameters ---------- drzfile : str (Default = '') filename of undistorted image which contains the new WCS and WCS prior to being updated newname : str (Default = None) Value of ``WCSNAME`` to be used to label the updated solution in the output (eq., ``_flt.fits``) files. If left blank or None, it will default to using the current ``WCSNAME`` value from the input drzfile. input : str (Default = '') filenames of distorted images to be updated using new WCS from 'drzfile'. These can be provided either as an ``@-file``, a comma-separated list of filenames or using wildcards. .. note:: A blank value will indicate that the task should derive the filenames from the 'drzfile' itself, if possible. The filenames will be derived from the ``D*DATA`` keywords written out by ``AstroDrizzle``. If they can not be found, the task will quit. origwcs : str (Default = None) Value of ``WCSNAME`` keyword prior to the drzfile image being updated by ``TweakReg``. If left blank or None, it will default to using the second to last ``WCSNAME*`` keyword value found in the header. wcsname : str (Default = None) Value of WCSNAME for updated solution written out by ``TweakReg`` as specified by the `wcsname` parameter from ``TweakReg``. If this is left blank or `None`, it will default to the current ``WCSNAME`` value from the input drzfile. extname : str (Default = 'SCI') Name of extension in `input` files to be updated with new WCS force : bool (Default = False) This parameters specified whether or not to force an update of the WCS even though WCS already exists with this solution or `wcsname`? verbose : bool (Default = False) This parameter specifies whether or not to print out additional messages during processing. Notes ----- The algorithm used by this function is based on linearization of the exact compound operator that converts input image coordinates to the coordinates (in the input image) that would result in alignment with the new drizzled image WCS. If no input distorted files are specified as input, this task will attempt to generate the list of filenames from the drizzled input file's own header. EXAMPLES -------- An image named ``acswfc_mos2_drz.fits`` was created from 4 images using astrodrizzle. This drizzled image was then aligned to another image using tweakreg and the header was updated using the ``WCSNAME`` = ``TWEAK_DRZ``. The new WCS can then be used to update each of the 4 images that were combined to make up this drizzled image using: >>> from drizzlepac import tweakback >>> tweakback.tweakback('acswfc_mos2_drz.fits') If the same WCS should be applied to a specific set of images, those images can be updated using: >>> tweakback.tweakback('acswfc_mos2_drz.fits', ... input='img_mos2a_flt.fits,img_mos2e_flt.fits') See Also -------- stwcs.wcsutil.altwcs: Alternate WCS implementation """ print("TweakBack Version {:s} started at: {:s}\n".format( __version__, util._ptime()[0])) # Interpret input list/string into list of filename(s) fltfiles = parseinput.parseinput(input)[0] if fltfiles is None or len(fltfiles) == 0: # try to extract the filenames from the drizzled file's header fltfiles = extract_input_filenames(drzfile) if fltfiles is None: print('*' * 60) print('*') print('* ERROR:') print('* No input filenames found! ') print( '* Please specify "fltfiles" or insure that input drizzled') print('* image contains D*DATA keywords. ') print('*') print('*' * 60) raise ValueError if not isinstance(fltfiles, list): fltfiles = [fltfiles] sciext = determine_extnum(drzfile, extname='SCI') scihdr = fits.getheader(drzfile, ext=sciext, memmap=False) ### Step 1: Read in updated and original WCS solutions # determine keys for all alternate WCS solutions in drizzled image header wkeys = wcsutil.altwcs.wcskeys(drzfile, ext=sciext) if len(wkeys) < 2: raise ValueError( f"'{drzfile}' must contain at least two valid WCS: original and updated." ) wnames = wcsutil.altwcs.wcsnames(drzfile, ext=sciext) if not util.is_blank(newname): final_name = newname else: final_name = wnames[wkeys[-1]] # Read in HSTWCS objects for final,updated WCS and previous WCS from # from drizzled image header # The final solution also serves as reference WCS when using updatehdr if not util.is_blank(wcsname): for wkey, wname in wnames.items(): if wname == wcsname: wcskey = wkey break else: raise ValueError( f"WCS with name '{wcsname}' not found in '{drzfile}'") else: wcskey = wkeys[-1] final_wcs = wcsutil.HSTWCS(drzfile, ext=sciext, wcskey=wcskey) if not util.is_blank(origwcs): for wkey, wname in wnames.items(): if wname == origwcs: orig_wcskey = wkey break else: raise ValueError( f"WCS with name '{origwcs}' not found in '{drzfile}'") else: _, orig_wcskey = determine_orig_wcsname(scihdr, wnames, wkeys) orig_wcs = wcsutil.HSTWCS(drzfile, ext=sciext, wcskey=orig_wcskey) # read in RMS values reported for new solution crderr1kw = 'CRDER1' + wkeys[-1] crderr2kw = 'CRDER2' + wkeys[-1] if crderr1kw in scihdr: crderr1 = fits.getval(drzfile, crderr1kw, ext=sciext, memmap=False) else: crderr1 = 0.0 if crderr2kw in scihdr: crderr2 = fits.getval(drzfile, crderr2kw, ext=sciext, memmap=False) else: crderr2 = 0.0 del scihdr ### Step 2: Apply solution to input file headers for fname in fltfiles: logstr = "....Updating header for {:s}...".format(fname) if verbose: print("\n{:s}\n".format(logstr)) else: log.info(logstr) # reset header WCS keywords to original (OPUS generated) values imhdulist = fits.open(fname, mode='update', memmap=False) extlist = get_ext_list(imhdulist, extname='SCI') if not extlist: extlist = [0] # Process MEF images... for ext in extlist: logstr = "Processing {:s}[{:s}]".format(imhdulist.filename(), ext2str(ext)) if verbose: print("\n{:s}\n".format(logstr)) else: log.info(logstr) chip_wcs = wcsutil.HSTWCS(imhdulist, ext=ext) update_chip_wcs(chip_wcs, orig_wcs, final_wcs, xrms=crderr1, yrms=crderr2) # Update FITS file with newly updated WCS for this chip extnum = imhdulist.index(imhdulist[ext]) updatehdr.update_wcs(imhdulist, extnum, chip_wcs, wcsname=final_name, reusename=False, verbose=verbose) imhdulist.close()
def build_nddata(image, group_id, source_catalog): """ Return a list of NDData objects for all chips in an image. Parameters =========== image : filename, HDUList Either filename or HDUList of a single HST observation group_id : int Integer ID for group this image should be associated with; primarily used when separate chips are in separate files to treat them all as one exposure. source_catalog : dict, optional If provided (default:None), these catalogs will be attached as `catalog` entries in each chip's NDData.meta. It should be provided as a dict of astropy Tables identified by chip number with each table containing sources from image extension `('sci',chip)` as generated by `generate_source_catalog()`. Returns ======== ndlist : list List of astropy NDData defined for all chips in input image """ open_file = False if isinstance(image, str): hdulist = pf.open(image) open_file = True elif isinstance(image, pf.HDUList): hdulist = image else: print("Wrong type of input, {}, for build_nddata...".format( type(image))) raise ValueError images = [] numsci = countExtn(hdulist) for chip in range(1, numsci + 1): im_data = hdulist[('SCI', chip)].data dq_data = hdulist[('DQ', chip)].data w = wcsutil.HSTWCS(hdulist, ('SCI', chip)) # Below, simply consider non-zero DQ data as invalid. # A more sophisticated approach would use bitmask module. # Also, here we set group ID to a different number for each image, # but for ACS images, for example, we likely would assign # the same group ID to the images corresponding to different # SCI extensions *of the same FITS file* so that they can be # aligned together. img = NDData(data=im_data, mask=dq_data != 0, wcs=w, meta={ 'chip': chip, 'group_id': group_id }) # append source catalog, if provided if source_catalog: imcat = source_catalog[chip] # rename xcentroid/ycentroid columns, if necessary, to be consistent with tweakwcs if 'xcentroid' in imcat.colnames: imcat.rename_column('xcentroid', 'x') imcat.rename_column('ycentroid', 'y') imcat.meta['name'] = 'im{:d} sources'.format(group_id) img.meta['catalog'] = imcat images.append(img) if open_file: hdulist.close() return images
def __init__(self, image, ext, dq_bits=0, dqimage=None, dqext=None, usermask=None, usermask_ext=None): """ Parameters ---------- image : ImageRef An :py:class:`~stsci.skypac.utils.ImageRef` object that refers to an open FITS file ext : tuple, int, str Extension specification in the `image` the `SkyLineMember` object will be associated with. An int `ext` specifies extension number. A tuple in the form (str, int) specifies extension name and number. A string `ext` specifies extension name and the extension version is assumed to be 1. See documentation for `astropy.io.fits.getData` for examples. dq_bits : int, None (Default = 0) Integer sum of all the DQ bit values from the input `image`'s DQ array that should be considered "good" when building masks for sky computations. For example, if pixels in the DQ array can be combinations of 1, 2, 4, and 8 flags and one wants to consider DQ "defects" having flags 2 and 4 as being acceptable for sky computations, then `dq_bits` should be set to 2+4=6. Then a DQ pixel having values 2,4, or 6 will be considered a good pixel, while a DQ pixel with a value, e.g., 1+2=3, 4+8=12, etc. will be flagged as a "bad" pixel. | Default value (0) will make *all* non-zero pixels in the DQ mask to be considered "bad" pixels, and the corresponding image pixels will not be used for sky computations. | Set `dq_bits` to `None` to turn off the use of image's DQ array for sky computations. .. note:: DQ masks (if used), *will be* combined with user masks specified by the `usermask` parameter. dqimage : ImageRef An :py:class:`~stsci.skypac.utils.ImageRef` object that refers to an open FITS file that has DQ data of the input `image`. .. note:: When DQ data are located in the same FITS file as the science image data (e.g., HST/ACS, HST/WFC3, etc.), `dqimage` may point to the same :py:class:`~stsci.skypac.utils.ImageRef` object. In this case the reference count of the \ :py:class:`~stsci.skypac.utils.ImageRef` object must be increased adequately. dqext : tuple, int, str Extension specification of the `dqimage` that contains `image`'s DQ information. See help for `ext` for more details on acceptable formats for this parameter. usermask : ImageRef An :py:class:`~stsci.skypac.utils.ImageRef` object that refers to an open FITS file that has user mask data that indicate what pixels in the input `image` should be used for sky computations (``1``) and which pixels should **not** be used for sky computations (``0``). usermask_ext : tuple, int, str Extension specification of the `usermask` mask file that contains user's mask data that should be associated with the input `image` and `ext`. See help for `ext` for more details on acceptable formats for this parameter. """ assert(hasattr(self.__class__, '_initialized') and \ self.__class__._initialized) self._reset() # check that input images and extensions are valid -- # either integers or tuples of strings and integers, e.g., ('sci',1): _check_valid_imgext(image, 'image', ext, 'ext', can_img_be_None=False) if dq_bits is not None: if dqimage is None: dq_bits = 0 else: _check_valid_imgext(dqimage, 'dqimage', dqext, 'dqext') _check_valid_imgext(usermask, 'usermask', usermask_ext, 'usermask_ext') # get telescope, instrument, and detector info: self.telescope, self.instrument, self.detector = get_instrument_info( image, ext) # check dq_bits: if dq_bits is not None and not isinstance(dq_bits, int): if image: dqimage.release() if usermask: usermask.release() if dqimage: dqimage.release() raise TypeError( "Argument 'dq_bits' must be either an integer or None.") # buld mask: self._buildMask(image.original_fname, ext, dq_bits, dqimage, dqext, usermask, usermask_ext) if dqimage: dqimage.release() if usermask: usermask.release() # save file, user mask, and DQ extension info: self._fname = image.original_fname self._basefname = basename(self._fname) self._image = image self._ext = ext self._can_free_image = image.can_reload_data and self.optimize != 'speed' # check extension and create a string representation: try: extstr = ext2str(ext) except ValueError: raise ValueError("Unexpected extension type \'{}\' for file {}.".\ format(ext,self._basefname)) self._id = "{:s}[{:s}]".format(self._basefname, extstr) # extract WCS for bounding-box computation try: if hasattr(image.hdu[ext], 'wcs'): self._wcs = image.hdu[ext].wcs else: if self.telescope in supported_telescopes: self._wcs = wcsutil.HSTWCS(image.hdu, ext) else: self._wcs = pywcs.WCS(image.hdu[ext].header, image.hdu) if self._wcs is None: raise Exception("Invalid WCS.") except: msg = "Unable to obtain WCS information for the file {:s}." \ .format(self._id) self._ml.error(msg) self._ml.flush() self._release_all() raise # determine pixel scale: self._get_pixel_scale() # see if image data are in counts or count-rate # and compute count(-rate) to flux (per arcsec^2) conversion factor: self._brightness_conv_from_hdu(image.hdu, self._idcscale) # process Sky user's keyword and its value: self._init_skyuser(image.hdu[ext].header) # Set polygon to be the bounding box of the chip: self._polygon = SphericalPolygon.from_wcs(self.wcs, steps=1)
def updatewcs_with_shift(image,reference,wcsname=None, reusename=False, fitgeom='rscale', rot=0.0,scale=1.0,xsh=0.0,ysh=0.0,fit=None, xrms=None, yrms = None, verbose=False,force=False,sciext='SCI'): """ Update the SCI headers in 'image' based on the fit provided as determined in the WCS specified by 'reference'. The fit should be a 2-D matrix as generated for use with 'make_vector_plot()'. Notes ----- The algorithm used to apply the provided fit solution to the image involves applying the following steps to the WCS of each of the input image's chips: 1. compute RA/Dec with full distortion correction for reference point as (Rc_i,Dc_i) 2. find the Xc,Yc for each Rc_i,Dc_i and get the difference from the CRPIX position for the reference WCS as (dXc_i,dYc_i) 3. apply fit (rot&scale) to (dXc_i,dYc_i) then apply shift, then add CRPIX back to get new (Xcs_i,Ycs_i) position 4. compute (Rcs_i,Dcs_i) as the sky coordinates for (Xcs_i,Ycs_i) 5. compute delta of (Rcs_i-Rc_i, Dcs_i-Dcs_i) as (dRcs_i,dDcs_i) 6. apply the fit to the chip's undistorted CD matrix, the apply linear distortion terms back in to create a new CD matrix 7. add (dRcs_i,dDcs_i) to CRVAL of the reference chip's WCS 8. update header with new WCS values Parameters ---------- image : str or PyFITS.HDUList object Filename, or PyFITS object, of image with WCS to be updated. All extensions with EXTNAME matches the value of the 'sciext' parameter value (by default, all 'SCI' extensions) will be updated. reference : str Filename of image/headerlet (FITS file) which contains the WCS used to define the tangent plane in which all the fit parameters (shift, rot, scale) were measured. wcsname : str Label to give to new WCS solution being created by this fit. If a value of None is given, it will automatically use 'TWEAK' as the label. If a WCS has a name with this specific value, the code will automatically append a version ID using the format '_n', such as 'TWEAK_1', 'TWEAK_2',or 'TWEAK_update_1'. [Default =None] reusename : bool User can specify whether or not to over-write WCS with same name. [Default: False] rot : float Amount of rotation measured in fit to be applied. [Default=0.0] scale : float Amount of scale change measured in fit to be applied. [Default=1.0] xsh : float Offset in X pixels from defined tangent plane to be applied to image. [Default=0.0] ysh : float Offset in Y pixels from defined tangent plane to be applied to image. [Default=0.0] fit : arr Linear coefficients for fit [Default = None] xrms : float RMS of fit in RA (in decimal degrees) that will be recorded as CRDER1 in WCS and header [Default = None] yrms : float RMS of fit in Dec (in decimal degrees) that will be recorded as CRDER2 in WCS and header [Default = None] verbose : bool Print extra messages during processing? [Default=False] force : bool Update header even though WCS already exists with this solution or wcsname? [Default=False] sciext : string Value of FITS EXTNAME keyword for extensions with WCS headers to be updated with the fit values. [Default='SCI'] """ # if input reference is a ref_wcs file from tweakshifts, use it if isinstance(reference, wcsutil.HSTWCS) or isinstance(reference, pywcs.WCS): wref = reference else: refimg = fits.open(reference, memmap=False) wref = None for extn in refimg: if 'extname' in extn.header and extn.header['extname'] == 'WCS': wref = pywcs.WCS(refimg['wcs'].header) break refimg.close() # else, we have presumably been provided a full undistorted image # as a reference, so use it with HSTWCS instead if wref is None: wref = wcsutil.HSTWCS(reference) if isinstance(image, fits.HDUList): open_image = False filename = image.filename() if image.fileinfo(0)['filemode'] is 'update': image_update = True else: image_update = False else: open_image = True filename = image image_update = None # Now that we are sure we have a good reference WCS to use, # continue with the update logstr = "....Updating header for {:s}...".format(filename) if verbose: print("\n{:s}\n".format(logstr)) else: log.info(logstr) # reset header WCS keywords to original (OPUS generated) values extlist = get_ext_list(image, extname='SCI') if extlist: if image_update: # Create initial WCSCORR extension wcscorr.init_wcscorr(image,force=force) else: extlist = [0] # insure that input PRIMARY WCS has been archived before overwriting # with new solution if open_image: fimg = fits.open(image, mode='update', memmap=False) image_update = True else: fimg = image if image_update: wcsutil.altwcs.archiveWCS(fimg,extlist,reusekey=True) # Process MEF images... for ext in extlist: logstr = "Processing {:s}[{:s}]".format(fimg.filename(), ext2str(ext)) if verbose: print("\n{:s}\n".format(logstr)) else: log.info(logstr) chip_wcs = wcsutil.HSTWCS(fimg,ext=ext) update_refchip_with_shift(chip_wcs, wref, fitgeom=fitgeom, rot=rot, scale=scale, xsh=xsh, ysh=ysh, fit=fit, xrms=xrms, yrms=yrms) #if util.is_blank(wcsname): #wcsname = 'TWEAK' # Update FITS file with newly updated WCS for this chip extnum = fimg.index(fimg[ext]) update_wcs(fimg, extnum, chip_wcs, wcsname=wcsname, reusename=reusename, verbose=verbose) if open_image: fimg.close()
def xy2rd(input, x=None, y=None, coords=None, coordfile=None, colnames=None, separator=None, hms=True, precision=6, output=None, verbose=True): """ Primary interface to perform coordinate transformations from pixel to sky coordinates using STWCS and full distortion models read from the input image header. """ single_coord = False # Only use value provided in `coords` if nothing has been specified for coordfile if coords is not None and coordfile is None: coordfile = coords warnings.simplefilter('always', DeprecationWarning) warnings.warn( "Please update calling code to pass in `coordfile` instead of `coords`.", category=DeprecationWarning) warnings.simplefilter('default', DeprecationWarning) if coordfile is not None: if colnames in blank_list: colnames = ['c1', 'c2'] # Determine columns which contain pixel positions cols = util.parse_colnames(colnames, coordfile) # read in columns from input coordinates file xyvals = np.loadtxt(coordfile, usecols=cols, delimiter=separator) if xyvals.ndim == 1: # only 1 entry in coordfile xlist = [xyvals[0].copy()] ylist = [xyvals[1].copy()] else: xlist = xyvals[:, 0].copy() ylist = xyvals[:, 1].copy() del xyvals else: if isinstance(x, np.ndarray): xlist = x.tolist() ylist = y.tolist() elif not isinstance(x, list): xlist = [x] ylist = [y] single_coord = True else: xlist = x ylist = y # start by reading in WCS+distortion info for input image inwcs = wcsutil.HSTWCS(input) if inwcs.wcs.is_unity(): print( "####\nNo valid WCS found in {}.\n Results may be invalid.\n####\n" .format(input)) # Now, convert pixel coordinates into sky coordinates dra, ddec = inwcs.all_pix2world(xlist, ylist, 1) # convert to HH:MM:SS.S format, if specified if hms: ra, dec = wcs_functions.ddtohms(dra, ddec, precision=precision) rastr = ra decstr = dec else: # add formatting based on precision here... rastr = [] decstr = [] fmt = "%." + repr(precision) + "f" for r, d in zip(dra, ddec): rastr.append(fmt % r) decstr.append(fmt % d) ra = dra dec = ddec if verbose or (not verbose and util.is_blank(output)): print('# Coordinate transformations for ', input) print('# X Y RA Dec\n') for x, y, r, d in zip(xlist, ylist, rastr, decstr): print("%.4f %.4f %s %s" % (x, y, r, d)) # Create output file, if specified if output: f = open(output, mode='w') f.write("# Coordinates converted from %s\n" % input) for r, d in zip(rastr, decstr): f.write('%s %s\n' % (r, d)) f.close() print('Wrote out results to: ', output) if single_coord: ra = ra[0] dec = dec[0] return ra, dec
def tran(inimage, outimage, direction='forward', x=None, y=None, coords=None, coordfile=None, colnames=None, separator=None, precision=6, output=None, verbose=True): """ Primary interface to perform coordinate transformations in pixel coordinates between 2 images using STWCS and full distortion models read from each image's header. """ single_coord = False # Only use value provided in `coords` if nothing has been specified for coordfile if coords is not None and coordfile is None: coordfile = coords warnings.simplefilter('always', DeprecationWarning) warnings.warn( "Please update calling code to pass in `coordfile` instead of `coords`.", category=DeprecationWarning) warnings.simplefilter('default', DeprecationWarning) if coordfile is not None: if colnames in util.blank_list: colnames = ['c1', 'c2'] # Determine columns which contain pixel positions cols = util.parse_colnames(colnames, coordfile) # read in columns from input coordinates file xyvals = np.loadtxt(coordfile, usecols=cols, delimiter=separator) if xyvals.ndim == 1: # only 1 entry in coordfile xlist = [xyvals[0].copy()] ylist = [xyvals[1].copy()] else: xlist = xyvals[:, 0].copy() ylist = xyvals[:, 1].copy() del xyvals else: if isinstance(x, np.ndarray): xlist = x.tolist() ylist = y.tolist() elif not isinstance(x, list): xlist = [x] ylist = [y] single_coord = True else: xlist = x ylist = y # start by reading in WCS+distortion info for each image im1wcs = wcsutil.HSTWCS(inimage) if im1wcs.wcs.is_unity(): print( "####\nNo valid input WCS found in {}.\n Results may be invalid.\n####\n" .format(inimage)) if util.is_blank(outimage): fname, fextn = fileutil.parseFilename(inimage) numsci = fileutil.countExtn(fname) chips = [] for e in range(1, numsci + 1): chips.append(wcsutil.HSTWCS(fname, ext=('sci', e))) if len(chips) == 0: chips = [im1wcs] im2wcs = distortion.utils.output_wcs(chips) else: im2wcs = wcsutil.HSTWCS(outimage) if im2wcs.wcs.is_unity(): print( "####\nNo valid output WCS found in {}.\n Results may be invalid.\n####\n" .format(outimage)) # Setup the transformation p2p = wcs_functions.WCSMap(im1wcs, im2wcs) if direction[0].lower() == 'f': outx, outy = p2p.forward(xlist, ylist) else: outx, outy = p2p.backward(xlist, ylist) if isinstance(outx, np.ndarray): outx = outx.tolist() outy = outy.tolist() # add formatting based on precision here... xstr = [] ystr = [] fmt = "%." + repr(precision) + "f" for ox, oy in zip(outx, outy): xstr.append(fmt % ox) ystr.append(fmt % oy) if verbose or (not verbose and util.is_blank(output)): print('# Coordinate transformations for ', inimage) print('# X(in) Y(in) X(out) Y(out)\n') for xs, ys, a, b in zip(xlist, ylist, xstr, ystr): print("%.4f %.4f %s %s" % (xs, ys, a, b)) # Create output file, if specified if output: f = open(output, mode='w') f.write("# Coordinates converted from %s\n" % inimage) for xs, ys in zip(xstr, ystr): f.write('%s %s\n' % (xs, ys)) f.close() print('Wrote out results to: ', output) if single_coord: outx = outx[0] outy = outy[0] return outx, outy