Skip to content

Commit 53491ba

Browse files
committed
Realized my changes were being ignored due to .gitignore. I've
implemented most of the C functions at this point but haven't tested most.
1 parent 7cbaa65 commit 53491ba

File tree

3 files changed

+293
-1
lines changed

3 files changed

+293
-1
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ test/flann_simple_test
1515
test/flann_uint_autotune.py
1616
build
1717
build_win
18-
lib
1918
dist/
2019
flann.kdev4
2120
TODO

src/ruby/lib/flann.rb

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
require 'ffi'
2+
require 'nmatrix'
3+
4+
require_relative "flann/index.rb"
5+
6+
module Flann
7+
extend FFI::Library
8+
ffi_lib "libflann"
9+
10+
# Declare enumerators
11+
Algorithm = enum(:linear, :kdtree, :kmeans, :composite, :kdtree_single, :saved, :autotuned)
12+
CentersInit = enum(:random, :gonzales, :kmeanspp)
13+
LogLevel = enum(:none, :fatal, :error, :warn, :info)
14+
DistanceType = enum(:euclidean, :manhattan, :minkowski, :hist_intersect, :hellinger, :chi_square, :kullback_leibler)
15+
16+
DEFAULT_PARAMETERS = [:kdtree,
17+
32, 0.0,
18+
0, -1, 0,
19+
4, 4,
20+
32, 11, :random, 0.2,
21+
0.9, 0.01, 0, 0.1,
22+
:none, 0
23+
]
24+
25+
# For NMatrix compatibility
26+
typedef :float, :float32
27+
typedef :double, :float64
28+
typedef :pointer, :index_params_ptr
29+
typedef :pointer, :index_ptr
30+
31+
# A nearest neighbor search index for a given dataset.
32+
class Parameters < FFI::Struct
33+
layout :algorithm, Flann::Algorithm, # The algorithm to use (linear, kdtree, kmeans, composite, kdtree_single, saved, autotuned)
34+
:checks, :int, # How many leaves (features) to use (for kdtree)
35+
:cluster_boundary_index, :float, # aka cb_tree, used when searching the kmeans tree
36+
:trees, :int, # Number of randomized trees to use (for kdtree)
37+
:branching, :int, # Branching factor (for kmeans tree)
38+
:iterations, :int, # Max iterations to perform in one kmeans clustering (kmeans tree)
39+
:centers_init, Flann::CentersInit, # Algorithm used (random, gonzales, kmeanspp)
40+
:target_precision, :float, # Precision desired (used for auto-tuning, -1 otherwise)
41+
:build_weight, :float, # Build tree time weighting factor
42+
:memory_weight, :float, # Index memory weighting factor
43+
:sample_fraction, :float, # What fraction of the dataset to use for autotuning
44+
45+
:table_number, :uint, # The number of hash tables to use
46+
:key_size, :uint, # The length of the key to use in the hash tables
47+
:multi_probe_level, :uint, # Number of levels to use in multi-probe LSH, 0 for standard LSH
48+
:log_level, Flann::LogLevel, # Determines the verbosity of each flann function
49+
:random_seed, :long # Random seed to use
50+
end
51+
52+
class << self
53+
54+
55+
def dtype_to_c d
56+
return :float if d == :float32
57+
return :double if d == :float64
58+
return d
59+
end
60+
61+
62+
# Allocates index space and distance space for storing results from various searches. For a k-nearest neighbors
63+
# search, for example, you want trows (the number of rows in the testset) times k (the number of nearest neighbors
64+
# being searched for).
65+
def allocate_results_space result_size
66+
[FFI::MemoryPointer.new(:int, result_size), FFI::MemoryPointer.new(:float, result_size)]
67+
end
68+
69+
70+
# 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+
parameters ||= DEFAULT_PARAMETERS unless block_given?
73+
74+
if parameters.is_a?(FFI::MemoryPointer) # User supplies us with the necessary parameters already in the correct form.
75+
c_parameters_ptr = parameters
76+
c_parameters = Flann::Parameters.new(c_parameters_ptr)
77+
elsif parameters.is_a?(Flann::Parameters)
78+
c_parameters = parameters
79+
c_parameters_ptr = parameters.pointer
80+
else
81+
# Set the old fasioned way
82+
c_parameters_ptr = FFI::MemoryPointer.new(Flann::Parameters.size)
83+
c_parameters = Flann::Parameters.new(c_parameters_ptr)
84+
if parameters.is_a?(Hash)
85+
parameters.each_pair do |key, value|
86+
c_parameters[key] = value
87+
end
88+
end
89+
end
90+
91+
# There may also be a block.
92+
yield c_parameters if block_given?
93+
94+
[c_parameters_ptr, c_parameters]
95+
end
96+
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+
123+
124+
# Find the k nearest neighbors.
125+
#
126+
# If no index parameters are given, FLANN_DEFAULT_PARAMETERS are used. A block is accepted as well.
127+
def nearest_neighbors dataset, testset, k, parameters: DEFAULT_PARAMETERS
128+
129+
# Get a pointer and a struct regardless of how the arguments are supplied.
130+
parameters_ptr, parameters = handle_parameters(parameters)
131+
result_size = testset.shape[0] * k
132+
indices_int_ptr, distances_float_ptr = allocate_results_space(result_size)
133+
134+
Flann.flann_find_nearest_neighbors FFI::Pointer.new_from_nmatrix(dataset), dataset.shape[0], dataset.shape[1],
135+
FFI::Pointer.new_from_nmatrix(testset), testset.shape[0],
136+
indices_int_ptr, distances_float_ptr, k, parameters_ptr
137+
138+
# Return results: two arrays, one of indices and one of distances.
139+
[indices_int_ptr.read_array_of_int(result_size), distances_float_ptr.read_array_of_float(result_size)]
140+
end
141+
alias :nn :nearest_neighbors
142+
143+
# Set the distance function to use when computing distances between data points.
144+
def set_distance_type! distance_function, order = 0
145+
Flann.send(:flann_set_distance_type, distance_function, order)
146+
self
147+
end
148+
149+
# Perform hierarchical clustering of a set of points.
150+
def compute_cluster_centers dataset, clusters, parameters: DEFAULT_PARAMETERS
151+
c_method = "flann_compute_cluster_centers_#{Flann::dtype_to_c(dataset.dtype)}".to_sym
152+
153+
result = dataset.clone_structure
154+
parameters_ptr, parameters = handle_parameters(parameters)
155+
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)
156+
end
157+
end
158+
159+
160+
protected
161+
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.
164+
attach_function :flann_build_index_byte, [:pointer, :int, :int, :pointer, :index_params_ptr], :index_ptr
165+
attach_function :flann_build_index_int, [:pointer, :int, :int, :pointer, :index_params_ptr], :index_ptr
166+
attach_function :flann_build_index_float, [:pointer, :int, :int, :pointer, :index_params_ptr], :index_ptr
167+
attach_function :flann_build_index_double, [:pointer, :int, :int, :pointer, :index_params_ptr], :index_ptr
168+
169+
# index, testset, trows, indices, dists, nn, flann_params
170+
attach_function :flann_find_nearest_neighbors_index, [:index_ptr, :pointer, :int, :pointer, :pointer, :int, :index_params_ptr], :int
171+
172+
# dataset, rows, cols, testset, trows, indices, dists, nn, flann_params
173+
attach_function :flann_find_nearest_neighbors, [:pointer, :int, :int, :pointer, :int, :pointer, :pointer, :int, :index_params_ptr], :int
174+
175+
# index, query point, result indices, result distances, max_nn, radius, flann_params
176+
attach_function :flann_radius_search_byte, [:index_ptr, :pointer, :pointer, :pointer, :int, :float, :index_params_ptr], :int
177+
attach_function :flann_radius_search_int, [:index_ptr, :pointer, :pointer, :pointer, :int, :float, :index_params_ptr], :int
178+
attach_function :flann_radius_search_float, [:index_ptr, :pointer, :pointer, :pointer, :int, :float, :index_params_ptr], :int
179+
attach_function :flann_radius_search_double, [:index_ptr, :pointer, :pointer, :pointer, :int, :float, :index_params_ptr], :int
180+
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
185+
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
190+
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
194+
attach_function :flann_free_index_double, [:index_ptr, :index_params_ptr], :int
195+
196+
attach_function :flann_set_distance_type, [DistanceType, :int], :void
197+
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
202+
203+
end

