From c8613d9554da3c63ea1df2b3c3ac4058dd49bd47 Mon Sep 17 00:00:00 2001 From: "Brandyn A. White" Date: Mon, 22 Nov 2010 04:53:59 -0500 Subject: [PATCH] Added back optional numpy module loading for ptone : ) Initial cython commit readme Changed all python library names to the new convention Signed-off-by: Brandyn A. White Fixed a typo Signed-off-by: Brandyn A. White Removed headers. Made the setup look for them. Signed-off-by: Brandyn A. White Updated readme with new path Added -fPIC Signed-off-by: Brandyn A. White Changed to use the shared libary, added help text Signed-off-by: Brandyn A. White Added a new include path that is the default for OS x Signed-off-by: Brandyn A. White Back to static (dynamic worked but it will be annoying for devs) Signed-off-by: Brandyn A. White Added another include search path for OSX Signed-off-by: Brandyn A. White --- README.asciidoc | 3 +- python/cython/README | 9 ++ python/cython/demo_cv_depth_show.py | 17 +++ python/cython/freenect.pyx | 204 ++++++++++++++++++++++++++++ python/cython/setup.py | 16 +++ python/demo_cv_depth_show.py | 4 +- python/demo_cv_rgb_show.py | 4 +- python/demo_mp_depth_show.py | 4 +- python/demo_mp_rgb_show.py | 4 +- python/demo_tilt.py | 12 +- python/freenect.py | 161 +++++++++++----------- python/freenect_synch.py | 6 +- 12 files changed, 341 insertions(+), 103 deletions(-) create mode 100644 python/cython/README create mode 100644 python/cython/demo_cv_depth_show.py create mode 100644 python/cython/freenect.pyx create mode 100644 python/cython/setup.py diff --git a/README.asciidoc b/README.asciidoc index 9aa96383..e8859951 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -75,7 +75,8 @@ currently consists of: patches in platform directory) - GPL'd - JoshBlake's C# OpenKinect (in the csharp directory, with inf files in platform directory) - Apache'd -- Brandyn's Python bindings (in c/python, see README there) +- Brandyn's Python bindings (Ctypes/Cython + Sync/Async) + in libfreenect/python. (see README there) The aggregated READMEs are below. diff --git a/python/cython/README b/python/cython/README new file mode 100644 index 00000000..b785c46c --- /dev/null +++ b/python/cython/README @@ -0,0 +1,9 @@ +B1;2600;0cBrandyn White +bwhite@dappervision.com + +**This is not completed yet, so if you are looking at it keep that in mind** + +To compile you need to copy libfreenect.a to this directory, then run the demo in this directory. Nothing else has been tested. The header files are in here for simplicity, when this is integrated with the build system they will be accessed directly. + +Build with (goes in this directory) +python setup.py build_ext --inplace diff --git a/python/cython/demo_cv_depth_show.py b/python/cython/demo_cv_depth_show.py new file mode 100644 index 00000000..316faac6 --- /dev/null +++ b/python/cython/demo_cv_depth_show.py @@ -0,0 +1,17 @@ +import freenect +import cv +import numpy as np + +cv.NamedWindow('Depth') + +def display(dev, data, timestamp): + data -= np.min(data.ravel()) + data *= 65536 / np.max(data.ravel()) + image = cv.CreateImageHeader((data.shape[1], data.shape[0]), + cv.IPL_DEPTH_16U, + 1) + cv.SetData(image, data.tostring(), + data.dtype.itemsize * data.shape[1]) + cv.ShowImage('Depth', image) + cv.WaitKey(5) +freenect.runloop(lambda *x: display(*freenect.depth_cb_np(*x))) diff --git a/python/cython/freenect.pyx b/python/cython/freenect.pyx new file mode 100644 index 00000000..d420b9c2 --- /dev/null +++ b/python/cython/freenect.pyx @@ -0,0 +1,204 @@ +import cython + +FORMAT_RGB = 0 +FORMAT_BAYER = 1 +FORMAT_11_BIT = 0 +FORMAT_10_BIT = 1 +LED_OFF = 0 +LED_GREEN = 1 +LED_RED = 2 +LED_YELLOW = 3 +LED_BLINK_YELLOW = 4 +LED_BLINK_GREEN = 5 +LED_BLINK_RED_YELLOW = 6 + +cdef extern from "Python.h": + object PyString_FromStringAndSize(char *s, Py_ssize_t len) + +cdef extern from "libfreenect.h": + ctypedef void (*freenect_depth_cb)(void *dev, char *depth, int timestamp) # was u_int32 + ctypedef void (*freenect_rgb_cb)(void *dev, char *rgb, int timestamp) # was u_int32 + int freenect_init(void **ctx, int usb_ctx) # changed from void * as usb_ctx is always NULL + int freenect_shutdown(void *ctx) + int freenect_process_events(void *ctx) + int freenect_num_devices(void *ctx) + int freenect_open_device(void *ctx, void **dev, int index) + int freenect_close_device(void *dev) + #void freenect_set_user(void *dev, void *user) + #void *freenect_get_user(void *dev) + void freenect_set_depth_callback(void *dev, freenect_depth_cb cb) + void freenect_set_rgb_callback(void *dev, freenect_rgb_cb cb) + int freenect_set_rgb_format(void *dev, int fmt) + int freenect_set_depth_format(void *dev, int fmt) + int freenect_start_depth(void *dev) + int freenect_start_rgb(void *dev) + int freenect_stop_depth(void *dev) + int freenect_stop_rgb(void *dev) + int freenect_set_tilt_degs(void *dev, double angle) + int freenect_set_led(void *dev, int option) + int freenect_get_raw_accel(void *dev, short int* x, short int* y, short int* z) # had to make these short int + int freenect_get_mks_accel(void *dev, double* x, double* y, double* z) + + +cdef class DevPtr: + cdef void* _ptr + def __repr__(self): + return "" + +cdef class CtxPtr: + cdef void* _ptr + def __repr__(self): + return "" + +def set_rgb_format(DevPtr dev, int fmt): + return freenect_set_rgb_format(dev._ptr, fmt) + +def set_depth_format(DevPtr dev, int fmt): + return freenect_set_depth_format(dev._ptr, fmt) + +def start_depth(DevPtr dev): + return freenect_start_depth(dev._ptr) + +def start_rgb(DevPtr dev): + return freenect_start_rgb(dev._ptr) + +def stop_depth(DevPtr dev): + return freenect_stop_depth(dev._ptr) + +def stop_rgb(DevPtr dev): + return freenect_stop_rgb(dev._ptr) + +def shutdown(CtxPtr ctx): + return freenect_shutdown(ctx._ptr) + +def process_events(CtxPtr ctx): + return freenect_process_events(ctx._ptr) + +def num_devices(CtxPtr ctx): + return freenect_num_devices(ctx._ptr) + +def close_device(DevPtr dev): + return freenect_close_device(dev._ptr) + +def set_tilt_degs(DevPtr dev, float angle): + freenect_set_tilt_degs(dev._ptr, angle) + +def set_led(DevPtr dev, int option): + return freenect_set_led(dev._ptr, option) + +cdef init(): + cdef void* ctx + if freenect_init(cython.address(ctx), 0) < 0: + print('Error: Cant open') + cdef CtxPtr ctx_out + ctx_out = CtxPtr() + ctx_out._ptr = ctx + return ctx_out + +cdef open_device(CtxPtr ctx, int index): + cdef void* dev + if freenect_open_device(ctx._ptr, cython.address(dev), index) < 0: + print('Error: Cant open') + cdef DevPtr dev_out + dev_out = DevPtr() + dev_out._ptr = dev + return dev_out + +_depth_cb, _rgb_cb = None, None + +cdef void depth_cb(void *dev, char *data, int timestamp): + nbytes = 614400 # 480 * 640 * 2 + cdef DevPtr dev_out + dev_out = DevPtr() + dev_out._ptr = dev + print(timestamp) + if _depth_cb: + _depth_cb(dev_out, PyString_FromStringAndSize(data, nbytes), timestamp) + + +cdef void rgb_cb(void *dev, char *data, int timestamp): + nbytes = 921600 # 480 * 640 * 3 + cdef DevPtr dev_out + dev_out = DevPtr() + dev_out._ptr = dev + print(timestamp) + if _rgb_cb: + _rgb_cb(dev_out, PyString_FromStringAndSize(data, nbytes), timestamp) + + +def runloop(depth=None, rgb=None): + """Sets up the kinect and maintains a runloop + + This is where most of the action happens. You can get the dev pointer from the callback + and let this function do all of the setup for you. You may want to use threads to perform + computation externally as the callbacks should really just be used for copying data. + + Args: + depth: A function that takes (dev, depth, timestamp), corresponding to C function. + If None (default), then you won't get a callback for depth. + rgb: A function that takes (dev, rgb, timestamp), corresponding to C function. + If None (default), then you won't get a callback for rgb. + """ + global _depth_cb, _rgb_cb + if depth: + _depth_cb = depth + if rgb: + _rgb_cb = rgb + cdef DevPtr dev + cdef CtxPtr ctx + cdef void* devp + cdef void* ctxp + ctx = init() + dev = open_device(ctx, 0) + devp = dev._ptr + ctxp = ctx._ptr + freenect_set_depth_format(devp, 0) + freenect_start_depth(devp) + freenect_set_rgb_format(devp, FORMAT_RGB) + freenect_start_rgb(devp) + freenect_set_depth_callback(devp, depth_cb) + freenect_set_rgb_callback(devp, rgb_cb) + while freenect_process_events(ctxp) >= 0: + pass + +def _load_numpy(): + try: + import numpy as np + return np + except ImportError, e: + print('You need the numpy library to use this function') + raise e + + +def depth_cb_np(dev, string, timestamp): + """Converts the raw depth data into a numpy array for your function + + Args: + dev: DevPtr object + string: A python string with the depth data + timestamp: An int representing the time + + Returns: + (dev, data, timestamp) where data is a 2D numpy array + """ + np = _load_numpy() + data = np.fromstring(string, dtype=np.uint16) + data.resize((480, 640)) + return dev, data, timestamp + + +def rgb_cb_np(dev, string, timestamp): + """Converts the raw depth data into a numpy array for your function + + Args: + dev: DevPtr object + string: A python string with the RGB data + timestamp: An int representing the time + + Returns: + (dev, data, timestamp) where data is a 2D numpy array + """ + np = _load_numpy() + data = np.fromstring(string, dtype=np.uint8) + data.resize((480, 640, 3)) + return dev, data, timestamp diff --git a/python/cython/setup.py b/python/cython/setup.py new file mode 100644 index 00000000..a56281ce --- /dev/null +++ b/python/cython/setup.py @@ -0,0 +1,16 @@ +from distutils.core import setup +from distutils.extension import Extension +from Cython.Distutils import build_ext + +ext_modules = [Extension("freenect", ["freenect.pyx"], + extra_objects=["../../c/build/lib/libfreenect.a"], + libraries=['usb-1.0'], + extra_compile_args=['-fPIC', '-I', '../../c/include/', + '-I', '/usr/include/libusb-1.0/', + '-I', '/usr/local/include/libusb-1.0', + '-I', '/usr/local/include'])] +setup( + name = 'freenect', + cmdclass = {'build_ext': build_ext}, + ext_modules = ext_modules +) diff --git a/python/demo_cv_depth_show.py b/python/demo_cv_depth_show.py index d86c92d3..2e77ea85 100755 --- a/python/demo_cv_depth_show.py +++ b/python/demo_cv_depth_show.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -from freenect import * +import freenect import cv import numpy as np @@ -16,4 +16,4 @@ def display(dev, data, timestamp): data.dtype.itemsize * data.shape[1]) cv.ShowImage('Depth', image) cv.WaitKey(5) -runloop(depth=depth_cb_np_factory(display)) +freenect.runloop(depth=lambda *x: display(*freenect.depth_cb_np(*x))) diff --git a/python/demo_cv_rgb_show.py b/python/demo_cv_rgb_show.py index 6cb828c4..d8fc9697 100755 --- a/python/demo_cv_rgb_show.py +++ b/python/demo_cv_rgb_show.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -from freenect import * +import freenect import cv cv.NamedWindow('RGB') @@ -14,4 +14,4 @@ def display(dev, data, timestamp): data.dtype.itemsize * 3 * data.shape[1]) cv.ShowImage('RGB', image) cv.WaitKey(5) -runloop(rgb=rgb_cb_np_factory(display)) +freenect.runloop(rgb=lambda *x: display(*freenect.rgb_cb_np(*x))) diff --git a/python/demo_mp_depth_show.py b/python/demo_mp_depth_show.py index 4bfac4e5..6b56bebe 100755 --- a/python/demo_mp_depth_show.py +++ b/python/demo_mp_depth_show.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -from freenect import * +import freenect import matplotlib.pyplot as mp mp.ion() @@ -15,4 +15,4 @@ def display(dev, data, timestamp): mp.draw() if __name__ == '__main__': - runloop(depth=depth_cb_np_factory(display)) + freenect.runloop(depth=lambda *x: display(*freenect.depth_cb_np(*x))) diff --git a/python/demo_mp_rgb_show.py b/python/demo_mp_rgb_show.py index 67ced54c..e0b32ecc 100755 --- a/python/demo_mp_rgb_show.py +++ b/python/demo_mp_rgb_show.py @@ -1,4 +1,4 @@ #!/usr/bin/env python -from freenect import * +import freenect from demo_mp_depth_show import display -runloop(rgb=rgb_cb_np_factory(display)) +freenect.runloop(rgb=lambda *x: display(*freenect.rgb_cb_np(*x))) diff --git a/python/demo_tilt.py b/python/demo_tilt.py index 9b1ebb93..28960238 100755 --- a/python/demo_tilt.py +++ b/python/demo_tilt.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -from freenect import * +import freenect import time import threading import random @@ -16,14 +16,14 @@ def tilt_and_sense(): led = random.randint(0, 6) tilt = random.randint(0, 30) print('Led[%d] Tilt[%d]' % (led, tilt)) - freenect_set_led(dev, led) - freenect_set_tilt_degs(dev, tilt) - print(raw_accel(dev)) - print(mks_accel(dev)) + freenect.set_led(dev, led) + freenect.set_tilt_degs(dev, tilt) + print(freenect.raw_accel(dev)) + print(freenect.mks_accel(dev)) threading.Thread(target=tilt_and_sense).start() def dev_getter(my_dev, *_): global dev dev = my_dev -runloop(depth=depth_cb_string_factory(dev_getter)) +freenect.runloop(depth=lambda *x: dev_getter(*freenect.depth_cb_np(*x))) diff --git a/python/freenect.py b/python/freenect.py index e98372d5..7d9d8a2d 100755 --- a/python/freenect.py +++ b/python/freenect.py @@ -24,10 +24,10 @@ import ctypes import array import itertools -FREENECT_FORMAT_RGB = 0 -FREENECT_FORMAT_BAYER = 1 -FREENECT_FORMAT_11_BIT = 0 -FREENECT_FORMAT_10_BIT = 1 +FORMAT_RGB = 0 +FORMAT_BAYER = 1 +FORMAT_11_BIT = 0 +FORMAT_10_BIT = 1 LED_OFF = 0 LED_GREEN = 1 LED_RED = 2 @@ -39,6 +39,10 @@ def _try_load(paths, names, extensions): out_accum = [] + try: + return _try_load_np(paths, names, extensions) + except ImportError, e: + out_accum.append(e) for x in itertools.product(paths, names, extensions): try: return ctypes.cdll.LoadLibrary('%s%s%s' % x) @@ -50,6 +54,14 @@ def _try_load(paths, names, extensions): raise OSError("Couldn't find shared library, was it built properly?") +def _try_load_np(paths, names, extensions): + import numpy as np + for path, name, extension in itertools.product(paths, names, extensions): + try: + return np.ctypeslib.load_library(name + extension, path) + except OSError: + pass + def _setup_shared_library(): """Types all of the shared library functions @@ -70,7 +82,7 @@ def mk(res, fun, arg): c_int16_p = POINTER(c_int16) c_double_p = POINTER(c_double) # This shared library could be a few places, lets just try them - fn = _try_load(['', '../build/lib/', '/usr/local/lib/'], + fn = _try_load(['', '../c/build/lib/', '/usr/local/lib/'], ['libfreenect'], ['.so', '.dylib', '.dll']) # int freenect_init(freenect_context **ctx, freenect_usb_context *usb_ctx); @@ -122,11 +134,12 @@ def mk(res, fun, arg): # Populate module namespace to mimic C interface def _populate_namespace(): - _fn, freenect_depth_cb, freenect_rgb_cb = _setup_shared_library() + _fn, depth_cb, rgb_cb = _setup_shared_library() + header = 'freenect_' for x in dir(_fn): - if x.startswith('freenect_'): - globals()[x] = getattr(_fn, x) - return freenect_depth_cb, freenect_rgb_cb + if x.startswith(header): + globals()[x[len(header):]] = getattr(_fn, x) + return depth_cb, rgb_cb def raw_accel(dev): @@ -139,8 +152,8 @@ def raw_accel(dev): Tuple of (x, y, z) as ctype.c_int16 values """ x, y, z = ctypes.c_int16(), ctypes.c_int16(), ctypes.c_int16() - freenect_get_raw_accel(dev, ctypes.byref(x), ctypes.byref(y), - ctypes.byref(z)) + get_raw_accel(dev, ctypes.byref(x), ctypes.byref(y), + ctypes.byref(z)) return x, y, z @@ -154,7 +167,7 @@ def mks_accel(dev): Tuple of (x, y, z) as ctype.c_double values """ x, y, z = ctypes.c_double(), ctypes.c_double(), ctypes.c_double() - freenect_get_mks_accel(dev, ctypes.byref(x), ctypes.byref(y), + get_mks_accel(dev, ctypes.byref(x), ctypes.byref(y), ctypes.byref(z)) return x, y, z @@ -172,21 +185,21 @@ def runloop(depth=None, rgb=None): rgb: A function that takes (dev, rgb, timestamp), corresponding to C function. If None (default), then you won't get a callback for rgb. """ - depth = freenect_depth_cb(depth if depth else lambda *x: None) - rgb = freenect_rgb_cb(rgb if rgb else lambda *x: None) + depth = depth_cb(depth if depth else lambda *x: None) + rgb = rgb_cb(rgb if rgb else lambda *x: None) ctx = ctypes.c_void_p() - if freenect_init(ctypes.byref(ctx), 0) < 0: + if init(ctypes.byref(ctx), 0) < 0: print('Error: Cant open') dev = ctypes.c_void_p() - if freenect_open_device(ctx, ctypes.byref(dev), 0) < 0: + if open_device(ctx, ctypes.byref(dev), 0) < 0: print('Error: Cant open') - freenect_set_depth_format(dev, 0) - freenect_set_depth_callback(dev, depth) - freenect_start_depth(dev) - freenect_set_rgb_format(dev, FREENECT_FORMAT_RGB) - freenect_set_rgb_callback(dev, rgb) - freenect_start_rgb(dev) - while freenect_process_events(ctx) >= 0: + set_depth_format(dev, 0) + set_depth_callback(dev, depth) + start_depth(dev) + set_rgb_format(dev, FORMAT_RGB) + set_rgb_callback(dev, rgb) + start_rgb(dev) + while process_events(ctx) >= 0: pass @@ -199,95 +212,73 @@ def _load_numpy(): raise e -def depth_cb_string_factory(func): - """Converts the raw depth data into a python string for your function +def depth_cb_decorator(func): + """Converts the raw depth data into a python string Args: func: A function that takes (dev, data, timestamp), corresponding to C function except that data is a python string corresponding to the data. + + Returns: + Function that takes (dev, depth, timestamp) that returns output of func """ def depth_cb(dev, depth, timestamp): nbytes = 614400 # 480 * 640 * 2 - func(dev, ctypes.string_at(depth, nbytes), timestamp) + return func(dev, ctypes.string_at(depth, nbytes), timestamp) return depth_cb -def rgb_cb_string_factory(func): - """Converts the raw RGB data into a python string for your function +def rgb_cb_decorator(func): + """Converts the raw RGB data into a python string Args: func: A function that takes (dev, data, timestamp), corresponding to C function except that data is a python string corresponding to the data. + + Returns: + Function that takes (dev, rgb, timestamp) that returns output of func """ def rgb_cb(dev, rgb, timestamp): nbytes = 921600 # 480 * 640 * 3 - func(dev, ctypes.string_at(rgb, nbytes), timestamp) + return func(dev, ctypes.string_at(rgb, nbytes), timestamp) return rgb_cb -def depth_cb_np_factory(func): - """Converts the raw depth data into a numpy array for your function - - Args: - func: A function that takes (dev, data, timestamp), corresponding to C function - except that data is a 2D numpy array corresponding to the data. - """ - np = _load_numpy() - - def depth_cb(dev, string, timestamp): - data = np.fromstring(string, dtype=np.uint16) - data.resize((480, 640)) - func(dev, data, timestamp) - return depth_cb_string_factory(depth_cb) - - -def rgb_cb_np_factory(func): - """Converts the raw RGB data into a numpy array for your function +@depth_cb_decorator +def depth_cb_np(dev, string, timestamp): + """Converts the raw depth data into a numpy array for your function Args: - func: A function that takes (dev, data, timestamp), corresponding to C function - except that data is a 2D numpy array corresponding to the data. - """ - np = _load_numpy() - - def rgb_cb(dev, string, timestamp): - data = np.fromstring(string, dtype=np.uint8) - data.resize((480, 640, 3)) - func(dev, data, timestamp) - return rgb_cb_string_factory(rgb_cb) + dev: DevPtr object + string: A python string with the depth data + timestamp: An int representing the time - -def depth_cb_array_factory(func): - """Converts the raw depth data into a 1D python array (not numpy) for your function - - Args: - func: A function that takes (dev, data, timestamp), corresponding to C function - except that data is a 1D python array corresponding to the data. - """ - - def depth_cb(dev, string, timestamp): - data = array.array('H') - assert(data.itemsize == 2) - data.fromstring(string) - func(dev, data, timestamp) - return depth_cb_string_factory(depth_cb) + Returns: + (dev, data, timestamp) where data is a 2D numpy array + """ + np = _load_numpy() + data = np.fromstring(string, dtype=np.uint16) + data.resize((480, 640)) + return dev, data, timestamp -def rgb_cb_array_factory(func): - """Converts the raw RGB data into a 1D python array (not numpy) for your function +@rgb_cb_decorator +def rgb_cb_np(dev, string, timestamp): + """Converts the raw depth data into a numpy array for your function Args: - func: A function that takes (dev, data, timestamp), corresponding to C function - except that data is a 1D python array corresponding to the data. - """ - - def rgb_cb(dev, string, timestamp): - data = array.array('B') - assert(data.itemsize == 1) - data.fromstring(string) - func(dev, data, timestamp) - return rgb_cb_string_factory(rgb_cb) + dev: DevPtr object + string: A python string with the RGB data + timestamp: An int representing the time -freenect_depth_cb, freenect_rgb_cb = _populate_namespace() + Returns: + (dev, data, timestamp) where data is a 2D numpy array + """ + np = _load_numpy() + data = np.fromstring(string, dtype=np.uint8) + data.resize((480, 640, 3)) + return dev, data, timestamp + +depth_cb, rgb_cb = _populate_namespace() diff --git a/python/freenect_synch.py b/python/freenect_synch.py index 4d8c0747..bebd6c7e 100755 --- a/python/freenect_synch.py +++ b/python/freenect_synch.py @@ -44,8 +44,8 @@ def _rgb_cb(dev, string, ts): rgbqueue = [string] rgbcond.notify() -depth_cb = freenect.depth_cb_string_factory(_depth_cb) -rgb_cb = freenect.rgb_cb_string_factory(_rgb_cb) +depth_cb = lambda *x: _depth_cb(*freenect.depth_cb_np(*x)) +rgb_cb = lambda *x: _rgb_cb(*freenect.rgb_cb_np(*x)) def get_depth(): """Grabs a new depth frame, blocking until the background thread provides one. @@ -84,6 +84,6 @@ def get_rgb(): try: loopthread == '' except: - loopthread = Thread(target=freenect.runloop,kwargs=dict(depth=depth_cb,rgb=rgb_cb)) + loopthread = Thread(target=freenect.runloop, kwargs=dict(depth=depth_cb, rgb=rgb_cb)) loopthread.start()