From e8c845d9403cce71ae994a8caa91110ba79c666e Mon Sep 17 00:00:00 2001
From: "Olivier J.N. Bertrand" <olivier.bertrand@uni-bielefeld.de>
Date: Sat, 16 Dec 2017 16:12:17 +0100
Subject: [PATCH] Split processing into place and motion codes

---
 navipy/database/__init__.py      |  69 ++++++++-------
 navipy/moving/test_maths.py      |  22 ++++-
 navipy/processing/pcode.py       | 141 +------------------------------
 navipy/processing/tools.py       |  78 +++++++++++++++++
 navipy/rendering/bee_sampling.py |   3 +-
 5 files changed, 136 insertions(+), 177 deletions(-)

diff --git a/navipy/database/__init__.py b/navipy/database/__init__.py
index 63b107c..d5074ac 100644
--- a/navipy/database/__init__.py
+++ b/navipy/database/__init__.py
@@ -116,10 +116,8 @@ It creates three sql table on initialisation.
 
     def __init__(self, filename, channels=['R', 'G', 'B', 'D']):
         """Initialisation of the database """
-        if not isinstance(filename, str):
-            raise TypeError('filename should be a string')
-        if not isinstance(channels, list):
-            raise TypeError('nb_channel should be a list')
+        assert isinstance(filename, str), 'filename should be a string'
+        assert isinstance(channels, list), 'nb_channel should be an integer'
         self.filename = filename
         self.channels = channels
         self.normalisation_columns = list()
@@ -145,13 +143,14 @@ It creates three sql table on initialisation.
         if os.path.exists(filename):
             # Check database
             self.db = sqlite3.connect(
-                filename, detect_types=sqlite3.PARSE_DECLTYPES)
+                'file:' + filename + '?cache=shared', uri=True,
+                detect_types=sqlite3.PARSE_DECLTYPES,
+                timeout=10)
             self.db_cursor = self.db.cursor()
             for tablename, _ in self.tablecolumns.items():
-                if not self.table_exist(tablename):
-                    raise Exception('{} does not contain\
-                                   a table named {}'.format(
-                                   filename, tablename))
+                assert self.table_exist(tablename),\
+                    '{} does not contain a table named {}'.format(
+                        filename, tablename)
         elif self.create():
             # Create database
             self.db = sqlite3.connect(
@@ -176,8 +175,7 @@ It creates three sql table on initialisation.
         self.viewing_directions[..., 1] = ma
 
     def table_exist(self, tablename):
-        if not isinstance(tablename, str):
-            raise TypeError('tablename should be a string')
+        assert isinstance(tablename, str), 'tablename should be a string'
         self.db_cursor.execute(
             """
             SELECT count(*)
@@ -191,19 +189,22 @@ It creates three sql table on initialisation.
             """
             SELECT count(*)
             FROM position_orientation
-            WHERE rowid=?;""", (rowid,))
+            WHERE rowid=?;
+            """, (rowid,))
         valid = bool(self.db_cursor.fetchone()[0])
         self.db_cursor.execute(
             """
             SELECT count(*)
             FROM normalisation
-            WHERE rowid=?;""", (rowid,))
+            WHERE rowid=?;
+            """, (rowid,))
         valid = valid and bool(self.db_cursor.fetchone()[0])
         self.db_cursor.execute(
             """
             SELECT count(*)
             FROM image
-            WHERE rowid=?;""", (rowid,))
+            WHERE rowid=?;
+            """, (rowid,))
         valid = valid and bool(self.db_cursor.fetchone()[0])
         return valid
 
@@ -309,8 +310,8 @@ database
         return posorient
 
     def read_posorient(self, posorient=None, rowid=None):
-        if (posorient is None) and (rowid is None):
-            raise ValueError('posorient and rowid can not be both None')
+        assert (posorient is None) or (rowid is None),\
+            'posorient and rowid can not be both None'
         if posorient is not None:
             rowid = self.get_posid(posorient)
         # Read images
@@ -336,8 +337,8 @@ database
         :returns: an image
         :rtype: numpy.ndarray
         """
-        if (posorient is None) and (rowid is None):
-            raise ValueError('posorient and rowid can not be both None')
+        assert (posorient is None) or (rowid is None),\
+            'posorient and rowid can not be both None'
         if posorient is not None:
             rowid = self.get_posid(posorient)
         # Read images
@@ -357,20 +358,19 @@ database
                 FROM {}
                 WHERE (rowid={})
                 """.format(tablename, rowid), self.db)
-        if not cmaxminrange.shape[0] == 1:
-            raise Exception('Error while reading normalisation factors')
+        assert cmaxminrange.shape[0] == 1,\
+            'Error while reading normalisation factors'
         cmaxminrange = cmaxminrange.iloc[0, :]
         return self.denormalise_image(image, cmaxminrange)
 
     def denormalise_image(self, image, cmaxminrange):
-        if not len(image.shape) == 3:
-            raise Exception('image should be 3D array')
-        if not image.shape[2] == len(self.channels):
-            raise Exception('image does not have the\
-                           required number of channels {}'.format(
-                           len(self.channels)))
-        if not isinstance(cmaxminrange, pd.Series):
-            raise TypeError('cmaxminrange should be a pandas Series')
+        assert len(image.shape) == 3,\
+            'image should be 3D array'
+        assert image.shape[2] == len(self.channels),\
+            'image does not have the required number of channels {}'.format(
+                len(self.channels))
+        assert isinstance(cmaxminrange, pd.Series),\
+            'cmaxminrange should be a pandas Series'
         denormed_im = np.zeros(image.shape, dtype=np.float)
         maxval_nim = np.iinfo(image.dtype).max
         #
@@ -383,9 +383,8 @@ database
             cimage *= crange
             cimage += cmin
             denormed_im[:, :, chan_i] = cimage
-            if not np.max(cimage) == cmax:
-                raise Exception('denormalisation failed\
-                               {}!={}'.format(np.max(cimage), cmax))
+            assert np.max(cimage) == cmax,\
+                'denormalisation failed {}!={}'.format(np.max(cimage), cmax)
         return denormed_im
 
 
@@ -421,10 +420,10 @@ class DataBaseSave(DataBase):
         self.insert_replace(tablename, params)
 
     def insert_replace(self, tablename, params):
-        if not isinstance(tablename, str):
-            raise TypeError('table are named by string')
-        if not isinstance(params, dict):
-            raise TypeError('params should be dictionary columns:val')
+        assert isinstance(tablename, str),\
+            'table are named by string'
+        assert isinstance(params, dict),\
+            'params should be dictionary columns:val'
         params_list = list()
         columns_str = ''
         for key, val in params.items():
diff --git a/navipy/moving/test_maths.py b/navipy/moving/test_maths.py
index 9e57530..bcc8c80 100644
--- a/navipy/moving/test_maths.py
+++ b/navipy/moving/test_maths.py
@@ -4,14 +4,16 @@ Test of maths
 import numpy as np
 import pandas as pd
 import navipy.moving.maths as navimaths
-
-
 import unittest
 
 
 class TestNavipyMovingMaths(unittest.TestCase):
+    """ A class to test some mathy function of the toolbox
+    """
 
     def test_motion_vec_pandas(self):
+        """ Test that a TypeError is correctly raised
+        """
         motion_vec = 'NotPandas'
         move_mode = 'on_cubic_grid'
         mode_param = dict()
@@ -22,6 +24,8 @@ class TestNavipyMovingMaths(unittest.TestCase):
                                move_mode)
 
     def test_notsupported_mofm(self):
+        """ Test that a TypeError is correctly raised
+        """
         motion_vec = pd.Series(data=0,
                                index=['x', 'y', 'z', 'dx', 'dy', 'dz'])
         move_mode = 'NotSupportedMode'
@@ -33,6 +37,12 @@ class TestNavipyMovingMaths(unittest.TestCase):
                                move_mode)
 
     def test_null_velocity(self):
