def test_ellipse_model_predict(): model = EllipseModel() model.params = (0, 0, 5, 10, 0) t = np.arange(0, 2 * np.pi, np.pi / 2) xy = np.array(((5, 0), (0, 10), (-5, 0), (0, -10))) assert_almost_equal(xy, model.predict_xy(t))
def test_ellipse_model_predict(): model = EllipseModel() model.params = (0, 0, 5, 10, 0) t = np.arange(0, 2 * np.pi, np.pi / 2) xy = np.array(((5, 0), (0, 10), (-5, 0), (0, -10))) assert_almost_equal(xy, model.predict_xy(t))
def _fit_ellipse(self, p): # UNTESTED """ Fit ellipse to points parameter; then determine the xy coordinates along the ellipse that are at most half a pixel apart. We use EllipseModel.predict_xy() to estimate the xy coordinates along the ellipse. As the parameter to predict_xy() is the set of angles along the ellipse, we must first determine the radian interval that results in a distance (arc length) of maximum 0.5 pixels apart between xy points. (This arc length starts at pi/4 or 0, depending on which of the width or height of the ellipse is longer). """ def get_arc_length_func(_a, _b): return lambda _t:np.sqrt(_a**2*np.sin(_t)**2 + _b**2*np.cos(_t)**2) el = EllipseModel() assert(el.estimate(p)) a, b = el.params[2], el.params[3] # width and height of ellipse self._center = np.array([el.params[0], el.params[1]]) if a < b: stop = np.pi/4 # whether maximum arc length is around np.pi/4 or 0 else: stop = 0. t = np.pi/8 # radians along the ellipse arc_length = 1. arc_length_func = get_arc_length_func(a, b) while arc_length > 0.5: t = t*0.75 # ellipse arc length # https://math.stackexchange.com/questions/433094/how-to-determine-the-arc-length-of-ellipse arc_length = quad(arc_length_func, stop - t, stop)[0] circumfrence = quad(arc_length_func, 0, 2*np.pi)[0] n = circumfrence // t + 1 intervals = np.linspace(0, 2*np.pi, n, endpoint=False) return el.predict_xy(intervals)
def test_ellipse_model_estimate(): # generate original data without noise model0 = EllipseModel() model0._params = (10, 20, 15, 25, 0) t = np.linspace(0, 2 * np.pi, 100) data0 = model0.predict_xy(t) # add gaussian noise to data np.random.seed(1234) data = data0 + np.random.normal(size=data0.shape) # estimate parameters of noisy data model_est = EllipseModel() model_est.estimate(data) # test whether estimated parameters almost equal original parameters assert_almost_equal(model0._params, model_est._params, 0)
def test_ellipse_model_estimate(): # generate original data without noise model0 = EllipseModel() model0.params = (10, 20, 15, 25, 0) t = np.linspace(0, 2 * np.pi, 100) data0 = model0.predict_xy(t) # add gaussian noise to data np.random.seed(1234) data = data0 + np.random.normal(size=data0.shape) # estimate parameters of noisy data model_est = EllipseModel() model_est.estimate(data) # test whether estimated parameters almost equal original parameters assert_almost_equal(model0.params, model_est.params, 0)
def FindEllipse(points, number=100): """ this method applies the ellipse model to the input points points --- (N,2) inputs array number --- number of points in the estimated ellipse ----- out --- return points on the ellipse """ if type(points) is not np.ndarray: raise TypeError("the input must be a nd-array") if points.shape[1] != 2: print "the input array must be (N,2)" return model = EllipseModel() # create an object model.estimate(points) out = model.predict_xy(np.linspace(0, 2 * math.pi, number)) return out
def test_ellipse_model_estimate(): for angle in range(0, 180, 15): rad = np.deg2rad(angle) # generate original data without noise model0 = EllipseModel() model0.params = (10, 20, 15, 25, rad) t = np.linspace(0, 2 * np.pi, 100) data0 = model0.predict_xy(t) # add gaussian noise to data random_state = np.random.RandomState(1234) data = data0 + random_state.normal(size=data0.shape) # estimate parameters of noisy data model_est = EllipseModel() model_est.estimate(data) # test whether estimated parameters almost equal original parameters assert_almost_equal(model0.params[:2], model_est.params[:2], 0) res = model_est.residuals(data0) assert_array_less(res, np.ones(res.shape))
def test_ellipse_model_estimate(): for angle in range(0, 180, 15): rad = np.deg2rad(angle) # generate original data without noise model0 = EllipseModel() model0.params = (10, 20, 15, 25, rad) t = np.linspace(0, 2 * np.pi, 100) data0 = model0.predict_xy(t) # add gaussian noise to data random_state = np.random.RandomState(1234) data = data0 + random_state.normal(size=data0.shape) # estimate parameters of noisy data model_est = EllipseModel() model_est.estimate(data) # test whether estimated parameters almost equal original parameters assert_almost_equal(model0.params[:2], model_est.params[:2], 0) res = model_est.residuals(data0) assert_array_less(res, np.ones(res.shape))
def grid_field_props( A, maxima='centroid', allProps=True, **kwargs): """ Extracts various measures from a spatial autocorrelogram Parameters ---------- A : array_like The spatial autocorrelogram (SAC) maxima : str, optional The method used to detect the peaks in the SAC. Legal values are 'single' and 'centroid'. Default 'centroid' allProps : bool, optional Whether to return a dictionary that contains the attempt to fit an ellipse around the edges of the central size peaks. See below Default True Returns ------- props : dict A dictionary containing measures of the SAC. Keys include: * gridness score * scale * orientation * coordinates of the peaks (nominally 6) closest to SAC centre * a binary mask around the extent of the 6 central fields * values of the rotation procedure used to calculate gridness * ellipse axes and angle (if allProps is True and the it worked) Notes ----- The output from this method can be used as input to the show() method of this class. When it is the plot produced will display a lot more informative. See Also -------- ephysiopy.common.binning.autoCorr2D() """ A_tmp = A.copy() A_tmp[~np.isfinite(A)] = -1 A_tmp[A_tmp <= 0] = -1 A_sz = np.array(np.shape(A)) # [STAGE 1] find peaks & identify 7 closest to centre if 'min_distance' in kwargs: min_distance = kwargs.pop('min_distance') else: min_distance = np.ceil(np.min(A_sz / 2) / 8.).astype(int) peak_idx, field_labels = _get_field_labels( A_tmp, neighbours=7, **kwargs) # a fcn for the labeled_comprehension function that returns # linear indices in A where the values in A for each label are # greater than half the max in that labeled region def fn(val, pos): return pos[val > (np.max(val)/2)] nLbls = np.max(field_labels) indices = ndimage.labeled_comprehension( A_tmp, field_labels, np.arange(0, nLbls), fn, np.ndarray, 0, True) # turn linear indices into coordinates coords = [np.unravel_index(i, np.shape(A)) for i in indices] half_peak_labels = np.zeros_like(A) for peak_id, coord in enumerate(coords): xc, yc = coord half_peak_labels[xc, yc] = peak_id # Get some statistics about the labeled regions # fieldPerim = bwperim(half_peak_labels) lbl_range = np.arange(0, nLbls) # meanRInLabel = ndimage.mean(A, half_peak_labels, lbl_range) # nPixelsInLabel = np.bincount(np.ravel(half_peak_labels.astype(int))) # sumRInLabel = ndimage.sum_labels(A, half_peak_labels, lbl_range) # maxRInLabel = ndimage.maximum(A, half_peak_labels, lbl_range) peak_coords = ndimage.maximum_position( A, half_peak_labels, lbl_range) # Get some distance and morphology measures centre = np.floor(np.array(np.shape(A))/2) centred_peak_coords = peak_coords - centre peak_dist_to_centre = np.hypot( centred_peak_coords.T[0], centred_peak_coords.T[1] ) closest_peak_idx = np.argsort(peak_dist_to_centre) central_peak_label = closest_peak_idx[0] closest_peak_idx = closest_peak_idx[1:np.min((7, len(closest_peak_idx)-1))] # closest_peak_idx should now the indices of the labeled 6 peaks # surrounding the central peak at the image centre scale = np.median(peak_dist_to_centre[closest_peak_idx]) orientation = np.nan orientation = grid_orientation( centred_peak_coords, closest_peak_idx) central_pt = peak_coords[central_peak_label] x = np.linspace(-central_pt[0], central_pt[0], A_sz[0]) y = np.linspace(-central_pt[1], central_pt[1], A_sz[1]) xv, yv = np.meshgrid(x, y, indexing='ij') dist_to_centre = np.hypot(xv, yv) # get the max distance of the half-peak width labeled fields # from the centre of the image max_dist_from_centre = 0 for peak_id, _coords in enumerate(coords): if peak_id in closest_peak_idx: xc, yc = _coords if np.any(xc) and np.any(yc): xc = xc - np.floor(A_sz[0]/2) yc = yc - np.floor(A_sz[1]/2) d = np.max(np.hypot(xc, yc)) if d > max_dist_from_centre: max_dist_from_centre = d # Set the outer bits and the central region of the SAC to nans # getting ready for the correlation procedure dist_to_centre[np.abs(dist_to_centre) > max_dist_from_centre] = 0 dist_to_centre[half_peak_labels == central_peak_label] = 0 dist_to_centre[dist_to_centre != 0] = 1 dist_to_centre = dist_to_centre.astype(bool) sac_middle = A.copy() sac_middle[~dist_to_centre] = np.nan if 'step' in kwargs.keys(): step = kwargs.pop('step') else: step = 30 try: gridscore, rotationCorrVals, rotationArr = gridness( sac_middle, step=step) except Exception: gridscore, rotationCorrVals, rotationArr = np.nan, np.nan, np.nan im_centre = central_pt if allProps: # attempt to fit an ellipse around the outer edges of the nearest # peaks to the centre of the SAC. First find the outer edges for # the closest peaks using a ndimages labeled_comprehension try: def fn2(val, pos): xc, yc = np.unravel_index(pos, A_sz) xc = xc - np.floor(A_sz[0]/2) yc = yc - np.floor(A_sz[1]/2) idx = np.argmax(np.hypot(xc, yc)) return xc[idx], yc[idx] ellipse_coords = ndimage.labeled_comprehension( A, half_peak_labels, closest_peak_idx, fn2, tuple, 0, True) ellipse_fit_coords = np.array([(x, y) for x, y in ellipse_coords]) from skimage.measure import EllipseModel E = EllipseModel() E.estimate(ellipse_fit_coords) im_centre = E.params[0:2] ellipse_axes = E.params[2:4] ellipse_angle = E.params[-1] ellipseXY = E.predict_xy(np.linspace(0, 2*np.pi, 50), E.params) # get the min containing circle given the eliipse minor axis from skimage.measure import CircleModel _params = im_centre _params.append(np.min(ellipse_axes)) circleXY = CircleModel().predict_xy( np.linspace(0, 2*np.pi, 50), params=_params) except (TypeError, ValueError): # non-iterable x and y i.e. ellipse coords fail ellipse_axes = None ellipse_angle = (None, None) ellipseXY = None circleXY = None # collect all the following keywords into a dict for output closest_peak_coords = np.array(peak_coords)[closest_peak_idx] dictKeys = ( 'gridscore', 'scale', 'orientation', 'closest_peak_coords', 'dist_to_centre', 'ellipse_axes', 'ellipse_angle', 'ellipseXY', 'circleXY', 'im_centre', 'rotationArr', 'rotationCorrVals') outDict = dict.fromkeys(dictKeys, np.nan) for thiskey in outDict.keys(): outDict[thiskey] = locals()[thiskey] # neat trick: locals is a dict holding all locally scoped variables return outDict
class DFTanalyzer: '''Class to provide the 2D-DFT (shifted) of an image and a derived ellipse model representing the the frequencies of interest. Based on that model, several parameters for image analysis are provided.''' def __init__(self, img): self.dft = fft.fftshift(fft.fft2(img)) self.contour = None self.ellipse = None self.wavelength = None self.low_pass = None self.filtered_img = None @property def abs_log_dft(self): '''Return log-transformed DFT.''' return np.abs(np.log(self.dft)) def fit_model(self, cut_percent, gauss_sigma): '''Fit an ellipse model to a contour of a certain value in the filtered DFT.''' #Fit ellipse model al_dft = self.abs_log_dft gauss_dft = gaussian(al_dft, gauss_sigma) contour_value = gauss_dft.min() + ( (gauss_dft.max() - gauss_dft.min()) * cut_percent / 100) contours = find_contours(gauss_dft, contour_value) assert len(contours) == 1 self.contour = contours[0] self.ellipse = EllipseModel() self.ellipse.estimate(self.contour[:, ::-1]) center = tuple([x / 2 for x in self.dft.shape]) offset = euclidean(center, (self.ellipse.params[1], self.ellipse.params[0])) half_diagonal = sqrt(sum((x**2 for x in self.dft.shape))) / 2 assert (offset / half_diagonal) <= 0.03 #derive wavelength and texture parameters xy_points = self.ellipse.predict_xy( np.linspace(0, 2 * np.pi, 4 * self.ellipse.params[0])) max_x = round(xy_points[:, 0].max()) min_y = round(xy_points[:, 1].min()) wavelength_x = self.dft.shape[1] / (max_x - center[1]) wavelength_y = self.dft.shape[0] / (center[0] - min_y) self.wavelength = round((wavelength_x + wavelength_y) / 2) @property def texture_radius(self): if self.wavelength is not None: return round(self.wavelength / 2) else: return None @property def min_patch_size(self): if self.wavelength is not None: return round(self.texture_radius**2 * pi) else: return None def apply_lowpass(self, upper, lower, gauss_sigma=1): '''Filter unwanted high frequencies based on the ellipse model.''' cx, cy, a, b, theta = self.ellipse.params self.low_pass = np.zeros_like(self.dft, dtype=np.float64) + lower rr, cc = draw.ellipse(cy, cx, b, a, self.low_pass.shape, (theta * -1)) self.low_pass[rr, cc] = upper self.low_pass = gaussian(self.low_pass, gauss_sigma) filtered_dft = self.dft * self.low_pass self.filtered_img = np.abs(fft.ifft2(fft.ifftshift(filtered_dft)))