Skip to content

Commit 7694b11

Browse files
committed
Lots of bug fixes and improvements. Had to disable for int32 temporarily
while I sort that out. This also adds the max_k option to radius_search as discussed in flann-lib#170.
1 parent 466c8e9 commit 7694b11

File tree

6 files changed

+131
-80
lines changed

6 files changed

+131
-80
lines changed

src/ruby/flann.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ Gem::Specification.new do |gem|
5353
gem.add_development_dependency 'rake'
5454
gem.add_development_dependency 'bundler'
5555
gem.add_development_dependency 'rspec'
56+
gem.add_development_dependency 'rspec-longrun'
5657
gem.add_development_dependency 'pry'
5758
end
5859

src/ruby/lib/flann.rb

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,17 @@ module Flann
3737
ffi_lib "libflann"
3838

3939
# Declare enumerators
40-
Algorithm = enum(:linear, :kdtree, :kmeans, :composite, :kdtree_single, :saved, :autotuned)
41-
CentersInit = enum(:random, :gonzales, :kmeanspp)
42-
LogLevel = enum(:none, :fatal, :error, :warn, :info)
43-
DistanceType = enum(:euclidean, :manhattan, :minkowski, :hist_intersect, :hellinger, :chi_square, :kullback_leibler)
40+
Algorithm = enum(:algorithm, [:linear, :kdtree, :kmeans, :composite, :kdtree_single, :hierarchical, :lsh, :kdtree_cuda, :saved, 254, :autotuned, 255])
41+
CentersInit = enum(:centers_init, [:random, :gonzales, :kmeanspp])
42+
LogLevel = enum(:log_level, [:none, :fatal, :error, :warn, :info, :debug])
43+
44+
# Note that Hamming and beyond are not supported in the C API. We include them here just in case of future improvements.
45+
DistanceType = enum(:distance_type, [:undefined, :euclidean, :l2, :manhattan, :l1, :minkowski, :max, :hist_intersect, :hellinger, :chi_square, :kullback_leibler, :hamming, :hamming_lut, :hamming_popcnt, :l2_simple])
4446

4547
# For NMatrix compatibility
46-
typedef :float, :float32
47-
typedef :double, :float64
48+
typedef :float, :float32
49+
typedef :double, :float64
50+
typedef :char, :byte
4851
typedef :pointer, :index_params_ptr
4952
typedef :pointer, :index_ptr
5053

@@ -123,8 +126,10 @@ def dtype_to_c d #:nodoc:
123126
# Allocates index space and distance space for storing results from various searches. For a k-nearest neighbors
124127
# search, for example, you want trows (the number of rows in the testset) times k (the number of nearest neighbors
125128
# being searched for).
129+
#
130+
# Note that c_type will produce float for everything except double, which produces double.
126131
def allocate_results_space result_size, c_type #:nodoc:
127-
[FFI::MemoryPointer.new(:int, result_size), FFI::MemoryPointer.new(c_type, result_size)]
132+
[FFI::MemoryPointer.new(:int, result_size), FFI::MemoryPointer.new(c_type == :double ? :double : :float, result_size)]
128133
end
129134

130135

@@ -175,15 +180,31 @@ def nearest_neighbors dataset, testset, k, parameters = {}
175180
indices_int_ptr, distances_t_ptr, k, parameters_ptr
176181

177182
# Return results: two arrays, one of indices and one of distances.
178-
[indices_int_ptr.read_array_of_int(result_size), distances_t_ptr.read_array_of_float(result_size)]
183+
[indices_int_ptr.read_array_of_int(result_size),
184+
c_type == :double ? distances_t_ptr.read_array_of_double(result_size) : distances_t_ptr.read_array_of_float(result_size)]
179185
end
180186
alias :nn :nearest_neighbors
181187

182188
# Set the distance function to use when computing distances between data points.
183-
def set_distance_type! distance_function, order = 0
184-
Flann.send(:flann_set_distance_type, distance_function, order)
189+
def set_distance_type! distance_function
190+
Flann.send(:flann_set_distance_type, distance_function, get_distance_order)
185191
self
186192
end
193+
alias :set_distance_type_and_order! :set_distance_type!
194+
195+
# Get the distance type and order
196+
def get_distance_type_and_order
197+
[Flann.flann_get_distance_type, Flann.flann_get_distance_order]
198+
end
199+
def get_distance_type
200+
Flann.flann_get_distance_type
201+
end
202+
def get_distance_order
203+
Flann.flann_get_distance_order
204+
end
205+
alias :distance_type :get_distance_type
206+
alias :distance_order :get_distance_order
207+
187208

188209
# Perform hierarchical clustering of a set of points.
189210
#
@@ -249,7 +270,9 @@ def cluster dataset, clusters, parameters = {}
249270
attach_function :flann_free_index_float, [:index_ptr, :index_params_ptr], :int
250271
attach_function :flann_free_index_double, [:index_ptr, :index_params_ptr], :int
251272

