Skip to content
Snippets Groups Projects
Commit bbc60185 authored by Sebastian's avatar Sebastian
Browse files

simpler interface

parent 86a69d11
No related branches found
No related tags found
1 merge request!155Feature/python
......@@ -12,6 +12,8 @@ from . misc import is_iframe
from . import ftltypes as ftl
from . import libde265
_calib_fmt = "@ddddIIdddd"
try:
import cv2 as cv
......@@ -19,6 +21,8 @@ try:
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.
......@@ -42,9 +46,10 @@ except ImportError:
class FTLStreamWriter:
def __init__(self, file):
self._file = open(file, "wb")
self._file.write(bytes(ord(c) for c in "FTLF"))
self._file.write(bytes([2]))
self._file.write(bytes([0]*64))
self._file.write(bytes(ord(c) for c in "FTLF")) # magic
self._file.write(bytes([2])) # version
self._file.write(bytes([0]*64)) # reserved
self._packer = msgpack.Packer(strict_types=False, use_bin_type=True)
def __del__(self):
......@@ -53,36 +58,20 @@ class FTLStreamWriter:
def close(self):
self._file.close()
def add_source(self, parameters, pose):
pass
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()
def add_frame(self, timestamp, src, channel, codec, data, encode=True):
pass
class FTLStreamBufferedWriter(FTLStreamWriter):
def __init__(self, file, fps=25.0):
super(FTLStreamWriter, file)
self._ts = 0
self._frame_t = int(1.0/fps)
self._n_sources = 0
self._frames = []
def add_source(self, count=1):
self._n_sources += count
return self._n_sources
def add_calibration(self, source):
if not 0 < source < self._n_sources:
raise ValueError("invalid source id")
def add_frame(self, source, channel, data, codec, definition=None, flags=0, encode=True):
if not 0 < source < self._n_sources:
def add_frame(self, timestamp, source, channel, channel_count, codec, data,
definition=None, flags=0, encode=True):
''' 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:
......@@ -92,7 +81,7 @@ class FTLStreamBufferedWriter(FTLStreamWriter):
raise ValueError("invalid codec")
if encode:
if definition is not None:
if definition is None:
definition = ftl.get_definition(data.shape)
if definition is None:
......@@ -131,38 +120,48 @@ class FTLStreamBufferedWriter(FTLStreamWriter):
if not isinstance(data, bytes):
raise ValueError("expected bytes")
ftl.Packet(int(codec), int(definition), 1, 0, int(flags), data)
sp = ftl.StreamPacket(int(timestamp), int(source),
int(channel_count), int(channel))
p = ftl.Packet(int(codec), int(definition), 1, 0, int(flags), data)
def push_frames(self):
for frame in self._frames:
self.add_raw(sp, p)
self._ts += self._frame_t
self.add_raw(sp, p)
def add_pose(self, timestamp, source, data):
if data.shape != (4, 4):
raise ValueError("invalid pose")
data.astype(np.float64).tobytes(order='F')
raise NotImplementedError("todo")
def add_calibration(self, timestamp, source, data):
struct.pack(_calib_fmt, *data)
raise NotImplementedError("todo")
class FTLStreamReader:
''' FTL file reader '''
''' FTL file reader. '''
def __init__(self, file):
self._file = open(file, "br")
self._decoders = {}
self._version = 0
self._decoders_hevc = {}
self._seen_iframe = set()
self._frameset = {}
self._frameset_new = {}
self._frame = None
# calibration and pose are cached
self._calibration = {}
self._pose = {}
self._ts = -sys.maxsize - 1
try:
magic = self._file.read(5)
version = int(magic[4])
self._version = int(magic[4])
if magic[:4] != bytes(ord(c) for c in "FTLF"):
raise Exception("wrong magic")
if == 2:
if self._version >= 2:
# first 64 bytes reserved
self._file.read(8*8)
self._unpacker = msgpack.Unpacker(self._file, raw=True, use_list=False)
......@@ -180,23 +179,15 @@ class FTLStreamReader:
return ftl.StreamPacket._make(v1), ftl.Packet._make(v2)
def _update_calib(self, sp, p):
''' Update calibration.
todo: fix endianess
'''
calibration = struct.unpack("@ddddIIdddd", p.data[:(4*8+2*4+4*8)])
''' Update calibration. '''
calibration = struct.unpack(_calib_fmt, p.data[:(4*8+2*4+4*8)])
self._calibration[sp.streamID] = ftl.Camera._make(calibration)
def _update_pose(self, sp, p):
''' Update pose
todo: fix endianess
'''
''' Update pose '''
pose = np.asarray(struct.unpack("@16d", p.data[:(16*8)]),
dtype=np.float64)
pose = pose.reshape((4, 4), order='F') # Eigen
self._pose[sp.streamID] = pose
def _process_json(self, sp, p):
......@@ -207,10 +198,10 @@ class FTLStreamReader:
k = (sp.streamID, sp.channel)
if k not in self._decoders:
self._decoders[k] = libde265.Decoder(ftl.definition_t[p.definition])
if k not in self._decoders_hevc:
self._decoders_hevc[k] = libde265.Decoder(ftl.definition_t[p.definition])
decoder = self._decoders[k]
decoder = self._decoders_hevc[k]
if k not in self._seen_iframe:
if not is_iframe(p.data):
......@@ -231,37 +222,39 @@ class FTLStreamReader:
# if this happens, does get_next_picture() in loop help?
warn("frame expected, no image from decoded")
self._frame = _ycrcb2rgb(img)
self._frameset_new[k] = self._frame
if ftl.is_float_channel(self._sp.channel):
raise NotImplementedError("non-color channel decoding not available")
else:
self._frame = _ycrcb2rgb(img)
def _decode_png(self, sp, p):
def _decode_opencv(self, sp, p):
try:
cv
except NameError:
raise Exception("OpenCV required for PNG decoding")
raise Exception("OpenCV required for OpenCV (png/jpeg) decoding")
self._frame = cv.imdecode(np.frombuffer(p.data, dtype=np.uint8),
cv.IMREAD_ANYDEPTH)
self._frame = self._frame.astype(np.float) / 1000.0
self._frameset_new[(sp.streamID, sp.channel)] = self._frame
cv.IMREAD_UNCHANGED)
def _flush_decoders(self):
for decoder in self._decoders.values():
decoder.flush_data()
if ftl.is_float_channel(self._sp.channel):
self._frame = self._frame.astype(np.float) / 1000.0
def seek(self, ts):
''' Read until timestamp reached '''
if self._ts >= ts:
if self.get_timestamp() >= ts:
raise Exception("trying to seek to earlier timestamp")
while self.read():
if self._ts >= ts:
if self.get_timestamp() >= ts:
break
def read(self):
'''
Reads data for until the next timestamp. Returns False if there is no
more data to read, otherwise returns True.
todo: make decoding optional
'''
self._frame = None
......@@ -270,19 +263,8 @@ class FTLStreamReader:
self._packets_read += 1
except msgpack.OutOfData:
self._frameset = self._frameset_new
self._frameset_new = {}
return False
if self._sp.timestamp < self._ts:
# old data, do not update
return True
if self._sp.timestamp > self._ts:
self._ts = self._sp.timestamp
self._frameset = self._frameset_new
self._frameset_new = {}
if self._p.block_total != 1 or self._p.block_number != 0:
raise Exception("Unsupported block format (todo)")
......@@ -299,7 +281,10 @@ class FTLStreamReader:
self._decode_hevc(self._sp, self._p)
elif self._p.codec == ftl.codec_t.PNG:
self._decode_png(self._sp, self._p)
self._decode_opencv(self._sp, self._p)
elif self._p.codec == ftl.codec_t.JPG:
self._decode_opencv(self._sp, self._p)
else:
raise Exception("unkowno codec %i" % self._p.codec)
......@@ -310,10 +295,22 @@ class FTLStreamReader:
return self._packets_read
def get_raw(self):
''' Returns previously received StreamPacket and Packet '''
return self._sp, self._p
def get_channel_type(self):
return ftl.Channel(self._sp.channel)
def get_source_id(self):
return self._sp.streamID
def get_timestamp(self):
return self._ts
return self._sp.timestamp
def get_frame(self):
''' Return decoded frame from previous packet. Returns None if previous
packet did not contain a (valid) frame. '''
return self._frame
def get_pose(self, source):
try:
......@@ -337,26 +334,7 @@ class FTLStreamReader:
return self._calibration[source]
except KeyError:
raise ValueError("source id %i not found" % source)
def get_frame(self):
''' Return decoded frame from previous packet. Returns None if previous
packet did not contain a (valid) frame. '''
return self._frame
def get_frameset(self):
return self._frameset
def get_frameset_frame(self, source, channel):
k = (source, channel)
if k in self._frameset:
return self._frameset[k]
else:
# raise an exception instead?
return None
def get_frameset_sources(self):
return list(set(src for src, _ in self._frameset.keys()))
def get_Q(self, source):
''' Disparity to depth matrix in OpenCV format '''
......@@ -374,3 +352,5 @@ class FTLStreamReader:
''' Get list of sources '''
return list(self._calibration.keys())
def get_version(self):
return self._version
......@@ -11,7 +11,7 @@ Packet = namedtuple("Packet", ["codec", "definition", "block_total",
"block_number", "flags", "data"])
StreamPacket = namedtuple("StreamPacket", ["timestamp", "streamID",
"chanel_count", "channel"])
"channel_count", "channel"])
# components/codecs/include/ftl/codecs/channels.hpp
class Channel(IntEnum):
......
......@@ -88,6 +88,12 @@ class _libde265error(IntEnum):
DE265_WARNING_SPS_MISSING_CANNOT_DECODE_SEI=1025
DE265_WARNING_COLLOCATED_MOTION_VECTOR_OUTSIDE_IMAGE_AREA=1026
class de265_chroma(IntEnum):
de265_chroma_mono = 0
de265_chroma_420 = 1
de265_chroma_422 = 2
de265_chroma_444 = 3
libde265 = ctypes.cdll.LoadLibrary("libde265.so.0")
libde265.de265_get_error_text.argtypes = [ctypes.c_void_p]
......@@ -131,6 +137,9 @@ libde265.de265_peek_next_picture.restype = ctypes.c_void_p
libde265.de265_release_next_picture.argtypes = [ctypes.c_void_p]
libde265.de265_release_next_picture.restype = None
libde265.de265_get_chroma_format.argtypes = [ctypes.c_void_p]
libde265.de265_get_chroma_format.restype = ctypes.c_int
libde265.de265_get_image_width.argtypes = [ctypes.c_void_p, ctypes.c_int]
libde265.de265_get_image_width.restype = ctypes.c_int
......@@ -172,16 +181,18 @@ class Decoder:
def _copy_image(self, de265_image):
res = np.zeros((self._size[0], self._size[1], 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))
bpp = libde265.de265_get_bits_per_pixel(de265_image, c)
if bpp != 8:
raise NotImplementedError("unsupported bits per pixel %i" % bpp)
img_ptr = libde265.de265_get_image_plane(de265_image, c, self._out_stride)
# for frombuffer() no copy assumed
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment