Commit dc5da658 authored by Evin Dunn's avatar Evin Dunn
Browse files

Simplifications to CandidateGraph.add_image()

parent 00850ae7
Loading
Loading
Loading
Loading
+56 −93
Original line number Diff line number Diff line
@@ -233,9 +233,8 @@ class CandidateGraph(nx.Graph):
        image_name : str
                     The file name of or path to the image to add

        adjacency : dict or path to json
                    The an adjacency dictionary or json file containing
                    the adjacency dictionary for the image
        adjacency : string or Node list
                    The list of adjacent Nodes or image files for this image

        basepath : str
                   The directory path for the image
@@ -282,52 +281,6 @@ class CandidateGraph(nx.Graph):
            # Return the Node reference
            return node

        def detect_adjacency(src_node, target_nodes):
            """
            Returns the target nodes, if any, that intersect with the source
            node

            src_node : Node
                       The Node we are testing for adjacency
            target_nodes : Node List
                           The list of Nodes that may intersect with the source
                           Node

            Returns
            -------
            Node List : The list of Nodes that intersect with the source Node
            """
            # Get datasets from target nodes
            adjacent_nodes = list()
            valid_datasets = list()
            datasets = [node.geodata for node in target_nodes]

            # Make sure target nodes have valid footprints
            for ds in datasets:
                # Skip the source node if it's in the list of target nodes
                if ds.file_name == src_node['image_path']:
                    continue
                # Grab footprints from nodes that have them
                fp = ds.footprint
                if fp and fp.IsValid():
                    valid_datasets.append(ds)
                else:
                    warnings.warn('Missing or invalid geospatial data for '
                                  '{}'.format(os.path.basename(ds.file_name)))

            # Grab the footprints and test for intersection
            for ds in valid_datasets:
                ds_file_name = os.path.basename(ds.file_name)
                try:
                    if new_node.geodata.footprint.Intersects(ds.footprint):
                        adjacent_nodes.append(ds_file_name)
                except:
                    warnings.warn('Failed to calculate intersection between {} '
                                  'and {}'.format(image_name, ds_file_name))

            # Return the adjacent Nodes for the source Node
            return adjacent_nodes

        # Check if image is already in the graph
        if image_name in self.graph['node_name_map']:
            warnings.warn("{} is already in the graph".format(image_name))
