Skip to content

Commit 46f238e

Browse files
committed
Added improved specs. No segfaults, but haven't tried Valgrind yet.
set_distance_type! is marked as pending.
1 parent 53491ba commit 46f238e

File tree

5 files changed

+148
-113
lines changed

5 files changed

+148
-113
lines changed

src/ruby/lib/flann.rb

Lines changed: 28 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -52,23 +52,24 @@ class Parameters < FFI::Struct
5252
class << self
5353

5454

55-
def dtype_to_c d
56-
return :float if d == :float32
57-
return :double if d == :float64
58-
return d
55+
DTYPE_TO_C = {:float32 => :float, :float64 => :double, :int32 => :int, :byte => :byte, :int8 => :byte}
56+
57+
def dtype_to_c d #:nodoc:
58+
return DTYPE_TO_C[d] if DTYPE_TO_C.has_key?(d)
59+
raise(NMatrix::DataTypeError, "FLANN does not support this dtype")
5960
end
6061

6162

6263
# Allocates index space and distance space for storing results from various searches. For a k-nearest neighbors
6364
# search, for example, you want trows (the number of rows in the testset) times k (the number of nearest neighbors
6465
# being searched for).
65-
def allocate_results_space result_size
66+
def allocate_results_space result_size #:nodoc:
6667
[FFI::MemoryPointer.new(:int, result_size), FFI::MemoryPointer.new(:float, result_size)]
6768
end
6869

6970

7071
# Don't know if these will be a hash, a static struct, or a pointer to a struct. Return the pointer and the struct.
71-
def handle_parameters parameters
72+
def handle_parameters parameters #:nodoc:
7273
parameters ||= DEFAULT_PARAMETERS unless block_given?
7374

7475
if parameters.is_a?(FFI::MemoryPointer) # User supplies us with the necessary parameters already in the correct form.
@@ -94,38 +95,11 @@ def handle_parameters parameters
9495
[c_parameters_ptr, c_parameters]
9596
end
9697

97-
def handle_index index
98-
return index.index_ptr if index.is_a?(Flann::Index)
99-
index
100-
end
101-
102-
103-
# Find the k nearest neighbors with an index already built.
104-
#
105-
def nearest_neighbors_by_index index, testset, k, parameters: DEFAULT_PARAMETERS
106-
parameters_ptr, parameters = handle_parameters(parameters)
107-
result_size = testset.shape[0] * k
108-
indices_int_ptr, distances_float_ptr = allocate_results_space(result_size)
109-
index_ptr = handle_index(index)
110-
111-
raise(ArgumentError, "did you forget to call #build! on your index?") if index.index_ptr.nil?
112-
113-
Flann.flann_find_nearest_neighbors_index index_ptr,
114-
FFI::Pointer.new_from_nmatrix(testset),
115-
testset.shape[0],
116-
indices_int_ptr, distances_float_ptr,
117-
k,
118-
parameters_ptr
119-
120-
[indices_int_ptr.read_array_of_int(result_size), distances_float_ptr.read_array_of_float(result_size)]
121-
end
122-
12398

12499
# Find the k nearest neighbors.
125100
#
126101
# If no index parameters are given, FLANN_DEFAULT_PARAMETERS are used. A block is accepted as well.
127102
def nearest_neighbors dataset, testset, k, parameters: DEFAULT_PARAMETERS
128-
129103
# Get a pointer and a struct regardless of how the arguments are supplied.
130104
parameters_ptr, parameters = handle_parameters(parameters)
131105
result_size = testset.shape[0] * k
@@ -147,20 +121,23 @@ def set_distance_type! distance_function, order = 0
147121
end
148122

149123
# Perform hierarchical clustering of a set of points.
150-
def compute_cluster_centers dataset, clusters, parameters: DEFAULT_PARAMETERS
124+
def cluster dataset, clusters, parameters: DEFAULT_PARAMETERS
151125
c_method = "flann_compute_cluster_centers_#{Flann::dtype_to_c(dataset.dtype)}".to_sym
152126

153127
result = dataset.clone_structure
154128
parameters_ptr, parameters = handle_parameters(parameters)
155129
Flann.send(c_method, FFI::Pointer.new_from_nmatrix(dataset), dataset.shape[0], dataset.shape[1], clusters, FFI::Pointer.new_from_nmatrix(result), parameters_ptr)
130+
131+
result
156132
end
133+
alias :compute_cluster_centers :cluster
157134
end
158135

