diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6e9e0a2ba1819a98a2feea1ad2d66bb40a64f0e0..a03ebb45cdc8615e780f394aebc6b9f6f8c6527d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -455,6 +455,10 @@ add_subdirectory(Utilities/Doxygen)
 
 add_subdirectory(Utilities/Completion)
 
+if (BUILD_DOCUMENTATION)
+    add_subdirectory(Documentation/Cookbook)
+endif()
+
 #----------------------------------------------------------------------------
 # Provide a target to generate the SuperBuild archive (only for Unix)
 if(UNIX)
diff --git a/Documentation/Cookbook/CMake/RunApplicationsRstGenerator.sh.cmake.in b/Documentation/Cookbook/CMake/RunApplicationsRstGenerator.sh.cmake.in
index 3c3a810ca20cd0987143828e74ee9e2d9d1771be..ff3c204aa76e02eedb85f763d3a476f749d3c16c 100644
--- a/Documentation/Cookbook/CMake/RunApplicationsRstGenerator.sh.cmake.in
+++ b/Documentation/Cookbook/CMake/RunApplicationsRstGenerator.sh.cmake.in
@@ -1,6 +1,8 @@
 #!/bin/sh
 
-export LD_LIBRARY_PATH=@OTB_INSTALL_PREFIX@/lib:$LD_LIBRARY_PATH
-export PYTHONPATH=@OTB_PYTHONPATH@:$PYTHONPATH
-export OTB_APPLICATION_PATH=@OTB_APPLICATION_PATH@
-@PYTHON_EXECUTABLE@ @CMAKE_SOURCE_DIR@/Scripts/otbGenerateWrappersRstDoc.py -o "$1"
+#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=@PYTHONPATH_COOKBOOK@:$PYTHONPATH
+export OTB_APPLICATION_PATH=@CMAKE_BINARY_DIR@/lib/otb/applications
+
+python3 @CMAKE_CURRENT_SOURCE_DIR@/Scripts/otbGenerateWrappersRstDoc.py -o "$1"
diff --git a/Documentation/Cookbook/CMakeLists.txt b/Documentation/Cookbook/CMakeLists.txt
index 52cd4f4f0174e3cb2a05131a97624d66170ac3cc..766102aab683e1727b8443e476e12b5f15eef865 100644
--- a/Documentation/Cookbook/CMakeLists.txt
+++ b/Documentation/Cookbook/CMakeLists.txt
@@ -1,62 +1,38 @@
-set(OUT_OF_SOURCE_BUILD FALSE)
-
-if(NOT PROJECT_NAME)
-  cmake_minimum_required(VERSION 3.0)
-  project(OTBCookBook)
-  set(OUT_OF_SOURCE_BUILD TRUE)
-  option(BUILD_TESTING "Build the testing tree." ON)
-endif()
-
-if(BUILD_TESTING)
-  enable_testing()
-  include(CTest)
-endif()
-
-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)")
-
-#find OTB
-find_package(OTB REQUIRED)
-
-if( OTB_FOUND )
-  include(${OTB_USE_FILE})
-  message(STATUS "Found OTB: ${OTB_DIR} (found version \"${OTB_VERSION}\")")
-else()
-  message(FATAL_ERROR "OTB not found. Please set OTB_DIR")
+#
+# Copyright (C) 2005-2017 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.
+#
+
+#
+# Build the cookbook
+#
+
+if (NOT UNIX)
+  message (STATUS "Not on Unix: skipping Cookbook build.")
   return()
 endif()
 
-get_filename_component(OTB_INSTALL_PREFIX ${OTB_MODULES_DIR} PATH)
-get_filename_component(OTB_INSTALL_PREFIX ${OTB_INSTALL_PREFIX} PATH)
-get_filename_component(OTB_INSTALL_PREFIX ${OTB_INSTALL_PREFIX} PATH)
-get_filename_component(OTB_INSTALL_PREFIX ${OTB_INSTALL_PREFIX} PATH)
-
-message(STATUS "OTB_PYTHONPATH        = '${OTB_PYTHONPATH}'")
-message(STATUS "OTB_APPLICATION_PATH  = '${OTB_APPLICATION_PATH}'")
-message(STATUS "OTB_INSTALL_PREFIX    = '${OTB_INSTALL_PREFIX}'")
-
-if(NOT OTB_PYTHONPATH)
-  message(FATAL_ERROR "OTB_PYTHONPATH empty")
-endif()
-
-if(NOT OTB_APPLICATION_PATH)
-  message(FATAL_ERROR "OTB_APPLICATION_PATH empty")
-endif()
-
-if(NOT OTB_INSTALL_PREFIX)
-  message(FATAL_ERROR "OTB_INSTALL_PREFIX empty")
-endif()
-
-
-macro(remove_and_make_directories)
-  foreach(dir in ${ARGV})
-    execute_process(COMMAND ${CMAKE_COMMAND} -E remove_directory ${dir})
-    execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${dir})
-  endforeach()
-endmacro()
+message(STATUS "")
+message(STATUS "Configuring Cookbook...")
 
-#find Python
-find_package(PythonInterp REQUIRED)
+# here we could add
+# find_package (Python3 COMPONENTS Interpreter)
+# but it requires cmake 3.12+
 
 find_program(SH_INTERP sh)
 mark_as_advanced(SH_INTERP)
@@ -73,18 +49,50 @@ mark_as_advanced(SPHINX_BUILD)
 find_program(PDFLATEX_COMMAND NAMES pdflatex )
 mark_as_advanced(PDFLATEX_COMMAND)
 
-foreach(cmd LATEX_COMMAND TAR_COMMAND PDFLATEX_COMMAND SPHINX_BUILD SH_INTERP)
+# Check that we found everything we need
+foreach(cmd
+    LATEX_COMMAND
+    TAR_COMMAND
+    PDFLATEX_COMMAND
+    SPHINX_BUILD
+    SH_INTERP)
   if(NOT ${cmd})
-    message(FATAL_ERROR "${cmd} not set. Cannot continue")
+    message(FATAL_ERROR "Error while configuring Cookbook, ${cmd} not set. Cannot continue")
   endif()
 endforeach()
 
-set(RST_SOURCE_DIR  ${CMAKE_CURRENT_SOURCE_DIR}/rst)
+# We need to set PYTHONPATH for the script otbGenerateWrappersRstDoc.py, depending on how the Python3 module was built
+if (OTB_WRAP_PYTHON3)
+    set(PYTHONPATH_COOKBOOK "${CMAKE_BINARY_DIR}/lib/otb/python3")
+elseif (OTB_WRAP_PYTHON)
+    # Cookbook only supports Python3
+    # But OTB_WRAP_PYTHON can wrap both python2 and python3
+    if (${PYTHONLIBS_VERSION_STRING} STRGREATER "3.0.0")
+        set(PYTHONPATH_COOKBOOK "${CMAKE_BINARY_DIR}/lib/otb/python")
+    else()
+        message(FATAL_ERROR "Must wrap OTB with python lib 3+ to build the cookbook, but found version ${PYTHONLIBS_VERSION_STRING}")
+    endif()
+endif()
 
+set(RST_SOURCE_DIR  ${CMAKE_CURRENT_SOURCE_DIR}/rst)
 set(RST_BINARY_DIR  ${CMAKE_CURRENT_BINARY_DIR}/rst)
 set(LATEX_DIR       ${CMAKE_CURRENT_BINARY_DIR}/latex)
 set(HTML_DIR        ${CMAKE_CURRENT_BINARY_DIR}/html)
 
+# Print summary of Cookbook configuration
+message(STATUS "RST_SOURCE_DIR = ${RST_SOURCE_DIR}")
+message(STATUS "RST_BINARY_DIR = ${RST_BINARY_DIR}")
+message(STATUS "LATEX_DIR = ${LATEX_DIR}")
+message(STATUS "HTML_DIR = ${HTML_DIR}")
+message(STATUS "PYTHONPATH_COOKBOOK = ${PYTHONPATH_COOKBOOK}")
+
+# Clean any existing build
+macro(remove_and_make_directories)
+  foreach(dir in ${ARGV})
+    execute_process(COMMAND ${CMAKE_COMMAND} -E remove_directory ${dir})
+    execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${dir})
+  endforeach()
+endmacro()
 
 remove_and_make_directories(
   ${HTML_DIR}
@@ -107,23 +115,28 @@ file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/Art DESTINATION  ${RST_BINARY_DIR})
 
 set(SPHINX_CONF_DIR ${CMAKE_CURRENT_BINARY_DIR})
 
