Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
No results found
Show changes
Commits on Source (25)
......@@ -11,28 +11,32 @@
<td>
```python
import otbApplication
import otbApplication as otb
input_path = 'my_image.tif'
resampled = otbApplication.Registry.CreateApplication(
app = otb.Registry.CreateApplication(
'RigidTransformResample'
)
resampled.SetParameterString('in', input_path)
resampled.SetParameterString('interpolator', 'linear')
resampled.SetParameterFloat(
'transform.type.id.scalex',
0.5
app.SetParameterString(
'in', input_path
)
resampled.SetParameterFloat(
'transform.type.id.scaley',
0.5
app.SetParameterString(
'interpolator', 'linear'
)
resampled.SetParameterString('out', 'output.tif')
resampled.SetParameterOutputImagePixelType(
'out', otbApplication.ImagePixelType_uint16
app.SetParameterFloat(
'transform.type.id.scalex', 0.5
)
app.SetParameterFloat(
'transform.type.id.scaley', 0.5
)
app.SetParameterString(
'out', 'output.tif'
)
app.SetParameterOutputImagePixelType(
'out', otb.ImagePixelType_uint16
)
resampled.ExecuteAndWriteOutput()
app.ExecuteAndWriteOutput()
```
</td>
......@@ -41,14 +45,17 @@ resampled.ExecuteAndWriteOutput()
```python
import pyotb
resampled = pyotb.RigidTransformResample({
app = pyotb.RigidTransformResample({
'in': 'my_image.tif',
'interpolator': 'linear',
'transform.type.id.scaley': 0.5,
'transform.type.id.scalex': 0.5
})
resampled.write('output.tif', pixel_type='uint16')
app.write(
'output.tif',
pixel_type='uint16'
)
```
</td>
......@@ -66,42 +73,55 @@ resampled.write('output.tif', pixel_type='uint16')
<td>
```python
import otbApplication
import otbApplication as otb
app1 = otbApplication.Registry.CreateApplication(
app1 = otb.Registry.CreateApplication(
'RigidTransformResample'
)
app1.SetParameterString('in', 'my_image.tif')
app1.SetParameterString('interpolator', 'linear')
app1.SetParameterString(
'in', 'my_image.tif'
)
app1.SetParameterString(
'interpolator', 'linear'
)
app1.SetParameterFloat(
'transform.type.id.scalex',
0.5
'transform.type.id.scalex', 0.5
)
app1.SetParameterFloat(
'transform.type.id.scaley',
0.5
'transform.type.id.scaley', 0.5
)
app1.Execute()
app2 = otbApplication.Registry.CreateApplication(
app2 = otb.Registry.CreateApplication(
'OpticalCalibration'
)
app2.ConnectImage('in', app1, 'out')
app2.SetParameterString('level', 'toa')
app2.Execute()
app3 = otbApplication.Registry.CreateApplication(
app3 = otb.Registry.CreateApplication(
'BinaryMorphologicalOperation'
)
app3.ConnectImage('in', app2, 'out')
app3.SetParameterString('filter', 'dilate')
app3.SetParameterString('structype', 'ball')
app3.SetParameterInt('xradius', 3)
app3.SetParameterInt('yradius', 3)
app3.SetParameterString('out', 'output.tif')
app3.ConnectImage(
'in', app2, 'out'
)
app3.SetParameterString(
'filter', 'dilate'
)
app3.SetParameterString(
'structype', 'ball'
)
app3.SetParameterInt(
'xradius', 3
)
app3.SetParameterInt(
'yradius', 3
)
app3.SetParameterString(
'out', 'output.tif'
)
app3.SetParameterOutputImagePixelType(
'out',
otbApplication.ImagePixelType_uint16
'out', otb.ImagePixelType_uint16
)
app3.ExecuteAndWriteOutput()
```
......@@ -159,28 +179,31 @@ Consider an example where we want to perform the arithmetic operation
<td>
```python
import otbApplication
import otbApplication as otb
bmx = otbApplication.Registry.CreateApplication(
bmx = otb.Registry.CreateApplication(
'BandMathX'
)
bmx.SetParameterStringList(
'il',
['image1.tif', 'image2.tif', 'image3.tif']
) # all images are 3-bands
['im1.tif', 'im2.tif', 'im3.tif']
)
exp = ('im1b1*im2b1-2*im3b1; '
'im1b2*im2b2-2*im3b2; '
'im1b3*im2b3-2*im3b3')
bmx.SetParameterString('exp', exp)
bmx.SetParameterString('out', 'output.tif')
bmx.SetParameterString(
'out',
'output.tif'
)
bmx.SetParameterOutputImagePixelType(
'out',
otbApplication.ImagePixelType_uint8
'out',
otb.ImagePixelType_uint8
)
bmx.ExecuteAndWriteOutput()
```
In OTB, this code works for 3-bands images.
Note: code limited to 3-bands images.
</td>
<td>
......@@ -188,16 +211,19 @@ In OTB, this code works for 3-bands images.
```python
import pyotb
# transforming filepaths to pyotb objects
input1 = pyotb.Input('image1.tif')
input2 = pyotb.Input('image2.tif')
input3 = pyotb.Input('image3.tif')
# filepaths --> pyotb objects
in1 = pyotb.Input('im1.tif')
in2 = pyotb.Input('im2.tif')
in3 = pyotb.Input('im3.tif')
res = input1 * input2 - 2 * input2
res.write('output.tif', pixel_type='uint8')
res = in1 * in2 - 2 * in3
res.write(
'output.tif',
pixel_type='uint8'
)
```
In pyotb,this code works with images of any number of bands.
Note: works with any number of bands.
</td>
</tr>
......@@ -215,13 +241,15 @@ In pyotb,this code works with images of any number of bands.
```python
import otbApplication
import otbApplication as otb
# first 3 channels
app = otbApplication.Registry.CreateApplication(
app = otb.Registry.CreateApplication(
'ExtractROI'
)
app.SetParameterString('in', 'my_image.tif')
app.SetParameterString(
'in', 'my_image.tif'
)
app.SetParameterStringList(
'cl',
['Channel1', 'Channel2', 'Channel3']
......@@ -229,16 +257,30 @@ app.SetParameterStringList(
app.Execute()
# 1000x1000 roi
app = otbApplication.Registry.CreateApplication(
app = otb.Registry.CreateApplication(
'ExtractROI'
)
app.SetParameterString('in', 'my_image.tif')
app.SetParameterString('mode', 'extent')
app.SetParameterString('mode.extent.unit', 'pxl')
app.SetParameterFloat('mode.extent.ulx', 0)
app.SetParameterFloat('mode.extent.uly', 0)
app.SetParameterFloat('mode.extent.lrx', 999)
app.SetParameterFloat('mode.extent.lry', 999)
app.SetParameterString(
'in', 'my_image.tif'
)
app.SetParameterString(
'mode', 'extent'
)
app.SetParameterString(
'mode.extent.unit', 'pxl'
)
app.SetParameterFloat(
'mode.extent.ulx', 0
)
app.SetParameterFloat(
'mode.extent.uly', 0
)
app.SetParameterFloat(
'mode.extent.lrx', 999
)
app.SetParameterFloat(
'mode.extent.lry', 999
)
app.Execute()
```
......@@ -248,11 +290,11 @@ app.Execute()
```python
import pyotb
# transforming filepath to pyotb object
# filepath --> pyotb object
inp = pyotb.Input('my_image.tif')
extracted = inp[:, :, :3] # first 3 channels
extracted = inp[:1000, :1000] # 1000x1000 roi
extracted = inp[:, :, :3] # Bands 1,2,3
extracted = inp[:1000, :1000] # ROI
```
</td>
......
.rst-content div[class^=highlight] {
border: 0px;
}
.rst-content div[class^=highlight] pre {
padding: 0px;
}
.rst-content pre code {
background: #eeffcc;
}
......@@ -11,7 +11,7 @@ to make OTB more Python friendly.
- [Installation](installation.md)
- [How to use pyotb](quickstart.md)
- [Useful features](features.md)
- [Functions](features.md)
- [Functions](functions.md)
- [Interaction with Python libraries (numpy, rasterio, tensorflow)](interaction.md)
## Examples
......
......@@ -43,11 +43,9 @@ noisy_image.write('image_plus_noise.tif')
!!! warning
Limitations :
- The whole image is loaded into memory
- The georeference can not be modified. Thus, numpy operations can not change
the image or pixel size
- The georeference can not be modified. Thus, numpy operations can not
change the image or pixel size
## Export to rasterio
......@@ -138,10 +136,6 @@ memory
!!! warning
Limitations :
- For OTBTF versions < 4.0.0, it is not possible to use the tensorflow
python API inside a script where OTBTF is used because of libraries
clashing between Tensorflow and OTBTF, i.e. `import tensorflow` doesn't
work in a script where OTBTF apps have been initialized. This is why we
recommend to use latest OTBTF versions
Due to compilation issues in OTBTF before version 4.0.0, tensorflow and
pyotb can't be imported in the same python code. This problem has been
fixed in OTBTF 4.0.0.
......@@ -15,12 +15,11 @@ resampled = pyotb.RigidTransformResample({
})
```
Note that pyotb has a 'lazy' evaluation: it only performs operation when it is
needed, i.e. results are written to disk.
Thus, the previous line doesn't trigger the application.
For now, `resampled` has not been executed. Indeed, pyotb has a 'lazy'
evaluation: applications are executed only when required. Generally, like in
this example, executions happen to write output images to disk.
To actually trigger the application execution, you need to write the result to
disk:
To actually trigger the application execution, `write()` has to be called:
```python
resampled.write('output.tif') # this is when the application actually runs
......@@ -28,42 +27,57 @@ resampled.write('output.tif') # this is when the application actually runs
### Using Python keyword arguments
It is also possible to use the Python keyword arguments notation for passing
the parameters:
One can use the Python keyword arguments notation for passing that parameters:
```python
output = pyotb.SuperImpose(inr='reference_image.tif', inm='image.tif')
```
is equivalent to:
Which is equivalent to:
```python
output = pyotb.SuperImpose({'inr': 'reference_image.tif', 'inm': 'image.tif'})
```
Limitations : for this notation, python doesn't accept the parameter `in` or
any parameter that contains a dots (e.g. `io.in)`.
For `in` and other main input parameters of an OTB app, you may simply pass
the value as first argument, pyotb will guess the parameter name.
For parameters that contains dots, you can either use a dictionary, or replace dots (`.`) with underscores (`_`) as follow :
!!! warning
```python
resampled = pyotb.RigidTransformResample(
'my_image.tif',
interpolator = 'linear',
transform_type_id_scaley = 0.5,
transform_type_id_scalex = 0.5
)
For this notation, python doesn't accept the parameter `in` or any
parameter that contains a dots (e.g. `io.in`). For `in` or other main
input parameters of an OTB application, you may simply pass the value as
first argument, pyotb will guess the parameter name. For parameters that
contains dots, you can either use a dictionary, or replace dots (`.`)
with underscores (`_`).
Let's take the example of the `OrthoRectification` application of OTB,
with the input image parameter named `io.in`:
Option #1, keyword-arg-free:
```python
ortho = pyotb.OrthoRectification('my_image.tif')
```
Option #2, replacing dots with underscores in parameter name:
```python
ortho = pyotb.OrthoRectification(io_in='my_image.tif')
```
## In-memory connections
The big asset of pyotb is the ease of in-memory connections between apps.
One nice feature of pyotb is in-memory connection between apps. It relies on
the so-called [streaming](https://www.orfeo-toolbox.org/CookBook/C++/StreamingAndThreading.html)
mechanism of OTB, that enables to process huge images with a limited memory
footprint.
Let's start from our previous example. Consider the case where one wants to
apply optical calibration and binary morphological dilatation
following the undersampling.
pyotb allows to pass any application's output to another. This enables to
build pipelines composed of several applications.
Using pyotb, you can pass the output of an app as input of another app :
Let's start from our previous example. Consider the case where one wants to
resample the image, then apply optical calibration and binary morphological
dilatation. We can write the following code to build a pipeline that will
generate the output in an end-to-end fashion, without being limited with the
input image size or writing temporary files.
```python
import pyotb
......@@ -85,34 +99,55 @@ dilated = pyotb.BinaryMorphologicalOperation({
'out': 'output.tif',
'filter': 'dilate',
'structype': 'ball',
'xradius': 3, 'yradius': 3
'xradius': 3,
'yradius': 3
})
```
dilated.write('result.tif')
We just have built our first pipeline! At this point, it's all symbolic since
no computation has been performed. To trigger our pipeline, one must call the
`write()` method from the pipeline termination:
```python
dilated.write('output.tif')
```
In the next section, we will detail how `write()` works.
## Writing the result of an app
Any pyotb object can be written to disk using the `write` method, e.g. :
Any pyotb object can be written to disk using `write()`.
Let's consider the following pyotb application instance:
```python
import pyotb
resampled = pyotb.RigidTransformResample({
'in': 'my_image.tif',
'interpolator': 'linear',
'transform.type.id.scaley': 0.5,
'transform.type.id.scalex': 0.5
})
```
# Here you can set optionally pixel type and extended filename variables
resampled.write(
{'out': 'output.tif'},
pixel_type='uint16',
ext_fname='?nodata=65535'
)
We can then write the output of `resampled` as following:
```python
resampled.write('output.tif')
```
!!! note
For applications that have multiple outputs, passing a `dict` of filenames
can be considered. Let's take the example of `MeanShiftSmoothing` which
has 2 output images:
```python
import pyotb
meanshift = pyotb.MeanShiftSmoothing('my_image.tif')
meanshift.write({'fout': 'output_1.tif', 'foutpos': 'output_2.tif'})
```
Another possibility for writing results is to set the output parameter when
initializing the application:
......@@ -126,4 +161,46 @@ resampled = pyotb.RigidTransformResample({
'transform.type.id.scaley': 0.5,
'transform.type.id.scalex': 0.5
})
```
\ No newline at end of file
```
### Pixel type
Setting the pixel type is optional, and can be achieved setting the
`pixel_type` argument:
```python
resampled.write('output.tif', pixel_type='uint16')
```
The value of `pixel_type` corresponds to the name of a pixel type from OTB
applications (e.g. `'uint8'`, `'float'`, etc).
### Extended filenames
Extended filenames can be passed as `str` or `dict`.
As `str`:
```python
resampled.write(
...
ext_fname='nodata=65535&box=0:0:256:256'
)
```
As `dict`:
```python
resampled.write(
...
ext_fname={'nodata': '65535', 'box': '0:0:256:256'}
)
```
!!! info
When `ext_fname` is provided and the output filenames contain already some
extended filename pattern, the ones provided in the filenames take
priority over the ones passed in `ext_fname`. This allows to fine-grained
tune extended filenames for each output, with a common extended filenames
keys/values basis.
/* this is for readthedocs theme */
.wy-nav-content {
max-width: 1000px;
}
\ No newline at end of file
......@@ -55,7 +55,8 @@ extra:
- icon: fontawesome/brands/gitlab
link: https://gitlab.orfeo-toolbox.org/nicolasnn/pyotb
extra_css:
- stylesheets/extra.css
- https://gitlab.orfeo-toolbox.org/orfeotoolbox/otb/-/raw/8.1.2-rc1/Documentation/Cookbook/_static/css/otb_theme.css
- extra.css
use_directory_urls: false # this creates some pyotb/core.html pages instead of pyotb/core/index.html
markdown_extensions:
......@@ -66,13 +67,15 @@ markdown_extensions:
toc_depth: 1-2
- pymdownx.highlight:
anchor_linenums: true
- pymdownx.inlinehilite
- pymdownx.snippets
- pymdownx.details
- pymdownx.superfences
- pymdownx.superfences:
custom_fences:
- name: python
class: python
format: !!python/name:pymdownx.superfences.fence_code_format
# Rest of the navigation..
site_name: "pyotb documentation: a Python extension of OTB"
# Rest of the navigation.
site_name: "pyotb: Orfeo ToolBox for Python"
repo_url: https://gitlab.orfeo-toolbox.org/nicolasnn/pyotb
repo_name: pyotb
docs_dir: doc/
......@@ -3,9 +3,6 @@
from __future__ import annotations
import os
import subprocess
import sys
from pathlib import Path
import otbApplication as otb # pylint: disable=import-error
......@@ -13,7 +10,7 @@ from .core import App
from .helpers import logger
def get_available_applications(as_subprocess: bool = False) -> list[str]:
def get_available_applications() -> tuple[str]:
"""Find available OTB applications.
Args:
......@@ -23,53 +20,13 @@ def get_available_applications(as_subprocess: bool = False) -> list[str]:
tuple of available applications
"""
app_list = ()
if as_subprocess and sys.executable:
# Currently, there is an incompatibility between OTBTF and Tensorflow that causes segfault
# when OTBTF apps are used in a script where tensorflow has already been imported.
# See https://github.com/remicres/otbtf/issues/28
# Thus, we run this piece of code in a clean independent `subprocess` that doesn't interact with Tensorflow
env = os.environ.copy()
if "PYTHONPATH" not in env:
env["PYTHONPATH"] = ""
env["PYTHONPATH"] += ":" + str(Path(otb.__file__).parent)
env[
"OTB_LOGGER_LEVEL"
] = "CRITICAL" # in order to suppress warnings while listing applications
pycmd = "import otbApplication; print(otbApplication.Registry.GetAvailableApplications())"
cmd_args = [sys.executable, "-c", pycmd]
try:
params = {"env": env, "stdout": subprocess.PIPE, "stderr": subprocess.PIPE}
with subprocess.Popen(cmd_args, **params) as process:
logger.debug("Exec \"%s '%s'\"", " ".join(cmd_args[:-1]), pycmd)
stdout, stderr = process.communicate()
stdout, stderr = stdout.decode(), stderr.decode()
# ast.literal_eval is secure and will raise more handy Exceptions than eval
from ast import literal_eval # pylint: disable=import-outside-toplevel
app_list = literal_eval(stdout.strip())
assert isinstance(app_list, (tuple, list))
except subprocess.SubprocessError:
logger.debug("Failed to call subprocess")
except (ValueError, SyntaxError, AssertionError):
logger.debug(
"Failed to decode output or convert to tuple:\nstdout=%s\nstderr=%s",
stdout,
stderr,
)
if not app_list:
logger.debug(
"Failed to list applications in an independent process. Falling back to local python import"
)
# Find applications using the normal way
if not app_list:
app_list = otb.Registry.GetAvailableApplications()
if not app_list:
raise SystemExit(
"Unable to load applications. Set env variable OTB_APPLICATION_PATH and try again."
)
logger.info("Successfully loaded %s OTB applications", len(app_list))
return app_list
app_list = otb.Registry.GetAvailableApplications()
if app_list:
logger.info("Successfully loaded %s OTB applications", len(app_list))
return app_list
raise SystemExit(
"Unable to load applications. Set env variable OTB_APPLICATION_PATH and try again."
)
class OTBTFApp(App):
......@@ -113,7 +70,7 @@ class OTBTFApp(App):
super().__init__(name, *args, **kwargs)
AVAILABLE_APPLICATIONS = get_available_applications(as_subprocess=True)
AVAILABLE_APPLICATIONS = get_available_applications()
# This is to enable aliases of Apps, i.e. using apps like `pyotb.AppName(...)` instead of `pyotb.App("AppName", ...)`
_CODE_TEMPLATE = (
......
......@@ -762,7 +762,7 @@ class App(OTBObject):
path: str | Path | dict[str, str] = None,
pixel_type: dict[str, str] | str = None,
preserve_dtype: bool = False,
ext_fname: str = "",
ext_fname: dict[str, str] | str = None,
**kwargs,
) -> bool:
"""Set output pixel type and write the output raster files.
......@@ -772,14 +772,14 @@ class App(OTBObject):
- dictionary containing key-arguments enumeration. Useful when a key contains
non-standard characters such as a point, e.g. {'io.out':'output.tif'}
- None if output file was passed during App init
ext_fname: Optional, an extended filename as understood by OTB (e.g. "&gdal:co:TILED=YES")
Will be used for all outputs (Default value = "")
pixel_type: Can be : - dictionary {out_param_key: pixeltype} when specifying for several outputs
- str (e.g. 'uint16') or otbApplication.ImagePixelType_... When there are several
outputs, all outputs are written with this unique type.
Valid pixel types are uint8, uint16, uint32, int16, int32, float, double,
cint16, cint32, cfloat, cdouble. (Default value = None)
preserve_dtype: propagate main input pixel type to outputs, in case pixel_type is None
ext_fname: Optional, an extended filename as understood by OTB (e.g. "&gdal:co:TILED=YES")
Will be used for all outputs (Default value = None)
**kwargs: keyword arguments e.g. out='output.tif'
Returns:
......@@ -1660,8 +1660,7 @@ def summarize(
useful to remove extended filenames.
Returns:
nested dictionary with serialized App(s) containing name and
parameters of an app and its parents
nested dictionary with serialized App(s) containing name and parameters of an app and its parents
"""
......