From f9591b939f29ac31d25e0e49a6a2d3fc4b936321 Mon Sep 17 00:00:00 2001 From: Sebastian Hahta <joseha@utu.fi> Date: Mon, 23 Mar 2020 15:25:13 +0200 Subject: [PATCH] Python SDK: FTLStreamReader --- SDK/C/src/streams.cpp | 15 +- SDK/Python/blender_script.py | 317 ------------------ SDK/Python/ftl/__init__.py | 9 +- SDK/Python/ftl/calibration.py | 43 +++ {python => SDK/Python}/ftl/codecs.py | 147 ++++++-- {python => SDK/Python}/ftl/libde265.py | 0 .../Python/ftl/streamreader.py | 79 +++-- SDK/Python/ftl/streamwriter.py | 11 + python/ftl/misc.py => SDK/Python/ftl/util.py | 55 +-- SDK/Python/test.py | 1 + SDK/Python/test/test_readwrite.py | 44 +++ SDK/Python/test/test_streamwriter.py | 29 +- SDK/{Python => }/blender.py | 19 +- python/.gitignore | 2 - python/README.md | 89 ----- python/ftl/.gitignore | 2 - python/ftl/__init__.py | 4 - python/ftl/ftlstreamwriter.py | 75 ----- python/ftl/ftltype.py | 92 ----- 19 files changed, 306 insertions(+), 727 deletions(-) delete mode 100644 SDK/Python/blender_script.py create mode 100644 SDK/Python/ftl/calibration.py rename {python => SDK/Python}/ftl/codecs.py (75%) rename {python => SDK/Python}/ftl/libde265.py (100%) rename python/ftl/ftlstreamreader.py => SDK/Python/ftl/streamreader.py (52%) rename python/ftl/misc.py => SDK/Python/ftl/util.py (76%) create mode 100644 SDK/Python/test.py create mode 100644 SDK/Python/test/test_readwrite.py rename SDK/{Python => }/blender.py (96%) delete mode 100644 python/.gitignore delete mode 100644 python/README.md delete mode 100644 python/ftl/.gitignore delete mode 100644 python/ftl/__init__.py delete mode 100644 python/ftl/ftlstreamwriter.py delete mode 100644 python/ftl/ftltype.py diff --git a/SDK/C/src/streams.cpp b/SDK/C/src/streams.cpp index f0037f7ca..4e449b24b 100644 --- a/SDK/C/src/streams.cpp +++ b/SDK/C/src/streams.cpp @@ -153,7 +153,6 @@ ftlError_t ftlImageWrite( LOG(INFO) << "MIN MAX " << minVal << " - " << maxVal; if (tmp2.empty()) return FTLERROR_STREAM_NO_DATA; - cv::flip(tmp2, tmp2, 0); // Flip to get opencv form. img.upload(tmp2); ftl::codecs::Channels<0> channels; @@ -264,12 +263,10 @@ ftlError_t ftlRemoveOcclusion(ftlStream_t stream, int32_t sourceId, ftlChannel_t //auto &mask = frame.create<cv::cuda::GpuMat>(ftl::codecs::Channel::Mask); auto &depth = frame.get<cv::cuda::GpuMat>(static_cast<ftl::codecs::Channel>(channel)); auto &intrin = frame.getLeft(); - + cv::Mat depthR(intrin.height, intrin.width, CV_32F, const_cast<float*>(data), pitch); - cv::Mat tmp; - cv::flip(depthR, tmp, 0); cv::cuda::GpuMat depthRGPU; - depthRGPU.upload(tmp); + depthRGPU.upload(depthR); ftl::cuda::remove_occlusions(depth, depthRGPU, intrin, 0); return FTLERROR_OK; @@ -291,14 +288,12 @@ ftlError_t ftlMaskOcclusion(ftlStream_t stream, int32_t sourceId, ftlChannel_t c auto &mask = frame.create<cv::cuda::GpuMat>(ftl::codecs::Channel::Mask); auto &depth = frame.get<cv::cuda::GpuMat>(static_cast<ftl::codecs::Channel>(channel)); auto &intrin = frame.getLeft(); - + mask.create(depth.size(), CV_8UC1); cv::Mat depthR(intrin.height, intrin.width, CV_32F, const_cast<float*>(data), pitch); - cv::Mat tmp; - cv::flip(depthR, tmp, 0); cv::cuda::GpuMat depthRGPU; - depthRGPU.upload(tmp); + depthRGPU.upload(depthR); ftl::cuda::mask_occlusions(depth, depthRGPU, mask, intrin, 0); ftlSelect(stream, FTLCHANNEL_Mask); @@ -334,7 +329,7 @@ ftlError_t ftlSelect(ftlStream_t stream, ftlChannel_t channel) { if (!stream->stream) return FTLERROR_STREAM_INVALID_STREAM; if (static_cast<int>(channel) < 0 || static_cast<int>(channel) > 32) return FTLERROR_STREAM_BAD_CHANNEL; - + ftl::codecs::Channels<0> channels; if (stream->stream->size() > static_cast<unsigned int>(stream->video_fs.id)) channels = stream->stream->selected(stream->video_fs.id); channels += static_cast<ftl::codecs::Channel>(channel); diff --git a/SDK/Python/blender_script.py b/SDK/Python/blender_script.py deleted file mode 100644 index 96dea4cb4..000000000 --- a/SDK/Python/blender_script.py +++ /dev/null @@ -1,317 +0,0 @@ -import bpy -import numpy as np -from mathutils import Matrix, Vector -from collections import namedtuple - -Camera = namedtuple("Camera", ["fx", "fy", "cx", "cy", "width", "height", - "min_depth", "max_depth", "baseline", "doff"]) - -_d_max = 65504.0 - -def lin2s(x): - a = 0.055 - if x <=0.0031308: - y = x * 12.92 - elif 0.0031308 < x <= 1 : - y = 1.055*(x**(1.0/2.4)) - 0.055 - - return y - -################################################################################ -# https://blender.stackexchange.com/a/120063 - -# BKE_camera_sensor_size -def get_sensor_size(sensor_fit, sensor_x, sensor_y): - if sensor_fit == 'VERTICAL': - return sensor_y - return sensor_x - -# BKE_camera_sensor_fit -def get_sensor_fit(sensor_fit, size_x, size_y): - if sensor_fit == 'AUTO': - if size_x >= size_y: - return 'HORIZONTAL' - else: - return 'VERTICAL' - return sensor_fit - -# Build intrinsic camera parameters from Blender camera data -# -# See notes on this in -# blender.stackexchange.com/questions/15102/what-is-blenders-camera-projection-matrix-model -# as well as -# https://blender.stackexchange.com/a/120063/3581 -def get_calibration_matrix_K_from_blender(camd): - if camd.type != 'PERSP': - raise ValueError('Non-perspective cameras not supported') - scene = bpy.context.scene - f_in_mm = camd.lens - scale = scene.render.resolution_percentage / 100 - resolution_x_in_px = scale * scene.render.resolution_x - resolution_y_in_px = scale * scene.render.resolution_y - sensor_size_in_mm = get_sensor_size(camd.sensor_fit, camd.sensor_width, camd.sensor_height) - sensor_fit = get_sensor_fit( - camd.sensor_fit, - scene.render.pixel_aspect_x * resolution_x_in_px, - scene.render.pixel_aspect_y * resolution_y_in_px - ) - pixel_aspect_ratio = scene.render.pixel_aspect_y / scene.render.pixel_aspect_x - if sensor_fit == 'HORIZONTAL': - view_fac_in_px = resolution_x_in_px - else: - view_fac_in_px = pixel_aspect_ratio * resolution_y_in_px - pixel_size_mm_per_px = sensor_size_in_mm / f_in_mm / view_fac_in_px - s_u = 1 / pixel_size_mm_per_px - s_v = 1 / pixel_size_mm_per_px / pixel_aspect_ratio - - # Parameters of intrinsic calibration matrix K - u_0 = resolution_x_in_px / 2 - camd.shift_x * view_fac_in_px - v_0 = resolution_y_in_px / 2 + camd.shift_y * view_fac_in_px / pixel_aspect_ratio - skew = 0 # only use rectangular pixels - - K = Matrix( - ((s_u, skew, u_0), - ( 0, s_v, v_0), - ( 0, 0, 1))) - return K - -def get_ftl_calibration_from_blender(camd): - if camd.type != 'PERSP': - raise ValueError('Non-perspective cameras not supported') - scene = bpy.context.scene - f_in_mm = camd.lens - scale = scene.render.resolution_percentage / 100 - resolution_x_in_px = scale * scene.render.resolution_x - resolution_y_in_px = scale * scene.render.resolution_y - sensor_size_in_mm = get_sensor_size(camd.sensor_fit, camd.sensor_width, camd.sensor_height) - sensor_fit = get_sensor_fit( - camd.sensor_fit, - scene.render.pixel_aspect_x * resolution_x_in_px, - scene.render.pixel_aspect_y * resolution_y_in_px - ) - pixel_aspect_ratio = scene.render.pixel_aspect_y / scene.render.pixel_aspect_x - if sensor_fit == 'HORIZONTAL': - view_fac_in_px = resolution_x_in_px - else: - view_fac_in_px = pixel_aspect_ratio * resolution_y_in_px - pixel_size_mm_per_px = sensor_size_in_mm / f_in_mm / view_fac_in_px - s_u = 1 / pixel_size_mm_per_px - s_v = 1 / pixel_size_mm_per_px / pixel_aspect_ratio - - # Parameters of intrinsic calibration matrix K - u_0 = resolution_x_in_px / 2 - camd.shift_x * view_fac_in_px - v_0 = resolution_y_in_px / 2 + camd.shift_y * view_fac_in_px / pixel_aspect_ratio - skew = 0 # only use rectangular pixels - - ftlcam = Camera(s_u, s_v, -u_0, -v_0, resolution_x_in_px, resolution_y_in_px, 0.1, 16.0, 0.15, 0.0) - return ftlcam - -# Returns camera rotation and translation matrices from Blender. -# -# There are 3 coordinate systems involved: -# 1. The World coordinates: "world" -# - right-handed -# 2. The Blender camera coordinates: "bcam" -# - x is horizontal -# - y is up -# - right-handed: negative z look-at direction -# 3. The desired computer vision camera coordinates: "cv" -# - x is horizontal -# - y is down (to align to the actual pixel coordinates -# used in digital images) -# - right-handed: positive z look-at direction -def get_3x4_RT_matrix_from_blender(cam): - # bcam stands for blender camera - R_bcam2cv = Matrix( - ((1, 0, 0), - (0, -1, 0), - (0, 0, -1))) - - # Transpose since the rotation is object rotation, - # and we want coordinate rotation - # R_world2bcam = cam.rotation_euler.to_matrix().transposed() - # T_world2bcam = -1*R_world2bcam * location - # - # Use matrix_world instead to account for all constraints - location, rotation = cam.matrix_world.decompose()[0:2] - R_world2bcam = rotation.to_matrix().transposed() - - # Convert camera location to translation vector used in coordinate changes - # T_world2bcam = -1*R_world2bcam*cam.location - # Use location from matrix_world to account for constraints: - T_world2bcam = -1*R_world2bcam @ location - - # Build the coordinate transform matrix from world to computer vision camera - R_world2cv = R_bcam2cv@R_world2bcam - T_world2cv = R_bcam2cv@T_world2bcam - - # put into 3x4 matrix - RT = Matrix(( - R_world2cv[0][:] + (T_world2cv[0],), - R_world2cv[1][:] + (T_world2cv[1],), - R_world2cv[2][:] + (T_world2cv[2],) - )) - return RT - -def get_3x4_P_matrix_from_blender(cam): - K = get_calibration_matrix_K_from_blender(cam.data) - RT = get_3x4_RT_matrix_from_blender(cam) - return K@RT, K, RT - -################################################################################ - -import typing - -class StereoImage(typing.NamedTuple): - intrinsics: Camera - pose: np.array - baseline: float - imL: np.array - depthL: np.array - imR: np.array - depthR: np.array - -def render(): - """ render active camera (image and depth) """ - - bpy.context.scene.use_nodes = True - tree = bpy.context.scene.node_tree - links = tree.links - - for n in tree.nodes: - tree.nodes.remove(n) - - rl = tree.nodes.new('CompositorNodeRLayers') - - v = tree.nodes.new('CompositorNodeViewer') - v.use_alpha = True - - links.new(rl.outputs['Image'], v.inputs['Image']) - # depth cannot be accessed in python; hack uses alpha to store z-values - links.new(rl.outputs['Depth'], v.inputs['Alpha']) - - bpy.ops.render.render() - pixels = bpy.data.images['Viewer Node'] - pix = np.array(pixels.pixels[:]) - - # sRGB conversion - #pix2 = np.zeros(pix.shape[:], dtype=np.float) - pix2 = np.copy(pix) - np.copyto(pix2, 1.055*(pix**(1.0/2.4)) - 0.055, where=pix <= 1) - np.copyto(pix2, pix * 12.92, where=pix <= 0.0031308) - - # Clamp? - pix2[pix2 > 1.0] = 1.0 - - - im = pix2.reshape((pixels.size[1], pixels.size[0], pixels.channels)) - im2 = (im[:,:,0:3]).astype(np.float32) - - depthim = (np.array(pixels.pixels[:]).reshape((pixels.size[1], pixels.size[0], pixels.channels))[:,:,3]).astype(np.float32) - # set invalid depth values to 0.0 - depthim[depthim >= _d_max] = 0.0 - - return im2, depthim - -def render_stereo(camera, baseline=0.15): - bpy.context.scene.camera = camera - #_, K, pose = get_3x4_P_matrix_from_blender(camera) - ftlcam = get_ftl_calibration_from_blender(camera.data) - pose = get_3x4_RT_matrix_from_blender(camera) - imL, depthL = render() - - location_old = camera.location.copy() - camera.location = (camera.matrix_world @ Vector((baseline, 0.0, 0.0, 1.0)))[0:3] - - imR, depthR = render() - - camera.location = location_old - - return StereoImage(Camera(ftlcam.fx, ftlcam.fy, ftlcam.cx, ftlcam.cy, ftlcam.width, ftlcam.height, 0.1, np.amax(depthL), baseline, 0.0), pose, baseline, imL, depthL, imR, depthR) - - -# ====== Load the FTL SDK ====================================================== -# TODO: Wrap this properly -from ctypes import * -ftl = CDLL('/home/nick/git-repos/ftl/build/SDK/C/libftl-dev.so.0') - -ftlCreateWriteStream = ftl.ftlCreateWriteStream -ftlCreateWriteStream.restype = c_void_p -ftlCreateWriteStream.argtypes = [c_char_p] - -ftlIntrinsicsWriteLeft = ftl.ftlIntrinsicsWriteLeft -ftlIntrinsicsWriteLeft.restype = c_int -ftlIntrinsicsWriteLeft.argtypes = [c_void_p, c_int, c_int, c_int, c_float, c_float, c_float, c_float, c_float, c_float] - -ftlIntrinsicsWriteRight = ftl.ftlIntrinsicsWriteRight -ftlIntrinsicsWriteRight.restype = c_int -ftlIntrinsicsWriteRight.argtypes = [c_void_p, c_int, c_int, c_int, c_float, c_float, c_float, c_float, c_float, c_float] - -ftlImageWrite = ftl.ftlImageWrite -ftlImageWrite.restype = c_int -ftlImageWrite.argtypes = [c_void_p, c_int, c_int, c_int, c_int, c_void_p] - -ftlRemoveOcclusion = ftl.ftlRemoveOcclusion -ftlRemoveOcclusion.restype = c_int -ftlRemoveOcclusion.argtypes = [c_void_p, c_int, c_int, c_int, c_void_p] - -ftlMaskOcclusion = ftl.ftlMaskOcclusion -ftlMaskOcclusion.restype = c_int -ftlMaskOcclusion.argtypes = [c_void_p, c_int, c_int, c_int, c_void_p] - -ftlEnablePipeline = ftl.ftlEnablePipeline -ftlEnablePipeline.restype = c_int -ftlEnablePipeline.argtypes = [c_void_p, c_int] - -ftlSelect = ftl.ftlSelect -ftlSelect.restype = c_int -ftlSelect.argtypes = [c_void_p, c_int] - -ftlPoseWrite = ftl.ftlPoseWrite -ftlPoseWrite.restype = c_int -ftlPoseWrite.argtypes = [c_void_p, c_int, c_void_p] - -ftlDestroyStream = ftl.ftlDestroyStream -ftlDestroyStream.restype = c_int -ftlDestroyStream.argtypes = [c_void_p] - -# ============================================================================== - -def ftlCheck(err): - if err != 0: - print("FTL SDK Error: ", err) - -def render_and_save(filename, cameras): - i = 0 - - stream = ftlCreateWriteStream(filename) - if stream == None: - print("Could not create FTL stream") - return - - ftlCheck(ftlEnablePipeline(stream, 0)) - - for camname in cameras: - obj = bpy.context.scene.objects[camname] - if obj.type == 'CAMERA': - image = render_stereo(obj, 0.15) - - ftlCheck(ftlIntrinsicsWriteLeft(c_void_p(stream), c_int(i), c_int(int(image.intrinsics.width)), c_int(int(image.intrinsics.height)), c_float(image.intrinsics.fx), c_float(image.intrinsics.cx), c_float(image.intrinsics.cy), c_float(image.intrinsics.baseline), c_float(image.intrinsics.min_depth), c_float(image.intrinsics.max_depth))) - ftlCheck(ftlIntrinsicsWriteRight(c_void_p(stream), c_int(i), c_int(int(image.intrinsics.width)), c_int(int(image.intrinsics.height)), c_float(image.intrinsics.fx), c_float(image.intrinsics.cx), c_float(image.intrinsics.cy), c_float(image.intrinsics.baseline), c_float(image.intrinsics.min_depth), c_float(image.intrinsics.max_depth))) - - M = np.identity(4,dtype=np.float32) - M[0:3,0:4] = image.pose - M = np.transpose(M) - M = np.linalg.inv(M) - ftlCheck(ftlPoseWrite(stream, c_int(i), M.ctypes.data_as(c_void_p))) - - ftlCheck(ftlImageWrite(stream, c_int(i), 0, 5, 0, image.imL.ctypes.data_as(c_void_p))) - ftlCheck(ftlImageWrite(stream, c_int(i), 2, 5, 0, image.imR.ctypes.data_as(c_void_p))) - ftlCheck(ftlImageWrite(stream, c_int(i), 22, 0, 0, image.depthL.ctypes.data_as(c_void_p))) - ftlCheck(ftlMaskOcclusion(stream, c_int(i), 22, 0, image.depthR.ctypes.data_as(c_void_p))) - i = i + 1 - - ftlCheck(ftlDestroyStream(stream)) - -render_and_save(b'./blender.ftl', ['Camera']) - diff --git a/SDK/Python/ftl/__init__.py b/SDK/Python/ftl/__init__.py index 1fdef3ada..0ac811185 100644 --- a/SDK/Python/ftl/__init__.py +++ b/SDK/Python/ftl/__init__.py @@ -1,3 +1,10 @@ from . streamwriter import FTLStreamWriter from . types import Camera -import types \ No newline at end of file +import types + +from warnings import warn as _warn + +try: + from . streamreader import FTLStreamReader +except ImportError as e: + _warn("Could not import StreamReader, missing dependecies? %s" % str(e)) \ No newline at end of file diff --git a/SDK/Python/ftl/calibration.py b/SDK/Python/ftl/calibration.py new file mode 100644 index 000000000..36fdab2f0 --- /dev/null +++ b/SDK/Python/ftl/calibration.py @@ -0,0 +1,43 @@ +import numpy as np +from . types import Camera + +def get_camera_matrix(calib): + K = np.identity(3, dtype=np.float64) + K[0,0] = calib.fx + K[1,1] = calib.fy + K[0,2] = -calib.cx + K[1,2] = -calib.cy + return K + +def get_Q(calib): + """ Disparity to depth matrix. Explained in "Learning OpenCV: Computer + Vision with the OpenCV Library" (2008) p. 435. + """ + Q = np.identity(4, dtype=np.float64) + Q[0,3] = calib.cx + Q[1,3] = calib.cy + Q[2,2] = 0.0 + Q[2,3] = calib.fx + Q[3,2] = -1 / calib.baseline + Q[3,3] = calib.doff + return Q + +class Calibration: + @staticmethod + def from_K(K, size, min_depth=0.0, max_depth=100.0, baseline=0.0, doff=0.0): + calib = Camera._make([K[0,0], K[1,1], K[0,2], K[1,2], size[1], size[0], + min_depth, max_depth, baseline, doff]) + return Calibration(calib, None, None) + + def __init__(self, calib, channel, capabilities): + self._calib = calib + self._capabilities = capabilities + + def matrix(self): + return get_camera_matrix(self._calib) + + def Q(self): + return get_Q(self._calib) + + def camera(self): + return self._calib diff --git a/python/ftl/codecs.py b/SDK/Python/ftl/codecs.py similarity index 75% rename from python/ftl/codecs.py rename to SDK/Python/ftl/codecs.py index bf4e21e68..dd14c27d8 100644 --- a/python/ftl/codecs.py +++ b/SDK/Python/ftl/codecs.py @@ -4,12 +4,67 @@ import msgpack import struct from warnings import warn -from . import ftltype +from . types import Channel, Camera, is_float_channel from . import libde265 -from . misc import Calibration +from . calibration import Calibration +from collections import namedtuple from enum import IntEnum +################################################################################ + +# components/codecs/include/ftl/codecs/packet.hpp +Packet = namedtuple("Packet", ["codec", "definition", "block_total", + "block_number", "flags", "data"]) + +StreamPacket = namedtuple("StreamPacket", ["timestamp", "frameset_id", + "frame_number", "channel"]) + +class PacketFlags: + RGB = 0x01 + MappedDepth = 0x02 + Float = 0x04 + Partial = 0x10 + Multiple = 0x80 + +# components/codecs/include/ftl/codecs/codecs.hpp +class codec_t(IntEnum): + JPG = 0 + PNG = 1 + H264 = 2 + HEVC = 3 + WAV = 4 + JSON = 100 + CALIBRATION = 101 + POSE = 102 + MSGPACK = 103, + STRING = 104, + RAW = 105 + +definition_t = { + 0 : (7680, 4320), + 1 : (2160, 3840), + 2 : (1080, 1920), + 3 : (720, 1280), + 4 : (576, 1024), + 5 : (480, 854), + 6 : (360, 640), + 7 : (0, 0), + 8 : (2056, 1852) +} + +def get_definition(shape): + for k, v in definition_t.items(): + if shape[:2] == v: + return k + + return 7 # (None) + +def process_flags_image(packet, im): + return im + +################################################################################ + _has_opencv = False try: import cv2 as cv @@ -71,19 +126,41 @@ class FTLDecoder: # OpenCV (optional) ################################################################################ +def split_images(packet, im): + if packet.block_total == 1: + return im + + n = packet.block_total + height, width = definition_t[packet.definition] + cols = im.shape[1] // width + + imgs = [] + for i in range(0, n): + y_start = (i//cols) * height + y_end = y_start + height + x_start = (i%cols) * width + x_end = x_start + width + imgs.append(im[y_start:y_end,x_start:x_end,...]) + + return imgs + def decode_codec_opencv(packet): if packet.block_total != 1 or packet.block_number != 0: - raise Exception("Unsupported block format (todo)") + warn("Unsupported block format (todo)") # is this relevant? - return _int_to_float(cv.imdecode(np.frombuffer(packet.data, dtype=np.uint8), - cv.IMREAD_UNCHANGED)) + im = _int_to_float(cv.imdecode(np.frombuffer(packet.data, dtype=np.uint8), + cv.IMREAD_UNCHANGED)) + + return split_images(packet, im) def decode_codec_opencv_float(packet): if packet.block_total != 1 or packet.block_number != 0: - raise Exception("Unsupported block format (todo)") + warn("Unsupported block format (todo)") # is this relevant? + + im = cv.imdecode(np.frombuffer(packet.data, dtype=np.uint8), + cv.IMREAD_UNCHANGED).astype(np.float) / 1000.0 - return cv.imdecode(np.frombuffer(packet.data, dtype=np.uint8), - cv.IMREAD_UNCHANGED).astype(np.float) / 1000.0 + return split_images(packet, im) ################################################################################ # HEVC @@ -237,7 +314,7 @@ class FTLDecoder_HEVC_Float(FTLDecoder_HEVC): if img is None: return None - if (packet.flags & ftltype.PacketFlags.MappedDepth): + if (packet.flags & PacketFlags.MappedDepth): return self._decode_format_mappeddepth(img) else: @@ -265,11 +342,11 @@ class FTLDecoder_HEVC_YCbCr(FTLDecoder_HEVC): def decode_codec_calibration(packet): calibration = struct.unpack("@ddddIIdddd", packet.data[:(4*8+2*4+4*8)]) - return Calibration(ftltype.Camera._make(calibration), 0, 0) + return Calibration(Camera._make(calibration), 0, 0) def decode_codec_msgpack_calibration(packet): calib, channel, capabilities = msgpack.unpackb(packet.data) - return Calibration(ftltype.Camera._make(calib), channel, capabilities) + return Calibration(Camera._make(calib), channel, capabilities) def decode_codec_msgpack_pose(packet): raw = msgpack.unpackb(packet.data) @@ -292,8 +369,8 @@ def create_decoder(codec, channel, version=3): @returns callable which takes packet as argument """ - if codec == ftltype.codec_t.HEVC: - if ftltype.is_float_channel(channel): + if codec == codec_t.HEVC: + if is_float_channel(channel): return FTLDecoder_HEVC_Float().decode else: if version < 3: @@ -301,33 +378,33 @@ def create_decoder(codec, channel, version=3): else: return FTLDecoder_HEVC_YCbCr().decode - elif codec == ftltype.codec_t.PNG: + elif codec == codec_t.PNG: if not _has_opencv: raise Exception("OpenCV required for OpenCV (png/jpeg) decoding") - if ftltype.is_float_channel(channel): + if is_float_channel(channel): return decode_codec_opencv_float else: return decode_codec_opencv - elif codec == ftltype.codec_t.JPG: + elif codec == codec_t.JPG: if not _has_opencv: raise Exception("OpenCV required for OpenCV (png/jpeg) decoding") return decode_codec_opencv - elif codec == ftltype.codec_t.MSGPACK: - if channel == ftltype.Channel.Calibration: + elif codec == codec_t.MSGPACK: + if channel == Channel.Calibration: return decode_codec_msgpack_calibration - elif channel == ftltype.Channel.Pose: + elif channel == Channel.Pose: return decode_codec_msgpack_pose else: return lambda packet: msgpack.unpackb(packet.data) - elif codec == ftltype.codec_t.CALIBRATION: + elif codec == codec_t.CALIBRATION: return decode_codec_calibration - elif codec == ftltype.codec_t.POSE: + elif codec == codec_t.POSE: return decode_codec_pose else: @@ -338,7 +415,7 @@ def create_decoder(codec, channel, version=3): ################################################################################ def create_packet(codec, definition, flags, data): - return ftltype.Packet._make((codec, definition, 1, 0, flags, data)) + return Packet._make((codec, definition, 1, 0, flags, data)) # TODO exception types? @@ -346,8 +423,8 @@ def encode_codec_opencv_jpg(data, **kwargs): params = [] retval, encoded = cv.imencode(".jpg", data, params) if retval: - return create_packet(ftltype.codec_t.JPG, - ftltype.get_definition(data), + return create_packet(codec_t.JPG, + get_definition(data), 0, encoded) else: @@ -358,8 +435,8 @@ def encode_codec_opencv_png(data, **kwargs): params = [cv.IMWRITE_PNG_COMPRESSION, 9] retval, encoded = cv.imencode(".png", data, params) if retval: - return create_packet(ftltype.codec_t.PNG, - ftltype.get_definition(data), + return create_packet(codec_t.PNG, + get_definition(data), 0, encoded) else: @@ -371,8 +448,8 @@ def encode_codec_opencv_png_float(data, compression=9): params = [cv.IMWRITE_PNG_COMPRESSION, compression] retval, encoded = cv.imencode(".png", data, params) if retval: - return create_packet(ftltype.codec_t.PNG, - ftltype.get_definition(data), + return create_packet(codec_t.PNG, + get_definition(data), 0, encoded) else: @@ -387,23 +464,23 @@ def create_encoder(codec, channel, **options): @returns callable which takes unencoded data and optional parameters """ - if codec == ftltype.codec_t.JPG: - if not ftltype.is_float_channel(channel): + if codec == codec_t.JPG: + if not is_float_channel(channel): return encode_codec_opencv_jpg else: raise Exception("JPG not supported for float channels") - elif codec == ftltype.codec_t.PNG: - if ftltype.is_float_channel(channel): + elif codec == codec_t.PNG: + if is_float_channel(channel): return encode_codec_opencv_png_float else: return encode_codec_opencv_png - elif codec == ftltype.codec_t.MSGPACK: - if channel == ftltype.Channel.Pose: + elif codec == codec_t.MSGPACK: + if channel == Channel.Pose: raise NotImplementedError("todo") - elif channel == ftltype.Channel.Calibration: + elif channel == Channel.Calibration: raise NotImplementedError("todo") else: diff --git a/python/ftl/libde265.py b/SDK/Python/ftl/libde265.py similarity index 100% rename from python/ftl/libde265.py rename to SDK/Python/ftl/libde265.py diff --git a/python/ftl/ftlstreamreader.py b/SDK/Python/ftl/streamreader.py similarity index 52% rename from python/ftl/ftlstreamreader.py rename to SDK/Python/ftl/streamreader.py index 1a33d16f3..e5c82c846 100644 --- a/python/ftl/ftlstreamreader.py +++ b/SDK/Python/ftl/streamreader.py @@ -3,21 +3,23 @@ import numpy as np from warnings import warn -from . import ftltype -from . codecs import create_decoder +from . types import Channel, Camera +from . codecs import create_decoder, StreamPacket, Packet, PacketFlags class FTLStreamReader: """ FTL file reader. """ - def __init__(self, file): + def __init__(self, file, max_buffer_size=64*2**20): self._file = open(file, "br") self._version = 0 self._decoders = {} - self._enabled_sources = [] self._available_sources = [] - self._data = None + self._p = None + self._sp = None + self._decoded = None + self._frames = [] try: magic = self._file.read(5) @@ -29,7 +31,9 @@ class FTLStreamReader: # first 64 bytes reserved self._file.read(8*8) - self._unpacker = msgpack.Unpacker(self._file, raw=True, use_list=False) + self._unpacker = msgpack.Unpacker(self._file, raw=True, + use_list=False, + max_buffer_size=max_buffer_size) except Exception as ex: self._file.close() @@ -40,9 +44,28 @@ class FTLStreamReader: def __del__(self): self._file.close() + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self._file.close() + + def __iter__(self): + return self + + def __next__(self): + if len(self._frames) > 0: + return self._frames.pop(0) + + if not self.read(): + raise StopIteration + + self._frames = self.get() + return self._frames.pop(0) + def _read_next(self): v1, v2 = self._unpacker.unpack() - return ftltype.StreamPacket._make(v1), ftltype.Packet._make(v2) + return StreamPacket._make(v1), Packet._make(v2) def seek(self, ts): """ Read until timestamp reached """ @@ -58,7 +81,7 @@ class FTLStreamReader: Reads data for until the next timestamp. Returns False if there is no more data to read, otherwise returns True. """ - self._data = None + self._decoded = None try: self._sp, self._p = self._read_next() @@ -67,39 +90,41 @@ class FTLStreamReader: except msgpack.OutOfData: return False - if self._sp.streamID not in self._available_sources: - self._available_sources.append(self._sp.streamID) - - if self._enabled_sources and self._sp.streamID not in self._enabled_sources: - return True + if self._sp.frameset_id not in self._available_sources: + self._available_sources.append(self._sp.frameset_id) - k = (self._sp.streamID, self._sp.channel) + k = (self._sp.frameset_id, self._sp.channel) if k not in self._decoders: self._decoders[k] = create_decoder(self._p.codec, self._sp.channel, self._version) - self._data = self._decoders[k](self._p) - + self._decoded = self._decoders[k](self._p) return True + def get(self): + if isinstance(self._decoded, list): + if self._p.flags == PacketFlags.Partial: + raise NotImplementedError("partial packets not implemented (todo)") + + res = [] + for src, data in enumerate(self._decoded): + # How are sources and frames mapped? What if frame missing from + # one of the sources? + res.append((self.get_timestamp(), src, Channel(self._sp.channel), data)) + + return res + + else: + return [(self.get_timestamp(), self._sp.frame_number, Channel(self._sp.channel), self._decoded)] + def get_raw(self): """ Returns previously read StreamPacket and Packet """ return self._sp, self._p - def get_channel(self): - return ftltype.Channel(self._sp.channel) - - def get_source_id(self): - return self._sp.streamID - def get_timestamp(self): return self._sp.timestamp - def get_data(self): - """ Returns decoded data """ - return self._data - def get_sources(self): - """ Return list of sources. Can change as stream is read. """ + """ Return list of sources. """ return list(self._available_sources) def get_version(self): diff --git a/SDK/Python/ftl/streamwriter.py b/SDK/Python/ftl/streamwriter.py index ce8d86479..b4703ade2 100644 --- a/SDK/Python/ftl/streamwriter.py +++ b/SDK/Python/ftl/streamwriter.py @@ -99,10 +99,21 @@ class FTLStreamWriter: if self._instance is None: raise Exception("Error: ftlCreateWriteStream") + def close(self): + if self._instance is not None: + _ftl_check(_c_api.ftlDestroyStream(self._instance)) + self._instance = None + def __del__(self): if self._instance is not None: _c_api.ftlDestroyStream(self._instance) + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self.close() + def _check_image(self, source, channel, data): """ Check image is has correct number of channels and correct size and convert to compatible datatype if necessary. Raises diff --git a/python/ftl/misc.py b/SDK/Python/ftl/util.py similarity index 76% rename from python/ftl/misc.py rename to SDK/Python/ftl/util.py index cde61b973..71ede7160 100644 --- a/python/ftl/misc.py +++ b/SDK/Python/ftl/util.py @@ -1,66 +1,23 @@ import numpy as np -from . ftltype import Camera - -def get_camera_matrix(calib): - K = np.identity(3, dtype=np.float64) - K[0,0] = calib.fx - K[1,1] = calib.fy - K[0,2] = calib.cx - K[1,2] = calib.cy - return K - -def get_Q(calib): - """ Disparity to depth matrix. Explained in "Learning OpenCV: Computer - Vision with the OpenCV Library" (2008) p. 435. - """ - Q = np.identity(4, dtype=np.float64) - Q[0,3] = calib.cx - Q[1,3] = calib.cy - Q[2,2] = 0.0 - Q[2,3] = calib.fx - Q[3,2] = -1 / calib.baseline - Q[3,3] = calib.doff - return Q - -def disparity_to_depth(disparity, camera, max_depth=10.0, invalid_value=0.0): +from . types import Camera + +def disparity_to_depth(disparity, camera, invalid_value=0.0): """ Calculate depth map from disparity map. Depth values smaller than 0.0 and larger than max_depth are set to invalid_value. """ depth = (camera.fx * camera.baseline) / (disparity - camera.doff) depth[depth < 0] = invalid_value - depth[depth > max_depth] = invalid_value + depth[depth > camera.max_depth] = invalid_value return depth def depth_to_disparity(depth, camera, invalid_value=0.0): """ Calculate disparity from depth image. Inverse of disparity_to_depth(). """ - invalid = depth == 0.0 - depth[invalid] = 1.0 - disparity = ((camera.fx * camera.baseline) / depth) + camera.doff - disparity[invalid] = invalid_value + valid = depth != invalid_value + disparity = np.divide((camera.fx * camera.baseline), depth, where=valid) + camera.doff return disparity -class Calibration: - @staticmethod - def from_K(K, size, min_depth=0.0, max_depth=100.0, baseline=0.0, doff=0.0): - calib = Camera._make([K[0,0], K[1,1], K[0,2], K[1,2], size[1], size[0], - min_depth, max_depth, baseline, doff]) - return Calibration(calib, None, None) - - def __init__(self, calib, channel, capabilities): - self._calib = calib - self._capabilities = capabilities - - def matrix(self): - return get_camera_matrix(self._calib) - - def Q(self): - return get_Q(self._calib) - - def params(self): - return self._calib - def point3d(calib, u, v, d): """ Calculate point 3D coordinates @param calib calibration diff --git a/SDK/Python/test.py b/SDK/Python/test.py new file mode 100644 index 000000000..308486e74 --- /dev/null +++ b/SDK/Python/test.py @@ -0,0 +1 @@ +from ftl import FTLStreamReader \ No newline at end of file diff --git a/SDK/Python/test/test_readwrite.py b/SDK/Python/test/test_readwrite.py new file mode 100644 index 000000000..030dcb7d0 --- /dev/null +++ b/SDK/Python/test/test_readwrite.py @@ -0,0 +1,44 @@ +import unittest +import tempfile +import os + +from ftl import FTLStreamWriter, FTLStreamReader +from ftl.types import Channel, Camera, FTLException + +import numpy as np +import cv2 + +class TestStreamWriter(unittest.TestCase): + + def test_read_write_frames_uint8_1080p(self): + """ Write calibration and random 1080p image and then read them """ + + f = tempfile.NamedTemporaryFile(suffix=".ftl") + + frames = [ + (0, Channel.Calibration, Camera(700.0, 700.0, 960.0, 540.0, 1920, 1080, 0.0, 10.0, 0.25, 0.0)), + (0, Channel.Colour, (np.random.rand(1080, 1920, 3) * 255).astype(np.uint8)) + ] + + with FTLStreamWriter(f.name) as writer: + for frame in frames: + writer.write(*frame) + + with FTLStreamReader(f.name) as reader: + for (_, src, channel, data), orig in zip(reader, frames): + self.assertEqual(src, orig[0]) + self.assertEqual(channel, orig[1]) + + if channel == Channel.Calibration: + # floating point accuracy can cause issues (writer uses 32 + # bit representation while python uses 64 by default) + self.assertEqual(data.camera(), orig[2]) + + elif channel == Channel.Colour: + # reader returns color channel as float, while writer + # internally converts it to uint8 + im = (data[:,:,0:3] * 255).astype(np.uint8) + self.assertTrue(np.array_equal(im, orig[2])) + +if __name__ == '__main__': + unittest.main() diff --git a/SDK/Python/test/test_streamwriter.py b/SDK/Python/test/test_streamwriter.py index b822a739c..cd6b31dee 100644 --- a/SDK/Python/test/test_streamwriter.py +++ b/SDK/Python/test/test_streamwriter.py @@ -10,17 +10,18 @@ import numpy as np class TestStreamWriter(unittest.TestCase): def test_create_and_delete_file(self): - """ Test constructor and destructor (empty file) """ + """ Test constructor and destructor """ - with tempfile.NamedTemporaryFile(suffix=".ftl") as f: - stream = FTLStreamWriter(f.name) - stream.__del__() + f = tempfile.NamedTemporaryFile(suffix=".ftl") + stream = FTLStreamWriter(f.name) + stream.close() + stream.__del__() def test_write_frames_float32_1080p(self): """ Write random image, correct types and values """ - with tempfile.NamedTemporaryFile(suffix=".ftl") as f: - stream = FTLStreamWriter(f.name) + f = tempfile.NamedTemporaryFile(suffix=".ftl") + with FTLStreamWriter(f.name) as stream: calib = Camera(700.0, 700.0, 960.0, 540.0, 1920, 1080, 0.0, 10.0, 0.20, 0.0) im = np.random.rand(1080, 1920, 3).astype(np.float32) @@ -29,29 +30,27 @@ class TestStreamWriter(unittest.TestCase): def test_write_calib_wrong_compatible_type(self): """ Write calibration with incorrect but compatible types (float/int) """ + f = tempfile.NamedTemporaryFile(suffix=".ftl") - with tempfile.NamedTemporaryFile(suffix=".ftl") as f: - stream = FTLStreamWriter(f.name) + with FTLStreamWriter(f.name) as stream: calib = Camera(700, 700.0, 960, 540.0, 1920.0, 1080, 0, 10.0, 0.2, 0) - stream.write(0, Channel.Calibration, calib) def test_write_calib_wrong_incompatible_type(self): """ Write calibration with incorrect and incompatible types """ - with tempfile.NamedTemporaryFile(suffix=".ftl") as f: - stream = FTLStreamWriter(f.name) - calib = Camera("foo", "bar", 960, 540.0, 1920.0, 1080, 0, 10.0, 0.2, 0) + f = tempfile.NamedTemporaryFile(suffix=".ftl") + with FTLStreamWriter(f.name) as stream: + calib = Camera("foo", "bar", 960, 540.0, 1920.0, 1080, 0, 10.0, 0.2, 0) with self.assertRaises(ValueError): stream.write(0, Channel.Calibration, calib) def test_empty_nextframe(self): """ Call nextframe() on empty stream """ - with tempfile.NamedTemporaryFile(suffix=".ftl") as f: - stream = FTLStreamWriter(f.name) - + f = tempfile.NamedTemporaryFile(suffix=".ftl") + with FTLStreamWriter(f.name) as stream: with self.assertRaises(FTLException): stream.next_frame() diff --git a/SDK/Python/blender.py b/SDK/blender.py similarity index 96% rename from SDK/Python/blender.py rename to SDK/blender.py index c238dc99b..517f11dec 100644 --- a/SDK/Python/blender.py +++ b/SDK/blender.py @@ -259,27 +259,28 @@ class FTL_OT_Operator(bpy.types.Operator): for i, camera in enumerate(cameras): res = render_stereo(camera, baseline) - writer.write(i, Channel.Calibration, res.intrinsics) - writer.write(i, Channel.Pose, res.pose) - writer.write(i, Channel.Left, res.imL) - writer.write(i, Channel.Right, res.imR) - depthL = res.depthL - depthR = res.depthR + imR = np.flip(res.imR, 0) + imL = np.flip(res.imL, 0) + depthL = np.flip(res.depthL, 0) + depthR = np.flip(res.depthR, 0) if options.depth_eevee and context.scene.render.engine != 'BLENDER_EEVEE': engine = context.scene.render.engine try: context.scene.render.engine = 'BLENDER_EEVEE' res_eevee = render_stereo(camera, baseline) - depthL = res_eevee.depthL - depthR = res_eevee.depthR + depthL = np.flip(res_eevee.depthL, 0) + depthR = np.flip(res_eevee.depthR, 0) finally: context.scene.render.engine = engine + writer.write(i, Channel.Calibration, res.intrinsics) + writer.write(i, Channel.Pose, res.pose) + writer.write(i, Channel.Left, imL) + writer.write(i, Channel.Right, imR) writer.write(i, depth_channel, depthL) - if options.mask_occlusions: writer.mask_occlusion(i, depth_channel, depthR) diff --git a/python/.gitignore b/python/.gitignore deleted file mode 100644 index feae5c192..000000000 --- a/python/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.pyc -__pycache__ \ No newline at end of file diff --git a/python/README.md b/python/README.md deleted file mode 100644 index a2918492f..000000000 --- a/python/README.md +++ /dev/null @@ -1,89 +0,0 @@ -Python support for `.ftl` files. At the moment, only reading RGB channels -(left/right) supported. Non-parallel decoding of 8 streams has a frame rate -of ~15 fps on i7-9700K. - -Required **Python** modules: - - * msgpack - * numpy - * skimage **or** OpenCV - -Required libraries - - * libde265 (available on most Linux distributions) - -## Example - -Example reads from `input.ftl` and writes to `output.ftl`. Calibration and -pose are copied directly (same method can be used for other channels as well). -The program copies left and right frames of source 0 to new file (and re-encodes -them in JPG) when both frames are available. - -```python -import ftl -from ftl import types - -reader = ftl.FTLStreamReader("./input.ftl") -writer = ftl.FTLStreamWriter("./output") - -source_id = 0 -fps = 25 -frame_t = int(1000.0/fps) -timestamp_out = 0 -timestamp_in = 0 - -im_left = None -im_right = None - -while reader.read(): - channel = reader.get_channel_type() - timestamp = reader.get_timestamp() - frame = reader.get_frame() - - if reader.get_source_id() != source_id: - # not interested in this source, skip - continue - - if channel in (types.Channel.Calibration, types.Channel.Configuration): - # copy calibration and pose (but replace timestamp with new value) - - sp, p = reader.get_raw() - sp = sp._replace(timestamp=timestamp_out) - writer.add_raw(sp, p) - continue - - if channel not in (types.Channel.Left, types.Channel.Right): - # only interested in left and right frame - continue - - if frame is None: - # no frame if decoding failed - continue - - if timestamp_in != timestamp: - # new timestamp, process available frames - - if not (im_left is None or im_right is None): - # save frames only if both of them were found for this timestamp - - # Note: In this expample channel is re-encoded. If channel content - # is not modified, lossy channels should be passed directly - # (using add_raw() in same way as for calibration/pose) instead of - # re-encoding them. - - writer.add_frame(timestamp_out, 0, types.Channel.Left, 2, - types.codec_t.JPG, im_left) - writer.add_frame(timestamp_out, 0, types.Channel.Right, 2, - types.codec_t.JPG, im_right) - - - timestamp_out += frame_t - timestamp_in = timestamp - im_left, im_right = None, None - - if channel is types.Channel.Left: - im_left = frame - else: - im_right = frame - -``` diff --git a/python/ftl/.gitignore b/python/ftl/.gitignore deleted file mode 100644 index a295864e3..000000000 --- a/python/ftl/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.pyc -__pycache__ diff --git a/python/ftl/__init__.py b/python/ftl/__init__.py deleted file mode 100644 index 9f7ea874e..000000000 --- a/python/ftl/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from . ftlstreamreader import FTLStreamReader -#from . ftlstreamwriter import FTLStreamWriter - -from . import ftltype as types diff --git a/python/ftl/ftlstreamwriter.py b/python/ftl/ftlstreamwriter.py deleted file mode 100644 index 3dfc8903b..000000000 --- a/python/ftl/ftlstreamwriter.py +++ /dev/null @@ -1,75 +0,0 @@ -import msgpack -import struct - -from . import ftltype - -from codecs import create_encoder - -class FTLStreamWriter: - def __init__(self, file, version=3): - self._file = open(file, "wb") - self._file.write(bytes(ord(c) for c in "FTLF")) # magic - self._file.write(bytes([version])) # version - self._file.write(bytes([0]*64)) # reserved - self._packer = msgpack.Packer(strict_types=False, use_bin_type=True) - - self._encoders = {} - self._channel_count = 0 - - def __del__(self): - self.close() - - def close(self): - self._file.close() - - def add_raw(self, sp, p): - if len(sp) != len(ftltype.StreamPacket._fields): - raise ValueError("invalid StreamPacket") - - if len(p) != len(ftltype.Packet._fields): - raise ValueError("invalid Packet") - - self._file.write(self._packer.pack((sp, p))) - self._file.flush() - - def create_encoder(self, source, codec, channel, **kwargs): - if channel not in ftltype.Channel: - raise ValueError("unknown channel") - - if not isinstance(source, int): - raise ValueError("source id must be int") - - if source < 0: - raise ValueError("source id must be positive") - - encoder = create_encoder(codec, channel, **kwargs) - self._encoders[(int(source), int(channel))] = encoder - self._channel_count += 1 - - def encode(self, source, timestamp, channel, data): - if not isinstance(source, int): - raise ValueError("source id must be int") - - if source < 0: - raise ValueError("source id must be positive") - - if timestamp < 0: - raise ValueError("timestamp must be positive") - - if channel not in ftltype.Channel: - raise ValueError("unknown channel") - - try: - p = self._encoders[(int(source), int(channel))](data) - except KeyError: - raise Exception("no encoder found, create_encoder() has to be" + - "called for every source and channel") - except Exception as ex: - raise Exception("Encoding error:" + str(ex)) - - sp = ftltype.StreamPacket._make((timestamp, - int(source), - int(channel), - self._channel_count)) - - self.add_raw(sp, p) diff --git a/python/ftl/ftltype.py b/python/ftl/ftltype.py deleted file mode 100644 index 9efad0172..000000000 --- a/python/ftl/ftltype.py +++ /dev/null @@ -1,92 +0,0 @@ -from collections import namedtuple -from enum import IntEnum - -# components/rgbd-sources/include/ftl/rgbd/camera.hpp -Camera = namedtuple("Camera", ["fx", "fy", "cx", "cy", "width", "height", - "min_depth", "max_depth", "baseline", "doff"]) - -# components/codecs/include/ftl/codecs/packet.hpp -Packet = namedtuple("Packet", ["codec", "definition", "block_total", - "block_number", "flags", "data"]) - -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 - Colour = 0 - Left = 0 - Depth = 1 - Right = 2 - Colour2 = 2 - Disparity = 3 - Depth2 = 3 - Deviation = 4 - Normals = 5 - Points = 6 - Confidence = 7 - Contribution = 7 - EnergyVector = 8 - Flow = 9 - Energy = 10 - Mask = 11 - Density = 12 - LeftGray = 13 - RightGray = 14 - Overlay1 = 15 - - AudioLeft = 32 - AudioRight = 33 - - Configuration = 64 - Calibration = 65 - Pose = 66 - Data = 67 - -_float_channels = [ - Channel.Depth, - Channel.Confidence, - Channel.Density, - Channel.Energy -] - -def is_float_channel(channel): - return channel in _float_channels - -# components/codecs/include/ftl/codecs/codecs.hpp -class codec_t(IntEnum): - JPG = 0 - PNG = 1 - H264 = 2 - HEVC = 3 - WAV = 4 - JSON = 100 - CALIBRATION = 101 - POSE = 102 - MSGPACK = 103, - STRING = 104, - RAW = 105 - -definition_t = { - 0 : (7680, 4320), - 1 : (2160, 3840), - 2 : (1080, 1920), - 3 : (720, 1280), - 4 : (576, 1024), - 5 : (480, 854), - 6 : (360, 640), - 7 : (0, 0), - 8 : (2056, 1852) -} - -def get_definition(shape): - for k, v in definition_t.items(): - if shape[:2] == v: - return k - - return 7 # (None) -- GitLab