nabo.cpp
Go to the documentation of this file.
00001 #include <Python.h>
00002 #include <boost/python.hpp>
00003 #include <numpy/arrayobject.h>
00004 #include "../nabo/nabo.h"
00005 #include <iostream>
00006 #include <cassert>
00007 
00008 using namespace boost::python;
00009 
00010 typedef Nabo::NNSearchD NNSNabo;
00011 typedef NNSNabo::Index Index;
00012 typedef NNSNabo::SearchType SearchType;
00013 typedef NNSNabo::SearchOptionFlags SearchOptionFlags;
00014 typedef Eigen::Map<NNSNabo::Matrix> MappedEigenDoubleMatrix;
00015 typedef Eigen::Map<NNSNabo::IndexMatrix> MappedEigenIndexMatrix;
00016 
00017 static const double infD = std::numeric_limits<double>::infinity();
00018 static const Index maxI = std::numeric_limits<Index>::max();
00019 
00020 #if PY_MAJOR_VERSION >= 3
00021 int
00022 init_numpy()
00023 {
00024         import_array();
00025         return 0;
00026 }
00027 #else
00028 void
00029 init_numpy()
00030 {
00031         import_array();
00032 }
00033 #endif
00034 
00035 void matrixSizeFromPythonArray(const PyObject* cloudObj, int& rowCount, int& colCount)
00036 {
00037         assert(PyArray_CHKFLAGS(cloudObj, NPY_C_CONTIGUOUS) || PyArray_CHKFLAGS(cloudObj, NPY_F_CONTIGUOUS));
00038         assert(PyArray_NDIM(cloudObj) == 2);
00039         const npy_intp *shape = PyArray_DIMS(cloudObj);
00040         if (PyArray_CHKFLAGS(cloudObj, NPY_F_CONTIGUOUS))
00041         {
00042                 colCount = shape[1];
00043                 rowCount = shape[0];
00044         }
00045         else
00046         {
00047                 colCount = shape[0];
00048                 rowCount = shape[1];
00049         }
00050 }
00051 
00052 void checkPythonArray(const PyObject* cloudObj, const char *paramName)
00053 {
00054         std::string startMsg("Argument \"");
00055         startMsg += paramName;
00056         startMsg += "\" ";
00057         
00058         if (!PyArray_Check(cloudObj))
00059                 throw std::runtime_error(startMsg + "must be a multi-dimensional array");
00060         const int nDim = PyArray_NDIM(cloudObj);
00061         if (nDim != 2)
00062                 throw std::runtime_error(startMsg + "must be a two-dimensional array");
00063         if (PyArray_TYPE(cloudObj) != NPY_FLOAT64)
00064                 throw std::runtime_error(startMsg + "must hold doubles");
00065         if (!PyArray_CHKFLAGS(cloudObj, NPY_C_CONTIGUOUS) && !PyArray_CHKFLAGS(cloudObj, NPY_F_CONTIGUOUS))
00066                 throw std::runtime_error(startMsg + "must be a continuous array");
00067 }
00068 
00069 MappedEigenDoubleMatrix* eigenFromBoostPython(const object cloudIn, const char *paramName)
00070 {
00071         int dimCount, pointCount;
00072         const PyObject *cloudObj(cloudIn.ptr());
00073         
00074         checkPythonArray(cloudObj, paramName);
00075         
00076         matrixSizeFromPythonArray(cloudObj, dimCount, pointCount);
00077         double* cloudData(reinterpret_cast<double*>(PyArray_DATA(cloudObj)));
00078         
00079         return new MappedEigenDoubleMatrix(cloudData, dimCount, pointCount);
00080 }
00081 
00082 void eigenFromBoostPython(NNSNabo::Matrix& cloudOut, const object cloudIn, const char *paramName)
00083 {
00084         int dimCount, pointCount;
00085         const PyObject *cloudObj(cloudIn.ptr());
00086         
00087         checkPythonArray(cloudObj, paramName);
00088         
00089         matrixSizeFromPythonArray(cloudObj, dimCount, pointCount);
00090         cloudOut.resize(dimCount, pointCount);
00091         
00092         memcpy(cloudOut.data(), PyArray_DATA(cloudObj), pointCount*dimCount*sizeof(double));
00093 }
00094 
00095 class NearestNeighbourSearch
00096 {
00097 public:
00098         NearestNeighbourSearch(const object pycloud, const SearchType searchType = NNSNabo::KDTREE_LINEAR_HEAP, const Index dim = maxI, const dict params = dict())
00099         {
00100                 // build cloud
00101                 eigenFromBoostPython(cloud, pycloud, "cloud");
00102                 
00103                 // build params
00104                 Nabo::Parameters _params;
00105 
00106 #if PY_MAJOR_VERSION >=3
00107                 object it = params.items();
00108 #else
00109                 object it = params.iteritems();
00110 #endif
00111 
00112                 for(int i = 0; i < len(params); ++i)
00113                 {
00114                         const tuple item(it.attr("next")());
00115                         const std::string key = extract<std::string>(item[0]);
00116                         const object val(item[1]);
00117                         const std::string valType(val.ptr()->ob_type->tp_name);
00118                         if (valType == "int")
00119                         {
00120                                 const int iVal = extract<int>(val);
00121                                 if (iVal >= 0)
00122                                         _params[key] = (unsigned)iVal;
00123                                 else
00124                                         _params[key] = iVal;
00125                         }
00126                 }
00127                 
00128                 // create search
00129                 nns = NNSNabo ::create(cloud, dim, searchType, 0, _params);
00130         }
00131         
00132         ~NearestNeighbourSearch()
00133         {
00134                 delete nns;
00135         }
00136         
00137         tuple knn(const object query, const Index k = 1, const double epsilon = 0, const unsigned optionFlags = 0, const double maxRadius = infD)
00138         {
00139                 // map query and create output matrices
00140                 MappedEigenDoubleMatrix* mappedQuery(eigenFromBoostPython(query, "query"));
00141                 NNSNabo::IndexMatrix indexMatrix(k, mappedQuery->cols());
00142                 NNSNabo::Matrix dists2Matrix(k, mappedQuery->cols());
00143                 
00144                 // do the search
00145                 nns->knn(*mappedQuery, indexMatrix, dists2Matrix, k, epsilon, optionFlags, maxRadius);
00146                 
00147                 // build resulting python types
00148                 npy_intp retDims[2] = { mappedQuery->cols(), k };
00149                 const int dataCount(k * mappedQuery->cols());
00150                 PyObject* dists2 = PyArray_EMPTY(2, retDims, PyArray_DOUBLE, PyArray_ISFORTRAN(query.ptr()));
00151                 memcpy(PyArray_DATA(dists2), dists2Matrix.data(), dataCount*sizeof(double));
00152                 PyObject* indices = PyArray_EMPTY(2, retDims, PyArray_INT, PyArray_ISFORTRAN(query.ptr()));
00153                 memcpy(PyArray_DATA(indices), indexMatrix.data(), dataCount*sizeof(int));
00154                 delete mappedQuery;
00155                 
00156                 // return results
00157                 return make_tuple(object(handle<>(indices)), object(handle<>(dists2)));
00158         }
00159         
00160 protected:
00161         NNSNabo *nns;
00162         NNSNabo::Matrix cloud;
00163 };
00164 
00165 BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(knn_overloads, knn, 1, 5)
00166 
00167 BOOST_PYTHON_MODULE(pynabo)
00168 {
00169         init_numpy();
00170         
00171         enum_<SearchType>("SearchType", "Type of algorithm used for search.")
00172                 .value("BRUTE_FORCE", NNSNabo::BRUTE_FORCE)
00173                 .value("KDTREE_LINEAR_HEAP", NNSNabo::KDTREE_LINEAR_HEAP)
00174                 .value("KDTREE_TREE_HEAP", NNSNabo::KDTREE_TREE_HEAP)
00175         ;
00176         
00177         enum_<SearchOptionFlags>("SearchOptionFlags", "Flags you can OR when creating search.")
00178                 .value("ALLOW_SELF_MATCH", NNSNabo::ALLOW_SELF_MATCH)
00179                 .value("SORT_RESULTS", NNSNabo::SORT_RESULTS)
00180         ;
00181         
00182         class_<NearestNeighbourSearch>(
00183                 "NearestNeighbourSearch",
00184                 "Nearest-neighbour search object, containing the data, on which you can do the knn(...) query.\n\n"
00185                 "The data and query must be continuous numpy arrays.\n"
00186                 "As numpy proposes both C and Fortran data orders, pynabo\n"
00187                 "will always consider the contiguous dimension to be coordinates\n"
00188                 "of points, regardless of order, as this provides the fastest\n"
00189                 "possible execution. The return values of knn(...) will have\n"
00190                 "the same order as the query and will have the different results\n"
00191                 "of each point in the contiguous dimension."
00192                 ,
00193                 init<const object, optional<const SearchType, const Index, const dict> >(
00194                         "Create a nearest-neighbour search.\n\n"
00195                         "Arguments:\n"
00196                         "    data -- data-point cloud in which to search, must be a numpy array\n"
00197                         "    searchType -- type of search, default: KDTREE_LINEAR_HEAP\n"
00198                         "    dim -- number of dimensions to consider, must be lower or equal to cloud.rows(), default: dim of data\n"
00199                         "    creationOptionFlags -- creation options, a bitwise OR of elements of CreationOptionFlags",
00200                         args("self", "data", "searchType", "dim", "creationOptionFlags, default: 0")
00201                 )
00202         )
00203         .def("knn", &NearestNeighbourSearch::knn,
00204                 knn_overloads(
00205                         args("self", "query", "k", "epsilon", "optionFlags", "maxRadius"),
00206                         "Find the k nearest neighbours of query in data.\n\n"
00207                         "Arguments:\n"
00208                         "    query -- query points, must be a numpy array\n"
00209                         "    k -- number of nearest neighbour requested, default: 1\n"
00210                         "    epsilon -- maximal ratio of error for approximate search, 0 for exact search; has no effect if the number of neighbour found is smaller than the number requested; default: 0.\n"
00211                         "    optionFlags -- search options, a bitwise OR of elements of SearchOptionFlags, default: 0\n"
00212                         "    maxRadius -- maximum radius in which to search, can be used to prune search, is not affected by epsilon, default: inf\n\n"
00213                         "Returns:\n"
00214                         "    A tuple of two 2D numpy arrays, the first containing indices to points in data, the other containing squared distances."
00215                 )
00216         )
00217         ;
00218 }


libnabo
Author(s): Stéphane Magnenat
autogenerated on Sun Feb 10 2019 03:52:20