Пример #1
0
def test_different():
    np.random.seed(0)
    x = np.random.random((512, 2))
    y = np.random.random((512, 2))

    r = stimage.xyxymatch(x,
                          y,
                          algorithm='tolerance',
                          tolerance=0.01,
                          separation=0.0)

    assert len(r) < 512 and len(r) > 0
    for i in range(len(r)):
        x0, y0 = r['input_x'][i], r['input_y'][i]
        x1, y1 = r['ref_x'][i], r['ref_y'][i]
        dx = x1 - x0
        dy = y1 - y0
        distance = dx * dx + dy * dy
        assert distance < 0.01 * 0.01
        assert r['input_idx'][i] < 512
        assert r['ref_idx'][i] < 512
Пример #2
0
def test_same():
    np.random.seed(0)
    x = np.random.random((512, 2))
    y = x[:]

    r = stimage.xyxymatch(x,
                          y,
                          algorithm='tolerance',
                          tolerance=0.01,
                          separation=0.0,
                          nmatch=0,
                          maxratio=0,
                          nreject=0)

    print(r.dtype)
    print(r.shape)

    assert len(r) == 512

    for i in range(512):
        assert r['input_x'][i] == r['ref_x'][i]
        assert r['input_y'][i] == r['ref_y'][i]
        assert r['input_idx'][i] == r['ref_idx'][i]
        assert r['input_idx'][i] < 512