159136

160137
protected
161138

162-
# byte: unsigned char*dataset, int rows, int cols, float* speedup, FLANNParameters* flann_params
163-
# only thing that changes is the pointer type for the first arg.
139+
# byte: unsigned char*dataset, int rows, int cols, float* speedup, FLANNParameters* flann_params
140+
# only thing that changes is the pointer type for the first arg.
164141
attach_function :flann_build_index_byte, [:pointer, :int, :int, :pointer, :index_params_ptr], :index_ptr
165142
attach_function :flann_build_index_int, [:pointer, :int, :int, :pointer, :index_params_ptr], :index_ptr
166143
attach_function :flann_build_index_float, [:pointer, :int, :int, :pointer, :index_params_ptr], :index_ptr
@@ -178,26 +155,26 @@ def compute_cluster_centers dataset, clusters, parameters: DEFAULT_PARAMETERS
178155
attach_function :flann_radius_search_float, [:index_ptr, :pointer, :pointer, :pointer, :int, :float, :index_params_ptr], :int
179156
attach_function :flann_radius_search_double, [:index_ptr, :pointer, :pointer, :pointer, :int, :float, :index_params_ptr], :int
180157

181-
attach_function :flann_save_index_byte, [:index_ptr, :pointer], :int
182-
attach_function :flann_save_index_int, [:index_ptr, :pointer], :int
183-
attach_function :flann_save_index_float, [:index_ptr, :pointer], :int
184-
attach_function :flann_save_index_double, [:index_ptr, :pointer], :int
158+
attach_function :flann_save_index_byte, [:index_ptr, :string], :int
159+
attach_function :flann_save_index_int, [:index_ptr, :string], :int
160+
attach_function :flann_save_index_float, [:index_ptr, :string], :int
161+
attach_function :flann_save_index_double, [:index_ptr, :string], :int
185162

186-
attach_function :flann_load_index_byte, [:pointer, :pointer, :int, :int], :index_ptr
187-
attach_function :flann_load_index_int, [:pointer, :pointer, :int, :int], :index_ptr
188-
attach_function :flann_load_index_float, [:pointer, :pointer, :int, :int], :index_ptr
189-
attach_function :flann_load_index_double, [:pointer, :pointer, :int, :int], :index_ptr
163+
attach_function :flann_load_index_byte, [:string, :pointer, :int, :int], :index_ptr
164+
attach_function :flann_load_index_int, [:string, :pointer, :int, :int], :index_ptr
165+
attach_function :flann_load_index_float, [:string, :pointer, :int, :int], :index_ptr
166+
attach_function :flann_load_index_double, [:string, :pointer, :int, :int], :index_ptr
190167

191-
attach_function :flann_free_index_byte, [:index_ptr, :index_params_ptr], :int
192-
attach_function :flann_free_index_int, [:index_ptr, :index_params_ptr], :int
193-
attach_function :flann_free_index_float, [:index_ptr, :index_params_ptr], :int
168+
attach_function :flann_free_index_byte, [:index_ptr, :index_params_ptr], :int
169+
attach_function :flann_free_index_int, [:index_ptr, :index_params_ptr], :int
170+
attach_function :flann_free_index_float, [:index_ptr, :index_params_ptr], :int
194171
attach_function :flann_free_index_double, [:index_ptr, :index_params_ptr], :int
195172

196173
attach_function :flann_set_distance_type, [DistanceType, :int], :void
197174

198-
attach_function :flann_compute_cluster_centers_byte, [:pointer, :int, :int, :int, :pointer, :index_params_ptr], :int
199-
attach_function :flann_compute_cluster_centers_int, [:pointer, :int, :int, :int, :pointer, :index_params_ptr], :int
200-
attach_function :flann_compute_cluster_centers_float, [:pointer, :int, :int, :int, :pointer, :index_params_ptr], :int
201-
attach_function :flann_compute_cluster_centers_double, [:pointer, :int, :int, :int, :pointer, :index_params_ptr], :int
175+
attach_function :flann_compute_cluster_centers_byte, [:pointer, :int, :int, :int, :pointer, :index_params_ptr], :int
176+
attach_function :flann_compute_cluster_centers_int, [:pointer, :int, :int, :int, :pointer, :index_params_ptr], :int
177+
attach_function :flann_compute_cluster_centers_float, [:pointer, :int, :int, :int, :pointer, :index_params_ptr], :int
178+
attach_function :flann_compute_cluster_centers_double, [:pointer, :int, :int, :int, :pointer, :index_params_ptr], :int
202179

