Esempio n. 1
0
    def test_error_radius_value(self):
        # Demonstrate that we calculate the expected value for the error
        # radius
        self.p.values['xbar'] = Uncertain(1025, 1)
        self.p.values['ybar'] = Uncertain(1025, 1)
        d_ncp = Detection(self.p, self.ncp_image)

        # cdelt gives the per-pixel increment along the axis in degrees
        # Detection.error_radius is in arcsec
        expected_error_radius = math.sqrt(
            (self.p.values['xbar'].error * self.ncp_image.wcs.cdelt[0] *
             3600)**2 + (self.p.values['ybar'].error *
                         self.ncp_image.wcs.cdelt[1] * 3600)**2)

        self.assertAlmostEqual(d_ncp.error_radius, expected_error_radius, 6)
Esempio n. 2
0
def get_paramset():
    paramset = ParamSet()
    paramset.sig = 1
    paramset.values = {
        'peak': Uncertain(10, 0),
        'flux': Uncertain(10, 0),
        'semimajor': Uncertain(10, 1),
        'semiminor': Uncertain(10, 1),
        'theta': Uncertain(0, 1),
        'semimaj_deconv': Uncertain(10, 1),
        'semimin_deconv': Uncertain(10, 1),
        'theta_deconv': Uncertain(10, 1),
        'xbar': Uncertain(10, 1),
        'ybar': Uncertain(10, 1)
    }
    return paramset
Esempio n. 3
0
    def __init__(self,
                 clean_bias=0.0,
                 clean_bias_error=0.0,
                 frac_flux_cal_error=0.0,
                 alpha_maj1=2.5,
                 alpha_min1=0.5,
                 alpha_maj2=0.5,
                 alpha_min2=2.5,
                 alpha_maj3=1.5,
                 alpha_min3=1.5):

        self.clean_bias = clean_bias
        self.clean_bias_error = clean_bias_error
        self.frac_flux_cal_error = frac_flux_cal_error
        self.alpha_maj1 = alpha_maj1
        self.alpha_min1 = alpha_min1
        self.alpha_maj2 = alpha_maj2
        self.alpha_min2 = alpha_min2
        self.alpha_maj3 = alpha_maj3
        self.alpha_min3 = alpha_min3

        self.values = {
            'peak': Uncertain(),
            'flux': Uncertain(),
            'xbar': Uncertain(),
            'ybar': Uncertain(),
            'semimajor': Uncertain(),
            'semiminor': Uncertain(),
            'theta': Uncertain(),
            'semimaj_deconv': Uncertain(),
            'semimin_deconv': Uncertain(),
            'theta_deconv': Uncertain()
        }
        # This parameter gives the number of components that could not be
        # deconvolved, IERR from deconf.f.
        self.deconv_imposs = 2

        # These flags are used to indicate where the values stored in this
        # parameterset have come from: we set them to True if & when moments
        # and/or Gaussian fitting succeeds.
        self.moments = False
        self.gaussian = False

        ##More metadata about the fit: only valid for Gaussian fits:
        self.chisq = None
        self.reduced_chisq = None
Esempio n. 4
0
    def testErrorBoxOverlapsEdge(self):
        """
        Error box overflows image

        Sometimes when fitting at a fixed position, we get extremely large
        uncertainty values.  These create an error box on position which
        extends outside the image, causing errors when we try to calculate the
        RA / Dec uncertainties.  This test ensures we handle this case
        gracefully.
        """
        img = self.image

        fake_params = sourcefinder.extract.ParamSet()
        fake_params.values.update({
            'peak': Uncertain(0.0, 0.5),
            'flux': Uncertain(0.0, 0.5),
            'xbar': Uncertain(5.5, 10000.5),  # Danger Will Robinson
            'ybar': Uncertain(5.5, 3),
            'semimajor': Uncertain(4, 200),
            'semiminor': Uncertain(4, 2),
            'theta': Uncertain(30, 10),
        })
        fake_params.sig = 0
        det = sourcefinder.extract.Detection(fake_params, img)
        # Raises runtime error prior to bugfix for issue #3294
        det._physical_coordinates()
        self.assertEqual(det.ra.error, float('inf'))
        self.assertEqual(det.dec.error, float('inf'))
