panomosity 0.1.17 → 0.1.19

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 036e68c0b7e7200740b6d253cbbe095a37980d0e6bd2ca342d8ba64402334662
4
- data.tar.gz: 56e5cfb0c46c144f92222b03a335fd92876c9390270d30b9d88dc9db9fd7e2df
3
+ metadata.gz: 9d31e6f456b80f0429a224a93939514115dd1f944e4a01c0e4b3992347d9c870
4
+ data.tar.gz: fc71c2b94cb75a36ed249b728a9382350fc72ac5562c3dd644f31a5289969523
5
5
  SHA512:
6
- metadata.gz: 1b84047e33b34e4a6d78c27be9c5e94e659a6a7f11aa69b2891d714756c08d57f62fc01d576c66d717a5b6b676b67b664de6dace0fc5cb4a355e1d9031d5c8d3
7
- data.tar.gz: 1caf1ab0ceb0e65f98a95bc4f4b9b50a19b5daef1bd414a82ce808dcc99cc6fee12a4532c8b6059ab2dbf2f66db7efe82c520c913a2618df5b688263a7174e4a
6
+ metadata.gz: 7b2697110bf392a3d7f15371ca57e7835a149c8dd371cc45317559a005b57ba2b8edbc696d7e87e1f12111a325567e066988eba06614cd61b8b9c1a3d9cd4dd8
7
+ data.tar.gz: b55224d26f9b7f0e45fbcc26478517c39e3c80482dc9f16496b811cc94352bef5dc1e62173abc784ace3dc73c4df0f0adf0cda642897a3df8eb79d10445d8b50
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- panomosity (0.1.17)
4
+ panomosity (0.1.18)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -0,0 +1,43 @@
1
+ require 'panomosity/utils'
2
+
3
+ module Panomosity
4
+ class Neighborhood
5
+ include Panomosity::Utils
6
+
7
+ attr_accessor :center, :pair, :distance, :pair_distance, :control_points, :control_points_within_std, :prdist_avg,
8
+ :prdist_std, :prx_avg, :prx_std, :pry_avg, :pry_std
9
+
10
+ def initialize(center:, pair:, distance:)
11
+ @center = center
12
+ @pair = pair
13
+ @distance = distance
14
+ end
15
+
16
+ def calculate
17
+ # Instead of setting a static distance use a distance that is dependent on the type of connection
18
+ if pair.horizontal?
19
+ @pair_distance = (pair.first_image.h * 0.1).round
20
+ @control_points = pair.control_points.select do |cp|
21
+ cp.x1.between?(center.x1 - distance, center.x1 + distance) && cp.y1.between?(center.y1 - pair_distance, center.y1 + pair_distance)
22
+ end
23
+ else
24
+ @pair_distance = (pair.first_image.w * 0.1).round
25
+ @control_points = pair.control_points.select do |cp|
26
+ cp.x1.between?(center.x1 - pair_distance, center.x1 + pair_distance) && cp.y1.between?(center.y1 - distance, center.y1 + distance)
27
+ end
28
+ end
29
+
30
+ @prdist_avg, @prdist_std = *calculate_average_and_std(values: control_points.map(&:prdist))
31
+ @prx_avg, @prx_std = *calculate_average_and_std(values: control_points.map(&:prx))
32
+ @pry_avg, @pry_std = *calculate_average_and_std(values: control_points.map(&:pry))
33
+
34
+ # add in control points that have similar distances (within std)
35
+ @control_points_within_std = pair.control_points.select { |c| c.prdist.between?(center.prdist - prdist_std, center.prdist + prdist_std) }
36
+ self
37
+ end
38
+
39
+ def info
40
+ "neighborhood: center: (#{center.x1},#{center.y1}) | prx_avg,prx_std: #{prx_avg},#{prx_std} | pry_avg,pry_std: #{pry_avg},#{pry_std} | prdist_avg,prdist_std: #{prdist_avg},#{prdist_std}"
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,109 @@
1
+ require 'panomosity/utils'
2
+
3
+ module Panomosity
4
+ class NeighborhoodGroup
5
+ include Panomosity::Utils
6
+ extend Panomosity::Utils
7
+
8
+ attr_accessor :center, :total_neighborhoods, :neighborhoods, :control_points, :prdist_avg, :prdist_std, :x_avg,
9
+ :y_avg
10
+
11
+ class << self
12
+ attr_accessor :logger
13
+
14
+ def horizontal?
15
+ @type == :horizontal
16
+ end
17
+
18
+ def vertical?
19
+ @type == :vertical
20
+ end
21
+
22
+ def horizontal
23
+ @horizontal_neighborhood_groups
24
+ end
25
+
26
+ def vertical
27
+ @vertical_neighborhood_groups
28
+ end
29
+
30
+ def neighborhoods
31
+ horizontal? ? @horizontal_neighborhoods : @vertical_neighborhoods
32
+ end
33
+
34
+ def neighborhoods=(value)
35
+ if horizontal?
36
+ @horizontal_neighborhoods = value
37
+ else
38
+ @vertical_neighborhoods = value
39
+ end
40
+ end
41
+
42
+ def neighborhood_groups
43
+ horizontal? ? @horizontal_neighborhood_groups : @vertical_neighborhood_groups
44
+ end
45
+
46
+ def neighborhood_groups=(value)
47
+ if horizontal?
48
+ @horizontal_neighborhood_groups = value
49
+ else
50
+ @vertical_neighborhood_groups = value
51
+ end
52
+ end
53
+
54
+ def parse_info(panorama)
55
+ @panorama = panorama
56
+ @logger = @panorama.logger
57
+ end
58
+ end
59
+
60
+ def self.calculate(name: :horizontal, pairs: [])
61
+ @type = name
62
+ default_count = 3
63
+ self.neighborhoods = pairs.map { |p| p.good_neighborhoods_within_std(count: default_count) }.flatten
64
+
65
+ if neighborhoods.empty?
66
+ logger.warn 'total neighborhoods came up empty, neighborhood default count to 2'
67
+ default_count = 2
68
+ self.neighborhoods = pairs.map { |p| p.good_neighborhoods_within_std(count: default_count) }.flatten
69
+ raise 'still could not find neighborhoods' if neighborhoods.empty?
70
+ end
71
+
72
+ logger.debug "twice reducing #{name} neighborhood std outliers"
73
+ avg, std = *calculate_average_and_std(values: neighborhoods.map(&:prdist_std))
74
+ neighborhoods.select! { |n| (avg - n.prdist_std).abs <= std }
75
+ avg, std = *calculate_average_and_std(values: neighborhoods.map(&:prdist_std))
76
+ neighborhoods.select! { |n| (avg - n.prdist_std).abs <= std }
77
+
78
+ self.neighborhood_groups = neighborhoods.map do |neighborhood|
79
+ group = NeighborhoodGroup.new(center: neighborhood, total_neighborhoods: neighborhoods)
80
+ group.calculate
81
+ end
82
+
83
+ neighborhood_groups.max_by(5) { |ng| ng.control_points.count }.each do |ng|
84
+ logger.debug "#{ng.prdist_avg} #{ng.prdist_std} #{ng.control_points.count} x#{ng.x_avg} y#{ng.y_avg}"
85
+ end
86
+
87
+ self.neighborhood_groups = neighborhood_groups.max_by(5) { |ng| ng.control_points.count }
88
+ end
89
+
90
+ def self.info
91
+
92
+ end
93
+
94
+ def initialize(center:, total_neighborhoods:)
95
+ @center = center
96
+ @total_neighborhoods = total_neighborhoods
97
+ end
98
+
99
+ def calculate
100
+ @neighborhoods = total_neighborhoods.select { |n| (n.prdist_avg - center.prdist_avg).abs <= center.prdist_std }
101
+ @control_points = neighborhoods.map(&:control_points_within_std).flatten.uniq { |cp| cp.raw }
102
+ @x_avg = calculate_average(values: control_points.map(&:px))
103
+ @y_avg = calculate_average(values: control_points.map(&:py))
104
+ @prdist_avg = center.prdist_avg
105
+ @prdist_std = center.prdist_std
106
+ self
107
+ end
108
+ end
109
+ end
@@ -26,16 +26,17 @@ module Panomosity
26
26
  end
