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 +4 -4
- data/Gemfile.lock +1 -1
- data/lib/panomosity/neighborhood.rb +43 -0
- data/lib/panomosity/neighborhood_group.rb +109 -0
- data/lib/panomosity/optimizer.rb +27 -43
- data/lib/panomosity/pair.rb +208 -0
- data/lib/panomosity/panorama.rb +41 -393
- data/lib/panomosity/runner.rb +16 -13
- data/lib/panomosity/utils.rb +12 -3
- data/lib/panomosity/version.rb +1 -1
- data/lib/panomosity.rb +18 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d31e6f456b80f0429a224a93939514115dd1f944e4a01c0e4b3992347d9c870
|
4
|
+
data.tar.gz: fc71c2b94cb75a36ed249b728a9382350fc72ac5562c3dd644f31a5289969523
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b2697110bf392a3d7f15371ca57e7835a149c8dd371cc45317559a005b57ba2b8edbc696d7e87e1f12111a325567e066988eba06614cd61b8b9c1a3d9cd4dd8
|
7
|
+
data.tar.gz: b55224d26f9b7f0e45fbcc26478517c39e3c80482dc9f16496b811cc94352bef5dc1e62173abc784ace3dc73c4df0f0adf0cda642897a3df8eb79d10445d8b50
|
data/Gemfile.lock
CHANGED
@@ -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
|
data/lib/panomosity/optimizer.rb
CHANGED
@@ -26,16 +26,17 @@ module Panomosity
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def run_position_optimizer
|
29
|
-
|
30
|
-
|
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
|
-
#
|
37
|
-
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
|
-
|
55
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/panomosity/panorama.rb
CHANGED
@@ -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, :
|
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,
|
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
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
32
|
+
Pair.calculate_neighborhoods(self)
|
33
|
+
Pair.calculate_neighborhood_groups
|
213
34
|
|
214
|
-
|
215
|
-
|
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 =
|
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 { |
|
386
|
-
changing_control_point_pair = changing_control_points_pairs.find { |
|
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.
|
391
|
-
changed_pairs << changing_control_point_pair.
|
392
|
-
bad_control_point = changing_control_point_pair.
|
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
|
434
|
-
group =
|
78
|
+
if pair.horizontal?
|
79
|
+
group = NeighborhoodGroup.horizontal.first
|
435
80
|
else
|
436
|
-
group =
|
81
|
+
group = NeighborhoodGroup.vertical.first
|
437
82
|
end
|
438
83
|
else
|
439
84
|
if bad_control_point.conn_type == :horizontal
|
440
|
-
group =
|
85
|
+
group = NeighborhoodGroup.horizontal.first
|
441
86
|
else
|
442
|
-
group =
|
87
|
+
group = NeighborhoodGroup.vertical.first
|
443
88
|
end
|
444
89
|
end
|
445
90
|
|
446
|
-
control_point = ControlPoint.new(group
|
91
|
+
control_point = ControlPoint.new(group.center.center.attributes)
|
447
92
|
|
448
93
|
if pair
|
449
|
-
control_point[:n] = pair
|
450
|
-
control_point[:N] = pair
|
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
|
460
|
-
y_diff = group
|
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.
|
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
|
data/lib/panomosity/runner.rb
CHANGED
@@ -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
|
-
|
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 =
|
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,
|
96
|
-
bad_control_points = panorama.
|
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
|
256
|
-
@lines = panorama.
|
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
|
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
|
469
|
+
panorama = Panorama.new(@input_file)
|
467
470
|
optimizer = Optimizer.new(panorama)
|
468
471
|
optimizer.run
|
469
472
|
|
data/lib/panomosity/utils.rb
CHANGED
@@ -1,15 +1,24 @@
|
|
1
1
|
module Panomosity
|
2
2
|
module Utils
|
3
|
-
def calculate_average_and_std(name: 'value', values: [], logger: nil)
|
4
|
-
|
5
|
-
|
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
|
data/lib/panomosity/version.rb
CHANGED
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.
|
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-
|
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
|