diff --git a/.gitignore b/.gitignore index c82a2d2d45d75be79cc8017c1ae310bfbe9f5a70..7089fbff58672c045049b9e9c683b420098b0425 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ **/include/ftl/config.h **/src/config.cpp **/*.blend1 -SDK/Python/ftl/__pycache__ +__pycache__ fabric/ fabric-js/ build/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 97954b3a9d23434b59b0be3dfa0ef85a83639596..423cb70b94a8bb418d8c700860ff0c9fe95c3d99 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,6 +18,8 @@ linux: stage: all tags: - linux + variables: + FTL_LIB: ../../build/SDK/C/libftl-dev.so # before_script: # - export DEBIAN_FRONTEND=noninteractive # - apt-get update -qq && apt-get install -y -qq g++ cmake git @@ -28,6 +30,8 @@ linux: - cmake .. -DWITH_OPTFLOW=TRUE -DUSE_CPPCHECK=FALSE -DBUILD_CALIBRATION=TRUE -DWITH_CERES=TRUE -DCMAKE_BUILD_TYPE=Release - make - ctest --output-on-failure + - cd ../SDK/Python + - python3 -m unittest discover test webserver-deploy: only: diff --git a/SDK/Python/LICENSE b/SDK/Python/LICENSE new file mode 120000 index 0000000000000000000000000000000000000000..30cff7403da04711c46979a06f6bf8eb10ee088a --- /dev/null +++ b/SDK/Python/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/SDK/Python/README.md b/SDK/Python/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/SDK/Python/ftl/streamwriter.py b/SDK/Python/ftl/streamwriter.py index 8d75dc793614038c998a3638d55d3b1bbe9649aa..ce8d86479cb6e705b39a641fdf73acf7a19fc7d0 100644 --- a/SDK/Python/ftl/streamwriter.py +++ b/SDK/Python/ftl/streamwriter.py @@ -1,17 +1,36 @@ -from . types import Channel, is_float_channel, Camera, Pipeline +from . types import FTLException, Channel, is_float_channel, Camera, Pipeline import numpy as np from enum import IntEnum import ctypes - +import sys import os.path +################################################################################ +# Try to locate shared library + _paths = [ ".", "/usr/lib", "/usr/local/lib", ] +_libpath = None +if "FTL_LIB" in os.environ and os.path.exists(os.environ["FTL_LIB"]): + _libpath = os.environ["FTL_LIB"] + +else: + for p in _paths: + p = os.path.join(p, "libftl-dev.so") + if os.path.exists(p): + _libpath = p + break + +if _libpath is None: + raise FileNotFoundError("libftl-dev.so not found") + +################################################################################ + class _imageformat_t(IntEnum): FLOAT = 0 BGRA = 1 @@ -20,16 +39,6 @@ class _imageformat_t(IntEnum): BGR = 4 RGB_FLOAT = 5 -_libpath = None -for p in _paths: - p = os.path.join(p, "libftl-dev.so") - if os.path.exists(p): - _libpath = p - break - -if _libpath is None: - raise FileNotFoundError("libftl-dev.so not found") - _c_api = ctypes.CDLL(_libpath) _c_api.ftlCreateWriteStream.restype = ctypes.c_void_p @@ -70,12 +79,22 @@ _c_api.ftlDestroyStream.argtypes = [ctypes.c_void_p] def _ftl_check(retval): if retval != 0: - raise Exception("FTL api returned %i" % retval) + raise FTLException(retval ,"FTL api returned %i" % retval) class FTLStreamWriter: def __init__(self, fname): self._sources = {} - self._instance = _c_api.ftlCreateWriteStream(bytes(fname, "utf8")) + + if isinstance(fname, str): + fname_ = bytes(fname, sys.getdefaultencoding()) + + elif isinstance(fname, bytes): + fname_ = fname + + else: + raise TypeError() + + self._instance = _c_api.ftlCreateWriteStream(fname_) if self._instance is None: raise Exception("Error: ftlCreateWriteStream") @@ -148,9 +167,11 @@ class FTLStreamWriter: func = _c_api.ftlIntrinsicsWriteLeft if channel == Channel.Calibration else _c_api.ftlIntrinsicsWriteRight - _ftl_check(func(self._instance, source, camera.width, camera.height, - camera.fx, camera.cx, camera.cy, camera.baseline, - camera.min_depth, camera.max_depth)) + _ftl_check(func(self._instance, source, + int(camera.width), int(camera.height), + float(camera.fx), float(camera.cx), + float(camera.cy), float(camera.baseline), + float(camera.min_depth), float(camera.max_depth))) self._sources[source] = camera @@ -168,6 +189,8 @@ class FTLStreamWriter: def write(self, source, channel, data): """ Write data to stream """ + source = int(source) + channel = Channel(channel) if channel in [Channel.Calibration, Channel.Calibration2]: self._write_calibration(source, channel, data) diff --git a/SDK/Python/ftl/types.py b/SDK/Python/ftl/types.py index 78d78e43068f8e49005e47b3c3e3c95640ed4bf6..54631343fe37a8cf1262d0b07ca88c52bf53702d 100644 --- a/SDK/Python/ftl/types.py +++ b/SDK/Python/ftl/types.py @@ -1,6 +1,11 @@ from typing import NamedTuple from enum import IntEnum +class FTLException(Exception): + def __init__(self, code, message): + self.code = code + self.message = message + class Pipeline(IntEnum): DEPTH = 0 RECONSTRUCT = 1 diff --git a/SDK/Python/setup.py b/SDK/Python/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..35234abc623c991442a59d9035dd8ecdc1ac339b --- /dev/null +++ b/SDK/Python/setup.py @@ -0,0 +1,16 @@ +import setuptools + +with open("README.md"), "r") as fh: + long_description = fh.read() + +setuptools.setup( + name="ftl-python", + version="0.0.1", # TODO: CMAKE + long_description=long_description, + long_description_content_type="text/markdown", + packages=setuptools.find_packages(), + classifiers=[ + "Programming Language :: Python :: 3" + ], + python_requires='>=3.6', +) diff --git a/SDK/Python/test/__init__.py b/SDK/Python/test/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/SDK/Python/test/test_streamwriter.py b/SDK/Python/test/test_streamwriter.py new file mode 100644 index 0000000000000000000000000000000000000000..b822a739c6c405dc50338d102de20b2a3a347777 --- /dev/null +++ b/SDK/Python/test/test_streamwriter.py @@ -0,0 +1,59 @@ +import unittest +import tempfile +import os + +from ftl import FTLStreamWriter +from ftl.types import Channel, Camera, FTLException + +import numpy as np + +class TestStreamWriter(unittest.TestCase): + + def test_create_and_delete_file(self): + """ Test constructor and destructor (empty file) """ + + with tempfile.NamedTemporaryFile(suffix=".ftl") as f: + stream = FTLStreamWriter(f.name) + 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) + 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) + + stream.write(0, Channel.Calibration, calib) + stream.write(0, Channel.Colour, im) + + def test_write_calib_wrong_compatible_type(self): + """ Write calibration with incorrect but compatible types (float/int) """ + + with tempfile.NamedTemporaryFile(suffix=".ftl") as f: + stream = FTLStreamWriter(f.name) + 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) + + 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) + + with self.assertRaises(FTLException): + stream.next_frame() + +if __name__ == '__main__': + unittest.main() \ No newline at end of file