Commit 0326969a authored by Victor Poughon's avatar Victor Poughon

DOC: Render examples in the CookBook

parent ffcfca59
Pipeline #266 failed with stage
in 60 minutes
......@@ -117,10 +117,15 @@ file(COPY ${RST_SOURCE_DIR} DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/Art DESTINATION ${RST_BINARY_DIR})
# Create symlinks to otb-data/Output and otb-data/Input in the rst build directory
# Used for including figures and images in the CookBook
execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink "${OTB_DATA_ROOT}/Output" "${RST_BINARY_DIR}/Output")
execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink "${OTB_DATA_ROOT}/Input" "${RST_BINARY_DIR}/Input")
set(SPHINX_CONF_DIR ${CMAKE_CURRENT_BINARY_DIR})
string(TIMESTAMP OTB_COPYRIGHT_YEAR "%Y")
set(OTB_COPYRIGHT_TEXT "${OTB_COPYRIGHT_YEAR} CNES.The OTB CookBook is licensed under a Creative Commons Attribution-ShareAlike 4.0 International license (CC-BY-SA)")
set(OTB_COPYRIGHT_TEXT "${OTB_COPYRIGHT_YEAR} CNES. The OTB CookBook is licensed under a Creative Commons Attribution-ShareAlike 4.0 International license (CC-BY-SA).")
configure_file(${RST_SOURCE_DIR}/conf.py.in ${SPHINX_CONF_DIR}/conf.py @ONLY)
......@@ -130,7 +135,16 @@ add_custom_target(generate_otbapps_rst
COMMAND ${SH_INTERP} ${CMAKE_CURRENT_BINARY_DIR}/RunApplicationsRstGenerator.sh
${RST_BINARY_DIR}
WORKING_DIRECTORY ${RST_BINARY_DIR}
COMMENT "Auto-generating Application Reference Documentation in RST"
COMMENT "Auto-generating Application Documentation in RST"
DEPENDS OTBSWIGWrapper-all
)
add_custom_target(generate_examples_rst
COMMAND "python3" ${CMAKE_CURRENT_SOURCE_DIR}/Scripts/otbGenerateExamplesRstDoc.py
${RST_BINARY_DIR}
${CMAKE_SOURCE_DIR}
WORKING_DIRECTORY ${RST_BINARY_DIR}
COMMENT "Auto-generating Examples in RST"
DEPENDS OTBSWIGWrapper-all
)
......@@ -152,6 +166,7 @@ add_custom_target(CookBookHTML
-c ${SPHINX_CONF_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
DEPENDS generate_otbapps_rst
DEPENDS generate_examples_rst
COMMENT "Building RST documentation in html")
add_custom_target(CookBookArchive
......
#!/usr/bin/env python3
#
# Copyright (C) 2005-2019 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 glob
import subprocess
import os
from os.path import join
import re
def run_example(otb_root, otb_data, name, dry_run):
"""
Run an example by name
Assumes the current working directory is an OTB build
"""
# Find binary in bin/
binary_names = glob.glob(join("bin", name))
if len(binary_names) == 0:
raise RuntimeError("Can't find binary for {}".format(name))
if len(binary_names) > 1:
raise RuntimeError("Found {} binaries for {}".format(len(binary_names), name))
binary = os.path.abspath(binary_names[0])
# Find source file in otb_root/Examples/<tag>/name
sources_files = glob.glob(join(otb_root, "Examples/*/" + name + ".cxx"))
if len(sources_files) == 0:
raise RuntimeError("Can't find source file for {}".format(name))
if len(sources_files) > 1:
raise RuntimeError("Found {} source files for {}".format(len(sources_files), name))
filename = os.path.abspath(sources_files[0])
# Extract example usage command arguments
usage_rx = r"\/\* Example usage:\n\.\/[a-zA-Z]+ (.*?)\*\/"
match = re.search(usage_rx, open(filename).read(), flags = re.MULTILINE | re.DOTALL)
if not match:
raise RuntimeError("Can't find example usage in {}".format(filename))
example_args = match.group(1).replace("\\\n", "").split()
# Make sure Output dir exists
os.makedirs(join(otb_data, "Output"), exist_ok=True)
print("$ " + binary + " " + " ".join(example_args))
if dry_run:
return
# Execute the example with otb_data as working directory,
# because paths are given relative to otb_data in the example usage
subprocess.check_call([binary, *example_args], cwd=otb_data)
# TODO handle examples with multiple usage (Examples/BasicFilters/DEMToRainbowExample.cxx)
def main():
parser = argparse.ArgumentParser(usage="Run one or all OTB cxx examples")
parser.add_argument("otb_root", help="Path to otb repository")
parser.add_argument("otb_data", help="Path to otb-data repository")
parser.add_argument("--name", type=str, help="Run only one example with then given name")
parser.add_argument("-n", "--dry-run", action='store_true', help="Dry run, only print commands")
parser.add_argument("-k", "--keep-going", action='store_true', help="Keep going after failing examples")
args = parser.parse_args()
if args.name:
run_example(args.otb_root, args.otb_data, args.name, dry_run=args.dry_run)
else:
list_of_examples =[os.path.splitext(os.path.basename(f))[0] for f in glob.glob(join(args.otb_root, "Examples/*/*.cxx"))]
for name in list_of_examples:
try:
run_example(args.otb_root, args.otb_data, name, dry_run=args.dry_run)
except Exception as e:
if args.keep_going:
print("Warning:", e)
else:
raise
if __name__ == "__main__":
main()
#!/usr/bin/env python3
#
# Copyright (C) 2005-2019 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 os
import os.path
from os.path import join
from collections import defaultdict
import re
import glob
from rst_utils import rst_section, RstPageHeading
def generate_examples_index(rst_dir, list_of_examples):
# Compute dictionary of tag -> (list of examples)
tag_files = defaultdict(list)
for filename in list_of_examples:
tag = filename.split("/")[1]
name, _ = os.path.splitext(filename.split("/")[2])
tag_files[tag].append(join(tag, name + ".rst"))
# Render index file and tag index files
os.makedirs(join(rst_dir, "Examples"), exist_ok=True)
index_f = open(join(rst_dir, "Examples.rst"), "w")
index_f.write(RstPageHeading("Examples", 3, ref="cpp-examples"))
for tag, examples_filenames in tag_files.items():
tag_filename = join("Examples", tag + ".rst")
index_f.write("\t" + tag_filename + "\n")
with open(join(rst_dir, tag_filename), "w") as tag_f:
tag_f.write(RstPageHeading(tag, 3))
for examples_filename in examples_filenames:
tag_f.write("\t" + examples_filename + "\n")
def indent(str):
return "\n".join([" " + line for line in str.split("\n")])
def cpp_uncomment(code):
# Strip '// '
return "\n".join([line[4:] for line in code.split("\n")])
def render_example(filename, otb_root):
"Render a cxx example to rst"
# Read the source code of the cxx example
code = open(join(otb_root, filename)).read()
# Don't show the license header to make it nicer,
# and the cookbook is already under a CC license
examples_license_header = open("templates/examples_license_header.txt").read()
code = code.replace(examples_license_header, "")
# Extract usage
# TODO handle multiple usage
rx_usage = r"\/\* Example usage:\n(\.\/[a-zA-Z]+ (.*?))\*\/"
usage_match = re.search(rx_usage, code, flags = re.MULTILINE | re.DOTALL)
if usage_match is None:
print("Warning: no usage found for example " + filename)
example_usage = ""
else:
example_usage = open("templates/example_usage.rst").read().format(indent(usage_match.group(1).strip()))
# Don't show usage in example source
code = re.sub(rx_usage, "", code, flags = re.MULTILINE | re.DOTALL)
# Make the link to the source code
link_name = os.path.basename(filename)
link_href = "https://gitlab.orfeo-toolbox.org/orfeotoolbox/otb/raw/develop/" + filename + "?inline=false"
# Read the description from the example .rst file if it exists
example_rst_file = join(otb_root, filename.replace(".cxx", ".rst"))
if os.path.isfile(example_rst_file):
rst_description = open(example_rst_file).read()
else:
rst_description = ""
# Render the template
template_example = open("templates/example.rst").read()
output_rst = template_example.format(
label="example-" + root,
heading=rst_section(name, "="),
description=rst_description,
usage=example_usage,
code=indent(code.strip()),
link_name=link_name,
link_href=link_href
)
return output_rst
if __name__ == "__main__":
parser = argparse.ArgumentParser(usage="Export examples to rst")
parser.add_argument("rst_dir", help="Directory where rst files are generated")
parser.add_argument("otb_root", help="OTB repository root")
args = parser.parse_args()
# Get list of cxx examples as relative paths from otb_root
list_of_examples = [os.path.relpath(p, start=args.otb_root) for p in glob.glob(join(args.otb_root, "Examples/*/*.cxx"))]
print("Generating rst for {} examples".format(len(list_of_examples)))
# Generate example index and tag indexes
generate_examples_index(join(args.rst_dir, "C++"), list_of_examples)
# Generate examples rst
for filename in list_of_examples:
name = os.path.basename(filename)
tag = filename.split("/")[1]
root, ext = os.path.splitext(name)
os.makedirs(join(args.rst_dir, "C++", "Examples", tag), exist_ok=True)
with open(join(args.rst_dir, "C++", "Examples", tag, root + ".rst"), "w") as output_file:
output_file.write(render_example(filename, args.otb_root))
......@@ -29,6 +29,8 @@ from otbApplication import ParameterType_Bool, ParameterType_Int, ParameterType_
from otb_warnings import application_documentation_warnings
from rst_utils import rst_section, RstPageHeading
linesep = os.linesep
def EncloseString(s):
......@@ -179,17 +181,6 @@ def render_choice(app, key):
choices=choice_entries,
)
def rst_section(text, delimiter, ref=None):
"Make a rst section title"
output = ""
if ref is not None:
output += ".. _" + ref + ":\n\n"
output += text + "\n" + delimiter * len(text) + "\n\n"
return output
def rst_parameter_value(app, key):
"Render a parameter value to rst"
......@@ -400,13 +391,6 @@ def GetApplicationTags(appname):
app = otbApplication.Registry.CreateApplication(appname)
return app.GetDocTags()
def RstPageHeading(text, maxdepth, ref=None):
output = rst_section(text, "=", ref=ref)
output += ".. toctree::" + linesep
output += "\t:maxdepth: " + maxdepth + linesep
output += linesep + linesep
return output
def GenerateRstForApplications(rst_dir):
"Generate .rst files for all applications"
......
def rst_section(text, delimiter, ref=None):
"Make a rst section title"
output = ""
if ref is not None:
output += ".. _" + ref + ":\n\n"
output += text + "\n" + delimiter * len(text) + "\n\n"
return output
def RstPageHeading(text, maxdepth, ref=None):
output = rst_section(text, "=", ref=ref)
output += ".. toctree::\n"
output += "\t:maxdepth: {}\n\n\n".format(maxdepth)
return output
.. _cpp-api:
C++ API
=======
.. toctree::
C++/Examples.rst
......@@ -13,5 +13,6 @@ Table of Contents
AdvancedUse
Recipes
Applications
C++
FAQ
Contributors
.. _{label}:
{heading}
{description}
{usage}
Example source code (`{link_name} <{link_href}>`_):
.. code-block:: cpp
{code}
Example usage:
.. code-block:: bash
{}
/*
* Copyright (C) 2005-2019 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.
*/
......@@ -19,88 +19,57 @@
*/
/* Example usage:
./ApplicationExample Input/QB_Suburb.png Output/ApplicationExample.png
*/
// Software Guide : BeginCommandLineArgs
// INPUTS: {QB_Suburb.png}
// OUTPUTS: {ApplicationExample.png}
// Software Guide : EndCommandLineArgs
// Software Guide : BeginLatex
// This example illustrates the creation of an application.
// A new application is a class, which derives from \subdoxygen{otb}{Wrapper}{Application} class.
// We start by including the needed header files.
//
// Software Guide : EndLatex
// Software Guide : BeginCodeSnippet
#include "otbWrapperApplication.h"
#include "otbWrapperApplicationFactory.h"
// Software Guide : EndCodeSnippet
namespace otb
{
// Software Guide : BeginLatex
// Application class is defined in Wrapper namespace.
//
// Software Guide : EndLatex
// Software Guide : BeginCodeSnippet
namespace Wrapper
{
// Software Guide : EndCodeSnippet
// Software Guide : BeginLatex
//
// ExampleApplication class is derived from Application class.
//
// Software Guide : EndLatex
// Software Guide : BeginCodeSnippet
class ApplicationExample : public Application
// Software Guide : EndCodeSnippet
{
public:
// Software Guide : BeginLatex
// Class declaration is followed by \code{ITK} public types for the class, the superclass and
// smart pointers.
// Software Guide : EndLatex
// Software Guide : BeginCodeSnippet
typedef ApplicationExample Self;
typedef Application Superclass;
typedef itk::SmartPointer<Self> Pointer;
typedef ApplicationExample Self;
typedef Application Superclass;
typedef itk::SmartPointer<Self> Pointer;
typedef itk::SmartPointer<const Self> ConstPointer;
// Software Guide : EndCodeSnippet
// Software Guide : BeginLatex
// Following macros are necessary to respect ITK object factory mechanisms. Please report
// to \ref{sec:FilterConventions} for additional information.
// Software Guide : EndLatex
// Software Guide : BeginCodeSnippet
itkNewMacro(Self);
itkTypeMacro(ExampleApplication, otb::Application);
// Software Guide : EndCodeSnippet
private:
// Software Guide : BeginLatex
// \doxygen{otb}{Application} relies on three main private methods: \code{DoInit()}, \code{DoUpdate()}, and \code{DoExecute()}.
// Section \ref{sec:appArchitecture} gives a description a these methods.
// Software Guide : EndLatex
// Software Guide : BeginLatex
// \code{DoInit()} method contains class information and description, parameter set up, and example values.
// Software Guide : EndLatex
void DoInit() override
{
// Software Guide : BeginLatex
// Application name and description are set using following methods :
// \begin{description}
// \item[\code{SetName()}] Name of the application.
......@@ -111,44 +80,36 @@ private:
// \item[\code{SetDocAuthors()}] Set the application Authors. Author List. Format : "John Doe, Winnie the Pooh" \dots
// \item[\code{SetDocSeeAlso()}] If the application is related to one another, it can be mentioned.
// \end{description}
// Software Guide : EndLatex
// Software Guide : BeginCodeSnippet
SetName("Example");
SetDescription("This application opens an image and save it. "
"Pay attention, it includes Latex snippets in order to generate "
"software guide documentation");
SetDescription(
"This application opens an image and save it. "
"Pay attention, it includes Latex snippets in order to generate "
"software guide documentation");
SetDocName("Example");
SetDocLongDescription("The purpose of this application is "
"to present parameters types,"
" and Application class framework. "
"It is used to generate Software guide documentation"
" for Application chapter example.");
SetDocLongDescription(
"The purpose of this application is "
"to present parameters types,"
" and Application class framework. "
"It is used to generate Software guide documentation"
" for Application chapter example.");
SetDocLimitations("None");
SetDocAuthors("OTB-Team");
SetDocSeeAlso(" ");
// Software Guide : EndCodeSnippet
// Software Guide : BeginLatex
// \code{AddDocTag()} method categorize the application using relevant tags.
// The header file \code{otbWrapperTags.h} in OTB sources contains some predefined tags defined in \code{Tags} namespace.
// Software Guide : EndLatex
// Software Guide : BeginCodeSnippet
AddDocTag(Tags::Analysis);
AddDocTag("Test");
// Software Guide : EndCodeSnippet
// Software Guide : BeginLatex
// Application parameters declaration is done using \code{AddParameter()} method.
// \code{AddParameter()} requires the input parameter type
// (ParameterType\_InputImage, ParameterType\_Int, ParameterType\_Float), its name and description.
// \subdoxygen{otb}{Wrapper}{Application} class contains methods to set parameters characteristics.
// Software Guide : EndLatex
// Software Guide : BeginCodeSnippet
AddParameter(ParameterType_InputImage, "in", "Input Image");
AddParameter(ParameterType_OutputImage, "out", "Output Image");
......@@ -181,13 +142,11 @@ private:
AddChoice("inchoice.choice1", "Choice 1");
AddChoice("inchoice.choice2", "Choice 2");
AddChoice("inchoice.choice3", "Choice 3");
AddParameter(ParameterType_Float, "inchoice.choice1.floatchoice1"
, "Example of float parameter for choice1");
AddParameter(ParameterType_Float, "inchoice.choice1.floatchoice1", "Example of float parameter for choice1");
SetDefaultParameterFloat("inchoice.choice1.floatchoice1", 0.125);
AddParameter(ParameterType_Float, "inchoice.choice3.floatchoice3"
, "Example of float parameter for choice3");
AddParameter(ParameterType_Float, "inchoice.choice3.floatchoice3", "Example of float parameter for choice3");
SetDefaultParameterFloat("inchoice.choice3.floatchoice3", 5.0);
AddParameter(ParameterType_Group, "ingroup", "Input group");
......@@ -195,14 +154,12 @@ private:
AddParameter(ParameterType_Int, "ingroup.valint", "Example of integer parameter for group");
MandatoryOff("ingroup.valint");
AddParameter(ParameterType_Group, "ingroup.images", "Input Images group");
AddParameter(ParameterType_InputImage, "ingroup.images.inputimage"
, "Input Image");
AddParameter(ParameterType_InputImage, "ingroup.images.inputimage", "Input Image");
MandatoryOff("ingroup.images.inputimage");
AddParameter(ParameterType_Group, "outgroup", "Output group");
MandatoryOff("outgroup");
AddParameter(ParameterType_OutputImage, "outgroup.outputimage"
, "Output Image");
AddParameter(ParameterType_OutputImage, "outgroup.outputimage", "Output Image");
MandatoryOff("outgroup.outputimage");
AddParameter(ParameterType_InputImageList, "il", "Input image list");
MandatoryOff("il");
......@@ -220,58 +177,42 @@ private:
AddParameter(ParameterType_ComplexOutputImage, "cout", "Output complex image");
MandatoryOff("cin");
MandatoryOff("cout");
// Software Guide : EndCodeSnippet
// Software Guide : BeginLatex
// An example of command-line is automatically generated. Method \code{SetDocExampleParameterValue()} is
// used to set parameters. Dataset should be located in \code{OTB-Data/Examples} directory.
// Software Guide : EndLatex
// Software Guide : BeginCodeSnippet
SetDocExampleParameterValue("boolean", "true");
SetDocExampleParameterValue("in", "QB_Suburb.png");
SetDocExampleParameterValue("out", "Application_Example.png");
// Software Guide : EndCodeSnippet
}
// Software Guide : BeginLatex
// \code{DoUpdateParameters()} is called as soon as a parameter value change. Section \ref{sec:appDoUpdateParameters}
// gives a complete description of this method.
// Software Guide : EndLatex
// Software Guide :BeginCodeSnippet
void DoUpdateParameters() override
{
}
// Software Guide : EndCodeSnippet
// Software Guide : BeginLatex
// \code{DoExecute()} contains the application core. Section \ref{sec:appDoExecute}
// gives a complete description of this method.
// Software Guide : EndLatex
// Software Guide :BeginCodeSnippet
void DoExecute() override
{
FloatVectorImageType::Pointer inImage = GetParameterImage("in");
int paramInt = GetParameterInt("param2");
otbAppLogDEBUG( << paramInt << std::endl );
otbAppLogDEBUG(<< paramInt << std::endl);
int paramFloat = GetParameterFloat("param3");
otbAppLogINFO( << paramFloat );
otbAppLogINFO(<< paramFloat);
SetParameterOutputImage("out", inImage);
}
// Software Guide :EndCodeSnippet
};
}
}
} // namespace Wrapper
} // namespace otb
// Software Guide : BeginLatex
// Finally \code{OTB\_APPLICATION\_EXPORT} is called:
// Software Guide : EndLatex
// Software Guide :BeginCodeSnippet
OTB_APPLICATION_EXPORT(otb::Wrapper::ApplicationExample)
// Software Guide :EndCodeSnippet
......@@ -19,24 +19,21 @@
*/
/* Example usage:
./DEMToRainbowExample Output/DEMToRainbowImageGenerator.png 6.5 45.5 500 500 0.002 -0.002 Input/DEM_srtm
*/
// Software Guide : BeginCommandLineArgs
// OUTPUTS: {DEMToRainbowImageGenerator.png}
// 6.5 45.5 500 500 0.002 -0.002 ${OTB_DATA_ROOT}/Input/DEM_srtm
// Software Guide : EndCommandLineArgs