Esempio n. 5
0
    def test_ra_error_scaling(self):
        # Demonstrate that RA errors scale with declination.
        # See HipChat discussion of 2013-08-28.
        # Values of all parameters are dummies except for the pixel position.
        # First, construct a source at the NCP
        self.p.values['xbar'] = Uncertain(1025, 1)
        self.p.values['ybar'] = Uncertain(1025, 1)
        d_ncp = Detection(self.p, self.ncp_image)

        # Then construct a source somewhere away from the NCP
        self.p.values['xbar'] = Uncertain(125, 1)
        self.p.values['ybar'] = Uncertain(125, 1)
        d_not_ncp = Detection(self.p, self.ncp_image)

        # One source is at higher declination
        self.assertGreater(d_ncp.dec.value, d_not_ncp.dec.value)

        # The RA error at higher declination must be greater
        self.assertGreater(d_ncp.ra.error, d_not_ncp.ra.error)
Esempio n. 6
0
    def testPositions(self):
        # The pixel position x, y of the source should be the same as the
        # position of the maximum in the generated result map.
        x_pos = 40
        y_pos = 60

        empty_data = numpy.zeros((100, 100))
        SrcMeasurement = namedtuple(
            "SrcMeasurement", ['peak', 'x', 'y', 'smaj', 'smin', 'theta'])
        src_measurement = SrcMeasurement(peak=Uncertain(10),
                                         x=Uncertain(x_pos),
                                         y=Uncertain(y_pos),
                                         smaj=Uncertain(10),
                                         smin=Uncertain(10),
                                         theta=Uncertain(0))
        gaussian_map, residual_map = generate_result_maps(
            empty_data, [src_measurement])
        gaussian_max_x = numpy.where(gaussian_map == gaussian_map.max())[0][0]
        gaussian_max_y = numpy.where(gaussian_map == gaussian_map.max())[1][0]

        self.assertEqual(gaussian_max_x, x_pos)
        self.assertEqual(gaussian_max_y, y_pos)
