예제 #1
0
    def test_merge(self):
        graph1 = {
            "sum1": {
                "arguments": {
                    "data1": {
                        "from_node": "node1"
                    },
                    "data2": {
                        "from_node": "node3"
                    }
                },
                "process_id": "sum",
                "result": True
            }
        }

        graph2 = {
            "sum1": {
                "arguments": {
                    "data": {
                        "from_node": "node4"
                    },
                    "data2": [{
                        "from_node": "node4"
                    }]
                },
                "process_id": "sum",
            },
            "sum2": {
                "arguments": {
                    "data": {
                        "from_node": "sum1"
                    },
                    "data2": [{
                        "from_node": "sum1"
                    }]
                },
                "process_id": "sum",
            }
        }

        builder1 = GraphBuilder(graph1)
        builder2 = GraphBuilder(graph2)

        merged = builder1.merge(builder2).processes

        import json
        print(json.dumps(merged, indent=2))
        self.assertIn("sum1", merged)
        self.assertIn("sum4", merged)
        self.assertIn("sum5", merged)
        self.assertEqual("sum4",
                         merged["sum5"]["arguments"]["data"]["from_node"])
        self.assertEqual("sum4",
                         merged["sum5"]["arguments"]["data2"][0]["from_node"])
예제 #2
0
    def _reduce_bands_binary_xy(self, operator, other: Union[ImageCollection,
                                                             Union[int,
                                                                   float]]):
        """
        Pixelwise comparison of this data cube with another cube or constant.

        :param other: Another data cube, or a constant
        :return:
        """
        if isinstance(other, int) or isinstance(other, float):
            my_builder = self._get_band_graph_builder()
            new_builder = None
            extend_previous_callback_graph = my_builder is not None
            if not extend_previous_callback_graph:
                new_builder = GraphBuilder()
                # TODO merge both process graphs?
                new_builder.add_process(operator,
                                        x={'from_argument': 'data'},
                                        y=other,
                                        result=True)
            else:
                new_builder = my_builder.shallow_copy()
                current_result = new_builder.find_result_node_id()
                new_builder.processes[current_result]['result'] = False
                new_builder.add_process(operator,
                                        x={'from_node': current_result},
                                        y=other,
                                        result=True)

            return self._create_reduced_collection(
                new_builder, extend_previous_callback_graph)
        elif isinstance(other, ImageCollection):
            return self._reduce_bands_binary(operator, other)
        else:
            raise ValueError("Unsupported right-hand operand: " + str(other))
예제 #3
0
    def __invert__(self):
        """

        :return:
        """
        operator = 'not'
        my_builder = self._get_band_graph_builder()
        new_builder = None
        extend_previous_callback_graph = my_builder is not None
        # TODO: why does these `add_process` calls use "expression" instead of "data" like the other cases?
        if not extend_previous_callback_graph:
            new_builder = GraphBuilder()
            # TODO merge both process graphs?
            new_builder.add_process(operator,
                                    expression={'from_argument': 'data'},
                                    result=True)
        else:
            new_builder = my_builder.copy()
            current_result = new_builder.find_result_node_id()
            new_builder.processes[current_result]['result'] = False
            new_builder.add_process(operator,
                                    expression={'from_node': current_result},
                                    result=True)

        return self._create_reduced_collection(new_builder,
                                               extend_previous_callback_graph)
예제 #4
0
    def test_create_from_existing(self):
        graph = {
            "sum_01": {
                "arguments": {
                    "data1": {
                        "from_node": "node1"
                    },
                    "data2": {
                        "from_node": "node3"
                    }
                },
                "process_id": "sum",
                "result": True
            },
            "sum_02": {
                "arguments": {
                    "data": {
                        "from_node": "node4"
                    }
                },
                "process_id": "sum",
            }
        }

        builder = GraphBuilder(graph)

        print(builder.processes)
        self.assertEqual(2, builder.id_counter["sum"])
예제 #5
0
 def _get_band_graph_builder(self):
     current_node = self.graph[self.node_id]
     if current_node["process_id"] == "reduce":
         # TODO: check "dimension" of "reduce" in some way?
         callback_graph = current_node["arguments"]["reducer"]["callback"]
         return GraphBuilder.from_process_graph(callback_graph)
     return None
