Source code for tkp.sourcefinder.utils

"""
This module contain utilities for the source finding routines
"""

import numpy
import math
import scipy.integrate
from tkp.sourcefinder.gaussian import gaussian
from tkp.utility import coordinates

[docs]def generate_subthresholds(min_value, max_value, num_thresholds): """ Generate a series of ``num_thresholds`` logarithmically spaced values in the range (min_value, max_value) (both exclusive). """ # First, we calculate a logarithmically spaced sequence between exp(0.0) # and (max - min + 1). That is, the total range is between 1 and one # greater than the difference between max and min. # We subtract 1 from this to get the range between 0 and (max-min). # We add min to that to get the range between min and max. subthrrange = numpy.logspace( 0.0, numpy.log(max_value + 1 - min_value), num=num_thresholds+1, # first value == min_value base=numpy.e, endpoint=False # do not include max_value )[1:] subthrrange += (min_value - 1) return subthrrange
[docs]def get_error_radius(wcs, x_value, x_error, y_value, y_error): """ Estimate an absolute angular error on the position (x_value, y_value) with the given errors. This is a pessimistic estimate, because we take sum of the error along the X and Y axes. Better might be to project them both back on to the major/minor axes of the elliptical fit, but this should do for now. """ error_radius = 0 try: centre_ra, centre_dec = wcs.p2s([x_value, y_value]) # We check all possible combinations in case we have a nonlinear # WCS. for pixpos in [ (x_value + x_error, y_value + y_error), (x_value - x_error, y_value + y_error), (x_value + x_error, y_value - y_error), (x_value - x_error, y_value - y_error) ]: error_ra, error_dec = wcs.p2s(pixpos) error_radius = max( error_radius, coordinates.angsep(centre_ra, centre_dec, error_ra, error_dec) ) 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 angular # uncertainty to infinity. error_radius = float('inf') return error_radius
[docs]def circular_mask(xdim, ydim, radius): """ Returns a numpy array of shape (xdim, ydim). All points with radius of the centre are set to 0; outside that region, they are set to 1. """ centre_x, centre_y = (xdim-1)/2.0, (ydim-1)/2.0 x, y = numpy.ogrid[-centre_x:xdim-centre_x, -centre_y:ydim-centre_y] return x*x + y*y >= radius*radius
[docs]def generate_result_maps(data, sourcelist): """Return a source and residual image Given a data array (image) and list of sources, return two images, one showing the sources themselves and the other the residual after the sources have been removed from the input data. """ residual_map = numpy.array(data) # array constructor copies by default gaussian_map = numpy.zeros(residual_map.shape) for src in sourcelist: # Include everything with 6 times the std deviation along the major # axis. Should be very very close to 100% of the flux. box_size = 6 * src.smaj.value / math.sqrt(2 * math.log(2)) lower_bound_x = max(0, int(src.x.value - 1 - box_size)) upper_bound_x = min(residual_map.shape[0], int(src.x.value - 1 + box_size)) lower_bound_y = max(0, int(src.y.value - 1 - box_size)) upper_bound_y = min(residual_map.shape[1], int(src.y.value - 1 + box_size)) local_gaussian = gaussian( src.peak.value, src.x.value, src.y.value, src.smaj.value, src.smin.value, src.theta.value )( numpy.indices(residual_map.shape)[0,lower_bound_x:upper_bound_x,lower_bound_y:upper_bound_y], numpy.indices(residual_map.shape)[1,lower_bound_x:upper_bound_x,lower_bound_y:upper_bound_y] ) gaussian_map[lower_bound_x:upper_bound_x, lower_bound_y:upper_bound_y] += local_gaussian residual_map[lower_bound_x:upper_bound_x, lower_bound_y:upper_bound_y] -= local_gaussian return gaussian_map, residual_map
[docs]def calculate_correlation_lengths(semimajor, semiminor): """Calculate the Condon correlation length In order to derive the error bars from Gauss fitting from the Condon (1997, PASP 109, 116C) formulae, one needs the so called correlation length. The Condon formulae assumes a circular area with diameter theta_N (in pixels) for the correlation. This was later generalized by Hopkins et al. (2003, AJ 125, 465) for correlation areas which are not axisymmetric. Basically one has theta_N^2 = theta_B*theta_b. Good estimates in general are: + theta_B = 2.0 * semimajar + theta_b = 2.0 * semiminor """ return (2.0 * semimajor, 2.0 * semiminor)
[docs]def calculate_beamsize(semimajor, semiminor): """Calculate the beamsize based on the semi major and minor axes""" return numpy.pi * semimajor * semiminor
[docs]def fudge_max_pix(semimajor, semiminor, theta): """Estimate peak flux correction at pixel of maximum flux Previously, we adopted Rengelink's correction for the underestimate of the peak of the Gaussian by the maximum pixel method: fudge_max_pix = 1.06. See the WENSS paper (1997A&AS..124..259R) or his thesis. (The peak of the Gaussian is, of course, never at the exact center of the pixel, that's why the maximum pixel method will always underestimate it.) But, instead of just taking 1.06 one can make an estimate of the overall correction by assuming that the true peak is at a random position on the peak pixel and averaging over all possible corrections. This overall correction makes use of the beamshape, so strictly speaking only accurate for unresolved sources. """ # scipy.integrate.dblquad: Computes a double integral # from the scipy docs: # Return the double (definite) integral of f1(y,x) from x=a..b # and y=f2(x)..f3(x). log20 = numpy.log(2.0) cos_theta = numpy.cos(theta) sin_theta = numpy.sin(theta) def landscape(y, x): up = math.pow(((cos_theta * x + sin_theta * y) / semiminor ), 2) down = math.pow(((cos_theta * y - sin_theta * x) / semimajor ), 2) return numpy.exp(log20 * ( up + down )) (correction, abserr) = scipy.integrate.dblquad(landscape, -0.5, 0.5, lambda ymin: -0.5, lambda ymax: 0.5) return correction
[docs]def maximum_pixel_method_variance(semimajor, semiminor, theta): """Estimate variance for peak flux at pixel position of maximum When we use the maximum pixel method, with a correction fudge_max_pix, there should be no bias, unless the peaks of the Gaussians are not randomly distributed, but relatively close to the centres of the pixels due to selection effects from detection thresholds. Disregarding the latter effect and noise, we can compute the variance of the maximum pixel method by integrating (the true flux-the average true flux)^2 = (the true flux-fudge_max_pix)^2 over the pixel area and dividing by the pixel area ( = 1). This is just equal to integral of the true flux^2 over the pixel area - fudge_max_pix^2. """ # scipy.integrate.dblquad: Computes a double integral # from the scipy docs: # Return the double (definite) integral of f1(y,x) from x=a..b # and y=f2(x)..f3(x). log20 = numpy.log(2.0) cos_theta = numpy.cos(theta) sin_theta = numpy.sin(theta) def landscape(y, x): return numpy.exp(2.0 * log20 * ( math.pow(((cos_theta * x + sin_theta * y) / semiminor), 2) + math.pow(((cos_theta * y - sin_theta * x) / semimajor), 2) ) ) (result, abserr) = scipy.integrate.dblquad(landscape, -0.5, 0.5, lambda ymin: -0.5, lambda ymax: 0.5) variance = result - math.pow(fudge_max_pix(semimajor, semiminor, theta), 2) return variance
[docs]def flatten(nested_list): """Flatten a nested list Nested lists are made in the deblending algorithm. They're awful. This is a piece of code I grabbed from http://www.daniweb.com/code/snippet216879.html. The output from this method is a generator, so make sure to turn it into a list, like this:: flattened = list(flatten(nested)). """ for elem in nested_list: if isinstance(elem, (tuple, list, numpy.ndarray)): for i in flatten(elem): yield i else: yield elem