panomosity 0.1.17 → 0.1.19

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 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