예제 #6
0
    def load_collection(cls,
                        collection_id: str,
                        session: 'Connection' = None,
                        spatial_extent: Union[Dict[str, float], None] = None,
                        temporal_extent: Union[List[Union[str,
                                                          datetime.datetime,
                                                          datetime.date]],
                                               None] = None,
                        bands: Union[List[str], None] = None,
                        fetch_metadata=True):
        """
        Create a new Image Collection/Raster Data cube.

        :param collection_id: A collection id, should exist in the backend.
        :param session: The session to use to connect with the backend.
        :param spatial_extent: limit data to specified bounding box or polygons
        :param temporal_extent: limit data to specified temporal interval
        :param bands: only add the specified bands
        :return:
        """
        # TODO: rename function to load_collection for better similarity with corresponding process id?
        builder = GraphBuilder()
        process_id = 'load_collection'
        normalized_temporal_extent = list(
            get_temporal_extent(extent=temporal_extent)
        ) if temporal_extent is not None else None
        arguments = {
            'id': collection_id,
            'spatial_extent': spatial_extent,
            'temporal_extent': normalized_temporal_extent,
        }
        metadata = session.collection_metadata(
            collection_id) if fetch_metadata else None
        if bands:
            if isinstance(bands, str):
                bands = [bands]
            if metadata:
                bands = [
                    metadata.band_dimension.band_name(b, allow_common=False)
                    for b in bands
                ]
            arguments['bands'] = bands
        node_id = builder.process(process_id, arguments)
        if bands:
            metadata = metadata.filter_bands(bands)
        return cls(node_id, builder, session, metadata=metadata)
예제 #7
0
 def test_merge_issue50(self):
     """https://github.com/Open-EO/openeo-python-client/issues/50"""
     graph = {
         'op3': {
             'process_id': 'op',
             'arguments': {
                 'data': {
                     'from_node': 'op1',
                     'ref': 'A'
                 }
             }
         },
         'op2': {
             'process_id': 'op',
             'arguments': {
                 'data': {
                     'from_node': 'src',
                     'ref': 'B'
                 }
             }
         },
         'op1': {
             'process_id': 'op',
             'arguments': {
                 'data': {
                     'from_node': 'op2',
                     'ref': 'C'
                 }
             }
         },
         'op4': {
             'process_id': 'op',
             'arguments': {
                 'data': {
                     'from_node': 'op3',
                     'ref': 'D'
                 }
             }
         },
     }
     builder = GraphBuilder(graph)
     assert builder.processes['op1']['arguments']['data'] == {
         'from_node': 'op2',
         'ref': 'C'
     }
     assert builder.processes['op2']['arguments']['data'] == {
         'from_node': 'src',
         'ref': 'B'
     }
     assert builder.processes['op3']['arguments']['data'] == {
         'from_node': 'op1',
         'ref': 'A'
     }
     assert builder.processes['op4']['arguments']['data'] == {
         'from_node': 'op3',
         'ref': 'D'
     }
예제 #8
0
 def _graph_merge(self, other_graph: Dict):
     newbuilder = self.builder.shallow_copy()
     merged = newbuilder.merge(GraphBuilder.from_process_graph(other_graph))
     # TODO: properly update metadata as well?
     newCollection = ImageCollectionClient(self.node_id,
                                           merged,
                                           self.session,
                                           metadata=self.metadata)
     return newCollection
예제 #9
0
def test_empty_mask():
    from shapely import geometry
    polygon = geometry.Polygon([[1.0, 1.0], [2.0, 1.0], [2.0, 1.0], [1.0,
                                                                     1.0]])

    client = ImageCollectionClient(node_id=None,
                                   builder=GraphBuilder(),
                                   session=None)

    with pytest.raises(ValueError, match=r"Mask .+ has an area of 0.0"):
        client.mask(polygon)
예제 #10
0
    def load_disk_collection(cls, session: 'Connection', file_format: str,
                             glob_pattern: str,
                             **options) -> 'ImageCollection':
        """
        Loads image data from disk as an ImageCollection.

        :param session: The session to use to connect with the backend.
        :param file_format: the file format, e.g. 'GTiff'
        :param glob_pattern: a glob pattern that matches the files to load from disk
        :param options: options specific to the file format
        :return: the data as an ImageCollection
        """
        builder = GraphBuilder()

        process_id = 'load_disk_data'
        arguments = {
            'format': file_format,
            'glob_pattern': glob_pattern,
            'options': options
        }

        node_id = builder.process(process_id, arguments)

        return cls(node_id, builder, session, metadata={})
