From 292b96e889ba52761110426e35f26ce438e238b9 Mon Sep 17 00:00:00 2001 From: Dmitry Sorokin Date: Mon, 15 Feb 2016 20:34:09 +0300 Subject: [PATCH 1/2] python wrapper for Frame object - ThermalCamera matplotlib visualization absolute temp values in C code, flat and full frames construction in C code --- CMakeLists.txt | 15 ++++ libseek_python.py | 20 +++++ pylibseek.cpp | 184 ++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 15 ++++ test_seekmod.py | 7 ++ view-absolute.py | 36 +++++++++ wscript | 2 +- 7 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 CMakeLists.txt create mode 100644 libseek_python.py create mode 100644 pylibseek.cpp create mode 100755 setup.py create mode 100755 test_seekmod.py create mode 100755 view-absolute.py diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6a0aed4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 2.6) +project(LIBSEEK) +#find_package(libusb REQUIRED) +find_path(LIBUSB_INCLUDE_PATH libusb.h /usr/include/libusb-1.0/) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Os -fpic --std=c++11") + +add_library(seek STATIC seek.cpp) +add_executable(seek-test seek-test.cpp) +add_executable(seek-test-calib seek-test-calib.cpp) + +target_include_directories(seek PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${LIBUSB_INCLUDE_PATH}) + +target_link_libraries(seek LINK_PUBLIC usb-1.0) +target_link_libraries(seek-test LINK_PUBLIC seek) +target_link_libraries(seek-test-calib LINK_PUBLIC seek) \ No newline at end of file diff --git a/libseek_python.py b/libseek_python.py new file mode 100644 index 0000000..80fc961 --- /dev/null +++ b/libseek_python.py @@ -0,0 +1,20 @@ +import pylibseek as lseek + + +class ThermalCamera(object): + def __init__(self, k=None, c=None): + if k: k = float(k) + if c: c = float(c) + lseek.init(k, c) + (width, height) = lseek.get_dimensions() + self.width = width + self.height = height + + def get_frame(self, absolute=True): + return lseek.get_frame(absolute) + + def get_flat_frame(self, absolute=True): + return lseek.get_flat_frame(absolute) + + def __del__(self): + lseek.deinit() diff --git a/pylibseek.cpp b/pylibseek.cpp new file mode 100644 index 0000000..fae9bc8 --- /dev/null +++ b/pylibseek.cpp @@ -0,0 +1,184 @@ +// +// Created by Dmitry Sorokin on 2/15/16. +// + +#include +#include "seek.hpp" + +static LibSeek::Imager *imgr; +static LibSeek::Frame *frm; +static bool initialized = false; +static unsigned int length = 0; +static float *absolute_data; +static float _K = 0.02; // Experimental values, looks realistic +static float _C = -618; + +// TODO: get_frame absolute should accept convertation coeff? + +void +parse_coef_args(PyObject *args) { + PyObject *p1 = nullptr; + PyObject *p2 = nullptr; + // TODO: can be a memleak here? + PyArg_UnpackTuple(args, "ref", 0, 2, &p1, &p2); + if (p1 != nullptr && PyFloat_Check(p1)) { + _K = PyFloat_AsDouble(p1); + } + if (p2 != nullptr && PyFloat_Check(p2)) { + _C = PyFloat_AsDouble(p2); + } +} + +bool +is_absolute(PyObject *args) { + PyObject *p1 = nullptr; + // TODO: can be a memleak here? + PyArg_UnpackTuple(args, "ref", 0, 1, &p1); + if (p1 != nullptr && PyBool_Check(p1)) { + return p1 == Py_True; + } + return false; +} + +static PyObject * +init(PyObject *self, PyObject *args) { + if (initialized) { + return nullptr; + } + parse_coef_args(args); + // TODO: throw exceptions + imgr = new LibSeek::Imager(); + frm = new LibSeek::Frame(); + imgr->init(); + imgr->frame_init(*frm); + length = frm->width()*frm->height(); + absolute_data = new float[length]; + initialized = true; + return Py_None; +} + +static bool check_init() { + if (initialized) + return true; + init(nullptr, nullptr); + return initialized; +} + +static PyObject * +deinit(PyObject *self, PyObject *args) { + check_init(); // TODO: throw exception if unitialized + delete frm; + delete imgr; + delete absolute_data; + return Py_None; +} + +const uint16_t * +_get_frame_data() { + imgr->frame_acquire(*frm); + return frm->data(); +} + +const float * +_convert_to_absolute(const uint16_t *data) { + for (unsigned int i = 0; i < length; i++) { + absolute_data[i] = data[i]*_K + _C; + } + return absolute_data; +} + +static PyObject * +_value_builder(const float value) { + return PyFloat_FromDouble(value); +} + +static PyObject * +_value_builder(const uint16_t value) { + return PyLong_FromLong(value); +} + +template +static PyObject * +_construct_flat_frame (T *data) { + PyObject *res = PyList_New(length); + + for (unsigned int i = 0; i < length; i++) { + PyList_SetItem(res, i, _value_builder(data[i])); + } + return res; +} + +template +static PyObject * +_construct_frame (T *data) { + PyObject *res = PyList_New(frm->height()); + unsigned int offset = 0; + + for (unsigned int i = 0; i < frm->height(); i++) { + PyObject *row = PyList_New(frm->width()); + PyList_SetItem(res, i, row); + for (unsigned int j = 0; j < frm->width(); j++) { + PyList_SetItem(row, j, _value_builder(data[offset])); + offset++; + } + } + return res; +} + +static PyObject * +get_frame_flat(PyObject *self, PyObject *args) { + check_init(); + + auto data = _get_frame_data(); + return is_absolute(args) ? + _construct_flat_frame(_convert_to_absolute(data)) : + _construct_flat_frame(data); +} + +static PyObject * +get_frame(PyObject *self, PyObject *args) { + check_init(); + + auto data = _get_frame_data(); + return is_absolute(args) ? + _construct_frame(_convert_to_absolute(data)) : + _construct_frame(data); +} + +static PyObject * +get_dimensions(PyObject *self, PyObject *args) { + check_init(); + PyObject *res = PyTuple_New(2); + PyTuple_SetItem(res, 0, PyLong_FromLong(frm->width())); + PyTuple_SetItem(res, 1, PyLong_FromLong(frm->height())); + return res; +} + + +static PyMethodDef libseekMethods[] = { + {"init", init, METH_VARARGS, + "Initialize device."}, + {"deinit", deinit, METH_NOARGS, + "Deinitialize device."}, + {"get_frame_flat", get_frame, METH_VARARGS, + "Get flattened frame from device."}, + {"get_frame", get_frame, METH_VARARGS, + "Get frame from device."}, + {"get_dimensions", get_dimensions, METH_NOARGS, + "Get frame width and height."}, + {NULL, NULL, 0, NULL} /* Sentinel */ +}; + +static struct PyModuleDef pylibseekmodule = { + PyModuleDef_HEAD_INIT, + "libseek_python", /* name of module */ + NULL, /* module documentation, may be NULL */ + -1, /* size of per-interpreter state of the module, + or -1 if the module keeps state in global variables. */ + libseekMethods +}; + +PyMODINIT_FUNC +PyInit_pylibseek(void) { + return PyModule_Create(&pylibseekmodule); +} \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..0e3f776 --- /dev/null +++ b/setup.py @@ -0,0 +1,15 @@ +from distutils.core import setup, Extension + +pylibseek = Extension('pylibseek', + sources = ["pylibseek.cpp"], + extra_compile_args = ["--std=c++11"], + include_dirs = ["./"], + library_dirs = ["./build", "./cmake_build"], + libraries = ["seek", "usb-1.0"], + #define_macros = [("DEBUG", 1)] + ) + +setup (name = 'LibSeek Python interface', + version = '1.0', + description = 'Thermal Imager interface module', + ext_modules = [pylibseek]) diff --git a/test_seekmod.py b/test_seekmod.py new file mode 100755 index 0000000..0dc58cc --- /dev/null +++ b/test_seekmod.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 +import libseek_python as l + +l.lseek.init(0.3, -500) +f = l.lseek.get_frame(True) +l.lseek.deinit() +print(f) diff --git a/view-absolute.py b/view-absolute.py new file mode 100755 index 0000000..854d339 --- /dev/null +++ b/view-absolute.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +import matplotlib.pyplot as plt +import matplotlib.animation as animation +from matplotlib.widgets import Slider + +import libseek_python as l + +mintemp, maxtemp = 0, 100 + +c = l.ThermalCamera() +f = c.get_frame() + +fig = plt.figure() +image = plt.imshow(f, cmap="gnuplot2", animated=True) +plt.clim(mintemp, maxtemp) + +ax_maxtemp = plt.axes([0.2, 0.01, 0.65, 0.03]) +ax_mintemp = plt.axes([0.2, 0.05, 0.65, 0.03]) +s_maxtemp = Slider(ax_maxtemp, 'Maximum', 30, 150, valinit=maxtemp) +s_mintemp = Slider(ax_mintemp, 'Minumum', 0, 40, valinit=mintemp) + + +def controls_update(_): + plt.clim(s_mintemp.val, s_maxtemp.val) + + +def anim(_): + f = c.get_frame() + image.set_array(f) + return image, + +s_maxtemp.on_changed(controls_update) +s_mintemp.on_changed(controls_update) +ani = animation.FuncAnimation(fig, anim, interval=100) +plt.show() + diff --git a/wscript b/wscript index 61398b5..a1f7964 100644 --- a/wscript +++ b/wscript @@ -11,7 +11,7 @@ def configure(conf): conf.load('compiler_cxx') if conf.env.CXX_NAME in ('gcc', 'clang'): - conf.env.CXXFLAGS += [ '-std=c++11' ] + conf.env.CXXFLAGS += [ '-std=c++11', '-fpic' ] conf.check_cfg(package='libusb-1.0', args='--cflags --libs', uselib_store="LIBUSB") From c915ebd9ed55785955fdf86cff4129349598dab7 Mon Sep 17 00:00:00 2001 From: Dmitry Sorokin Date: Thu, 18 Feb 2016 19:43:11 +0300 Subject: [PATCH 2/2] python2 support added --- pylibseek.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pylibseek.cpp b/pylibseek.cpp index fae9bc8..ed707ec 100644 --- a/pylibseek.cpp +++ b/pylibseek.cpp @@ -169,6 +169,7 @@ static PyMethodDef libseekMethods[] = { {NULL, NULL, 0, NULL} /* Sentinel */ }; +#if PY_MAJOR_VERSION >= 3 static struct PyModuleDef pylibseekmodule = { PyModuleDef_HEAD_INIT, "libseek_python", /* name of module */ @@ -181,4 +182,10 @@ static struct PyModuleDef pylibseekmodule = { PyMODINIT_FUNC PyInit_pylibseek(void) { return PyModule_Create(&pylibseekmodule); -} \ No newline at end of file +} +#else +PyMODINIT_FUNC +initpylibseek(void) { + PyObject *module = Py_InitModule("pylibseek", libseekMethods); +} +#endif