203180
end

src/ruby/lib/flann/index.rb

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
class FFI::Pointer
22
class << self
33
def new_from_nmatrix nm
4+
raise(StorageError, "dense storage expected") unless nm.dense?
45
::FFI::Pointer.new(nm.data_pointer).tap { |p| p.autorelease = false }
56
end
67
end
@@ -28,11 +29,11 @@ def initialize dataset: nil, dtype: :float64, parameters: Flann::DEFAULT_PARAMET
2829

2930
# Build an index
3031
def build!
31-
raise("no dataset specified") if @dataset.nil?
32+
raise("no dataset specified") if dataset.nil?
3233

3334
c_method = "flann_build_index_#{Flann::dtype_to_c(dtype)}".to_sym
3435
speedup_float_ptr = FFI::MemoryPointer.new(:float)
35-
@index_ptr = Flann.send(c_method, FFI::Pointer.new_from_nmatrix(@dataset), @dataset.shape[0], @dataset.shape[1], speedup_float_ptr, parameters_ptr)
36+
@index_ptr = Flann.send(c_method, FFI::Pointer.new_from_nmatrix(dataset), dataset.shape[0], dataset.shape[1], speedup_float_ptr, parameters_ptr)
3637

3738
# Return the speedup
3839
speedup_float_ptr.read_float
@@ -41,47 +42,57 @@ def build!
4142
# Get the nearest neighbors based on this index. Forces a build of the index if one hasn't been done yet.
4243
def nearest_neighbors testset, k, parameters: DEFAULT_PARAMETERS
4344
self.build! if index_ptr.nil?
44-
Flann::nearest_neighbors_by_index index_ptr, testset, k, parameters: parameters
45+
46+
parameters_ptr, parameters = Flann::handle_parameters(parameters)
47+
result_size = testset.shape[0] * k
48+
indices_int_ptr, distances_float_ptr = Flann::allocate_results_space(result_size)
49+
50+
Flann.flann_find_nearest_neighbors_index index_ptr,
51+
FFI::Pointer.new_from_nmatrix(testset),
52+
testset.shape[0],
53+
indices_int_ptr, distances_float_ptr,
54+
k,
55+
parameters_ptr
56+
57+
[indices_int_ptr.read_array_of_int(result_size), distances_float_ptr.read_array_of_float(result_size)]
4558
end
4659

4760
# Perform a radius search on a single query point
4861
def radius_search query, radius, max_k: dataset.shape[1], parameters: DEFAULT_PARAMETERS
4962
self.build! if index_ptr.nil?
50-
parameters_ptr, parameters = handle_parameters(parameters)
51-
indices_int_ptr, distances_float_ptr = allocate_results_space(max_k)
63+
parameters_ptr, parameters = Flann::handle_parameters(parameters)
64+
indices_int_ptr, distances_float_ptr = Flann::allocate_results_space(max_k)
5265

5366
c_method = "flann_radius_search_#{Flann::dtype_to_c(dtype)}".to_sym
54-
Flann.send(c_method, FFI::Pointer.new_from_nmatrix(query), indices_int_ptr, distances_float_ptr, max_k, radius, parameters_ptr)
67+
Flann.send(c_method, index_ptr, FFI::Pointer.new_from_nmatrix(query), indices_int_ptr, distances_float_ptr, max_k, radius, parameters_ptr)
5568

5669
# Return results: two arrays, one of indices and one of distances.
57-
[indices_int_ptr.read_array_of_int(result_size), distances_float_ptr.read_array_of_float(result_size)]
70+
[indices_int_ptr.read_array_of_int(max_k), distances_float_ptr.read_array_of_float(max_k)]
5871
end
5972

6073
# Save an index to a file (without the dataset).
6174
def save filename
62-
raise(IOError, "Cannot write an unbuilt index") if self.index_ptr.nil?
63-
c_filename = FFI::MemoryPointer.from_string(filename)
75+
raise(IOError, "Cannot write an unbuilt index") if index_ptr.nil? # FIXME: This should probably have its own exception type.
6476
c_method = "flann_save_index_#{Flann::dtype_to_c(dtype)}".to_sym
65-
Flann.send(c_method, c_filename)
77+
Flann.send(c_method, index_ptr, filename)
6678
self
6779
end
6880