27
27
 
28
28
  def run_position_optimizer
29
- panorama.calculate_neighborhoods(amount_ratio: 1.0, log: false)
30
- panorama.calculate_neighborhood_groups(name: :horizontal, pairs: panorama.horizontal_pairs)
31
- panorama.calculate_neighborhood_groups(name: :vertical, pairs: panorama.vertical_pairs)
29
+ Pair.calculate_neighborhoods(panorama)
30
+ Pair.calculate_neighborhood_groups
32
31
 
33
32
  ds = images.map(&:d).uniq.sort
34
33
  es = images.map(&:e).uniq.sort
35
34
 
36
- # start horizontally
37
- x_avg = panorama.horizontal_neighborhoods_group.first[:x_avg]
35
+ # get the average error for the best neighborhood group
36
+ x_avg = NeighborhoodGroup.horizontal.first.x_avg
37
+ y_avg = NeighborhoodGroup.vertical.first.y_avg
38
38
 
39
+ # start horizontally
39
40
  d_map = {}
40
41
  ds.each_with_index do |d, i|
41
42
  d_map[d] = d + -x_avg * i
@@ -43,16 +44,15 @@ module Panomosity
43
44
  logger.debug "created d_map #{d_map}"
44
45
 
45
46
  # vertical
46
- y_avg = panorama.vertical_neighborhoods_group.first[:y_avg]
47
-
48
47
  e_map = {}
49
48
  es.each_with_index do |e, i|
50
49
  e_map[e] = e + -y_avg * i
51
50
  end
52
51
  logger.debug "created e_map #{e_map}"
53
52
 
54
- x_avg = panorama.vertical_neighborhoods_group.first[:x_avg]
55
- y_avg = panorama.horizontal_neighborhoods_group.first[:y_avg]
53
+ # add in the other offset
54
+ x_avg = NeighborhoodGroup.vertical.first.x_avg
55
+ y_avg = NeighborhoodGroup.horizontal.first.y_avg
56
56
 
57
57
  de_map = {}
58
58
  d_map.each_with_index do |(dk,dv),di|
@@ -77,26 +77,12 @@ module Panomosity
77
77
  r = images.map(&:r).first
78
78
  original_roll = r
79
79
  logger.debug "current roll #{r}"
80
- panorama.calculate_neighborhoods(amount_ratio: 1.0, log: false)
81
- panorama.calculate_neighborhood_groups(name: :horizontal, pairs: panorama.horizontal_pairs)
82
- panorama.calculate_neighborhood_groups(name: :vertical, pairs: panorama.vertical_pairs)
83
-
84
80
  # we grab the top 5 neighborhood groups and get the average distance for them and average that
85
- horizontal_distances = panorama.horizontal_neighborhoods_group[0..5].map{|g| g[:prdist_avg]}
86
- vertical_distances = panorama.vertical_neighborhoods_group[0..5].map{|g| g[:prdist_avg]}
87
- dist_avg = calculate_average_and_std(values: horizontal_distances + vertical_distances).first
81
+ dist_avg = calculate_average_distance
88
82
 
89
83
  r -= 0.05
90
84
  logger.debug "current roll #{r}"
91
- panorama.images.each { |i| i.r = r }
92
- panorama.control_points = ControlPoint.calculate_distances(panorama.images, panorama.variable)
93
- panorama.calculate_neighborhoods(amount_ratio: 1.0, log: false)
94
- panorama.calculate_neighborhood_groups(name: :horizontal, pairs: panorama.horizontal_pairs)
95
- panorama.calculate_neighborhood_groups(name: :vertical, pairs: panorama.vertical_pairs)
96
-
97
- horizontal_distances = panorama.horizontal_neighborhoods_group[0..5].map{|g| g[:prdist_avg]}
98
- vertical_distances = panorama.vertical_neighborhoods_group[0..5].map{|g| g[:prdist_avg]}
99
- new_dist_avg = calculate_average_and_std(values: horizontal_distances + vertical_distances).first
85
+ new_dist_avg = recalculate_average_distance(roll: r)
100
86
  logger.debug "avg: #{dist_avg} new_avg: #{new_dist_avg}"
101
87
 
102
88
  if new_dist_avg < dist_avg
@@ -108,30 +94,14 @@ module Panomosity
108
94
  r = original_roll
109
95
  r += 0.05
110
96
  logger.debug "current roll #{r}"
111
- panorama.images.each { |i| i.r = r }
112
- panorama.control_points = ControlPoint.calculate_distances(panorama.images, panorama.variable)
113
- panorama.calculate_neighborhoods(amount_ratio: 1.0, log: false)
114
- panorama.calculate_neighborhood_groups(name: :horizontal, pairs: panorama.horizontal_pairs)
115
- panorama.calculate_neighborhood_groups(name: :vertical, pairs: panorama.vertical_pairs)
116
-
117
- horizontal_distances = panorama.horizontal_neighborhoods_group[0..5].map{|g| g[:prdist_avg]}
118
- vertical_distances = panorama.vertical_neighborhoods_group[0..5].map{|g| g[:prdist_avg]}
119
- new_dist_avg = calculate_average_and_std(values: horizontal_distances + vertical_distances).first
97
+ new_dist_avg = recalculate_average_distance(roll: r)
120
98
  end
121
99
 
122
100
  while new_dist_avg <= dist_avg
123
101
  r = r.send(operation, 0.05)
124
102
  logger.debug "current roll #{r}"
125
103
  dist_avg = new_dist_avg
126
- panorama.images.each { |i| i.r = r }
127
- panorama.control_points = ControlPoint.calculate_distances(panorama.images, panorama.variable)
128
- panorama.calculate_neighborhoods(amount_ratio: 1.0, log: false)
129
- panorama.calculate_neighborhood_groups(name: :horizontal, pairs: panorama.horizontal_pairs)
130
- panorama.calculate_neighborhood_groups(name: :vertical, pairs: panorama.vertical_pairs)
131
-
132
- horizontal_distances = panorama.horizontal_neighborhoods_group[0..5].map{|g| g[:prdist_avg]}
133
- vertical_distances = panorama.vertical_neighborhoods_group[0..5].map{|g| g[:prdist_avg]}
134
- new_dist_avg = calculate_average_and_std(values: horizontal_distances + vertical_distances).first
104
+ new_dist_avg = recalculate_average_distance(roll: r)
135
105
  logger.debug "avg: #{dist_avg} new_avg: #{new_dist_avg}"
136
106
  end
137
107
 
@@ -139,5 +109,19 @@ module Panomosity
139
109
  image.r = r
140
110
  end
141
111
  end
112
+
113
+ def calculate_average_distance
114
+ Pair.calculate_neighborhoods(panorama)
115
+ Pair.calculate_neighborhood_groups
116
+ horizontal_distances = NeighborhoodGroup.horizontal.map(&:prdist_avg)
117
+ vertical_distances = NeighborhoodGroup.vertical.map(&:prdist_avg)
118
+ calculate_average(values: horizontal_distances + vertical_distances)
119
+ end
120
+
121
+ def recalculate_average_distance(roll:)
122
+ panorama.images.each { |i| i.r = roll }
123
+ panorama.control_points = ControlPoint.calculate_distances(panorama.images, panorama.variable)
124
+ calculate_average_distance
125
+ end
142
126
  end
143
127
  end
@@ -0,0 +1,208 @@
1
+ require 'panomosity/utils'
2
+
3
+ module Panomosity
4
+ class Pair
5
+ include Panomosity::Utils
6
+ extend Panomosity::Utils
7
+
8
+ attr_accessor :pair, :control_points, :neighborhoods, :type
9
+
10
+ class << self
11
+ attr_accessor :logger
12
+
13
+ def horizontal
14
+ @horizontal_pairs
15
+ end
16
+
17
+ def vertical
18
+ @vertical_pairs
19
+ end
20
+
21
+ def all
22
+ @pairs
23
+ end
24
+
25
+ def good_control_points_to_keep
26
+ @pairs.map(&:good_control_points_to_keep).flatten.uniq { |cp| cp.raw }
27
+ end
28
+
29
+ def unconnected
30
+ @pairs.select(&:unconnected?).sort_by(&:to_s)
31
+ end
32
+
33
+ def without_enough_control_points(ignore_connected: false)
34
+ @pairs.select { |pair| (ignore_connected || pair.connected?) && pair.control_points.count < 3 }
35
+ end
36
+ end
37
+
38
+ def self.create_pairs_from_panorama(panorama)
39
+ @panorama = panorama
40
+ @logger = @panorama.logger
41
+
42
+ images = @panorama.images
43
+ columns = images.map(&:column).uniq.sort
44
+ rows = images.map(&:row).uniq.sort
45
+
46
+ @pairs = []
47
+ # horizontal pair creation
48
+ rows.each do |row|
49
+ columns.each do |column|
50
+ next if column == columns.last
51
+ image_1 = images.find { |i| i.row == row && i.column == column }
52
+ image_2 = images.find { |i| i.row == row && i.column == column.next }
53
+ control_points = @panorama.control_points.select { |cp| [cp.n1, cp.n2].sort == [image_1.id, image_2.id].sort }
54
+ @pairs << Pair.new([image_1, image_2].sort_by(&:id), control_points: control_points, type: :horizontal)
55
+ end
56
+ end
57
+
58
+ # vertical pair creation
59
+ columns.each do |column|
60
+ rows.each do |row|
61
+ next if row == rows.last
62
+ image_1 = images.find { |i| i.column == column && i.row == row }
63
+ image_2 = images.find { |i| i.column == column && i.row == row.next }
64
+ control_points = @panorama.control_points.select { |cp| [cp.n1, cp.n2].sort == [image_1.id, image_2.id].sort }
65
+ @pairs << Pair.new([image_1, image_2].sort_by(&:id), control_points: control_points, type: :vertical)
66
+ end
67
+ end
68
+ end
69
+
70
+ def self.calculate_neighborhoods(panorama, distance: 30)
71
+ create_pairs_from_panorama(panorama)
72
+ @pairs.each { |pair| pair.calculate_neighborhoods(distance: distance) }
73
+
74
+ # separate out into horizontal and vertical pairs
75
+ @horizontal_pairs = @pairs.select(&:horizontal?)
76
+ @vertical_pairs = @pairs.select(&:vertical?)
77
+
78
+ # sort pairs by average distance first and number of control points descending second
79
+ @horizontal_pairs = @horizontal_pairs.sort_by { |pair| [pair.average_distance, -pair.control_points.count] }
80
+ @vertical_pairs = @vertical_pairs.sort_by { |pair| [pair.average_distance, -pair.control_points.count] }
81
+
82
+ log_detailed_neighborhood_info(name: :horizontal, pairs: @horizontal_pairs)
83
+ log_detailed_neighborhood_info(name: :vertical, pairs: @vertical_pairs)
84
+ end
85
+
86
+ def self.log_detailed_neighborhood_info(name: :horizontal, pairs: [])
87
+ return unless @panorama.options[:very_verbose]
88
+ logger.debug "showing #{name} pair information"
89
+ pair = pairs.max_by { |p| p.control_points_of_best_neighborhood.count }
90
+ logger.debug "best #{name} pair #{pair.to_s} found #{pair.control_points_of_best_neighborhood.count} control points"
91
+ pairs.each do |p|
92
+ logger.debug "#{name} pair #{p.to_s} found #{p.control_points_of_best_neighborhood.count} control points"
93
+ p.neighborhoods.each do |n|
94
+ logger.debug "neighborhood centered at #{n.center.x1},#{n.center.y1}: #{n.control_points.count} control points"
95
+ logger.debug "neighborhood centered at #{n.center.x1},#{n.center.y1}: prdist #{n.prdist_avg},#{n.prdist_std} prx #{n.prx_avg},#{n.prx_std} pry #{n.pry_avg},#{n.pry_std}"
96
+ n.control_points.each { |point| logger.debug point.detailed_info }
97
+ end
98
+ end
99
+ end
100
+
101
+ def self.calculate_neighborhood_groups
102
+ NeighborhoodGroup.parse_info(@panorama)
103
+ NeighborhoodGroup.calculate(name: :horizontal, pairs: @horizontal_pairs)
104
+ NeighborhoodGroup.calculate(name: :vertical, pairs: @vertical_pairs)
105
+ end
106
+
107
+ def self.info
108
+ logger.debug "total number of control points: #{@pairs.map(&:control_points).flatten.count}"
109
+ logger.debug 'displaying horizontal pair info'
110
+ logger.debug "total number of horizontal control points: #{@horizontal_pairs.map(&:control_points).flatten.count}"
111
+ @horizontal_pairs.each do |pair|
112
+ logger.debug pair.info
113
+ logger.debug "total number of control points: #{pair.control_points.count}"
114
+ x_dist, x_std = *calculate_average_and_std(values: pair.control_points.map(&:prx))
115
+ y_dist, y_std = *calculate_average_and_std(values: pair.control_points.map(&:pry))
116
+ dist, std = *calculate_average_and_std(values: pair.control_points.map(&:prdist))
117
+ logger.debug "control points: x_dist,x_std: #{x_dist},#{x_std} | y_dist,y_std: #{y_dist},#{y_std} | dist,std: #{dist},#{std}"
118
+ logger.debug "total number of neighborhoods: #{pair.neighborhoods.count}"
119
+ logger.debug "total number single cp neighborhoods: #{pair.neighborhoods.select{|n| n.control_points.count == 1}.count}"
120
+ pair.neighborhoods.each do |neighborhood|
121
+ logger.debug neighborhood.info
122
+ logger.debug "neighborhood: distance,pair_distance: #{neighborhood.distance},#{neighborhood.pair_distance} | total number of control points: #{neighborhood.control_points.count}"
123
+ logger.debug "neighborhood: center prdist: #{neighborhood.center.prdist} | total number of control points within std: #{neighborhood.control_points_within_std.count}"
124
+ end
125
+ end
126
+ end
127
+
128
+ def initialize(pair, control_points: [], type: nil)
129
+ @pair = pair
130
+ @control_points = control_points
131
+ @neighborhoods = []
132
+ @type = type
133
+ end
134
+
135
+ def to_s
136
+ pair.map(&:id)
137
+ end
138
+
139
+ def info
140
+ "#{to_s}(#{type}) image_1 d,e: #{pair.first.d},#{pair.first.e} | image_2 d,e: #{pair.last.d},#{pair.last.e}"
141
+ end
142
+
143
+ def horizontal?
144
+ @type == :horizontal || (control_points.first && control_points.first.conn_type == :horizontal)
145
+ end
146
+
147
+ def vertical?
148
+ @type == :vertical || (control_points.first && control_points.first.conn_type == :vertical)
149
+ end
150
+
151
+ def connected?
152
+ !unconnected?
153
+ end
154
+
155
+ def unconnected?
156
+ control_points.empty?
157
+ end
158
+
159
+ def first_image
160
+ pair.first
161
+ end
162
+
163
+ def last_image
164
+ pair.last
165
+ end
166
+
167
+ def average_distance
168
+ calculate_average(values: control_points.map(&:prdist), ignore_empty: true)
169
+ end
170
+
171
+ def calculate_neighborhoods(distance: 30)
172
+ @neighborhoods = control_points.map do |cp|
173
+ neighborhood = Neighborhood.new(center: cp, pair: self, distance: distance)
174
+ neighborhood.calculate
175
+ end
176
+ end
177
+
178
+ # gets all control points for neighborhoods with a good std of distance
179
+ def good_neighborhoods_within_std(count: 3)
180
+ @neighborhoods.select { |n| n.control_points_within_std.count >= count }
181
+ end
182
+
183
+ def good_control_points_to_keep(count: 3)
184
+ control_points_to_keep = good_neighborhoods_within_std(count: count).map(&:control_points_within_std).flatten.uniq { |cp| cp.raw }
185
+
186
+ # Keep all our control points if we have less than 10
187
+ if control_points.count >= 10
188
+ ratio = control_points_to_keep.count.to_f / control_points.count
189
+ if ratio < 0.5
190
+ Panomosity.logger.warn "#{to_s} keeping less than 50% (#{(ratio*100).round(4)}%) of #{control_points.count} control points. Reverting and keeping all control points"
191
+ control_points
192
+ else
193
+ control_points_to_keep
194
+ end
195
+ else
196
+ control_points
197
+ end
198
+ end
199
+
200
+ def best_neighborhood
201
+ @best_neighborhood ||= @neighborhoods.max_by { |n| n.control_points.count }
202
+ end
203
+
204
+ def control_points_of_best_neighborhood
205
+ best_neighborhood ? best_neighborhood.control_points : []
206
+ end
207
+ end
208
+ end
@@ -2,368 +2,43 @@ module Panomosity
2
2
  class Panorama