252-
attach_function :flann_set_distance_type, [DistanceType, :int], :void
273+
attach_function :flann_set_distance_type, [:distance_type, :int], :void
274+
attach_function :flann_get_distance_type, [], :distance_type
275+
attach_function :flann_get_distance_order, [], :int
253276

254277
attach_function :flann_compute_cluster_centers_byte, [:pointer, :int, :int, :int, :pointer, :index_params_ptr], :int
255278
attach_function :flann_compute_cluster_centers_int, [:pointer, :int, :int, :int, :pointer, :index_params_ptr], :int

src/ruby/lib/flann/index.rb

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ class FFI::Pointer
2929
class << self
3030
def new_from_nmatrix nm
3131
raise(StorageError, "dense storage expected") unless nm.dense?
32-
::FFI::Pointer.new(nm.data_pointer).tap { |p| p.autorelease = false }
32+
c_type = Flann::dtype_to_c(nm.dtype)
33+
c_type = :uchar if c_type == :byte
34+
::FFI::Pointer.new(c_type, nm.data_pointer).tap { |p| p.autorelease = false }
3335
end
3436
end
3537
end
@@ -43,9 +45,11 @@ class Index
4345
#
4446
# * https://github.com/mariusmuja/flann/tree/master/src/cpp/flann/algorithms
4547
#
46-
def initialize dataset = nil, dtype: :float64, parameters: Flann::Parameters::DEFAULT
47-
@dataset = dataset
48-
@dtype = (!dataset.nil? && dataset.is_a?(NMatrix)) ? dataset.dtype : dtype
48+
def initialize index_dataset = nil, dtype: :float64, parameters: Flann::Parameters::DEFAULT
49+
@dataset = index_dataset
50+
#require 'pry'
51+
#binding.pry if @dataset.nil?
52+
@dtype = (!index_dataset.nil? && index_dataset.is_a?(NMatrix)) ? index_dataset.dtype : dtype
4953
@index_ptr = nil
5054

5155
@parameters_ptr, @parameters = Flann::handle_parameters(parameters)
@@ -55,17 +59,24 @@ def initialize dataset = nil, dtype: :float64, parameters: Flann::Parameters::DE
5559
attr_reader :dtype, :dataset, :parameters, :parameters_ptr, :index_ptr
5660

5761
# Assign a new dataset. Requires that the old index be freed.
58-
def dataset= new_dataset
62+
def dataset= index_dataset
5963
free!
64+
@dataset = index_dataset
6065
end
6166

6267
# Build an index
6368
def build!
6469
raise("no dataset specified") if dataset.nil?
65-
66-
c_method = "flann_build_index_#{Flann::dtype_to_c(dtype)}".to_sym
70+
c_type = Flann::dtype_to_c(dtype)
71+
c_method = "flann_build_index_#{c_type}".to_sym
6772
speedup_float_ptr = FFI::MemoryPointer.new(:float)
6873
@index_ptr = Flann.send(c_method, FFI::Pointer.new_from_nmatrix(dataset), dataset.shape[0], dataset.shape[1], speedup_float_ptr, parameters_ptr)
74+
if index_ptr.address == 0
75+
require 'pry'
76+
binding.pry
77+
raise("failed to allocate index_ptr")
78+
end
79+
6980

7081
# Return the speedup
7182
speedup_float_ptr.read_float
@@ -91,12 +102,14 @@ def nearest_neighbors testset, k, parameters = {}
91102
k,
92103
parameters_ptr
93104

94-
[indices_int_ptr.read_array_of_int(result_size), distances_t_ptr.read_array_of_float(result_size)]
105+
106+
[indices_int_ptr.read_array_of_int(result_size),
107+
c_type == :double ? distances_t_ptr.read_array_of_double(result_size) : distances_t_ptr.read_array_of_float(result_size)]
95108
end
96109

97110
# Perform a radius search on a single query point
98-
def radius_search query, radius, parameters = {}
99-
max_k = parameters[:max_neighbors] || dataset.shape[1]
111+
def radius_search query, radius, max_k=nil, parameters = {}
112+
max_k ||= dataset.shape[1]
100113
parameters = Parameters.new(Flann::Parameters::DEFAULT.merge(parameters))
101114

102115
self.build! if index_ptr.nil?
@@ -109,7 +122,8 @@ def radius_search query, radius, parameters = {}
109122
Flann.send(c_method, index_ptr, FFI::Pointer.new_from_nmatrix(query), indices_int_ptr, distances_t_ptr, max_k, radius, parameters_ptr)
110123

111124
# Return results: two arrays, one of indices and one of distances.
112-
[indices_int_ptr.read_array_of_int(max_k), distances_t_ptr.read_array_of_float(max_k)]
125+
[indices_int_ptr.read_array_of_int(max_k),
126+
c_type == :double ? distances_t_ptr.read_array_of_double(max_k) : distances_t_ptr.read_array_of_float(max_k)]
113127
end
114128

115129
# Save an index to a file (without the dataset).

src/ruby/spec/flann_spec.rb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,18 @@
6868

