diff --git a/.gitignore b/.gitignore index a1db5f50..6b98ba8d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,14 +3,21 @@ .pydevproject .pydevproject.bak -py_bind/optv/*.c +.idea/ +.vscode/ +env/ -*.pyc +py_bind/c_src +py_bind/optv/*.c +py_bind/dist +py_bind/build +py_bind/liboptv liboptv/config.h.in~ - liboptv/tests/check_fb.trs liboptv/build -py_bind/build *~ +py_bind/optv/*.c +py_bind/build +*.pyc diff --git a/.travis.yml b/.travis.yml index 9cad5617..9178ceba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,20 +1,19 @@ -language: c -compiler: - - clang - - gcc +language: python before_install: - sudo apt-get -qq update - - sudo apt-get install -y cmake - - sudo apt-get install -y check - sudo apt-get install -y build-essential - sudo apt-get install -y git + - sudo apt install python-pip + - pip install numpy + - pip install cython install: true script: -- cd liboptv -- mkdir _build && cd _build -- cmake ../ -- make -- make verify +- cd py_bind +- python setup.py prepare +- python setup.py install +- cd test +- pip install nose +- nosetests diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..2bc43d10 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,10 @@ +# A Dockerfile for an Ubuntu machine that can compile liboptv and the Cython extensions + +FROM ubuntu:18.04 + +RUN apt-get update +RUN apt-get --assume-yes install cmake +RUN apt-get --assume-yes install g++ +RUN apt-get --assume-yes install python-pip +RUN pip install virtualenv +RUN virtualenv /env --python=`which python2` diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 00000000..20d8ba1e --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,10 @@ +# Set up the Ubuntu container so we can develop the Cython code on it +version: '3' +services: + optv-dev: + image: ptv-ubuntu:1 + build: ./ + volumes: + - ../:/src + command: tail -f /dev/null + diff --git a/liboptv/.gitignore b/liboptv/.gitignore index c84f4712..75e6c347 100644 --- a/liboptv/.gitignore +++ b/liboptv/.gitignore @@ -27,4 +27,9 @@ tests/.libs/ tests/check_fb tests/*.o +CMakeCache.txt +*.cmake +CMakeFiles/ +install_manifest.txt +*.so stamp-h1 diff --git a/liboptv/CMakeLists.txt b/liboptv/CMakeLists.txt index 7acad429..f9de2013 100644 --- a/liboptv/CMakeLists.txt +++ b/liboptv/CMakeLists.txt @@ -1,9 +1,9 @@ cmake_minimum_required(VERSION 2.8) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/CMakeModules/") project(OpenPTV) -enable_testing() +# enable_testing() add_subdirectory(src) -add_subdirectory(tests) +# add_subdirectory(tests) INSTALL(DIRECTORY include/ DESTINATION include/optv/) diff --git a/liboptv/include/vec_utils.h b/liboptv/include/vec_utils.h index d8cb6f3a..d76010b5 100644 --- a/liboptv/include/vec_utils.h +++ b/liboptv/include/vec_utils.h @@ -7,7 +7,18 @@ doubles. #include -#define EMPTY_CELL 0.0/0.0 +#ifdef NAN + #define EMPTY_CELL NAN +#else + #if _MSC_VER <= 1500 // Visual C 2008 - for Python 2.7, or earlier + #define MSVC_NAN_REQUIRED + double return_nan(void); + #define EMPTY_CELL return_nan() + #else // More modern compilers or non Visual Studio + #define EMPTY_CELL 0.0/0.0 + #endif +#endif + #define is_empty(x) isnan(x) #define norm(x,y,z) sqrt((x)*(x) + (y)*(y) + (z)*(z)) diff --git a/liboptv/src/orientation.c b/liboptv/src/orientation.c index 8b48f02b..21f23c22 100644 --- a/liboptv/src/orientation.c +++ b/liboptv/src/orientation.c @@ -262,6 +262,9 @@ double* orient (Calibration* cal_in, control_par *cpar, int nfix, vec3d fix[], double *P, *y, *yh, *Xbeta, *resi; vec3d glass_dir, tmp_vec, e1, e2; + double (*X)[NPAR]; + double (*Xh)[NPAR]; + Calibration *cal; /* small perturbation for translation/rotation in meters and in radians */ @@ -278,8 +281,8 @@ double* orient (Calibration* cal_in, control_par *cpar, int nfix, vec3d fix[], Xbeta = (double *) calloc(maxsize, sizeof(double)); resi = (double *) calloc(maxsize, sizeof(double)); - double (*X)[NPAR] = malloc(sizeof (*X) * maxsize); - double (*Xh)[NPAR] = malloc(sizeof (*Xh) * maxsize); + X = malloc(sizeof (*X) * maxsize); + Xh = malloc(sizeof (*Xh) * maxsize); for(i = 0; i < maxsize; i++) { for(j = 0; j < NPAR; j++) { @@ -759,13 +762,13 @@ int read_man_ori_fix(vec3d fix4[4], char* calblock_filename, * Returns: pointer to a new orient_par structure. */ orient_par* read_orient_par(char *filename) { + orient_par *ret; FILE * file = fopen(filename, "r"); if (file == NULL) { printf("Could not open orientation parameters file %s.\n", filename); return NULL; } - orient_par *ret; ret = malloc(sizeof(orient_par)); if ( !(fscanf(file, "%d", &ret->useflag)==1) /* use every point or every other pt */ diff --git a/liboptv/src/parameters.c b/liboptv/src/parameters.c index 176a77db..785328de 100644 --- a/liboptv/src/parameters.c +++ b/liboptv/src/parameters.c @@ -476,13 +476,14 @@ int compare_mm_np(mm_np *mm_np1, mm_np *mm_np2) * Returns: pointer to a new target_par structure. */ target_par* read_target_par(char *filename) { + target_par *ret; + FILE * file = fopen(filename, "r"); if (file == NULL) { printf("Could not open target recognition parameters file %s.\n", filename); return NULL; } - target_par *ret; ret = malloc(sizeof(target_par)); if ( !(fscanf(file, "%d", &ret->gvthres[0])==1) /* threshold for binarization 1.image */ diff --git a/liboptv/src/track.c b/liboptv/src/track.c index 3b9a3c8f..3568078a 100644 --- a/liboptv/src/track.c +++ b/liboptv/src/track.c @@ -31,6 +31,9 @@ #include #include +#define _USE_MATH_DEFINES +#include + /* internal-use defines, not needed by the outside world. */ #define TR_UNUSED -1 diff --git a/liboptv/src/vec_utils.c b/liboptv/src/vec_utils.c index fe0abfe5..dfa65f63 100644 --- a/liboptv/src/vec_utils.c +++ b/liboptv/src/vec_utils.c @@ -9,6 +9,17 @@ the logical structure, and allow optimizing for size as well. #include "vec_utils.h" #include +#ifdef MSVC_NAN_REQUIRED + +/* Returns a NAN, which is surprisingly non-trivial on Visual C for Python 2.7 */ + +static const unsigned long _explicit_dNAN[2] = {0x00000000, 0x7ff80000}; +double return_nan(void) { + return *( double* )_explicit_dNAN; +} + +#endif + /* vec_init() initializes all components of a 3D vector to NaN. Arguments: diff --git a/py_bind/README.txt b/py_bind/README.txt index e6b865fc..c2f1365d 100644 --- a/py_bind/README.txt +++ b/py_bind/README.txt @@ -13,49 +13,31 @@ The plan is to add more wrappers as other contributors of liboptv find them necessary and choose to add them here. -Installation on Linux / OS X ----------------------------- -This package assumes that liboptv is already installed. If it is not, see the -instructions for installing it in the liboptv source code. +Installation +------------ +Run pip install openptv, which should install everything. -To build the wrapper, Cython must also be installed. Binary installers are -available at www.cython.org. Linux users may simply install from the package -manager, Windows users can get it through Python(x,y). If you have installed -openptv-python from source, then you already have Cython working. +Building the Package +-------------------- +The package has to be built from the full repository. Make sure all the dependencies +in requirements.txt are installed, then run: -The test suite consists of Python code that may be run automatically using the -Nose test harness: https://nose.readthedocs.org/en/latest/# +python setup.py prepare # This copies the liboptv sources and converts pyx files to C +python setup.py build # This builds the package -With the dependencies installed, the optv package is installed by typing the -following command in a terminal: +You can then create source distributions and binary wheels: - sudo python setup.py install +python setup.py sdist bdist_wheel -Note that on many systems you will first need to obtain administrator -privileges. On Linux the 'sudo' command is recommended, as shown above. +You can upload them to your favorite repository, as they reside in the dist subdirectory. -Installation on Windows ------------------------ -Install liboptv as instructed in the Windows installation section of -liboptv/README.txt. This way you already have an MSYS environment, -which you continue to use here. +Note: You need to build wheels for each platform you want to support. -At this point, since we are building a Python module, you must have a -Python version installed. The Python(x,y) distribution, available from -https://code.google.com/p/pythonxy/ contains all you need. During the -installation you will be asked to choose packages. To the default -selection add the Cython package. For testing your installation later, -make sure the ``nose`` package is also installed. +On Windows, you must install the Visual C++ Compiler for Python 2.7. It can be found here: +https://www.microsoft.com/en-us/download/details.aspx?id=44266 -The commands for installing the Python modules are a bit more elaborate -than the Linux instructions because Windows is evil. First one builds the -package: +You will need to build the package from a Visual C++ for Python Command Prompt. - python setup.py build_ext -I/usr/include -L/usr/lib/ --compiler=mingw32 - -Then installation is simply - - python setup.py install Testing the installation ------------------------ diff --git a/py_bind/requirements.txt b/py_bind/requirements.txt new file mode 100644 index 00000000..960203de --- /dev/null +++ b/py_bind/requirements.txt @@ -0,0 +1,4 @@ +Cython==0.29.5 +nose==1.3.7 +numpy==1.16.1 +PyYAML>=4.2b1 diff --git a/py_bind/setup.py b/py_bind/setup.py index 38af823e..0a8a06f7 100644 --- a/py_bind/setup.py +++ b/py_bind/setup.py @@ -1,36 +1,159 @@ # -*- coding: utf-8 -*- +from __future__ import print_function from distutils.core import setup -from Cython.Distutils import build_ext -from Cython.Distutils.extension import Extension - -import numpy as np +import setuptools import os -inc_dirs = [np.get_include(), '.'] +import shutil +import sys +import glob +from setuptools import Extension +from setuptools.command.build_ext import build_ext + + +class PrepareCommand(setuptools.Command): + # We must make some preparations before we can build the extension. + # First, we should copy the liboptv sources to a subdirectory, so they can be included with the sdist package. + # Second, we convert the pyx files to c files, so the package can be installed from source without requiring Cython + description = "Copy the liboptv sources and convert pyx files to C before building" + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + self.copy_source_files() + self.convert_to_c() + + def copy_source_files(self): + if not os.path.exists('../liboptv'): + print('../liboptv does not exist. You must run setup.py prepare from with the full liboptv repository', + file=sys.stderr) + raise Exception('.//liboptv does not exist') + + print('Copying the liboptv source files...') + if os.path.exists('./liboptv'): + shutil.rmtree('./liboptv') + os.makedirs('./liboptv') + shutil.copytree('../liboptv/include', 'liboptv/include/optv') + shutil.copytree('../liboptv/src', 'liboptv/src') + + def convert_to_c(self): + print('Converting pyx files to C sources...') + pyx_files = glob.glob('./optv/*.pyx') + for pyx in pyx_files: + self.cython(pyx) + + def cython(self, pyx): + from Cython.Compiler.CmdLine import parse_command_line + from Cython.Compiler.Main import compile + options, sources = parse_command_line(['-2', pyx]) + result = compile(sources, options) + if result.num_errors > 0: + print('Errors converting %s to C' % pyx, file=sys.stderr) + raise Exception('Errors converting %s to C' % pyx) + self.announce('Converted %s to C' % pyx) + + +class BuildExt(build_ext, object): + def run(self): + if not os.path.exists('./liboptv') or not glob.glob('./optv/*.c'): + print('You must run setup.py prepare before building the extension', file=sys.stderr) + raise Exception('You must run setup.py prepare before building the extension') + self.add_include_dirs() + super(BuildExt, self).run() + + # We inherite from object to make super() work, see here: https://stackoverflow.com/a/18392639/871910 + + @staticmethod + def get_numpy_include_dir(): + # Get the numpy include directory, adapted from the following RLs: + # https://www.programcreek.com/python/example/60953/__builtin__.__NUMPY_SETUP__ + # https://github.com/astropy/astropy-helpers/blob/master/astropy_helpers/utils.py + if sys.version_info[0] >= 3: + import builtins + if hasattr(builtins, '__NUMPY_SETUP__'): + del builtins.__NUMPY_SETUP__ + import imp + import numpy + imp.reload(numpy) + else: + import __builtin__ + if hasattr(__builtin__, '__NUMPY_SETUP__'): + del __builtin__.__NUMPY_SETUP__ + import numpy + reload(numpy) + + try: + return numpy.get_include() + except AttributeError: + return numpy.get_include_dir() + + def add_include_dirs(self): + # All the Extension objects do not have their include_dir specified, we add it here as it requires + # importing numpy, which we do not want to do unless build_ext is really running. + # This allows pip to install numpy as it processes dependencies before building extensions + np_include_dir = BuildExt.get_numpy_include_dir() + include_dirs = [np_include_dir, '.', './liboptv/include', './liboptv/include/optv'] + + for extension in self.extensions: # We dug into setuptools and distutils to find the properties to change + extension.include_dirs = include_dirs + +# The python bindings have been redone, so they do not require the liboptv.so to be installed. +# We do the following: +# +# Copy all the C sources, to c-src, as setup.py only packs files under setup.py in the ZIP file +# +# Find all the C source files from liboptv/src and add them to each extension, so they are compiled with the extension. +# This may seem expensive, as the files are added to each compiled extension (as if using a static liboptv library) +# in the future we may unite Cython modules into one extension (really not straightforward) and save the extra space. +# +# Tell Cython to look for header files in c-src/include/ (for the Cython code) and c-src/include/optv (for the C code) + +def get_liboptv_sources(): + return glob.glob('./liboptv/src/*.c') + def mk_ext(name, files): - return Extension(name, files, libraries=['optv'], include_dirs=inc_dirs, - pyrex_include_dirs=['.']) + # Do not specify include dirs, as they require numpy to be installed. Add them in BuildExt + return Extension(name, files + get_liboptv_sources()) + ext_mods = [ - mk_ext("optv.tracking_framebuf", ["optv/tracking_framebuf.pyx"]), - mk_ext("optv.parameters", ["optv/parameters.pyx"]), - mk_ext("optv.calibration", ["optv/calibration.pyx"]), - mk_ext("optv.transforms", ["optv/transforms.pyx"]), - mk_ext("optv.imgcoord", ["optv/imgcoord.pyx"]), - mk_ext("optv.image_processing", ["optv/image_processing.pyx"]), - mk_ext("optv.correspondences", ["optv/correspondences.pyx"]), - mk_ext("optv.segmentation", ["optv/segmentation.pyx"]), - mk_ext("optv.epipolar", ["optv/epipolar.pyx"]), - mk_ext("optv.tracker", ["optv/tracker.pyx"]), - mk_ext("optv.orientation", ["optv/orientation.pyx"]) + mk_ext("optv.tracking_framebuf", ["optv/tracking_framebuf.c"]), + mk_ext("optv.parameters", ["optv/parameters.c"]), + mk_ext("optv.calibration", ["optv/calibration.c"]), + mk_ext("optv.transforms", ["optv/transforms.c"]), + mk_ext("optv.imgcoord", ["optv/imgcoord.c"]), + mk_ext("optv.image_processing", ["optv/image_processing.c"]), + mk_ext("optv.correspondences", ["optv/correspondences.c"]), + mk_ext("optv.segmentation", ["optv/segmentation.c"]), + mk_ext("optv.epipolar", ["optv/epipolar.c"]), + mk_ext("optv.tracker", ["optv/tracker.c"]), + mk_ext("optv.orientation", ["optv/orientation.c"]) ] setup( name="optv", - cmdclass = {'build_ext': build_ext}, + cmdclass={ + 'build_ext': BuildExt, + 'prepare': PrepareCommand, + }, packages=['optv'], - ext_modules = ext_mods, - package_data = {'optv': ['*.pxd']} + ext_modules=ext_mods, + include_package_data=True, + data_files=[ + ('liboptv', glob.glob('liboptv/src/*.c') + glob.glob('liboptv/include/optv/*.h')) + ], + package_data={ + 'optv': ['*.pxd', '*.c', '*.h'], + }, + version='0.2.3', + install_requires=[ + 'numpy==1.16.1', + 'pyyaml', + ], + setup_requires=['numpy==1.16.1'], ) - -