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

Added CandidateGraph.add_image()

parent 075ba57e
Loading
Loading
Loading
Loading
+222 −6
Original line number Diff line number Diff line
@@ -165,7 +165,7 @@ class CandidateGraph(nx.Graph):
                    adjacency_dict[i.file_name].append(j.file_name)
                    adjacency_dict[j.file_name].append(i.file_name)
            except:
                warnings.warn('Failed to calculated intersection between {} and {}'.format(i, j))
                warnings.warn('Failed to calculate intersection between {} and {}'.format(i, j))

        return cls(adjacency_dict)

@@ -223,16 +223,234 @@ class CandidateGraph(nx.Graph):
        """
        return self.node[node_index]['image_name']

    def add_image(self, *args, **kwargs):
    def add_image(self, image_name, adjacency=None, basepath=None, apply_func=None):
        """
        Adds an image node to the graph.

        Parameters
        ----------
        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

        basepath : str
                   The directory path for the image

        apply_func : function
                     A static function that takes an Edge as its parameter
                     Function will be applied to all Edges generated when adding
                     the image

        """

        def add_node(img_pth):
            """
            Adds a new, disconnected Node to the graph and returns a reference
            to it

            img_pth : The absolute path to the image

            Returns
            -------
            Node : A reference to the added Node

            """
        raise NotImplementedError
        self._order_adjacency()

            # Check that image path exists
            try:
                assert os.path.exists(img_pth)
            except AssertionError:
                raise FileNotFoundError("Could not add {} to CandidateGraph; "
                                        "File does not exist".format(img_pth))

            # Grab the image name
            img_nm = os.path.basename(img_pth)

            # Get the node id & map [id -> Node] in the graph
            id = self.graph['node_counter']
            node = Node(img_nm, img_pth, id)
            self.node[id] = node

            # Map [image name -> id] in the graph and increment node counter
            self.graph['node_name_map'][img_nm] = id
            self.graph['node_counter'] += 1

            # 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))
            return

        # Basepath resolution
        if basepath:
            image_path = os.path.join(basepath, image_name)
        else:
            image_path = image_name
            image_name = os.path.basename(image_path)

        # Create new node within graph
        new_node = add_node(image_path)

        # If adjacency supplied, build list of adjacent nodes
        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:
                try:
                    assert os.path.exists(adjacency)
                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 "
                                "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:
            # 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 \
                    new_node.geodata.footprint.IsValid():
                warnings.warn('Missing or invalid geospatial data for '
                              '{0}; {0} will be added to the CandidateGraph'
                              'as a disconnected Node'.format(image_name))
                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()])

        # Build new edge(s)
        for a_img in adjacent_nodes:
            # Must be 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():
                    # Set the nodes for the new edge
                    a_node_idx = self.graph['node_name_map'][a_img]
                    s = self.node[a_node_idx]
                    d = new_node

                # If adjacent img isnt already in graph, add it
                else:
                    # Set the nodes for the new graph
                    s = new_node
                    d = add_node(os.path.join(basepath, a_img))
            else:
                raise TypeError("Adjacency dict must have image names as keys "
                                "and lists of adjacent image names as values; "
                                "Could not add {} to "
                                "CandidateGraph".format(image_name))

            # Create the new edge
            new_edge = Edge(s, d)

            # If there's a clean func for the new edge, apply it
            if apply_func:
                ERR = "Named parameter 'apply_func' must be a static " \
                      "function or list of static functions; These " \
                      "function(s) are applied to all new edges generated " \
                      "when adding {0}; Could not add {0} to " \
                      "CandidateGraph".format(image_name)
                # 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)
                        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()

            # Add the new edge to the graph
            self.edge[s_id][d_id] = new_edge

    def extract_features(self, band=1, *args, **kwargs):  # pragma: no cover
        """
@@ -425,8 +643,6 @@ class CandidateGraph(nx.Graph):
                          of keys in graph_masks
        """
        return_lis = []
        if callable(function):
            function = function.__name__

        for s, d, edge in self.edges_iter(data=True):
            try: