Skip to content

Commit 9806537

Browse files
committed
WIP
1 parent 48916fa commit 9806537

File tree

8 files changed

+277
-61
lines changed

8 files changed

+277
-61
lines changed

docs/_static/custom.css

+4
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,7 @@ pre {
104104
font-size: 75%;
105105
padding: 0 1ex;
106106
}
107+
108+
nav.contents.local {
109+
border: none;
110+
}

docs/api-processbuilder.rst

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
.. FYI this file is intended to be "included"
2+
from the ProcessBuilder class doc block
3+
4+
5+
The :py:class:`ProcessBuilder <openeo.processes.ProcessBuilder>` class
6+
is a helper class that implements
7+
(much like the :ref:`openEO process functions <openeo_processes_functions>`)
8+
each openEO process as a method.
9+
On top of that it also adds syntactic sugar to support Python operators as well
10+
(e.g. ``+`` is translated to the ``add`` process).
11+
12+
.. attention::
13+
As normal user, you should never create a
14+
:py:class:`ProcessBuilder <openeo.processes.ProcessBuilder>` instance
15+
directly.
16+
17+
You should only interact with this class inside a callback
18+
function/lambda while building a child callback process graph
19+
as discussed at :ref:`child_callback_callable`.
20+
21+
22+
For example, let's start from this simple usage snippet
23+
where we want to reduce the temporal dimension
24+
by taking the temporal mean of each timeseries:
25+
26+
.. code-block:: python
27+
28+
def my_reducer(data):
29+
return data.mean()
30+
31+
cube.reduce_dimension(reducer=my_reducer, dimension="t")
32+
33+
Note that this ``my_reducer`` function has a ``data`` argument,
34+
which conceptually corresponds to an array of pixel values
35+
(along the temporal dimension).
36+
However, it's important to understand that the ``my_reducer`` function
37+
is actually *not evaluated when you execute your process graph*
38+
on an openEO back-end, e.g. as a batch jobs.
39+
Instead, ``my_reducer`` is evaluated
40+
*while building your process graph client-side*
41+
(at the time you execute that ``cube.reduce_dimension()`` statement to be precise).
42+
This means that that ``data`` argument is actually not a concrete array of EO data,
43+
but some kind of *virtual placeholder*,
44+
a :py:class:`ProcessBuilder <openeo.processes.ProcessBuilder>` instance,
45+
that keeps track of the operations you intend to do on the EO data.
46+
47+
To make that more concrete, let's add type hints
48+
(which will make it easier to discover what we can do with the argument,
49+
depending on which editor or IDE you are using):
50+
51+
.. code-block:: python
52+
53+
from openeo.processes import ProcessBuilder
54+
55+
def my_reducer(data: ProcessBuilder) -> ProcessBuilder:
56+
return data.mean()
57+
58+
cube.reduce_dimension(reducer=my_reducer, dimension="t")
59+
60+
61+
Because :py:class:`ProcessBuilder <openeo.processes.ProcessBuilder>` methods
62+
return new :py:class:`ProcessBuilder <openeo.processes.ProcessBuilder>` instances,
63+
and because it support syntactic sugar to use Python operators on it,
64+
and because :ref:`openeo.process functions <openeo_processes_functions>`
65+
also accept and return :py:class:`ProcessBuilder <openeo.processes.ProcessBuilder>` instances,
66+
we can mix methods, functions and operators in the callback function like this:
67+
68+
.. code-block:: python
69+
70+
from openeo.processes import ProcessBuilder, cos
71+
72+
def my_reducer(data: ProcessBuilder) -> ProcessBuilder:
73+
return cos(data.mean()) + 1.23
74+
75+
cube.reduce_dimension(reducer=my_reducer, dimension="t")
76+
77+
or compactly, using an anonymous lambda expression:
78+
79+
.. code-block:: python
80+
81+
from openeo.processes import cos
82+
83+
cube.reduce_dimension(
84+
reducer=lambda data: cos(data.mean())) + 1.23,
85+
dimension="t"
86+
)

docs/api-processes.rst

+57-8
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,69 @@
1-
==========================
1+
=========================
22
API: ``openeo.processes``
3-
==========================
3+
=========================
44

5-
The ``openeo.processes`` module contains helper to build call
5+
The ``openeo.processes`` module contains building blocks and helpers
6+
to construct so called "child callbacks" for openEO processes like
7+
:py:meth:`openeo.rest.datacube.DataCube.apply` and
8+
:py:meth:`openeo.rest.datacube.DataCube.reduce_dimension`,
9+
as discussed at :ref:`child_callback_callable`.
610

11+
.. note::
12+
The contents of the ``openeo.processes`` module is automatically compiled
13+
from the official openEO process specifications.
14+
Developers that want to fix bugs in, or add implementations to this
15+
module should not touch the file directly, but instead address it in the
16+
upstream `openeo-processes <https://github.com/Open-EO/openeo-processes>`_ repository
17+
or in the internal tooling to generate this file.
18+
19+
20+
.. contents:: Sections:
21+
:depth: 1
22+
:local:
23+
:backlinks: top
24+
25+
26+
.. _openeo_processes_functions:
27+
28+
Functions in ``openeo.processes``
29+
---------------------------------
30+
31+
The ``openeo.processes`` module implements (at top-level)
32+
a regular Python function for each openEO process
33+
(not only the official stable ones, but also experimental ones in "proposal" state).
34+
35+
These functions can be used directly as child callback,
36+
for example as follows:
37+
38+
.. code-block:: python
39+
40+
from openeo.processes import absolute, max
41+
42+
cube.apply(absolute)
43+
cube.reduce_dimension(max, dimension="t")
44+
45+
46+
Note how the signatures of the parent :py:class:`DataCube <openeo.rest.datacube.DataCube>` methods
47+
and the callback functions match up:
48+
49+
- :py:meth:`DataCube.apply() <openeo.rest.datacube.DataCube.apply>`
50+
expects a callback that receives a single numerical value,
51+
which corresponds to the parameter signature of :py:func:`openeo.processes.absolute`
52+
- :py:meth:`DataCube.reduce_dimension() <openeo.rest.datacube.DataCube.reduce_dimension>`
53+
expects a callback that receives an array of numerical values,
54+
which corresponds to the parameter signature :py:func:`openeo.processes.max`
755

8-
``openeo.processes`` functions
9-
-------------------------------
1056

1157
.. automodule:: openeo.processes
1258
:members:
13-
:exclude-members: ProcessBuilder
59+
:exclude-members: ProcessBuilder, process, _process
60+
1461

62+
``ProcessBuilder`` helper class
63+
--------------------------------
1564

16-
``openeo.processes.ProcessBuilder``
17-
------------------------------------
65+
.. FYI the ProcessBuilder docs are provided through its doc block
66+
with an "include" of "api-processbuilder.rst"
1867
1968
.. automodule:: openeo.processes
2069
:members: ProcessBuilder

docs/api.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
=============
2-
API: General
2+
API (General)
33
=============
44

55
High level Interface

docs/processes.rst

+59-32
Original file line numberDiff line numberDiff line change
@@ -227,20 +227,37 @@ to be invoked on a subset or slice of the datacube.
227227
For example:
228228

229229
* process ``apply`` requires a transformation that will be applied
230-
to each pixel in the cube (separately)
230+
to each pixel in the cube (separately), e.g. in pseudocode
231+
232+
.. code-block:: text
233+
234+
cube.apply(
235+
given a pixel value
236+
=> scale it with factor 0.01
237+
)
238+
231239
* process ``reduce_dimension`` requires an aggregation function to convert
232-
an array of pixel values (along a given dimension) to a single value
240+
an array of pixel values (along a given dimension) to a single value,
241+
e.g. in pseudocode
242+
243+
.. code-block:: text
244+
245+
cube.reduce_dimension(
246+
given a pixel timeseries (array) for a (x,y)-location
247+
=> temporal mean of that array
248+
)
249+
233250
* process ``aggregate_spatial`` requires a function to aggregate the values
234251
in one or more geometries
235252

236253
These transformation functions are usually called "**callbacks**"
237254
because instead of being called explicitly by the user,
238-
they are called by their "parent" process
255+
they are called and managed by their "parent" process
239256
(the ``apply``, ``reduce_dimension`` and ``aggregate_spatial`` in the examples)
240257

241258

242259
The openEO Python Client Library currently provides a couple of DataCube methods
243-
that expect a callback, most commonly:
260+
that expect such a callback, most commonly:
244261

245262
- :py:meth:`openeo.rest.datacube.DataCube.aggregate_spatial`
246263
- :py:meth:`openeo.rest.datacube.DataCube.aggregate_temporal`
@@ -249,8 +266,14 @@ that expect a callback, most commonly:
249266
- :py:meth:`openeo.rest.datacube.DataCube.apply_neighborhood`
250267
- :py:meth:`openeo.rest.datacube.DataCube.reduce_dimension`
251268

252-
These functions support several ways to specify the desired callback.
269+
The openEO Python Client Library supports several ways
270+
to specify the desired callback for these functions:
271+
253272

273+
.. contents::
274+
:depth: 1
275+
:local:
276+
:backlinks: top
254277

255278
Callback as string
256279
------------------
@@ -269,7 +292,7 @@ for example:
269292
This approach is only possible if the desired transformation is available
270293
as a single process. If not, use one of the methods below.
271294

272-
Also important is that the "signature" of the provided callback process
295+
It's also important to note that the "signature" of the provided callback process
273296
should correspond properly with what the parent process expects.
274297
For example: ``apply`` requires a callback process that receives a
275298
number and returns one (like ``absolute`` or ``sqrt``),
@@ -283,44 +306,41 @@ Callback as a callable
283306
-----------------------
284307

285308
You can also specify the callback as a "callable":
286-
a Python object that can be called (e.g. a function without parenthesis).
309+
which is a fancy word for a Python object that can be called,
310+
but just think of it like a function you can call.
287311