예제 #11
0
    def _reduce_bands_binary_const(self, operator, other: Union[int, float]):
        my_builder = self._get_band_graph_builder()
        new_builder = None
        extend_previous_callback_graph = my_builder is not None
        if not extend_previous_callback_graph:
            new_builder = GraphBuilder()
            # TODO merge both process graphs?
            new_builder.add_process(operator,
                                    data=[{
                                        'from_argument': 'data'
                                    }, other],
                                    result=True)
        else:
            current_result = my_builder.find_result_node_id()
            new_builder = my_builder.shallow_copy()
            new_builder.processes[current_result]['result'] = False
            new_builder.add_process(operator,
                                    data=[{
                                        'from_node': current_result
                                    }, other],
                                    result=True)

        return self._create_reduced_collection(new_builder,
                                               extend_previous_callback_graph)
예제 #12
0
 def test_create_empty(self):
     builder = GraphBuilder()
     builder.process("sum", {})
     self.assertEqual(1, len(builder.processes))
예제 #13
0
    def _reduce_bands_binary(self,
                             operator,
                             other: 'ImageCollectionClient',
                             arg_name='data'):
        # first we create the callback
        my_builder = self._get_band_graph_builder()
        other_builder = other._get_band_graph_builder()
        merged = GraphBuilder.combine(operator=operator,
                                      first=my_builder
                                      or {'from_argument': 'data'},
                                      second=other_builder
                                      or {'from_argument': 'data'},
                                      arg_name=arg_name)
        # callback is ready, now we need to properly set up the reduce process that will invoke it
        if my_builder is None and other_builder is None:
            # there was no previous reduce step, perhaps this is a cube merge?
            # cube merge is happening when node id's differ, otherwise we can use regular reduce
            if (self.node_id != other.node_id):
                # we're combining data from two different datacubes: http://api.openeo.org/v/0.4.0/processreference/#merge_cubes

                # set result node id's first, to keep track
                my_builder = self.builder
                my_builder.processes[self.node_id]['result'] = True
                other_builder = other.builder
                other_builder.processes[other.node_id]['result'] = True

                cubes_merged = GraphBuilder.combine(operator="merge_cubes",
                                                    first=my_builder,
                                                    second=other_builder,
                                                    arg_name="cubes")
                node_id = cubes_merged.find_result_node_id()
                the_node = cubes_merged.processes[node_id]
                the_node["result"] = False
                cubes = the_node["arguments"]["cubes"]
                the_node["arguments"]["cube1"] = cubes[0]
                the_node["arguments"]["cube2"] = cubes[1]
                del the_node["arguments"]["cubes"]

                #there can be only one process for now
                cube_list = list(
                    merged.processes.values())[0]["arguments"][arg_name]
                assert len(cube_list) == 2
                # it is really not clear if this is the agreed way to go
                cube_list[0]["from_argument"] = "x"
                cube_list[1]["from_argument"] = "y"
                the_node["arguments"]["overlap_resolver"] = {
                    'callback': merged.processes
                }
                the_node["arguments"]["binary"] = True
                return ImageCollectionClient(node_id,
                                             cubes_merged,
                                             self.session,
                                             metadata=self.metadata)
            else:
                args = {
                    'data': {
                        'from_node': self.node_id
                    },
                    'reducer': {
                        'callback': merged.processes
                    }
                }
                return self.graph_add_process("reduce", args)
        else:
            left_data_arg = self.builder.processes[
                self.node_id]["arguments"]["data"]
            right_data_arg = other.builder.processes[
                other.node_id]["arguments"]["data"]
            if left_data_arg != right_data_arg:
                raise BandMathException(
                    "'Band math' between bands of different image collections is not supported yet."
                )
            node_id = self.node_id
            reducing_graph = self
            if reducing_graph.graph[node_id]["process_id"] != "reduce":
                node_id = other.node_id
                reducing_graph = other
            new_builder = reducing_graph.builder.shallow_copy()
            new_builder.processes[node_id]['arguments']['reducer'][
                'callback'] = merged.processes
            # now current_node should be a reduce node, let's modify it
            # TODO: properly update metadata of reduced cube? #metadatareducedimension
            return ImageCollectionClient(node_id,
                                         new_builder,
                                         reducing_graph.session,
                                         metadata=self.metadata)