Commit f08256a6 authored by kree's avatar kree
Browse files

Merge pull request #107 from jlaura/master

Primarily fixes errors I induced in the ratio test.
parents 3cc29457 fe92b000
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -60,3 +60,4 @@ notifications:
          - krodriguez@usgs.gov
      on_success: always
      on_failure: always
      
+150 −64
Original line number Diff line number Diff line
import collections
from time import gmtime, strftime

import pandas as pd

POINT_TYPE = 2
MEASURE_TYPE = 2
class Point(object):
    """
    An n-image correspondence container class to store
    information common to all identical correspondences across
    an image set.

    Attributes
    ----------
    point_id : int
               A unique identifier for the given point

class CSeries(pd.Series):
    """
    A custom pandas series that can accept additional methods
    subpixel : bool
               Whether or not the point has been subpixel registered

    point_type : an ISIS identifier for the type of the point
                 as defined in the ISIS protobuf spec.

    correspondences : list
                      of image correspondences
    """
    __slots__ = '_subpixel', 'point_id', 'point_type', 'correspondences'

    def __init__(self, pid, point_type=2):
        self.point_id = pid
        self._subpixel = False
        self.point_type = point_type
        self.correspondences = []

    def __repr__(self):
        return str(self.point_id)

    def __eq__(self, other):
        return self.point_id == other

    def __hash__(self):
        return hash(self.point_id)

    @property
    def _constructor(self):
        return CSeries  # pragma: no cover
    def subpixel(self):
        return self._subpixel

    @subpixel.setter
    def subpixel(self, v):
        if isinstance(v, bool):
            self._subpixel = v
        if self._subpixel is True:
            self.point_type = 3


class C(pd.DataFrame):
class Correspondence(object):
    """
    Control network designed in the ISIS format.
    A single correspondence (image measure).

    Attributes
    ----------

    n : int
        Number of control points
    id : int
         The index of the point in a matches dataframe (stored as an edge attribute)

    m : int
        Number of control measures
    x : float
        The x coordinate of the measure in image space

    creationdate : str
                   The date that this control network was created.

    modifieddate : str
                   The date that this control network was last modified.

    Examples
    --------
    This example illustrates the manual creation of a pandas dataframe with
    a multi-index (created from a list of tuples).

    >>> ids = ['pt1','pt1', 'pt1', 'pt2', 'pt2']
    >>> ptype = [2,2,2,2,2]
    >>> serials = ['a', 'b', 'c', 'b', 'c']
    >>> mtype = [2,2,2,2,2]
    >>> multi_index = pd.MultiIndex.from_tuples(list(zip(ids, ptype, serials, mtype)),\
                                    names=['Id', 'Type', 'Serial Number', 'Measure Type'])
    >>> columns = ['Random Number']
    >>> data_length = 5
    >>> data = np.random.randn(data_length)
    >>> C = control.C(data, index=multi_index, columns=columns)
    y : float
        The y coordinate of the measure in image space

    measure_type : int
                   The ISIS measure type as per the protobuf spec

    serial : str
             A unique serial number for the image the measure corresponds to
             In the case of an ISIS cube, this is a valid ISIS serial number,
             else, None.
    """
    __slots__ = 'id', 'x', 'y', 'measure_type', 'serial'

    def __init__(self, *args, **kwargs):
        super(C, self).__init__(*args, **kwargs)
        self._creationdate = strftime("%Y-%m-%d %H:%M:%S", gmtime())
    def __init__(self, id, x, y, measure_type=2, serial=None):
        self.id = id
        self.x = x
        self.y = y
        self.measure_type = measure_type
        self.serial = serial

    @property
    def _constructor(self):
        return C
    def __repr__(self):
        return str(self.id)

    _constructor_sliced = CSeries
    def __eq__(self, other):
        return self.id == other

    @property
    def n(self):
        if not getattr(self, '_n', None):
            self._n = len(self['pid'].unique())
        return self._n
    def __hash__(self):
        return hash(self.id)

    @property
    def m(self):
        if not getattr(self, '_m', None):
            self._m = len(self)
        return self._m

class CorrespondenceNetwork(object):
    """
    A container of points and associated correspondences.  The primary
    data structures are point_to_correspondence and correspondence_to_point.
    These two attributes store the mapping between point and correspondences.

    Attributes
    ----------
    point_to_correspondence : dict
                              with key equal to an instance of the Point class and
                              values equal to a list of Correspondences.

    correspondence_to_point : dict
                              with key equal to a correspondence identifier (not the class) and
                              value equal to a unique point_id (not an instance of the Point class).
                              This attribute serves as a low memory reverse lookup table

    point_id : int
               The current 'new' point id if an additional point were to be added

    n_points : int
               The number of points in the CorrespondenceNetwork

    n_measures : int
                 The number of Correspondences in the CorrespondenceNetwork

    creationdate : str
                   The date the instance of this class was first instantiated

    modifieddata : str
                   The date this class last had correspondences and/or points added
    """
    def __init__(self):
        self.point_to_correspondence = collections.defaultdict(list)
        self.correspondence_to_point = {}
        self.point_id = 0
        self.creationdate = strftime("%Y-%m-%d %H:%M:%S", gmtime())
        self.modifieddate = strftime("%Y-%m-%d %H:%M:%S", gmtime())

    @property
    def creationdate(self):
        return self._creationdate
    def n_points(self):
        return len(self.point_to_correspondence.keys())

    @property
    def modifieddate(self):
        if not getattr(self, '_modifieddate', None):
            self._modifieddate = 'Not modified'
        return self._modifieddate

    '''
    @modifieddate.setter
    def update_modifieddate(self):
        self._modifieddate = strftime("%Y-%m-%d %H:%M:%S", gmtime())
    '''
    def n_measures(self):
        return len(self.correspondence_to_point.keys())

    def add_correspondences(self, edge, matches):
        # Convert the matches dataframe to a dict
        df = matches.to_dict()
        source_image = next(iter(df['source_image'].values()))
        destination_image = next(iter(df['destination_image'].values()))

        # TODO: Handle subpixel registration here
        s_kps = edge.source.get_keypoint_coordinates().values
        d_kps = edge.destination.get_keypoint_coordinates().values

        # Load the correspondence to point data structure
        for k, source_idx in df['source_idx'].items():
            p = Point(self.point_id)
            destination_idx = df['destination_idx'][k]

            sidx = Correspondence(source_idx, *s_kps[source_idx], serial=edge.source.isis_serial)
            didx = Correspondence(destination_idx, *d_kps[destination_idx], serial=edge.destination.isis_serial)

            p.correspondences = [sidx, didx]

            self.correspondence_to_point[(source_image, source_idx)] = self.point_id
            self.correspondence_to_point[(destination_image, destination_idx)] = self.point_id

            self.point_to_correspondence[p].append((source_image, sidx))
            self.point_to_correspondence[p].append((destination_image, didx))

            self.point_id += 1
        self._update_modified_date()

    def _update_modified_date(self):
        self.modifieddate = strftime("%Y-%m-%d %H:%M:%S", gmtime())

    def to_dataframe(self):
        pass
+52 −10
Original line number Diff line number Diff line
@@ -3,7 +3,13 @@ import sys
from time import gmtime, strftime
import unittest

from unittest.mock import Mock, MagicMock

from autocnet.graph.edge import Edge
from autocnet.graph.node import Node

import numpy as np
import pandas as pd

sys.path.insert(0, os.path.abspath('..'))

@@ -12,24 +18,60 @@ from autocnet.control import control

class TestC(unittest.TestCase):

    def setUp(self):
        x = list(range(10))
        y = list(range(10))
        pid = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2]
        nid = [1, 2, 1, 2, 1, 2, 1, 2, 1, 2]
    @classmethod
    def setUpClass(cls):
        npts = 10
        coords = pd.DataFrame(np.arange(npts * 2).reshape(-1, 2))
        source = np.zeros(npts)
        destination = np.ones(npts)
        pid = np.arange(npts)

        matches = pd.DataFrame(np.vstack((source, pid, destination, pid)).T, columns=['source_image',
                                                                                      'source_idx',
                                                                                      'destination_image',
                                                                                      'destination_idx'])

        edge = Mock(spec=Edge)
        edge.source = Mock(spec=Node)
        edge.destination = Mock(spec=Node)
        edge.source.isis_serial = None
        edge.destination.isis_serial = None
        edge.source.get_keypoint_coordinates = MagicMock(return_value=coords)
        edge.destination.get_keypoint_coordinates = MagicMock(return_value=coords)

        data = np.array([x, y, pid, nid]).T
        cls.C = control.CorrespondenceNetwork()
        cls.C.add_correspondences(edge, matches)

        self.C = control.C(data, columns=['x', 'y', 'pid', 'nid'])

    def test_n_point(self):
        self.assertEqual(self.C.n, 4)
        self.assertEqual(self.C.n_points, 10)

    def test_n_measures(self):
        self.assertEqual(self.C.m, 10)
        self.assertEqual(self.C.n_measures, 20)

    def test_modified_date(self):
        self.assertEqual(self.C.modifieddate, 'Not modified')
        self.assertIsInstance(self.C.modifieddate, str)

    def test_creation_date(self):
        self.assertEqual(self.C.creationdate, strftime("%Y-%m-%d %H:%M:%S", gmtime()))

    def test_point_subpixel(self):
        for k, v in self.C.point_to_correspondence.items():
            self.assertFalse(k.subpixel)
            k.subpixel = True
            self.assertTrue(k.subpixel)
            break

    def test_equalities(self):
        points = []
        correspondences = []
        for k, v in self.C.point_to_correspondence.items():
            points.append(k)
            correspondences.extend(v)
        self.assertEqual(points[0], points[0])
        self.assertNotEqual(points[-1], points[1])
        self.assertEqual(correspondences[1][0], correspondences[1][0])

    def test_to_dataframe(self):
        self.C.to_dataframe()
+17 −19
Original line number Diff line number Diff line
import pvl

from autocnet.fileio import ControlNetFileV0002_pb2 as cnf
from autocnet.control.control import POINT_TYPE, MEASURE_TYPE

#TODO: Protobuf3 should be a conditional import, if availble use it, otherwise bail

@@ -27,6 +26,7 @@ def write_filelist(lst, path="fromlist.lis"):
        handle.write('\n')
    return


def to_isis(path, C, mode='w', version=VERSION,
            headerstartbyte=HEADERSTARTBYTE,
            networkid='None', targetname='None',
@@ -83,7 +83,6 @@ def to_isis(path, C, mode='w', version=VERSION,
                                                                           point_sizes)
            # Write the buffer header
            store.write(buffer_header, HEADERSTARTBYTE)

            # Then write the points, so we know where to start writing, + 1 to avoid overwrite
            point_start_offset = HEADERSTARTBYTE + buffer_header_size
            for i, point in enumerate(point_messages):
@@ -150,28 +149,27 @@ class IsisStore(object):
        """
        point_sizes = []
        point_messages = []
        for pid, point in cnet.groupby('pid'):
            # Instantiate the proto spec
            point_spec = cnf.ControlPointFileEntryV0002()

            # Get the subset of the dataframe
            try:
                point_spec.id = pid
            except:
        for pid, measure_list in cnet.point_to_correspondence.items():
            point_spec = cnf.ControlPointFileEntryV0002()
            point_spec.id = str(pid)
            point_spec.type = POINT_TYPE
            point_spec.type = pid.point_type

            # The reference index should always be the image with the lowest index
            point_spec.referenceIndex = 0

            # A single extend call is cheaper than many add calls to pack points
            measure_iterable = []
            for name, row in point.iterrows():

            for node_id, m in measure_list:
                measure_spec = point_spec.Measure()
                measure_spec.serialnumber = row.nid
                measure_spec.type = row.point_type
                measure_spec.sample = row.x
                measure_spec.line = row.y
                try:
                    measure_spec.serialnumber = m.serial
                except:
                    measure_spec.serialnumber = str(m.serial)
                measure_spec.type = m.measure_type
                measure_spec.sample = float(m.x)
                measure_spec.line = float(m.y)

                measure_iterable.append(measure_spec)
            point_spec.measures.extend(measure_iterable)
