diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 596e7ed1b2e611de5b21ef6c5f4598f17ab8fe19..4c7c2deae3ee6a73758a2b7634fb121dcf4075e1 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -207,6 +207,17 @@ imports:
   script:
     - python -m pytest --junitxml=$ARTIFACT_TEST_DIR/report_imports.xml $OTBTF_SRC/test/imports_test.py
 
+numpy_gdal_otb:
+  extends: .applications_test_base
+  script:
+    - python -m pytest --junitxml=$ARTIFACT_TEST_DIR/report_numpy.xml $OTBTF_SRC/test/numpy_test.py
+
+rio:
+  extends: .applications_test_base
+  script:
+    - sudo pip install rasterio
+    - python -m pytest --junitxml=$ARTIFACT_TEST_DIR/report_rio.xml $OTBTF_SRC/test/rio_test.py
+
 deploy_cpu-dev-testing:
   stage: Update dev image
   extends: .docker_build_base
diff --git a/Dockerfile b/Dockerfile
index f8b82f7999e159f4ee5ec3eb1fba88a0eb46a449..a612da257648a30ca2cec7ed44578e950d1b1607 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -27,8 +27,7 @@ RUN ln -s /usr/bin/python3 /usr/local/bin/python && ln -s /usr/bin/pip3 /usr/loc
 RUN pip install --no-cache-dir pip --upgrade
 # NumPy version is conflicting with system's gdal dep and may require venv
 ARG NUMPY_SPEC="==1.22.*"
-ARG PROTO_SPEC="==3.20.*"
-RUN pip install --no-cache-dir -U wheel mock six future tqdm deprecated "numpy$NUMPY_SPEC" "protobuf$PROTO_SPEC" packaging requests \
+RUN pip install --no-cache-dir -U wheel mock six future tqdm deprecated "numpy$NUMPY_SPEC" packaging requests \
  && pip install --no-cache-dir --no-deps keras_applications keras_preprocessing
 
 # ----------------------------------------------------------------------------
diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt
index 7cf3b01d4bb68d8d9e26ca9d4f7c75a50fd1da06..e0ac4a4eaebbea499e0464edacc49489c1e3ebdd 100644
--- a/RELEASE_NOTES.txt
+++ b/RELEASE_NOTES.txt
@@ -12,6 +12,7 @@ Version 4.0.0alpha (4 apr 2023)
 * Tensorflow version: 2.12.0
 * Fixed Tensorflow error "Cannot register 2 metrics with the same name" + new test
 * Faster CI build thanks to bazel remote cache
+* /home/otbuser/.local/bin added to user path
 
 Version 3.4.0 (22 mar 2023)
 ----------------------------------------------------------------
diff --git a/doc/api_distributed.md b/doc/api_distributed.md
index 1478022c7ea7ce341682a8af3103452d9428bf00..077704e01c3fd70f699d34d9045c517799200292 100644
--- a/doc/api_distributed.md
+++ b/doc/api_distributed.md
@@ -24,7 +24,7 @@ on all GPUs.
 ## Python code
 
 We can start from the codebase of the fully convolutional model example 
