panomosity 0.1.36 → 0.1.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed12e60dac2b5b41da899dadf401e2c51eaa498508fd78a6d0280d3bb262208f
4
- data.tar.gz: 511594cc4b28962db894a9ad399a5e8b81f90808ff1a1ba32de4fab6938a4760
3
+ metadata.gz: 43cc742ee7f3a7e491f8e2ffe3a2f47096c649949e4c9a7cc144c7a2baea819c
4
+ data.tar.gz: 2754ed973a428206f5d2fa4a2327a77fa43a5fedb7151be499f7aaa029d0ca50
5
5
  SHA512:
6
- metadata.gz: ab3adea770a98b961b048e456dc8a1b88f69cdd11503f4fa4bef201c436d357b1021d0a7b8e16bd48202669f0b0785955be780312b08dcceb224d2bd9f4847b2
7
- data.tar.gz: 29ed19c8aabeb5d6c2d9ca784dc7dea1c7f3f7540537735a72bf5e21af55fde8a296ad48cec08b0c0aa36b6d28c74ff0f653ac42d8456d5e9d35b6d379c50972
6
+ metadata.gz: b1a7250d3b3c71680547decb6062086889c5dc60b03ba32a0bff7331264696fb435131fb9239a8b9c37e0aec0e90e6e5dcc56156eb2b7424ec7013cbe364928d
7
+ data.tar.gz: f430213bf4698399eae2607930ba443c529da150df03034f2e3100e68a45d43d18d228a8e64fc74f63561518a89e26db04b653d9a0e751de70a38c059f887c38
@@ -1,31 +1,38 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- panomosity (0.1.36)
4
+ panomosity (0.1.39)
5
+ write_xlsx
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
8
9
  specs:
9
10
  coderay (1.1.2)
10
- diff-lcs (1.3)
11
+ diff-lcs (1.4.4)
11
12
  method_source (0.9.0)
12
13
  pry (0.11.3)
13
14
  coderay (~> 1.1.0)
14
15
  method_source (~> 0.9.0)
15
16
  rake (10.5.0)
16
- rspec (3.8.0)
17
- rspec-core (~> 3.8.0)
18
- rspec-expectations (~> 3.8.0)
19
- rspec-mocks (~> 3.8.0)
20
- rspec-core (3.8.0)
21
- rspec-support (~> 3.8.0)
22
- rspec-expectations (3.8.1)
17
+ rspec (3.9.0)
18
+ rspec-core (~> 3.9.0)
19
+ rspec-expectations (~> 3.9.0)
20
+ rspec-mocks (~> 3.9.0)
21
+ rspec-core (3.9.2)
22
+ rspec-support (~> 3.9.3)
23
+ rspec-expectations (3.9.2)
23
24
  diff-lcs (>= 1.2.0, < 2.0)
24
- rspec-support (~> 3.8.0)
25
- rspec-mocks (3.8.0)
25
+ rspec-support (~> 3.9.0)
26
+ rspec-mocks (3.9.1)
26
27
  diff-lcs (>= 1.2.0, < 2.0)
27
- rspec-support (~> 3.8.0)
28
- rspec-support (3.8.0)
28
+ rspec-support (~> 3.9.0)
29
+ rspec-support (3.9.3)
30
+ rubyzip (2.3.0)
31
+ write_xlsx (0.85.9)
32
+ rubyzip (>= 1.0.0)
33
+ zip-zip
34
+ zip-zip (0.3)
35
+ rubyzip (>= 1.0.0)
29
36
 
30
37
  PLATFORMS
31
38
  ruby
@@ -38,4 +45,4 @@ DEPENDENCIES
38
45
  rspec (~> 3.0)
39
46
 
40
47
  BUNDLED WITH
41
- 1.16.1
48
+ 1.17.3
@@ -1,5 +1,7 @@
1
1
  require 'panomosity/control_point'
2
+ require 'panomosity/generalized_neighborhood'
2
3
  require 'panomosity/image'
4
+ require 'panomosity/measure'
3
5
  require 'panomosity/neighborhood'
4
6
  require 'panomosity/neighborhood_group'
5
7
  require 'panomosity/optimisation_variable'
@@ -9,10 +11,12 @@ require 'panomosity/panorama'
9
11
  require 'panomosity/panorama_variable'
10
12
  require 'panomosity/runner'
11
13
  require 'panomosity/utils'
14
+ require 'panomosity/errors'
12
15
  require 'panomosity/version'
13
16
  require 'pathname'
14
17
  require 'fileutils'
15
18
  require 'optparse'
19
+ require 'json'
16
20
 
17
21
  module Panomosity
18
22
  def self.parse(arguments)
@@ -79,6 +83,33 @@ module Panomosity
79
83
  exit
80
84
  end
81
85
 
86
+ parser.on('--regional-distance-similarities-count COUNT', Integer, 'Set the minimum amount of regional control point counts for determining similar neighborhoods (default: 3)') do |count|
87
+ options[:regional_distance_similarities_count] = count
88
+ end
89
+
90
+ parser.on('--max-reduction-attempts COUNT', Integer, 'Set the max reduction attempts when removing neighborhood outliers (default: 2)') do |count|
91
+ options[:max_reduction_attempts] = count
92
+ end
93
+
94
+ desc = <<~DESC
95
+ Set distances to use when determining neighborhood region size in pairs
96
+ Use JSON e.g. '{"x1": 150, "x2": 30}'
97
+ Defaults:
98
+ Vertical pair is x is 10% of image width and y is 100px
99
+ Horizontal pair is x is 100px and y is 10% of image height
100
+ DESC
101
+ parser.on('--distances [DISTANCE_JSON]', desc) do |distances|
102
+ options[:distances] = JSON.parse(distances) rescue nil
103
+ end
104
+
105
+ parser.on('--distances-horizontal [DISTANCE_JSON]', 'Same as above but only affects horizontal image pairs') do |distances|
106
+ options[:regional_distance_similarities_count] = JSON.parse(distances) rescue nil
107
+ end
108
+
109
+ parser.on('--distances-vertical [DISTANCE_JSON]', 'Same as above but only affects vertical image pairs') do |distances|
110
+ options[:regional_distance_similarities_count] = JSON.parse(distances) rescue nil
111
+ end
112
+
82
113
  parser.on('-h', '--help', 'Display this screen') do
83
114
  puts parser
84
115
  exit
@@ -196,8 +196,8 @@ module Panomosity
196
196
  "#{to_s.sub(/\n/, '')} dist #{dist.round(4)} pixel_dist #{px.round(4)},#{py.round(4)},#{pdist.round(4)} pixel_r_dist #{prx.round(4)},#{pry.round(4)},#{prdist.round(4)} conn_type #{conn_type}"
197
197
  end
198
198
 
199
- def attributes
200
- @attributes
199
+ def attributes(raw: false)
200
+ @attributes.keep_if { |k, _| raw || !%i(raw i1 i2).include?(k) }
201
201
  end
202
202
  end
203
203
  end
@@ -0,0 +1,3 @@
1
+ module Panomosity
2
+ class NoSimilarNeighborhoodsError < StandardError; end
3
+ end
@@ -0,0 +1,278 @@
1
+ require 'panomosity/utils'
2
+
3
+ module Panomosity
4
+ class GeneralizedNeighborhood
5
+ include Panomosity::Utils
6
+
7
+ CONTROL_POINT_ATTRIBUTES = [:dist_avg, :dist_std, :x_avg, :x_std, :y_avg, :y_std]
8
+ ATTRIBUTES = [:center, :scope, :control_points, :count] + CONTROL_POINT_ATTRIBUTES
9
+ DEFAULT_DISTANCE = 100
10
+
11
+ attr_reader :measure, :options
12
+ attr_accessor *ATTRIBUTES
13
+
14
+ class << self
15
+ include Panomosity::Utils
16
+
17
+ attr_accessor :options, :neighborhoods, :horizontal_similar_neighborhoods, :vertical_similar_neighborhoods, :horizontal_neighborhoods_by_similar_neighborhood, :vertical_neighborhoods_by_similar_neighborhood
18
+
19
+ def logger
20
+ @logger ||= Panomosity.logger
21
+ end
22
+
23
+ def attributes
24
+ { name: name }
25
+ end
26
+
27
+ def horizontal
28
+ horizontal_neighborhoods_by_similar_neighborhood
29
+ end
30
+
31
+ def vertical
32
+ vertical_neighborhoods_by_similar_neighborhood
33
+ end
34
+
35
+ def calculate_all(panorama:, options: {})
36
+ @neighborhoods = []
37
+ @options = options
38
+
39
+ Pair.create_pairs_from_panorama(panorama)
40
+ calculate_from_pairs
41
+ calculate_from_neighborhoods(type: :horizontal)
42
+ calculate_from_neighborhoods(type: :vertical)
43
+
44
+ @neighborhoods
45
+ end
46
+
47
+ def calculate_from_pairs
48
+ logger.debug 'calculating neighborhoods from pairs'
49
+ Pair.all.each do |pair|
50
+ control_points = pair.control_points.select(&:not_generated?)
51
+ control_points.each do |control_point|
52
+ base_params = { center: control_point, scope: pair, options: options }
53
+ position_params = { measure: { type: :position } }
54
+ neighborhood = calculate_neighborhood(**base_params.merge(position_params))
55
+ pair.generalized_neighborhoods << neighborhood
56
+ distance_params = { measure: { type: :distance, distances: { x1: neighborhood.dist_std } } }
57
+ distance_neighborhood = calculate_neighborhood(**base_params.merge(distance_params))
58
+ neighborhood.reference = distance_neighborhood
59
+ pair.generalized_neighborhoods << distance_neighborhood
60
+ end
61
+ end
62
+ end
63
+
64
+ def calculate_from_neighborhoods(type:)
65
+ count = options[:regional_distance_similarities_count] || 3
66
+ attempts = options[:max_reduction_attempts] || 2
67
+
68
+ # calculates similar neighborhoods based on the regional control point distances by pair
69
+ calculate_similar_neighborhoods(type: type, count: count)
70
+ if neighborhoods.empty?
71
+ logger.warn 'total neighborhoods came up empty, neighborhood default count to 2'
72
+ calculate_similar_neighborhoods(type: type, count: 2)
73
+ raise 'still could not find neighborhoods' if neighborhoods.empty?
74
+ end
75
+
76
+ std_outlier_reduction(type: type, max_reduction_attempts: attempts)
77
+ calculate_neighborhoods_by_similar_neighborhood(type: type)
78
+ end
79
+
80
+ def calculate_neighborhood(center:, scope:, options:, measure: {})
81
+ neighborhood = new(center: center, scope: scope, options: options)
82
+ neighborhood.update_measure(measure)
83
+ @neighborhoods << neighborhood.calculate
84
+ neighborhood
85
+ end
86
+
87
+ def similar_neighborhoods(type: :horizontal)
88
+ type == :horizontal ? @horizontal_similar_neighborhoods : @vertical_similar_neighborhoods
89
+ end
90
+
91
+ def similar_neighborhoods!(type: :horizontal)
92
+ neighborhoods = similar_neighborhoods(type: type)
93
+
94
+ if neighborhoods.nil? || neighborhoods.empty?
95
+ error = "No similar #{type} neighborhoods found"
96
+ raise NoSimilarNeighborhoodsError, error
97
+ else
98
+ neighborhoods
99
+ end
100
+ end
101
+
102
+ def neighborhoods_by_similar_neighborhood(type: :horizontal)
103
+ type == :horizontal ? @horizontal_neighborhoods_by_similar_neighborhood : @vertical_neighborhoods_by_similar_neighborhood
104
+ end
105
+
106
+ def calculate_similar_neighborhoods(type: :horizontal, count: 3)
107
+ similar_neighborhoods = neighborhoods.select(&:measure_position?).select(&:"#{type}?").select do |neighborhood|
108
+ neighborhood.scope.similar_neighborhoods << neighborhood if neighborhood.reference.count >= count
109
+ end
110
+ self.send(:"#{type}_similar_neighborhoods=", similar_neighborhoods)
111
+ end
112
+
113
+ def std_outlier_reduction(type: :horizontal, max_reduction_attempts: 2, reduction_attempts: 0)
114
+ return if reduction_attempts >= max_reduction_attempts
115
+
116
+ logger.debug "twice reducing #{type} neighborhood std outliers"
117
+
118
+ neighborhoods = similar_neighborhoods!(type: type)
119
+ std_dist_of_neighborhoods = neighborhoods.map(&:dist_std)
120
+ avg, std = *calculate_average_and_std(values: std_dist_of_neighborhoods)
121
+
122
+ similar_neighborhoods!(type: type).select! { |n| (avg - n.dist_std).abs <= std }
123
+ std_outlier_reduction(type: type, max_reduction_attempts: max_reduction_attempts, reduction_attempts: reduction_attempts + 1)
124
+ end
125
+
126
+ def calculate_neighborhoods_by_similar_neighborhood(type: :horizontal)
127
+ instance_variable_set("@#{type}_neighborhoods_by_similar_neighborhood", [])
128
+ similar_neighborhoods(type: type).each do |neighborhood|
129
+ base_params = { center: neighborhood, scope: self, options: options }
130
+ distance_params = { measure: { type: :distance, distances: { x1: neighborhood.dist_std } } }
131
+ neighborhoods_by_similar_neighborhood(type: type) << calculate_neighborhood(**base_params.merge(distance_params))
132
+ end
133
+
134
+ neighborhoods_by_similar_neighborhood(type: type).sort_by! { |n| -n.count }
135
+ neighborhoods_by_similar_neighborhood(type: type).max_by(5) { |n| n.count }.each do |n|
136
+ logger.debug "#{n.dist_avg} #{n.dist_std} #{n.count} x#{n.x_avg} y#{n.y_avg}"
137
+ end
138
+ end
139
+ end
140
+
141
+ def initialize(center:, scope:, options: {})
142
+ @center = center
143
+ @scope = scope
144
+ @options = options
145
+ @measure = Measure.new
146
+ end
147
+
148
+ def id
149
+ @id
150
+ end
151
+
152
+ def id=(id)
153
+ @id = id
154
+ end
155
+
156
+ def reference
157
+ @reference
158
+ end
159
+
160
+ def reference=(reference)
161
+ @reference = reference
162
+ end
163
+
164
+ def pair_scope?
165
+ scope.class.name == 'Panomosity::Pair'
166
+ end
167
+
168
+ def neighborhood_scope?
169
+ scope.class.name == self.class.name
170
+ end
171
+
172
+ def measure_position?
173
+ measure.position?
174
+ end
175
+
176
+ def measure_distance?
177
+ measure.distance?
178
+ end
179
+
180
+ def horizontal?
181
+ pair_scope? ? scope.horizontal? : center.horizontal?
182
+ end
183
+
184
+ def vertical?
185
+ pair_scope? ? scope.vertical? : center.vertical?
186
+ end
187
+
188
+ def type
189
+ horizontal? ? :horizontal : :vertical
190
+ end
191
+
192
+ def update_measure(params)
193
+ measure.update(params)
194
+ set_distance_defaults
195
+ set_measure_defaults
196
+ end
197
+
198
+ def distances_from_options(type: :horizontal)
199
+ if type == :both
200
+ options.fetch(:distances, {})
201
+ else
202
+ options[:distances]&.fetch(type, {}) || {}
203
+ end
204
+ end
205
+
206
+ def set_distance_defaults
207
+ return unless measure_position?
208
+
209
+ measure.update_distances(distances_from_options(type: :both))
210
+
211
+ if scope.horizontal?
212
+ measure.update_distances(x2: (scope.first_image.h * 0.1).round)
213
+ measure.update_distances(distances_from_options(type: :horizontal))
214
+ else
215
+ measure.update_distances(x1: (scope.first_image.w * 0.1).round)
216
+ measure.update_distances(distances_from_options(type: :vertical))
217
+ end
218
+
219
+ measure.distances[:x1] ||= DEFAULT_DISTANCE
220
+ measure.distances[:x2] ||= DEFAULT_DISTANCE
221
+ end
222
+
223
+ def set_measure_defaults
224
+ center_values = if measure_position?
225
+ { x1: center.x1, x2: center.y1 }
226
+ elsif pair_scope?
227
+ { x1: center.pdist }
228
+ else
229
+ { x1: center.dist_avg }
230
+ end
231
+
232
+ measure.update(center: center_values)
233
+ end
234
+
235
+ def calculate
236
+ if pair_scope?
237
+ elements = scope.control_points.select(&:not_generated?)
238
+
239
+ @control_points = if measure_position?
240
+ elements.select { |cp| measure.includes?(cp.x1, cp.y1) }
241
+ else
242
+ elements.select { |cp| measure.includes?(cp.pdist) }
243
+ end
244
+ else
245
+ @neighborhoods = scope.similar_neighborhoods(type: center.type).select { |n| measure.includes?(n.dist_avg) }
246
+ distance_neighborhoods = @neighborhoods.map(&:reference)
247
+ @control_points = distance_neighborhoods.map(&:control_points).flatten.uniq(&:raw)
248
+ end
249
+
250
+ @dist_avg, @dist_std = *calculate_average_and_std(values: control_points.map(&:pdist), ignore_empty: true)
251
+ @x_avg, @x_std = *calculate_average_and_std(values: control_points.map(&:px), ignore_empty: true)
252
+ @y_avg, @y_std = *calculate_average_and_std(values: control_points.map(&:py), ignore_empty: true)
253
+ @count = control_points.count
254
+
255
+ self
256
+ end
257
+
258
+ def attributes
259
+ attributes = CONTROL_POINT_ATTRIBUTES.reduce({}) { |h, k| h.merge!({ k => self.send(k) }) }
260
+ measure_attributes = measure.attributes.reduce({}) do |hash, (key, value)|
261
+ hash["measure_#{key}"] = value
262
+ hash
263
+ end
264
+ if pair_scope?
265
+ center_id = center[:id]
266
+ scope_id = [scope.control_points.first.n1, scope.control_points.first.n2]
267
+ scope_name = 'pair'
268
+ else
269
+ center_id = center.id
270
+ scope_id = nil
271
+ scope_name = 'neighborhood'
272
+ end
273
+ attributes.merge!(measure_attributes)
274
+ attributes.merge!(id: id, center: center_id, scope_id: scope_id, scope_name: scope_name, type: type, control_points: control_points.map{|c| c[:id]})
275
+ attributes
276
+ end
277
+ end
278
+ end
@@ -1,4 +1,4 @@
1
- # Exmaple of an image line
1
+ # Example of an image line
2
2
  # i w2448 h3264 f0 v=0 Ra=0 Rb=0 Rc=0 Rd=0 Re=0 Eev6.433 Er1 Eb1 r0 p0 y0 TrX0.88957152 TrY0.79560269 TrZ1 Tpy0 Tpp0 j0 a=0 b=0 c=0 d=0 e=0 g0 t0 Va=0 Vb=0 Vc=0 Vd=0 Vx=0 Vy=0 Vm5 n"WZ8ppTx9PtcxASB3hbeeuS6Z"\n
3
3
 
4
4
  module Panomosity
@@ -167,5 +167,11 @@ module Panomosity
167
167
  return unless name =~ /c(\d+)_r(\d+)\.jpg/
168
168
  name.scan(/c(\d+)_r(\d+)\.jpg/).flatten.map(&:to_i).last
169
169
  end
170
+
171
+ def attributes
172
+ attributes = @attributes.keep_if { |k, _| !%i(raw).include?(k) }
173
+ attributes.merge!(column: column || 0, row: row || 0)
174
+ attributes
175
+ end
170
176
  end
171
177
  end
@@ -0,0 +1,56 @@
1
+ require 'panomosity/utils'
2
+
3
+ module Panomosity
4
+ class Measure
5
+ include Panomosity::Utils
6
+
7
+ ATTRIBUTES = [:type, :center, :distances]
8
+ DEFAULT_DISTANCE = 100
9
+
10
+ attr_reader :options
11
+ attr_accessor *ATTRIBUTES
12
+
13
+ def initialize(attributes = {})
14
+ @attributes = attributes
15
+ @attributes[:center] ||= {}
16
+ @attributes[:distances] ||= {}
17
+ end
18
+
19
+ def position?
20
+ type == :position
21
+ end
22
+
23
+ def distance?
24
+ type == :distance
25
+ end
26
+
27
+ def type
28
+ @attributes[:type]
29
+ end
30
+
31
+ def center
32
+ @attributes[:center]
33
+ end
34
+
35
+ def distances
36
+ @attributes[:distances]
37
+ end
38
+
39
+ def update(attributes = {})
40
+
41
+ @attributes.merge!(attributes)
42
+ end
43
+
44
+ def update_distances(values = {})
45
+ @attributes[:distances].merge!(values)
46
+ end
47
+
48
+ def includes?(*values)
49
+ center.values.zip(values, distances.values).all? { |center, value, distance| (center - value).abs <= distance }
50
+ end
51
+
52
+ def attributes
53
+ { type: type, center: center.values, distances: distances.values }
54
+ end
55
+ end
56
+ end
@@ -59,12 +59,12 @@ module Panomosity
59
59
  end
60
60
 
61
61
  def to_s
62
- line_values = @@attributes.map { |attribute| "#{attribute}#{self.send(attribute)}" }
63
- "v #{line_values.join(' ')}\n"
62
+ line_values = @@attributes.map { |attribute| "#{attribute}#{self.send(attribute)}" if self.send(attribute) }
63
+ "v #{line_values.compact.join(' ')}\n"
64
64
  end
65
65
 
66
66
  def attributes
67
- @attributes.keep_if { |k,_| !%i(raw).include?(k) }
67
+ @attributes.keep_if { |k, _| !%i(raw).include?(k) }
68
68
  end
69
69
  end
70
70
  end
@@ -30,17 +30,14 @@ module Panomosity
30
30
  logger.info "applying custom values of xh_avg: #{xh_avg}, yh_avg: #{yh_avg}, xv_avg: #{xv_avg}, yv_avg: #{yv_avg}"
31
31
  end
32
32
 
33
- unless xh_avg && yh_avg && xv_avg && yv_avg
34
- Pair.calculate_neighborhoods(panorama)
35
- Pair.calculate_neighborhood_groups
36
- end
33
+ panorama.calculate_neighborhoods unless xh_avg && yh_avg && xv_avg && yv_avg
37
34
 
38
35
  ds = images.map(&:d).uniq.sort
39
36
  es = images.map(&:e).uniq.sort
40
37
 
41
38
  # get the average error for the best neighborhood group
42
- x_avg = xh_avg || NeighborhoodGroup.horizontal.first.rx_avg
43
- y_avg = yv_avg || NeighborhoodGroup.vertical.first.ry_avg
39
+ x_avg = xh_avg || GeneralizedNeighborhood.horizontal.first.x_avg
40
+ y_avg = yv_avg || GeneralizedNeighborhood.vertical.first.y_avg
44
41
 
45
42
  # start horizontally
46
43
  d_map = {}
@@ -57,8 +54,8 @@ module Panomosity
57
54
  logger.debug "created e_map #{e_map}"
58
55
 
59
56
  # add in the other offset
60
- x_avg = xv_avg || NeighborhoodGroup.vertical.first.rx_avg
61
- y_avg = yh_avg || NeighborhoodGroup.horizontal.first.ry_avg
57
+ x_avg = xv_avg || GeneralizedNeighborhood.vertical.first.x_avg
58
+ y_avg = yh_avg || GeneralizedNeighborhood.horizontal.first.y_avg
62
59
 
63
60
  de_map = {}
64
61
  d_map.each_with_index do |(dk,dv),di|
@@ -142,10 +139,9 @@ module Panomosity
142
139
  end
143
140
 
144
141
  def calculate_average_distance
145
- Pair.calculate_neighborhoods(panorama)
146
- Pair.calculate_neighborhood_groups
147
- horizontal_distances = NeighborhoodGroup.horizontal[0..4].map(&:prdist_avg)
148
- vertical_distances = NeighborhoodGroup.vertical[0..4].map(&:prdist_avg)
142
+ panorama.calculate_neighborhoods
143
+ horizontal_distances = GeneralizedNeighborhood.horizontal[0..4].map(&:dist_avg)
144
+ vertical_distances = GeneralizedNeighborhood.vertical[0..4].map(&:dist_avg)
149
145
  calculate_average(values: horizontal_distances + vertical_distances)
150
146
  end
151
147
 
@@ -161,9 +157,8 @@ module Panomosity
161
157
  images.each do |image|
162
158
  image.r = 0.0
163
159
  end
164
- Pair.calculate_neighborhoods(panorama)
165
- Pair.calculate_neighborhood_groups
166
- cps = NeighborhoodGroup.horizontal.first.control_points
160
+ panorama.calculate_neighborhoods
161
+ cps = GeneralizedNeighborhood.horizontal.first.control_points
167
162
  avg, std = *calculate_average_and_std(values: cps.map(&:roll))
168
163
  # 0.1 degrees of std
169
164
  while std > 0.1
@@ -5,7 +5,7 @@ module Panomosity
5
5
  include Panomosity::Utils
6
6
  extend Panomosity::Utils
7
7
 
8
- attr_accessor :pair, :control_points, :neighborhoods, :type
8
+ attr_accessor :pair, :control_points, :neighborhoods, :type, :generalized_neighborhoods, :similar_neighborhoods
9
9
 
10
10
  class << self
11
11
  attr_accessor :panorama, :logger
@@ -22,8 +22,8 @@ module Panomosity
22
22
  @pairs
23
23
  end
24
24
 
25
- def good_control_points_to_keep(count: 3)
26
- @pairs.map { |pair| pair.good_control_points_to_keep(count: count) }.flatten.uniq(&:raw)
25
+ def select_control_points_with_regional_distance_similarities
26
+ @pairs.map(&:select_control_points_with_regional_distance_similarities).flatten.uniq(&:raw)
27
27
  end
28
28
 
29
29
  def unconnected
@@ -44,6 +44,9 @@ module Panomosity
44
44
  rows = images.map(&:row).uniq.sort
45
45
 
46
46
  @pairs = []
47
+ @horizontal_pairs = []
48
+ @vertical_pairs = []
49
+
47
50
  # horizontal pair creation
48
51
  rows.each do |row|
49
52
  columns.each do |column|
@@ -52,7 +55,9 @@ module Panomosity
52
55
  image_2 = images.find { |i| i.row == row && i.column == column.next }
53
56
  next if @panorama.calibration? && (image_1.nil? || image_2.nil?)
54
57
  control_points = @panorama.control_points.select { |cp| [cp.n1, cp.n2].sort == [image_1.id, image_2.id].sort }
55
- @pairs << Pair.new([image_1, image_2].sort_by(&:id), control_points: control_points, type: :horizontal)
58
+ pair = Pair.new([image_1, image_2].sort_by(&:id), control_points: control_points, type: :horizontal)
59
+ @pairs << pair
60
+ @horizontal_pairs << pair
56
61
  end
57
62
  end
58
63
 
@@ -64,7 +69,9 @@ module Panomosity
64
69
  image_2 = images.find { |i| i.column == column && i.row == row.next }
65
70
  next if @panorama.calibration? && (image_1.nil? || image_2.nil?)
66
71
  control_points = @panorama.control_points.select { |cp| [cp.n1, cp.n2].sort == [image_1.id, image_2.id].sort }
67
- @pairs << Pair.new([image_1, image_2].sort_by(&:id), control_points: control_points, type: :vertical)
72
+ pair = Pair.new([image_1, image_2].sort_by(&:id), control_points: control_points, type: :vertical)
73
+ @pairs << pair
74
+ @vertical_pairs << pair
68
75
  end
69
76
  end
70
77
  end
@@ -150,6 +157,8 @@ module Panomosity
150
157
  @pair = pair
151
158
  @control_points = control_points
152
159
  @neighborhoods = []
160
+ @generalized_neighborhoods = []
161
+ @similar_neighborhoods = []
153
162
  @type = type
154
163
  end
155
164
 
@@ -157,6 +166,10 @@ module Panomosity
157
166
  pair.map(&:id).to_s.gsub(' ', '')
158
167
  end
159
168
 
169
+ def ==(other)
170
+ to_s == other.to_s
171
+ end
172
+
160
173
  def info
161
174
  "#{to_s}(#{type}) image_1 d,e: #{pair.first.d},#{pair.first.e} | image_2 d,e: #{pair.last.d},#{pair.last.e}"
162
175
  end
@@ -218,6 +231,27 @@ module Panomosity
218
231
  end
219
232
  end
220
233
 
234
+ # Distance neighborhoods
235
+ def similar_control_points
236
+ @similar_control_points ||= similar_neighborhoods.map(&:reference).map(&:control_points).flatten.uniq(&:raw)
237
+ end
238
+
239
+ def select_control_points_with_regional_distance_similarities
240
+ # Keep all our control points if we have less than 10
241
+ if control_points.count >= 10
242
+ ratio = similar_control_points.count.to_f / control_points.count
243
+ if ratio < 0.2
244
+ Panomosity.logger.warn "#{to_s} keeping less than 20% (#{(ratio * 100).round(4)}%) of #{control_points.count} control points. Reverting and keeping all control points"
245
+ control_points
246
+ else
247
+ similar_control_points
248
+ end
249
+ else
250
+ Panomosity.logger.debug "Skipping pair #{to_s} since it has fewer than 10 control points"
251
+ control_points
252
+ end
253
+ end
254
+
221
255
  def best_neighborhood
222
256
  @best_neighborhood ||= @neighborhoods.max_by { |n| n.control_points.count }
223
257
  end
@@ -225,5 +259,17 @@ module Panomosity
225
259
  def control_points_of_best_neighborhood
226
260
  best_neighborhood ? best_neighborhood.control_points : []
227
261
  end
262
+
263
+ def attributes
264
+ x_avg, x_std = *calculate_average_and_std(values: control_points.map(&:px), ignore_empty: true)
265
+ y_avg, y_std = *calculate_average_and_std(values: control_points.map(&:py), ignore_empty: true)
266
+ dist_avg, dist_std = *calculate_average_and_std(values: control_points.map(&:pdist), ignore_empty: true)
267
+ i1 = control_points.first.n1
268
+ i2 = control_points.first.n2
269
+ {
270
+ id: [i1, i2], n: i1, N: i2, count: control_points.count, type: type,
271
+ x_avg: x_avg, x_std: x_std, y_avg: y_avg, y_std: y_std, dist_avg: dist_avg, dist_std: dist_std
272
+ }
273
+ end
228
274
  end
229
275
  end
@@ -18,22 +18,25 @@ module Panomosity
18
18
  @logger = Panomosity.logger
19
19
  end
20
20
 
21
+ def calculate_neighborhoods
22
+ GeneralizedNeighborhood.calculate_all(panorama: self, options: options)
23
+ end
24
+
21
25
  def clean_control_points
22
- if calibration?
23
- Pair.calculate_neighborhoods(self, distance: 30)
24
- else
25
- Pair.calculate_neighborhoods(self, distance: 100)
26
- end
27
- control_points_to_keep = Pair.good_control_points_to_keep(count: 2)
26
+ options.merge!(distances: { x1: 30, x2: 30 }) if calibration? && !options[:distances].nil?
27
+ options.merge!(regional_distance_similarities_count: 2) unless options[:regional_distance_similarities_count]
28
+ calculate_neighborhoods
29
+
30
+ control_points_to_keep = Pair.select_control_points_with_regional_distance_similarities
28
31
  bad_control_points = control_points.reject { |cp| control_points_to_keep.map(&:raw).include?(cp.raw) }
29
32
  # far_control_points = control_points.select { |cp| cp.prdist > 50 }
30
33
  control_points_to_clean = bad_control_points.uniq(&:raw)
31
34
 
32
35
  # log warnings
33
36
  control_point_ratio = control_points_to_clean.count.to_f / control_points.count
34
- logger.warn "Removing more than 30% (#{(control_point_ratio*100).round(4)}%) of control points. May potentially cause issues." if control_point_ratio >= 0.3
37
+ logger.warn "Removing more than 30% (#{(control_point_ratio * 100).round(4)}%) of control points. May potentially cause issues." if control_point_ratio >= 0.3
35
38
  control_point_pair_ratio = Pair.without_enough_control_points(ignore_connected: true).count.to_f / Pair.all.count
36
- logger.warn "More than 50% (#{(control_point_pair_ratio*100).round(4)}%) of pairs have fewer than 3 control points. May potentially cause issues." if control_point_pair_ratio >= 0.5
39
+ logger.warn "More than 50% (#{(control_point_pair_ratio * 100).round(4)}%) of pairs have fewer than 3 control points. May potentially cause issues." if control_point_pair_ratio >= 0.5
37
40
 
38
41
  control_points_to_clean
39
42
  end
@@ -41,12 +44,8 @@ module Panomosity
41
44
  def fix_unconnected_image_pairs
42
45
  logger.info 'finding unconnected image pairs'
43
46
 
44
- if calibration?
45
- Pair.calculate_neighborhoods(self, distance: 30)
46
- else
47
- Pair.calculate_neighborhoods(self, distance: 100)
48
- end
49
- Pair.calculate_neighborhood_groups
47
+ options.merge!(distances: { x1: 30, x2: 30 }) if calibration? && !options[:distances].nil?
48
+ calculate_neighborhoods
50
49
 
51
50
  unconnected_image_pairs = Pair.unconnected
52
51
  logger.debug unconnected_image_pairs.map { |i| { type: i.type, pair: i.pair.map(&:id) } }
@@ -92,19 +91,19 @@ module Panomosity
92
91
  def generate_control_points(pair: nil, bad_control_point: nil, message: '')
93
92
  if pair
94
93
  if pair.horizontal?
95
- group = NeighborhoodGroup.horizontal.first
94
+ group = GeneralizedNeighborhood.horizontal.first
96
95
  else
97
- group = NeighborhoodGroup.vertical.first
96
+ group = GeneralizedNeighborhood.vertical.first
98
97
  end
99
98
  else
100
99
  if bad_control_point.conn_type == :horizontal
101
- group = NeighborhoodGroup.horizontal.first
100
+ group = GeneralizedNeighborhood.horizontal.first
102
101
  else
103
- group = NeighborhoodGroup.vertical.first
102
+ group = GeneralizedNeighborhood.vertical.first
104
103
  end
105
104
  end
106
105
 
107
- control_point = ControlPoint.new(group.center.center.attributes)
106
+ control_point = ControlPoint.new(group.center.center.attributes(raw: true))
108
107
 
109
108
  if pair
110
109
  control_point[:n] = pair.first_image.id
@@ -153,8 +152,7 @@ module Panomosity
153
152
  end
154
153
 
155
154
  def diagnose
156
- Pair.calculate_neighborhoods(self)
157
- Pair.calculate_neighborhood_groups
155
+ calculate_neighborhoods
158
156
 
159
157
  recommendations = []
160
158
  messages = []
@@ -184,7 +182,7 @@ module Panomosity
184
182
  end
185
183
 
186
184
  # neighborhood group tests
187
- group_count = NeighborhoodGroup.horizontal.count
185
+ group_count = GeneralizedNeighborhood.horizontal.count
188
186
  if group_count < 5
189
187
  message = <<~MESSAGE
190
188
  Total number of horizontal neighborhood groups is #{group_count} which is very low.
@@ -194,7 +192,7 @@ module Panomosity
194
192
  messages << message
195
193
  end
196
194
 
197
- group_std_avg = calculate_average(values: NeighborhoodGroup.horizontal[0..4].map(&:prdist_std))
195
+ group_std_avg = calculate_average(values: GeneralizedNeighborhood.horizontal[0..4].map(&:dist_std))
198
196
  if group_std_avg > 1.0
199
197
  message = <<~MESSAGE
200
198
  The standard deviation of distances in the top 5 horizontal neighborhood groups is #{group_std_avg} which is high.
@@ -207,7 +205,7 @@ module Panomosity
207
205
  messages << message
208
206
  end
209
207
 
210
- group_control_points = NeighborhoodGroup.horizontal.first.control_points.count
208
+ group_control_points = GeneralizedNeighborhood.horizontal.first.control_points.count
211
209
  total_control_points = Pair.horizontal.map(&:control_points).flatten.uniq(&:raw).count
212
210
  group_control_point_ratio = group_control_points.to_f / total_control_points
213
211
  if group_control_point_ratio < 0.2
@@ -222,7 +220,7 @@ module Panomosity
222
220
  recommendations << 'horizontal'
223
221
  end
224
222
 
225
- group_count = NeighborhoodGroup.vertical.count
223
+ group_count = GeneralizedNeighborhood.vertical.count
226
224
  if group_count < 5
227
225
  message = <<~MESSAGE
228
226
  Total number of vertical neighborhood groups is #{group_count} which is very low.
@@ -232,7 +230,7 @@ module Panomosity
232
230
  messages << message
233
231
  end
234
232
 
235
- group_std_avg = calculate_average(values: NeighborhoodGroup.vertical[0..4].map(&:prdist_std))
233
+ group_std_avg = calculate_average(values: GeneralizedNeighborhood.vertical[0..4].map(&:dist_std))
236
234
  if group_std_avg > 1.0
237
235
  message = <<~MESSAGE
238
236
  The standard deviation of distances in the top 5 vertical neighborhood groups is #{group_std_avg} which is high.
@@ -245,7 +243,7 @@ module Panomosity
245
243
  messages << message
246
244
  end
247
245
 
248
- group_control_points = NeighborhoodGroup.vertical.first.control_points.count
246
+ group_control_points = GeneralizedNeighborhood.vertical.first.control_points.count
249
247
  total_control_points = Pair.vertical.map(&:control_points).flatten.uniq(&:raw).count
250
248
  group_control_point_ratio = group_control_points.to_f / total_control_points
251
249
  if group_control_point_ratio < 0.2
@@ -274,11 +272,24 @@ module Panomosity
274
272
  delta_d: delta_d,
275
273
  delta_e: delta_e,
276
274
  roll: roll,
277
- horizontal: NeighborhoodGroup.horizontal.first.serialize,
278
- vertical: NeighborhoodGroup.vertical.first.serialize
275
+ horizontal: GeneralizedNeighborhood.horizontal.first.attributes,
276
+ vertical: GeneralizedNeighborhood.vertical.first.attributes
279
277
  }
280
278
  }
279
+ rescue => error
280
+ message = "Got error #{error.message} when calculating neighborhoods. Recommending fallback"
281
281
 
282
+ logger.error message
283
+ error.backtrace.each { |line| logger.error line }
284
+
285
+ recommendations = %w(horizontal vertical)
286
+
287
+ diagnostic_report = {
288
+ messages: messages,
289
+ recommendations: recommendations,
290
+ data: {}
291
+ }
292
+ ensure
282
293
  File.open('diagnostic_report.json', 'w+') { |f| f.puts diagnostic_report.to_json }
283
294
 
284
295
  if recommendations.empty?
@@ -301,17 +312,13 @@ module Panomosity
301
312
  calibration_report = JSON.parse(File.read(filename))
302
313
 
303
314
  if @options[:report_type] == 'position'
304
- if calibration?
305
- Pair.calculate_neighborhoods(self, distance: 30)
306
- else
307
- Pair.calculate_neighborhoods(self, distance: 100)
308
- end
309
- Pair.calculate_neighborhood_groups
315
+ options.merge!(distances: { x1: 30, x2: 30 }) if calibration? && !options[:distances].nil?
316
+ calculate_neighborhoods
310
317
 
311
- xh_avg = NeighborhoodGroup.horizontal.first.x_avg
312
- yh_avg = NeighborhoodGroup.horizontal.first.y_avg
313
- xv_avg = NeighborhoodGroup.vertical.first.x_avg
314
- yv_avg = NeighborhoodGroup.vertical.first.y_avg
318
+ xh_avg = GeneralizedNeighborhood.horizontal.first.x_avg
319
+ yh_avg = GeneralizedNeighborhood.horizontal.first.y_avg
320
+ xv_avg = GeneralizedNeighborhood.vertical.first.x_avg
321
+ yv_avg = GeneralizedNeighborhood.vertical.first.y_avg
315
322
  calibration_report['position'] = {
316
323
  xh_avg: xh_avg,
317
324
  yh_avg: yh_avg,
@@ -327,7 +334,39 @@ module Panomosity
327
334
  end
328
335
 
329
336
  def calibration?
330
- !!@input.split(/\n/).find { |line| '#panomosity calibration true' }
337
+ !!@input.split(/\n/).find { |line| line == '#panomosity calibration true' }
338
+ end
339
+
340
+ def save_file(filename)
341
+ logger.info "saving file #{filename}"
342
+
343
+ lines = @input.each_line.map do |line|
344
+ objects = [images, variable, control_points, optimisation_variables].flatten
345
+ object = objects.find { |object| object.raw == line }
346
+ object&.to_s || line
347
+ end.compact
348
+
349
+ File.open(filename, 'w') { |f| lines.each { |line| f.puts line } }
350
+ end
351
+
352
+ def attributes
353
+ calculate_neighborhoods
354
+ control_points = self.control_points.dup
355
+ control_points.each_with_index { |cp, i| cp[:id] = i }
356
+ neighborhoods = GeneralizedNeighborhood.neighborhoods.dup
357
+ neighborhoods.each_with_index { |n, i| n.id = i }
358
+ types = %i(horizontal vertical)
359
+ similar_neighborhoods = types.map { |type| GeneralizedNeighborhood.similar_neighborhoods(type: type) }.flatten
360
+ neighborhoods_by_similar_neighborhood = types.map { |type| GeneralizedNeighborhood.neighborhoods_by_similar_neighborhood(type: type) }.flatten
361
+ {
362
+ images: images.map(&:attributes),
363
+ variable: variable.attributes,
364
+ control_points: control_points.map(&:attributes),
365
+ optimisation_variables: optimisation_variables.map(&:attributes),
366
+ pairs: Pair.all.map(&:attributes),
367
+ similar_neighborhoods: similar_neighborhoods.map(&:attributes),
368
+ neighborhoods_by_similar_neighborhood: neighborhoods_by_similar_neighborhood.map(&:attributes)
369
+ }
331
370
  end
332
371
  end
333
372
  end
@@ -60,5 +60,9 @@ module Panomosity
60
60
  line_values = @@attributes.map { |attribute| "#{attribute}#{self.send(attribute)}" }
61
61
  "p #{line_values.join(' ')}\n"
62
62
  end
63
+
64
+ def attributes
65
+ @attributes.keep_if { |k, _| !%i(raw).include?(k) }
66
+ end
63
67
  end
64
68
  end
@@ -1,14 +1,16 @@
1
1
  require 'logger'
2
2
  require 'json'
3
3
  require 'csv'
4
+ require 'panomosity/xlsx_writer'
4
5
 
5
6
  module Panomosity
6
7
  class Runner
7
8
  include Panomosity::Utils
9
+ include Panomosity::XLSXWriter
8
10
 
9
11
  attr_reader :logger
10
12
 
11
- AVAILABLE_COMMANDS = %w(
13
+ AVAILABLE_COMMANDS = %w[
12
14
  add_calibration_flag
13
15
  apply_calibration_values
14
16
  check_position_changes
@@ -20,12 +22,14 @@ module Panomosity
20
22
  crop_centers
21
23
  diagnose
22
24
  export_control_point_csv
25
+ export_panorama_attributes_to_csv
23
26
  fix_conversion_errors
24
27
  fix_unconnected_image_pairs
25
28
  generate_border_line_control_points
26
29
  get_columns_and_rows
27
30
  get_control_point_info
28
31
  get_neighborhood_info
32
+ get_panorama_attributes
29
33
  import_control_point_csv
30
34
  merge_image_parameters
31
35
  nona_grid
@@ -33,7 +37,7 @@ module Panomosity
33
37
  prepare_for_pr0ntools
34
38
  remove_anchor_variables
35
39
  standardize_roll
36
- )
40
+ ]
37
41
 
38
42
  def initialize(options)
39
43
  @options = options
@@ -226,12 +230,19 @@ module Panomosity
226
230
  logger.debug "cropping to #{percent}%"
227
231
  unless @options[:without_cropping]
228
232
  images = Image.parse(@input_file)
229
- images.each do |image|
233
+
234
+ crop_center_commands = images.map do |image|
230
235
  width_offset = (image.w.to_f * (1 - scale_factor) / 2).round
231
236
  height_offset = (image.h.to_f * (1 - scale_factor) / 2).round
232
237
  logger.debug "cropping #{image.name}"
233
- `convert #{image.name} -crop "#{percent}%x#{percent}%+#{width_offset}+#{height_offset}" #{image.name}`
238
+
239
+ "convert #{image.name} -crop '#{percent}%x#{percent}%+#{width_offset}+#{height_offset}' #{image.name}"
234
240
  end
241
+
242
+ crop_centers_command = crop_center_commands.join("\n")
243
+ parallel_command = "cat <<'EOF' | parallel \n%s\nEOF"
244
+ crop_centers_command = parallel_command % crop_centers_command
245
+ `#{crop_centers_command}`
235
246
  end
236
247
 
237
248
  # Since all images have been cropped, we need to change d,e params to move images based on how much was cropped
@@ -295,10 +306,10 @@ module Panomosity
295
306
  def export_control_point_csv
296
307
  logger.info 'exporting control point csv'
297
308
  panorama = Panorama.new(@input_file, @options)
298
- Pair.calculate_neighborhoods(panorama, distance: 30)
309
+ panorama.calculate_neighborhoods
299
310
 
300
311
  filename = 'control_points.csv'
301
- headers = %w(image_1_name image_2_name image_1_id image_2_id type width height d1 e1 d2 e2 x1 y1 x2 y2 rx ry r)
312
+ headers = %w[image_1_name image_2_name image_1_id image_2_id type width height d1 e1 d2 e2 x1 y1 x2 y2 rx ry r]
302
313
  CSV.open(filename, 'w+') do |csv|
303
314
  csv << headers
304
315
  panorama.control_points.each do |cp|
@@ -307,7 +318,7 @@ module Panomosity
307
318
  cp.i2.d, cp.i2.e, cp.x1, cp.y1, cp.x2, cp.y2, cp.px, cp.py, cp.pdist]
308
319
  end
309
320
  end
310
-
321
+
311
322
  logger.info 'Done. Check for control_points.csv'
312
323
  end
313
324
 
@@ -481,6 +492,78 @@ module Panomosity
481
492
  panorama.get_neighborhood_info
482
493
  end
483
494
 
495
+ def get_panorama_attributes
496
+ # for printing out the JSON
497
+ logger.level = Logger::UNKNOWN
498
+ logger.info 'getting panorama attributes'
499
+ panorama = Panorama.new(@input_file, @options)
500
+ puts panorama.attributes.to_json
501
+ end
502
+
503
+ def export_panorama_attributes_to_csv
504
+ panorama = Panorama.new(@input_file, @options)
505
+ panorama.calculate_neighborhoods
506
+
507
+ logger.info "Making #{Dir.pwd}/#{@input}_attributes Directory"
508
+
509
+ dir = "#{Dir.pwd}/#{@input}_attributes"
510
+ Dir.mkdir(dir) unless File.exists?(dir)
511
+
512
+ logger.info "Done. Moving into #{dir} Directory"
513
+
514
+ Dir.chdir "#{@input}_attributes"
515
+
516
+ logger.info "Making control_points.csv"
517
+
518
+ filename = 'control_points.csv'
519
+ headers = %w[image_1_name image_2_name image_1_id image_2_id type width height d1 e1 d2 e2 x1 y1 x2 y2 rx ry r]
520
+ CSV.open(filename, 'w+') do |csv|
521
+ csv << headers
522
+ panorama.control_points.each do |cp|
523
+ pair = Pair.all.find { |p| p.control_points.include?(cp) }
524
+ csv << [cp.i1.name, cp.i2.name, cp.i1.id, cp.i2.id, pair.type, cp.i1.w, cp.i1.h, cp.i1.d, cp.i1.e,
525
+ cp.i2.d, cp.i2.e, cp.x1, cp.y1, cp.x2, cp.y2, cp.px, cp.py, cp.pdist]
526
+ end
527
+ end
528
+
529
+ logger.info "Done. Check for #{filename}"
530
+
531
+ attributes = panorama.attributes
532
+
533
+ logger.info "Making cp_pairs.csv"
534
+
535
+ filename = 'cp_pairs.csv'
536
+ headers = attributes[:pairs].first.keys
537
+ CSV.open(filename,"w") do |csv|
538
+ csv << headers
539
+ attributes[:pairs].each do |pair|
540
+ csv << pair.values
541
+ end
542
+ end
543
+
544
+ logger.info "Done. Check for #{filename}"
545
+
546
+ logger.info "Making neighborhood_pairs.csv"
547
+
548
+ filename = 'neighborhood_pairs.csv'
549
+ headers = ['data_type', attributes[:similar_neighborhoods].first.keys].flatten
550
+ CSV.open(filename,'w') do |csv|
551
+ csv << headers
552
+ attributes[:similar_neighborhoods].each do |neighborhood|
553
+ csv << ['similar_neighborhoods', neighborhood.values].flatten(1)
554
+ end
555
+ attributes[:neighborhoods_by_similar_neighborhood].each do |neighborhood|
556
+ csv << ['neighborhoods_by_similar_neighborhood', neighborhood.values].flatten(1)
557
+ end
558
+ end
559
+
560
+ logger.info "Done. Check for #{filename}"
561
+
562
+ csv_to_xlsx(['control_points.csv', 'cp_pairs.csv', 'neighborhood_pairs.csv'], 'attributes')
563
+
564
+ Dir.chdir '..'
565
+ end
566
+
484
567
  def import_control_point_csv
