From 3c8f3cf33303a71226d1437a6d5ac8c9e0a8ec01 Mon Sep 17 00:00:00 2001
From: Sebastian Hahta <joseha@utu.fi>
Date: Fri, 10 Jan 2020 15:57:25 +0200
Subject: [PATCH] support for new depth channel x265 format

---
 python/ftl/ftlstream.py | 40 +++++++++++++++++++++++++++---------
 python/ftl/ftltypes.py  |  4 ++++
 python/ftl/libde265.py  | 45 +++++++++++++++++++++++++----------------
 3 files changed, 63 insertions(+), 26 deletions(-)

diff --git a/python/ftl/ftlstream.py b/python/ftl/ftlstream.py
index 65514f0ba..2af8e7164 100644
--- a/python/ftl/ftlstream.py
+++ b/python/ftl/ftlstream.py
@@ -246,21 +246,43 @@ class FTLStreamReader:
             warn("frame expected, no image received from decoder")
 
         if ftl.is_float_channel(self._sp.channel):
-            # TODO: only supports 8 bits per pixel format and 16 bits
-            #       ()"old format")
-            #
-            # NVPipe: (2 * width), high bits in left, low in right
-
-            high = img[:,(img.shape[1]//2):,0].astype(np.uint32) << 8
-            low = img[:,:(img.shape[1]//2),0].astype(np.uint32)
-            img = (high|low).astype(np.float)/1000.0
+            if (p.flags & ftl.PacketFlags.MappedDepth):
+                # New format
+
+                # hardcoded constants maxdepth and P
+                maxdepth = 16
+                P = (2.0 * 256.0) / 16384.0
+
+                # use only 8 bits of 10
+                img = (img >> 2).astype(np.float) / 255
+                
+                L = img[:,:,0]
+                Ha = img[:,:,1]
+                Hb = img[:,:,2]
+
+                m = np.floor(4.0 * (L/P) - 0.5).astype(np.int) % 4
+                L0 = L - ((L-(P / 8.0)) % P) + (P / 4.0) * m.astype(np.float) - (P/8.0)
+                
+                s = np.zeros(img.shape[:2], dtype=np.float)
+                np.copyto(s, (P/2.0) * Ha, where=m == 0)
+                np.copyto(s, (P/2.0) * Hb, where=m == 1)
+                np.copyto(s, (P/2.0) * (1.0 - Ha), where=m == 2)
+                np.copyto(s, (P/2.0) * (1.0 - Hb), where=m == 3)
+                
+                img = (L0+s) * maxdepth
 
+            else:
+                # NvPipe format
+                high = img[:,(img.shape[1]//2):,0].astype(np.uint32) << 8
+                low = img[:,:(img.shape[1]//2),0].astype(np.uint32)
+                img = (high|low).astype(np.float)/1000.0
+            '''
             try:
                 img[img < self._calibration[sp.streamID].min_depth] = 0.0
                 img[img > self._calibration[sp.streamID].max_depth] = 0.0
             except KeyError:
                 warn("no calibration for received frame")
-
+            '''
             self._frame = img
 
         else:
diff --git a/python/ftl/ftltypes.py b/python/ftl/ftltypes.py
index 69eb87248..6c2200414 100644
--- a/python/ftl/ftltypes.py
+++ b/python/ftl/ftltypes.py
@@ -13,6 +13,10 @@ Packet = namedtuple("Packet", ["codec", "definition", "block_total",
 StreamPacket = namedtuple("StreamPacket", ["timestamp", "streamID",
                                            "channel_count", "channel"])
 
+class PacketFlags:
+    RGB = 0x00000001
+    MappedDepth = 0x00000002
+
 # components/codecs/include/ftl/codecs/channels.hpp
 class Channel(IntEnum):
     None_           = -1
diff --git a/python/ftl/libde265.py b/python/ftl/libde265.py
index c57404eaa..20ef8ca38 100644
--- a/python/ftl/libde265.py
+++ b/python/ftl/libde265.py
@@ -1,24 +1,28 @@
-'''
-Python wrapper for libde265. Only decoding is (partly) implemented.
+'''!
+Python wrapper for libde265. Only decoding is implemented.
 
 Requirements:
  * libde265 library (libde265.so.0)
  * numpy
- * opencv or skimage
+ * opencv (recommended) or skimage
 
 '''
 
 try:
     import cv2 as cv
     def _resize(img, size):
-        return cv.resize(img, dsize=tuple(reversed(size)), interpolation=cv.INTER_CUBIC)
+        dst = np.zeros(size, dtype=img.dtype)
+        cv.resize(img, tuple(reversed(size)), dst, interpolation=cv.INTER_LINEAR)
+        return dst
 
 except ImportError:
+    # seems to be much slower than OpenCV resize()
+
     from skimage.transform import resize as resize_skimage
     def _resize(img, size):
-        # skimage resize() return dtype float64, convert back to uint8
+        # skimage resize() return dtype float64, convert back to original type
         # order: 0 nn, 1 bilinear, 3 bicubic
-        return (resize_skimage(img, size, order=3, mode="constant", cval=0) * 255).astype(np.uint8)
+        return (resize_skimage(img, size, order=2, mode="constant", cval=0) *  np.iinfo(img.dtype).max).astype(img.dtype)
 
 from warnings import warn
 
@@ -39,6 +43,10 @@ if _threads is None:
 
 _threads = 1
 
+################################################################################
+# interface and definitions from libde256 api
+################################################################################
+
 # error codes copied from header (de265.h)
 
 class _libde265error(IntEnum):
@@ -155,6 +163,8 @@ libde265.de265_get_image_plane.restype = ctypes.POINTER(ctypes.c_char)
 libde265.de265_get_number_of_input_bytes_pending.argtypes = [ctypes.c_void_p]
 libde265.de265_get_number_of_input_bytes_pending.restype = ctypes.c_int
 
+################################################################################
+
 class libde265Error(Exception):
     def __init__(self, code):
         super(libde265Error, self).__init__(
@@ -168,7 +178,7 @@ class Decoder:
         self._more = ctypes.c_int()
         self._out_stride = ctypes.c_int()
         self._ctx = libde265.de265_new_decoder()
-        self._supress_warnings = False
+        self._disable_warnings = False
 
         err = libde265.de265_start_worker_threads(self._ctx, threads)
 
@@ -182,28 +192,29 @@ class Decoder:
         size = (libde265.de265_get_image_height(de265_image, 0),
                 libde265.de265_get_image_width(de265_image, 0))
 
-        res = np.zeros((*size, 3), dtype=np.uint8)
+        res = np.zeros((*size, 3), dtype=np.uint16)
 
         # libde265: always 420 (???)
-        # chroma_format = libde265.de265_get_chroma_format(de265_image)
-
+        chroma_format = libde265.de265_get_chroma_format(de265_image)
+        if chroma_format != de265_chroma.de265_chroma_420:
+            raise NotImplementedError("Unsupported chroma format %s" % str(chroma_format))
+        
         for c in range(0, 3):
             size_channel = (libde265.de265_get_image_height(de265_image, c),
                             libde265.de265_get_image_width(de265_image, c))
 
             if size_channel[0] > size[0] or size_channel[1] > size[1]:
-                # Is this possible?
-                print(size, size_channel)
                 raise Exception("Channel larger than first channel")
 
             bpp = libde265.de265_get_bits_per_pixel(de265_image, c)
-            if bpp != 8:
-                raise NotImplementedError("%i-bit format not implemented (TODO)" % bpp)
+            if bpp == 8:
+                dtype = np.uint8
+            else:
+                dtype = np.uint16
 
             img_ptr = libde265.de265_get_image_plane(de265_image, c, self._out_stride)
 
-			# libde: how is 10 bits per pixel returned
-            ch = np.frombuffer(img_ptr[:size_channel[0] * size_channel[1]], dtype=np.uint8)
+            ch = np.frombuffer(img_ptr[:size_channel[0] * self._out_stride.value], dtype=dtype)
             ch.shape = size_channel
 
             res[:,:,c] = _resize(ch, size)
@@ -211,7 +222,7 @@ class Decoder:
         return res
 
     def _warning(self):
-        if self._supress_warnings:
+        if self._disable_warnings:
             return
 
         code = libde265.de265_get_warning(self._ctx)
-- 
GitLab