예제 #1
0
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))
예제 #2
0
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))
예제 #3
0
    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)
예제 #4
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 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)
예제 #6
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
예제 #7
0
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))
예제 #8
0
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))
예제 #9
0
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
예제 #10
0
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)))