def circular_annulus_footprint(radius_inner, radius_outer, dtype=np.int): """ Create a circular annulus footprint. A pixel is considered to be entirely in or out of the footprint depending on whether its center is in or out of the footprint. The size of the output array is the minimal bounding box for the footprint. Parameters ---------- radius_inner : int The inner radius of the circular annulus. radius_outer : int The outer radius of the circular annulus. dtype : data-type, optional The data type of the output `~numpy.ndarray`. Returns ------- footprint : `~numpy.ndarray` A footprint where array elements are 1 within the footprint and 0 otherwise. Examples -------- >>> from astroimtools import circular_footprint >>> circular_annulus_footprint(1, 2) array([[0, 0, 1, 0, 0], [0, 1, 0, 1, 0], [1, 0, 0, 0, 1], [0, 1, 0, 1, 0], [0, 0, 1, 0, 0]]) """ if radius_inner > radius_outer: raise ValueError('radius_outer must be >= radius_inner') size = (radius_outer * 2) + 1 y, x = np.mgrid[0:size, 0:size] circle_outer = Ellipse2D(1, radius_outer, radius_outer, radius_outer, radius_outer, theta=0)(x, y) circle_inner = Ellipse2D(1., radius_outer, radius_outer, radius_inner, radius_inner, theta=0)(x, y) return np.asarray(circle_outer - circle_inner, dtype=dtype)
def ellipse_region(cube, center, a, ecc, theta, annulus = False): ''' Function that returns a mask that defines the elements that lie within an ellipsoidal region. The ellipse can also be annular. Inputs: Cube: The data array center: Tuple of the central coordinates (x_0, y_0) (spaxels) a: The semi-major axis length (spaxels). ecc: The eccentricity of the ellipse (0<ecc<1) theta: The rotation angle of the ellipse from vertical (degrees) rotating clockwise. Optional Inputs: annulus: False if just simple ellipse, otherwise is the INNER annular radius (spaxels) ''' # Define angle, eccentricity term, and semi-minor axis an = Angle(theta, 'deg') e = np.sqrt(1 - (ecc**2)) b = a * e # Create outer ellipse ell_region = Ellipse2D(amplitude=10, x_0 = center[0], y_0 = center[1],\ a=a, b=b, theta=an.radian) x,y = np.mgrid[0:cube.shape[1], 0:cube.shape[2]] ell_good = ell_region(x,y) if annulus: # Define inner ellipse parameters and then create inner mask a2 = annulus b2 = a2 * e ell_region_inner = Ellipse2D(amplitude=10, x_0 = center[0],\ y_0 = center[1], a=a2, b=b2, theta=an.radian) # Set region of outer mask and within inner ellipse to zero, leaving # an annular elliptical region ell_inner_good = ell_region_inner(x,y) ell_good[ell_inner_good > 0] = 0 #fig, ax = mpl.subplots(figsize = (12,7)) #mpl.imshow(np.nansum(cube, axis = 0), origin = 'lower', interpolation = None) #mpl.imshow(ell_good, alpha=0.2, origin = 'lower', interpolation = None) #mpl.show() # Return the mask return np.array(ell_good, dtype = bool)
def intensity_props(data, blob, min_rad=4): ''' Return the mean and std for the elliptical region in the given data. Parameters ---------- data : LowerDimensionalObject or SpectralCube Data to estimate the background from. blob : numpy.array Contains the properties of the region. ''' y, x, major, minor, pa = blob[:5] inner_ellipse = \ Ellipse2D(True, x, y, max(min_rad, 0.75 * major), max(min_rad, 0.75 * minor), pa) yy, xx = np.mgrid[:data.shape[-2], :data.shape[-1]] ellip_mask = inner_ellipse(xx, yy).astype(bool) vals = data[ellip_mask] mean, sig = robust_skewed_std(vals) return mean, sig
def test_ellipse_extent(): # Test this properly bounds the ellipse imshape = (100, 100) coords = y, x = np.indices(imshape) amplitude = 1 x0 = 50 y0 = 50 a = 30 b = 10 theta = np.pi / 4 model = Ellipse2D(amplitude, x0, y0, a, b, theta) dx, dy = ellipse_extent(a, b, theta) limits = ((y0 - dy, y0 + dy), (x0 - dx, x0 + dx)) model.bounding_box = limits actual = model.render(coords=coords) expected = model(x, y) # Check that the full ellipse is captured np.testing.assert_allclose(expected, actual, atol=0, rtol=1) # Check the bounding_box isn't too large limits = np.array(limits).flatten() for i in [0, 1]: s = actual.sum(axis=i) diff = np.abs(limits[2 * i] - np.where(s > 0)[0][0]) assert diff < 1
def add_holes(shape, nholes=100, rad_max=30, rad_min=10, hole_level=None, return_info=False, max_corr=0.5, max_trials=500, max_eccent=2.): ylim, xlim = shape array = np.ones(shape) * 255 yy, xx = np.mgrid[-ylim/2:ylim/2, -xlim/2:xlim/2] params = np.empty((0, 5)) ct = 0 trial = 0 while True: xcenter = np.random.random_integers(-(xlim/2-rad_max), high=xlim/2-rad_max) ycenter = np.random.random_integers(-(ylim/2-rad_max), high=ylim/2-rad_max) major = np.random.uniform(rad_min, rad_max) minor = np.random.uniform(major / float(max_eccent), major) pa = np.random.uniform(0, np.pi) blob = np.array([ycenter, xcenter, major, minor, pa]) if ct != 0: corrs = np.array([overlap_metric(par, blob, return_corr=True) for par in params]) if np.any(corrs > max_corr): trial += 1 continue if hole_level is None: # Choose random hole bkg levels bkg = np.random.random_integers(0, 230) else: bkg = hole_level ellip = Ellipse2D(True, xcenter, ycenter, major, minor, pa) array[ellip(yy, xx).astype(bool)] = bkg params = np.append(params, blob[np.newaxis, :], axis=0) ct += 1 trial += 1 if ct == nholes: break if trial == max_trials: Warning("Only found %i valid regions in %i trials." % (ct, max_trials)) break if return_info: return array, params return array
def circular_annulus_footprint(radius_inner, radius_outer, dtype=np.int): if radius_inner > radius_outer: raise ValueError('radius_outer must be >= radius_inner') size = (radius_outer * 2) + 1 y, x = np.mgrid[0:size, 0:size] circle_outer = Ellipse2D(1, radius_outer, radius_outer, radius_outer, radius_outer, theta=0)(x, y) circle_inner = Ellipse2D(1., radius_outer, radius_outer, radius_inner, radius_inner, theta=0)(x, y) return np.asarray(circle_outer - circle_inner, dtype=dtype)
def fitEllipse(cc,axesLength,angle): x0, y0 = cc[0],cc[1] a, b = axesLength[0],axesLength[1] theta = Angle(angle, 'deg') e = Ellipse2D(amplitude=100., x_0=x0, y_0=y0, a=a, b=b, theta=theta.radian) y, x = np.mgrid[0:1000, 0:1000] center = PixCoord(x=x0, y=y0) reg = EllipsePixelRegion(center=center, width=2*a, height=2*b, angle=theta) patch = reg.as_artist(facecolor='none', edgecolor='red', lw=2) return reg
def test_half_ellipse_in_fraction_rotate(): blob = [70., 30., 20., 10., np.pi / 4.] ellip = Ellipse2D(True, blob[1], blob[0], blob[2], blob[3], blob[4]) yy, xx = np.mgrid[:101, :1011] mask = ellip(yy, xx).astype(bool) np.testing.assert_allclose(fraction_in_mask(blob, mask), 1.0, rtol=0.01)
def circular_annulus_footprint(radius_inner, radius_outer, dtype=np.int): """ Create a circular annulus footprint. A pixel is considered to be entirely in or out of the footprint depending on whether its center is in or out of the footprint. The size of the output array is the minimal bounding box for the footprint. Parameters ---------- radius_inner : int The inner radius of the circular annulus. radius_outer : int The outer radius of the circular annulus. dtype : data-type, optional The data type of the output `~numpy.ndarray`. Returns ------- footprint : `~numpy.ndarray` A footprint where array elements are 1 within the footprint and 0 otherwise. """ size = (radius_outer * 2) + 1 y, x = np.mgrid[0:size, 0:size] circle_outer = Ellipse2D(1, radius_outer, radius_outer, radius_outer, radius_outer, theta=0)(x, y) circle_inner = Ellipse2D(1., radius_outer, radius_outer, radius_inner, radius_inner, theta=0)(x, y) return np.asarray(circle_outer - circle_inner, dtype=dtype)
def ellipse_in_array(params, shape): ''' Test if the entire ellipse is within the given shape. ''' yext, xext = Ellipse2D(True, params[1], params[0], params[2], params[3], params[4]).bounding_box bottom_corner = np.array([floor_int(yext[0]), floor_int(xext[0])]) top_corner = np.array([ceil_int(yext[1]), ceil_int(xext[1])]) return in_array(bottom_corner, shape) and in_array(top_corner, shape)
def elliptical_footprint(a, b, theta=0, dtype=np.int): """ Create an elliptical footprint. A pixel is considered to be entirely in or out of the footprint depending on whether its center is in or out of the footprint. The size of the output array is the minimal bounding box for the footprint. Parameters ---------- a : int The semimajor axis. b : int The semiminor axis. theta : float, optional The rotation angle in radians of the semimajor axis. The angle is measured counterclockwise from the positive x axis. dtype : data-type, optional The data type of the output `~numpy.ndarray`. Returns ------- footprint : `~numpy.ndarray` A footprint where array elements are 1 within the footprint and 0 otherwise. Examples -------- >>> import numpy as np >>> from astroimtools import elliptical_footprint >>> elliptical_footprint(3, 1, theta=np.pi/4.) array([[1, 1, 0, 0, 0], [1, 1, 1, 0, 0], [0, 1, 1, 1, 0], [0, 0, 1, 1, 1], [0, 0, 0, 1, 1]]) """ if b > a: raise ValueError('a must be >= b') size = (a * 2) + 1 y, x = np.mgrid[0:size, 0:size] ellipse = Ellipse2D(1, a, a, a, b, theta=theta)(x, y) # crop to minimal bounding box yi, xi = ellipse.nonzero() idx = (slice(min(yi), max(yi) + 1), slice(min(xi), max(xi) + 1)) return np.asarray(ellipse[idx], dtype=dtype)
def shape_from_blob_moments(blob, response, expand_factor=np.sqrt(2)): ''' Use blob properties to define a region, then correct its shape by using the response surface ''' yy, xx = np.mgrid[:response.shape[0], :response.shape[1]] mask = Ellipse2D.evaluate(yy, xx, True, blob[0], blob[1], expand_factor * blob[2], expand_factor * blob[3], blob[4]).astype(np.int) resp = response.copy() # We don't care about the negative values here, so set to 0 resp[resp < 0.0] = 0.0 props = regionprops(mask, intensity_image=resp)[0] # Now use the moments to refine the shape wprops = weighted_props(props) y, x, major, minor, pa = wprops new_mask = Ellipse2D.evaluate(yy, xx, True, y, x, major, minor, pa).astype(bool) # Find the maximum value in the response max_resp = np.max(response[new_mask]) # print(blob) # print((y, x, major, minor, pa, max_resp)) # import matplotlib.pyplot as p # p.imshow(response, origin='lower', cmap='afmhot') # p.contour(mask, colors='r') # p.contour(new_mask, colors='g') # p.draw() # import time; time.sleep(0.1) # raw_input("?") # p.clf() return np.array([y, x, major, minor, pa, max_resp])
def __init__(self, stddev_maj, stddev_min, position_angle, support_scaling=1, **kwargs): self._model = Ellipse2D(1. / (np.pi * stddev_maj * stddev_min), 0, 0, stddev_maj, stddev_min, position_angle) try: from astropy.modeling.utils import ellipse_extent except ImportError: raise NotImplementedError("EllipticalTophat2DKernel requires" " astropy 1.1b1 or greater.") max_extent = \ np.max(ellipse_extent(stddev_maj, stddev_min, position_angle)) self._default_size = \ _round_up_to_odd_integer(support_scaling * 2 * max_extent) super(EllipticalTophat2DKernel, self).__init__(**kwargs) self._truncation = 0
def fp_wat_cal(wat_map, angle, fp_row, fp_col, a=27.3, b=5, window_row=101, window_col=101): ''' obtain the statistic corresponding to the footprint region of the image wat_map: 2-d numpy.array, water map angle: degree(unit), direction of the ellipse fp_row, fp_col: location of the footprint. a,b: the length corresponding to the semimajor axis and semiminor axis respectively. window_row,window_col: the sub-region size of water map with the centered footprint. ''' row_map, col_map = wat_map.shape center_row = round((window_row - 1) / 2) center_col = round((window_col - 1) / 2) if col_map - fp_col < center_col + 1: wat_map = np.pad(wat_map, ((0, 0), (0, center_col + 1)), constant_values=0) if row_map - fp_row < center_row + 1: wat_map = np.pad(wat_map, ((0, center_row + 1), (0, 0)), constant_values=0) wat_map_window = wat_map[fp_row - center_row:fp_row + center_row + 1, fp_col - center_col:fp_col + center_col + 1] theta = Angle(angle, 'deg') y, x = np.mgrid[0:window_row, 0:window_col] e = Ellipse2D(amplitude=1., x_0=center_col, y_0=center_row, a=a, b=b, theta=theta.radian) mask = e(x, y) ## from botton-letf coordinate to up-left(image col-row) coordinate mask = np.flip(mask, axis=0) values = wat_map_window[mask == 1] wat_percent = np.sum(values) / values.shape return wat_percent
def elliptical_footprint(a, b, theta=0, dtype=np.int): """ Create an elliptical footprint. A pixel is considered to be entirely in or out of the footprint depending on whether its center is in or out of the footprint. The size of the output array is the minimal bounding box for the footprint. Parameters ---------- a : int The semimajor axis. b : int The semiminor axis. theta : float, optional The angle in radians of the semimajor axis. The angle is measured counterclockwise from the positive x axis. dtype : data-type, optional The data type of the output `~numpy.ndarray`. Returns ------- footprint : `~numpy.ndarray` A footprint where array elements are 1 within the footprint and 0 otherwise. """ size = (a * 2) + 1 y, x = np.mgrid[0:size, 0:size] ellipse = Ellipse2D(1, a, a, a, b, theta=theta)(x, y) # crop to minimal bounding box yi, xi = ellipse.nonzero() idx = (slice(min(yi), max(yi) + 1), slice(min(xi), max(xi) + 1)) return np.asarray(ellipse[idx], dtype=dtype)
def fp_mask(img, a, b, center_row, center_col, angle): ''' obtain the ellipse mask of the image img: 2-d numpy.array; a,b: the length corresponding to the semimajor axis and semiminor axis respectively. center_row,center_col: the center location ''' if len(img.shape) == 3: img_row, img_col, _ = img.shape else: img_row, img_col = img.shape theta = Angle(angle, 'deg') y, x = np.mgrid[0:img_row, 0:img_col] e = Ellipse2D(amplitude=1., x_0=center_col, y_0=center_row, a=a, b=b, theta=theta.radian) mask = e(x, y) ## from botton-letf coordinate to up-left(image col-row) coordinate mask = np.flip(mask, axis=0) return mask
def elliptical_annulus_footprint(a_inner, a_outer, b_inner, theta=0, dtype=np.int): """ Create an elliptical annulus footprint. A pixel is considered to be entirely in or out of the footprint depending on whether its center is in or out of the footprint. The size of the output array is the minimal bounding box for the footprint. Parameters ---------- a_inner : int The inner semimajor axis. a_outer : int The outer semimajor axis. b_inner : int The inner semiminor axis. The outer semiminor axis is calculated using the same axis ratio as the semimajor axis: .. math:: b_{outer} = b_{inner} \\left( \\frac{a_{outer}}{a_{inner}} \\right) theta : float, optional The rotation angle in radians of the semimajor axis. The angle is measured counterclockwise from the positive x axis. dtype : data-type, optional The data type of the output `~numpy.ndarray`. Returns ------- footprint : `~numpy.ndarray` A footprint where array elements are 1 within the footprint and 0 otherwise. Examples -------- >>> import numpy as np >>> from astroimtools import elliptical_annulus_footprint >>> elliptical_annulus_footprint(2, 4, 1, theta=np.pi/4.) array([[0, 1, 1, 0, 0, 0, 0], [1, 1, 1, 1, 0, 0, 0], [1, 1, 0, 0, 1, 0, 0], [0, 1, 0, 0, 0, 1, 0], [0, 0, 1, 0, 0, 1, 1], [0, 0, 0, 1, 1, 1, 1], [0, 0, 0, 0, 1, 1, 0]]) """ if a_inner > a_outer: raise ValueError('a_outer must be >= a_inner') if b_inner > a_inner: raise ValueError('a_inner must be >= b_inner') size = (a_outer * 2) + 1 y, x = np.mgrid[0:size, 0:size] b_outer = b_inner * (a_outer / a_inner) ellipse_outer = Ellipse2D(1, a_outer, a_outer, a_outer, b_outer, theta=theta)(x, y) ellipse_inner = Ellipse2D(1, a_outer, a_outer, a_inner, b_inner, theta=theta)(x, y) annulus = ellipse_outer - ellipse_inner # crop to minimal bounding box yi, xi = annulus.nonzero() idx = (slice(min(yi), max(yi) + 1), slice(min(xi), max(xi) + 1)) return np.asarray(annulus[idx], dtype=dtype)
import numpy as np from astropy.io import fits from astropy.modeling.models import Ellipse2D (x_0, y_0) = (1000., 2500.) # Center of ellipse, pixel coords (a, b) = (500., 300.) # Major and minor axes of ellipse, pixel coords theta = np.pi/4 # Orientation of ellipse, radians ellipse = Ellipse2D(amplitude=1., x_0=x_0, y_0=y_0, a=a, b=b, theta=theta) img_filename = 'elp1m008-fl05-20170101-0119-e91.fits' # Example image I had access to with fits.open(img_filename) as hdul: data = hdul[0].data y, x = np.mgrid[0:data.shape[0], 0:data.shape[1]] ellipse_mask = ellipse(x, y).astype(bool) cropped_data = np.where(ellipse_mask, data, np.nan) hdul[0].data = cropped_data hdul.writeto('cropped_image.fits')
def find_bubble_edges(array, blob, max_extent=1.0, edge_mask=None, nsig_thresh=1, value_thresh=None, radius=None, return_mask=False, min_pixels=16, filter_size=4, verbose=False, min_radius_frac=0.0, try_local_bkg=True, **kwargs): ''' Expand/contract to match the contours in the data. Parameters ---------- array : 2D numpy.ndarray or spectral_cube.LowerDimensionalObject Data used to define the region boundaries. max_extent : float, optional Multiplied by the major radius to set how far should be searched when searching for the boundary. nsig_thresh : float, optional Number of times sigma above the mean to set the boundary intensity requirement. This is used whenever the local background is higher than the given `value_thresh`. value_thresh : float, optional When given, sets the minimum intensity for defining a bubble edge. The natural choice is a few times the noise level in the cube. radius : float, optional Give an optional radius to use instead of the major radius defined for the bubble. kwargs : passed to profile.profile_line. Returns ------- extent_coords : np.ndarray Array with the positions of edges. ''' if try_local_bkg: mean, std = intensity_props(array, blob) background_thresh = mean + nsig_thresh * std # Define a suitable background based on the intensity within the # elliptical region if value_thresh is None: value_thresh = background_thresh else: # If value_thresh is higher use it. Otherwise use the bkg. if value_thresh < background_thresh: value_thresh = background_thresh # Set the number of theta to be ~ the perimeter. y, x, major, minor, pa = blob[:5] y = int(np.round(y, decimals=0)) x = int(np.round(x, decimals=0)) # If the center is on the edge of the array, subtract one to # index correctly if y == array.shape[0]: y -= 1 if x == array.shape[1]: x -= 1 # Use the ellipse model to define a bounding box for the mask. bbox = Ellipse2D(True, 0.0, 0.0, major * max_extent, minor * max_extent, pa).bounding_box y_range = ceil_int(bbox[0][1] - bbox[0][0] + 1 + filter_size) x_range = ceil_int(bbox[1][1] - bbox[1][0] + 1 + filter_size) shell_thetas = [] yy, xx = np.mgrid[-int(y_range / 2):int(y_range / 2) + 1, -int(x_range / 2):int(x_range / 2) + 1] if edge_mask is not None: arr = edge_mask[max(0, y - int(y_range / 2)):y + int(y_range / 2) + 1, max(0, x - int(x_range / 2)):x + int(x_range / 2) + 1] else: arr = array[max(0, y - int(y_range / 2)):y + int(y_range / 2) + 1, max(0, x - int(x_range / 2)):x + int(x_range / 2) + 1] # Adjust meshes if they exceed the array shape x_min = -min(0, x - int(x_range / 2)) x_max = xx.shape[1] - max(0, x + int(x_range / 2) - array.shape[1] + 1) y_min = -min(0, y - int(y_range / 2)) y_max = yy.shape[0] - max(0, y + int(y_range / 2) - array.shape[0] + 1) offset = (max(0, int(y - (y_range / 2))), max(0, int(x - (x_range / 2)))) yy = yy[y_min:y_max, x_min:x_max] xx = xx[y_min:y_max, x_min:x_max] dist_arr = np.sqrt(yy**2 + xx**2) if edge_mask is not None: smooth_mask = arr else: smooth_mask = \ smooth_edges(arr <= value_thresh, filter_size, min_pixels) region_mask = \ Ellipse2D(True, 0.0, 0.0, major * max_extent, minor * max_extent, pa)(xx, yy).astype(bool) region_mask = nd.binary_dilation(region_mask, eight_conn, iterations=2) # The bubble center must fall within a valid region mid_pt = np.where(dist_arr == 0.0) if len(mid_pt[0]) == 0: middle_fail = True else: local_center = zip(*np.where(dist_arr == 0.0))[0] middle_fail = False # _make_bubble_mask(smooth_mask, local_center) # If the center is not contained within a bubble region, return # empties. bad_case = not smooth_mask.any() or smooth_mask.all() or \ (smooth_mask * region_mask).all() or middle_fail if bad_case: if return_mask: return np.array([]), 0.0, 0.0, value_thresh, smooth_mask return np.array([]), 0.0, 0.0, value_thresh orig_perim = find_contours(region_mask, 0, fully_connected='high')[0] # new_perim = find_contours(smooth_mask, 0, fully_connected='high') coords = [] extent_mask = np.zeros_like(region_mask) # for perim in new_perim: # perim = perim.astype(np.int) # good_pts = \ # np.array([pos for pos, pt in enumerate(perim) # if region_mask[pt[0], pt[1]]]) # if not good_pts.any(): # continue # # Now split into sections # from utils import consec_split # split_pts = consec_split(good_pts) # # Remove the duplicated end point if it was initially connected # if len(split_pts) > 1: # # Join these if the end pts initially matched # if split_pts[0][0] == split_pts[-1][-1]: # split_pts[0] = np.append(split_pts[0], # split_pts[-1][::-1]) # split_pts.pop(-1) # for split in split_pts: # coords.append(perim[split]) # extent_mask[perim[good_pts][:, 0], perim[good_pts][:, 1]] = True # Based on the curvature of the shell, only fit points whose # orientation matches the assumed centre. # incoord, outcoord = shell_orientation(coords, local_center, # verbose=False) # Now only keep the points that are not blocked from the centre pixel for pt in orig_perim: theta = np.arctan2(pt[0] - local_center[0], pt[1] - local_center[1]) num_pts = int( np.round(np.hypot(pt[0] - local_center[0], pt[1] - local_center[1]), decimals=0)) ys = np.round(np.linspace(local_center[0], pt[0], num_pts), decimals=0).astype(np.int) xs = np.round(np.linspace(local_center[1], pt[1], num_pts), decimals=0).astype(np.int) not_on_edge = np.logical_and(ys < smooth_mask.shape[0], xs < smooth_mask.shape[1]) ys = ys[not_on_edge] xs = xs[not_on_edge] dist = np.sqrt((ys - local_center[0])**2 + (xs - local_center[1])**2) prof = smooth_mask[ys, xs] prof = prof[dist >= min_radius_frac * minor] ys = ys[dist >= min_radius_frac * minor] xs = xs[dist >= min_radius_frac * minor] # Look for the first 0 and ignore all others past it zeros = np.where(prof == 0)[0] # If none, move on if not zeros.any(): continue edge = zeros[0] extent_mask[ys[edge], xs[edge]] = True coords.append((ys[edge], xs[edge])) shell_thetas.append(theta) # Calculate the fraction of the region associated with a shell shell_frac = len(shell_thetas) / float(len(orig_perim)) shell_thetas = np.array(shell_thetas) coords = np.array(coords) # Use the theta values to find the standard deviation i.e. how # dispersed the shell locations are. Assumes a circle, but we only # consider moderately elongated ellipses, so the statistics approx. # hold. theta_var = np.sqrt(circvar(shell_thetas * u.rad)).value extent_coords = \ np.vstack([pt + off for pt, off in zip(np.where(extent_mask), offset)]).T if verbose: print("Shell fraction : " + str(shell_frac)) print("Angular Std. : " + str(theta_var)) import matplotlib.pyplot as p true_region_mask = \ Ellipse2D(True, 0.0, 0.0, major, minor, pa)(xx, yy).astype(bool) ax = p.subplot(121) ax.imshow(arr, origin='lower', interpolation='nearest') ax.contour(smooth_mask, colors='b') ax.contour(region_mask, colors='r') ax.contour(true_region_mask, colors='g') if len(coords) > 0: p.plot(coords[:, 1], coords[:, 0], 'bD') p.plot(local_center[1], local_center[0], 'gD') ax2 = p.subplot(122) ax2.imshow(extent_mask, origin='lower', interpolation='nearest') p.draw() raw_input("?") p.clf() if return_mask: return extent_coords, shell_frac, theta_var, value_thresh, \ extent_mask return extent_coords, shell_frac, theta_var, value_thresh
def _ellipse_overlap(blob1, blob2, grid_space=0.5, return_corr=False): ''' Ellipse intersection are difficult. But counting common pixels is not! This routine creates arrays up-sampled from the original pixel scale to better estimate the overlap fraction. ''' ellip1_area = np.pi * blob1[2] * blob1[3] ellip2_area = np.pi * blob2[2] * blob2[3] if ellip1_area >= ellip2_area: large_blob = blob1 small_blob = blob2 large_ellip_area = ellip1_area small_ellip_area = ellip2_area else: large_blob = blob2 small_blob = blob1 large_ellip_area = ellip2_area small_ellip_area = ellip1_area bound1 = Ellipse2D(True, 0.0, 0.0, large_blob[2], large_blob[3], large_blob[4]).bounding_box bound2 = Ellipse2D(True, small_blob[1]-large_blob[1], small_blob[0]-large_blob[0], small_blob[2], small_blob[3], small_blob[4]).bounding_box # If there is no overlap in the bounding boxes, there is no overlap if bound1[0][1] <= bound2[0][0] or bound1[1][1] <= bound2[1][0]: return 0.0 # Now check if the smaller one is completely inside the larger low_in_large = bound1[0][0] <= bound2[0][0] and \ bound1[0][1] >= bound2[0][1] high_in_large = bound1[1][0] <= bound2[1][0] and \ bound1[1][1] >= bound2[1][1] if low_in_large and high_in_large: if return_corr: # Aover / sqrt(A1 * A2) = A1 / sqrt(A1 * A2) = sqrt(A1/A2) return np.sqrt(small_ellip_area / large_ellip_area) return 1.0 # Only evaluate the overlap between the two. ybounds = (max(bound1[0][0], bound2[0][0]), min(bound1[0][1], bound2[0][1])) xbounds = (max(bound1[1][0], bound2[1][0]), min(bound1[1][1], bound2[1][1])) yy, xx = \ np.meshgrid(np.arange(ybounds[0] - grid_space, ybounds[1] + grid_space, grid_space), np.arange(xbounds[0] - grid_space, xbounds[1] + grid_space, grid_space)) ellip1 = Ellipse2D.evaluate(xx, yy, True, 0.0, 0.0, large_blob[2], large_blob[3], large_blob[4]) ellip2 = Ellipse2D.evaluate(xx, yy, True, small_blob[1]-large_blob[1], small_blob[0]-large_blob[0], small_blob[2], small_blob[3], small_blob[4]) overlap_area = np.sum(np.logical_and(ellip1, ellip2)) * grid_space ** 2 if return_corr: return overlap_area / np.sqrt(small_ellip_area * large_ellip_area) return overlap_area / small_ellip_area
def _ellipse_overlap(blob1, blob2, grid_space=0.5, return_corr=False): ''' Ellipse intersection are difficult. But counting common pixels is not! This routine creates arrays up-sampled from the original pixel scale to better estimate the overlap fraction. ''' ellip1_area = np.pi * blob1[2] * blob1[3] ellip2_area = np.pi * blob2[2] * blob2[3] if ellip1_area >= ellip2_area: large_blob = blob1 small_blob = blob2 large_ellip_area = ellip1_area small_ellip_area = ellip2_area else: large_blob = blob2 small_blob = blob1 large_ellip_area = ellip2_area small_ellip_area = ellip1_area bound1 = Ellipse2D(True, 0.0, 0.0, large_blob[2], large_blob[3], large_blob[4]).bounding_box bound2 = Ellipse2D(True, small_blob[1] - large_blob[1], small_blob[0] - large_blob[0], small_blob[2], small_blob[3], small_blob[4]).bounding_box # If there is no overlap in the bounding boxes, there is no overlap if bound1[0][1] <= bound2[0][0] or bound1[1][1] <= bound2[1][0]: return 0.0 # Now check if the smaller one is completely inside the larger low_in_large = bound1[0][0] <= bound2[0][0] and \ bound1[0][1] >= bound2[0][1] high_in_large = bound1[1][0] <= bound2[1][0] and \ bound1[1][1] >= bound2[1][1] if low_in_large and high_in_large: if return_corr: # Aover / sqrt(A1 * A2) = A1 / sqrt(A1 * A2) = sqrt(A1/A2) return np.sqrt(small_ellip_area / large_ellip_area) return 1.0 # Only evaluate the overlap between the two. ybounds = (max(bound1[0][0], bound2[0][0]), min(bound1[0][1], bound2[0][1])) xbounds = (max(bound1[1][0], bound2[1][0]), min(bound1[1][1], bound2[1][1])) yy, xx = \ np.meshgrid(np.arange(ybounds[0] - grid_space, ybounds[1] + grid_space, grid_space), np.arange(xbounds[0] - grid_space, xbounds[1] + grid_space, grid_space)) ellip1 = Ellipse2D.evaluate(xx, yy, True, 0.0, 0.0, large_blob[2], large_blob[3], large_blob[4]) ellip2 = Ellipse2D.evaluate(xx, yy, True, small_blob[1] - large_blob[1], small_blob[0] - large_blob[0], small_blob[2], small_blob[3], small_blob[4]) overlap_area = np.sum(np.logical_and(ellip1, ellip2)) * grid_space**2 if return_corr: return overlap_area / np.sqrt(small_ellip_area * large_ellip_area) return overlap_area / small_ellip_area
def galaxy_mask(header,leda): ra, dec, theta, diam, ba = leda sz = (header["NAXIS1"], header["NAXIS2"]) outmsk = np.zeros((sz[0],sz[1]),dtype=bool) w = WCS(header) ##CCD corners (these are actually in the header for DECam) xc = [0,0,sz[0]-1,sz[0]-1] yc = [0,sz[1]-1,0,sz[1]-1] xcen = (sz[0]-1)/2 ycen = (sz[1]-1)/2 c = w.pixel_to_world(xcen,ycen) sep = c.separation(w.pixel_to_world(xc, yc)) racen = c.ra.degree deccen = c.dec.degree dac = np.max(sep.degree) coordleda = SkyCoord(ra,dec,frame='icrs',unit='deg') dangle = c.separation(coordleda).degree keep = dangle < dac + diam #filter out galaxies with too large angle displace to consider if sum(keep) == 0: return outmsk relevant = w.world_to_pixel(coordleda[keep]) with warnings.catch_warnings(): warnings.simplefilter('ignore', category=(AstropyWarning,RuntimeWarning)) wperm = Wcsprm(header=header.tostring().encode('utf-8')) cd = wperm.cd #* wperm.cdelt[np.mod(np.linspace(0,3,4,dtype=int),2).reshape(2,2)] pscl = np.mean(np.sqrt(np.sum(cd**2,axis=1)))*3600 #convert back to arcsec from deg for interp msz = np.ceil(3600*diam[keep]/pscl).astype(int) #this is the mask size in the pixel scale msz += np.mod(msz,2) == 0 #adjust even size to be odd so that the center means something mh = (msz-1)//2 #I think this -1 is necessary becase we are all odd by construction ix = np.round(relevant[0]).astype(int) iy = np.round(relevant[1]).astype(int) #largest circle approx must fall in ccd insideim = ((ix+mh) >= 0) & ((ix-mh) < sz[0]) & ((iy+mh) >= 0) & (iy-mh < sz[1]) nim = sum(insideim) #filter out galaxy with no overlap if nim == 0: return outmsk rainl = coordleda[keep][insideim].ra.degree decinl = coordleda[keep][insideim].dec.degree thetal = np.deg2rad(theta[keep][insideim]) print("Masked Galaxies: %d" % nim) for i in range(0,nim): un, ue = tan_unit_vectors(rainl[i],decinl[i],racen,deccen) ueofn = un*np.cos(thetal[i])+ue*np.sin(thetal[i]) angle = np.rad2deg(np.arctan2(ueofn[1],ueofn[0])) ixn = ix[insideim][i] iyn = iy[insideim][i] mhn = mh[insideim][i] mszn = msz[insideim][i] e = Ellipse2D(x_0=mhn, y_0=mhn, a=mhn, b=ba[keep][insideim][i]*mhn, theta=np.deg2rad(angle)) y, x = np.mgrid[0:mszn, 0:mszn] smsk = (e(x, y) == 1) outmsk[np.clip(ixn-mhn,a_min=0,a_max=None):np.clip(ixn+mhn+1,a_min=None,a_max=sz[0]), np.clip(iyn-mhn,a_min=0,a_max=None):np.clip(iyn+mhn+1,a_min=None,a_max=sz[1])] |= smsk[ np.clip(mhn-ixn,a_min=0,a_max=None):np.clip(sz[0]-ixn+mhn,a_min=None,a_max=mszn), np.clip(mhn-iyn,a_min=0,a_max=None):np.clip(sz[1]-iyn+mhn,a_min=None,a_max=mszn)] return outmsk.T #revist need for transpose