-described in the OTBTF [Python API tutorial](#api_tutorial.html).
+described in the OTBTF [Python API tutorial](api_tutorial.html).
 
 ### Dataset
 
diff --git a/doc/deprecated.md b/doc/deprecated.md
index c0477962cb43ac9c43e08c336c18beeb6a6c7623..3f76c1dbd1d62e3313da1e85e4d3b8e8ecef8bdf 100644
--- a/doc/deprecated.md
+++ b/doc/deprecated.md
@@ -35,4 +35,4 @@ training, etc. is done using the so-called `tensorflow.Strategy`
 
 !!! Note
 
-    Read our [tutorial](#api_tutorial.html) to know more on working with Keras!
\ No newline at end of file
+    Read our [tutorial](api_tutorial.html) to know more on working with Keras!
\ No newline at end of file
diff --git a/doc/docker_build.md b/doc/docker_build.md
index c7a3c1e97e5030760d79c2b1429578b12d32f4cb..debc1ea8d5ae2994949b46a61e70a5ed14290ce3 100644
--- a/doc/docker_build.md
+++ b/doc/docker_build.md
@@ -49,7 +49,7 @@ be a different branch of OTB, bazel cache will help you to rebuild everything
 except TF, even if the docker cache was purged (after `docker 
 [system|builder] prune`).
 In order to recycle the cache, bazel config and TF git tag should be exactly 
-the same, any change in [build-env-tf.sh](build-env-tf.sh) and `--build-arg` 
+the same, any change in *tools/docker/build-env-tf.sh* and `--build-arg` 
 (if related to bazel env, cuda, mkl, xla...) may result in a fresh new build.
 
 Start a cache daemon - here with max 20GB but 10GB should be enough to save 2 
diff --git a/doc/docker_use.md b/doc/docker_use.md
index 47749325def9c1ab1ae2d162bcb6abb236909fe6..ebbab5a492f122be2e690c1497544c31a2e4d708 100644
--- a/doc/docker_use.md
+++ b/doc/docker_use.md
@@ -104,7 +104,7 @@ Troubleshooting:
 
 If you want to use optimization flags, change GPUs compute capability, etc. 
 you can build your own docker image using the provided dockerfile. 
-See the [docker build documentation](#docker_build.html).
+See the [docker build documentation](docker_build.html).
 
 ## Older images
 
diff --git a/doc/index.md b/doc/index.md
index 4bb28c76017bbf1abd6a3c9fddf72b554d848855..f96bb5cb09e46b7302f3d3f463b8f69175707fc2 100644
--- a/doc/index.md
+++ b/doc/index.md
@@ -16,7 +16,7 @@
 
 
 This remote module of the [Orfeo ToolBox](https://www.orfeo-toolbox.org) 
-provides a generic, multi-purpose deep learning framework, targeting remote 
+provides a generic, multipurpose deep learning framework, targeting remote 
 sensing images processing. It contains a set of new process objects for OTB 
 that internally invoke [Tensorflow](https://www.tensorflow.org/), and new [OTB 
 applications](#otb-applications) to perform deep learning with real-world 
@@ -50,7 +50,7 @@ set of _patches images_ and delivering samples as `tf.dataset` that can be
 used in your favorite TensorFlow pipelines, or convert your patches into 
 TFRecords. The `otbtf.TFRecords` enables you train networks from TFRecords 
 files, which is quite suited for distributed training. Read more in the 
-[tutorial for keras](otbtf/examples/tensorflow_v2x/fcnn/README.md).
+[tutorial for keras](api_tutorial.html).
 
 ## Examples
 
diff --git a/test/imports_test.py b/test/imports_test.py
index a89ab3dc2566d40c95ef05cc109ce8a1939d940a..e745ed5baacd0a8a7a141891e3fb497ad537b81c 100644
--- a/test/imports_test.py
+++ b/test/imports_test.py
@@ -19,5 +19,16 @@ class ImportsTest(unittest.TestCase):
         self.assertTrue(tensorflow.__version__)
 
 
+    def test_import_all(self):
+        import otbApplication
+        self.assertTrue(otbApplication.Registry_GetAvailableApplications())
+        import tensorflow
+        self.assertTrue(tensorflow.__version__)
+        from osgeo import gdal
+        self.assertTrue(gdal.__version__)
+        import numpy
+        self.assertTrue(numpy.__version__)
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/test/numpy_test.py b/test/numpy_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..55f0272ce4b2570c4ff0642ded73bf75baaa0d58
--- /dev/null
+++ b/test/numpy_test.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+import pytest
+import unittest
+import otbApplication
+from osgeo import gdal
+from test_utils import resolve_paths
+
+FILENAME = resolve_paths('$DATADIR/fake_spot6.jp2')
+
+class NumpyTest(unittest.TestCase):
+
+    def test_gdal_as_nparr(self):
+        gdal_ds = gdal.Open(FILENAME)
+        band = gdal_ds.GetRasterBand(1)
+        arr = band.ReadAsArray()
+        self.assertTrue(arr.shape)
+
+
+    def test_otb_as_nparr(self):
+        app = otbApplication.Registry.CreateApplication('ExtractROI')
+        app.SetParameterString("in", FILENAME)
+        app.Execute()
+        arr = app.GetVectorImageAsNumpyArray('out')
+        self.assertTrue(arr.shape)
+
+    def test_gdal_and_otb_np(self):
+        gdal_ds = gdal.Open(FILENAME)
+        band = gdal_ds.GetRasterBand(1)
+        arr = band.ReadAsArray()
+        app = otbApplication.Registry.CreateApplication('ExtractROI')
+        app.SetImageFromNumpyArray('in', arr)
+        app.SetParameterInt('startx', 0)
+        app.SetParameterInt('starty', 0)
+        app.SetParameterInt('sizex', 10)
+        app.SetParameterInt('sizey', 10)
+        app.Execute()
+        arr2 = app.GetVectorImageAsNumpyArray('out')
+        self.assertTrue(arr2.shape)
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/test/rio_test.py b/test/rio_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..c6a0f2f1b4bd0f49ba68266f20005433987919ab
--- /dev/null
+++ b/test/rio_test.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+import pytest
+import unittest
+import rasterio
+import rasterio.features
+import rasterio.warp
+from test_utils import resolve_paths
+
+FILENAME = resolve_paths('$DATADIR/fake_spot6.jp2')
+
+class NumpyTest(unittest.TestCase):
+
+    def test_rio_read_md(self):
+        with rasterio.open(FILENAME) as dataset:
+            # Read the dataset's valid data mask as a ndarray.
+            mask = dataset.dataset_mask()
+
+            # Extract feature shapes and values from the array.
+            for geom, val in rasterio.features.shapes(
+                    mask, transform=dataset.transform
+            ):
+                # Transform shapes from the dataset's own coordinate
+                # reference system to CRS84 (EPSG:4326).
+                geom = rasterio.warp.transform_geom(
+                    dataset.crs, 'EPSG:4326', geom, precision=6
+                )
+                self.assertTrue(geom)
+
+
+    def test_import_all(self):
+        import otbApplication
+        self.assertTrue(otbApplication.Registry_GetAvailableApplications())
+        import tensorflow
+        self.assertTrue(tensorflow.__version__)
+        from osgeo import gdal
+        self.assertTrue(gdal.__version__)
+        import numpy
+        self.assertTrue(numpy.__version__)
+        self.test_rio_read_md()
+        import otbtf
+        self.assertTrue(otbtf.__version__)
+
+
+if __name__ == '__main__':
+    unittest.main()