From f3ad7cf3c6620cba0fcd70e63f310292651d7e14 Mon Sep 17 00:00:00 2001
From: lodenthal <lodenthal@uni-bielefeld.de>
Date: Mon, 20 Nov 2017 11:33:19 +0300
Subject: [PATCH] added database _init, its now the old database.py file

---
 src/database/__init__.py | 437 ++++++++++++++++++++++++++++++++++++++-
 1 file changed, 436 insertions(+), 1 deletion(-)

diff --git a/src/database/__init__.py b/src/database/__init__.py
index 844a07f..37d5330 100644
--- a/src/database/__init__.py
+++ b/src/database/__init__.py
@@ -1 +1,436 @@
-from .database import DataBaseLoad, DataBaseSave
+"""
+Database are generated by the rendering module, and contains all \
+images and there corresponding position-orientations.
+
+* position_orientation: containing all position and orientation of where \
+images were rendered. The position-orientation is described by \
+['x','y','z','alpha_0','alpha_1','alpha_2']
+* image: containing all images ever rendered. Each channel of each image \
+are normalised, so to use the full coding range.
+* normalisation: the normalisation constantes
+
+
+How to load a database
+----------------------
+
+.. code-block:: python
+
+   from database import DataBaseLoad
+   mydb_filename = 'database.db'
+   mydb = DataBaseLoad(mydb_filename)
+
+How to load all position-orientation
+------------------------------------
+
+The database contains all position-orientation \
+at which an image as been rendered. In certain \
+situation, it may be usefull to know all \
+position-orientation in the database. More technically \
+speaking, loading the full table of position-orientaiton.
+
+.. code-block:: python
+
+   posorients = mydb.get_posorients()
+   posorients.head()
+
+How to load an image
+--------------------
+
+The database contains images which can be processed differently \
+depending on the navigation strategy beeing used.
+
+Images are at given position-orientations. To load an image \
+the position-orientation can be given. The DataBaseLoader will \
+look if this position-orientation has been rendered. If it is \
+the case, the image will be returned.
+
+.. code-block:: python
+
+   posorient = pd.Series(index=['x', 'y', 'z',
+                             'alpha_0', 'alpha_1', 'alpha_2'])
+   posorient.x = -0.6
+   posorient.y = -7.2
+   posorient.z = 2.35
+   posorient.alpha_0 = np.pi / 2
+   posorient.alpha_1 = 0
+   posorient.alpha_2 = 0
+   image = mydb.read_image(posorient=posorient)
+
+.. plot:: example/database/load_image_posorient.py
+
+However, looking in the database if an image has already been \
+rendered at a given position-orientation can cost time. To speed up \
+certain calculation, image can instead be access by row number. \
+Indeed each position-orientation can be identified by a unique row \
+number. This number is consistant through the entire database. Thus, \
+an image can be loaded by providing the row number.
+
+.. code-block:: python
+
+   rowid = 1000
+   image = mydb.read_image(rowid=rowid)
+
+.. plot:: example/database/load_image_rowid.py
+
+
+
+.. todo: channels as part of database
+
+"""
+
+import os
+import numpy as np
+import pandas as pd
+import sqlite3
+import io
+import warnings
+
+
+def adapt_array(arr):
+    """
+    http://stackoverflow.com/a/31312102/190597 (SoulNibbler)
+    """
+    out = io.BytesIO()
+    np.save(out, arr)
+    out.seek(0)
+    return sqlite3.Binary(out.read())
+
+
+def convert_array(text):
+    out = io.BytesIO(text)
+    out.seek(0)
+    return np.load(out)
+
+
+# Converts np.array to TEXT when inserting
+sqlite3.register_adapter(np.ndarray, adapt_array)
+# Converts TEXT to np.array when selecting
+sqlite3.register_converter("array", convert_array)
+
+
+class DataBase():
+    """DataBase is the parent class of DataBaseLoad and DataBaseSave.
+It creates three sql table on initialisation.
+    """
+    __float_tolerance = 1e-14
+
+    def __init__(self, filename, channels=['R', 'G', 'B', 'D']):
+        """Initialisation of the database """
+        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()
+        for chan_n in self.channels:
+            self.normalisation_columns.append(str(chan_n) + '_max')
+            self.normalisation_columns.append(str(chan_n) + '_min')
+            self.normalisation_columns.append(str(chan_n) + '_range')
+
+        self.tablecolumns = dict()
+        self.tablecolumns['position_orientation'] = dict()
+        self.tablecolumns['position_orientation']['x'] = 'real'
+        self.tablecolumns['position_orientation']['y'] = 'real'
+        self.tablecolumns['position_orientation']['z'] = 'real'
+        self.tablecolumns['position_orientation']['alpha_0'] = 'real'
+        self.tablecolumns['position_orientation']['alpha_1'] = 'real'
+        self.tablecolumns['position_orientation']['alpha_2'] = 'real'
+        self.tablecolumns['image'] = dict()
+        self.tablecolumns['image']['data'] = 'array'
+        self.tablecolumns['normalisation'] = dict()
+        for col in self.normalisation_columns:
+            self.tablecolumns['normalisation'][col] = 'real'
+
+        if os.path.exists(filename):
+            # Check database
+            self.db = sqlite3.connect(
+                filename, detect_types=sqlite3.PARSE_DECLTYPES)
+            self.db_cursor = self.db.cursor()
+            for tablename, _ in self.tablecolumns.items():
+                print(tablename)
+                assert self.table_exist(tablename),\
+                    '{} does not contain a table named {}'.format(
+                        filename, tablename)
+        elif self.create():
+            # Create database
+            self.db = sqlite3.connect(
+                filename, detect_types=sqlite3.PARSE_DECLTYPES)
+            self.db_cursor = self.db.cursor()
+            for key, val in self.tablecolumns.items():
+                columns = "(id integer primary key autoincrement"
+                for colname, coltype in val.items():
+                    columns += ' , ' + colname + ' ' + coltype
+                columns += ')'
+                print(key, columns)
+                self.db_cursor.execute(
+                    "create table {} {}".format(key, columns))
+            self.db.commit()
+        else:
+            raise NameError('Database {} does not exist'.format(filename))
+
+        azimuth = np.linspace(-180, 180, 360)
+        elevation = np.linspace(-90, 90, 180)
+        [ma, me] = np.meshgrid(azimuth, elevation)
+        self.viewing_directions = np.zeros((ma.shape[0], ma.shape[1], 2))
+        self.viewing_directions[..., 0] = me
+        self.viewing_directions[..., 1] = ma
+
+    def table_exist(self, tablename):
+        assert isinstance(tablename, str), 'tablename should be a string'
+        self.db_cursor.execute(
+            """
+            SELECT count(*)
+            FROM sqlite_master
+            WHERE type='table' and name=?;
+            """, (tablename,))
+        return bool(self.db_cursor.fetchone())
+
+    def check_data_validity(self, rowid):
+        self.db_cursor.execute(
+            """
+            SELECT count(*)
+            FROM position_orientation
+            WHERE rowid=?;""", (rowid,))
+        valid = bool(self.db_cursor.fetchone()[0])
+        self.db_cursor.execute(
+            """
+            SELECT count(*)
+            FROM normalisation
+            WHERE rowid=?;""", (rowid,))
+        valid = valid and bool(self.db_cursor.fetchone()[0])
+        self.db_cursor.execute(
+            """
+            SELECT count(*)
+            FROM image
+            WHERE rowid=?;""", (rowid,))
+        valid = valid and bool(self.db_cursor.fetchone()[0])
+        return valid
+
+    def get_posid(self, posorient):
+        assert isinstance(posorient, pd.Series),\
+            'posorient should be a pandas Series'
+        where = """x>=? and x<=?"""
+        where += """and y>=? and y<=?"""
+        where += """and z>=? and z<=?"""
+        where += """and alpha_0>=? and alpha_0<=?"""
+        where += """and alpha_1>=? and alpha_1<=?"""
+        where += """and alpha_2>=? and alpha_2<=?"""
+        params = (
+            posorient['x'] - self.__float_tolerance,
+            posorient['x'] + self.__float_tolerance,
+            posorient['y'] - self.__float_tolerance,
+            posorient['y'] + self.__float_tolerance,
+            posorient['z'] - self.__float_tolerance,
+            posorient['z'] + self.__float_tolerance,
+            posorient['alpha_0'] - self.__float_tolerance,
+            posorient['alpha_0'] + self.__float_tolerance,
+            posorient['alpha_1'] - self.__float_tolerance,
+            posorient['alpha_1'] + self.__float_tolerance,
+            posorient['alpha_2'] - self.__float_tolerance,
+            posorient['alpha_2'] + self.__float_tolerance)
+        self.db_cursor.execute(
+            """
+            SELECT count(*)
+            FROM position_orientation
+            WHERE {};""".format(where), params)
+        exist = self.db_cursor.fetchone()[0]  # [0] because of tupple
+        if bool(exist):
+            self.db_cursor.execute(
+                """
+                SELECT rowid
+                FROM position_orientation
+                WHERE {};
+                """.format(where), params)
+            return self.db_cursor.fetchone()[0]
+        elif self.create():
+            self.db_cursor.execute(
+                """
+                INSERT
+                INTO position_orientation(x,y,z,alpha_0,alpha_1,alpha_2)
+                VALUES (?,?,?,?,?,?)
+                """, (
+                    posorient['x'],
+                    posorient['y'],
+                    posorient['z'],
+                    posorient['alpha_0'],
+                    posorient['alpha_1'],
+                    posorient['alpha_2']))
+            rowid = self.db_cursor.lastrowid
+            self.db.commit()
+            return rowid
+        else:
+            print(posorient)
+            raise ValueError('posorient not found')
+
+    def create(self):
+        return False
+
+
+class DataBaseLoad(DataBase):
+    """A database generated by the rendering module is based on sqlite3.
+    """
+
+    def __init__(self, filename, channels=['R', 'G', 'B', 'D']):
+        """Initialise the DataBaseLoader"""
+        DataBase.__init__(self, filename, channels=channels)
+
+    def create(self):
+        """use to decide weather to alter the database or not
+        return False because we do not want
+        to write on database (Load class)"""
+        return False
+
+    def get_posorients(self):
+        """Return the position orientations of all points in the \
+database
+        """
+        posorient = pd.read_sql_query(
+            "select * from position_orientation;", self.db)
+        posorient.set_index('id', inplace=True)
+        return posorient
+
+    def read_image(self, posorient=None, rowid=None):
+        """Read an image at a given position-orientation or given id of row in the \
+        database.
+
+        :param posorient: a pandas Series with index \
+                          ['x','y','z','alpha_0','alpha_1','alpha_2']
+        :param rowid: an integer
+        :returns: an image
+        :rtype: numpy.ndarray
+        """
+        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
+        tablename = 'image'
+        self.db_cursor.execute(
+            """
+                SELECT data
+                FROM {}
+                WHERE (rowid=?)
+                """.format(tablename), (rowid,))
+        image = self.db_cursor.fetchone()[0]
+        # Read cmaxminrange
+        tablename = 'normalisation'
+        cmaxminrange = pd.read_sql_query(
+            """
+                SELECT *
+                FROM {}
+                WHERE (rowid={})
+                """.format(tablename, rowid), self.db)
+        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):
+        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
+        #
+        for chan_i, chan_n in enumerate(self.channels):
+            cimage = image[:, :, chan_i].astype(float)
+            cmax = cmaxminrange.loc[str(chan_n) + '_max']
+            cmin = cmaxminrange.loc[str(chan_n) + '_min']
+            crange = cmaxminrange.loc[str(chan_n) + '_range']
+            cimage /= maxval_nim
+            cimage *= crange
+            cimage += cmin
+            denormed_im[:, :, chan_i] = cimage
+            assert np.max(cimage) == cmax,\
+                'denormalisation failed {}!={}'.format(np.max(cimage), cmax)
+        return denormed_im
+
+
+class DataBaseSave(DataBase):
+    def __init__(self, filename, channels=['R', 'G', 'B', 'D'],
+                 arr_dtype=np.uint8):
+        """
+        """
+        DataBase.__init__(self, filename, channels=channels)
+        self.arr_dtype = arr_dtype
+
+    def create(self):
+        """use to decide weather to alter the database or not
+        return True because we will need
+        to write on database (Save class)"""
+        return True
+
+    def write_image(self, posorient, image):
+        normed_im, cmaxminrange = self.normalise_image(image, self.arr_dtype)
+        rowid = self.get_posid(posorient)
+        # Write image
+        tablename = 'image'
+        params = dict()
+        params['rowid'] = rowid
+        params['data'] = normed_im
+        self.insert_replace(tablename, params)
+        #
+        tablename = 'normalisation'
+        params = dict()
+        params['rowid'] = rowid
+        for chan_n in self.normalisation_columns:
+            params[chan_n] = cmaxminrange.loc[chan_n]
+        self.insert_replace(tablename, params)
+
+    def insert_replace(self, tablename, params):
+        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():
+            columns_str += key + ','
+            params_list.append(val)
+        columns_str = columns_str[:-1]  # remove last comma
+        if len(params_list) == 0:
+            warnings.warn('nothing to be done in {}'.format(tablename))
+            return
+        questionsmarks = '?'
+        for _ in range(1, len(params_list)):
+            questionsmarks += ',?'
+        self.db_cursor.execute(
+            """
+            INSERT OR REPLACE
+            INTO {} ({})
+            VALUES ({})
+            """.format(tablename,
+                       columns_str,
+                       questionsmarks),
+            tuple(params_list)
+        )
+        self.db.commit()
+
+    def normalise_image(self, image, dtype=np.uint8):
+        normed_im = np.zeros(image.shape, dtype=dtype)
+        maxval_nim = np.iinfo(normed_im.dtype).max
+        #
+        columns = list()
+        for chan_n in self.channels:
+            columns.append(str(chan_n) + '_max')
+            columns.append(str(chan_n) + '_min')
+            columns.append(str(chan_n) + '_range')
+
+        cmaxminrange = pd.Series(index=columns)
+        for chan_i, chan_n in enumerate(self.channels):
+            cimage = image[:, :, chan_i].astype(float)
+            cmax = cimage.max()
+            cmin = cimage.min()
+            crange = cmax - cmin
+            cimage -= cmin
+            cimage /= crange
+            cimage *= maxval_nim
+            cimage = cimage.astype(normed_im.dtype)
+            normed_im[:, :, chan_i] = cimage
+            cmaxminrange.loc[str(chan_n) + '_max'] = cmax
+            cmaxminrange.loc[str(chan_n) + '_min'] = cmin
+            cmaxminrange.loc[str(chan_n) + '_range'] = crange
+        return normed_im, cmaxminrange
-- 
GitLab