3
3
  include Panomosity::Utils
4
4
 
5
- attr_accessor :images, :control_points, :variable, :optimisation_variables, :logger, :horizontal_pairs,
6
- :vertical_pairs, :horizontal_neighborhoods_group, :vertical_neighborhoods_group
5
+ attr_accessor :images, :control_points, :variable, :optimisation_variables, :logger, :options
7
6
 
8
- def initialize(input, logger = nil)
7
+ def initialize(input, options = {})
9
8
  @input = input
9
+ @options = options
10
10
  @images = Image.parse(@input)
11
11
  @variable = PanoramaVariable.parse(@input).first
12
12
  ControlPoint.parse(@input)
13
13
  @control_points = ControlPoint.calculate_distances(@images, @variable)
14
14
  @optimisation_variables = OptimisationVariable.parse(@input)
15
-
16
- if logger
17
- @logger = logger
18
- else
19
- @logger = Logger.new(STDOUT)
20
- @logger.level = Logger::DEBUG
21
- @logger.formatter = proc do |severity, datetime, progname, msg|
22
- "[#{datetime}][#{severity}] #{msg}\n"
23
- end
24
- end
15
+ @logger = Panomosity.logger
25
16
  end
26
17
 
27
- def calculate_neighborhoods(distance: 30, amount_ratio: 0.2, log: true)
28
- pairs = control_points.group_by { |cp| [cp.n1, cp.n2] }
29
- amount = (amount_ratio * (images.count)).floor
30
-
31
- # separate out into horizontal and vertical pairs
32
- horizontal_pairs = pairs.select { |_,cps| cps.first.conn_type == :horizontal }
33
- vertical_pairs = pairs.select { |_,cps| cps.first.conn_type == :vertical }
34
-
35
- # sort pairs by average distance first and number of control points descending second
36
- horizontal_pairs = horizontal_pairs.sort_by { |_, cps| [calculate_average_and_std(values: cps.map(&:prdist)).first, -cps.count] }
37
- vertical_pairs = vertical_pairs.sort_by { |_, cps| [calculate_average_and_std(values: cps.map(&:prdist)).first, -cps.count] }
38
-
39
- # select a set amount
40
- horizontal_pairs = horizontal_pairs[0..(amount-1)]
41
- vertical_pairs = vertical_pairs[0..(amount-1)]
42
-
43
- # looks for the highest concentration of points with similar distances within the neighborhood (30px) of the average
44
- # group cps that are close in distance to each other
45
- # start with horizontal pairs
46
- @horizontal_pairs = calculate_neighborhood_info(pairs: horizontal_pairs, distance: distance)
47
- log_detailed_neighborhood_info(name: :horizontal, pairs: @horizontal_pairs) if log
48
-
49
- # vertical pairs
50
- @vertical_pairs = calculate_neighborhood_info(pairs: vertical_pairs, distance: distance)
51
- log_detailed_neighborhood_info(name: :vertical, pairs: @vertical_pairs) if log
52
-
53
- { horizontal_pairs: @horizontal_pairs, vertical_pairs: @vertical_pairs }
54
- end
55
-
56
- def calculate_neighborhood_info(pairs: [], distance: 30)
57
- pairs.map do |pair, cps|
58
- neighborhoods = cps.map do |cp|
59
- neighborhood = cps.select { |c| c.x1.between?(cp.x1 - distance, cp.x1 + distance) && c.y1.between?(cp.y1 - distance, cp.y1 + distance) }
60
- if neighborhood.count > 1
61
- prdist_avg, prdist_std = *calculate_average_and_std(values: neighborhood.map(&:prdist))
62
- prx_avg, prx_std = *calculate_average_and_std(values: neighborhood.map(&:prx))
63
- pry_avg, pry_std = *calculate_average_and_std(values: neighborhood.map(&:pry))
64
-
65
- # add in control points that have similar distances (within std)
66
- neighborhood_within_std = cps.select { |c| c.prdist.between?(cp.prdist - prdist_std, cp.prdist + prdist_std) }
67
- {
68
- cp: cp,
69
- prdist_avg: prdist_avg,
70
- prdist_std: prdist_std,
71
- prx_avg: prx_avg,
72
- prx_std: prx_std,
73
- pry_avg: pry_avg,
74
- pry_std: pry_std,
75
- neighborhood: neighborhood,
76
- neighborhood_within_std: neighborhood_within_std
77
- }
78
- else
79
- nil
80
- end
81
- end.compact
82
- # gets all control points for neighborhoods with a good std of distance
83
- control_points_within_a_std = neighborhoods.select { |n| n[:neighborhood_within_std].count >= 3 }.flatten
84
-
85
- best_neighborhood = neighborhoods.sort_by { |n| -n[:neighborhood].count }.first
86
- {
87
- pair: pair,
88
- control_points: cps,
89
- neighborhoods: neighborhoods,
90
- best_neighborhood: best_neighborhood,
91
- control_points_within_a_std: control_points_within_a_std
92
- }
93
- end
94
- end
95
-
96
- # get all neighborhoods based on how many control points are within the std of the distance
97
- def calculate_neighborhood_groups(name: :horizontal, pairs: [])
98
- neighborhood_default = 3
99
- total_neighborhoods = pairs.map { |p| p[:neighborhoods].select { |n| n[:neighborhood_within_std].count >= neighborhood_default }}.flatten
100
-
101
- if total_neighborhoods.map { |n| n[:prdist_std] }.empty?
102
- logger.warn 'total_neighborhoods came up empty, neighborhood default count to 2'
103
- neighborhood_default = 2
104
- total_neighborhoods = pairs.map { |p| p[:neighborhoods].select { |n| n[:neighborhood_within_std].count >= neighborhood_default }}.flatten
105
- raise 'still could not find neighborhoods' if total_neighborhoods.map { |n| n[:prdist_std] }.empty?
106
- end
107
-
108
- logger.debug "twice reducing #{name} neighborhood std outliers"
109
- avg, std = *calculate_average_and_std(values: total_neighborhoods.map { |n| n[:prdist_std] })
110
- total_neighborhoods.select! { |n| (avg - n[:prdist_std]).abs <= std }
111
- avg, std = *calculate_average_and_std(values: total_neighborhoods.map { |n| n[:prdist_std] })
112
- total_neighborhoods.select! { |n| (avg - n[:prdist_std]).abs <= std }
113
- neighborhood_group = total_neighborhoods.map do |neighborhood|
114
- ns_total = total_neighborhoods.select { |n| (n[:prdist_avg] - neighborhood[:prdist_avg]).abs < neighborhood[:prdist_std] }
115
- cps_total = ns_total.map { |n| n[:neighborhood_within_std].count }.reduce(:+)
116
- x_avg, _ = *calculate_average_and_std(values: ns_total.map { |n| n[:neighborhood_within_std] }.flatten.map(&:px))
117
- y_avg, _ = *calculate_average_and_std(values: ns_total.map { |n| n[:neighborhood_within_std] }.flatten.map(&:py))
118
- {
119
- neighborhood: neighborhood,
120
- total_neighboorhoods: ns_total,
121
- total_control_points: cps_total,
122
- prdist_avg: neighborhood[:prdist_avg],
123
- prdist_std: neighborhood[:prdist_std],
124
- x_avg: x_avg,
125
- y_avg: y_avg
126
- }
127
- end
128
- neighborhood_group.sort_by { |n| -n[:total_control_points] }[0..5].each do |ng|
129
- logger.debug "#{ng[:prdist_avg]} #{ng[:prdist_std]} #{ng[:total_control_points]} x#{ng[:x_avg]} y#{ng[:y_avg]}"
130
- end
131
-
132
- if name == :horizontal
133
- @horizontal_neighborhoods_group = neighborhood_group.sort_by { |n| -n[:total_control_points] }
134
- else
135
- @vertical_neighborhoods_group = neighborhood_group.sort_by { |n| -n[:total_control_points] }
136
- end
137
- end
138
-
139
- def log_detailed_neighborhood_info(name: :horizontal, pairs: [])
140
- logger.debug "showing #{name} pair information"
141
- pair = pairs.sort_by{|pair| pair[:best_neighborhood] ? -pair[:best_neighborhood][:neighborhood].count : 0}.first
142
- pairs.each do |p|
143
- logger.debug "#{name} pair #{p[:pair]} found #{p[:best_neighborhood] ? p[:best_neighborhood][:neighborhood].count : 0} control points"
144
- p[:neighborhoods].each do |neighborhood|
145
- logger.debug "neighborhood centered at #{neighborhood[:cp].x1},#{neighborhood[:cp].y1}: #{neighborhood[:neighborhood].count} control points"
146
- logger.debug "neighborhood centered at #{neighborhood[:cp].x1},#{neighborhood[:cp].y1}: prdist #{neighborhood[:prdist_avg]},#{neighborhood[:prdist_std]} prx #{neighborhood[:prx_avg]},#{neighborhood[:prx_std]} pry #{neighborhood[:pry_avg]},#{neighborhood[:pry_std]}"
147
- neighborhood[:neighborhood].each do |point|
148
- logger.debug point.detailed_info
149
- end
150
- end
151
- end
152
- pairs.each do |p|
153
- logger.debug "#{name} pair #{p[:pair]} found #{p[:best_neighborhood] ? p[:best_neighborhood][:neighborhood].count : 0} control points"
154
- end
155
- #logger.debug "#{name} pair #{pair[:pair]} found #{pair[:best_neighborhood] ? pair[:best_neighborhood][:neighborhood].count : 0} control points"
156
- end
157
-
158
- def clean_control_points(options = {})
159
- bad_control_points = []
160
- min_control_points = 5
161
- control_points.group_by { |cp| [cp.n1, cp.n2] }.select { |_, cps| cps.count > min_control_points }.each do |pair, cps|
162
- logger.debug "cleaning pair #{pair.first} <> #{pair.last}"
163
- average_x, x_std = *calculate_average_and_std(name: :x, values: cps.map(&:px), logger: logger)
164
- average_y, y_std = *calculate_average_and_std(name: :y, values: cps.map(&:py), logger: logger)
165
-
166
- max_removal = ((options[:max_removal] || 0.2) * cps.count).floor
167
- min_cps = 8
168
- max_iterations = 10
169
- iterations = 0
170
- bad_cps = cps.select { |cp| (cp.px - average_x).abs >= x_std || (cp.py - average_y).abs >= y_std }
171
- while bad_cps.count > max_removal && (cps.count - bad_cps.count) >= min_cps && iterations <= max_iterations
172
- x_std *= 1.1
173
- y_std *= 1.1
174
- iterations += 1
175
- bad_cps = cps.select { |cp| (cp.px - average_x).abs >= x_std || (cp.py - average_y).abs >= y_std }
176
- end
177
-
178
- logger.info "found #{bad_cps.count} outliers"
179
- bad_control_points << bad_cps if bad_cps.count <= max_removal
180
- end
181
- bad_control_points.flatten!
182
- end
183
-
184
- def clean_control_points_neighborhoods(options = {})
185
- calculate_neighborhoods(amount_ratio: 1.0)
186
- control_points = (@horizontal_pairs.map { |pair| pair[:control_points_within_a_std].map { |n| n[:neighborhood_within_std] } } +
187
- @vertical_pairs.map { |pair| pair[:control_points_within_a_std].map { |n| n[:neighborhood_within_std] } }).flatten.uniq { |cp| cp.raw }
188
- bad_control_points = @control_points.reject { |cp| control_points.map(&:raw).include?(cp.raw) }
18
+ def clean_control_points
19
+ Pair.calculate_neighborhoods(self)
20
+ control_points_to_keep = Pair.good_control_points_to_keep
21
+ bad_control_points = control_points.reject { |cp| control_points_to_keep.map(&:raw).include?(cp.raw) }
22
+ control_point_ratio = bad_control_points.count.to_f / control_points.count
23
+ 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
24
+ control_point_pair_ratio = Pair.without_enough_control_points(ignore_connected: true).count.to_f / Pair.all.count
25
+ 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
26
+ bad_control_points
189
27
  end
190
28
 
191
29
  def fix_unconnected_image_pairs
192
- horizontal_control_points, vertical_control_points = *control_points.partition { |cp| cp.conn_type == :horizontal }
193
- control_points_of_pair = horizontal_control_points.group_by { |cp| [cp.n1, cp.n2] }.sort_by { |_, members| members.count }.last.last
194
- logger.debug "found horizontal pair #{control_points_of_pair.first.n1} <> #{control_points_of_pair.first.n2} with #{control_points_of_pair.count} connections"
195
- average_distance, distance_std = *calculate_average_and_std(name: :distance, values: control_points_of_pair.map(&:pdist), logger: logger)
196
- horizontal_control_points_of_pair = control_points_of_pair.select { |cp| (cp.pdist - average_distance).abs < distance_std }
197
- logger.info "removed #{control_points_of_pair.count - horizontal_control_points_of_pair.count} outliers"
198
- # For logging
199
- calculate_average_and_std(name: :distance, values: horizontal_control_points_of_pair.map(&:pdist), logger: logger)
200
-
201
- control_points_of_pair = vertical_control_points.group_by { |cp| [cp.n1, cp.n2] }.sort_by { |_, members| members.count }.last.last
202
- logger.debug "found vertical pair #{control_points_of_pair.first.n1} <> #{control_points_of_pair.first.n2} with #{control_points_of_pair.count} connections"
203
- average_distance, distance_std = *calculate_average_and_std(name: :distance, values: control_points_of_pair.map(&:pdist), logger: logger)
204
- vertical_control_points_of_pair = control_points_of_pair.select { |cp| (cp.pdist - average_distance).abs < distance_std }
205
- logger.info "removed #{control_points_of_pair.count - vertical_control_points_of_pair.count} outliers"
206
- # For logging
207
- calculate_average_and_std(name: :distance, values: vertical_control_points_of_pair.map(&:pdist), logger: logger)
208
-
209
30
  logger.info 'finding unconnected image pairs'
210
- unconnected_image_pairs = find_unconnected_image_pairs
211
31
 
212
- logger.info unconnected_image_pairs.map { |i| { type: i[:type], pair: i[:pair].map(&:id) } }
32
+ Pair.calculate_neighborhoods(self)
33
+ Pair.calculate_neighborhood_groups
213
34
 
214
- logger.info 'finding control points with unrealistic distances (<1)'
215
- bad_control_points = control_points.select { |cp| cp.pdist <= 1.0 }
216
- logger.info 'adding pairs that have do not have enough control points (<3)'
217
- changing_control_points_pairs = control_points.group_by { |cp| [cp.n1, cp.n2] }.select { |_, cps| cps.count < 3 }
218
- changed_pairs = []
219
-
220
- logger.info 'writing new control points'
221
- control_point_lines_started = false
222
- @lines = @input.each_line.map do |line|
223
- cp = ControlPoint.parse_line(line)
224
- if cp.nil?
225
- # Control point lines ended
226
- if control_point_lines_started
227
- control_point_lines_started = false
228
- unconnected_image_pairs.map do |pair|
229
- if pair[:type] == :horizontal
230
- control_point = horizontal_control_points_of_pair.first
231
- else
232
- control_point = vertical_control_points_of_pair.first
233
- end
234
-
235
- x_diff = control_point.x2 - control_point.x1
236
- y_diff = control_point.y2 - control_point.y1
237
-
238
- x1 = x_diff <= 0 ? -x_diff + 15 : 0
239
- y1 = y_diff <= 0 ? -y_diff + 15 : 0
240
-
241
- control_point[:n] = pair[:pair].first.id
242
- control_point[:N] = pair[:pair].last.id
243
- control_point[:x] = x1
244
- control_point[:X] = x1 + x_diff
245
- control_point[:y] = y1
246
- control_point[:Y] = y1 + y_diff
247
-
248
- logger.debug "adding control points connecting #{control_point.n1} <> #{control_point.n2}"
249
- i = images.first
250
- 3.times.map do
251
- if control_point.conn_type == :horizontal
252
- control_point[:x] += 5
253
- control_point[:X] += 5
254
- control_point[:y] += i.h * 0.25
255
- control_point[:Y] += i.h * 0.25
256
- else
257
- control_point[:x] += i.w * 0.25
258
- control_point[:X] += i.w * 0.25
259
- control_point[:y] += 5
260
- control_point[:Y] += 5
261
- end
262
- control_point.to_s
263
- end.join
264
- end + [line]
265
- else
266
- next line
267
- end
268
- else
269
- control_point_lines_started = true
270
- bad_control_point = bad_control_points.find { |cp| cp.raw == line }
271
- changing_control_point_pair = changing_control_points_pairs.find { |_, cps| cps.find { |cp| cp.raw == line } }
272
-
273
- if bad_control_point
274
- if bad_control_point.conn_type == :horizontal
275
- control_point = horizontal_control_points_of_pair.first
276
- else
277
- control_point = vertical_control_points_of_pair.first
278
- end
279
-
280
- x_diff = control_point.x2 - control_point.x1
281
- y_diff = control_point.y2 - control_point.y1
282
-
283
- x1 = x_diff <= 0 ? -x_diff + 15 : 0
284
- y1 = y_diff <= 0 ? -y_diff + 15 : 0
285
-
286
- control_point[:n] = bad_control_point[:n]
287
- control_point[:N] = bad_control_point[:N]
288
- control_point[:x] = x1
289
- control_point[:X] = x1 + x_diff
290
- control_point[:y] = y1
291
- control_point[:Y] = y1 + y_diff
292
-
293
- logger.debug "replacing unrealistic control point connecting #{control_point.n1} <> #{control_point.n2}"
294
- i = images.first
295
- 3.times.map do
296
- if control_point.conn_type == :horizontal
297
- control_point[:x] += 5
298
- control_point[:X] += 5
299
- control_point[:y] += i.h * 0.25
300
- control_point[:Y] += i.h * 0.25
301
- else
302
- control_point[:x] += i.w * 0.25
303
- control_point[:X] += i.w * 0.25
304
- control_point[:y] += 5
305
- control_point[:Y] += 5
306
- end
307
- control_point.to_s
308
- end.join
309
- elsif changing_control_point_pair && !changed_pairs.include?(changing_control_point_pair.first)
310
- changed_pairs << changing_control_point_pair.first
311
- bad_control_point = changing_control_point_pair.last.first
312
- if bad_control_point.conn_type == :horizontal
313
- control_point = horizontal_control_points_of_pair.first
314
- else
315
- control_point = vertical_control_points_of_pair.first
316
- end
317
-
318
- x_diff = control_point.x2 - control_point.x1
319
- y_diff = control_point.y2 - control_point.y1
320
-
321
- x1 = x_diff <= 0 ? -x_diff + 15 : 0
322
- y1 = y_diff <= 0 ? -y_diff + 15 : 0
323
-
324
- control_point[:n] = bad_control_point[:n]
325
- control_point[:N] = bad_control_point[:N]
326
- control_point[:x] = x1
327
- control_point[:X] = x1 + x_diff
328
- control_point[:y] = y1
329
- control_point[:Y] = y1 + y_diff
330
-
331
- logger.debug "adding control points connecting #{control_point.n1} <> #{control_point.n2}"
332
- i = images.first
333
- 3.times.map do
334
- if control_point.conn_type == :horizontal
335
- control_point[:x] += 5
336
- control_point[:X] += 5
337
- control_point[:y] += i.h * 0.25
338
- control_point[:Y] += i.h * 0.25
339
- else
340
- control_point[:x] += i.w * 0.25
341
- control_point[:X] += i.w * 0.25
342
- control_point[:y] += 5
343
- control_point[:Y] += 5
344
- end
345
- control_point.to_s
346
- end.join
347
- else
348
- next line
349
- end
350
- end
351
- end.compact.flatten
352
- end
353
-
354
- def fix_unconnected_image_pairs_neighborhoods
355
- calculate_neighborhoods(amount_ratio: 1.0)
356
- calculate_neighborhood_groups(name: :horizontal, pairs: @horizontal_pairs)
357
- calculate_neighborhood_groups(name: :vertical, pairs: @vertical_pairs)
358
-
359
- logger.info 'finding unconnected image pairs'
360
- unconnected_image_pairs = find_unconnected_image_pairs
361
- logger.info unconnected_image_pairs.map { |i| { type: i[:type], pair: i[:pair].map(&:id) } }
35
+ unconnected_image_pairs = Pair.unconnected
36
+ logger.debug unconnected_image_pairs.map { |i| { type: i.type, pair: i.pair.map(&:id) } }
362
37
 
363
38
  logger.info 'finding control points with unrealistic distances (<1)'
364
39
  bad_control_points = control_points.select { |cp| cp.pdist <= 1.0 }
365
40
  logger.info 'adding pairs that have do not have enough control points (<3)'
366
- changing_control_points_pairs = control_points.group_by { |cp| [cp.n1, cp.n2] }.select { |_, cps| cps.count < 3 }
41
+ changing_control_points_pairs = Pair.without_enough_control_points
367
42
  changed_pairs = []
368
43
 
369
44
  logger.info 'writing new control points'
@@ -382,14 +57,14 @@ module Panomosity
382
57
  end
383
58
  else
384
59
  control_point_lines_started = true
385
- bad_control_point = bad_control_points.find { |cp| cp.raw == line }
386
- changing_control_point_pair = changing_control_points_pairs.find { |_, cps| cps.find { |cp| cp.raw == line } }
60
+ bad_control_point = bad_control_points.find { |c| c.raw == line }
61
+ changing_control_point_pair = changing_control_points_pairs.find { |pair| pair.control_points.find { |c| c.raw == line } }
387
62
 
388
63
  if bad_control_point
389
64
  generate_control_points(bad_control_point: bad_control_point, message: 'replacing unrealistic control point connecting')
390
- elsif changing_control_point_pair && !changed_pairs.include?(changing_control_point_pair.first)
391
- changed_pairs << changing_control_point_pair.first
392
- bad_control_point = changing_control_point_pair.last.first
65
+ elsif changing_control_point_pair && !changed_pairs.include?(changing_control_point_pair.to_s)
66
+ changed_pairs << changing_control_point_pair.to_s
67
+ bad_control_point = changing_control_point_pair.control_points.first
393
68
  generate_control_points(bad_control_point: bad_control_point, message: 'adding control points connecting')
394
69
  else
395
70
  next line
@@ -398,56 +73,26 @@ module Panomosity
398
73
  end.compact.flatten
399
74
  end
400
75
 
401
- def find_unconnected_image_pairs
402
- ds = images.map(&:d).uniq.sort
403
- es = images.map(&:e).uniq.sort
404
-
405
- unconnected_image_pairs = []
406
- # horizontal connection checking
407
- es.each do |e|
408
- ds.each_with_index do |d, index|
409
- next if index == (ds.count - 1)
410
- image_1 = images.find { |i| i.e == e && i.d == d }
411
- image_2 = images.find { |i| i.e == e && i.d == ds[index+1] }
412
- connected = control_points.any? { |cp| (cp.n1 == image_1.id && cp.n2 == image_2.id) || (cp.n1 == image_2.id && cp.n2 == image_1.id) }
413
- unconnected_image_pairs << { type: :horizontal, pair: [image_1, image_2].sort_by(&:id) } unless connected
414
- end
415
- end
416
-
417
- # vertical connection checking
418
- ds.each do |d|
419
- es.each_with_index do |e, index|
420
- next if index == (es.count - 1)
421
- image_1 = images.find { |i| i.d == d && i.e == e }
422
- image_2 = images.find { |i| i.d == d && i.e == es[index+1] }
423
- connected = control_points.any? { |cp| (cp.n1 == image_1.id && cp.n2 == image_2.id) || (cp.n1 == image_2.id && cp.n2 == image_1.id) }
424
- unconnected_image_pairs << { type: :vertical, pair: [image_1, image_2].sort_by(&:id) } unless connected
425
- end
426
- end
427
-
428
- unconnected_image_pairs
429
- end
430
-
431
76
  def generate_control_points(pair: nil, bad_control_point: nil, message: '')
432
77
  if pair
433
- if pair[:type] == :horizontal
434
- group = @horizontal_neighborhoods_group.first
78
+ if pair.horizontal?
79
+ group = NeighborhoodGroup.horizontal.first
435
80
  else
436
- group = @vertical_neighborhoods_group.first
81
+ group = NeighborhoodGroup.vertical.first
437
82
  end
438
83
  else
439
84
  if bad_control_point.conn_type == :horizontal
440
- group = @horizontal_neighborhoods_group.first
85
+ group = NeighborhoodGroup.horizontal.first
441
86
  else
442
- group = @vertical_neighborhoods_group.first
87
+ group = NeighborhoodGroup.vertical.first
443
88
  end
444
89
  end
445
90
 
446
- control_point = ControlPoint.new(group[:neighborhood][:cp].attributes)
91
+ control_point = ControlPoint.new(group.center.center.attributes)
447
92
 
448
93
  if pair
449
- control_point[:n] = pair[:pair].first.id
450
- control_point[:N] = pair[:pair].last.id
94
+ control_point[:n] = pair.first_image.id
95
+ control_point[:N] = pair.last_image.id
451
96
  else
452
97
  control_point[:n] = bad_control_point[:n]
453
98
  control_point[:N] = bad_control_point[:N]
@@ -456,8 +101,8 @@ module Panomosity
456
101
  image_1 = images.find { |i| i.id == control_point[:n] }
457
102
  image_2 = images.find { |i| i.id == control_point[:N] }
458
103
 
459
- x_diff = group[:x_avg] + (image_2.d - image_1.d)
460
- y_diff = group[:y_avg] + (image_2.e - image_1.e)
104
+ x_diff = group.x_avg + (image_2.d - image_1.d)
105
+ y_diff = group.y_avg + (image_2.e - image_1.e)
461
106
 
462
107
  x1 = x_diff <= 0 ? -x_diff + 15 : 0
463
108
  y1 = y_diff <= 0 ? -y_diff + 15 : 0
@@ -467,22 +112,25 @@ module Panomosity
467
112
  control_point[:y] = y1
468
113
  control_point[:Y] = y1 + y_diff
469
114
 
470
- logger.debug "#{message} #{control_point.n1} <> #{control_point.n2}"
115
+ logger.info "#{message} #{control_point.n1} <> #{control_point.n2}"
471
116
  i = images.first
472
117
  3.times.map do
473
118
  if control_point.conn_type == :horizontal
474
- control_point[:x] += 5
475
- control_point[:X] += 5
476
119
  control_point[:y] += i.h * 0.25
477
120
  control_point[:Y] += i.h * 0.25
478
121
  else
479
122
  control_point[:x] += i.w * 0.25
480
123
  control_point[:X] += i.w * 0.25
481
- control_point[:y] += 5
482
- control_point[:Y] += 5
483
124
  end
484
125
  control_point.to_s
