panomosity 0.1.34 → 0.1.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/lib/panomosity.rb +36 -0
- data/lib/panomosity/control_point.rb +2 -2
- data/lib/panomosity/errors.rb +3 -0
- data/lib/panomosity/generalized_neighborhood.rb +278 -0
- data/lib/panomosity/image.rb +7 -1
- data/lib/panomosity/measure.rb +56 -0
- data/lib/panomosity/optimisation_variable.rb +3 -3
- data/lib/panomosity/optimizer.rb +10 -15
- data/lib/panomosity/pair.rb +51 -5
- data/lib/panomosity/panorama.rb +79 -40
- data/lib/panomosity/panorama_variable.rb +4 -0
- data/lib/panomosity/runner.rb +72 -4
- data/lib/panomosity/utils.rb +1 -1
- data/lib/panomosity/version.rb +1 -1
- metadata +6 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d0babb2f2886451531b10cafb8fb2b8ccce33edd260247d1a100001120c3af57
|
|
4
|
+
data.tar.gz: 93c56a79260969f6a77c87d019bbd5efb11c6bcd908fd6ea05f5ad13334c2b3a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2f373113dd8fbb7e70045e94cd20d97342f1d955f64b221208288246f8fbcc486e05785e518769d51b73096029b8570861929a9eb10fd1ed0081c448d9111895
|
|
7
|
+
data.tar.gz: e82ff01175bbf2010e102fceae0acc512517ac51037b2e70a1b893ecd6325c0afc251e4a3525fe003528a557b5849590c56229b3c51edab896eb4a79b71bc4d0
|
data/Gemfile.lock
CHANGED
data/lib/panomosity.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
require 'panomosity/control_point'
|
|
2
|
+
require 'panomosity/generalized_neighborhood'
|
|
2
3
|
require 'panomosity/image'
|
|
4
|
+
require 'panomosity/measure'
|
|
3
5
|
require 'panomosity/neighborhood'
|
|
4
6
|
require 'panomosity/neighborhood_group'
|
|
5
7
|
require 'panomosity/optimisation_variable'
|
|
@@ -9,10 +11,12 @@ require 'panomosity/panorama'
|
|
|
9
11
|
require 'panomosity/panorama_variable'
|
|
10
12
|
require 'panomosity/runner'
|
|
11
13
|
require 'panomosity/utils'
|
|
14
|
+
require 'panomosity/errors'
|
|
12
15
|
require 'panomosity/version'
|
|
13
16
|
require 'pathname'
|
|
14
17
|
require 'fileutils'
|
|
15
18
|
require 'optparse'
|
|
19
|
+
require 'json'
|
|
16
20
|
|
|
17
21
|
module Panomosity
|
|
18
22
|
def self.parse(arguments)
|
|
@@ -74,6 +78,38 @@ module Panomosity
|
|
|
74
78
|
options[:darwin] = type
|
|
75
79
|
end
|
|
76
80
|
|
|
81
|
+
parser.on('--version', 'Show the installed version') do
|
|
82
|
+
puts VERSION
|
|
83
|
+
exit
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
parser.on('--regional-distance-similarities-count COUNT', Integer, 'Set the minimum amount of regional control point counts for determining similar neighborhoods (default: 3)') do |count|
|
|
87
|
+
options[:regional_distance_similarities_count] = count
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
parser.on('--max-reduction-attempts COUNT', Integer, 'Set the max reduction attempts when removing neighborhood outliers (default: 2)') do |count|
|
|
91
|
+
options[:max_reduction_attempts] = count
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
desc = <<~DESC
|
|
95
|
+
Set distances to use when determining neighborhood region size in pairs
|
|
96
|
+
Use JSON e.g. '{"x1": 150, "x2": 30}'
|
|
97
|
+
Defaults:
|
|
98
|
+
Vertical pair is x is 10% of image width and y is 100px
|
|
99
|
+
Horizontal pair is x is 100px and y is 10% of image height
|
|
100
|
+
DESC
|
|
101
|
+
parser.on('--distances [DISTANCE_JSON]', desc) do |distances|
|
|
102
|
+
options[:distances] = JSON.parse(distances) rescue nil
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
parser.on('--distances-horizontal [DISTANCE_JSON]', 'Same as above but only affects horizontal image pairs') do |distances|
|
|
106
|
+
options[:regional_distance_similarities_count] = JSON.parse(distances) rescue nil
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
parser.on('--distances-vertical [DISTANCE_JSON]', 'Same as above but only affects vertical image pairs') do |distances|
|
|
110
|
+
options[:regional_distance_similarities_count] = JSON.parse(distances) rescue nil
|
|
111
|
+
end
|
|
112
|
+
|
|
77
113
|
parser.on('-h', '--help', 'Display this screen') do
|
|
78
114
|
puts parser
|
|
79
115
|
exit
|
|
@@ -196,8 +196,8 @@ module Panomosity
|
|
|
196
196
|
"#{to_s.sub(/\n/, '')} dist #{dist.round(4)} pixel_dist #{px.round(4)},#{py.round(4)},#{pdist.round(4)} pixel_r_dist #{prx.round(4)},#{pry.round(4)},#{prdist.round(4)} conn_type #{conn_type}"
|
|
197
197
|
end
|
|
198
198
|
|
|
199
|
-
def attributes
|
|
200
|
-
@attributes
|
|
199
|
+
def attributes(raw: false)
|
|
200
|
+
@attributes.keep_if { |k, _| raw || !%i(raw i1 i2).include?(k) }
|
|
201
201
|
end
|
|
202
202
|
end
|
|
203
203
|
end
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
require 'panomosity/utils'
|
|
2
|
+
|
|
3
|
+
module Panomosity
|
|
4
|
+
class GeneralizedNeighborhood
|
|
5
|
+
include Panomosity::Utils
|
|
6
|
+
|
|
7
|
+
CONTROL_POINT_ATTRIBUTES = [:dist_avg, :dist_std, :x_avg, :x_std, :y_avg, :y_std]
|
|
8
|
+
ATTRIBUTES = [:center, :scope, :control_points, :count] + CONTROL_POINT_ATTRIBUTES
|
|
9
|
+
DEFAULT_DISTANCE = 100
|
|
10
|
+
|
|
11
|
+
attr_reader :measure, :options
|
|
12
|
+
attr_accessor *ATTRIBUTES
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
include Panomosity::Utils
|
|
16
|
+
|
|
17
|
+
attr_accessor :options, :neighborhoods, :horizontal_similar_neighborhoods, :vertical_similar_neighborhoods, :horizontal_neighborhoods_by_similar_neighborhood, :vertical_neighborhoods_by_similar_neighborhood
|
|
18
|
+
|
|
19
|
+
def logger
|
|
20
|
+
@logger ||= Panomosity.logger
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def attributes
|
|
24
|
+
{ name: name }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def horizontal
|
|
28
|
+
horizontal_neighborhoods_by_similar_neighborhood
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def vertical
|
|
32
|
+
vertical_neighborhoods_by_similar_neighborhood
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def calculate_all(panorama:, options: {})
|
|
36
|
+
@neighborhoods = []
|
|
37
|
+
@options = options
|
|
38
|
+
|
|
39
|
+
Pair.create_pairs_from_panorama(panorama)
|
|
40
|
+
calculate_from_pairs
|
|
41
|
+
calculate_from_neighborhoods(type: :horizontal)
|
|
42
|
+
calculate_from_neighborhoods(type: :vertical)
|
|
43
|
+
|
|
44
|
+
@neighborhoods
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def calculate_from_pairs
|
|
48
|
+
logger.debug 'calculating neighborhoods from pairs'
|
|
49
|
+
Pair.all.each do |pair|
|
|
50
|
+
control_points = pair.control_points.select(&:not_generated?)
|
|
51
|
+
control_points.each do |control_point|
|
|
52
|
+
base_params = { center: control_point, scope: pair, options: options }
|
|
53
|
+
position_params = { measure: { type: :position } }
|
|
54
|
+
neighborhood = calculate_neighborhood(**base_params.merge(position_params))
|
|
55
|
+
pair.generalized_neighborhoods << neighborhood
|
|
56
|
+
distance_params = { measure: { type: :distance, distances: { x1: neighborhood.dist_std } } }
|
|
57
|
+
distance_neighborhood = calculate_neighborhood(**base_params.merge(distance_params))
|
|
58
|
+
neighborhood.reference = distance_neighborhood
|
|
59
|
+
pair.generalized_neighborhoods << distance_neighborhood
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def calculate_from_neighborhoods(type:)
|
|
65
|
+
count = options[:regional_distance_similarities_count] || 3
|
|
66
|
+
attempts = options[:max_reduction_attempts] || 2
|
|
67
|
+
|
|
68
|
+
# calculates similar neighborhoods based on the regional control point distances by pair
|
|
69
|
+
calculate_similar_neighborhoods(type: type, count: count)
|
|
70
|
+
if neighborhoods.empty?
|
|
71
|
+
logger.warn 'total neighborhoods came up empty, neighborhood default count to 2'
|
|
72
|
+
calculate_similar_neighborhoods(type: type, count: 2)
|
|
73
|
+
raise 'still could not find neighborhoods' if neighborhoods.empty?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
std_outlier_reduction(type: type, max_reduction_attempts: attempts)
|
|
77
|
+
calculate_neighborhoods_by_similar_neighborhood(type: type)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def calculate_neighborhood(center:, scope:, options:, measure: {})
|
|
81
|
+
neighborhood = new(center: center, scope: scope, options: options)
|
|
82
|
+
neighborhood.update_measure(measure)
|
|
83
|
+
@neighborhoods << neighborhood.calculate
|
|
84
|
+
neighborhood
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def similar_neighborhoods(type: :horizontal)
|
|
88
|
+
type == :horizontal ? @horizontal_similar_neighborhoods : @vertical_similar_neighborhoods
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def similar_neighborhoods!(type: :horizontal)
|
|
92
|
+
neighborhoods = similar_neighborhoods(type: type)
|
|
93
|
+
|
|
94
|
+
if neighborhoods.nil? || neighborhoods.empty?
|
|
95
|
+
error = "No similar #{type} neighborhoods found"
|
|
96
|
+
raise NoSimilarNeighborhoodsError, error
|
|
97
|
+
else
|
|
98
|
+
neighborhoods
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def neighborhoods_by_similar_neighborhood(type: :horizontal)
|
|
103
|
+
type == :horizontal ? @horizontal_neighborhoods_by_similar_neighborhood : @vertical_neighborhoods_by_similar_neighborhood
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def calculate_similar_neighborhoods(type: :horizontal, count: 3)
|
|
107
|
+
similar_neighborhoods = neighborhoods.select(&:measure_position?).select(&:"#{type}?").select do |neighborhood|
|
|
108
|
+
neighborhood.scope.similar_neighborhoods << neighborhood if neighborhood.reference.count >= count
|
|
109
|
+
end
|
|
110
|
+
self.send(:"#{type}_similar_neighborhoods=", similar_neighborhoods)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def std_outlier_reduction(type: :horizontal, max_reduction_attempts: 2, reduction_attempts: 0)
|
|
114
|
+
return if reduction_attempts >= max_reduction_attempts
|
|
115
|
+
|
|
116
|
+
logger.debug "twice reducing #{type} neighborhood std outliers"
|
|
117
|
+
|
|
118
|
+
neighborhoods = similar_neighborhoods!(type: type)
|
|
119
|
+
std_dist_of_neighborhoods = neighborhoods.map(&:dist_std)
|
|
120
|
+
avg, std = *calculate_average_and_std(values: std_dist_of_neighborhoods)
|
|
121
|
+
|
|
122
|
+
similar_neighborhoods!(type: type).select! { |n| (avg - n.dist_std).abs <= std }
|
|
123
|
+
std_outlier_reduction(type: type, max_reduction_attempts: max_reduction_attempts, reduction_attempts: reduction_attempts + 1)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def calculate_neighborhoods_by_similar_neighborhood(type: :horizontal)
|
|
127
|
+
instance_variable_set("@#{type}_neighborhoods_by_similar_neighborhood", [])
|
|
128
|
+
similar_neighborhoods(type: type).each do |neighborhood|
|
|
129
|
+
base_params = { center: neighborhood, scope: self, options: options }
|
|
130
|
+
distance_params = { measure: { type: :distance, distances: { x1: neighborhood.dist_std } } }
|
|
131
|
+
neighborhoods_by_similar_neighborhood(type: type) << calculate_neighborhood(**base_params.merge(distance_params))
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
neighborhoods_by_similar_neighborhood(type: type).sort_by! { |n| -n.count }
|
|
135
|
+
neighborhoods_by_similar_neighborhood(type: type).max_by(5) { |n| n.count }.each do |n|
|
|
136
|
+
logger.debug "#{n.dist_avg} #{n.dist_std} #{n.count} x#{n.x_avg} y#{n.y_avg}"
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def initialize(center:, scope:, options: {})
|
|
142
|
+
@center = center
|
|
143
|
+
@scope = scope
|
|
144
|
+
@options = options
|
|
145
|
+
@measure = Measure.new
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def id
|
|
149
|
+
@id
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def id=(id)
|
|
153
|
+
@id = id
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def reference
|
|
157
|
+
@reference
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def reference=(reference)
|
|
161
|
+
@reference = reference
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def pair_scope?
|
|
165
|
+
scope.class.name == 'Panomosity::Pair'
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def neighborhood_scope?
|
|
169
|
+
scope.class.name == self.class.name
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def measure_position?
|
|
173
|
+
measure.position?
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def measure_distance?
|
|
177
|
+
measure.distance?
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def horizontal?
|
|
181
|
+
pair_scope? ? scope.horizontal? : center.horizontal?
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def vertical?
|
|
185
|
+
pair_scope? ? scope.vertical? : center.vertical?
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def type
|
|
189
|
+
horizontal? ? :horizontal : :vertical
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def update_measure(params)
|
|
193
|
+
measure.update(params)
|
|
194
|
+
set_distance_defaults
|
|
195
|
+
set_measure_defaults
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def distances_from_options(type: :horizontal)
|
|
199
|
+
if type == :both
|
|
200
|
+
options.fetch(:distances, {})
|
|
201
|
+
else
|
|
202
|
+
options[:distances]&.fetch(type, {}) || {}
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def set_distance_defaults
|
|
207
|
+
return unless measure_position?
|
|
208
|
+
|
|
209
|
+
measure.update_distances(distances_from_options(type: :both))
|
|
210
|
+
|
|
211
|
+
if scope.horizontal?
|
|
212
|
+
measure.update_distances(x2: (scope.first_image.h * 0.1).round)
|
|
213
|
+
measure.update_distances(distances_from_options(type: :horizontal))
|
|
214
|
+
else
|
|
215
|
+
measure.update_distances(x1: (scope.first_image.w * 0.1).round)
|
|
216
|
+
measure.update_distances(distances_from_options(type: :vertical))
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
measure.distances[:x1] ||= DEFAULT_DISTANCE
|
|
220
|
+
measure.distances[:x2] ||= DEFAULT_DISTANCE
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def set_measure_defaults
|
|
224
|
+
center_values = if measure_position?
|
|
225
|
+
{ x1: center.x1, x2: center.y1 }
|
|
226
|
+
elsif pair_scope?
|
|
227
|
+
{ x1: center.pdist }
|
|
228
|
+
else
|
|
229
|
+
{ x1: center.dist_avg }
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
measure.update(center: center_values)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def calculate
|
|
236
|
+
if pair_scope?
|
|
237
|
+
elements = scope.control_points.select(&:not_generated?)
|
|
238
|
+
|
|
239
|
+
@control_points = if measure_position?
|
|
240
|
+
elements.select { |cp| measure.includes?(cp.x1, cp.y1) }
|
|
241
|
+
else
|
|
242
|
+
elements.select { |cp| measure.includes?(cp.pdist) }
|
|
243
|
+
end
|
|
244
|
+
else
|
|
245
|
+
@neighborhoods = scope.similar_neighborhoods(type: center.type).select { |n| measure.includes?(n.dist_avg) }
|
|
246
|
+
distance_neighborhoods = @neighborhoods.map(&:reference)
|
|
247
|
+
@control_points = distance_neighborhoods.map(&:control_points).flatten.uniq(&:raw)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
@dist_avg, @dist_std = *calculate_average_and_std(values: control_points.map(&:pdist), ignore_empty: true)
|
|
251
|
+
@x_avg, @x_std = *calculate_average_and_std(values: control_points.map(&:px), ignore_empty: true)
|
|
252
|
+
@y_avg, @y_std = *calculate_average_and_std(values: control_points.map(&:py), ignore_empty: true)
|
|
253
|
+
@count = control_points.count
|
|
254
|
+
|
|
255
|
+
self
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def attributes
|
|
259
|
+
attributes = CONTROL_POINT_ATTRIBUTES.reduce({}) { |h, k| h.merge!({ k => self.send(k) }) }
|
|
260
|
+
measure_attributes = measure.attributes.reduce({}) do |hash, (key, value)|
|
|
261
|
+
hash["measure_#{key}"] = value
|
|
262
|
+
hash
|
|
263
|
+
end
|
|
264
|
+
if pair_scope?
|
|
265
|
+
center_id = center[:id]
|
|
266
|
+
scope_id = [scope.control_points.first.n1, scope.control_points.first.n2]
|
|
267
|
+
scope_name = 'pair'
|
|
268
|
+
else
|
|
269
|
+
center_id = center.id
|
|
270
|
+
scope_id = nil
|
|
271
|
+
scope_name = 'neighborhood'
|
|
272
|
+
end
|
|
273
|
+
attributes.merge!(measure_attributes)
|
|
274
|
+
attributes.merge!(id: id, center: center_id, scope_id: scope_id, scope_name: scope_name, type: type, control_points: control_points.map{|c| c[:id]})
|
|
275
|
+
attributes
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
data/lib/panomosity/image.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Example of an image line
|
|
2
2
|
# i w2448 h3264 f0 v=0 Ra=0 Rb=0 Rc=0 Rd=0 Re=0 Eev6.433 Er1 Eb1 r0 p0 y0 TrX0.88957152 TrY0.79560269 TrZ1 Tpy0 Tpp0 j0 a=0 b=0 c=0 d=0 e=0 g0 t0 Va=0 Vb=0 Vc=0 Vd=0 Vx=0 Vy=0 Vm5 n"WZ8ppTx9PtcxASB3hbeeuS6Z"\n
|
|
3
3
|
|
|
4
4
|
module Panomosity
|
|
@@ -167,5 +167,11 @@ module Panomosity
|
|
|
167
167
|
return unless name =~ /c(\d+)_r(\d+)\.jpg/
|
|
168
168
|
name.scan(/c(\d+)_r(\d+)\.jpg/).flatten.map(&:to_i).last
|
|
169
169
|
end
|
|
170
|
+
|
|
171
|
+
def attributes
|
|
172
|
+
attributes = @attributes.keep_if { |k, _| !%i(raw).include?(k) }
|
|
173
|
+
attributes.merge!(column: column || 0, row: row || 0)
|
|
174
|
+
attributes
|
|
175
|
+
end
|
|
170
176
|
end
|
|
171
177
|
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
require 'panomosity/utils'
|
|
2
|
+
|
|
3
|
+
module Panomosity
|
|
4
|
+
class Measure
|
|
5
|
+
include Panomosity::Utils
|
|
6
|
+
|
|
7
|
+
ATTRIBUTES = [:type, :center, :distances]
|
|
8
|
+
DEFAULT_DISTANCE = 100
|
|
9
|
+
|
|
10
|
+
attr_reader :options
|
|
11
|
+
attr_accessor *ATTRIBUTES
|
|
12
|
+
|
|
13
|
+
def initialize(attributes = {})
|
|
14
|
+
@attributes = attributes
|
|
15
|
+
@attributes[:center] ||= {}
|
|
16
|
+
@attributes[:distances] ||= {}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def position?
|
|
20
|
+
type == :position
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def distance?
|
|
24
|
+
type == :distance
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def type
|
|
28
|
+
@attributes[:type]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def center
|
|
32
|
+
@attributes[:center]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def distances
|
|
36
|
+
@attributes[:distances]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def update(attributes = {})
|
|
40
|
+
|
|
41
|
+
@attributes.merge!(attributes)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def update_distances(values = {})
|
|
45
|
+
@attributes[:distances].merge!(values)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def includes?(*values)
|
|
49
|
+
center.values.zip(values, distances.values).all? { |center, value, distance| (center - value).abs <= distance }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def attributes
|
|
53
|
+
{ type: type, center: center.values, distances: distances.values }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -59,12 +59,12 @@ module Panomosity
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def to_s
|
|
62
|
-
line_values = @@attributes.map { |attribute| "#{attribute}#{self.send(attribute)}" }
|
|
63
|
-
"v #{line_values.join(' ')}\n"
|
|
62
|
+
line_values = @@attributes.map { |attribute| "#{attribute}#{self.send(attribute)}" if self.send(attribute) }
|
|
63
|
+
"v #{line_values.compact.join(' ')}\n"
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
def attributes
|
|
67
|
-
@attributes.keep_if { |k,_| !%i(raw).include?(k) }
|
|
67
|
+
@attributes.keep_if { |k, _| !%i(raw).include?(k) }
|
|
68
68
|
end
|
|
69
69
|
end
|
|
70
70
|
end
|
data/lib/panomosity/optimizer.rb
CHANGED
|
@@ -30,17 +30,14 @@ module Panomosity
|
|
|
30
30
|
logger.info "applying custom values of xh_avg: #{xh_avg}, yh_avg: #{yh_avg}, xv_avg: #{xv_avg}, yv_avg: #{yv_avg}"
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
unless xh_avg && yh_avg && xv_avg && yv_avg
|
|
34
|
-
Pair.calculate_neighborhoods(panorama)
|
|
35
|
-
Pair.calculate_neighborhood_groups
|
|
36
|
-
end
|
|
33
|
+
panorama.calculate_neighborhoods unless xh_avg && yh_avg && xv_avg && yv_avg
|
|
37
34
|
|
|
38
35
|
ds = images.map(&:d).uniq.sort
|
|
39
36
|
es = images.map(&:e).uniq.sort
|
|
40
37
|
|
|
41
38
|
# get the average error for the best neighborhood group
|
|
42
|
-
x_avg = xh_avg ||
|
|
43
|
-
y_avg = yv_avg ||
|
|
39
|
+
x_avg = xh_avg || GeneralizedNeighborhood.horizontal.first.x_avg
|
|
40
|
+
y_avg = yv_avg || GeneralizedNeighborhood.vertical.first.y_avg
|
|
44
41
|
|
|
45
42
|
# start horizontally
|
|
46
43
|
d_map = {}
|
|
@@ -57,8 +54,8 @@ module Panomosity
|
|
|
57
54
|
logger.debug "created e_map #{e_map}"
|
|
58
55
|
|
|
59
56
|
# add in the other offset
|
|
60
|
-
x_avg = xv_avg ||
|
|
61
|
-
y_avg = yh_avg ||
|
|
57
|
+
x_avg = xv_avg || GeneralizedNeighborhood.vertical.first.x_avg
|
|
58
|
+
y_avg = yh_avg || GeneralizedNeighborhood.horizontal.first.y_avg
|
|
62
59
|
|
|
63
60
|
de_map = {}
|
|
64
61
|
d_map.each_with_index do |(dk,dv),di|
|
|
@@ -142,10 +139,9 @@ module Panomosity
|
|
|
142
139
|
end
|
|
143
140
|
|
|
144
141
|
def calculate_average_distance
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
vertical_distances = NeighborhoodGroup.vertical[0..4].map(&:prdist_avg)
|
|
142
|
+
panorama.calculate_neighborhoods
|
|
143
|
+
horizontal_distances = GeneralizedNeighborhood.horizontal[0..4].map(&:dist_avg)
|
|
144
|
+
vertical_distances = GeneralizedNeighborhood.vertical[0..4].map(&:dist_avg)
|
|
149
145
|
calculate_average(values: horizontal_distances + vertical_distances)
|
|
150
146
|
end
|
|
151
147
|
|
|
@@ -161,9 +157,8 @@ module Panomosity
|
|
|
161
157
|
images.each do |image|
|
|
162
158
|
image.r = 0.0
|
|
163
159
|
end
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
cps = NeighborhoodGroup.horizontal.first.control_points
|
|
160
|
+
panorama.calculate_neighborhoods
|
|
161
|
+
cps = GeneralizedNeighborhood.horizontal.first.control_points
|
|
167
162
|
avg, std = *calculate_average_and_std(values: cps.map(&:roll))
|
|
168
163
|
# 0.1 degrees of std
|
|
169
164
|
while std > 0.1
|
data/lib/panomosity/pair.rb
CHANGED
|
@@ -5,7 +5,7 @@ module Panomosity
|
|
|
5
5
|
include Panomosity::Utils
|
|
6
6
|
extend Panomosity::Utils
|
|
7
7
|
|
|
8
|
-
attr_accessor :pair, :control_points, :neighborhoods, :type
|
|
8
|
+
attr_accessor :pair, :control_points, :neighborhoods, :type, :generalized_neighborhoods, :similar_neighborhoods
|
|
9
9
|
|
|
10
10
|
class << self
|
|
11
11
|
attr_accessor :panorama, :logger
|
|
@@ -22,8 +22,8 @@ module Panomosity
|
|
|
22
22
|
@pairs
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
def
|
|
26
|
-
@pairs.map
|
|
25
|
+
def select_control_points_with_regional_distance_similarities
|
|
26
|
+
@pairs.map(&:select_control_points_with_regional_distance_similarities).flatten.uniq(&:raw)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def unconnected
|
|
@@ -44,6 +44,9 @@ module Panomosity
|
|
|
44
44
|
rows = images.map(&:row).uniq.sort
|
|
45
45
|
|
|
46
46
|
@pairs = []
|
|
47
|
+
@horizontal_pairs = []
|
|
48
|
+
@vertical_pairs = []
|
|
49
|
+
|
|
47
50
|
# horizontal pair creation
|
|
48
51
|
rows.each do |row|
|
|
49
52
|
columns.each do |column|
|
|
@@ -52,7 +55,9 @@ module Panomosity
|
|
|
52
55
|
image_2 = images.find { |i| i.row == row && i.column == column.next }
|
|
53
56
|
next if @panorama.calibration? && (image_1.nil? || image_2.nil?)
|
|
54
57
|
control_points = @panorama.control_points.select { |cp| [cp.n1, cp.n2].sort == [image_1.id, image_2.id].sort }
|
|
55
|
-
|
|
58
|
+
pair = Pair.new([image_1, image_2].sort_by(&:id), control_points: control_points, type: :horizontal)
|
|
59
|
+
@pairs << pair
|
|
60
|
+
@horizontal_pairs << pair
|
|
56
61
|
end
|
|
57
62
|
end
|
|
58
63
|
|
|
@@ -64,7 +69,9 @@ module Panomosity
|
|
|
64
69
|
image_2 = images.find { |i| i.column == column && i.row == row.next }
|
|
65
70
|
next if @panorama.calibration? && (image_1.nil? || image_2.nil?)
|
|
66
71
|
control_points = @panorama.control_points.select { |cp| [cp.n1, cp.n2].sort == [image_1.id, image_2.id].sort }
|
|
67
|
-
|
|
72
|
+
pair = Pair.new([image_1, image_2].sort_by(&:id), control_points: control_points, type: :vertical)
|
|
73
|
+
@pairs << pair
|
|
74
|
+
@vertical_pairs << pair
|
|
68
75
|
end
|
|
69
76
|
end
|
|
70
77
|
end
|
|
@@ -150,6 +157,8 @@ module Panomosity
|
|
|
150
157
|
@pair = pair
|
|
151
158
|
@control_points = control_points
|
|
152
159
|
@neighborhoods = []
|
|
160
|
+
@generalized_neighborhoods = []
|
|
161
|
+
@similar_neighborhoods = []
|
|
153
162
|
@type = type
|
|
154
163
|
end
|
|
155
164
|
|
|
@@ -157,6 +166,10 @@ module Panomosity
|
|
|
157
166
|
pair.map(&:id).to_s.gsub(' ', '')
|
|
158
167
|
end
|
|
159
168
|
|
|
169
|
+
def ==(other)
|
|
170
|
+
to_s == other.to_s
|
|
171
|
+
end
|
|
172
|
+
|
|
160
173
|
def info
|
|
161
174
|
"#{to_s}(#{type}) image_1 d,e: #{pair.first.d},#{pair.first.e} | image_2 d,e: #{pair.last.d},#{pair.last.e}"
|
|
162
175
|
end
|
|
@@ -218,6 +231,27 @@ module Panomosity
|
|
|
218
231
|
end
|
|
219
232
|
end
|
|
220
233
|
|
|
234
|
+
# Distance neighborhoods
|
|
235
|
+
def similar_control_points
|
|
236
|
+
@similar_control_points ||= similar_neighborhoods.map(&:reference).map(&:control_points).flatten.uniq(&:raw)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def select_control_points_with_regional_distance_similarities
|
|
240
|
+
# Keep all our control points if we have less than 10
|
|
241
|
+
if control_points.count >= 10
|
|
242
|
+
ratio = similar_control_points.count.to_f / control_points.count
|
|
243
|
+
if ratio < 0.2
|
|
244
|
+
Panomosity.logger.warn "#{to_s} keeping less than 20% (#{(ratio * 100).round(4)}%) of #{control_points.count} control points. Reverting and keeping all control points"
|
|
245
|
+
control_points
|
|
246
|
+
else
|
|
247
|
+
similar_control_points
|
|
248
|
+
end
|
|
249
|
+
else
|
|
250
|
+
Panomosity.logger.debug "Skipping pair #{to_s} since it has fewer than 10 control points"
|
|
251
|
+
control_points
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
221
255
|
def best_neighborhood
|
|
222
256
|
@best_neighborhood ||= @neighborhoods.max_by { |n| n.control_points.count }
|
|
223
257
|
end
|
|
@@ -225,5 +259,17 @@ module Panomosity
|
|
|
225
259
|
def control_points_of_best_neighborhood
|
|
226
260
|
best_neighborhood ? best_neighborhood.control_points : []
|
|
227
261
|
end
|
|
262
|
+
|
|
263
|
+
def attributes
|
|
264
|
+
x_avg, x_std = *calculate_average_and_std(values: control_points.map(&:px), ignore_empty: true)
|
|
265
|
+
y_avg, y_std = *calculate_average_and_std(values: control_points.map(&:py), ignore_empty: true)
|
|
266
|
+
dist_avg, dist_std = *calculate_average_and_std(values: control_points.map(&:pdist), ignore_empty: true)
|
|
267
|
+
i1 = control_points.first.n1
|
|
268
|
+
i2 = control_points.first.n2
|
|
269
|
+
{
|
|
270
|
+
id: [i1, i2], n: i1, N: i2, count: control_points.count, type: type,
|
|
271
|
+
x_avg: x_avg, x_std: x_std, y_avg: y_avg, y_std: y_std, dist_avg: dist_avg, dist_std: dist_std
|
|
272
|
+
}
|
|
273
|
+
end
|
|
228
274
|
end
|
|
229
275
|
end
|
data/lib/panomosity/panorama.rb
CHANGED
|
@@ -18,22 +18,25 @@ module Panomosity
|
|
|
18
18
|
@logger = Panomosity.logger
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
+
def calculate_neighborhoods
|
|
22
|
+
GeneralizedNeighborhood.calculate_all(panorama: self, options: options)
|
|
23
|
+
end
|
|
24
|
+
|
|
21
25
|
def clean_control_points
|
|
22
|
-
if calibration?
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
control_points_to_keep = Pair.good_control_points_to_keep(count: 2)
|
|
26
|
+
options.merge!(distances: { x1: 30, x2: 30 }) if calibration? && !options[:distances].nil?
|
|
27
|
+
options.merge!(regional_distance_similarities_count: 2) unless options[:regional_distance_similarities_count]
|
|
28
|
+
calculate_neighborhoods
|
|
29
|
+
|
|
30
|
+
control_points_to_keep = Pair.select_control_points_with_regional_distance_similarities
|
|
28
31
|
bad_control_points = control_points.reject { |cp| control_points_to_keep.map(&:raw).include?(cp.raw) }
|
|
29
32
|
# far_control_points = control_points.select { |cp| cp.prdist > 50 }
|
|
30
33
|
control_points_to_clean = bad_control_points.uniq(&:raw)
|
|
31
34
|
|
|
32
35
|
# log warnings
|
|
33
36
|
control_point_ratio = control_points_to_clean.count.to_f / control_points.count
|
|
34
|
-
logger.warn "Removing more than 30% (#{(control_point_ratio*100).round(4)}%) of control points. May potentially cause issues." if control_point_ratio >= 0.3
|
|
37
|
+
logger.warn "Removing more than 30% (#{(control_point_ratio * 100).round(4)}%) of control points. May potentially cause issues." if control_point_ratio >= 0.3
|
|
35
38
|
control_point_pair_ratio = Pair.without_enough_control_points(ignore_connected: true).count.to_f / Pair.all.count
|
|
36
|
-
logger.warn "More than 50% (#{(control_point_pair_ratio*100).round(4)}%) of pairs have fewer than 3 control points. May potentially cause issues." if control_point_pair_ratio >= 0.5
|
|
39
|
+
logger.warn "More than 50% (#{(control_point_pair_ratio * 100).round(4)}%) of pairs have fewer than 3 control points. May potentially cause issues." if control_point_pair_ratio >= 0.5
|
|
37
40
|
|
|
38
41
|
control_points_to_clean
|
|
39
42
|
end
|
|
@@ -41,12 +44,8 @@ module Panomosity
|
|
|
41
44
|
def fix_unconnected_image_pairs
|
|
42
45
|
logger.info 'finding unconnected image pairs'
|
|
43
46
|
|
|
44
|
-
if calibration?
|
|
45
|
-
|
|
46
|
-
else
|
|
47
|
-
Pair.calculate_neighborhoods(self, distance: 100)
|
|
48
|
-
end
|
|
49
|
-
Pair.calculate_neighborhood_groups
|
|
47
|
+
options.merge!(distances: { x1: 30, x2: 30 }) if calibration? && !options[:distances].nil?
|
|
48
|
+
calculate_neighborhoods
|
|
50
49
|
|
|
51
50
|
unconnected_image_pairs = Pair.unconnected
|
|
52
51
|
logger.debug unconnected_image_pairs.map { |i| { type: i.type, pair: i.pair.map(&:id) } }
|
|
@@ -92,19 +91,19 @@ module Panomosity
|
|
|
92
91
|
def generate_control_points(pair: nil, bad_control_point: nil, message: '')
|
|
93
92
|
if pair
|
|
94
93
|
if pair.horizontal?
|
|
95
|
-
group =
|
|
94
|
+
group = GeneralizedNeighborhood.horizontal.first
|
|
96
95
|
else
|
|
97
|
-
group =
|
|
96
|
+
group = GeneralizedNeighborhood.vertical.first
|
|
98
97
|
end
|
|
99
98
|
else
|
|
100
99
|
if bad_control_point.conn_type == :horizontal
|
|
101
|
-
group =
|
|
100
|
+
group = GeneralizedNeighborhood.horizontal.first
|
|
102
101
|
else
|
|
103
|
-
group =
|
|
102
|
+
group = GeneralizedNeighborhood.vertical.first
|
|
104
103
|
end
|
|
105
104
|
end
|
|
106
105
|
|
|
107
|
-
control_point = ControlPoint.new(group.center.center.attributes)
|
|
106
|
+
control_point = ControlPoint.new(group.center.center.attributes(raw: true))
|
|
108
107
|
|
|
109
108
|
if pair
|
|
110
109
|
control_point[:n] = pair.first_image.id
|
|
@@ -153,8 +152,7 @@ module Panomosity
|
|
|
153
152
|
end
|
|
154
153
|
|
|
155
154
|
def diagnose
|
|
156
|
-
|
|
157
|
-
Pair.calculate_neighborhood_groups
|
|
155
|
+
calculate_neighborhoods
|
|
158
156
|
|
|
159
157
|
recommendations = []
|
|
160
158
|
messages = []
|
|
@@ -184,7 +182,7 @@ module Panomosity
|
|
|
184
182
|
end
|
|
185
183
|
|
|
186
184
|
# neighborhood group tests
|
|
187
|
-
group_count =
|
|
185
|
+
group_count = GeneralizedNeighborhood.horizontal.count
|
|
188
186
|
if group_count < 5
|
|
189
187
|
message = <<~MESSAGE
|
|
190
188
|
Total number of horizontal neighborhood groups is #{group_count} which is very low.
|
|
@@ -194,7 +192,7 @@ module Panomosity
|
|
|
194
192
|
messages << message
|
|
195
193
|
end
|
|
196
194
|
|
|
197
|
-
group_std_avg = calculate_average(values:
|
|
195
|
+
group_std_avg = calculate_average(values: GeneralizedNeighborhood.horizontal[0..4].map(&:dist_std))
|
|
198
196
|
if group_std_avg > 1.0
|
|
199
197
|
message = <<~MESSAGE
|
|
200
198
|
The standard deviation of distances in the top 5 horizontal neighborhood groups is #{group_std_avg} which is high.
|
|
@@ -207,7 +205,7 @@ module Panomosity
|
|
|
207
205
|
messages << message
|
|
208
206
|
end
|
|
209
207
|
|
|
210
|
-
group_control_points =
|
|
208
|
+
group_control_points = GeneralizedNeighborhood.horizontal.first.control_points.count
|
|
211
209
|
total_control_points = Pair.horizontal.map(&:control_points).flatten.uniq(&:raw).count
|
|
212
210
|
group_control_point_ratio = group_control_points.to_f / total_control_points
|
|
213
211
|
if group_control_point_ratio < 0.2
|
|
@@ -222,7 +220,7 @@ module Panomosity
|
|
|
222
220
|
recommendations << 'horizontal'
|
|
223
221
|
end
|
|
224
222
|
|
|
225
|
-
group_count =
|
|
223
|
+
group_count = GeneralizedNeighborhood.vertical.count
|
|
226
224
|
if group_count < 5
|
|
227
225
|
message = <<~MESSAGE
|
|
228
226
|
Total number of vertical neighborhood groups is #{group_count} which is very low.
|
|
@@ -232,7 +230,7 @@ module Panomosity
|
|
|
232
230
|
messages << message
|
|
233
231
|
end
|
|
234
232
|
|
|
235
|
-
group_std_avg = calculate_average(values:
|
|
233
|
+
group_std_avg = calculate_average(values: GeneralizedNeighborhood.vertical[0..4].map(&:dist_std))
|
|
236
234
|
if group_std_avg > 1.0
|
|
237
235
|
message = <<~MESSAGE
|
|
238
236
|
The standard deviation of distances in the top 5 vertical neighborhood groups is #{group_std_avg} which is high.
|
|
@@ -245,7 +243,7 @@ module Panomosity
|
|
|
245
243
|
messages << message
|
|
246
244
|
end
|
|
247
245
|
|
|
248
|
-
group_control_points =
|
|
246
|
+
group_control_points = GeneralizedNeighborhood.vertical.first.control_points.count
|
|
249
247
|
total_control_points = Pair.vertical.map(&:control_points).flatten.uniq(&:raw).count
|
|
250
248
|
group_control_point_ratio = group_control_points.to_f / total_control_points
|
|
251
249
|
if group_control_point_ratio < 0.2
|
|
@@ -274,11 +272,24 @@ module Panomosity
|
|
|
274
272
|
delta_d: delta_d,
|
|
275
273
|
delta_e: delta_e,
|
|
276
274
|
roll: roll,
|
|
277
|
-
horizontal:
|
|
278
|
-
vertical:
|
|
275
|
+
horizontal: GeneralizedNeighborhood.horizontal.first.attributes,
|
|
276
|
+
vertical: GeneralizedNeighborhood.vertical.first.attributes
|
|
279
277
|
}
|
|
280
278
|
}
|
|
279
|
+
rescue => error
|
|
280
|
+
message = "Got error #{error.message} when calculating neighborhoods. Recommending fallback"
|
|
281
281
|
|
|
282
|
+
logger.error message
|
|
283
|
+
error.backtrace.each { |line| logger.error line }
|
|
284
|
+
|
|
285
|
+
recommendations = %w(horizontal vertical)
|
|
286
|
+
|
|
287
|
+
diagnostic_report = {
|
|
288
|
+
messages: messages,
|
|
289
|
+
recommendations: recommendations,
|
|
290
|
+
data: {}
|
|
291
|
+
}
|
|
292
|
+
ensure
|
|
282
293
|
File.open('diagnostic_report.json', 'w+') { |f| f.puts diagnostic_report.to_json }
|
|
283
294
|
|
|
284
295
|
if recommendations.empty?
|
|
@@ -301,17 +312,13 @@ module Panomosity
|
|
|
301
312
|
calibration_report = JSON.parse(File.read(filename))
|
|
302
313
|
|
|
303
314
|
if @options[:report_type] == 'position'
|
|
304
|
-
if calibration?
|
|
305
|
-
|
|
306
|
-
else
|
|
307
|
-
Pair.calculate_neighborhoods(self, distance: 100)
|
|
308
|
-
end
|
|
309
|
-
Pair.calculate_neighborhood_groups
|
|
315
|
+
options.merge!(distances: { x1: 30, x2: 30 }) if calibration? && !options[:distances].nil?
|
|
316
|
+
calculate_neighborhoods
|
|
310
317
|
|
|
311
|
-
xh_avg =
|
|
312
|
-
yh_avg =
|
|
313
|
-
xv_avg =
|
|
314
|
-
yv_avg =
|
|
318
|
+
xh_avg = GeneralizedNeighborhood.horizontal.first.x_avg
|
|
319
|
+
yh_avg = GeneralizedNeighborhood.horizontal.first.y_avg
|
|
320
|
+
xv_avg = GeneralizedNeighborhood.vertical.first.x_avg
|
|
321
|
+
yv_avg = GeneralizedNeighborhood.vertical.first.y_avg
|
|
315
322
|
calibration_report['position'] = {
|
|
316
323
|
xh_avg: xh_avg,
|
|
317
324
|
yh_avg: yh_avg,
|
|
@@ -327,7 +334,39 @@ module Panomosity
|
|
|
327
334
|
end
|
|
328
335
|
|
|
329
336
|
def calibration?
|
|
330
|
-
!!@input.split(/\n/).find { |line| '#panomosity calibration true' }
|
|
337
|
+
!!@input.split(/\n/).find { |line| line == '#panomosity calibration true' }
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def save_file(filename)
|
|
341
|
+
logger.info "saving file #{filename}"
|
|
342
|
+
|
|
343
|
+
lines = @input.each_line.map do |line|
|
|
344
|
+
objects = [images, variable, control_points, optimisation_variables].flatten
|
|
345
|
+
object = objects.find { |object| object.raw == line }
|
|
346
|
+
object&.to_s || line
|
|
347
|
+
end.compact
|
|
348
|
+
|
|
349
|
+
File.open(filename, 'w') { |f| lines.each { |line| f.puts line } }
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def attributes
|
|
353
|
+
calculate_neighborhoods
|
|
354
|
+
control_points = self.control_points.dup
|
|
355
|
+
control_points.each_with_index { |cp, i| cp[:id] = i }
|
|
356
|
+
neighborhoods = GeneralizedNeighborhood.neighborhoods.dup
|
|
357
|
+
neighborhoods.each_with_index { |n, i| n.id = i }
|
|
358
|
+
types = %i(horizontal vertical)
|
|
359
|
+
similar_neighborhoods = types.map { |type| GeneralizedNeighborhood.similar_neighborhoods(type: type) }.flatten
|
|
360
|
+
neighborhoods_by_similar_neighborhood = types.map { |type| GeneralizedNeighborhood.neighborhoods_by_similar_neighborhood(type: type) }.flatten
|
|
361
|
+
{
|
|
362
|
+
images: images.map(&:attributes),
|
|
363
|
+
variable: variable.attributes,
|
|
364
|
+
control_points: control_points.map(&:attributes),
|
|
365
|
+
optimisation_variables: optimisation_variables.map(&:attributes),
|
|
366
|
+
pairs: Pair.all.map(&:attributes),
|
|
367
|
+
similar_neighborhoods: similar_neighborhoods.map(&:attributes),
|
|
368
|
+
neighborhoods_by_similar_neighborhood: neighborhoods_by_similar_neighborhood.map(&:attributes)
|
|
369
|
+
}
|
|
331
370
|
end
|
|
332
371
|
end
|
|
333
372
|
end
|
data/lib/panomosity/runner.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require 'logger'
|
|
2
2
|
require 'json'
|
|
3
|
+
require 'csv'
|
|
3
4
|
|
|
4
5
|
module Panomosity
|
|
5
6
|
class Runner
|
|
@@ -18,12 +19,15 @@ module Panomosity
|
|
|
18
19
|
create_calibration_report
|
|
19
20
|
crop_centers
|
|
20
21
|
diagnose
|
|
22
|
+
export_control_point_csv
|
|
21
23
|
fix_conversion_errors
|
|
22
24
|
fix_unconnected_image_pairs
|
|
23
25
|
generate_border_line_control_points
|
|
24
26
|
get_columns_and_rows
|
|
25
27
|
get_control_point_info
|
|
26
28
|
get_neighborhood_info
|
|
29
|
+
get_panorama_attributes
|
|
30
|
+
import_control_point_csv
|
|
27
31
|
merge_image_parameters
|
|
28
32
|
nona_grid
|
|
29
33
|
optimize
|
|
@@ -289,6 +293,25 @@ module Panomosity
|
|
|
289
293
|
panorama.diagnose
|
|
290
294
|
end
|
|
291
295
|
|
|
296
|
+
def export_control_point_csv
|
|
297
|
+
logger.info 'exporting control point csv'
|
|
298
|
+
panorama = Panorama.new(@input_file, @options)
|
|
299
|
+
panorama.calculate_neighborhoods
|
|
300
|
+
|
|
301
|
+
filename = 'control_points.csv'
|
|
302
|
+
headers = %w(image_1_name image_2_name image_1_id image_2_id type width height d1 e1 d2 e2 x1 y1 x2 y2 rx ry r)
|
|
303
|
+
CSV.open(filename, 'w+') do |csv|
|
|
304
|
+
csv << headers
|
|
305
|
+
panorama.control_points.each do |cp|
|
|
306
|
+
pair = Pair.all.find { |p| p.control_points.include?(cp) }
|
|
307
|
+
csv << [cp.i1.name, cp.i2.name, cp.i1.id, cp.i2.id, pair.type, cp.i1.w, cp.i1.h, cp.i1.d, cp.i1.e,
|
|
308
|
+
cp.i2.d, cp.i2.e, cp.x1, cp.y1, cp.x2, cp.y2, cp.px, cp.py, cp.pdist]
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
logger.info 'Done. Check for control_points.csv'
|
|
313
|
+
end
|
|
314
|
+
|
|
292
315
|
def fix_conversion_errors
|
|
293
316
|
logger.info 'fixing conversion errors'
|
|
294
317
|
@lines = @input_file.each_line.map do |line|
|
|
@@ -459,6 +482,41 @@ module Panomosity
|
|
|
459
482
|
panorama.get_neighborhood_info
|
|
460
483
|
end
|
|
461
484
|
|
|
485
|
+
def get_panorama_attributes
|
|
486
|
+
# for printing out the JSON
|
|
487
|
+
logger.level = Logger::UNKNOWN
|
|
488
|
+
logger.info 'getting panorama attributes'
|
|
489
|
+
panorama = Panorama.new(@input_file, @options)
|
|
490
|
+
puts panorama.attributes.to_json
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
def import_control_point_csv
|
|
494
|
+
logger.info 'importing control point csv'
|
|
495
|
+
panorama = Panorama.new(@input_file, @options)
|
|
496
|
+
|
|
497
|
+
filename = @csv || 'control_points.csv'
|
|
498
|
+
csv = CSV.read(filename)
|
|
499
|
+
headers = csv.first
|
|
500
|
+
csv = csv[1..(csv.length - 1)]
|
|
501
|
+
data = csv.map { |s| Hash[headers.zip(s)] }
|
|
502
|
+
|
|
503
|
+
@lines = @input_file.each_line.map do |line|
|
|
504
|
+
cp = panorama.control_points.find { |c| c.raw == line }
|
|
505
|
+
if cp
|
|
506
|
+
kept_cp = data.find do |d|
|
|
507
|
+
cp.i1.id == d['image_1_id'].to_i && cp.i2.id == d['image_2_id'].to_i &&
|
|
508
|
+
cp.x1.round(4) == d['x1'].to_f.round(4) && cp.x2.round(4) == d['x2'].to_f.round(4) &&
|
|
509
|
+
cp.y1.round(4) == d['y1'].to_f.round(4) && cp.y2.round(4) == d['y2'].to_f.round(4)
|
|
510
|
+
end
|
|
511
|
+
cp.to_s if kept_cp
|
|
512
|
+
else
|
|
513
|
+
next line
|
|
514
|
+
end
|
|
515
|
+
end.compact
|
|
516
|
+
|
|
517
|
+
save_file
|
|
518
|
+
end
|
|
519
|
+
|
|
462
520
|
def merge_image_parameters
|
|
463
521
|
logger.info 'merging image parameters'
|
|
464
522
|
control_points = ControlPoint.parse(@compare_file)
|
|
@@ -552,12 +610,17 @@ module Panomosity
|
|
|
552
610
|
logger.info 'preparing for pr0ntools'
|
|
553
611
|
|
|
554
612
|
images = Image.parse(@input_file)
|
|
613
|
+
# ds --> X (cols)
|
|
614
|
+
# es --> Y (rows)
|
|
555
615
|
ds = images.map(&:d).uniq.sort
|
|
556
616
|
es = images.map(&:e).uniq.sort
|
|
557
617
|
|
|
558
|
-
# within two pixels of each other
|
|
559
|
-
|
|
560
|
-
|
|
618
|
+
# split ds and es into groups within two pixels of each other,
|
|
619
|
+
# required because d/e can vary a few pixels based on system precision
|
|
620
|
+
# each group of d/e represents an acceptable pixel range
|
|
621
|
+
# for images to be considered part of a col/row
|
|
622
|
+
d_groups = ds.map { |d| ds.select { |n| n.between?(d-2, d+2) }.sort }.uniq
|
|
623
|
+
e_groups = es.map { |e| es.select { |n| n.between?(e-2, e+2) }.sort }.uniq
|
|
561
624
|
|
|
562
625
|
fov = images.map(&:v).uniq.find { |v| v != 0.0 }
|
|
563
626
|
images.each do |i|
|
|
@@ -583,7 +646,12 @@ module Panomosity
|
|
|
583
646
|
FileUtils.mkdir_p(new_dir)
|
|
584
647
|
|
|
585
648
|
logger.info 'renaming files to format cX_rY.jpg'
|
|
586
|
-
images.each
|
|
649
|
+
images.each do |image|
|
|
650
|
+
original_name = image[:original_n]
|
|
651
|
+
new_name = new_dir + image.n
|
|
652
|
+
FileUtils.cp(original_name, new_name)
|
|
653
|
+
logger.debug "copied #{original_name} to #{new_name}"
|
|
654
|
+
end
|
|
587
655
|
|
|
588
656
|
logger.info 'creating scan.json'
|
|
589
657
|
image_first_col = images.find { |image| image.n == 'c0_r0.jpg' }
|
data/lib/panomosity/utils.rb
CHANGED
data/lib/panomosity/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: panomosity
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.39
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Oliver Garcia
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2020-09-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -77,7 +77,10 @@ files:
|
|
|
77
77
|
- exe/panomosity
|
|
78
78
|
- lib/panomosity.rb
|
|
79
79
|
- lib/panomosity/control_point.rb
|
|
80
|
+
- lib/panomosity/errors.rb
|
|
81
|
+
- lib/panomosity/generalized_neighborhood.rb
|
|
80
82
|
- lib/panomosity/image.rb
|
|
83
|
+
- lib/panomosity/measure.rb
|
|
81
84
|
- lib/panomosity/neighborhood.rb
|
|
82
85
|
- lib/panomosity/neighborhood_group.rb
|
|
83
86
|
- lib/panomosity/optimisation_variable.rb
|
|
@@ -108,8 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
108
111
|
- !ruby/object:Gem::Version
|
|
109
112
|
version: '0'
|
|
110
113
|
requirements: []
|
|
111
|
-
|
|
112
|
-
rubygems_version: 2.7.3
|
|
114
|
+
rubygems_version: 3.0.8
|
|
113
115
|
signing_key:
|
|
114
116
|
specification_version: 4
|
|
115
117
|
summary: Wrapper for the PTO file parsing needed for PanoTools.
|