Esempio n. 7
0
    def _physical_coordinates(self):
        """Convert the pixel parameters for this object into something
        physical."""

        # First, the RA & dec.
        self.ra, self.dec = [
            Uncertain(x)
            for x in self.imagedata.wcs.p2s([self.x.value, self.y.value])
        ]
        if numpy.isnan(self.dec.value) or abs(self.dec) > 90.0:
            raise ValueError("object falls outside the sky")

        # First, determine local north.
        help1 = numpy.cos(numpy.radians(self.ra.value))
        help2 = numpy.sin(numpy.radians(self.ra.value))
        help3 = numpy.cos(numpy.radians(self.dec.value))
        help4 = numpy.sin(numpy.radians(self.dec.value))
        center_position = numpy.array([help3 * help1, help3 * help2, help4])

        # The length of this vector is chosen such that it touches
        # the tangent plane at center position.
        # The cross product of the local north vector and the local east
        # vector will always be aligned with the center_position vector.
        if center_position[2] != 0:
            local_north_position = numpy.array(
                [0., 0., 1. / center_position[2]])
        else:
            # If we are right on the equator (ie dec=0) the division above
            # will blow up: as a workaround, we use something Really Big
            # instead.
            local_north_position = numpy.array([0., 0., 99e99])
        # Next, determine the orientation of the y-axis wrt local north
        # by incrementing y by a small amount and converting that
        # to celestial coordinates. That small increment is conveniently
        # chosen to be an increment of 1 pixel.

        endy_ra, endy_dec = self.imagedata.wcs.p2s(
            [self.x.value, self.y.value + 1.])
        help5 = numpy.cos(numpy.radians(endy_ra))
        help6 = numpy.sin(numpy.radians(endy_ra))
        help7 = numpy.cos(numpy.radians(endy_dec))
        help8 = numpy.sin(numpy.radians(endy_dec))
        endy_position = numpy.array([help7 * help5, help7 * help6, help8])

        # Extend the length of endy_position to make it touch the plane
        # tangent at center_position.
        endy_position /= numpy.dot(center_position, endy_position)

        diff1 = endy_position - center_position
        diff2 = local_north_position - center_position

        cross_prod = numpy.cross(diff2, diff1)

        length_cross_sq = numpy.dot(cross_prod, cross_prod)

        normalization = numpy.dot(diff1, diff1) * numpy.dot(diff2, diff2)

        # The length of the cross product equals the product of the lengths of
        # the vectors times the sine of their angle.
        # This is the angle between the y-axis and local north,
        # measured eastwards.
        # yoffset_angle = numpy.degrees(
        #    numpy.arcsin(numpy.sqrt(length_cross_sq/normalization)))
        # The formula above is commented out because the angle computed
        # in this way will always be 0<=yoffset_angle<=90.
        # We'll use the dotproduct instead.
        yoffs_rad = (numpy.arccos(
            numpy.dot(diff1, diff2) / numpy.sqrt(normalization)))

        # The multiplication with -sign_cor makes sure that the angle
        # is measured eastwards (increasing RA), not westwards.
        sign_cor = (numpy.dot(cross_prod, center_position) /
                    numpy.sqrt(length_cross_sq))
        yoffs_rad *= -sign_cor
        yoffset_angle = numpy.degrees(yoffs_rad)

        # Now that we have the BPA, we can also compute the position errors
        # properly, by projecting the errors in pixel coordinates (x and y)
        # on local north and local east.
        errorx_proj = numpy.sqrt((self.x.error * numpy.cos(yoffs_rad))**2 +
                                 (self.y.error * numpy.sin(yoffs_rad))**2)
        errory_proj = numpy.sqrt((self.x.error * numpy.sin(yoffs_rad))**2 +
                                 (self.y.error * numpy.cos(yoffs_rad))**2)

        # Now we have to sort out which combination of errorx_proj and
        # errory_proj gives the largest errors in RA and Dec.
        try:
            end_ra1, end_dec1 = self.imagedata.wcs.p2s(
                [self.x.value + errorx_proj, self.y.value])
            end_ra2, end_dec2 = self.imagedata.wcs.p2s(
                [self.x.value, self.y.value + errory_proj])
            # Here we include the position calibration errors
            self.ra.error = self.eps_ra + max(
                numpy.fabs(self.ra.value - end_ra1),
                numpy.fabs(self.ra.value - end_ra2))
            self.dec.error = self.eps_dec + max(
                numpy.fabs(self.dec.value - end_dec1),
                numpy.fabs(self.dec.value - end_dec2))
        except RuntimeError:
            # We get a runtime error from wcs.p2s if the errors place the
            # limits outside of the image.
            # In which case we set the RA / DEC uncertainties to infinity
            self.ra.error = float('inf')
            self.dec.error = float('inf')

        # Estimate an absolute angular error on our central position.
        self.error_radius = utils.get_error_radius(self.imagedata.wcs,
                                                   self.x.value, self.x.error,
                                                   self.y.value, self.y.error)

        # Now we can compute the BPA, east from local north.
        # That these angles can simply be added is not completely trivial.
        # First, the Gaussian in gaussian.py must be such that theta is
        # measured from the positive y-axis in the direction of negative x.
        # Secondly, x and y are defined such that the direction
        # positive y-->negative x-->negative y-->positive x is the same
        # direction (counterclockwise) as (local) north-->east-->south-->west.
        # If these two conditions are matched, the formula below is valid.
        # Of course, the formula is also valid if theta is measured
        # from the positive y-axis towards positive x
        # and both of these directions are equal (clockwise).
        self.theta_celes = Uncertain(
            (numpy.degrees(self.theta.value) + yoffset_angle) % 180,
            numpy.degrees(self.theta.error))
        self.theta_dc_celes = Uncertain(
            (self.theta_dc.value + yoffset_angle) % 180,
            numpy.degrees(self.theta_dc.error))

        # Next, the axes.
        # Note that the signs of numpy.sin and numpy.cos in the
        # four expressions below are arbitrary.
        self.end_smaj_x = (self.x.value -
                           numpy.sin(self.theta.value) * self.smaj.value)
        self.start_smaj_x = (self.x.value +
                             numpy.sin(self.theta.value) * self.smaj.value)
        self.end_smaj_y = (self.y.value +
                           numpy.cos(self.theta.value) * self.smaj.value)
        self.start_smaj_y = (self.y.value -
                             numpy.cos(self.theta.value) * self.smaj.value)
        self.end_smin_x = (self.x.value +
                           numpy.cos(self.theta.value) * self.smin.value)
        self.start_smin_x = (self.x.value -
                             numpy.cos(self.theta.value) * self.smin.value)
        self.end_smin_y = (self.y.value +
                           numpy.sin(self.theta.value) * self.smin.value)
        self.start_smin_y = (self.y.value -
                             numpy.sin(self.theta.value) * self.smin.value)

        def pixel_to_spatial(x, y):
            try:
                return self.imagedata.wcs.p2s([x, y])
            except RuntimeError:
                logger.debug("pixel_to_spatial failed at %f, %f" % (x, y))
                return numpy.nan, numpy.nan

        end_smaj_ra, end_smaj_dec = pixel_to_spatial(self.end_smaj_x,
                                                     self.end_smaj_y)
        end_smin_ra, end_smin_dec = pixel_to_spatial(self.end_smin_x,
                                                     self.end_smin_y)

        smaj_asec = coordinates.angsep(self.ra.value, self.dec.value,
                                       end_smaj_ra, end_smaj_dec)
        scaling_smaj = smaj_asec / self.smaj.value
        errsmaj_asec = scaling_smaj * self.smaj.error
        self.smaj_asec = Uncertain(smaj_asec, errsmaj_asec)

        smin_asec = coordinates.angsep(self.ra.value, self.dec.value,
                                       end_smin_ra, end_smin_dec)
        scaling_smin = smin_asec / self.smin.value
        errsmin_asec = scaling_smin * self.smin.error
        self.smin_asec = Uncertain(smin_asec, errsmin_asec)