@@ -343,42 +296,22 @@ class CandidateGraph(nx.Graph):
        # Create new node within graph
        new_node = add_node(image_path)

        # If adjacency supplied, build list of adjacent nodes
        # If adjacency supplied make sure it's the right type
        if adjacency:
            # Type check
            try:
                assert type(adjacency) is dict or type(adjacency) is str
            except AssertionError:
                raise TypeError("Named parameter 'adjacency' must be a dict or "
                                "path to a json file containing the adjacency "
                                "dict; Could not add {} to "
                                "CandidateGraph".format(image_name))

            # If a json is supplied, load as dict
            if type(adjacency) is not dict:
                adjacency = os.path.join(basepath, adjacency)
                try:
                    assert os.path.exists(adjacency)
                assert type(adjacency) is list
            except AssertionError:
                    raise FileNotFoundError(
                        "Could not load adjacency {}; "
                        "File does not exist; Could not add {} to "
                        "CandidateGraph".format(adjacency, image_name))
                adjacency = io_json.read_json(adjacency)

            # Make sure added image is in adjacency dict
            try:
                assert image_name in adjacency.keys()
            except AssertionError:
                raise KeyError("Adjacency dict contains no key for"
                               "{0}; Could not add {0} to "
                raise TypeError("Named parameter 'adjacency' must be a list of"
                                "adjacent Node objects or list of adjacent "
                                "images; Could not add {} to "
                                "CandidateGraph".format(image_name))

            # Build list of adjacent images from dict
            adjacent_nodes = adjacency[image_name]

        # If adjacency not supplied, figure it out from footprints
        else:
            # Create empty adjacency list
            adjacency = list()

            # Make sure new node has valid footprint; If not, it will be a
            # disconnected node on the graph
            if not new_node.geodata.footprint or not \
@@ -389,12 +322,36 @@ class CandidateGraph(nx.Graph):
                return

            # Detect adjacency between our new node and the CG's nodes
            adjacent_nodes = detect_adjacency(new_node,
                                              [self.node[idx] for idx in self.nodes()])
            target_nodes = [self.node[idx] for idx in self.nodes()]
            valid_datasets = list()
            datasets = [node.geodata for node in target_nodes]

        # Build new edge(s)
        for a_img in adjacent_nodes:
            # Must be string (image name)
            # Make sure target nodes have valid footprints
            for ds in datasets:
                # Skip the source node if it's in the list of target nodes
                if ds.file_name == new_node['image_path']:
                    continue
                # Grab footprints from nodes that have them
                fp = ds.footprint
                if fp and fp.IsValid():
                    valid_datasets.append(ds)
                else:
                    warnings.warn('Missing or invalid geospatial data for '
                                  '{}'.format(os.path.basename(ds.file_name)))

            # Grab the footprints and test for intersection
            for ds in valid_datasets:
                ds_file_name = os.path.basename(ds.file_name)
                try:
                    if new_node.geodata.footprint.Intersects(ds.footprint):
                        adjacency.append(ds_file_name)
                except:
                    warnings.warn('Failed to calculate intersection between {} '
                                  'and {}'.format(image_name, ds_file_name))

        # Build new edge(s) from adjacency
        for a_img in adjacency:
            # If string (image name)
            if isinstance(a_img, str):
                # If adjacent img is already in the graph
                if a_img in self.graph['node_name_map'].keys():
@@ -408,10 +365,23 @@ class CandidateGraph(nx.Graph):
                    # Set the nodes for the new graph
                    s = new_node
                    d = add_node(os.path.join(basepath, a_img))
            # If Node
            elif isinstance(a_img, Node):
                # If it's already in the graph, it'll be the source node,
                # since its idx is lower than our new node
                if a_img['image_name'] in [self.node[idx]['image_name'] for idx in self.nodes()]:
                    s = a_img
                    d = new_node
                # Otherwise, can't create edge
                else:
                    warnings.warn("{0} is not in the graph; No Edge between"
                                  "{0} and {1} can be "
                                  "created".format(a_img['image_name'],
                                                   image_name))
                    continue
            else:
                raise TypeError("Adjacency dict must have image names as keys "
                                "and lists of adjacent image names as values; "
                                "Could not add {} to "
                raise TypeError("Adjacency list contains Node objects or image "
                                "names; Could not add {} to "
                                "CandidateGraph".format(image_name))

            # Create the new edge
@@ -427,27 +397,20 @@ class CandidateGraph(nx.Graph):
                # Type Check
                try:
                    assert callable(apply_func) or type(apply_func) is list
                except AssertionError:
                    raise TypeError(ERR)

                    # If it's a function, apply it
                    if callable(apply_func):
                        apply_func(new_edge)
                    # If it's a list of functions, apply all of them
                    else:
                    for func in apply_func:
                        try:
                            assert callable(func)
                        [func(new_edge) for func in apply_func]
                except AssertionError:
                    raise TypeError(ERR)
                        func(new_edge)

            # Grab node ids
            s_id = s['node_id']
            d_id = d['node_id']

            # Make sure source node is a key in the edge lookup
            # TODO: Is there a better way to do this?
            if s_id not in self.edge.keys():
                self.edge[s_id] = dict()

+27 −29
Original line number Diff line number Diff line
@@ -57,7 +57,7 @@ def test_size(graph):


def test_add_image(graph):
    # apply_funcs
    # apply_func
    def extract_and_match(edge):
        for n in [edge.source, edge.destination]:
            n.extract_features(n.get_array(band=1),
@@ -72,8 +72,10 @@ def test_add_image(graph):
    # Test with all optional args
    cub_img = "AS15-M-0299_crop.cub"
    png_img = "AS15-M-0299_SML.png"
    cub_adj = {cub_img: ["AS15-M-0298_crop.cub", "AS15-M-0297_crop.cub"]}
    png_adj = {png_img: ["AS15-M-0298_crop.cub", "AS15-M-0297_crop.cub"]}

    cub_adj = ["AS15-M-0298_crop.cub", "AS15-M-0297_crop.cub"]

    png_adj = ["AS15-M-0298_crop.cub", "AS15-M-0297_crop.cub"]
    cang.add_image(cub_img, adjacency=cub_adj, basepath=basepath,
                   apply_func=extract_and_match)

@@ -87,6 +89,11 @@ def test_add_image(graph):
    assert cang[0][2].destination['image_name'] == cang.edge[0][2].destination['image_name'] == cub_img
    assert cang[1][2].destination['image_name'] == cang.edge[1][2].destination['image_name'] == cub_img

    # Test when img is already in graph
    cang = network.CandidateGraph.from_adjacency(cube_adjacency,
                                                 basepath=basepath)
    cang.add_image(cub_adj[0], basepath=basepath)

    # Test for file not found
    with pytest.raises(FileNotFoundError):
        cang = network.CandidateGraph.from_adjacency(cube_adjacency,
@@ -105,35 +112,33 @@ def test_add_image(graph):
    cang.add_image(cub_img, basepath=basepath)  # Autodetect

    # Test auto-detect when new node does not intersect
    # TODO: Need a non-intersecting cube file; network.py, lines 324-325
    # Need a non-intersecting cube file

    # Test when img is already in graph
    # Test when adjacency is list of nodes
    cang = network.CandidateGraph.from_adjacency(cube_adjacency,
                                                 basepath=basepath)
    cang.add_image("AS15-M-0297_crop.cub", basepath=basepath)
    cub_adj2 = [cang.node[0], cang.node[1]]
    cang.add_image(cub_img, adjacency=cub_adj2, basepath=basepath)

    # Test when adjacency is of wrong type
    with pytest.raises(TypeError):
    # Test when an adjacency node is not in graph
    cang = network.CandidateGraph.from_adjacency(cube_adjacency,
                                                 basepath=basepath)
        cang.add_image(png_img, adjacency=1, basepath=basepath)  # Invalid
    cub_adj2 = [cang.node[0], cang.node[1]]
    not_there = node.Node("_" + cub_img, os.path.join(basepath, cub_img), 15)
    adj = [not_there]
    edges_bf = cang.edges()
    cang.add_image(cub_img, adjacency=adj, basepath=basepath)
    assert cang.edges() == edges_bf     # Should be no change in edges

    # Test when loading adjacency from json
    cang = network.CandidateGraph.from_adjacency(cube_adjacency,
                                                 basepath=basepath)
    cang.add_image(cub_img, adjacency='cube_adjacency.json', basepath=basepath)
    with pytest.raises(FileNotFoundError):
    # Test when adjacency is of wrong type
    with pytest.raises(TypeError):
        cang = network.CandidateGraph.from_adjacency(cube_adjacency,
                                                     basepath=basepath)
        cang.add_image(cub_img, adjacency='null.json',
                       basepath=basepath)

    # Test when adjacency doesn't contain image as key
    with pytest.raises(KeyError):
        cang.add_image(png_img, adjacency=1, basepath=basepath)  # Invalid
    with pytest.raises(TypeError):
        cang = network.CandidateGraph.from_adjacency(cube_adjacency,
                                                     basepath=basepath)
        cang.add_image(cub_img, adjacency='two_image_adjacency.json',
                       basepath=basepath)
        cang.add_image(png_img, adjacency=[1], basepath=basepath)  # Invalid

    # Test when no adjacency supplied, but image doesn't have footprint;
    # This results in a disconnected node added to the graph
@@ -145,14 +150,7 @@ def test_add_image(graph):

    # Test when adjacency includes a node not already in the graph
    adj = cub_adj
    adj[cub_img].append("AS15-M-0300_crop.cub")
    cang = network.CandidateGraph.from_adjacency(cube_adjacency,
                                                 basepath=basepath)
    cang.add_image(cub_img, adjacency=adj, basepath=basepath)

    # Test when adjacency includes a list of something other than image names
    adj[cub_img].append(1)
    with pytest.raises(TypeError):
    adj.append("AS15-M-0300_crop.cub")
    cang = network.CandidateGraph.from_adjacency(cube_adjacency,
                                                 basepath=basepath)
    cang.add_image(cub_img, adjacency=adj, basepath=basepath)