6969
context "#set_distance_type!" do
7070
it "sets the distance functor without error" do
71-
pending "distance type unsupported in the C bindings, use the C++ bindings instead"
71+
#pending "distance type unsupported in the C bindings, use the C++ bindings instead"
7272
Flann.set_distance_type! :euclidean
73+
d = Flann.get_distance_type
74+
expect(d).to eq(:euclidean)
7375
end
7476
end
7577

7678
[:byte, :int32, :float32, :float64].each do |dtype|
7779
before :each do
78-
@dataset = NMatrix.random([1000,128], dtype: dtype)
79-
@testset = NMatrix.random([100,128], dtype: dtype)
80+
scale = [:byte, :int32, :int64].include?(dtype) ? 255 : 1.0
81+
@dataset = NMatrix.random([1000,128], dtype: dtype, scale: scale)
82+
@testset = NMatrix.random([100,128], dtype: dtype, scale: scale)
8083
end
8184

8285
context "#nearest_neighbors" do

src/ruby/spec/index_spec.rb

Lines changed: 64 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -29,61 +29,70 @@
2929

3030

3131
describe Flann::Index do
32-
33-
before :each do
34-
@dataset = NMatrix.random([1000,128])
35-
@testset = NMatrix.random([100,128])
36-
@index = Flann::Index.new(@dataset) do |t|
37-
t[:algorithm] = :kdtree
38-
t[:trees] = 4
39-
end
40-
@index.build!
41-
end
42-
43-
44-
context "#build!" do
45-
it "builds a kdtree index with block parameters" do
46-
# Empty: handled in :each, above
47-
end
48-
end
49-
50-
51-
context "#nearest_neighbors" do
52-
it "runs without error" do
53-
@index.nearest_neighbors @testset, 5
32+
[:byte, :float32, :float64].each do |dtype|
33+
context dtype.inspect do
34+
before :each do
35+
scale = [:byte, :int32, :int64].include?(dtype) ? 255 : 1.0
36+
37+
@dataset = NMatrix.random([1000,128], dtype: dtype, scale: scale)
38+
@testset = NMatrix.random([100,128], dtype: dtype, scale: scale)
39+
@index = Flann::Index.new(@dataset) do |t|
40+
t[:algorithm] = :kdtree
41+
t[:trees] = 4
42+
end
43+
@index.build!
44+
end
45+
46+
47+
context "#build!" do
48+
it "builds a kdtree index with block parameters" do
49+
scale = [:byte, :int].include?(dtype) ? 255 : 1.0
50+
51+
@dataset = NMatrix.random([1000,128], dtype: dtype, scale: scale)
52+
@testset = NMatrix.random([100,128], dtype: dtype, scale: scale)
53+
@index = Flann::Index.new(@dataset) do |t|
54+
t[:algorithm] = :kdtree
55+
t[:trees] = 4
56+
end
57+
@index.build!
58+
end
59+
end
60+
61+
62+
context "#nearest_neighbors" do
63+
it "runs without error" do
64+
@index.nearest_neighbors @testset, 5
65+
end
66+
end
67+
68+
69+
context "#radius_search" do
70+
it "runs without error" do
71+
query = NMatrix.random([1,128])
72+
@index.radius_search query, 0.4
73+
end
74+
end
75+
76+
77+
context "#save" do
78+
it "saves an index to a file which can be loaded again" do
79+
FileUtils.rm("temp_index.save_file", :force => true)
80+
@index.save("temp_index.save_file")
81+
82+
raise(IOError, "save failed") unless File.exists?("temp_index.save_file")
83+
84+
post_index = Flann::Index.new(@dataset)
85+
post_index.load!("temp_index.save_file")
86+
FileUtils.rm("temp_index.save_file", :force => true)
87+
end
88+
end
89+
90+
91+
context "#free!" do
92+
it "frees an index" do
93+
@index.free!
94+
end
95+
end
5496
end
5597
end
56-
57-
58-
context "#radius_search" do
59-
it "runs without error" do
60-
query = NMatrix.random([1,128])
61-
@index.radius_search query, 0.4
62-
end
63-
end
64-
65-
66-
context "#save" do
67-
it "saves an index to a file which can be loaded again" do
68-
FileUtils.rm("temp_index.save_file", :force => true)
69-
@index.save("temp_index.save_file")
70-
71-
raise(IOError, "save failed") unless File.exists?("temp_index.save_file")
72-
73-
post_index = Flann::Index.new(@dataset)
74-
post_index.load!("temp_index.save_file")
75-
FileUtils.rm("temp_index.save_file", :force => true)
76-
end
77-
end
78-
79-
80-
context "#free!" do
81-
it "frees an index" do
82-
@index.free!
83-
end
84-
end
85-
86-
87-
88-
8998
end

src/ruby/spec/spec_helper.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@
2626
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2727

2828
require 'rspec'
29+
require 'rspec/longrun'
2930

3031
require "./lib/flann"

0 commit comments

Comments
 (0)