Esempio n. 8
0
    def deconvolve_from_clean_beam(self, beam):
        """Deconvolve with the clean beam"""

        # If the fitted axes are smaller than the clean beam
        # (=restoring beam) axes, the axes and position angle
        # can be deconvolved from it.
        fmaj = 2. * self['semimajor'].value
        fmajerror = 2. * self['semimajor'].error
        fmin = 2. * self['semiminor'].value
        fminerror = 2. * self['semiminor'].error
        fpa = numpy.degrees(self['theta'].value)
        fpaerror = numpy.degrees(self['theta'].error)
        cmaj = 2. * beam[0]
        cmin = 2. * beam[1]
        cpa = numpy.degrees(beam[2])

        rmaj, rmin, rpa, ierr = deconv(fmaj, fmin, fpa, cmaj, cmin, cpa)
        # This parameter gives the number of components that could not be
        # deconvolved, IERR from deconf.f.
        self.deconv_imposs = ierr
        # Now, figure out the error bars.
        if rmaj > 0:
            # In this case the deconvolved position angle is defined.
            # For convenience we reset rpa to the interval [-90, 90].
            if rpa > 90:
                rpa = -numpy.mod(-rpa, 180.)
            self['theta_deconv'].value = rpa

            # In the general case, where the restoring beam is elliptic,
            # calculating the error bars of the deconvolved position angle
            # is more complicated than in the NVSS case, where a circular
            # restoring beam was used.
            # In the NVSS case the error bars of the deconvolved angle are
            # equal to the fitted angle.
            rmaj1, rmin1, rpa1, ierr1 = deconv(fmaj, fmin, fpa + fpaerror,
                                               cmaj, cmin, cpa)
            if ierr1 < 2:
                if rpa1 > 90:
                    rpa1 = -numpy.mod(-rpa1, 180.)
                rpaerror1 = numpy.abs(rpa1 - rpa)
                # An angle error can never be more than 90 degrees.
                if rpaerror1 > 90.:
                    rpaerror1 = numpy.mod(-rpaerror1, 180.)
            else:
                rpaerror1 = numpy.nan
            rmaj2, rmin2, rpa2, ierr2 = deconv(fmaj, fmin, fpa - fpaerror,
                                               cmaj, cmin, cpa)
            if ierr2 < 2:
                if rpa2 > 90:
                    rpa2 = -numpy.mod(-rpa2, 180.)
                rpaerror2 = numpy.abs(rpa2 - rpa)
                # An angle error can never be more than 90 degrees.
                if rpaerror2 > 90.:
                    rpaerror2 = numpy.mod(-rpaerror2, 180.)
            else:
                rpaerror2 = numpy.nan
            if numpy.isnan(rpaerror1) or numpy.isnan(rpaerror2):
                self['theta_deconv'].error = numpy.nansum(
                    [rpaerror1, rpaerror2])
            else:
                self['theta_deconv'].error = numpy.mean([rpaerror1, rpaerror2])
            self['semimaj_deconv'].value = rmaj / 2.
            rmaj3, rmin3, rpa3, ierr3 = deconv(fmaj + fmajerror, fmin, fpa,
                                               cmaj, cmin, cpa)
            # If rmaj>0, then rmaj3 should also be > 0,
            # if I am not mistaken, see the formulas at
            # the end of ch.2 of Spreeuw's Ph.D. thesis.
            if fmaj - fmajerror > fmin:
                rmaj4, rmin4, rpa4, ierr4 = deconv(fmaj - fmajerror, fmin, fpa,
                                                   cmaj, cmin, cpa)
                if rmaj4 > 0:
                    self['semimaj_deconv'].error = numpy.mean(
                        [numpy.abs(rmaj3 - rmaj),
                         numpy.abs(rmaj - rmaj4)])
                else:
                    self['semimaj_deconv'].error = numpy.abs(rmaj3 - rmaj)
            else:
                rmin4, rmaj4, rpa4, ierr4 = deconv(fmin, fmaj - fmajerror, fpa,
                                                   cmaj, cmin, cpa)
                if rmaj4 > 0:
                    self['semimaj_deconv'].error = numpy.mean(
                        [numpy.abs(rmaj3 - rmaj),
                         numpy.abs(rmaj - rmaj4)])
                else:
                    self['semimaj_deconv'].error = numpy.abs(rmaj3 - rmaj)
            if rmin > 0:
                self['semimin_deconv'].value = rmin / 2.
                if fmin + fminerror < fmaj:
                    rmaj5, rmin5, rpa5, ierr5 = deconv(fmaj, fmin + fminerror,
                                                       fpa, cmaj, cmin, cpa)
                else:
                    rmin5, rmaj5, rpa5, ierr5 = deconv(fmin + fminerror, fmaj,
                                                       fpa, cmaj, cmin, cpa)
                # If rmin > 0, then rmin5 should also be > 0,
                # if I am not mistaken, see the formulas at
                # the end of ch.2 of Spreeuw's Ph.D. thesis.
                rmaj6, rmin6, rpa6, ierr6 = deconv(fmaj, fmin - fminerror, fpa,
                                                   cmaj, cmin, cpa)
                if rmin6 > 0:
                    self['semimin_deconv'].error = numpy.mean(
                        [numpy.abs(rmin6 - rmin),
                         numpy.abs(rmin5 - rmin)])
                else:
                    self['semimin_deconv'].error = numpy.abs(rmin5 - rmin)
            else:
                self['semimin_deconv'] = Uncertain(numpy.nan, numpy.nan)
        else:
            self['semimaj_deconv'] = Uncertain(numpy.nan, numpy.nan)
            self['semimin_deconv'] = Uncertain(numpy.nan, numpy.nan)
            self['theta_deconv'] = Uncertain(numpy.nan, numpy.nan)

        return self
