提交 21bec7e8 编写于 作者: M Marius Muja

Make python bindings compatible with python2 and python3

上级 f320d4b5
......@@ -104,7 +104,7 @@ public:
bestParams_ = estimateBuildParams();
Logger::info("----------------------------------------------------\n");
Logger::info("Autotuned parameters:\n");
if (Logger::getLevel()<=FLANN_LOG_INFO)
if (Logger::getLevel()>=FLANN_LOG_INFO)
print_params(bestParams_);
Logger::info("----------------------------------------------------\n");
......@@ -113,7 +113,7 @@ public:
speedup_ = estimateSearchParams(bestSearchParams_);
Logger::info("----------------------------------------------------\n");
Logger::info("Search parameters:\n");
if (Logger::getLevel()<=FLANN_LOG_INFO)
if (Logger::getLevel()>=FLANN_LOG_INFO)
print_params(bestSearchParams_);
Logger::info("----------------------------------------------------\n");
bestParams_["search_params"] = bestSearchParams_;
......
......@@ -24,9 +24,8 @@
#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
#THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from index import *
from io.dataset import load, save
try:
from io.hdf5_dataset import load_range
except:
pass
#import sys
#import os
#sys.path.insert(0, os.path.split(__file__)[0]) # make python3 happy
from pyflann.index import *
#Copyright 2008-2009 Marius Muja (mariusm@cs.ubc.ca). All rights reserved.
#Copyright 2008-2009 David G. Lowe (lowe@cs.ubc.ca). All rights reserved.
#
#THE BSD LICENSE
#
#Redistribution and use in source and binary forms, with or without
#modification, are permitted provided that the following conditions
#are met:
#
#1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
#THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
#IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
#OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
#IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
#INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
#NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
#DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
#THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
#THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#from pyflann import *
#from pyflann_parameters import parameter_list, algorithm_names
#from pyflann_parameters import centers_init_names, log_level_names
from flann_ctypes import *
#Copyright 2008-2009 Marius Muja (mariusm@cs.ubc.ca). All rights reserved.
#Copyright 2008-2009 David G. Lowe (lowe@cs.ubc.ca). All rights reserved.
#
#THE BSD LICENSE
#
#Redistribution and use in source and binary forms, with or without
#modification, are permitted provided that the following conditions
#are met:
#
#1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
#THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
#IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
#OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
#IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
#INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
#NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
#DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
#THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
#THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from ctypes import *
#from ctypes.util import find_library
from numpy import float32, float64, uint8, int32, matrix, array, empty, reshape, require
from numpy.ctypeslib import load_library, ndpointer
import os
from pyflann.exceptions import FLANNException
import sys
STRING = c_char_p
class CustomStructure(Structure):
"""
This class extends the functionality of the ctype's structure
class by adding custom default values to the fields and a way of translating
field types.
"""
_defaults_ = {}
_translation_ = {}
def __init__(self):
Structure.__init__(self)
self.__field_names = [ f for (f,t) in self._fields_]
self.update(self._defaults_)
def update(self, dict):
for k,v in dict.iteritems():
if k in self.__field_names:
setattr(self,k,self.__translate(k,v))
def __getitem__(self, k):
if k in self.__field_names:
return self.__translate_back(k,getattr(self,k))
def __setitem__(self, k, v):
if k in self.__field_names:
setattr(self,k,self.__translate(k,v))
else:
raise KeyError("No such member: "+k)
def keys(self):
return self.__field_names
def __translate(self,k,v):
if k in self._translation_:
if v in self._translation_[k]:
return self._translation_[k][v]
return v
def __translate_back(self,k,v):
if k in self._translation_:
for tk,tv in self._translation_[k].iteritems():
if tv==v:
return tk
return v
class FLANNParameters(CustomStructure):
_fields_ = [
('algorithm', c_int),
('checks', c_int),
('cb_index', c_float),
('eps', c_float),
('trees', c_int),
('leaf_max_size', c_int),
('branching', c_int),
('iterations', c_int),
('centers_init', c_int),
('target_precision', c_float),
('build_weight', c_float),
('memory_weight', c_float),
('sample_fraction', c_float),
('table_number_', c_uint),
('key_size_', c_uint),
('multi_probe_level_', c_uint),
('log_level', c_int),
('random_seed', c_long),
]
_defaults_ = {
'algorithm' : 'kdtree',
'checks' : 32,
'eps' : 0.0,
'cb_index' : 0.5,
'trees' : 1,
'leaf_max_size' : 4,
'branching' : 32,
'iterations' : 5,
'centers_init' : 'random',
'target_precision' : 0.9,
'build_weight' : 0.01,
'memory_weight' : 0.0,
'sample_fraction' : 0.1,
'table_number_': 12,
'key_size_': 20,
'multi_probe_level_': 2,
'log_level' : "warning",
'random_seed' : -1
}
_translation_ = {
"algorithm" : {"linear" : 0, "kdtree" : 1, "kmeans" : 2, "composite" : 3, "kdtree_simple" : 4, "saved": 254, "autotuned" : 255, "default" : 1},
"centers_init" : {"random" : 0, "gonzales" : 1, "kmeanspp" : 2, "default" : 0},
"log_level" : {"none" : 0, "fatal" : 1, "error" : 2, "warning" : 3, "info" : 4, "default" : 2}
}
default_flags = ['C_CONTIGUOUS', 'ALIGNED']
allowed_types = [ float32, float64, uint8, int32]
FLANN_INDEX = c_void_p
def load_flann_library():
root_dir = os.path.abspath(os.path.dirname(__file__))
libnames = ['libflann.so']
libdir = 'lib'
if sys.platform == 'win32':
libnames = ['flann.dll', 'libflann.dll']
elif sys.platform == 'darwin':
libnames = ['libflann.dylib']
while root_dir!=None:
for libname in libnames:
try:
#print "Trying ",os.path.join(root_dir,'lib',libname)
flannlib = cdll[os.path.join(root_dir,libdir,libname)]
return flannlib
except Exception,e:
pass
try:
flannlib = cdll[os.path.join(root_dir,"build",libdir,libname)]
return flannlib
except Exception,e:
pass
tmp = os.path.dirname(root_dir)
if tmp == root_dir:
root_dir = None
else:
root_dir = tmp
# if we didn't find the library so far, try loading without
# a full path as a last resort
for libname in libnames:
try:
#print "Trying",libname
flannlib=cdll[libname]
return flannlib
except:
pass
return None
flannlib = load_flann_library()
if flannlib == None:
raise ImportError('Cannot load dynamic library. Did you compile FLANN?')
class FlannLib: pass
flann = FlannLib()
flannlib.flann_log_verbosity.restype = None
flannlib.flann_log_verbosity.argtypes = [
c_int # level
]
flannlib.flann_set_distance_type.restype = None
flannlib.flann_set_distance_type.argtypes = [
c_int,
c_int,
]
type_mappings = ( ('float','float32'),
('double','float64'),
('byte','uint8'),
('int','int32') )
def define_functions(str):
for type in type_mappings:
exec str%{'C':type[0],'numpy':type[1]}
flann.build_index = {}
define_functions(r"""
flannlib.flann_build_index_%(C)s.restype = FLANN_INDEX
flannlib.flann_build_index_%(C)s.argtypes = [
ndpointer(%(numpy)s, ndim = 2, flags='aligned, c_contiguous'), # dataset
c_int, # rows
c_int, # cols
POINTER(c_float), # speedup
POINTER(FLANNParameters) # flann_params
]
flann.build_index[%(numpy)s] = flannlib.flann_build_index_%(C)s
""")
flann.save_index = {}
define_functions(r"""
flannlib.flann_save_index_%(C)s.restype = None
flannlib.flann_save_index_%(C)s.argtypes = [
FLANN_INDEX, # index_id
c_char_p #filename
]
flann.save_index[%(numpy)s] = flannlib.flann_save_index_%(C)s
""")
flann.load_index = {}
define_functions(r"""
flannlib.flann_load_index_%(C)s.restype = FLANN_INDEX
flannlib.flann_load_index_%(C)s.argtypes = [
c_char_p, #filename
ndpointer(%(numpy)s, ndim = 2, flags='aligned, c_contiguous'), # dataset
c_int, # rows
c_int, # cols
]
flann.load_index[%(numpy)s] = flannlib.flann_load_index_%(C)s
""")
flann.find_nearest_neighbors = {}
define_functions(r"""
flannlib.flann_find_nearest_neighbors_%(C)s.restype = c_int
flannlib.flann_find_nearest_neighbors_%(C)s.argtypes = [
ndpointer(%(numpy)s, ndim = 2, flags='aligned, c_contiguous'), # dataset
c_int, # rows
c_int, # cols
ndpointer(%(numpy)s, ndim = 2, flags='aligned, c_contiguous'), # testset
c_int, # tcount
ndpointer(int32, ndim = 2, flags='aligned, c_contiguous, writeable'), # result
ndpointer(float32, ndim = 2, flags='aligned, c_contiguous, writeable'), # dists
c_int, # nn
POINTER(FLANNParameters) # flann_params
]
flann.find_nearest_neighbors[%(numpy)s] = flannlib.flann_find_nearest_neighbors_%(C)s
""")
# fix definition for the 'double' case
flannlib.flann_find_nearest_neighbors_double.restype = c_int
flannlib.flann_find_nearest_neighbors_double.argtypes = [
ndpointer(float64, ndim = 2, flags='aligned, c_contiguous'), # dataset
c_int, # rows
c_int, # cols
ndpointer(float64, ndim = 2, flags='aligned, c_contiguous'), # testset
c_int, # tcount
ndpointer(int32, ndim = 2, flags='aligned, c_contiguous, writeable'), # result
ndpointer(float64, ndim = 2, flags='aligned, c_contiguous, writeable'), # dists
c_int, # nn
POINTER(FLANNParameters) # flann_params
]
flann.find_nearest_neighbors[float64] = flannlib.flann_find_nearest_neighbors_double
flann.find_nearest_neighbors_index = {}
define_functions(r"""
flannlib.flann_find_nearest_neighbors_index_%(C)s.restype = c_int
flannlib.flann_find_nearest_neighbors_index_%(C)s.argtypes = [
FLANN_INDEX, # index_id
ndpointer(%(numpy)s, ndim = 2, flags='aligned, c_contiguous'), # testset
c_int, # tcount
ndpointer(int32, ndim = 2, flags='aligned, c_contiguous, writeable'), # result
ndpointer(float32, ndim = 2, flags='aligned, c_contiguous, writeable'), # dists
c_int, # nn
POINTER(FLANNParameters) # flann_params
]
flann.find_nearest_neighbors_index[%(numpy)s] = flannlib.flann_find_nearest_neighbors_index_%(C)s
""")
flannlib.flann_find_nearest_neighbors_index_double.restype = c_int
flannlib.flann_find_nearest_neighbors_index_double.argtypes = [
FLANN_INDEX, # index_id
ndpointer(float64, ndim = 2, flags='aligned, c_contiguous'), # testset
c_int, # tcount
ndpointer(int32, ndim = 2, flags='aligned, c_contiguous, writeable'), # result
ndpointer(float64, ndim = 2, flags='aligned, c_contiguous, writeable'), # dists
c_int, # nn
POINTER(FLANNParameters) # flann_params
]
flann.find_nearest_neighbors_index[float64] = flannlib.flann_find_nearest_neighbors_index_double
flann.radius_search = {}
define_functions(r"""
flannlib.flann_radius_search_%(C)s.restype = c_int
flannlib.flann_radius_search_%(C)s.argtypes = [
FLANN_INDEX, # index_id
ndpointer(%(numpy)s, ndim = 1, flags='aligned, c_contiguous'), # query
ndpointer(int32, ndim = 1, flags='aligned, c_contiguous, writeable'), # indices
ndpointer(float32, ndim = 1, flags='aligned, c_contiguous, writeable'), # dists
c_int, # max_nn
c_float, # radius
POINTER(FLANNParameters) # flann_params
]
flann.radius_search[%(numpy)s] = flannlib.flann_radius_search_%(C)s
""")
flannlib.flann_radius_search_double.restype = c_int
flannlib.flann_radius_search_double.argtypes = [
FLANN_INDEX, # index_id
ndpointer(float64, ndim = 1, flags='aligned, c_contiguous'), # query
ndpointer(int32, ndim = 1, flags='aligned, c_contiguous, writeable'), # indices
ndpointer(float64, ndim = 1, flags='aligned, c_contiguous, writeable'), # dists
c_int, # max_nn
c_float, # radius
POINTER(FLANNParameters) # flann_params
]
flann.radius_search[float64] = flannlib.flann_radius_search_double
flann.compute_cluster_centers = {}
define_functions(r"""
flannlib.flann_compute_cluster_centers_%(C)s.restype = c_int
flannlib.flann_compute_cluster_centers_%(C)s.argtypes = [
ndpointer(%(numpy)s, ndim = 2, flags='aligned, c_contiguous'), # dataset
c_int, # rows
c_int, # cols
c_int, # clusters
ndpointer(float32, flags='aligned, c_contiguous, writeable'), # result
POINTER(FLANNParameters) # flann_params
]
flann.compute_cluster_centers[%(numpy)s] = flannlib.flann_compute_cluster_centers_%(C)s
""")
# double is an exception
flannlib.flann_compute_cluster_centers_double.restype = c_int
flannlib.flann_compute_cluster_centers_double.argtypes = [
ndpointer(float64, ndim = 2, flags='aligned, c_contiguous'), # dataset
c_int, # rows
c_int, # cols
c_int, # clusters
ndpointer(float64, flags='aligned, c_contiguous, writeable'), # result
POINTER(FLANNParameters) # flann_params
]
flann.compute_cluster_centers[float64] = flannlib.flann_compute_cluster_centers_double
flann.free_index = {}
define_functions(r"""
flannlib.flann_free_index_%(C)s.restype = None
flannlib.flann_free_index_%(C)s.argtypes = [
FLANN_INDEX, # index_id
POINTER(FLANNParameters) # flann_params
]
flann.free_index[%(numpy)s] = flannlib.flann_free_index_%(C)s
""")
def ensure_2d_array(array, flags, **kwargs):
array = require(array, requirements = flags, **kwargs)
if len(array.shape) == 1:
array = array.reshape(-1,array.size)
return array
......@@ -25,15 +25,10 @@
#THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# This module defines a set of exceptions that are used through out
# the flann application
# This module defines exceptions that are used by the flann python bindings
class FLANNException(Exception):
def __init__(self, *args):
Exception.__init__(self, *args)
class CommandException(Exception):
def __init__(self, *args):
Exception.__init__(self, *args)
......@@ -24,8 +24,8 @@
#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
#THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from bindings.flann_ctypes import *
from io.dataset import *
from pyflann.flann_ctypes import *
from pyflann.exceptions import *
import numpy.random as _rn
......@@ -53,6 +53,13 @@ def set_distance_type(distance_type, order = 0):
flannlib.flann_set_distance_type(distance_type,order)
def to_bytes(string):
if sys.hexversion > 0x03000000:
return bytes(string,'utf-8')
return string
# This class is derived from an initial implementation by Hoyt Koepke (hoytak@cs.ubc.ca)
class FLANN:
"""
......@@ -169,9 +176,8 @@ class FLANN:
"""
This saves the index to a disk file.
"""
if self.__curindex != None:
flann.save_index[self.__curindex_type](self.__curindex, c_char_p(filename))
flann.save_index[self.__curindex_type](self.__curindex, c_char_p(to_bytes(filename)))
def load_index(self, filename, pts):
"""
......@@ -190,7 +196,7 @@ class FLANN:
self.__curindex_data = None
self.__curindex_type = None
self.__curindex = flann.load_index[pts.dtype.type](c_char_p(filename), pts, npts, dim)
self.__curindex = flann.load_index[pts.dtype.type](c_char_p(to_bytes(filename)), pts, npts, dim)
self.__curindex_data = pts
self.__curindex_type = pts.dtype.type
......
#Copyright 2008-2009 Marius Muja (mariusm@cs.ubc.ca). All rights reserved.
#Copyright 2008-2009 David G. Lowe (lowe@cs.ubc.ca). All rights reserved.
#
#THE BSD LICENSE
#
#Redistribution and use in source and binary forms, with or without
#modification, are permitted provided that the following conditions
#are met:
#
#1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
#THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
#IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
#OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
#IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
#INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
#NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
#DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
#THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
#THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from dataset import *
\ No newline at end of file
#Copyright 2008-2009 Marius Muja (mariusm@cs.ubc.ca). All rights reserved.
#Copyright 2008-2009 David G. Lowe (lowe@cs.ubc.ca). All rights reserved.
#
#THE BSD LICENSE
#
#Redistribution and use in source and binary forms, with or without
#modification, are permitted provided that the following conditions
#are met:
#
#1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
#THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
#IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
#OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
#IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
#INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
#NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
#DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
#THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
#THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import with_statement
from pyflann.exceptions import FLANNException
import numpy
import os.path
def check(filename):
f = open(filename,"r")
header = f.read(6)
if header[0:6]=="BINARY":
return True
return False
def save(dataset, filename):
if not isinstance(dataset,numpy.ndarray):
raise FLANNException("Dataset must be in numpy format")
with open(filename+".meta", 'w') as fd_meta:
fd_meta.write(\
"""BINARY
%d
%d
%s"""%(dataset.shape[0],dataset.shape[1],dataset.dtype.name))
dataset.tofile(filename)
def load(filename, rows = -1, cols = -1, dtype = numpy.float32):
if os.path.isfile(filename+".meta"):
with open(filename+".meta","r") as fd:
header = fd.readline()
assert( header[0:6] == "BINARY")
rows = int(fd.readline())
cols = int(fd.readline())
dtype = numpy.dtype(fd.readline().strip())
else:
if rows==-1 or cols==-1:
raise "No .meta file present, you must specify dataset rows, cols asd dtype"
data = numpy.fromfile(file=filename, dtype=dtype, count=rows*cols)
data.shape = (rows,cols)
return data
\ No newline at end of file
#Copyright 2008-2009 Marius Muja (mariusm@cs.ubc.ca). All rights reserved.
#Copyright 2008-2009 David G. Lowe (lowe@cs.ubc.ca). All rights reserved.
#
#THE BSD LICENSE
#
#Redistribution and use in source and binary forms, with or without
#modification, are permitted provided that the following conditions
#are met:
#
#1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
#THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
#IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
#OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
#IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
#INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
#NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
#DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
#THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
#THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import with_statement
from pyflann.exceptions import FLANNException
import numpy
def is_number(s):
try:
float(s)
return True
except ValueError:
return False
def check(filename):
with open(filename,"r") as f:
line = f.readline().strip()
if line[0]=='#': # first line might be a comment
line = f.readline().strip()
values = line.split()
if len(values)==0:
return False
return all(map(is_number, values))
def save(dataset, filename):
if not isinstance(dataset,numpy.ndarray):
raise FLANNException("Can only save numpy arrays")
numpy.savetxt(filename,dataset, fmt="%g")
def load(filename, rows = -1, cols = -1, dtype = numpy.float32):
dataset = numpy.loadtxt(filename, dtype=dtype)
return dataset
\ No newline at end of file
#Copyright 2008-2009 Marius Muja (mariusm@cs.ubc.ca). All rights reserved.
#Copyright 2008-2009 David G. Lowe (lowe@cs.ubc.ca). All rights reserved.
#
#THE BSD LICENSE
#
#Redistribution and use in source and binary forms, with or without
#modification, are permitted provided that the following conditions
#are met:
#
#1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
#THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
#IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
#OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
#IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
#INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
#NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
#DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
#THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
#THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import with_statement
from pyflann.exceptions import FLANNException
import binary_dataset
import dat_dataset
import npy_dataset
import hdf5_dataset
import os.path
from numpy import float32
dataset_formats = {
'bin' : binary_dataset,
'dat' : dat_dataset,
'npy' : npy_dataset,
'hdf5' : hdf5_dataset
}
def load(filename, rows = -1, cols = -1, dtype = float32, **kwargs):
for format in dataset_formats.values():
if format.check(filename):
return format.load(filename, rows, cols, dtype, **kwargs)
raise FLANNException("Error: Unknown dataset format")
def save(dataset, filename, format = None, **kwargs):
try:
if format is None:
basename,extension = os.path.splitext(filename)
format = extension[1:]
handler = dataset_formats[format]
handler.save(dataset, filename, **kwargs)
except Exception,e:
raise FLANNException(e)
#Copyright 2008-2009 Marius Muja (mariusm@cs.ubc.ca). All rights reserved.
#Copyright 2008-2009 David G. Lowe (lowe@cs.ubc.ca). All rights reserved.
#
#THE BSD LICENSE
#
#Redistribution and use in source and binary forms, with or without
#modification, are permitted provided that the following conditions
#are met:
#
#1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
#THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
#IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
#OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
#IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
#INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
#NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
#DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
#THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
#THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import with_statement
from pyflann.exceptions import FLANNException
import numpy
have_h5py = True
try:
import h5py
except Exception,e:
have_h5py = False
if not have_h5py:
def __missing_h5py(*args,**kwargs):
raise FLANNException("h5py library not found")
check = __missing_h5py
save = __missing_h5py
load = __missing_h5py
load_range = __missing_h5py
else:
def check(filename):
f = open(filename,"r")
header = f.read(4)
if header[1:4]=="HDF": return True
return False
def save(dataset, filename, **kwargs):
if not isinstance(dataset,numpy.ndarray):
raise FLANNException("Dataset must be in numpy format")
try:
if 'title' in kwargs:
title_name = kwargs['title']
else:
title_name = "Dataset saved by pyflann"
if 'dataset_name' in kwargs:
dataset_name = kwargs['dataset_name']
else:
dataset_name = 'dataset'
h5file = h5py.File(filename)
h5file.create_dataset(dataset_name, data=dataset)
h5file.close()
except Exception,e:
h5file.close()
raise FLANNException(e)
def load(filename, rows = -1, cols = -1, dtype = numpy.float32, **kwargs):
try:
h5file = h5py.File(filename)
if 'dataset_name' in kwargs:
dataset_name = kwargs['dataset_name']
else:
dataset_name = 'dataset'
for node in h5file.keys():
if node == dataset_name:
data = numpy.array(h5file[node])
h5file.close()
return data
except Exception,e:
h5file.close()
raise FLANNException(e)
def load_range(filename, array_name, range):
h5file = h5py.File(filename)
dataset = h5file[array_name]
return dataset[range[0]:range[1]]
#Copyright 2008-2009 Marius Muja (mariusm@cs.ubc.ca). All rights reserved.
#Copyright 2008-2009 David G. Lowe (lowe@cs.ubc.ca). All rights reserved.
#
#THE BSD LICENSE
#
#Redistribution and use in source and binary forms, with or without
#modification, are permitted provided that the following conditions
#are met:
#
#1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
#THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
#IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
#OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
#IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
#INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
#NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
#DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
#THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
#THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import with_statement
from pyflann.exceptions import FLANNException
import numpy
def check(filename):
f = open(filename,"r")
header = f.read(6)
if header[1:6]=="NUMPY": return True
return False
def save(dataset, filename):
if not isinstance(dataset,numpy.ndarray):
raise FLANNException("Dataset must be in numpy format")
try:
numpy.save(filename, dataset)
except:
raise FLANNException("Format not supported. You need at least numpy version 1.1")
def load(filename, rows = -1, cols = -1, dtype = numpy.float32):
try:
tmp = numpy.save
except:
raise FLANNException("Format not supported. You need at least numpy version 1.1")
data = numpy.load(filename)
return data
\ No newline at end of file
#Copyright 2008-2009 Marius Muja (mariusm@cs.ubc.ca). All rights reserved.
#Copyright 2008-2009 David G. Lowe (lowe@cs.ubc.ca). All rights reserved.
#
#THE BSD LICENSE
#
#Redistribution and use in source and binary forms, with or without
#modification, are permitted provided that the following conditions
#are met:
#
#1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
#THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
#IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
#OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
#IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
#INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
#NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
#DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
#THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
#THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#Copyright 2008-2009 Marius Muja (mariusm@cs.ubc.ca). All rights reserved.
#Copyright 2008-2009 David G. Lowe (lowe@cs.ubc.ca). All rights reserved.
#
#THE BSD LICENSE
#
#Redistribution and use in source and binary forms, with or without
#modification, are permitted provided that the following conditions
#are met:
#
#1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
#THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
#IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
#OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
#IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
#INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
#NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
#DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
#THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
#THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import sys
try:
from scipy.weave import ext_tools
except ImportError:
sys.stderr.write('Weave is required by the %s module.\n'%__name__)
sys.stderr.write('It is included as a standalone package or as part of scipy.\n')
sys.stderr.write('See www.scipy.org/Weave for more information.')
sys.exit(1)
from string import split, join
import os
import numpy
import struct
# type values
float64_1d = numpy.empty((1),dtype=numpy.float64)
float64_2d = numpy.empty((1,1),dtype=numpy.float64)
float64_3d = numpy.empty((1,1,1),dtype=numpy.float64)
float32_1d = numpy.empty((1),dtype=numpy.float32)
float32_2d = numpy.empty((1,1),dtype=numpy.float32)
float32_3d = numpy.empty((1,1),dtype=numpy.float32)
int32_1d = numpy.empty((1),dtype=numpy.int32)
int32_2d = numpy.empty((1,1),dtype=numpy.int32)
int32_3d = numpy.empty((1,1,1),dtype=numpy.int32)
struct_support_code = r"""
template <typename S>
S* py_to_struct(PyObject* obj)
{
S* ptr;
int length;
PyString_AsStringAndSize(obj,(char**)&ptr,&length);
return ptr;
}
"""
class CModule:
def __init__(self, suppress_warnings = True, force_name = None):
if type(force_name) != type(""):
call_frame = sys._getframe().f_back
name = call_frame.f_globals['__name__']
else:
name = force_name
self.module = sys.modules[name]
self.dest_dir = os.path.dirname(self.module.__file__)
self._module_name = split(name,".")[-1]+"_c"
# check to see if rebuild needed
self.extension = ext_tools.ext_module(self._module_name)
self.customize = self.extension.customize
self.customize.add_include_dir(self.dest_dir)
self.customize.add_support_code(struct_support_code)
if suppress_warnings:
self.customize.add_extra_compile_arg('-Wno-unused-variable')
self.customize.add_extra_compile_arg('-Wno-write-strings')
#self.customize.add_extra_compile_arg('-Wno-deprecated')
#self.customize.add_extra_compile_arg('-Wno-unused')
def get_name():
return self._module_name
def include(self,header):
self.customize.add_header(header)
def add_support_code(self,code):
self.customize.add_support_code(code)
def extra_args(self,*varargs):
for t in varargs:
assert(type(t) == tuple)
assert(len(t) == 2)
assert(type(t[0]) == str)
def decorate(func):
name = func.__name__
code = func()
if type(code) != type(""):
code = func.__doc__
import inspect
(args,_,_,defaults) = inspect.getargspec(func)
(file,line) = inspect.getframeinfo(inspect.currentframe().f_back)[0:2]
code = ('#line %d "%s"\n'%(line,file))+code
defaults = [] if defaults==None else defaults
if len(args) != len(defaults):
raise Exception("The %s function must have default values for all arguments"%name)
arg_tuples = zip(args,defaults) + list(varargs)
self.add_function(name,code,*arg_tuples)
return func
return decorate
def __call__(self,func):
name = func.__name__
code = func.__doc__
if code == None:
code = func()
import inspect
(args,_,_,defaults) = inspect.getargspec(func)
(file,line) = inspect.getframeinfo(inspect.currentframe().f_back)[0:2]
code = ('#line %d "%s"\n'%(line,file))+code
defaults = [] if defaults==None else defaults
if len(args) != len(defaults):
raise Exception("The %s function must have default values for all arguments"%name)
vardict = dict(zip(args,defaults))
self.extension.add_function(ext_tools.ext_function(name, code, args, local_dict = vardict))
return func
def add_function(self,name, code, *varlist):
for t in varlist:
assert(type(t) == tuple)
assert(len(t) == 2)
assert(type(t[0]) == str)
args = [n for n, v in varlist]
vardict = dict(varlist)
self.extension.add_function(ext_tools.ext_function(name, code, args, local_dict = vardict))
def _import(self,**kw):
self.extension.compile(location=self.dest_dir,**kw)
return "from %s import *"%self._module_name
class CStruct:
def __init__(self, members):
self.__members = members
format = join([ s for (s,_,_) in members],'')
self.__struct_dict = dict( (v for (_,v,_) in members) )
self.__translation_dict = dict( ( (k[0],v) for (_,k,v) in members if v != None))
print self.__translation_dict
self.__struct = struct.Struct(format)
def pack(self, **kwargs):
pass
\ No newline at end of file
......@@ -22,7 +22,7 @@ setup(name='flann',
author_email='mariusm@cs.ubc.ca',
license='BSD',
url='http://www.cs.ubc.ca/~mariusm/flann/',
packages=['pyflann', 'pyflann.io', 'pyflann.bindings', 'pyflann.util', 'pyflann.lib'],
packages=['pyflann', 'pyflann.lib'],
package_dir={'pyflann.lib': find_path() },
package_data={'pyflann.lib': ['libflann.so', 'flann.dll', 'libflann.dll', 'libflann.dylib']},
)
#!/usr/bin/env python2
#!/usr/bin/env python
import sys
from os.path import *
from pyflann import *
......
#!/usr/bin/env python2
#!/usr/bin/env python
import sys
from os.path import *
from pyflann import *
......
#!/usr/bin/env python2
#!/usr/bin/env python
import sys
from os.path import *
from pyflann import *
......@@ -19,7 +19,7 @@ class Test_PyFLANN_clustering(unittest.TestCase):
x = rand(100, 10000)
nK = 10
centroids = self.nn.kmeans(x, nK)
self.assert_(len(centroids) == nK)
self.assertTrue(len(centroids) == nK)
def test2d_small(self):
......@@ -47,7 +47,7 @@ class Test_PyFLANN_clustering(unittest.TestCase):
"""
seed(0)
x = rand(N, dim)
xc = concatenate(tuple([x for i in xrange(dup)]))
xc = concatenate(tuple([x for i in range(dup)]))
if dup > 1: xc += randn(xc.shape[0], xc.shape[1])*0.000001/dim
......@@ -76,7 +76,7 @@ class Test_PyFLANN_clustering(unittest.TestCase):
cl1 = self.nn.kmeans(data, 50, random_seed = rnseed)
cl2 = self.nn.kmeans(data, 50, random_seed = rnseed)
self.assert_(all(cl1 == cl2))
self.assertTrue(all(cl1 == cl2))
def testrandnumber_different(self):
......@@ -87,7 +87,7 @@ class Test_PyFLANN_clustering(unittest.TestCase):
cl1 = self.nn.kmeans(data, 50, random_seed = rnseed)
cl2 = self.nn.kmeans(data, 50)
self.assert_(any(cl1 != cl2))
self.assertTrue(any(cl1 != cl2))
......
#!/usr/bin/env python2
#!/usr/bin/env python
from pyflann import *
from copy import copy
......@@ -78,7 +78,7 @@ class Test_PyFLANN_nn_index(unittest.TestCase):
correct = all(nnidx == arange(N, dtype = index_type))
nn.delete_index()
self.assert_(correct)
self.assertTrue(correct)
def run_nn_index_save_rand(self, dim, N, Nq, **kwargs):
......@@ -100,7 +100,7 @@ class Test_PyFLANN_nn_index(unittest.TestCase):
del nn
correct = all(nnidx == nnidx2)
self.assert_(correct)
self.assertTrue(correct)
if __name__ == '__main__':
unittest.main()
#!/usr/bin/env python2
#!/usr/bin/env python
import sys
from os.path import *
import os
......@@ -90,28 +90,28 @@ class Test_PyFLANN_nn(unittest.TestCase):
perm = permutation(N)
idx,dists = self.nn.nn(x, x[perm], **kwargs)
self.assert_(all(idx == perm))
self.assertTrue(all(idx == perm))
# Make sure it's okay if we do make all the points equal
x_mult_nn = concatenate([x for i in xrange(num_neighbors)])
x_mult_nn = concatenate([x for i in range(num_neighbors)])
nidx,ndists = self.nn.nn(x_mult_nn, x, num_neighbors = num_neighbors, **kwargs)
correctness = 0.0
for i in xrange(N):
correctness += float(len(set(nidx[i]).intersection([i + n*N for n in xrange(num_neighbors)])))/num_neighbors
for i in range(N):
correctness += float(len(set(nidx[i]).intersection([i + n*N for n in range(num_neighbors)])))/num_neighbors
self.assert_(correctness / N >= 0.99,
self.assertTrue(correctness / N >= 0.99,
'failed #1: N=%d,correctness=%f' % (N, correctness/N))
# now what happens if they are slightly off
x_mult_nn += randn(x_mult_nn.shape[0], x_mult_nn.shape[1])*0.0001/dim
n2idx,n2dists = self.nn.nn(x_mult_nn, x, num_neighbors = num_neighbors, **kwargs)
for i in xrange(N):
correctness += float(len(set(n2idx[i]).intersection([i + n*N for n in xrange(num_neighbors)])))/num_neighbors
for i in range(N):
correctness += float(len(set(n2idx[i]).intersection([i + n*N for n in range(num_neighbors)])))/num_neighbors
self.assert_(correctness / N >= 0.99,
self.assertTrue(correctness / N >= 0.99,
'failed #2: N=%d,correctness=%f' % (N, correctness/N))
if __name__ == '__main__':
......
#!/usr/bin/env python2
#!/usr/bin/env python
import sys
from os.path import *
import os
......@@ -65,12 +65,12 @@ class Test_PyFLANN_nn(unittest.TestCase):
target_precision = tp, checks=-2, **kwargs)
correctness = 0.0
for i in xrange(N):
for i in range(N):
l1 = self.__ensure_list(nidx[i])
l2 = self.__ensure_list(gt_idx[i])
correctness += float(len(set(l1).intersection(l2)))/num_neighbors
correctness /= N
self.assert_(correctness >= tp*0.9,
self.assertTrue(correctness >= tp*0.9,
'failed #1: targ_prec=%f, N=%d,correctness=%f' % (tp, N, correctness))
if __name__ == '__main__':
......
#!/usr/bin/env python2
#!/usr/bin/env python
from pyflann import *
from copy import copy
......@@ -28,7 +28,7 @@ class Test_PyFLANN_nn_index(unittest.TestCase):
correct = all(nnidx == arange(N, dtype = index_type))
nn.delete_index()
self.assert_(correct)
self.assertTrue(correct)
def testnn_index_random_permute(self):
......@@ -38,7 +38,7 @@ class Test_PyFLANN_nn_index(unittest.TestCase):
N = 100
nns = [None]*numtests
x = [rand(N, dim) for i in xrange(numtests)]
x = [rand(N, dim) for i in range(numtests)]
correct = ones(numtests, dtype=bool_)
for i in permutation(numtests):
......@@ -56,13 +56,13 @@ class Test_PyFLANN_nn_index(unittest.TestCase):
nnidx,nndist = nns[i].nn_index(x[i])
correct[i] = all(nnidx == arange(N, dtype = index_type))
for i in reversed(xrange(numtests)):
for i in reversed(range(numtests)):
if rand() < 0.5:
nns[i].delete_index()
else:
del nns[i]
self.assert_(all(correct))
self.assertTrue(all(correct))
def testnn_index_bad_index_call_noindex(self):
nn = FLANN()
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册