Loading autocnet/examples/Apollo15/adjacency.json +25 −1 Original line number Diff line number Diff line {"AS15-M-0297_SML.png": ["AS15-M-0298_SML.png", "AS15-M-0300_SML.png", "AS15-M-0299_SML.png", "AS15-M-0296_SML.png"], "AS15-M-0300_SML.png": ["AS15-M-0298_SML.png", "AS15-M-0297_SML.png", "AS15-M-0299_SML.png"], "AS15-M-0299_SML.png": ["AS15-M-0298_SML.png", "AS15-M-0297_SML.png", "AS15-M-0300_SML.png", "AS15-M-0296_SML.png"], "AS15-M-0295_SML.png": [], "AS15-M-0296_SML.png": ["AS15-M-0298_SML.png", "AS15-M-0297_SML.png", "AS15-M-0299_SML.png"], "AS15-M-0298_SML.png": ["AS15-M-0297_SML.png", "AS15-M-0300_SML.png", "AS15-M-0299_SML.png", "AS15-M-0296_SML.png"]} No newline at end of file {"autocnet/examples/Apollo15/AS15-M-0297_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0298_SML.png", "autocnet/examples/Apollo15/AS15-M-0300_SML.png", "autocnet/examples/Apollo15/AS15-M-0299_SML.png", "autocnet/examples/Apollo15/AS15-M-0296_SML.png"], "autocnet/examples/Apollo15/AS15-M-0300_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0298_SML.png", "autocnet/examples/Apollo15/AS15-M-0297_SML.png", "autocnet/examples/Apollo15/AS15-M-0299_SML.png"], "autocnet/examples/Apollo15/AS15-M-0299_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0298_SML.png", "autocnet/examples/Apollo15/AS15-M-0297_SML.png", "autocnet/examples/Apollo15/AS15-M-0300_SML.png", "autocnet/examples/Apollo15/AS15-M-0296_SML.png"], "autocnet/examples/Apollo15/AS15-M-0295_SML.png" : [], "autocnet/examples/Apollo15/AS15-M-0296_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0298_SML.png", "autocnet/examples/Apollo15/AS15-M-0297_SML.png", "autocnet/examples/Apollo15/AS15-M-0299_SML.png"], "autocnet/examples/Apollo15/AS15-M-0298_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0297_SML.png", "autocnet/examples/Apollo15/AS15-M-0300_SML.png", "autocnet/examples/Apollo15/AS15-M-0299_SML.png", "autocnet/examples/Apollo15/AS15-M-0296_SML.png"]} autocnet/examples/Apollo15/two_image_adjacency.json +4 −2 Original line number Diff line number Diff line {"AS15-M-0297_SML.png": ["AS15-M-0298_SML.png"], "AS15-M-0298_SML.png": ["AS15-M-0297_SML.png"]} {"autocnet/examples/Apollo15/AS15-M-0297_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0298_SML.png"], "autocnet/examples/Apollo15/AS15-M-0298_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0297_SML.png"]} autocnet/graph/network.py +124 −33 Original line number Diff line number Diff line Loading @@ -5,9 +5,12 @@ import pandas as pd import cv2 import numpy as np from scipy.misc import bytescale # store image array from autocnet.control.control import C from autocnet.fileio import io_json from autocnet.fileio.io_gdal import GeoDataset from autocnet.matcher import feature_extractor as fe # extract features from image class CandidateGraph(nx.Graph): """ Loading @@ -17,6 +20,12 @@ class CandidateGraph(nx.Graph): ---------- Attributes node_counter : int The number of nodes in the graph. node_name_map : dict The mapping of image labels (i.e. file base names) to their corresponding node indices. ---------- """ Loading @@ -24,47 +33,138 @@ class CandidateGraph(nx.Graph): super(CandidateGraph, self).__init__(*args, **kwargs) self.node_counter = 0 node_labels = {} self.node_name_map = {} # the node_name is the relative path for the image for node_name, node_attributes in self.nodes_iter(data=True): if os.path.isabs(node_name): node_attributes['image_name'] = os.path.basename(node_name) node_attributes['image_path'] = node_name else: node_attributes['image_name'] = node_name node_attributes['image_path'] = None node_attributes['image_name'] = os.path.basename(os.path.abspath(node_name)) node_attributes['image_path'] = os.path.abspath(node_name) node_labels[node_attributes['image_name']] = self.node_counter # fill the dictionary used for relabelling nodes with relative path keys node_labels[node_name] = self.node_counter # fill the dictionary used for mapping base name to node index self.node_name_map[node_attributes['image_name']] = self.node_counter self.node_counter += 1 nx.relabel_nodes(self, node_labels, copy=False) @classmethod def from_adjacency_file(cls, inputfile): """ Instantiate the class using an adjacency file. This file must contain relative or absolute paths to image files. Parameters ---------- inputfile : str The input file containing the graph representation Returns ------- : object A Network graph object Examples -------- >>> from autocnet.examples import get_path >>> inputfile = get_path('adjacency.json') >>> candidate_graph = network.CandidateGraph.from_adjacency_file(inputfile) """ adjacency_dict = io_json.read_json(inputfile) return cls(adjacency_dict) def get_name(self, nodeIndex): """ Get the image name for the given node. Parameters ---------- nodeIndex : int The index of the node. Returns ------- : str The name of the image attached to the given node. """ return self.node[nodeIndex]['image_name'] def get_keypoints(self, nodeIndex): """ Get the list of keypoints for the given node. Parameters ---------- nodeIndex : int The index of the node. Returns ------- : list The list of keypoints for the given node. """ return self.node[nodeIndex]['keypoints'] def add_image(self, *args, **kwargs): """ Adds an image node to the graph. Parameters ========== ---------- """ raise NotImplementedError self.add_node(self.node_counter, *args, **kwargs) #self.node_labels[self.node[self.node_counter]['image_name']] = self.node_counter self.node_counter += 1 def adjacency_to_json(self, outputfile): def get_geodataset(self, nodeIndex): """ Write the edge structure to a JSON adjacency list Constructs a GeoDataset object from the given node image and assigns the dataset and its NumPy array to the 'handle' and 'image' node attributes. Parameters ========== ---------- nodeIndex : int The index of the node. outputfile : str PATH where the JSON will be written """ adjacency_dict = {} for n in self.nodes(): adjacency_dict[n] = self.neighbors(n) io_json.write_json(adjacency_dict, outputfile) self.node[nodeIndex]['handle'] = GeoDataset(self.node[nodeIndex]['image_path']) self.node[nodeIndex]['image'] = bytescale(self.node[nodeIndex]['handle'].read_array()) def add_matches(self, matches): def extract_features(self, nfeatures) : """ Extracts features from each image in the graph and uses the result to assign the node attributes for 'handle', 'image', 'keypoints', and 'descriptors'. Parameters ---------- nfeatures : int The number of features to be extracted. """ # Loop through the nodes (i.e. images) on the graph and fill in their attributes. # These attributes are... # geo dataset (handle and image) # features (keypoints and descriptors) for node, attributes in self.nodes_iter(data=True): self.get_geodataset(node) extraction_params = {'nfeatures' : nfeatures} attributes['keypoints'], attributes['descriptors'] = fe.extract_features(attributes['image'], extraction_params) def add_matches(self, matches): """ Adds match data to a node and attributes the data to the appropriate edges, e.g. if A-B have a match, edge A-B is attributes with the pandas dataframe. Loading Loading @@ -198,28 +298,19 @@ class CandidateGraph(nx.Graph): return merged_cnet @classmethod def from_adjacency(cls, inputfile): def to_json_file(self, outputfile): """ Instantiate the class using an adjacency list Write the edge structure to a JSON adjacency list Parameters ---------- inputfile : str The input file containing the graph representation Returns ------- : object A Network graph object ========== Examples -------- >>> from autocnet.examples import get_path >>> inputfile = get_path('adjacency.json') >>> candidate_graph = network.CandidateGraph.from_adjacency(inputfile) outputfile : str PATH where the JSON will be written """ adjacency_dict = io_json.read_json(inputfile) return cls(adjacency_dict) adjacency_dict = {} for n in self.nodes(): adjacency_dict[n] = self.neighbors(n) io_json.write_json(adjacency_dict, outputfile) autocnet/graph/tests/test_network.py +30 −10 Original line number Diff line number Diff line import os import sys sys.path.insert(0, os.path.abspath('..')) import cv2 import numpy as np import unittest from autocnet.examples import get_path import sys sys.path.insert(0, os.path.abspath('..')) from .. import network class TestCandidateGraph(unittest.TestCase): def setUp(self): self.graph = network.CandidateGraph.from_adjacency(get_path('adjacency.json')) self.graph = network.CandidateGraph.from_adjacency_file(get_path('adjacency.json')) def test_get_name(self): node_number = self.graph.node_name_map['AS15-M-0297_SML.png'] name = self.graph.get_name(node_number) self.assertEquals(name, 'AS15-M-0297_SML.png') def test_add_image(self): with self.assertRaises(NotImplementedError): self.graph.add_image() self.assertEqual(self.graph.node_counter, 7) def test_adjacency_to_json(self): self.graph.adjacency_to_json('test_adjacency_to_json.json') self.assertTrue(os.path.exists('test_adjacency_to_json.json')) def test_to_json_file(self): self.graph.to_json_file('test_graph_to_json.json') self.assertTrue(os.path.exists('test_graph_to_json.json')) def test_extract_features(self): # also tests get_geodataset() and get_keypoints self.graph.extract_features(10) node_number = self.graph.node_name_map['AS15-M-0297_SML.png'] node = self.graph.node[node_number] self.assertEquals(len(node['image']), 1012) self.assertEquals(len(node['keypoints']), 10) self.assertEquals(len(node['descriptors']), 10) self.assertIsInstance(node['keypoints'][0], type(cv2.KeyPoint())) self.assertIsInstance(node['descriptors'][0], np.ndarray) self.assertEquals(self.graph.get_keypoints(node_number), node['keypoints']) def tearDown(self): try: os.remove('test_adjacency_to_json.json') os.remove('test_graph_to_json.json') except: pass functional_tests/test_two_image.py +4 −16 Original line number Diff line number Diff line import os import unittest import pandas as pd from scipy.misc import bytescale from autocnet.examples import get_path from autocnet.fileio.io_gdal import GeoDataset from autocnet.fileio.io_controlnetwork import to_isis from autocnet.fileio.io_gdal import GeoDataset from autocnet.graph.network import CandidateGraph from autocnet.matcher import feature_extractor as fe from autocnet.matcher.matcher import FlannMatcher Loading Loading @@ -43,20 +44,13 @@ class TestTwoImageMatching(unittest.TestCase): # Step: Create an adjacency graph adjacency = get_path('two_image_adjacency.json') basepath = os.path.dirname(adjacency) cg = CandidateGraph.from_adjacency(adjacency) cg = CandidateGraph.from_adjacency_file(adjacency) self.assertEqual(2, cg.number_of_nodes()) self.assertEqual(1, cg.number_of_edges()) # Step: Extract image data and attribute nodes cg.extract_features(25) for node, attributes in cg.nodes_iter(data=True): dataset = GeoDataset(os.path.join(basepath, attributes['image_name'])) attributes['handle'] = dataset img = bytescale(dataset.read_array()) attributes['image'] = img # Step: Then find features and descriptors attributes['keypoints'], attributes['descriptors'] = fe.extract_features(attributes['image'], {'nfeatures':25}) self.assertIn(len(attributes['keypoints']), [24, 25, 26]) # Step: Then apply a FLANN matcher Loading @@ -71,12 +65,6 @@ class TestTwoImageMatching(unittest.TestCase): detectme = OutlierDetector() cg.add_matches(matches) # Step: Compute Homography transformation_matrix, mask = cg.compute_homography(0, 1) self.assertEquals(len(transformation_matrix), 3) #TODO: write better test #self.assertEquals(len(mask), 19) # Step: And create a C object cnet = cg.to_cnet() Loading Loading
autocnet/examples/Apollo15/adjacency.json +25 −1 Original line number Diff line number Diff line {"AS15-M-0297_SML.png": ["AS15-M-0298_SML.png", "AS15-M-0300_SML.png", "AS15-M-0299_SML.png", "AS15-M-0296_SML.png"], "AS15-M-0300_SML.png": ["AS15-M-0298_SML.png", "AS15-M-0297_SML.png", "AS15-M-0299_SML.png"], "AS15-M-0299_SML.png": ["AS15-M-0298_SML.png", "AS15-M-0297_SML.png", "AS15-M-0300_SML.png", "AS15-M-0296_SML.png"], "AS15-M-0295_SML.png": [], "AS15-M-0296_SML.png": ["AS15-M-0298_SML.png", "AS15-M-0297_SML.png", "AS15-M-0299_SML.png"], "AS15-M-0298_SML.png": ["AS15-M-0297_SML.png", "AS15-M-0300_SML.png", "AS15-M-0299_SML.png", "AS15-M-0296_SML.png"]} No newline at end of file {"autocnet/examples/Apollo15/AS15-M-0297_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0298_SML.png", "autocnet/examples/Apollo15/AS15-M-0300_SML.png", "autocnet/examples/Apollo15/AS15-M-0299_SML.png", "autocnet/examples/Apollo15/AS15-M-0296_SML.png"], "autocnet/examples/Apollo15/AS15-M-0300_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0298_SML.png", "autocnet/examples/Apollo15/AS15-M-0297_SML.png", "autocnet/examples/Apollo15/AS15-M-0299_SML.png"], "autocnet/examples/Apollo15/AS15-M-0299_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0298_SML.png", "autocnet/examples/Apollo15/AS15-M-0297_SML.png", "autocnet/examples/Apollo15/AS15-M-0300_SML.png", "autocnet/examples/Apollo15/AS15-M-0296_SML.png"], "autocnet/examples/Apollo15/AS15-M-0295_SML.png" : [], "autocnet/examples/Apollo15/AS15-M-0296_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0298_SML.png", "autocnet/examples/Apollo15/AS15-M-0297_SML.png", "autocnet/examples/Apollo15/AS15-M-0299_SML.png"], "autocnet/examples/Apollo15/AS15-M-0298_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0297_SML.png", "autocnet/examples/Apollo15/AS15-M-0300_SML.png", "autocnet/examples/Apollo15/AS15-M-0299_SML.png", "autocnet/examples/Apollo15/AS15-M-0296_SML.png"]}
autocnet/examples/Apollo15/two_image_adjacency.json +4 −2 Original line number Diff line number Diff line {"AS15-M-0297_SML.png": ["AS15-M-0298_SML.png"], "AS15-M-0298_SML.png": ["AS15-M-0297_SML.png"]} {"autocnet/examples/Apollo15/AS15-M-0297_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0298_SML.png"], "autocnet/examples/Apollo15/AS15-M-0298_SML.png" : ["autocnet/examples/Apollo15/AS15-M-0297_SML.png"]}
autocnet/graph/network.py +124 −33 Original line number Diff line number Diff line Loading @@ -5,9 +5,12 @@ import pandas as pd import cv2 import numpy as np from scipy.misc import bytescale # store image array from autocnet.control.control import C from autocnet.fileio import io_json from autocnet.fileio.io_gdal import GeoDataset from autocnet.matcher import feature_extractor as fe # extract features from image class CandidateGraph(nx.Graph): """ Loading @@ -17,6 +20,12 @@ class CandidateGraph(nx.Graph): ---------- Attributes node_counter : int The number of nodes in the graph. node_name_map : dict The mapping of image labels (i.e. file base names) to their corresponding node indices. ---------- """ Loading @@ -24,47 +33,138 @@ class CandidateGraph(nx.Graph): super(CandidateGraph, self).__init__(*args, **kwargs) self.node_counter = 0 node_labels = {} self.node_name_map = {} # the node_name is the relative path for the image for node_name, node_attributes in self.nodes_iter(data=True): if os.path.isabs(node_name): node_attributes['image_name'] = os.path.basename(node_name) node_attributes['image_path'] = node_name else: node_attributes['image_name'] = node_name node_attributes['image_path'] = None node_attributes['image_name'] = os.path.basename(os.path.abspath(node_name)) node_attributes['image_path'] = os.path.abspath(node_name) node_labels[node_attributes['image_name']] = self.node_counter # fill the dictionary used for relabelling nodes with relative path keys node_labels[node_name] = self.node_counter # fill the dictionary used for mapping base name to node index self.node_name_map[node_attributes['image_name']] = self.node_counter self.node_counter += 1 nx.relabel_nodes(self, node_labels, copy=False) @classmethod def from_adjacency_file(cls, inputfile): """ Instantiate the class using an adjacency file. This file must contain relative or absolute paths to image files. Parameters ---------- inputfile : str The input file containing the graph representation Returns ------- : object A Network graph object Examples -------- >>> from autocnet.examples import get_path >>> inputfile = get_path('adjacency.json') >>> candidate_graph = network.CandidateGraph.from_adjacency_file(inputfile) """ adjacency_dict = io_json.read_json(inputfile) return cls(adjacency_dict) def get_name(self, nodeIndex): """ Get the image name for the given node. Parameters ---------- nodeIndex : int The index of the node. Returns ------- : str The name of the image attached to the given node. """ return self.node[nodeIndex]['image_name'] def get_keypoints(self, nodeIndex): """ Get the list of keypoints for the given node. Parameters ---------- nodeIndex : int The index of the node. Returns ------- : list The list of keypoints for the given node. """ return self.node[nodeIndex]['keypoints'] def add_image(self, *args, **kwargs): """ Adds an image node to the graph. Parameters ========== ---------- """ raise NotImplementedError self.add_node(self.node_counter, *args, **kwargs) #self.node_labels[self.node[self.node_counter]['image_name']] = self.node_counter self.node_counter += 1 def adjacency_to_json(self, outputfile): def get_geodataset(self, nodeIndex): """ Write the edge structure to a JSON adjacency list Constructs a GeoDataset object from the given node image and assigns the dataset and its NumPy array to the 'handle' and 'image' node attributes. Parameters ========== ---------- nodeIndex : int The index of the node. outputfile : str PATH where the JSON will be written """ adjacency_dict = {} for n in self.nodes(): adjacency_dict[n] = self.neighbors(n) io_json.write_json(adjacency_dict, outputfile) self.node[nodeIndex]['handle'] = GeoDataset(self.node[nodeIndex]['image_path']) self.node[nodeIndex]['image'] = bytescale(self.node[nodeIndex]['handle'].read_array()) def add_matches(self, matches): def extract_features(self, nfeatures) : """ Extracts features from each image in the graph and uses the result to assign the node attributes for 'handle', 'image', 'keypoints', and 'descriptors'. Parameters ---------- nfeatures : int The number of features to be extracted. """ # Loop through the nodes (i.e. images) on the graph and fill in their attributes. # These attributes are... # geo dataset (handle and image) # features (keypoints and descriptors) for node, attributes in self.nodes_iter(data=True): self.get_geodataset(node) extraction_params = {'nfeatures' : nfeatures} attributes['keypoints'], attributes['descriptors'] = fe.extract_features(attributes['image'], extraction_params) def add_matches(self, matches): """ Adds match data to a node and attributes the data to the appropriate edges, e.g. if A-B have a match, edge A-B is attributes with the pandas dataframe. Loading Loading @@ -198,28 +298,19 @@ class CandidateGraph(nx.Graph): return merged_cnet @classmethod def from_adjacency(cls, inputfile): def to_json_file(self, outputfile): """ Instantiate the class using an adjacency list Write the edge structure to a JSON adjacency list Parameters ---------- inputfile : str The input file containing the graph representation Returns ------- : object A Network graph object ========== Examples -------- >>> from autocnet.examples import get_path >>> inputfile = get_path('adjacency.json') >>> candidate_graph = network.CandidateGraph.from_adjacency(inputfile) outputfile : str PATH where the JSON will be written """ adjacency_dict = io_json.read_json(inputfile) return cls(adjacency_dict) adjacency_dict = {} for n in self.nodes(): adjacency_dict[n] = self.neighbors(n) io_json.write_json(adjacency_dict, outputfile)
autocnet/graph/tests/test_network.py +30 −10 Original line number Diff line number Diff line import os import sys sys.path.insert(0, os.path.abspath('..')) import cv2 import numpy as np import unittest from autocnet.examples import get_path import sys sys.path.insert(0, os.path.abspath('..')) from .. import network class TestCandidateGraph(unittest.TestCase): def setUp(self): self.graph = network.CandidateGraph.from_adjacency(get_path('adjacency.json')) self.graph = network.CandidateGraph.from_adjacency_file(get_path('adjacency.json')) def test_get_name(self): node_number = self.graph.node_name_map['AS15-M-0297_SML.png'] name = self.graph.get_name(node_number) self.assertEquals(name, 'AS15-M-0297_SML.png') def test_add_image(self): with self.assertRaises(NotImplementedError): self.graph.add_image() self.assertEqual(self.graph.node_counter, 7) def test_adjacency_to_json(self): self.graph.adjacency_to_json('test_adjacency_to_json.json') self.assertTrue(os.path.exists('test_adjacency_to_json.json')) def test_to_json_file(self): self.graph.to_json_file('test_graph_to_json.json') self.assertTrue(os.path.exists('test_graph_to_json.json')) def test_extract_features(self): # also tests get_geodataset() and get_keypoints self.graph.extract_features(10) node_number = self.graph.node_name_map['AS15-M-0297_SML.png'] node = self.graph.node[node_number] self.assertEquals(len(node['image']), 1012) self.assertEquals(len(node['keypoints']), 10) self.assertEquals(len(node['descriptors']), 10) self.assertIsInstance(node['keypoints'][0], type(cv2.KeyPoint())) self.assertIsInstance(node['descriptors'][0], np.ndarray) self.assertEquals(self.graph.get_keypoints(node_number), node['keypoints']) def tearDown(self): try: os.remove('test_adjacency_to_json.json') os.remove('test_graph_to_json.json') except: pass
functional_tests/test_two_image.py +4 −16 Original line number Diff line number Diff line import os import unittest import pandas as pd from scipy.misc import bytescale from autocnet.examples import get_path from autocnet.fileio.io_gdal import GeoDataset from autocnet.fileio.io_controlnetwork import to_isis from autocnet.fileio.io_gdal import GeoDataset from autocnet.graph.network import CandidateGraph from autocnet.matcher import feature_extractor as fe from autocnet.matcher.matcher import FlannMatcher Loading Loading @@ -43,20 +44,13 @@ class TestTwoImageMatching(unittest.TestCase): # Step: Create an adjacency graph adjacency = get_path('two_image_adjacency.json') basepath = os.path.dirname(adjacency) cg = CandidateGraph.from_adjacency(adjacency) cg = CandidateGraph.from_adjacency_file(adjacency) self.assertEqual(2, cg.number_of_nodes()) self.assertEqual(1, cg.number_of_edges()) # Step: Extract image data and attribute nodes cg.extract_features(25) for node, attributes in cg.nodes_iter(data=True): dataset = GeoDataset(os.path.join(basepath, attributes['image_name'])) attributes['handle'] = dataset img = bytescale(dataset.read_array()) attributes['image'] = img # Step: Then find features and descriptors attributes['keypoints'], attributes['descriptors'] = fe.extract_features(attributes['image'], {'nfeatures':25}) self.assertIn(len(attributes['keypoints']), [24, 25, 26]) # Step: Then apply a FLANN matcher Loading @@ -71,12 +65,6 @@ class TestTwoImageMatching(unittest.TestCase): detectme = OutlierDetector() cg.add_matches(matches) # Step: Compute Homography transformation_matrix, mask = cg.compute_homography(0, 1) self.assertEquals(len(transformation_matrix), 3) #TODO: write better test #self.assertEquals(len(mask), 19) # Step: And create a C object cnet = cg.to_cnet() Loading