Esempio n. 9
0
    def _condon_formulae(self, noise, beam):
        """Returns the errors on parameters from Gaussian fits according to
        the Condon (PASP 109, 166 (1997)) formulae.

        These formulae are not perfect, but we'll use them for the
        time being.  (See Refregier and Brown (astro-ph/9803279v1) for
        a more rigorous approach.) It also returns the corrected peak.
        The peak is corrected for the overestimate due to the local
        noise gradient.
        """
        peak = self['peak'].value
        flux = self['flux'].value
        smaj = self['semimajor'].value
        smin = self['semiminor'].value
        theta = self['theta'].value

        theta_B, theta_b = utils.calculate_correlation_lengths(
            beam[0], beam[1])

        rho_sq1 = ((smaj * smin / (theta_B * theta_b)) *
                   (1. + (theta_B / (2. * smaj))**2)**self.alpha_maj1 *
                   (1. + (theta_b / (2. * smin))**2)**self.alpha_min1 *
                   (peak / noise)**2)
        rho_sq2 = ((smaj * smin / (theta_B * theta_b)) *
                   (1. + (theta_B / (2. * smaj))**2)**self.alpha_maj2 *
                   (1. + (theta_b / (2. * smin))**2)**self.alpha_min2 *
                   (peak / noise)**2)
        rho_sq3 = ((smaj * smin / (theta_B * theta_b)) *
                   (1. + (theta_B / (2. * smaj))**2)**self.alpha_maj3 *
                   (1. + (theta_b / (2. * smin))**2)**self.alpha_min3 *
                   (peak / noise)**2)

        rho1 = numpy.sqrt(rho_sq1)
        rho2 = numpy.sqrt(rho_sq2)
        rho3 = numpy.sqrt(rho_sq3)

        denom1 = numpy.sqrt(2. * numpy.log(2.)) * rho1
        denom2 = numpy.sqrt(2. * numpy.log(2.)) * rho2

        # Here you get the errors parallel to the fitted semi-major and
        # semi-minor axes as taken from the NVSS paper (Condon et al. 1998,
        # AJ, 115, 1693), formula 25.
        # Those variances are twice the theoreticals, so the errors in
        # position are sqrt(2) as large as one would get from formula 21
        # of the Condon (1997) paper.
        error_par_major = 2. * smaj / denom1
        error_par_minor = 2. * smin / denom2

        # When these errors are converted to RA and Dec,
        # calibration uncertainties will have to be added,
        # like in formulae 27 of the NVSS paper.
        errorx = numpy.sqrt((error_par_major * numpy.sin(theta))**2 +
                            (error_par_minor * numpy.cos(theta))**2)
        errory = numpy.sqrt((error_par_major * numpy.cos(theta))**2 +
                            (error_par_minor * numpy.sin(theta))**2)

        # Note that we report errors in HWHM axes instead of FWHM axes
        # so the errors are half the errors of formula 29 of the NVSS paper.
        errorsmaj = numpy.sqrt(2) * smaj / rho1
        errorsmin = numpy.sqrt(2) * smin / rho2

        if smaj > smin:
            errortheta = 2.0 * (smaj * smin / (smaj**2 - smin**2)) / rho2
        else:
            errortheta = numpy.pi
        if errortheta > numpy.pi:
            errortheta = numpy.pi

        peak += -noise**2 / peak + self.clean_bias

        errorpeaksq = ((self.frac_flux_cal_error * peak)**2 +
                       self.clean_bias_error**2 + 2. * peak**2 / rho_sq3)

        errorpeak = numpy.sqrt(errorpeaksq)

        help1 = (errorsmaj / smaj)**2
        help2 = (errorsmin / smin)**2
        help3 = theta_B * theta_b / (4. * smaj * smin)
        errorflux = numpy.abs(flux) * numpy.sqrt(errorpeaksq / peak**2 +
                                                 help3 * (help1 + help2))

        self['peak'] = Uncertain(peak, errorpeak)
        self['flux'].error = errorflux
        self['xbar'].error = errorx
        self['ybar'].error = errory
        self['semimajor'].error = errorsmaj
        self['semiminor'].error = errorsmin
        self['theta'].error = errortheta

        return self
Esempio n. 10
0
 def test_error_radius_with_dec(self):
     self.p.values['xbar'] = Uncertain(1025, 1)
     self.p.values['ybar'] = Uncertain(1025, 1)
     d_ncp = Detection(self.p, self.ncp_image)
     d_equator = Detection(self.p, self.equator_image)
     self.assertEqual(d_ncp.error_radius, d_equator.error_radius)