Пример #3
0
def run(sl_names, img_names, diagnostic_mode=False, log_level=logutil.logging.INFO):
    """main running subroutine

    Parameters
    ----------
    sl_names : list
        A list containing the reference sourcelist filename and the comparison sourcelist filename, in that
        order.

    img_names : list
        A list containing the reference image filename and the comparison image filename, in that order.

    diagnostic_mode : Bool, optional
        If this option is set to Boolean 'True', region files will be created to test the quality of the
        coordinate transformation. Default value is Boolean 'False'.

    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 'INFO'.

    Returns
    -------
    Nothing.

    """

    log.setLevel(log_level)
    # 0: correct ra, dec, x, y in HLA sourcelists
    if sl_names[1].endswith("phot.txt"):
        if not os.path.exists(sl_names[1].replace("phot.txt", "phot_corrected.txt")):
            corrected_hla_slname = svmqa.correct_hla_classic_ra_dec(sl_names[1], img_names[0],
                                                                    sl_names[1].split("_")[-1][:-8], log_level=log_level)
        sl_names[1] = corrected_hla_slname

    # 1: get sourcelist data from files
    ref_data, comp_data = cu.slFiles2dataTables(sl_names)

    # 2: stack up RA and DEC columns
    ref_ra_dec_values = np.stack((ref_data['RA'], ref_data['DEC']), axis=1)
    comp_ra_dec_values = np.stack((comp_data['RA'], comp_data['DEC']), axis=1)

    # 3: transform comp frame RA, DEC into Ref frame X, Y values and ref RA, DEC into comp X, Y values
    ref_xy_in_comp_frame = hla_flag_filter.rdtoxy(ref_ra_dec_values, img_names[1], "[1]", origin=0)
    comp_xy_in_ref_frame = hla_flag_filter.rdtoxy(comp_ra_dec_values, img_names[0], "[1]", origin=0)

    # 4: (diagnostic only) write out region files to check coordinate transformation
    if diagnostic_mode:
        for data_table, reg_filename in zip([ref_data, comp_data, comp_xy_in_ref_frame], ["ref_orig.reg", "comp_orig.reg", "comp_xform.reg"]):
            write_region_file(data_table, reg_filename)

    new_comp_xy = np.stack((comp_xy_in_ref_frame[:, 0], comp_xy_in_ref_frame[:, 1]), axis=1)
    ref_xy = np.stack((ref_data['X'], ref_data['Y']), axis=1)
    comp_xy = np.stack((comp_data['X'], comp_data['Y']), axis=1)

    matches = xyxymatch(new_comp_xy, ref_xy, tolerance=5.0, separation=1.0)
    # Report number and percentage of the total number of detected ref and comp sources that were matched
    log.info("Sourcelist Matching Results")
    log.info(
        "Reference sourcelist:  {} of {} total sources matched ({} %)".format(len(matches),
                                                                              len(ref_xy),
                                                                              100.0 * (float(len(matches)) / float(len(ref_xy)))))
    log.info(
        "Comparison sourcelist: {} of {} total sources matched ({} %)".format(len(matches),
                                                                              len(new_comp_xy),
                                                                              100.0 * (float(len(matches)) / float(len(new_comp_xy)))))
    # extract indices of the matching ref and comp lines and then use them to compile lists of matched X, Y,
    # RA and DEC for calculation of differences
    matched_lines_comp = []
    matched_lines_ref = []
    for item in matches:
        matched_lines_comp.append(item[2])
        matched_lines_ref.append(item[5])

    matching_values_ref_x = ref_data['X'][[matched_lines_ref]]
    matching_values_ref_y = ref_data['Y'][[matched_lines_ref]]
    matching_values_ref_ra = ref_data['RA'][[matched_lines_ref]]
    matching_values_ref_dec = ref_data['DEC'][[matched_lines_ref]]
    matching_values_comp_x = new_comp_xy[:, 0][[matched_lines_comp]]
    matching_values_comp_y = new_comp_xy[:, 1][[matched_lines_comp]]
    matching_values_comp_ra = comp_ra_dec_values[:, 0][[matched_lines_comp]]
    matching_values_comp_dec = comp_ra_dec_values[:, 1][[matched_lines_comp]]


    # get coordinate system type from fits headers
    ref_frame = fits.getval(img_names[0], "radesys", ext=('sci', 1)).lower()
    comp_frame = fits.getval(img_names[1], "radesys", ext=('sci', 1)).lower()

    # force RA and Dec values to be the correct type for SkyCoord() call
    if str(type(matching_values_ref_ra)) == "<class 'astropy.table.column.Column'>":
        matching_values_ref_ra = matching_values_ref_ra.tolist()
    if str(type(matching_values_ref_dec)) == "<class 'astropy.table.column.Column'>":
        matching_values_ref_dec = matching_values_ref_dec.tolist()
    if str(type(matching_values_comp_ra)) == "<class 'astropy.table.column.Column'>":
        matching_values_comp_ra = matching_values_comp_ra.tolist()
    if str(type(matching_values_comp_dec)) == "<class 'astropy.table.column.Column'>":
        matching_values_comp_dec = matching_values_comp_dec.tolist()

    # convert reference and comparison RA/Dec values into SkyCoord objects
    matching_values_ref_rd = SkyCoord(matching_values_ref_ra, matching_values_ref_dec, frame=ref_frame, unit="deg")
    matching_values_comp_rd = SkyCoord(matching_values_comp_ra, matching_values_comp_dec, frame=comp_frame, unit="deg")
    # convert to ICRS coord system
    if ref_frame != "icrs":
        matching_values_ref_rd = matching_values_ref_rd.icrs
    if comp_frame != "icrs":
        matching_values_comp_rd = matching_values_comp_rd.icrs

    # compute mean-subtracted differences
    diff_x = (matching_values_comp_x - matching_values_ref_x)
    diff_x -= sigma_clipped_stats(diff_x, sigma=3, maxiters=3)[0]
    diff_y = (matching_values_comp_y - matching_values_ref_y)
    diff_y -= sigma_clipped_stats(diff_y, sigma=3, maxiters=3)[0]
    diff_xy = np.sqrt(diff_x**2 + diff_y**2)
    diff_rd = matching_values_comp_rd.separation(matching_values_ref_rd).arcsec


    diff_list = [diff_x, diff_y, diff_xy, diff_rd]
    title_list = ["X-axis differences", "Y-axis differences", "Seperation", "On-sky separation"]
    units_list = ["HAP WFC3/UVIS pixels", "HAP WFC3/UVIS pixels", "HAP WFC3/UVIS pixels", "Arcseconds"]
    for diff_ra, title, units in zip(diff_list, title_list, units_list):
        log.info("Comparison - reference {} statistics ({})".format(title, units))

        _ = compute_stats(diff_ra, title, log_level=log_level)

    generate_sorted_region_file(diff_xy, ref_xy_in_comp_frame[matched_lines_ref], comp_xy[matched_lines_comp], ref_data['FLAGS'][matched_lines_ref], comp_data['FLAGS'][matched_lines_comp])
