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)
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
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
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'))
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)
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)
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)
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
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
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)