288-
The openEO Python Client Library defines the
289-
official processes in the :py:mod:`openeo.processes` module,
290-
which can be used directly:
312+
You can use a regular Python function, like this:
291313

292314
.. code-block:: python
293315
294-
from openeo.processes import absolute, max
316+
def transform(x):
317+
return x * 2 + 3
295318
296-
cube.apply(absolute)
297-
cube.reduce_dimension(max, dimension="t")
319+
cube.apply(transform)
298320
299-
You can also use ``lambda`` functions:
321+
or, more compactly, a "lambda"
322+
(a construct in Python to create anonymous inline functions):
300323

301324
.. code-block:: python
302325
303326
cube.apply(lambda x: x * 2 + 3)
304327
305328
306-
or normal Python functions:
329+
The openEO Python Client Library implements most of the official openEO processes as
330+
:ref:`functions in the "openeo.processes" module <openeo_processes_functions>`,
331+
which can be used directly as callback:
307332

308333
.. code-block:: python
309334
310-
from openeo.processes import array_element
311-
312-
def my_bandmath(data):
313-
band1 = array_element(data, index=0)
314-
band2 = array_element(data, index=1)
315-
return band1 + 1.2 * band2
316-
335+
from openeo.processes import absolute, max
317336
318-
cube.reduce_dimension(my_bandmath, dimension="bands")
337+
cube.apply(absolute)
338+
cube.reduce_dimension(max, dimension="t")
319339
320340
321-
The argument that is passed to these functions is
322-
an instance of :py:class:`openeo.processes.ProcessBuilder`.
323-
This is a helper object with predefined methods for all standard processes,
341+
The argument that will be passed to all these callback functions is
342+
a :py:class:`ProcessBuilder <openeo.processes.ProcessBuilder>` instance.
343+
This is a helper object with predefined methods for all standard openEO processes,
324344
allowing to use an object oriented coding style to define the callback.
325345
For example:
326346

@@ -334,12 +354,14 @@ For example:
334354
cube.reduce_dimension(avg, dimension="t")
335355
336356
337-
These methods also return ``ProcessBuilder`` objects,
357+
These methods also return :py:class:`ProcessBuilder <openeo.processes.ProcessBuilder>` objects,
338358
which also allows writing callbacks in chained fashion:
339359

340360
.. code-block:: python
341361
342-
cube.apply(lambda x: x.absolute().cos().add(y=1.23))
362+
cube.apply(
363+
lambda x: x.absolute().cos().add(y=1.23)
364+
)
343365
344366
345367
All this gives a lot of flexibility to define callbacks compactly
@@ -377,7 +399,7 @@ looks intuitive and straightforward, but it should be noted
377399
that not everything is allowed in these functions.
378400
You should just limit yourself to calling
379401
:py:mod:`openeo.processes` functions,
380-
:py:class:`openeo.processes.ProcessBuilder` methods
402+
:py:class:`ProcessBuilder <openeo.processes.ProcessBuilder>` methods
381403
and basic math operators.
382404
Don't call functions from other libraries like numpy or scipy.
383405
Don't use Python control flow statements like ``if/else`` constructs
@@ -414,9 +436,14 @@ Callback as ``PGNode``
414436
-----------------------
415437

416438
You can also pass a :py:class:`~openeo.internal.graph_building.PGNode` object as callback.
417-
This method is used internally and could be useful for more
418-
advanced use cases, but it requires more in-depth knowledge of
419-
the openEO API and openEO Python Client Library to construct correctly.
439+
440+
.. attention::
441+
This approach should generally not be used in normal use cases.
442+
The other options discussed above should be preferred.
443+
It's mainly intended for internal use and an occasional, advanced use case.
444+
It requires in-depth knowledge of the openEO API
445+
and openEO Python Client Library to construct correctly.
446+
420447
Some examples:
421448

422449
.. code-block:: python

openeo/internal/processes/generator.py

+4
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ def generate_process_py(processes: List[Process], output=sys.stdout, argv=None):
117117
118118
119119
class ProcessBuilder(ProcessBuilderBase):
120+
\"\"\"
121+
.. include:: api-processbuilder.rst
122+
\"\"\"
120123
121124
_ITERATION_LIMIT = 100
122125
@@ -234,6 +237,7 @@ def __gt__(self, other) -> 'ProcessBuilder':
234237
# Used command line arguments:
235238
# {cli}
236239
""".format(cli=" ".join(argv))))
240+
# TODO: add metadata (both as comment and as variable in the module) about the source of the process specs (e.g. version/commit of openeo-processes)
237241
output.write(oo_src)
238242
output.write(fun_src)
239243

openeo/processes.py

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010

1111

1212
class ProcessBuilder(ProcessBuilderBase):
13+
"""
14+
.. include:: api-processbuilder.rst
15+
"""
1316

1417
_ITERATION_LIMIT = 100
1518

0 commit comments

Comments
 (0)