Пример #4
0
    def __call__(self, refcat, imcat, tp_wcs=None):
        r""" Performs catalog matching.

        Parameters
        ----------

        refcat: astropy.table.Table
            A reference source catalog. When a tangent-plane ``WCS`` is
            provided through ``tp_wcs``, the catalog must contain ``'RA'`` and
            ``'DEC'`` columns which indicate reference source world
            coordinates (in degrees). Alternatively, when ``tp_wcs`` is `None`,
            reference catalog must contain ``'TPx'`` and ``'TPy'`` columns that
            provide undistorted (distortion-correction applied) source
            coordinates in some *tangent plane*. In this case, the ``'RA'``
            and ``'DEC'`` columns in the ``refcat`` catalog will be ignored.

        imcat: astropy.table.Table
            Source catalog associated with an image. Must contain ``'x'`` and
            ``'y'`` columns which indicate source coordinates (in pixels) in
            the associated image. Alternatively, when ``tp_wcs`` is `None`,
            catalog must contain ``'TPx'`` and ``'TPy'`` columns that
            provide undistorted (distortion-correction applied) source
            coordinates in **the same**\ *tangent plane* used to define
            ``refcat``'s tangent plane coordinates. In this case, the ``'x'``
            and ``'y'`` columns in the ``imcat`` catalog will be ignored.

        tp_wcs: TPWCS, None, optional
            A ``WCS`` that defines a tangent plane onto which both
            reference and image catalog sources can be projected. For this
            reason, ``tp_wcs`` is associated with the image in which sources
            from the ``imcat`` catalog were found in the sense that ``tp_wcs``
            must be able to map image coordinates ``'x'`` and ``'y'`` from the
            ``imcat`` catalog to the tangent plane. When ``tp_wcs`` is
            provided, the ``'TPx'`` and ``'TPy'`` columns in both ``imcat`` and
            ``refcat`` catalogs will be ignored (if present).

        Returns
        -------
        (refcat_idx, imcat_idx): tuple of numpy.ndarray
            A tuple of two 1D `numpy.ndarray` containing indices of matched
            sources in the ``refcat`` and ``imcat`` catalogs accordingly.

        """
        # Check catalogs:
        if not isinstance(refcat, astropy.table.Table):
            raise TypeError("'refcat' must be an instance of "
                            "astropy.table.Table")

        if not refcat:
            raise ValueError("Reference catalog must contain at least one "
                             "source.")

        if not isinstance(imcat, astropy.table.Table):
            raise TypeError("'imcat' must be an instance of "
                            "astropy.table.Table")

        if not imcat:
            raise ValueError("Image catalog must contain at least one "
                             "source.")

        if tp_wcs is None:
            if 'TPx' not in refcat.colnames or 'TPy' not in refcat.colnames:
                raise KeyError("When tangent plane WCS is not provided, "
                               "'refcat' must contain both 'TPx' and 'TPy' "
                               "columns.")

            if 'TPx' not in imcat.colnames or 'TPy' not in imcat.colnames:
                raise KeyError("When tangent plane WCS is not provided, "
                               "'imcat' must contain both 'TPx' and 'TPy' "
                               "columns.")

            imxy = np.asarray([imcat['TPx'], imcat['TPy']]).T
            refxy = np.asarray([refcat['TPx'], refcat['TPy']]).T

        else:
            if 'RA' not in refcat.colnames or 'DEC' not in refcat.colnames:
                raise KeyError("When tangent plane WCS is provided,  'refcat' "
                               "must contain both 'RA' and 'DEC' columns.")

            if 'x' not in imcat.colnames or 'y' not in imcat.colnames:
                raise KeyError("When tangent plane WCS is provided,  'imcat' "
                               "must contain both 'x' and 'y' columns.")

            # compute x & y in the tangent plane provided by tp_wcs:
            imxy = np.asarray(tp_wcs.det_to_tanp(imcat['x'], imcat['y'])).T

            refxy = np.asarray(
                tp_wcs.world_to_tanp(refcat['RA'], refcat['DEC'])).T

        imcat_name = imcat.meta.get('name', 'Unnamed')
        if imcat_name is None:
            imcat_name = 'Unnamed'

        refcat_name = refcat.meta.get('name', 'Unnamed')
        if refcat_name is None:
            refcat_name = 'Unnamed'

        log.info("Matching sources from '{:s}' catalog with sources from the "
                 "reference '{:s}' catalog.".format(imcat_name, refcat_name))

        ps = 1.0 if tp_wcs is None else tp_wcs.tanp_center_pixel_scale

        if self._use2dhist:
            # Determine xyoff (X,Y offset) and tolerance
            # to be used with xyxymatch:
            zpxoff, zpyoff = _estimate_2dhist_shift(imxy / ps,
                                                    refxy / ps,
                                                    searchrad=self._searchrad)
            xyoff = (zpxoff * ps, zpyoff * ps)

        else:
            xyoff = (self._xoffset * ps, self._yoffset * ps)

        matches = xyxymatch(imxy,
                            refxy,
                            origin=xyoff,
                            tolerance=ps * self._tolerance,
                            separation=ps * self._separation)

        return matches['ref_idx'], matches['input_idx']
