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