+        """ Test null velocity
+
+        When the agent has a null velocity, the next postion
+        should be equal to the current position. Here, we
+        test this by series equality.
+        """
         # Test if stay at same position.
         motion_vec = pd.Series(data=0,
                                index=['x', 'y', 'z', 'dx', 'dy', 'dz'])
@@ -44,7 +54,13 @@ class TestNavipyMovingMaths(unittest.TestCase):
                         'At null velocity the agent should not move')
 
     def test_closest_cubic(self):
-        """ Test if the snaping to cubic is correct """
+        """ Test if the snaping to cubic is correct
+
+        When the agent move on the grid, its position has to be snapped
+        to the grid position. On a cubic grid, the instability is at
+        22.5 degrees modulo 45 degrees. We therefore test the functions
+        close to each instability.
+        """
         positions = pd.DataFrame({'x': [0, 0, 0, 1, 1, 1, 2, 2, 2],
                                   'y': [0, 1, 2, 0, 1, 2, 0, 1, 2],
                                   'z': [0, 0, 0, 0, 0, 0, 0, 0, 0]},
diff --git a/navipy/processing/pcode.py b/navipy/processing/pcode.py
index b552906..f2f8824 100644
--- a/navipy/processing/pcode.py
+++ b/navipy/processing/pcode.py
@@ -1,53 +1,5 @@
 """
-The scene processing part of the toolbox defines methodes
-to transform an image into a place code in the sense
-of Basten and Mallot (2010).
-
-The scene is either
-
-* a 4d numpy array, used when the image is a equirectangular \
-projection, i.e. a panoramic image.
-* a 3d numpy array, used when the viewing direction can not \
-be mapped/projected on a regular image (e.g. insect eye).
-
-We thus define the following for a scene:
-
-image based scene (IBS)
-  A classical image. Each pixel is viewed in a direction
-  (elevation,azimuth) in a regular manner.
-  In that case the scene is a 4d numpy array
-  [elevation-index,azimuth-index,channel-index,1].
-
-Omatidium based scene (OBS)
-  In an ommatidia based scene, the viewing direction
-  do not need to be regularally spaced.
-  In that case the scene is a 3d numpy array
-  [ommatidia-index, channel-index,1].
-
-By extension a place-code is either image based or ommatidium based.
-The number of dimension of an ib-place-code is always 4, and of an
-ob-place-code always 3.
-
-image based place-code (IBPC)
-    A place code derived from IBS. Each pixel is viewed in a direction
-    (elevation,azimuth) in a regular manner.
-    In that case the scene is a 4d numpy array
-    [elevation-index,azimuth-index,channel-index,component-index].
-
-Omatidium based place-code (OBPC)
-    A place code derived from OBS, the viewing direction
-    do not need to be regularally spaced.
-    In that case the scene is a 3d numpy array
-    [ommatidia-index, channel-index,component-index].
-
-
-Abusing the terminology of a place-code, a scene can be a place-code.
-Therefore ibs and obs have 4 and 3 dimension, respectively.
-
-.. todo:
-
-   * implement optic flow vector
-
+place code
 """
 import numpy as np
 import pandas as pd
@@ -58,35 +10,8 @@ from .constants import __obpc_indeces__
 from .tools import is_ibpc
 from .tools import is_obpc
 from .tools import spherical_to_cartesian
-
-
-def is_numeric_array(array):
-    """Checks if the dtype of the array is numeric.
-
-    Booleans, unsigned integer, signed integer, floats and complex are
-    considered numeric.
-
-    Parameters
-    ----------
-    array : `numpy.ndarray`-like
-        The array to check.
-
-    Returns
-    -------
-    is_numeric : `bool`
-        True if it is a recognized numerical and False if object or
-        string.
-    """
-    numerical_dtype_kinds = {'b',  # boolean
-                             'u',  # unsigned integer
-                             'i',  # signed integer
-                             'f',  # floats
-                             'c'}  # complex
-    try:
-        return array.dtype.kind in numerical_dtype_kinds
-    except AttributeError:
-        # in case it's not a numpy array it will probably have no dtype.
-        return np.asarray(array).dtype.kind in numerical_dtype_kinds
+from .tools import check_scene
+from .tools import check_viewing_direction
 
 
 def scene(database, posorient=None, rowid=None):
@@ -147,61 +72,6 @@ def scene(database, posorient=None, rowid=None):
         raise ValueError('either rowid or posoriend must be given')
 
 
-def check_scene(scene):
-    if is_ibpc(scene):
-        #  print("normal")
-        if not is_numeric_array(scene):
-            raise TypeError('scene is of non numeric type')
-        if not ~np.any(np.isnan(scene)):
-            raise ValueError('scene contains nans')
-        if not len(scene.shape) == 4:
-            raise Exception('scene has wrong shape, must have 4 dimensions')
-        if not (scene.shape[1] > 0):
-            raise Exception('scenes first dimension is empty')
-        if not (scene.shape[0] > 0):
-            raise Exception('scenes second dimension is empty')
-        if not (scene.shape[2] == 4):
-            raise Exception('3rd dimension of scene must be four')
-        if not (scene.shape[3] == 1):
-            raise Exception('4rd dimension of scene must be one')
-        # assert ~(np.any(np.isNone(scene)))
-        return True
-
-    elif is_obpc(scene):
-        if not is_numeric_array(scene):
-            raise TypeError('scene is of non numeric type')
-        if not ~np.any(np.isnan(scene)):
-            raise ValueError('scene contains nans')
-        if not len(scene.shape) == 3:
-            raise Exception('scene has wrong shape, must have 4 dimensions')
-        if not ~(scene.shape[1] <= 0):
-            raise Exception('scenes first dimension is empty')
-        if not ~(scene.shape[0] <= 0):
-            raise Exception('scenes second dimension is empty')
-        if not (scene.shape[2] == 4):
-            raise Exception('3rd dimension of scene must be four')
-        if not (scene.shape[3] == 1):
-            raise Exception('4rd dimension of scene must be one')
-        # assert ~(np.any(np.isNone(scene)))
-        return True
-
-
-def check_viewing_direction(viewing_direction):
-    if not is_numeric_array(viewing_direction):
-        raise TypeError('viewing direction is of non numeric type')
-    if not ~np.any(np.isnan(viewing_direction)):
-        raise ValueError('viewing direction contains nans')
-    if not len(viewing_direction.shape) == 3:
-        raise Exception('viewing direction must have 3 dimensions')
-    if not (viewing_direction.shape[1] > 0):
-        raise Exception('viewing direction has empty second dimension')
-    if not (viewing_direction.shape[0] > 0):
-        raise Exception('viewing direction has empty first dimension')
-    if not (viewing_direction.shape[2] == 2):
-        raise Exception(' 3rd dimension of viewing direction must equal 2')
-    return True
-
-
 def skyline(scene):
     """Return the average along the elevation of a scene
     :param scene: the scenery at a given location (a 4d numpy array)
@@ -373,8 +243,3 @@ def apcv(place_code, viewing_directions):
         return (scaled_lv.sum(axis=0))[np.newaxis, ...]
     else:
         raise TypeError('place code is neither an ibpc nor obpc')
-
-
-def optic_flow(place_code, viewing_directions, velocity):
-    """NOT IMPLEMENTED"""
-    raise NameError('Not Implemented')
diff --git a/navipy/processing/tools.py b/navipy/processing/tools.py
index d61aefa..06771d6 100644
--- a/navipy/processing/tools.py
+++ b/navipy/processing/tools.py
@@ -6,6 +6,84 @@ from .constants import __eye_indeces__
 import numpy as np
 
 
+def check_scene(scene):
+    if is_ibpc(scene):
+        #  print("normal")
+        if not is_numeric_array(scene):
+            raise TypeError('scene is of non numeric type')
+        if not ~np.any(np.isnan(scene)):
+            raise ValueError('scene contains nans')
+        if not len(scene.shape) == 4:
+            raise Exception('scene has wrong shape, must have 4 dimensions')
+        if not (scene.shape[1] > 0):
+            raise Exception('scenes first dimension is empty')
+        if not (scene.shape[0] > 0):
+            raise Exception('scenes second dimension is empty')
+        if not (scene.shape[2] == 4):
+            raise Exception('3rd dimension of scene must be four')
+        if not (scene.shape[3] == 1):
+            raise Exception('4rd dimension of scene must be one')
+        # assert ~(np.any(np.isNone(scene)))
+        return True
+
+    elif is_obpc(scene):
+        if not is_numeric_array(scene):
+            raise TypeError('scene is of non numeric type')
+        if not ~np.any(np.isnan(scene)):
+            raise ValueError('scene contains nans')
+        if not len(scene.shape) == 3:
+            raise Exception('scene has wrong shape, must have 4 dimensions')
+        if not ~(scene.shape[1] <= 0):
+            raise Exception('scenes first dimension is empty')
+        if not ~(scene.shape[0] <= 0):
+            raise Exception('scenes second dimension is empty')
+        if not (scene.shape[2] == 4):
+            raise Exception('3rd dimension of scene must be four')
+        if not (scene.shape[3] == 1):
+            raise Exception('4rd dimension of scene must be one')
+        # assert ~(np.any(np.isNone(scene)))
+        return True
+
+
+def check_viewing_direction(viewing_direction):
+    if not is_numeric_array(viewing_direction):
+        raise TypeError('viewing direction is of non numeric type')
+    if not ~np.any(np.isnan(viewing_direction)):
+        raise ValueError('viewing direction contains nans')
+    if not len(viewing_direction.shape) == 3:
+        raise Exception('viewing direction must have 3 dimensions')
+    if not (viewing_direction.shape[1] > 0):
+        raise Exception('viewing direction has empty second dimension')
+    if not (viewing_direction.shape[0] > 0):
+        raise Exception('viewing direction has empty first dimension')
+    if not (viewing_direction.shape[2] == 2):
+        raise Exception(' 3rd dimension of viewing direction must equal 2')
+    return True
+
+
+def is_numeric_array(array):
+    """Checks if the dtype of the array is numeric.
+
+    Booleans, unsigned integer, signed integer, floats and complex are
+    considered numeric.
+
+    :param array : `numpy.ndarray`-like The array to check.
+    :returns: True if it is a recognized numerical and False \
+    if object or  string.
+    :rtype:bool
+    """
+    numerical_dtype_kinds = {'b',  # boolean
+                             'u',  # unsigned integer
+                             'i',  # signed integer
+                             'f',  # floats
+                             'c'}  # complex
+    try:
+        return array.dtype.kind in numerical_dtype_kinds
+    except AttributeError:
+        # in case it's not a numpy array it will probably have no dtype.
+        return np.asarray(array).dtype.kind in numerical_dtype_kinds
+
+
 def is_ibpc(place_code):
     """Test if a place code is image based
 
diff --git a/navipy/rendering/bee_sampling.py b/navipy/rendering/bee_sampling.py
index ff113ec..0d91148 100644
--- a/navipy/rendering/bee_sampling.py
+++ b/navipy/rendering/bee_sampling.py
@@ -88,7 +88,8 @@ harddrive space, as each image is composed of 4 channels of 180x360 pixels.
         dataloger = DataBaseSave(database_filename,
                                  channels=['R', 'G', 'B', 'D'],
                                  arr_dtype=np.uint8)
-        for frame_i, posorient in self.__grid_posorients.iterrows():
+        for frame_i, posorient in self.__grid_posorients.iloc[::-1].iterrows():
+            print(frame_i)
             if np.any(np.isnan(posorient)):
                 # warnings.warn('frame_i: {} posorient nans'.format(frame_i))
                 continue
-- 
GitLab