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
00101 eigenFromBoostPython(cloud, pycloud, "cloud");
00102
00103
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
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
00140 MappedEigenDoubleMatrix* mappedQuery(eigenFromBoostPython(query, "query"));
00141 NNSNabo::IndexMatrix indexMatrix(k, mappedQuery->cols());
00142 NNSNabo::Matrix dists2Matrix(k, mappedQuery->cols());
00143
00144
00145 nns->knn(*mappedQuery, indexMatrix, dists2Matrix, k, epsilon, optionFlags, maxRadius);
00146
00147
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
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 }