src/ruby/lib/flann/index.rb

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
class FFI::Pointer
2+
class << self
3+
def new_from_nmatrix nm
4+
::FFI::Pointer.new(nm.data_pointer).tap { |p| p.autorelease = false }
5+
end
6+
end
7+
end
8+
9+
module Flann
10+
class Index
11+
12+
# Constructor takes a block where we set each of the parameters. We need to be careful to do this since
13+
# we're using the C API and not C++; so everything important needs to be initialized or there could be
14+
# a segfault. For reasonable default definitions, see:
15+
#
16+
# * https://github.com/mariusmuja/flann/tree/master/src/cpp/flann/algorithms
17+
#
18+
def initialize dataset: nil, dtype: :float64, parameters: Flann::DEFAULT_PARAMETERS
19+
@dataset = dataset
20+
@dtype = (!dataset.nil? && dataset.is_a?(NMatrix)) ? dataset.dtype : dtype
21+
@index_ptr = nil
22+
23+
@parameters_ptr, @parameters = Flann::handle_parameters(parameters)
24+
25+
yield @parameters if block_given?
26+
end
27+
attr_reader :dtype, :dataset, :parameters, :parameters_ptr, :index_ptr
28+
29+
# Build an index
30+
def build!
31+
raise("no dataset specified") if @dataset.nil?
32+
33+
c_method = "flann_build_index_#{Flann::dtype_to_c(dtype)}".to_sym
34+
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+
37+
# Return the speedup
38+
speedup_float_ptr.read_float
39+
end
40+
41+
# Get the nearest neighbors based on this index. Forces a build of the index if one hasn't been done yet.
42+
def nearest_neighbors testset, k, parameters: DEFAULT_PARAMETERS
43+
self.build! if index_ptr.nil?
44+
Flann::nearest_neighbors_by_index index_ptr, testset, k, parameters: parameters
45+
end
46+
47+
# Perform a radius search on a single query point
48+
def radius_search query, radius, max_k: dataset.shape[1], parameters: DEFAULT_PARAMETERS
49+
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)
52+
53+
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)
55+
56+
# 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)]
58+
end
59+
60+
# Save an index to a file (without the dataset).
61+
def save filename
62+
raise(IOError, "Cannot write an unbuilt index") if self.index_ptr.nil?
63+
c_filename = FFI::MemoryPointer.from_string(filename)
64+
c_method = "flann_save_index_#{Flann::dtype_to_c(dtype)}".to_sym
65+
Flann.send(c_method, c_filename)
66+
self
67+
end
68+
69+
# Load an index from a file (with the dataset already known!).
70+
#
71+
# FIXME: This needs to free the previous dataset first.
72+
def load! filename
73+
c_filename = FFI::MemoryPointer.from_string(filename)
74+
c_method = "flann_load_index_#{Flann::dtype_to_c(dtype)}".to_sym
75+
76+
@index_ptr = Flann.send(c_method, c_filename, FFI::Pointer.new_from_nmatrix(@dataset), @dataset.shape[0], @dataset.shape[1])
77+
self
78+
end
79+
80+
# Free an index
81+
def free! parameters = DEFAULT_PARAMETERS
82+
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)
85+
@index_ptr = nil
86+
self
87+
end
88+
89+
end
90+
end

0 commit comments

Comments
 (0)