485
126
  end.join
486
127
  end
128
+
129
+ def get_neigborhood_info
130
+ Pair.calculate_neighborhoods(self)
131
+ Pair.calculate_neighborhood_groups
132
+ Pair.info
133
+ NeighborhoodGroup.info
134
+ end
487
135
  end
488
136
  end
@@ -17,7 +17,8 @@ module Panomosity
17
17
  fix_unconnected_image_pairs
18
18
  generate_border_line_control_points
19
19
  get_columns_and_rows
20
- get_detailed_control_point_info
20
+ get_control_point_info
21
+ get_neigborhood_info
21
22
  merge_image_parameters
22
23
  nona_grid
23
24
  optimize
@@ -36,17 +37,13 @@ module Panomosity
36
37
  @output_file = File.new(@output, 'w') if @output
37
38
  @csv_file = File.new(@csv, 'r').read if @csv
38
39
  @compare_file = File.new(@compare, 'r').read if @compare
39
- @logger = Logger.new(STDOUT)
40
+ @logger = Panomosity.logger
40
41
 
41
- if options[:verbose]
42
+ if options[:verbose] || options[:very_verbose]
42
43
  @logger.level = Logger::DEBUG
43
44
  else
44
45
  @logger.level = Logger::INFO
45
46
  end
46
-
47
- @logger.formatter = proc do |severity, datetime, progname, msg|
48
- "[#{datetime}][#{severity}] #{msg}\n"
49
- end
50
47
  end
51
48
 
52
49
  def run(command)
@@ -92,8 +89,8 @@ module Panomosity
92
89
  def clean_control_points
93
90
  logger.info 'cleaning control points'
94
91
  # Since this is very exact, having many outliers in control points distances will cause errors
95
- panorama = Panorama.new(@input_file, logger)
96
- bad_control_points = panorama.clean_control_points_neighborhoods(@options)
92
+ panorama = Panorama.new(@input_file, @options)
93
+ bad_control_points = panorama.clean_control_points
97
94
 
98
95
  logger.info "removing #{bad_control_points.count} control points"
99
96
  @lines = @input_file.each_line.map do |line|
@@ -252,8 +249,8 @@ module Panomosity
252
249
 
253
250
  def fix_unconnected_image_pairs
254
251
  logger.info 'fixing unconnected image pairs'
255
- panorama = Panorama.new(@input_file, logger)
256
- @lines = panorama.fix_unconnected_image_pairs_neighborhoods
252
+ panorama = Panorama.new(@input_file)
253
+ @lines = panorama.fix_unconnected_image_pairs
257
254
  save_file
258
255
  end
259
256
 
@@ -388,7 +385,7 @@ module Panomosity
388
385
  puts "#{columns},#{rows}"
389
386
  end
390
387
 
391
- def get_detailed_control_point_info
388
+ def get_control_point_info
392
389
  logger.info 'getting detailed control point info'
393
390
  images = Image.parse(@input_file)
394
391
  panorama_variable = PanoramaVariable.parse(@input_file).first
@@ -400,6 +397,12 @@ module Panomosity
400
397
  end
401
398
  end
402
399
 
400
+ def get_neigborhood_info
401
+ logger.info 'getting detailed neighborhood info'
402
+ panorama = Panorama.new(@input_file, @options)
403
+ panorama.get_neigborhood_info
404
+ end
405
+
403
406
  def merge_image_parameters
404
407
  logger.info 'merging image parameters'
405
408
  control_points = ControlPoint.parse(@compare_file)
@@ -463,7 +466,7 @@ module Panomosity
463
466
 
464
467
  def optimize
465
468
  logger.info 'optimizing'
466
- panorama = Panorama.new(@input_file, logger)
469
+ panorama = Panorama.new(@input_file)
467
470
  optimizer = Optimizer.new(panorama)
468
471
  optimizer.run
469
472
 
@@ -1,15 +1,24 @@
1
1
  module Panomosity
2
2
  module Utils
3
- def calculate_average_and_std(name: 'value', values: [], logger: nil)
4
- average_value = values.reduce(:+).to_f / values.count
5
- logger.debug "average #{name}: #{average_value}" if logger
3
+ def calculate_average_and_std(name: 'value', values: [], logger: nil, ignore_empty: false)
4
+ return [0, 0] if ignore_empty && values.empty?
5
+ average_value = calculate_average(name: name, values: values, logger: logger, ignore_empty: ignore_empty)
6
+
6
7
  if values.count == 1
7
8
  value_std = 0.0
8
9
  else
9
10
  value_std = Math.sqrt(values.map { |v| (v - average_value) ** 2 }.reduce(:+) / (values.count - 1))
10
11
  end
11
12
  logger.debug "#{name} std: #{value_std}" if logger
13
+
12
14
  [average_value, value_std]
13
15
  end
16
+
17
+ def calculate_average(name: 'value', values: [], logger: nil, ignore_empty: false)
18
+ return 0 if ignore_empty && values.empty?
19
+ average_value = values.reduce(:+).to_f / values.count
20
+ logger.debug "average #{name}: #{average_value}" if logger
21
+ average_value
22
+ end
14
23
  end
15
24
  end
@@ -1,3 +1,3 @@
1
1
  module Panomosity
2
- VERSION = '0.1.17'
2
+ VERSION = '0.1.19'
3
3
  end
data/lib/panomosity.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  require 'panomosity/control_point'
2
2
  require 'panomosity/image'
3
+ require 'panomosity/neighborhood'
4
+ require 'panomosity/neighborhood_group'
3
5
  require 'panomosity/optimisation_variable'
4
6
  require 'panomosity/optimizer'
7
+ require 'panomosity/pair'
5
8
  require 'panomosity/panorama'
6
9
  require 'panomosity/panorama_variable'
7
10
  require 'panomosity/runner'
@@ -55,6 +58,10 @@ module Panomosity
55
58
  options[:verbose] = v
56
59
  end
57
60
 
61
+ parser.on('-v', '--vverbose', 'Run very verbosely') do |v|
62
+ options[:very_verbose] = v
63
+ end
64
+
58
65
  parser.on('-h', '--help', 'Display this screen') do
59
66
  puts parser
60
67
  exit
@@ -66,4 +73,15 @@ module Panomosity
66
73
  runner = Runner.new(options)
67
74
  runner.run(ARGV[0])
68
75
  end
76
+
77
+ def self.logger
78
+ if @logger.nil?
79
+ @logger = Logger.new(STDOUT)
80
+ @logger.level = Logger::DEBUG
81
+ @logger.formatter = proc do |severity, datetime, progname, msg|
82
+ "[#{datetime}][#{severity}] #{msg}\n"
83
+ end
84
+ end
85
+ @logger
86
+ end
69
87
  end
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.17
4
+ version: 0.1.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oliver Garcia
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-11-05 00:00:00.000000000 Z
11
+ date: 2018-11-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -76,8 +76,11 @@ files:
76
76
  - lib/panomosity.rb
77
77
  - lib/panomosity/control_point.rb
78
78
  - lib/panomosity/image.rb
79
+ - lib/panomosity/neighborhood.rb
80
+ - lib/panomosity/neighborhood_group.rb
79
81
  - lib/panomosity/optimisation_variable.rb
80
82
  - lib/panomosity/optimizer.rb
83
+ - lib/panomosity/pair.rb
81
84
  - lib/panomosity/panorama.rb
82
85
  - lib/panomosity/panorama_variable.rb
83
86
  - lib/panomosity/runner.rb