diff --git a/python/ftl/ftlstream.py b/python/ftl/ftlstream.py index c3790ac5491d2e3b5c439a7539ecbd57d5694c7c..65514f0ba1109700b338cf30e91b2d046754cc9b 100644 --- a/python/ftl/ftlstream.py +++ b/python/ftl/ftlstream.py @@ -14,20 +14,20 @@ from . import libde265 try: import cv2 as cv - + def _ycrcb2rgb(img): return cv.cvtColor(img, cv.COLOR_YCrCb2RGB) - + except ImportError: warn("OpenCV not available. OpenCV required for full functionality.") def _ycrcb2rgb(img): ''' YCrCb to RGB, based on OpenCV documentation definition. - + Note: It seems this implementation is not perfectly equivalent to OpenCV's (results not exactly same, why?) ''' - + rgb = np.zeros(img.shape, np.float) Y = img[:,:,0].astype(np.float) @@ -73,7 +73,7 @@ class FTLStreamWriter: def add_raw(self, sp, p): if len(sp) != len(ftl.StreamPacket._fields) or len(p) != len(ftl.Packet._fields): raise ValueError("invalid input") - + self._file.write(self._packer.pack((sp, p))) self._file.flush() @@ -82,13 +82,13 @@ class FTLStreamWriter: ''' Write frame to file. If encode is False (data already encoded), definition needs to be specified. ''' - + if source < 0: raise ValueError("invalid source id") if channel not in ftl.Channel: raise ValueError("invalid channel") - + if codec not in ftl.codec_t: raise ValueError("invalid codec") @@ -98,7 +98,7 @@ class FTLStreamWriter: if definition is None: raise ValueError("unsupported resolution") - + if definition != ftl.get_definition(data.shape): # todo: could replace definition or scale raise ValueError("definition does not match frame resolution") @@ -107,25 +107,25 @@ class FTLStreamWriter: if ftl.is_float_channel(channel): # scaling always same (???) data = (data * 1000).astype(np.uint16) - + params = [cv.IMWRITE_PNG_COMPRESSION, 9] retval, data = cv.imencode(".png", data, params) - + if not retval: raise Exception("encoding error (PNG)") - + elif codec == ftl.codec_t.JPG: params = [] retval, data = cv.imencode(".jpg", data, params) if not retval: raise Exception("encoding error (JPG)") - + else: raise ValueError("unsupported codec") data = data.tobytes() - + if definition is None: raise ValueError("definition required") @@ -152,7 +152,7 @@ class FTLStreamWriter: class FTLStreamReader: ''' FTL file reader. ''' - + def __init__(self, file): self._file = open(file, "br") self._version = 0 @@ -161,7 +161,7 @@ class FTLStreamReader: self._seen_iframe = set() self._frame = None - + # calibration and pose are cached self._calibration = {} self._pose = {} @@ -171,38 +171,38 @@ class FTLStreamReader: self._version = int(magic[4]) if magic[:4] != bytes(ord(c) for c in "FTLF"): raise Exception("wrong magic") - + if self._version >= 2: # first 64 bytes reserved self._file.read(8*8) self._unpacker = msgpack.Unpacker(self._file, raw=True, use_list=False) - + except Exception as ex: self._file.close() raise ex - + self._packets_read = 0 - + def __del__(self): self._file.close() - + def _read_next(self): v1, v2 = self._unpacker.unpack() return ftl.StreamPacket._make(v1), ftl.Packet._make(v2) - + def _update_calib(self, sp, p): ''' Update calibration. ''' - + if p.codec == ftl.codec_t.MSGPACK: # TODO: channel and capabilities should be saved as well calib, channel, capabilities = msgpack.unpackb(p.data) self._calibration[sp.streamID] = ftl.Camera._make(calib) - + elif p.codec == ftl.codec_t.CALIBRATION: calibration = struct.unpack("@ddddIIdddd", p.data[:(4*8+2*4+4*8)]) self._calibration[sp.streamID] = ftl.Camera._make(calibration) - + else: raise Exception("Unknown codec %i for calibration" % p.codec) @@ -212,18 +212,18 @@ class FTLStreamReader: dtype=np.float64) pose = pose.reshape((4, 4), order='F') # Eigen self._pose[sp.streamID] = pose - + def _process_json(self, sp, p): raise NotImplementedError("json decoding not implemented") def _decode_hevc(self, sp, p): ''' Decode HEVC frame ''' - + k = (sp.streamID, sp.channel) - + if k not in self._decoders_hevc: - self._decoders_hevc[k] = libde265.Decoder(ftl.definition_t[p.definition]) - + self._decoders_hevc[k] = libde265.Decoder() + decoder = self._decoders_hevc[k] if k not in self._seen_iframe: @@ -231,23 +231,38 @@ class FTLStreamReader: # can't decode before first I-frame has been received warn("received P-frame before I-frame") return - + self._seen_iframe.add(k) - + decoder.push_data(p.data) decoder.push_end_of_frame() - + while decoder.get_number_of_input_bytes_pending() > 0: decoder.decode() - + img = decoder.get_next_picture() if img is None: # if this happens, does get_next_picture() in loop help? - warn("frame expected, no image from decoded") - + warn("frame expected, no image received from decoder") + if ftl.is_float_channel(self._sp.channel): - raise NotImplementedError("non-color channel decoding not available") - + # 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 + + 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: if self._version < 3: self._frame = _ycrcb2rgb(img) @@ -270,7 +285,7 @@ class FTLStreamReader: ''' Read until timestamp reached ''' if self.get_timestamp() >= ts: raise Exception("trying to seek to earlier timestamp") - + while self.read(): if self.get_timestamp() >= ts: break @@ -283,20 +298,20 @@ class FTLStreamReader: todo: make (frame) decoding optional ''' self._frame = None - + try: self._sp, self._p = self._read_next() self._packets_read += 1 - + except msgpack.OutOfData: return False - + if self._p.block_total != 1 or self._p.block_number != 0: raise Exception("Unsupported block format (todo)") # calibration/pose cached # todo: should be done by user instead? - + if self._sp.channel == ftl.Channel.Calibration: self._update_calib(self._sp, self._p) @@ -362,7 +377,7 @@ class FTLStreamReader: return self._calibration[source] except KeyError: raise ValueError("source id %i not found" % source) - + def get_Q(self, source): ''' Disparity to depth matrix (OpenCV) ''' diff --git a/python/ftl/libde265.py b/python/ftl/libde265.py index 20f2f250c0c7344bc81682e15ab6bf08965318b2..c57404eaa00477e8c487d608caecea82a8e57bfa 100644 --- a/python/ftl/libde265.py +++ b/python/ftl/libde265.py @@ -12,7 +12,7 @@ try: import cv2 as cv def _resize(img, size): return cv.resize(img, dsize=tuple(reversed(size)), interpolation=cv.INTER_CUBIC) - + except ImportError: from skimage.transform import resize as resize_skimage def _resize(img, size): @@ -27,7 +27,7 @@ from enum import IntEnum import numpy as np -import os +import os ''' # default number of worker threads for decoder: half of os.cpu_count() @@ -164,8 +164,7 @@ class WaitingForInput(libde265Error): pass class Decoder: - def __init__(self, size, threads=_threads): - self._size = size + def __init__(self, threads=_threads): self._more = ctypes.c_int() self._out_stride = ctypes.c_int() self._ctx = libde265.de265_new_decoder() @@ -175,84 +174,92 @@ class Decoder: if err: raise libde265Error(err) - + def __del__(self): libde265.de265_free_decoder(self._ctx) def _copy_image(self, de265_image): - res = np.zeros((self._size[0], self._size[1], 3), dtype=np.uint8) + 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) # libde265: always 420 (???) # chroma_format = libde265.de265_get_chroma_format(de265_image) for c in range(0, 3): - size = (libde265.de265_get_image_height(de265_image, c), - libde265.de265_get_image_width(de265_image, c)) - + 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("unsupported bits per pixel %i" % bpp) + raise NotImplementedError("%i-bit format not implemented (TODO)" % bpp) img_ptr = libde265.de265_get_image_plane(de265_image, c, self._out_stride) - - # for frombuffer() no copy assumed - ch = np.frombuffer(img_ptr[:size[0] * size[1]], dtype=np.uint8) - ch.shape = size - - res[:,:,c] = _resize(ch, self._size) - + + # libde: how is 10 bits per pixel returned + ch = np.frombuffer(img_ptr[:size_channel[0] * size_channel[1]], dtype=np.uint8) + ch.shape = size_channel + + res[:,:,c] = _resize(ch, size) + return res def _warning(self): if self._supress_warnings: return - + code = libde265.de265_get_warning(self._ctx) - + if code != _libde265error.DE265_OK: msg = libde265.de265_get_error_text(code).decode("ascii") warn(msg) def decode(self): err = libde265.de265_decode(self._ctx, self._more) - + if err: if err == _libde265error.DE265_ERROR_WAITING_FOR_INPUT_DATA: raise WaitingForInput(err) raise libde265Error(err) - + self._warning() return self._more.value != 0 - + def flush_data(self): err = libde265.de265_flush_data(self._ctx) - + if err: raise libde265Error(err) - + def push_data(self, data): if not isinstance(data, bytes): raise ValueError("expected bytes") - + err = libde265.de265_push_data(self._ctx, data, len(data), None, None) - + if err: raise libde265Error(err) - + def push_end_of_frame(self): err = libde265.de265_push_end_of_frame(self._ctx) - + if err: raise libde265Error(err) - + def push_NAL(self, data): if not isinstance(data, bytes): raise ValueError("expected bytes") - + err = libde265.de265_push_NAL(self._ctx, data, len(data), None, None) - + if err: raise libde265Error(err) @@ -266,7 +273,7 @@ class Decoder: if not de265_image: return None - + res = self._copy_image(de265_image) libde265.de265_release_next_picture(self._ctx) @@ -281,7 +288,7 @@ class Decoder: if not de265_image: return None - + res = self._copy_image(de265_image) libde265.de265_release_next_picture(self._ctx) diff --git a/python/ftl/misc.py b/python/ftl/misc.py index 5494382ca1376c8bf96a9d30d7d863d8bfc9eff9..c47a47cf2c3ca2680b4031a56c42eec83fd99a7d 100644 --- a/python/ftl/misc.py +++ b/python/ftl/misc.py @@ -8,6 +8,13 @@ def disparity_to_depth(disparity, camera, max_depth=10.0, invalid_value=0.0): depth[depth > max_depth] = invalid_value return depth +def depth_to_disparity(depth, camera, invalid_value=0.0): + invalid = depth == 0.0 + depth[invalid] = 1.0 + disparity = ((camera.fx * camera.baseline) / depth) + camera.doff + disparity[invalid] = invalid_value + return disparity + from enum import IntEnum # components/codecs/include/ftl/codecs/hevc.hpp