485
568
  logger.info 'importing control point csv'
486
569
  panorama = Panorama.new(@input_file, @options)
@@ -565,6 +648,7 @@ module Panomosity
565
648
  save_file
566
649
  logger.debug "running nona #{@output}"
567
650
  output = "nona --save-intermediate-images --intermediate-suffix=intermediate -v -m TIFF_m --seam=blend #{@output} -o #{res}_res_stitch_section_c#{column.to_s.rjust(5, '0')}_"
651
+ # output = "nona-mask #@output -m TIFF_m -o #{res}_res_stitch_section_c#{column.to_s.rjust(5, '0')}_"
568
652
  logger.debug output
569
653
  output
570
654
  end
@@ -601,12 +685,17 @@ module Panomosity
601
685
  logger.info 'preparing for pr0ntools'
602
686
 
603
687
  images = Image.parse(@input_file)
688
+ # ds --> X (cols)
689
+ # es --> Y (rows)
604
690
  ds = images.map(&:d).uniq.sort
605
691
  es = images.map(&:e).uniq.sort
606
692
 
607
- # within two pixels of each other
608
- d_groups = ds.map { |d| ds.select { |n| n.between?(d-2, d+2) }.sort }
609
- e_groups = es.map { |e| es.select { |n| n.between?(e-2, e+2) }.sort }
693
+ # split ds and es into groups within two pixels of each other,
694
+ # required because d/e can vary a few pixels based on system precision
695
+ # each group of d/e represents an acceptable pixel range
696
+ # for images to be considered part of a col/row
697
+ d_groups = ds.map { |d| ds.select { |n| n.between?(d-2, d+2) }.sort }.uniq
698
+ e_groups = es.map { |e| es.select { |n| n.between?(e-2, e+2) }.sort }.uniq
610
699
 
611
700
  fov = images.map(&:v).uniq.find { |v| v != 0.0 }
612
701
  images.each do |i|
@@ -632,7 +721,12 @@ module Panomosity
632
721
  FileUtils.mkdir_p(new_dir)
633
722
 
634
723
  logger.info 'renaming files to format cX_rY.jpg'
635
- images.each { |image| FileUtils.cp(image[:original_n], new_dir + image.n) }
724
+ images.each do |image|
725
+ original_name = image[:original_n]
726
+ new_name = new_dir + image.n
727
+ FileUtils.cp(original_name, new_name)
728
+ logger.debug "copied #{original_name} to #{new_name}"
729
+ end
636
730
 
637
731
  logger.info 'creating scan.json'
638
732
  image_first_col = images.find { |image| image.n == 'c0_r0.jpg' }
@@ -31,4 +31,4 @@ module Panomosity
31
31
  values
32
32
  end
33
33
  end
34
- end
34
+ end
@@ -1,3 +1,3 @@
1
1
  module Panomosity
2
- VERSION = '0.1.36'
2
+ VERSION = '0.1.41'
3
3
  end
@@ -0,0 +1,40 @@
1
+ require 'write_xlsx'
2
+
3
+ module Panomosity
4
+ module XLSXWriter
5
+ SEPERATE_CSV_COLUMNS = /,(?![^\[]*\])/.freeze
6
+
7
+
8
+ def csv_to_xlsx(input_files, output_file)
9
+ input_files = Array(input_files)
10
+
11
+ logger.info "Creating #{output_file}.xlsx"
12
+
13
+ workbook = WriteXLSX.new("#{output_file}.xlsx")
14
+
15
+ input_files.each do |csv|
16
+ logger.info "Creating #{csv} Worksheet"
17
+
18
+ worksheet = workbook.add_worksheet(csv)
19
+
20
+ seperate_csv_columns = /,(?![^\[]*\])/
21
+
22
+ File.open(csv, "r") do |f|
23
+ f.each_line do |rows|
24
+ cells = rows.split(SEPERATE_CSV_COLUMNS)
25
+
26
+ cells.each_with_index do |cell, column|
27
+ row = f.lineno - 1
28
+ data = cell.tr_s('"', '').strip
29
+ worksheet.write(row, column, data)
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ workbook.close
36
+
37
+ logger.info "Done. Check for #{output_file}.xlsx"
38
+ end
39
+ end
40
+ end
@@ -6,7 +6,7 @@ require 'panomosity/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = 'panomosity'
8
8
  spec.version = Panomosity::VERSION
9
- spec.authors = ['Oliver Garcia']
9
+ spec.authors = ['Oliver Garcia', 'Joshua Stowers']
10
10
  spec.email = ['ogarci5@gmail.com']
11
11
 
12
12
  spec.summary = %q{Wrapper for the PTO file parsing needed for PanoTools.}
@@ -24,4 +24,6 @@ Gem::Specification.new do |spec|
24
24
  spec.add_development_dependency 'bundler', '~> 1.16'
25
25
  spec.add_development_dependency 'rake', '~> 10.0'
26
26
  spec.add_development_dependency 'rspec', '~> 3.0'
27
+ spec.add_runtime_dependency 'write_xlsx'
28
+
27
29
  end
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: panomosity
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.36
4
+ version: 0.1.41
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oliver Garcia
8
+ - Joshua Stowers
8
9
  autorequire:
9
10
  bindir: exe
10
11
  cert_chain: []
11
- date: 2019-06-20 00:00:00.000000000 Z
12
+ date: 2020-10-30 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: bundler
@@ -52,6 +53,20 @@ dependencies:
52
53
  - - "~>"
53
54
  - !ruby/object:Gem::Version
54
55
  version: '3.0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: write_xlsx
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
55
70
  description: Custom scripts to help with PTO parsing and different strategies.
56
71
  email:
57
72
  - ogarci5@gmail.com
@@ -77,7 +92,10 @@ files:
77
92
  - exe/panomosity
78
93
  - lib/panomosity.rb
79
94
  - lib/panomosity/control_point.rb
95
+ - lib/panomosity/errors.rb
96
+ - lib/panomosity/generalized_neighborhood.rb
80
97
  - lib/panomosity/image.rb
98
+ - lib/panomosity/measure.rb
81
99
  - lib/panomosity/neighborhood.rb
82
100
  - lib/panomosity/neighborhood_group.rb
83
101
  - lib/panomosity/optimisation_variable.rb
@@ -88,6 +106,7 @@ files:
88
106
  - lib/panomosity/runner.rb
89
107
  - lib/panomosity/utils.rb
90
108
  - lib/panomosity/version.rb
109
+ - lib/panomosity/xlsx_writer.rb
91
110
  - panomosity.gemspec
92
111
  homepage: https://github.com/elevatesystems/panomosity
93
112
  licenses:
@@ -108,8 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
127
  - !ruby/object:Gem::Version
109
128
  version: '0'
110
129
  requirements: []
111
- rubyforge_project:
112
- rubygems_version: 2.7.3
130
+ rubygems_version: 3.0.8
113
131
  signing_key:
114
132
  specification_version: 4
115
133
  summary: Wrapper for the PTO file parsing needed for PanoTools.