Пример #5
0
    def match2ref(self, refcat, minobj=15, searchrad=1.0,
                  searchunits='arcseconds', separation=0.5,
                  use2dhist=True, xoffset=0.0, yoffset=0.0, tolerance=1.0):
        """ Uses xyxymatch to cross-match sources between this catalog and
            a reference catalog.

        Parameters
        ----------

        refcat : RefCatalog
            A `RefCatalog` object that contains a catalog of reference sources
            as well as a valid reference WCS.

        minobj : int, None, optional
            Minimum number of identified objects from each input image to use
            in matching objects from other images. If the default `None` value
            is used then `align` will automatically deternmine the minimum
            number of sources from the value of the `fitgeom` parameter.

        searchrad : float, optional
            The search radius for a match.

        searchunits : str, optional
            Units for search radius.

        separation : float, optional
            The  minimum  separation for sources in the input and reference
            catalogs in order to be considered to be disctinct sources.
            Objects closer together than 'separation' pixels
            are removed from the input and reference coordinate lists prior
            to matching. This parameter gets passed directly to
            :py:func:`~stsci.stimage.xyxymatch` for use in matching the object
            lists from each image with the reference image's object list.

        use2dhist : bool, optional
            Use 2D histogram to find initial offset?

        xoffset : float, optional
            Initial estimate for the offset in X between the images and the
            reference frame. This offset will be used for all input images
            provided. This parameter is ignored when `use2dhist` is `True`.

        yoffset : float (Default = 0.0)
            Initial estimate for the offset in Y between the images and the
            reference frame. This offset will be used for all input images
            provided. This parameter is ignored when `use2dhist` is `True`.

        tolerance : float, optional
            The matching tolerance in pixels after applying an initial solution
            derived from the 'triangles' algorithm.  This parameter gets passed
            directly to :py:func:`~stsci.stimage.xyxymatch` for use in
            matching the object lists from each image with the reference
            image's object list.

        """

        colnames = self._catalog.colnames

        if 'xref' not in colnames or 'yref' not in colnames:
            raise RuntimeError("'calc_xyref()' should have been run prior "
                               "to match2ref()")

        im_xyref = np.asanyarray([self._catalog['xref'],
                                  self._catalog['yref']]).T
        refxy = np.asanyarray([refcat.catalog['xref'],
                               refcat.catalog['yref']]).T

        log.info("Matching sources from '{}' with sources from reference "
                 "{:s} '{}'".format(self.name, 'image', refcat.name))

        # convert tolerance from units of arcseconds to pixels, as needed
        if searchunits == 'arcseconds':
            searchrad /= refcat.pscale

        xyoff = (xoffset, yoffset)

        if use2dhist:
            # Determine xyoff (X,Y offset) and tolerance
            # to be used with xyxymatch:
            zpxoff, zpyoff, flux, zpqual = matchutils.build_xy_zeropoint(
                im_xyref,
                refxy,
                searchrad=searchrad
            )

            if zpqual is not None:
                xyoff = (zpxoff, zpyoff)
                # set tolerance as well
                # This value allows initial guess to be off by 1 in both and
                # still pick up the identified matches
                tolerance = 1.5

        matches = xyxymatch(
            im_xyref,
            refxy,
            origin=xyoff,
            tolerance=tolerance,
            separation=separation
        )

        nmatches = len(matches)
        self._catalog.meta['nmatches'] = nmatches
        minput_idx = matches['input_idx']

        catlen = len(self._catalog)

        # matched_ref_id:
        if 'matched_ref_id' not in colnames:
            c = table.MaskedColumn(name='matched_ref_id', dtype=int,
                                   length=catlen, mask=True)
            self._catalog.add_column(c)
        else:
            self._catalog['matched_ref_id'].mask = True
        self._catalog['matched_ref_id'][minput_idx] = \
            self._catalog['id'][minput_idx]
        self._catalog['matched_ref_id'].mask[minput_idx] = False

        # this is needed to index reference catalog directly without using
        # astropy table indexing which at this moment is experimental:
        if '_raw_matched_ref_idx' not in colnames:
            c = table.MaskedColumn(name='_raw_matched_ref_idx',
                                   dtype=int, length=catlen, mask=True)
            self._catalog.add_column(c)
        else:
            self._catalog['_raw_matched_ref_idx'].mask = True
        self._catalog['_raw_matched_ref_idx'][minput_idx] = \
            matches['ref_idx']
        self._catalog['_raw_matched_ref_idx'].mask[minput_idx] = False

        log.info("Found {:d} matches for '{}'...".format(nmatches, self.name))

        return matches