Commit c0241853 authored by Jay's avatar Jay
Browse files

Refactors spatial suppression to be a straight function call

parent 68cfc91d
Loading
Loading
Loading
Loading
+4 −11
Original line number Diff line number Diff line
@@ -8,7 +8,6 @@ from scipy.spatial.distance import cdist

import autocnet
from autocnet.utils import utils
from autocnet.matcher import health
from autocnet.matcher import outlier_detector as od
from autocnet.matcher import suppression_funcs as spf
from autocnet.matcher import subpixel as sp
@@ -57,6 +56,8 @@ class Edge(dict, MutableMapping):
        o = other.__dict__
        for k, v in d.items():
            if isinstance(v, pd.DataFrame):
                if not k in o.keys():
                    print(o)
                if not v.equals(o[k]):
                    eq = False
            elif isinstance(v, np.ndarray):
@@ -326,17 +327,9 @@ class Edge(dict, MutableMapping):
        merged = matches.merge(coords, left_on=['source_idx'], right_index=True)
        merged['strength'] = merged.apply(suppression_func, axis=1, args=([self]))

        if not hasattr(self, 'suppression'):
            # Instantiate the suppression object and suppress matches
            self.suppression = od.SpatialSuppression(merged, domain, **kwargs)
            self.suppression.suppress()
        else:
            for k, v in kwargs.items():
                if hasattr(self.suppression, k):
                    setattr(self.suppression, k, v)
            self.suppression.suppress()
        smask, k = od.spatial_suppression(merged, domain, **kwargs)

        mask[mask] = self.suppression.mask
        mask[mask] = smask
        self.masks = ('suppression', mask)

    def plot_source(self, ax=None, clean_keys=[], **kwargs):  # pragma: no cover
+33 −61
Original line number Diff line number Diff line
@@ -5,7 +5,6 @@ import warnings
import numpy as np
import pandas as pd

from autocnet.utils.observable import Observable

def distance_ratio(matches, ratio=0.8, single=False):
    """
@@ -46,7 +45,7 @@ def distance_ratio(matches, ratio=0.8, single=False):
    return mask


class SpatialSuppression(Observable):
def spatial_suppression(df, domain, min_radius=1.5, k=250, error_k=0.1):
    """
    Spatial suppression using disc based method.

@@ -76,57 +75,35 @@ class SpatialSuppression(Observable):
    domain : tuple
             The (x,y) extent of the input domain

    Returns
    -------
    mask : pd.Series
           Boolean suppression mask

    k : int
        The number of unsuppressed observations

    References
    ----------
    [Gauglitz2011]_

    """

    def __init__(self, df, domain, min_radius=1.5, k=250, error_k=0.1):
    columns = df.columns
    for i in ['x', 'y', 'strength']:
        if i not in columns:
            raise ValueError('The dataframe is missing a {} column.'.format(i))
        self.df = df.sort_values(by=['strength'], ascending=False).copy()
        self.max_radius = max(domain)
        self.min_radius = min_radius
        self.domain = domain
        self.mask = pd.Series(False, index=self.df.index)

        self.k = k
        self._error_k = error_k

        self.attrs = ['mask', 'k', 'error_k']
    df = df.sort_values(by=['strength'], ascending=False).copy()
    max_radius = max(domain)
    mask = pd.Series(False, index=df.index)

        self._action_stack = deque(maxlen=10)
        self._current_action_stack = 0
        self._observers = set()

    @property
    def nvalid(self):
        return self.mask.sum()

    @property
    def error_k(self):
        return self._error_k

    @error_k.setter
    def error_k(self, v):
        self._error_k = v

    def suppress(self):
        """
        Suppress subpixel registered points so that k +- k * error_k
        points, with good spatial distribution, remain
        """
    process = True
        if self.k > len(self.df):
            warnings.warn('Only {} valid points, but {} points requested'.format(len(self.df), self.k))
            self.k = len(self.df)
            result = self.df.index
    if k > len(df):
        warnings.warn('Only {} valid points, but {} points requested'.format(len(df), k))
        k = len(df)
        result = df.index
        process = False
        nsteps = max(self.domain) * 0.95
        search_space = np.linspace(self.min_radius, self.max_radius, nsteps)
    nsteps = max(domain) * 0.95
    search_space = np.linspace(min_radius, max_radius, nsteps)
    cell_sizes = search_space / math.sqrt(2)
    min_idx = 0
    max_idx = len(search_space) - 1
@@ -145,21 +122,21 @@ class SpatialSuppression(Observable):
            process = False

        cell_size = cell_sizes[mid_idx]
            n_x_cells = int(self.domain[0] / cell_size)
            n_y_cells = int(self.domain[1] / cell_size)
        n_x_cells = int(domain[0] / cell_size)
        n_y_cells = int(domain[1] / cell_size)
        grid = np.zeros((n_x_cells, n_y_cells), dtype=np.bool)

        # Assign all points to bins
            x_edges = np.linspace(0, self.domain[0], n_x_cells)
            y_edges = np.linspace(0, self.domain[1], n_y_cells)
            xbins = np.digitize(self.df['x'], bins=x_edges)
            ybins = np.digitize(self.df['y'], bins=y_edges)
        x_edges = np.linspace(0, domain[0], n_x_cells)
        y_edges = np.linspace(0, domain[1], n_y_cells)
        xbins = np.digitize(df['x'], bins=x_edges)
        ybins = np.digitize(df['y'], bins=y_edges)

        # Convert bins to cells
        xbins -= 1
        ybins -= 1
        pts = []
            for i, (idx, p) in enumerate(self.df.iterrows()):
        for i, (idx, p) in enumerate(df.iterrows()):
            x_center = xbins[i]
            y_center = ybins[i]
            cell = grid[y_center, x_center]
@@ -167,7 +144,7 @@ class SpatialSuppression(Observable):
            if cell == False:
                result.append(idx)
                pts.append((p[['x', 'y']]))
                    if len(result) > self.k + self.k * self.error_k:
                if len(result) > k + k * error_k:
                    # Too many points, break
                    min_idx = mid_idx
                    break
@@ -193,26 +170,21 @@ class SpatialSuppression(Observable):
                     x_min: x_max] = True

        #  Check break conditions
            if self.k - self.k * self.error_k <= len(result) <= self.k + self.k * self.error_k:
        if k - k * error_k <= len(result) <= k + k * error_k:
            process = False
            elif len(result) < self.k - self.k * self.error_k:
        elif len(result) < k - k * error_k:
            # The radius is too large
            max_idx = mid_idx
            if max_idx == 0:
                warnings.warn('Unable to retrieve {} points. Consider reducing the amount of points you request(k)'
                                  .format(self.k))
                              .format(k))
                process = False
            if min_idx == max_idx:
                process = False
        self.mask = pd.Series(False, self.df.index)
        self.mask.loc[list(result)] = True
        state_package = {'mask': self.mask,
                         'k': self.k,
                         'error_k': self.error_k}

        self._action_stack.append(state_package)
        self._notify_subscribers(self)
        self._current_action_stack = len(self._action_stack) - 1  # 0 based vs. 1 based
    mask = pd.Series(False, df.index)
    mask.loc[list(result)] = True

    return mask, k


def self_neighbors(matches):
+0 −130
Original line number Diff line number Diff line
@@ -87,133 +87,3 @@ def subpixel_offset(template, search, **kwargs):

    x_offset, y_offset, strength = functions[method](template, search, **kwargs)
    return x_offset, y_offset, strength

'''
Stub for an observable subpixel class

class PatternMatch(Observable):

    """
    Attributes
    ----------
    df : dataframe
         A dataframe of point to be subpixel registered

    img1 : object or ndarray
           A file handle object or ndarray to use to subpixel register

    img2 : object or ndarray
           A file handle object or ndarray to use to subpixel register

    destination : object
                  Destination node

    threshold_mask : series
                     A pandas series masking values < threshold

    shift_mask : series
                 A pandas series masking values with shifts larger
                 than the allowed x, y shifts

    subpixel_mask : series
                    A composite mask, threshold_mask & shift_mask
    """

    def __init__(self, img1, img2, df, min_x_shift=-1.0, max_x_shift=1.0,
                 min_y_shift=-1.0, max_y_shift=1.0, threshold=0.8):
        self.img1 = img1
        self.img2 = img2
        self.df = df

        self._min_x_shift = min_x_shift
        self._min_y_shift = min_y_shift
        self._max_x_shift = max_x_shift
        self._max_y_shift = max_y_shift
        self._threshold = threshold

        self.threshold_mask = pd.Series(True, index=self.df.index)
        self.shift_mask = pd.Series(True, index=self.df.index)
        self.subpixel_mask = self.threshold_mask & self.subpixel_mask

        self._action_stack = deque(maxlen=20)
        self._current_action_stack = 0
        self._observers = set()
        self.attrs = ['threshold', 'min_x_shift', 'max_x_shift',
                      'min_y_shift', 'm_y_shift', 'threshold_mask',
                      'shift_mask', 'subpixel_mask']

    def clip_roi(self, img, center):

    @property
    def threshold(self):
        return self._threshold

    @threshold.setter
    def threshold(self, v):
        if 0 <= v <= 1:
            self._threshold = v

            # Update the mask here
            self.threshold_mask = self.d

            current_state = self._action_stack[self._current_action_stack]
            current_state['threshold'] = self.threshold

            self._update_stack(current_state)

    @property
    def min_x_shift(self):
        return self._min_x_shift

    @min_x_shift.setter
    def min_x_shift(self, v):
        self._min_x_shift = v

        # Update mask here

        current_state = self._action_stack[self._current_action_stack]
        current_state['min_x_shift'] = self.min_x_shift
        self._update_stack()

    @property
    def min_y_shift(self):
        return self._min_y_shift

    @min_x_shift.setter
    def min_y_shift(self, v):
        self._min_y_shift = v

        # Update mask here

        current_state = self._action_stack[self._current_action_stack]
        current_state['min_y_shift'] = self.min_y_shift
        self._update_stack()

    @property
    def max_x_shift(self):
        return self._max_x_shift

    @max_x_shift.setter
    def max_x_shift(self, v):
        self._max_x_shift = v

        # Update mask here

        current_state = self._action_stack[self._current_action_stack]
        current_state['max_x_shift'] = self.max_x_shift
        self._update_stack()

    @property
    def max_y_shift(self):
        return self._max_y_shift

    @max_y_shift.setter
    def max_y_shift(self, v):
        self._max_y_shift = v

        # Update mask here

        current_state = self._action_stack[self._current_action_stack]
        current_state['max_y_shift'] = self.max_y_shift
        self._update_stack()
'''
+15 −34
Original line number Diff line number Diff line
@@ -7,7 +7,6 @@ import numpy as np
import pandas as pd

from .. import outlier_detector
from autocnet.matcher.outlier_detector import SpatialSuppression

sys.path.append(os.path.abspath('..'))

@@ -53,37 +52,23 @@ class TestSpatialSuppression(unittest.TestCase):
        y = seed.randint(0, 100, 100).astype(np.float32)
        strength = seed.rand(100)
        data = np.vstack((x, y, strength)).T
        df = pd.DataFrame(data, columns=['x', 'y', 'strength'])
        self.suppression_obj = outlier_detector.SpatialSuppression(df, (100, 100), k=25)

    def test_properties(self):
        self.assertEqual(self.suppression_obj.k, 25)
        self.suppression_obj.k = 26
        self.assertTrue(self.suppression_obj.k, 26)

        self.assertEqual(self.suppression_obj.error_k, 0.1)
        self.suppression_obj.error_k = 0.05
        self.assertEqual(self.suppression_obj.error_k, 0.05)

        self.assertEqual(self.suppression_obj.nvalid, 0)
        self.assertIsInstance(self.suppression_obj.df, pd.DataFrame)
        self.df = pd.DataFrame(data, columns=['x', 'y', 'strength'])
        self.domain = (100,100)

    def test_suppress_non_optimal(self):
        with warnings.catch_warnings(record=True) as w:
            self.suppression_obj.suppress()
            mask, k = outlier_detector.spatial_suppression(self.df, self.domain, k=25)
            self.assertEqual(len(w), 1)
            self.assertEqual(w[0].category, UserWarning)

        self.assertEqual(self.suppression_obj.mask.sum(), 28)
        self.assertEqual(mask.sum(), 28)

    def test_suppress(self):
        self.suppression_obj.k = 30
        self.suppression_obj.suppress()
        self.assertIn(self.suppression_obj.mask.sum(), list(range(27, 35)))
        mask, k = outlier_detector.spatial_suppression(self.df, self.domain, k=30)
        self.assertIn(mask.sum(), list(range(27, 35)))

        with warnings.catch_warnings(record=True) as w:
            self.suppression_obj.k = 101
            self.suppression_obj.suppress()
            mask, k = outlier_detector.spatial_suppression(self.df, self.domain, k=101)
            self.assertEqual(len(w), 1)
            self.assertTrue(issubclass(w[0].category, UserWarning))

@@ -95,24 +80,20 @@ class testSuppressionRanges(unittest.TestCase):

    def test_min_max(self):
        df = pd.DataFrame(self.r.uniform(0,2,(500, 3)), columns=['x', 'y', 'strength'])
        sup = SpatialSuppression(df, (1.5,1.5), k = 1)
        sup.suppress()
        self.assertEqual(len(df[sup.mask]), 1)
        mask, k = outlier_detector.spatial_suppression(df, (1.5,1.5), k = 1)
        self.assertEqual(len(df[mask]), 1)

    def test_point_overload(self):
        df = pd.DataFrame(self.r.uniform(0,15,(500, 3)), columns=['x', 'y', 'strength'])
        sup = SpatialSuppression(df, (15,15), k = 200)
        sup.suppress()
        self.assertEqual(len(df[sup.mask]), 69)
        mask, k = outlier_detector.spatial_suppression(df, (15,15), k = 200)
        self.assertEqual(len(df[mask]), 69)

    def test_small_distribution(self):
        df = pd.DataFrame(self.r.uniform(0,25,(500, 3)), columns=['x', 'y', 'strength'])
        sup = SpatialSuppression(df, (25,25), k = 25)
        sup.suppress()
        self.assertEqual(len(df[sup.mask]), 28)
        mask, k = outlier_detector.spatial_suppression(df, (25,25), k = 25)
        self.assertEqual(len(df[mask]), 28)

    def test_normal_distribution(self):
        df = pd.DataFrame(self.r.uniform(0,100,(500, 3)), columns=['x', 'y', 'strength'])
        sup = SpatialSuppression(df, (100,100), k = 15)
        sup.suppress()
        self.assertEqual(len(df[sup.mask]), 17)
        mask, k = outlier_detector.spatial_suppression(df, (100,100), k = 15)
        self.assertEqual(len(df[mask]), 17)