def match_2dhist_fit(imglist, reference_catalog, **fit_pars): """Perform cross-matching and final fit using 2dHistogram matching Parameters ---------- imglist : list List of input image `~tweakwcs.tpwcs.FITSWCS` objects with metadata and source catalogs reference_catalog : Table Astropy Table of reference sources for this field Returns -------- imglist : list List of input image `~tweakwcs.tpwcs.FITSWCS` objects with metadata and source catalogs """ log.info("{} (match_2dhist_fit) Cross matching and fitting " "{}".format("-" * 20, "-" * 28)) # Specify matching algorithm to use match = tweakwcs.TPMatch(**fit_pars) # Align images and correct WCS tweakwcs.align_wcs(imglist, reference_catalog, match=match, expand_refcat=False) # Interpret RMS values from tweakwcs interpret_fit_rms(imglist, reference_catalog) return imglist
def match_relative_fit(imglist, reference_catalog, **fit_pars): """Perform cross-matching and final fit using relative matching algorithm Parameters ---------- imglist : list List of input image `~tweakwcs.tpwcs.FITSWCS` objects with metadata and source catalogs reference_catalog : Table Astropy Table of reference sources for this field Returns -------- imglist : list List of input image `~tweakwcs.tpwcs.FITSWCS` objects with metadata and source catalogs """ log.info("{} (match_relative_fit) Cross matching and fitting {}".format( "-" * 20, "-" * 27)) if 'fitgeom' in fit_pars: fitgeom = fit_pars['fitgeom'] del fit_pars['fitgeom'] else: fitgeom = 'general' # 0: Specify matching algorithm to use match = tweakwcs.TPMatch(**fit_pars) # match = tweakwcs.TPMatch(searchrad=250, separation=0.1, # tolerance=100, use2dhist=False) # Align images and correct WCS # NOTE: this invocation does not use an astrometric catalog. This call allows all the input images to be aligned in # a relative way using the first input image as the reference. # 1: Perform relative alignment tweakwcs.align_wcs(imglist, None, match=match, expand_refcat=True, fitgeom=fitgeom) # Set all the group_id values to be the same so the various images/chips will be aligned to the astrometric # reference catalog as an ensemble. # astrometric reference catalog as an ensemble. BEWARE: If additional iterations of solutions are to be # done, the group_id values need to be restored. for image in imglist: image.meta["group_id"] = 1234567 # 2: Perform absolute alignment tweakwcs.align_wcs(imglist, reference_catalog, match=match, fitgeom=fitgeom) # 3: Interpret RMS values from tweakwcs interpret_fit_rms(imglist, reference_catalog) return imglist
def match_default_fit(imglist, reference_catalog, **fit_pars): """Perform cross-matching and final fit using default tolerance matching Parameters ---------- imglist : list List of input image `~tweakwcs.tpwcs.FITSWCS` objects with metadata and source catalogs reference_catalog : Table Astropy Table of reference sources for this field Returns -------- imglist : list List of input image `~tweakwcs.tpwcs.FITSWCS` objects with metadata and source catalogs """ if 'fitgeom' in fit_pars: fitgeom = fit_pars['fitgeom'] del fit_pars['fitgeom'] else: fitgeom = 'rscale' common_pars = fit_pars['pars'] del fit_pars['pars'] log.info("{} (match_default_fit) Cross matching and fitting " "{}".format("-" * 20, "-" * 27)) # Specify matching algorithm to use match = tweakwcs.TPMatch(**fit_pars) # Align images and correct WCS matched_cat = tweakwcs.align_wcs(imglist, reference_catalog, match=match, minobj=common_pars['MIN_FIT_MATCHES'], expand_refcat=False, fitgeom=fitgeom) # Interpret RMS values from tweakwcs interpret_fit_rms(imglist, reference_catalog) del matched_cat return imglist
def match_relative_fit(imglist, reference_catalog, **fit_pars): """Perform cross-matching and final fit using relative matching algorithm Parameters ---------- imglist : list List of input image `~tweakwcs.tpwcs.FITSWCS` objects with metadata and source catalogs reference_catalog : Table Astropy Table of reference sources for this field Returns -------- imglist : list List of input image `~tweakwcs.tpwcs.FITSWCS` objects with metadata and source catalogs """ log.info("{} (match_relative_fit) Cross matching and fitting {}".format( "-" * 20, "-" * 27)) if 'fitgeom' in fit_pars: fitgeom = fit_pars['fitgeom'] del fit_pars['fitgeom'] else: fitgeom = 'rscale' common_pars = fit_pars['pars'] del fit_pars['pars'] # 0: Specify matching algorithm to use match = tweakwcs.TPMatch(**fit_pars) # match = tweakwcs.TPMatch(searchrad=250, separation=0.1, # tolerance=100, use2dhist=False) # Align images and correct WCS # NOTE: this invocation does not use an astrometric catalog. This call # allows all the input images to be aligned in # a relative way using the first input image as the reference. # 1: Perform relative alignment match_relcat = tweakwcs.align_wcs(imglist, None, match=match, minobj=common_pars['MIN_FIT_MATCHES'], expand_refcat=True, fitgeom=fitgeom) log.info("Relative alignment found: ") for i in imglist: info = i.meta['fit_info'] if 'shift' not in info: off = (0., 0.) rot = 0. scale = -1. nmatches = 0 else: off = info['shift'] rot = info['<rot>'] scale = info['<scale>'] nmatches = info['nmatches'] msg = "Image {} --".format(i.meta['name']) msg += "\n SHIFT:({:9.4f},{:9.4f}) NMATCHES: {} ".format( off[0], off[1], nmatches) msg += "\n ROT:{:9.4f} SCALE:{:9.4f}".format(rot, scale) msg += "\n Using fitgeom = '{}'".format(fitgeom) log.info(msg) # This logic enables performing only relative fitting and skipping fitting to GAIA if reference_catalog is not None: # Set all the group_id values to be the same so the various images/chips will be aligned to the astrometric # reference catalog as an ensemble. # astrometric reference catalog as an ensemble. BEWARE: If additional iterations of solutions are to be # done, the group_id values need to be restored. for image in imglist: image.meta["group_id"] = 1234567 # 2: Perform absolute alignment matched_cat = tweakwcs.align_wcs(imglist, reference_catalog, match=match, minobj=common_pars['MIN_FIT_MATCHES'], fitgeom=fitgeom) else: # Insure the expanded reference catalog has all the information needed # to complete processing. # TODO: Work out how to get the 'mag' column from input source catalog # into this extended reference catalog... reference_catalog = match_relcat reference_catalog['mag'] = np.array([-999.9] * len(reference_catalog), np.float32) reference_catalog.meta['catalog'] = 'relative' # 3: Interpret RMS values from tweakwcs interpret_fit_rms(imglist, reference_catalog) del match_relcat return imglist
def determine_alignment_residuals(input, files, catalogs=None, max_srcs=1000, json_timestamp=None, json_time_since_epoch=None, log_level=logutil.logging.INFO): """Determine the relative alignment between members of an association. Parameters ----------- input : string Original pipeline input filename. This filename will be used to define the output analysis results filename. files : list Set of files on which to actually perform comparison. The original pipeline can work on both CTE-corrected and non-CTE-corrected files, but this comparison will only be performed on CTE-corrected products when available. catalogs : list, optional List of dictionaries containing the source catalogs for each input chip. The list NEEDS to be in the same order as the filenames given in `files`. Each dictionary for each file will need to have numerical (integer) keys for each 'sci' extension. If left as `None`, this function will create it's own set of catalogs using `astrometric_utils.extract_point_sources`. json_timestamp: str, optional Universal .json file generation date and time (local timezone) that will be used in the instantiation of the HapDiagnostic object. Format: MM/DD/YYYYTHH:MM:SS (Example: 05/04/2020T13:46:35). If not specified, default value is logical 'None' json_time_since_epoch : float Universal .json file generation time that will be used in the instantiation of the HapDiagnostic object. Format: Time (in seconds) elapsed since January 1, 1970, 00:00:00 (UTC). If not specified, default value is logical 'None' log_level : int, optional The desired level of verboseness in the log statements displayed on the screen and written to the .log file. Default value is 'NOTSET'. Returns -------- resids_files : list of string Name of JSON files containing all the extracted results from the comparisons being performed. """ log.setLevel(log_level) if catalogs is None: # Open all files as HDUList objects hdus = [fits.open(f) for f in files] # Determine sources from each chip src_cats = [] num_srcs = [] for hdu in hdus: numsci = countExtn(hdu) nums = 0 img_cats = {} if hdu[("SCI", 1)].data.max() == 0.0: log.info( "SKIPPING point-source finding for blank image: {}".format( hdu.filename())) continue log.info("Determining point-sources for {}".format(hdu.filename())) for chip in range(numsci): chip += 1 img_cats[chip] = amutils.extract_point_sources( hdu[("SCI", chip)].data, nbright=max_srcs) nums += len(img_cats[chip]) log.info("Identified {} point-sources from {}".format( nums, hdu.filename())) num_srcs.append(nums) src_cats.append(img_cats) else: src_cats = catalogs num_srcs = [] for img in src_cats: num_img = 0 for chip in img: num_img += len(img[chip]) num_srcs.append(num_img) if len(num_srcs) == 0 or (len(num_srcs) > 0 and max(num_srcs) <= 3): log.warning( "Not enough sources identified in input images for comparison") return [] # Combine WCS from HDULists and source catalogs into tweakwcs-compatible input imglist = [] for i, (f, cat) in enumerate(zip(files, src_cats)): imglist += amutils.build_wcscat(f, i, cat) # Setup matching algorithm using parameters tuned to well-aligned images match = tweakwcs.TPMatch(searchrad=5, separation=4.0, tolerance=1.0, use2dhist=True) try: # perform relative fitting matchlist = tweakwcs.align_wcs(imglist, None, minobj=6, match=match, expand_refcat=False) del matchlist except Exception: try: # Try without 2dHist use to see whether we can get any matches at all match = tweakwcs.TPMatch(searchrad=5, separation=4.0, tolerance=1.0, use2dhist=False) matchlist = tweakwcs.align_wcs(imglist, None, minobj=6, match=match, expand_refcat=False) del matchlist except Exception: log.warning("Problem encountered during matching of sources") return [] # Check to see whether there were any successful fits... align_success = False for img in imglist: wcsname = fits.getval(img.meta['filename'], 'wcsname', ext=("sci", 1)) img.meta['wcsname'] = wcsname img.meta['fit_info']['aligned_to'] = imglist[0].meta['filename'] img.meta['reference_catalog'] = None for img in imglist: if img.meta['fit_info']['status'] == 'SUCCESS' and '-FIT' in wcsname: align_success = True break resids_files = [] if align_success: # extract results in the style of 'tweakreg' resids = extract_residuals(imglist) if resids is not None: resids_files = generate_output_files( resids, json_timestamp=json_timestamp, json_time_since_epoch=json_time_since_epoch, exclude_fields=['group_id']) return resids_files
def test_multichip_fitswcs_alignment(): h1 = fits.Header.fromfile(get_pkg_data_filename('data/wfc3_uvis1.hdr')) w1 = wcs.WCS(h1) imcat1 = tweakwcs.FITSWCS(w1) imcat1.meta['catalog'] = table.Table.read( get_pkg_data_filename('data/wfc3_uvis1.cat'), format='ascii.csv', delimiter=' ', names=['x', 'y']) imcat1.meta['group_id'] = 1 imcat1.meta['name'] = 'ext1' h2 = fits.Header.fromfile(get_pkg_data_filename('data/wfc3_uvis2.hdr')) w2 = wcs.WCS(h2) imcat2 = tweakwcs.FITSWCS(w2) imcat2.meta['catalog'] = table.Table.read( get_pkg_data_filename('data/wfc3_uvis2.cat'), format='ascii.csv', delimiter=' ', names=['x', 'y']) imcat2.meta['group_id'] = 1 imcat2.meta['name'] = 'ext4' refcat = table.Table.read(get_pkg_data_filename('data/ref.cat'), format='ascii.csv', delimiter=' ', names=['RA', 'DEC']) tweakwcs.align_wcs([imcat1, imcat2], refcat, match=_match, nclip=None, sigma=3, fitgeom='general') fi1 = imcat1.meta['fit_info'] fi2 = imcat2.meta['fit_info'] w1 = imcat1.wcs w2 = imcat2.wcs assert np.allclose(w1.wcs.crval, (83.206917667519, -67.73275818507248), rtol=0) assert np.allclose( w1.wcs.cd, np.array([[3.93222694902149e-06, -1.0106698270131359e-05], [-1.0377001075437075e-05, -4.577945148472431e-06]]), atol=0.0, rtol=1e-8) assert np.allclose(w2.wcs.crval, (83.15167050722597, -67.74220306069903), rtol=0) assert np.allclose( w2.wcs.cd, np.array([[3.834449806681195e-06, -9.996495217498745e-06], [-1.0348147451241423e-05, -4.503496019301529e-06]]), atol=0.0, rtol=1e-8) assert np.allclose(fi1['<scale>'], 1.0025, rtol=0, atol=2e-8) assert np.allclose(fi2['<scale>'], 1.0025, rtol=0, atol=2e-8) assert fi1['rmse'] < 5e-5 assert fi2['rmse'] < 5e-5 cat1 = imcat1.meta['catalog'] ra1, dec1 = w1.all_pix2world(cat1['x'], cat1['y'], 0) cat2 = imcat2.meta['catalog'] ra2, dec2 = w2.all_pix2world(cat2['x'], cat2['y'], 0) ra = np.concatenate([ra1, ra2]) dec = np.concatenate([dec1, dec2]) rmse_ra = np.sqrt(np.mean((ra - refcat['RA'])**2)) rmse_dec = np.sqrt(np.mean((dec - refcat['DEC'])**2)) assert rmse_ra < 1e-9 assert rmse_dec < 1e-9
def test_different_ref_tpwcs_fitswcs_alignment(wcsno, refscale, dra, ddec): # This test was designed to check that the results of alignment, # in particular and most importantly, the sky positions of sources in # aligned images do not depend on the tangent reference plane used # for alignment. [#125] h1 = fits.Header.fromfile(get_pkg_data_filename('data/wfc3_uvis1.hdr')) w1 = wcs.WCS(h1) imcat1 = tweakwcs.FITSWCS(w1) imcat1.meta['catalog'] = table.Table.read( get_pkg_data_filename('data/wfc3_uvis1.cat'), format='ascii.csv', delimiter=' ', names=['x', 'y']) imcat1.meta['group_id'] = 1 imcat1.meta['name'] = 'ext1' h2 = fits.Header.fromfile(get_pkg_data_filename('data/wfc3_uvis2.hdr')) w2 = wcs.WCS(h2) imcat2 = tweakwcs.FITSWCS(w2) imcat2.meta['catalog'] = table.Table.read( get_pkg_data_filename('data/wfc3_uvis2.cat'), format='ascii.csv', delimiter=' ', names=['x', 'y']) imcat2.meta['group_id'] = 1 imcat2.meta['name'] = 'ext4' refcat = table.Table.read(get_pkg_data_filename('data/ref.cat'), format='ascii.csv', delimiter=' ', names=['RA', 'DEC']) refwcses = [wcs.WCS(h1), wcs.WCS(h2)] refwcs = refwcses[wcsno] # change pointing of the reference WCS (alignment tangent plane): refwcs.wcs.crval = refwcses[1 - wcsno].wcs.crval + np.asarray([dra, ddec]) rotm = tweakwcs.linearfit.build_fit_matrix(*refscale) refwcs.wcs.cd = np.dot(refwcs.wcs.cd, rotm) refwcs.wcs.set() ref_tpwcs = tweakwcs.FITSWCS(refwcs) tweakwcs.align_wcs([imcat1, imcat2], refcat, ref_tpwcs=ref_tpwcs, match=_match, nclip=None, sigma=3, fitgeom='general') fi1 = imcat1.meta['fit_info'] fi2 = imcat2.meta['fit_info'] w1 = imcat1.wcs w2 = imcat2.wcs assert fi1['rmse'] < 1e-4 assert fi2['rmse'] < 1e-4 cat1 = imcat1.meta['catalog'] ra1, dec1 = w1.all_pix2world(cat1['x'], cat1['y'], 0) cat2 = imcat2.meta['catalog'] ra2, dec2 = w2.all_pix2world(cat2['x'], cat2['y'], 0) ra = np.concatenate([ra1, ra2]) dec = np.concatenate([dec1, dec2]) rmse_ra = np.sqrt(np.mean((ra - refcat['RA'])**2)) rmse_dec = np.sqrt(np.mean((dec - refcat['DEC'])**2)) assert rmse_ra < 5e-9 assert rmse_dec < 5e-9
def test_multichip_jwst_alignment(): w1 = _make_gwcs_wcs('data/wfc3_uvis1.hdr') imcat1 = tweakwcs.JWSTgWCS(w1, {'v2_ref': 0, 'v3_ref': 0, 'roll_ref': 0}) imcat1.meta['catalog'] = table.Table.read( get_pkg_data_filename('data/wfc3_uvis1.cat'), format='ascii.csv', delimiter=' ', names=['x', 'y']) imcat1.meta['catalog']['x'] += 1 imcat1.meta['catalog']['y'] += 1 imcat1.meta['group_id'] = 1 imcat1.meta['name'] = 'ext1' w2 = _make_gwcs_wcs('data/wfc3_uvis2.hdr') imcat2 = tweakwcs.JWSTgWCS(w2, {'v2_ref': 0, 'v3_ref': 0, 'roll_ref': 0}) imcat2.meta['catalog'] = table.Table.read( get_pkg_data_filename('data/wfc3_uvis2.cat'), format='ascii.csv', delimiter=' ', names=['x', 'y']) imcat2.meta['catalog']['x'] += 1 imcat2.meta['catalog']['y'] += 1 imcat2.meta['group_id'] = 1 imcat2.meta['name'] = 'ext4' refcat = table.Table.read(get_pkg_data_filename('data/ref.cat'), format='ascii.csv', delimiter=' ', names=['RA', 'DEC']) tweakwcs.align_wcs([imcat1, imcat2], refcat, match=_match, nclip=None, sigma=3, fitgeom='general') fi1 = imcat1.meta['fit_info'] fi2 = imcat2.meta['fit_info'] w1m = imcat1.wcs w2m = imcat2.wcs assert np.allclose(w1m(*w1.crpix), (83.206917667519, -67.73275818507248), rtol=0) assert np.allclose(w2m(*w2.crpix), (83.15167050722597, -67.74220306069903), rtol=0) assert np.allclose(fi1['<scale>'], 1.0025, rtol=0, atol=2e-8) assert np.allclose(fi2['<scale>'], 1.0025, rtol=0, atol=2e-8) assert fi1['rmse'] < 5e-5 assert fi2['rmse'] < 5e-5 ra1, dec1 = imcat1.wcs(imcat1.meta['catalog']['x'], imcat1.meta['catalog']['y']) ra2, dec2 = imcat2.wcs(imcat2.meta['catalog']['x'], imcat2.meta['catalog']['y']) ra = np.concatenate([ra1, ra2]) dec = np.concatenate([dec1, dec2]) rra = refcat['RA'] rdec = refcat['DEC'] rmse_ra = np.sqrt(np.mean((ra - rra)**2)) rmse_dec = np.sqrt(np.mean((dec - rdec)**2)) assert rmse_ra < 3e-9 assert rmse_dec < 3e-10
def determine_alignment_residuals(input, files, max_srcs=2000): """Determine the relative alignment between members of an association. Parameters ----------- input : string Original pipeline input filename. This filename will be used to define the output analysis results filename. files : list Set of files on which to actually perform comparison. The original pipeline can work on both CTE-corrected and non-CTE-corrected files, but this comparison will only be performed on CTE-corrected products when available. Returns -------- results : string Name of JSON file containing all the extracted results from the comparisons being performed. """ # Open all files as HDUList objects hdus = [fits.open(f) for f in files] # Determine sources from each chip src_cats = [] num_srcs = [] for hdu in hdus: numsci = countExtn(hdu) nums = 0 img_cats = {} for chip in range(numsci): chip += 1 img_cats[chip] = amutils.extract_point_sources(hdu[("SCI", chip)].data, nbright=max_srcs) nums += len(img_cats[chip]) num_srcs.append(nums) src_cats.append(img_cats) if len(num_srcs) == 0 or (len(num_srcs) > 0 and max(num_srcs) <= 3): return None # src_cats = [amutils.generate_source_catalog(hdu) for hdu in hdus] # Combine WCS from HDULists and source catalogs into tweakwcs-compatible input imglist = [] for i, (f, cat) in enumerate(zip(files, src_cats)): imglist += amutils.build_wcscat(f, i, cat) # Setup matching algorithm using parameters tuned to well-aligned images match = tweakwcs.TPMatch(searchrad=5, separation=1.0, tolerance=4.0, use2dhist=True) try: # perform relative fitting matchlist = tweakwcs.align_wcs(imglist, None, match=match, expand_refcat=False) del matchlist except Exception: return None # Check to see whether there were any successful fits... align_success = False for img in imglist: if img.meta['fit_info']['status'] == 'SUCCESS': align_success = True break if align_success: # extract results in the style of 'tweakreg' resids = extract_residuals(imglist) if resids is None: resids_file = None else: # Define name for output JSON file... resids_file = "{}_astrometry_resids.json".format(input[:9]) # Remove any previously computed results if os.path.exists(resids_file): os.remove(resids_file) # Dump the results to a JSON file now... with open(resids_file, 'w') as jfile: json.dump(resids, jfile) else: resids_file = None return resids_file