Commit 7782dd7f authored by Julien Osman's avatar Julien Osman
Browse files

Merge branch '2226-wrap-image-metadata-in-swig' into 'develop'

Provide access to the metadata from the python API

Closes #2226

See merge request !870
parents eae58af1 97cff932
Pipeline #9351 passed with stages
in 20 minutes and 49 seconds
#!/bin/sh
#
# Copyright (C) 2005-2020 Centre National d'Etudes Spatiales (CNES)
#
# This file is part of Orfeo Toolbox
#
# https://www.orfeo-toolbox.org/
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#cmake builds with rpath in the binary dir, so we don't need to set LD_LIBRARY_PATH here
#export LD_LIBRARY_PATH=@CMAKE_BINARY_DIR@/lib:$LD_LIBRARY_PATH
export PYTHONPATH=@CMAKE_BINARY_DIR@/lib/otb/python:$PYTHONPATH
export OTB_APPLICATION_PATH=@CMAKE_BINARY_DIR@/lib/otb/applications
python3 @CMAKE_CURRENT_SOURCE_DIR@/Scripts/otbGeneratePythonApiRstDoc.py "$1"
......@@ -92,6 +92,12 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/CMake/RunApplicationsRstGenerator.sh.
${CMAKE_CURRENT_BINARY_DIR}/RunApplicationsRstGenerator.sh
@ONLY)
execute_process(COMMAND ${CMAKE_COMMAND} -E remove ${CMAKE_CURRENT_BINARY_DIR}/RunPythonApiRstGenerator.sh)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/CMake/RunPythonApiRstGenerator.sh.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/RunPythonApiRstGenerator.sh
@ONLY)
file(COPY ${RST_SOURCE_DIR} DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/Art DESTINATION ${RST_BINARY_DIR})
......@@ -118,6 +124,15 @@ add_custom_target(generate_otbapps_rst
DEPENDS OTBSWIGWrapper-all
)
add_custom_target(generate_pythonapi_rst
COMMAND ${SH_INTERP} ${CMAKE_CURRENT_BINARY_DIR}/RunPythonApiRstGenerator.sh
${RST_BINARY_DIR}
WORKING_DIRECTORY ${RST_BINARY_DIR}
COMMENT "Auto-generating Python Api Documentation in RST"
DEPENDS OTBSWIGWrapper-all
)
add_custom_target(generate_examples_rst
COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/Scripts/otbGenerateExamplesRstDoc.py
${RST_BINARY_DIR}
......@@ -144,6 +159,7 @@ add_custom_target(CookBookHTML
-c ${SPHINX_CONF_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
DEPENDS generate_otbapps_rst
DEPENDS generate_pythonapi_rst
DEPENDS generate_examples_rst
COMMENT "Building RST documentation in html")
......
#!/usr/bin/env python3
#
# Copyright (C) 2005-2021 Centre National d'Etudes Spatiales (CNES)
#
# This file is part of Orfeo Toolbox
#
# https://www.orfeo-toolbox.org/
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import argparse
import otbApplication
def format_key_list(keys_str):
"""
Format the key list
:param keys_str: a string containing the keys separated by spaces
:return: a string containing the keys ordered and separated by a coma and a space
"""
key_list = keys_str.split(" ")
key_list.sort()
return ", ".join(key_list)
def GenerateRstForPythonAPi(rst_dir):
" Generate the .rst file for the PythonAPI page"
print("Generating rst for Python API")
# Instentiate an ImageMetadata object to retrieve the keys
imd = otbApplication.ImageMetadata()
# Render the page
output_python_api = template_python_api.format(
key_list_double=format_key_list(imd.GetKeyListNum()),
key_list_string=format_key_list(imd.GetKeyListStr()),
key_list_l1d=format_key_list(imd.GetKeyListL1D()),
# key_list_l2d=format_key_list(imd.GetKeyListL2D()),
key_list_time=format_key_list(imd.GetKeyListTime())
)
# Write the page
with open(rst_dir + '/PythonAPI.rst', 'w',encoding='utf-8') as new_rst_file:
new_rst_file.write(output_python_api)
if __name__ == "__main__":
parser = argparse.ArgumentParser(usage="Export the PythonAPI doc page to rst file")
parser.add_argument("rst_dir", help="Directory where rst files are generated")
args = parser.parse_args()
# Load rst template
template_python_api = open("templates/PythonAPI.rst").read()
GenerateRstForPythonAPi(args.rst_dir)
......@@ -150,7 +150,7 @@ SuperBuild: Build OTB and all dependencies
OTB’s compilation is customized by specifying configuration variables.
The most important configuration variables are shown in the
table above. The simplest way to provide
table above. The simplest way to provide
configuration variables is via the command line ``-D`` option:
::
......@@ -285,7 +285,7 @@ FindXXX.cmake scripts, or with the ``XXX_INCLUDEDIR`` and
``XXX_LIBRARY`` variables.
Additionally, decide which module you wish to enable, together with tests and
examples. Refer to table above for the list of CMake variables.
examples. Refer to table above for the list of CMake variables.
OTB is modular. It is possible to only build some modules
instead of the whole set. To deactivate a module (and the ones that
......@@ -295,7 +295,7 @@ depend on it) switch off the CMake variable
Some of the OTB capabilities are considered as optional, and you can
deactivate the related modules thanks to a set of CMake variables
starting with ``OTB_USE_XXX``. The table below shows which modules
starting with ``OTB_USE_XXX``. The table below shows which modules
are associated to these variables. It is very important to notice that
these variable override the variable ``OTB_BUILD_DEFAULT_MODULES``.
......@@ -366,7 +366,8 @@ hours to run them all, depending on compilation options
(release mode does make a difference) and hardware.
To run the tests, first make sure to set the option
``BUILD_TESTING`` to ``ON`` before building the library.
``BUILD_TESTING`` to ``ON`` before building the library. If you want to run the tests for the
python API, you will also need to install the python module `pytest`.
For some of the tests, you also need the test data and the baselines (~1GB). These files are stored
using `git-lfs` in the `Data` folder at the root of otb sources. To download them, you have to make
......
......@@ -106,7 +106,7 @@ functions *SetParameters()* and *GetParameters()*.
.. code-block:: python
params = {"in":"myInput.tif", "type.mean.radius":4}
params = {{"in":"myInput.tif", "type.mean.radius":4}}
app.SetParameters(params)
params2 = app.GetParameters()
......@@ -328,13 +328,15 @@ functions:
+---------------------------------+---------------------------------------+
| ``GetImageProjection(...)`` | Projection WKT string |
+---------------------------------+---------------------------------------+
| ``GetImageMetaData(...)`` | the entire MetaDataDictionary |
| ``GetMetadataDictionary(...)`` | the entire MetaDataDictionary |
+---------------------------------+---------------------------------------+
| ``GetImageRequestedRegion(...)``| requested region |
+---------------------------------+---------------------------------------+
| ``GetImageBasePixelType(...)`` | pixel type of the underlying |
| | Image/VectorImage. |
+---------------------------------+---------------------------------------+
| ``GetImageMetadata(...)`` | the ImateMetadata object |
+---------------------------------+---------------------------------------+
All these getters functions use the following arguments:
......@@ -372,26 +374,61 @@ The Python dictionary used has the following entries:
* ``'spacing'``: signed spacing of the image
* ``'size'``: full size of the image
* ``'region'``: region of the image present in the buffer
* ``'metadata'``: metadata dictionary (contains projection, sensor model,...)
* ``'metadata'``: metadata dictionary (contains projection,...)
The metadata dictionary contains various type of data. Here are the available keys of the dictionnary, ordered by type:
* double:
{key_list_double}
* string:
{key_list_string}
* LUT 1D:
{key_list_l1d}
* time object:
{key_list_time}
This dictionary also contains metadata related to projection and
sensor model. The coresponding keys are not accessible at the
moment. But the dictionary offers a few extra methods:
* ``GetProjectedGeometry()`` returns a string representing the
projection. It can be a WKN, an EPSG or a PROJ string.
* ``GetProjectionWKT()`` returns a string representing the projection
as a WKT.
* ``GetProjectionProj()`` returns a string representing the projection
as a PROJ string.
Now some basic Q&A about this interface:
Q: What portion of the image is exported to Numpy array?
A: By default, the whole image is exported. If you had a non-empty requested
region (the result of calling PropagateRequestedRegion()), then this region
is exported.
* **What portion of the image is exported to Numpy array?**
By default, the whole image is exported. If you had a non-empty
requested region (the result of calling
PropagateRequestedRegion()), then this region is exported.
Q: What is the difference between ImportImage and ImportVectorImage?
A: The first one is here for Applications that expect a monoband otb::Image.
In most cases, you will use the second one: ImportVectorImage.
* **What is the difference between ImportImage and ImportVectorImage?**
Q: What kind of objects are there in this dictionary export?
A: The array is a numpy.ndarray. The other fields are wrapped
objects from the OTB library but you can interact with them in a
Python way: they support ``len()`` and ``str()`` operator, as well as
bracket operator ``[]``. Some of them also have a ``keys()`` function just like
dictionaries.
The first one is here for Applications that expect a monoband
otb::Image. In most cases, you will use the second one:
ImportVectorImage.
* **What kind of objects are there in this dictionary export?**
The array is a numpy.ndarray. The other fields are wrapped objects
from the OTB library but you can interact with them in a Python
way: they support ``len()`` and ``str()`` operator, as well as
bracket operator ``[]``. Some of them also have a ``keys()``
function just like dictionaries.
This interface allows you to export OTB images (or extracts) to Numpy array,
process them by other means, and re-import them with preserved metadata. Please
note that this is different from an in-memory connection.
......
......@@ -58,6 +58,32 @@ inline std::ostream& Join(std::ostream& os, TRange const& range, std::string con
}
return os;
}
/**
* Joins elements from a map (second) into a stream.
* \tparam Tmap Map type
* \param os destination stream
* \param[in] range Range to print
* \param[in] separator Separator string to use between elements.
*
* \return the stream
* \throw None At least, this function is exception neutral.
*/
template <typename Tmap>
inline std::ostream& JoinMap(std::ostream& os, Tmap const& range, std::string const& separator)
{
if (!boost::empty(range))
{
typename boost::range_iterator<Tmap const>::type first = boost::begin(range);
typename boost::range_iterator<Tmap const>::type const last = boost::end(range);
os << first->second;
for (++first; first != last; ++first)
{
os << separator << first->second;
}
}
return os;
}
} // end namespace otb
#ifndef OTB_MANUAL_INSTANTIATION
......
......@@ -145,6 +145,9 @@ public:
/** Test if a key is available */
bool Has(const MDNum& key) const;
/** Return the list of valid keys */
std::string GetKeyListNum() const;
// -------------------- String utility function ----------------------------
......@@ -159,6 +162,9 @@ public:
/** Test if a key is available */
bool Has(const MDStr& key) const;
/** Return the list of valid keys */
std::string GetKeyListStr() const;
// -------------------- LUT1D utility function ----------------------------
......@@ -173,6 +179,9 @@ public:
/** Test if a key is available */
bool Has(const MDL1D& key) const;
/** Return the list of valid keys */
std::string GetKeyListL1D() const;
// -------------------- 2D LUT utility function ----------------------------
......@@ -187,6 +196,9 @@ public:
/** Test if a key is available */
bool Has(const MDL2D& key) const;
/** Return the list of valid keys */
// std::string GetKeyListL2D() const;
// -------------------- Time utility function ----------------------------
......@@ -202,6 +214,9 @@ public:
/** Test if a key is available */
bool Has(const MDTime& key) const;
/** Return the list of valid keys */
std::string GetKeyListTime() const;
// -------------------- Extra keys utility function --------------------------
/** Read-only accessor to extra keys */
......
......@@ -24,18 +24,18 @@
#include <string>
#include <vector>
#include <cstdio>
#include <unordered_map>
#include <boost/bimap.hpp>
#include <boost/algorithm/string.hpp>
#include "itkDataObject.h"
#include "itkVariableLengthVector.h"
#include "OTBMetadataExport.h"
#include "otbStringUtils.h"
#include "otbJoinContainer.h"
#include <unordered_map>
namespace otb
{
/** \namespace MetaDataKey
......
......@@ -20,6 +20,7 @@
#include "otbImageMetadata.h"
#include "otbSpatialReference.h"
#include "otbJoinContainer.h"
namespace otb
{
......@@ -166,6 +167,16 @@ bool ImageMetadataBase::Has(const MDNum& key) const
return (NumericKeys.find(key) != NumericKeys.end());
}
std::string ImageMetadataBase::GetKeyListNum() const
{
std::ostringstream oss;
for (const auto& kv : MetaData::MDNumNames.left)
oss << kv.second << " ";
auto returnString = oss.str();
returnString.pop_back();
return returnString;
}
// -------------------- String utility function ----------------------------
const std::string & ImageMetadataBase::operator[](const MDStr& key) const
......@@ -188,6 +199,13 @@ bool ImageMetadataBase::Has(const MDStr& key) const
return (StringKeys.find(key) != StringKeys.end());
}
std::string ImageMetadataBase::GetKeyListStr() const
{
std::ostringstream oss;
JoinMap(oss, MetaData::MDStrNames.left, " ");
return oss.str();
}
// -------------------- LUT1D utility function ----------------------------
const MetaData::LUT1D & ImageMetadataBase::operator[](const MDL1D& key) const
......@@ -210,6 +228,16 @@ bool ImageMetadataBase::Has(const MDL1D& key) const
return (LUT1DKeys.find(key) != LUT1DKeys.end());
}
std::string ImageMetadataBase::GetKeyListL1D() const
{
std::ostringstream oss;
for (const auto& kv : MetaData::MDL1DNames.left)
oss << kv.second << " ";
auto returnString = oss.str();
returnString.pop_back();
return returnString;
}
// -------------------- 2D LUT utility function ----------------------------
const MetaData::LUT2D & ImageMetadataBase::operator[](const MDL2D& key) const
......@@ -232,6 +260,16 @@ bool ImageMetadataBase::Has(const MDL2D& key) const
return (LUT2DKeys.find(key) != LUT2DKeys.end());
}
//std::string ImageMetadataBase::GetKeyListL2D() const
//{
// std::ostringstream oss;
// for (const auto& kv : MetaData::MDL2DNames.left)
// oss << kv.second << " ";
// auto returnString = oss.str();
// returnString.pop_back();
// return returnString;
//}
// -------------------- Time utility function ----------------------------
const MetaData::TimePoint & ImageMetadataBase::operator[](const MDTime& key) const
......@@ -254,6 +292,16 @@ bool ImageMetadataBase::Has(const MDTime& key) const
return (TimeKeys.find(key) != TimeKeys.end());
}
std::string ImageMetadataBase::GetKeyListTime() const
{
std::ostringstream oss;
for (const auto& kv : MetaData::MDTimeNames.left)
oss << kv.second << " ";
auto returnString = oss.str();
returnString.pop_back();
return returnString;
}
// -------------------- Extra keys utility function --------------------------
const std::string & ImageMetadataBase::operator[](const std::string & key) const
......
......@@ -239,8 +239,6 @@ void LUT<VDim>::FromString(std::string str)
template class LUT<1>;
template class LUT<2>;
// array<pair<> >
// boost::flat_map<>
MDNumBmType MDNumNames = bimapGenerator<MDNum>(std::map<MDNum, std::string> {
{MDNum::TileHintX,"TileHintX"},
{MDNum::TileHintY,"TileHintY"},
......
......@@ -825,8 +825,13 @@ public:
* the index of the largest possible region starts at (0,0).*/
ImageBaseType::RegionType GetImageRequestedRegion(const std::string& key, unsigned int idx = 0);
/** Get/Set the ImageMetadata of the image parameter 'key'. The optional 'idx'
* allows selecting the image in an InputImageList.*/
ImageMetadata &GetImageMetadata(const std::string& key, unsigned int idx = 0);
void SetImageMetadata(const ImageMetadata & imd, const std::string& key, unsigned int idx = 0);
/** Returns a copy of the metadata dictionary of the image */
itk::MetaDataDictionary GetImageMetaData(const std::string& key, unsigned int idx = 0);
itk::MetaDataDictionary GetMetadataDictionary(const std::string& key, unsigned int idx = 0);
/** Find out what is the pixel type from an image parameter
* This function assumes that the underlying object is either an otb::Image
......
......@@ -1670,7 +1670,26 @@ ImageBaseType::RegionType Application::GetImageRequestedRegion(const std::string
return requested;
}
itk::MetaDataDictionary Application::GetImageMetaData(const std::string& key, unsigned int idx)
ImageMetadata &Application::GetImageMetadata(const std::string& key, unsigned int idx)
{
ImageBaseType* image = this->GetParameterImageBase(key, idx);
otb::ImageCommons* castedImage = dynamic_cast<otb::ImageCommons*>(image);
if (castedImage)
return castedImage->GetImageMetadata();
throw std::runtime_error("Cannot retrieve metadata from the image parameter " + key);
}
void Application::SetImageMetadata(const ImageMetadata & imd, const std::string& key, unsigned int idx)
{
ImageBaseType* image = this->GetParameterImageBase(key, idx);
otb::ImageCommons* castedImage = dynamic_cast<otb::ImageCommons*>(image);
if (castedImage)
castedImage->SetImageMetadata(imd);
else
throw std::runtime_error("Cannot set metadata to the image parameter " + key);
}
itk::MetaDataDictionary Application::GetMetadataDictionary(const std::string& key, unsigned int idx)
{
ImageBaseType* image = this->GetParameterImageBase(key, idx);
return image->GetMetaDataDictionary();
......
......@@ -64,7 +64,7 @@ int otbWrapperImageInterface(int argc, char* argv[])
ofs << "ProjectionRef:" << std::endl;
ofs << app1->GetImageProjection("out") << std::endl;
itk::MetaDataDictionary dict = app1->GetImageMetaData("out");
itk::MetaDataDictionary dict = app1->GetMetadataDictionary("out");
ofs << "Dictionary keys:" << std::endl;
for (auto& key : dict.GetKeys())
{
......
......@@ -50,4 +50,4 @@ if ( OTB_WRAP_PYTHON )
message( WARNING
"OTB wrappers will be done without support for NumPy (not found).")
endif()
endif()
\ No newline at end of file
endif()
......@@ -34,6 +34,8 @@
%include "itkMacro.i"
%include "itkBase.i"
%include "otbImageMetadata.i"
#if OTB_SWIGNUMPY
%include "numpy.i"
......@@ -146,20 +148,6 @@ public:
// TODO : finish wrapping
};
class GCP
{
public:
std::string m_Id;
std::string m_Info;
double m_GCPCol;
double m_GCPRow;
double m_GCPX;
double m_GCPY;
double m_GCPZ;
GCP();
void Print(std::ostream& os) const;
};
} // end of namespace otb
#if SWIGPYTHON
......@@ -310,7 +298,9 @@ public:
std::string GetImageProjection(const std::string & key, unsigned int idx = 0);
unsigned long PropagateRequestedRegion(const std::string & key, itk::ImageRegion<2> region, unsigned int idx = 0);
itk::ImageRegion<2> GetImageRequestedRegion(const std::string & key, unsigned int idx = 0);
itkMetaDataDictionary GetImageMetaData(const std::string & key, unsigned int idx = 0);
otb::ImageMetadata &GetImageMetadata(const std::string& key, unsigned int idx = 0);
void SetImageMetadata(const otb::ImageMetadata & imd, const std::string& key, unsigned int idx = 0);
itkMetaDataDictionary GetMetadataDictionary(const std::string & key, unsigned int idx = 0);
otb::Wrapper::ImagePixelType GetImageBasePixelType(const std::string & key, unsigned int idx = 0);
itkProcessObject* GetProgressSource() const;
......@@ -492,7 +482,7 @@ public:
itk::Vector<SpacePrecisionType,2> spacing,
itk::Size<2> size,
itk::ImageRegion<2> bufferRegion,
itkMetaDataDictionary metadata)
otb::ImageMetadata metadata)
{
img->SetOrigin(origin);
otb::internal::SetSignedSpacing(img, spacing);
......@@ -506,7 +496,7 @@ public:
}
img->SetRequestedRegion(bufferRegion);
img->SetBufferedRegion(bufferRegion);
img->SetMetaDataDictionary(metadata);
dynamic_cast<otb::ImageCommons*>(img)->SetImageMetadata(metadata);
}
} /* end of %extend */
#endif /* OTB_SWIGNUMPY */
......@@ -897,7 +887,7 @@ class ApplicationProxy(object):
output["spacing"] = self.GetImageSpacing(paramKey)
output["size"] = self.GetImageSize(paramKey)
output["region"] = self.GetImageRequestedRegion(paramKey)
output["metadata"] = self.GetImageMetaData(paramKey)
output["metadata"] = self.GetImageMetadata(paramKey)
return output
}
......