@@ -291,8 +289,8 @@ class IsisStore(object):
                        ('Created', cnet.creationdate),
                        ('LastModified', cnet.modifieddate),
                        ('Description', description),
                        ('NumberOfPoints', cnet.n),
                        ('NumberOfMeasures', cnet.m),
                        ('NumberOfPoints', cnet.n_points),
                        ('NumberOfMeasures', cnet.n_measures),
                        ('Version', version)
                        ])
                  }),
+50 −45
Original line number Diff line number Diff line
import os
from time import gmtime, strftime
import unittest
import sys
sys.path.insert(0, os.path.abspath('..'))

import unittest
from unittest.mock import Mock, MagicMock
import numpy as np
import pandas as pd
import pvl
@@ -12,44 +10,50 @@ from .. import io_controlnetwork
from .. import ControlNetFileV0002_pb2 as cnf

from autocnet.utils.utils import find_in_dict
from autocnet.control.control import C

class TestWriteIsisControlNetwork(unittest.TestCase):

    def setUp(self):
        """
        Not 100% sure how to mock in the DF without creating lots of methods...
        """

        serial_times = {295: '1971-07-31T01:24:11.754',
                   296: '1971-07-31T01:24:36.970',
                   297: '1971-07-31T01:25:02.243',
                   298: '1971-07-31T01:25:27.457',
                   299: '1971-07-31T01:25:52.669',
                   300: '1971-07-31T01:26:17.923'}
        self.serials = ['APOLLO15/METRIC/{}'.format(i) for i in serial_times.values()]


        x = list(range(5))
        y = list(range(5))
        pid = [0,0,1,1,1]
        idx = pid
        serials = [self.serials[0], self.serials[1], self.serials[2],
                   self.serials[2], self.serials[3]]