6981
# Load an index from a file (with the dataset already known!).
7082
#
7183
# FIXME: This needs to free the previous dataset first.
7284
def load! filename
73-
c_filename = FFI::MemoryPointer.from_string(filename)
7485
c_method = "flann_load_index_#{Flann::dtype_to_c(dtype)}".to_sym
7586

76-
@index_ptr = Flann.send(c_method, c_filename, FFI::Pointer.new_from_nmatrix(@dataset), @dataset.shape[0], @dataset.shape[1])
87+
@index_ptr = Flann.send(c_method, filename, FFI::Pointer.new_from_nmatrix(dataset), dataset.shape[0], dataset.shape[1])
7788
self
7889
end
7990

8091
# Free an index
8192
def free! parameters = DEFAULT_PARAMETERS
8293
c_method = "flann_free_index_#{Flann::dtype_to_c(dtype)}".to_sym
83-
parameters_ptr, parameters = handle_parameters(parameters)
84-
Flann.send(c_method, @index_ptr, parameters_ptr)
94+
parameters_ptr, parameters = Flann::handle_parameters(parameters)
95+
Flann.send(c_method, index_ptr, parameters_ptr)
8596
@index_ptr = nil
8697
self
8798
end

src/ruby/spec/basic_spec.rb

Lines changed: 0 additions & 48 deletions
This file was deleted.

src/ruby/spec/flann_spec.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
require File.dirname(__FILE__) + "/spec_helper.rb"
2+
3+
describe Flann do
4+
context "#set_distance_type!" do
5+
it "sets the distance functor without error" do
6+
pending "distance type unsupported in the C bindings, use the C++ bindings instead"
7+
Flann.set_distance_type! :euclidean
8+
end
9+
end
10+
11+
[:byte, :int32, :float32, :float64].each do |dtype|
12+
before :each do
13+
@dataset = NMatrix.random([1000,128], dtype: dtype)
14+
@testset = NMatrix.random([100,128], dtype: dtype)
15+
end
16+
17+
context "#nearest_neighbors" do
18+
it "computes the nearest neighbors without an index" do
19+
Flann.nearest_neighbors @dataset, @testset, 5
20+
end
21+
end
22+
23+
context "#cluster" do
24+
it "calls flann_compute_cluster_centers_... properly" do
25+
Flann.cluster(@dataset, 5)
26+
end
27+
end
28+
29+
30+
end
31+
32+
33+
end

src/ruby/spec/index_spec.rb

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
require File.dirname(__FILE__) + "/spec_helper.rb"
2+
3+
4+
describe Flann::Index do
5+
6+
before :each do
7+
@dataset = NMatrix.random([1000,128])
8+
@testset = NMatrix.random([100,128])
9+
@index = Flann::Index.new(dataset: @dataset) do |t|
10+
t[:algorithm] = :kdtree
11+
t[:trees] = 4
12+
end
13+
@index.build!
14+
end
15+
16+
17+
context "#build!" do
18+
it "builds a kdtree index with block parameters" do
19+
# Empty: handled in :each, above
20+
end
21+
end
22+
23+
24+
context "#nearest_neighbors" do
25+
it "runs without error" do
26+
@index.nearest_neighbors @testset, 5
27+
end
28+
end
29+
30+
31+
context "#radius_search" do
32+
it "runs without error" do
33+
query = NMatrix.random([1,128])
34+
@index.radius_search query, 0.4
35+
end
36+
end
37+
38+
39+
context "#save" do
40+
it "saves an index to a file which can be loaded again" do
41+
FileUtils.rm("temp_index.save_file", :force => true)
42+
@index.save("temp_index.save_file")
43+
44+
raise(IOError, "save failed") unless File.exists?("temp_index.save_file")
45+
46+
post_index = Flann::Index.new(dataset: @dataset)
47+
post_index.load!("temp_index.save_file")
48+
FileUtils.rm("temp_index.save_file", :force => true)
49+
end
50+
end
51+
52+
53+
context "#free!" do
54+
it "frees an index" do
55+
@index.free!
56+
end
57+
end
58+
59+
60+
61+
62+
end

0 commit comments

Comments
 (0)