-configure_file(${RST_SOURCE_DIR}/conf.py.in ${SPHINX_CONF_DIR}/conf.py @ONLY)
+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)")
 
-#configure_file(${RST_SOURCE_DIR}/Makefile.in ${RST_GENERATED_SOURCE_DIR}/Makefile.sphinx @ONLY)
-# Internal variables.
-# PAPEROPT_a4     = -D latex_paper_size=a4
-# PAPEROPT_letter = -D latex_paper_size=letter
-# ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-# # the i18n builder cannot share the environment and doctrees with the others
-# I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+configure_file(${RST_SOURCE_DIR}/conf.py.in ${SPHINX_CONF_DIR}/conf.py @ONLY)
 
 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"
+  DEPENDS OTBSWIGWrapper-all
   )
 
+# Add all applications as dependencies to rst generation
+set(app_names ${OTB_APPLICATIONS_NAME_LIST})
+list(REMOVE_ITEM app_names "TestApplication")
+list(REMOVE_ITEM app_names "ApplicationExample")
+list(REMOVE_DUPLICATES app_names)
+foreach(app_name ${app_names})
+  add_dependencies(generate_otbapps_rst otbapp_${app_name})
+endforeach()
+
 add_custom_target(CookBookHTML
   COMMAND ${SPHINX_BUILD}
   -b html
diff --git a/Documentation/Cookbook/Scripts/otbGenerateWrappersRstDoc.py b/Documentation/Cookbook/Scripts/otbGenerateWrappersRstDoc.py
index 2d894a7ba97e611a58a78d1102bbd437ce596f2f..7bb739bed0feff46340b2ba82c01a19525465858 100755
--- a/Documentation/Cookbook/Scripts/otbGenerateWrappersRstDoc.py
+++ b/Documentation/Cookbook/Scripts/otbGenerateWrappersRstDoc.py
@@ -1,4 +1,24 @@
-#!/usr/bin/python
+#!/usr/bin/env python3
+#
+# Copyright (C) 2005-2017 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 otbApplication
 import os
 import sys
@@ -59,7 +79,7 @@ def GetPixelType(value):
     # look for type
     foundcode = -1
     foundname = ""
-    for ptypename, ptypecode in pixeltypes.iteritems():
+    for ptypename, ptypecode in pixeltypes.items():
         if value.endswith(ptypename):
             foundcode = ptypecode
             foundname = ptypename
@@ -169,14 +189,14 @@ def FindLengthOfLargestColumnText(app,paramlist):
 
 def RstTableHeaderLine(strlist, listlen, delimiter):
     line = "+"
-    for i in xrange(len(strlist)):
+    for i in range(len(strlist)):
         line += delimiter * listlen[i] + '+'
     line += linesep
     return line
 
 def RstTableHeading(strlist, listlen):
     heading = RstTableHeaderLine(strlist, listlen, '-')
-    for i in xrange(len(strlist)):
+    for i in range(len(strlist)):
          spaces = ' ' * ((listlen[i] - len(strlist[i])) )
          heading += '|' + strlist[i] +  spaces
     heading += '|' + linesep
@@ -192,7 +212,7 @@ def GenerateParametersTable(app,paramlist):
     colLength = FindLengthOfLargestColumnText(app, paramlist)
     output = linesep + ".. [#] Table: Parameters table for " + ConvertString(app.GetDocName()) + "." + linesep + linesep
     headerlist = ["Parameter Key", "Parameter Name", "Parameter Type"]
-    for i in xrange(len(headerlist)):
+    for i in range(len(headerlist)):
         colLength[i] = len(headerlist[i]) if colLength[i] < len(headerlist[i]) else colLength[i]
     output += RstTableHeading(headerlist, colLength)
     for param in paramlist:
@@ -449,7 +469,7 @@ def ApplicationToRst(appname):
     try:
         app = otbApplication.Registry.CreateApplication(appname)
     except e:
-        print e
+        print(e)
     # TODO: remove this when bug 440 is fixed
     app.Init()
     output += RstHeading(app.GetName() + ' - ' + app.GetDocName(), '^')
@@ -519,18 +539,19 @@ def GenerateRstForApplications():
     allApps = None
     try:
         allApps = otbApplication.Registry.GetAvailableApplications( )
+        print(allApps)
     except:
-        print 'error in otbApplication.Registry.GetAvailableApplications()'
+        print('error in otbApplication.Registry.GetAvailableApplications()')
         sys.exit(1)
 
     if not allApps:
-	print 'No OTB applications available. Please check OTB_APPLICATION_PATH env variable'
-	sys.exit(1)
+        print('No OTB applications available. Please check OTB_APPLICATION_PATH env variable')
+        sys.exit(1)
 
     writtenTags = []
     appNames = [app for app in allApps if app not in blackList]
 
-    print "All apps: %s" % (appNames,)
+    print("All apps: %s" % (appNames,))
 
     appIndexFile = open(RST_DIR + '/Applications.rst', 'w')
     appIndexFile.write(RstPageHeading("Applications Reference Documentation", "2", ref="apprefdoc"))
@@ -538,7 +559,7 @@ def GenerateRstForApplications():
         tags = GetApplicationTags(appName)
 
         if not tags:
-            print "No tags for application: "  +  appName
+            print("No tags for application: "  +  appName)
             sys.exit(1)
 
         tag = tags[0]
@@ -548,7 +569,7 @@ def GenerateRstForApplications():
             tag_ = tag.replace(' ', '_')
 
         if not tag_:
-            print 'empty tag found for ' + appName
+            print('empty tag found for ' + appName)
 
         if not tag_ in writtenTags:
             appIndexFile.write('\tApplications/' + tag_ + '.rst' + linesep)
@@ -565,7 +586,7 @@ def GenerateRstForApplications():
             tagFile.write("\tapp_" + appName + linesep)
             tagFile.close()
 
-        print "Generating " + appName + ".rst" +  " on tag " + tag_
+        print("Generating " + appName + ".rst" +  " on tag " + tag_)
         appFile = open(RST_DIR + '/Applications/app_'  + appName + '.rst', 'w')
         out = ApplicationToRst(appName)
         appFile.write(out)
@@ -574,17 +595,16 @@ def GenerateRstForApplications():
     return out
 
 
-# Start parsing options
-parser = OptionParser(usage="Export application(s) to rst file.")
-parser.add_option("-a",dest="appname",help="Generate rst only for this application (eg: OrthoRectification)")
-parser.add_option("-m",dest="module",help="Generate rst only for this module (eg: Image Manipulation)")
-parser.add_option("-o",dest="rstdir",help="directory where rst files are generated")
-(options, args) = parser.parse_args()
+if __name__ == "__main__":
+    parser = OptionParser(usage="Export application(s) to rst file.")
+    parser.add_option("-a",dest="appname",help="Generate rst only for this application (eg: OrthoRectification)")
+    parser.add_option("-m",dest="module",help="Generate rst only for this module (eg: Image Manipulation)")
+    parser.add_option("-o",dest="rstdir",help="directory where rst files are generated")
+    (options, args) = parser.parse_args()
 
-RST_DIR = options.rstdir
+    RST_DIR = options.rstdir
 
-if not options.appname is None:
-    out = ApplicationToRst(options.appname)
-    #print out
-else:
-    GenerateRstForApplications()
+    if not options.appname is None:
+        out = ApplicationToRst(options.appname)
+    else:
+        GenerateRstForApplications()
diff --git a/Documentation/Cookbook/rst/conf.py.in b/Documentation/Cookbook/rst/conf.py.in
index 1e4442b07f398766735fa3898e75692c5b9d52d4..c9e101cb7ab2865e843b0479c3be4ae0d4d6e501 100644
--- a/Documentation/Cookbook/rst/conf.py.in
+++ b/Documentation/Cookbook/rst/conf.py.in
@@ -14,12 +14,7 @@
 
 import sys
 import os
-HAVE_RTD_THEME=False
-try:
-    import sphinx_rtd_theme
-    HAVE_RTD_THEME=True
-except:
-    pass
+import sphinx_rtd_theme
 
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
@@ -111,11 +106,10 @@ pygments_style = 'sphinx'
 
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
-if HAVE_RTD_THEME:
-    html_theme = 'sphinx_rtd_theme'
-    # Add any paths that contain custom themes here, relative to this directory.
-    html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
-    
+html_theme = 'sphinx_rtd_theme'
+# Add any paths that contain custom themes here, relative to this directory.
+html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
 # documentation.