from autocnet.control.control import CorrespondenceNetwork
from autocnet.graph.edge import Edge
from autocnet.graph.node import Node

sys.path.insert(0, os.path.abspath('..'))

        columns = ['x', 'y', 'idx', 'pid', 'nid', 'point_type']
        self.data_length = 5

        data = [x,y, idx, pid, serials, [2] * self.data_length]
class TestWriteIsisControlNetwork(unittest.TestCase):

        self.creation_time = strftime("%Y-%m-%d %H:%M:%S", gmtime())
        cnet = C(data, index=columns).T
    @classmethod
    def setUpClass(cls):

        serial_times = {295: '1971-07-31T01:24:11.754',
                        296: '1971-07-31T01:24:36.970'}
        cls.serials = ['APOLLO15/METRIC/{}'.format(i) for i in serial_times.values()]

        # Create an edge and a set of matches
        cls.npts = 5
        coords = pd.DataFrame(np.arange(cls.npts * 2).reshape(-1, 2))
        source = np.zeros(cls.npts)
        destination = np.ones(cls.npts)
        pid = np.arange(cls.npts)

        matches = pd.DataFrame(np.vstack((source, pid, destination, pid)).T, columns=['source_image',
                                                                                      'source_idx',
                                                                                      'destination_image',
                                                                                      'destination_idx'])

        edge = Mock(spec=Edge)
        edge.source = Mock(spec=Node)
        edge.destination = Mock(spec=Node)
        edge.source.isis_serial = cls.serials[0]
        edge.destination.isis_serial = cls.serials[1]
        edge.source.get_keypoint_coordinates = MagicMock(return_value=coords)
        edge.destination.get_keypoint_coordinates = MagicMock(return_value=coords)

        cnet = CorrespondenceNetwork()
        cnet.add_correspondences(edge, matches)
        cls.creation_date = cnet.creationdate
        cls.modified_date = cnet.modifieddate
        io_controlnetwork.to_isis('test.net', cnet, mode='wb', targetname='Moon')

        self.header_message_size = 85
        self.point_start_byte = 65621
        cls.header_message_size = 98
        cls.point_start_byte = 65634

    def test_create_buffer_header(self):
        with open('test.net', 'rb') as f:
@@ -63,20 +67,20 @@ class TestWriteIsisControlNetwork(unittest.TestCase):
            self.assertEqual('Moon', header_protocol.targetName)
            self.assertEqual(io_controlnetwork.DEFAULTUSERNAME,
                             header_protocol.userName)
            self.assertEqual(self.creation_time,
            self.assertEqual(self.creation_date,
                             header_protocol.created)
            self.assertEqual('None', header_protocol.description)
            self.assertEqual('Not modified', header_protocol.lastModified)
            self.assertEqual(self.modified_date, header_protocol.lastModified)

            #Repeating
            self.assertEqual([135, 199], header_protocol.pointMessageSizes)
            self.assertEqual([135] * self.npts, header_protocol.pointMessageSizes)

    def test_create_point(self):
        with open('test.net', 'rb') as f:

            with open('test.net', 'rb') as f:
                f.seek(self.point_start_byte)
                for i, length in enumerate([135, 199]):
                for i, length in enumerate([135] * self.npts):
                    point_protocol = cnf.ControlPointFileEntryV0002()
                    raw_point = f.read(length)
                    point_protocol.ParseFromString(raw_point)
@@ -90,16 +94,17 @@ class TestWriteIsisControlNetwork(unittest.TestCase):
        pvl_header = pvl.load('test.net')

        npoints = find_in_dict(pvl_header, 'NumberOfPoints')
        self.assertEqual(2, npoints)
        self.assertEqual(5, npoints)

        mpoints = find_in_dict(pvl_header, 'NumberOfMeasures')
        self.assertEqual(5, mpoints)
        self.assertEqual(10, mpoints)

        points_bytes = find_in_dict(pvl_header, 'PointsBytes')
        self.assertEqual(334, points_bytes)
        self.assertEqual(675, points_bytes)

        points_start_byte = find_in_dict(pvl_header, 'PointsStartByte')
        self.assertEqual(65621, points_start_byte)
        self.assertEqual(65634, points_start_byte)

    def tearDown(self):
    @classmethod
    def tearDownClass(cls):
        os.remove('test.net')
Loading