panomosity 0.1.35 → 0.1.40

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b65bc97cd45e178084afa204a41355038ed8071d361fde9cd14ef264717c2471
4
- data.tar.gz: '0384cff13bd3d72aa97e09f7efc8b0c40baff294921f9f5c22f3ac2c4c9617a1'
3
+ metadata.gz: 508c47f3caa59487c8e85f4787044dd174b9e43a63fce0ee0ecfc210c88ecef7
4
+ data.tar.gz: d7e9a8a263379d90f984746e0b3f2215943322418c59ce4f9f25012563fbce82
5
5
  SHA512:
6
- metadata.gz: dbc856dc3727226f568e3a24dc28038dca5900a2e2f19227fe118b51bd6de0a414f503f066a91fc93d5ce972bedcdffd917fee75cc7376075af251d5a7960261
7
- data.tar.gz: 2911664ac77d3702b8e43f61238d61abbcc1fb7477e18f6a10ff07780b402137dcf7ac9bae0d12b2630394fbb4044b7a794cb179e665e77fdbca5cc06a890ee6
6
+ metadata.gz: 73a953315be6abba6b26a8371dbf5f0ad131fbae1733b59c12e243d6e31e531a2b9bf3e908b2d5b5d03423ef3006347dd15be55dafcd38c019735e57ea998ad4
7
+ data.tar.gz: 380b7ac01f1306116cbdc2c5982760f2495ca107321a6564bfca266b449e6c631ec8752b40228e12b8ffb8127f8f3305c3451a93a5ba2a084a91f3bd838ad30d
@@ -1,31 +1,38 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- panomosity (0.1.34)
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,13 +1,16 @@
1
1
  require 'logger'
2
2
  require 'json'
3
+ require 'csv'
4
+ require 'panomosity/xlsx_writer'
3
5
 
4
6
  module Panomosity
5
7
  class Runner
6
8
  include Panomosity::Utils
9
+ include Panomosity::XLSXWriter
7
10
 
8
11
  attr_reader :logger
9
12
 
10
- AVAILABLE_COMMANDS = %w(
13
+ AVAILABLE_COMMANDS = %w[
11
14
  add_calibration_flag
12
15
  apply_calibration_values
13
16
  check_position_changes
@@ -18,19 +21,23 @@ module Panomosity
18
21
  create_calibration_report
19
22
  crop_centers
20
23
  diagnose
24
+ export_control_point_csv
25
+ export_panorama_attributes_to_csv
21
26
  fix_conversion_errors
22
27
  fix_unconnected_image_pairs
23
28
  generate_border_line_control_points
24
29
  get_columns_and_rows
25
30
  get_control_point_info
26
31
  get_neighborhood_info
32
+ get_panorama_attributes
33
+ import_control_point_csv
27
34
  merge_image_parameters
28
35
  nona_grid
29
36
  optimize
30
37
  prepare_for_pr0ntools
31
38
  remove_anchor_variables
32
39
  standardize_roll
33
- )
40
+ ]
34
41
 
35
42
  def initialize(options)
36
43
  @options = options
@@ -289,6 +296,25 @@ module Panomosity
289
296
  panorama.diagnose
290
297
  end
291
298
 
299
+ def export_control_point_csv
300
+ logger.info 'exporting control point csv'
301
+ panorama = Panorama.new(@input_file, @options)
302
+ panorama.calculate_neighborhoods
303
+
304
+ filename = 'control_points.csv'
305
+ 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]
306
+ CSV.open(filename, 'w+') do |csv|
307
+ csv << headers
308
+ panorama.control_points.each do |cp|
309
+ pair = Pair.all.find { |p| p.control_points.include?(cp) }
310
+ 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,
311
+ cp.i2.d, cp.i2.e, cp.x1, cp.y1, cp.x2, cp.y2, cp.px, cp.py, cp.pdist]
312
+ end
313
+ end
314
+
315
+ logger.info 'Done. Check for control_points.csv'
316
+ end
317
+
292
318
  def fix_conversion_errors
293
319
  logger.info 'fixing conversion errors'
294
320
  @lines = @input_file.each_line.map do |line|
@@ -459,6 +485,105 @@ module Panomosity
459
485
  panorama.get_neighborhood_info
460
486
  end
461
487
 
488
+ def get_panorama_attributes
489
+ # for printing out the JSON
490
+ logger.level = Logger::UNKNOWN
491
+ logger.info 'getting panorama attributes'
492
+ panorama = Panorama.new(@input_file, @options)
493
+ puts panorama.attributes.to_json
494
+ end
495
+
496
+ def export_panorama_attributes_to_csv
497
+ panorama = Panorama.new(@input_file, @options)
498
+ panorama.calculate_neighborhoods
499
+
500
+ logger.info "Making #{Dir.pwd}/#{@input}_attributes Directory"
501
+
502
+ dir = "#{Dir.pwd}/#{@input}_attributes"
503
+ Dir.mkdir(dir) unless File.exists?(dir)
504
+
505
+ logger.info "Done. Moving into #{dir} Directory"
506
+
507
+ Dir.chdir "#{@input}_attributes"
508
+
509
+ logger.info "Making control_points.csv"
510
+
511
+ filename = 'control_points.csv'
512
+ 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]
513
+ CSV.open(filename, 'w+') do |csv|
514
+ csv << headers
515
+ panorama.control_points.each do |cp|
516
+ pair = Pair.all.find { |p| p.control_points.include?(cp) }
517
+ 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,
518
+ cp.i2.d, cp.i2.e, cp.x1, cp.y1, cp.x2, cp.y2, cp.px, cp.py, cp.pdist]
519
+ end
520
+ end
521
+
522
+ logger.info "Done. Check for #{filename}"
523
+
524
+ attributes = panorama.attributes
525
+
526
+ logger.info "Making cp_pairs.csv"
527
+
528
+ filename = 'cp_pairs.csv'
529
+ headers = attributes[:pairs].first.keys
530
+ CSV.open(filename,"w") do |csv|
531
+ csv << headers
532
+ attributes[:pairs].each do |pair|
533
+ csv << pair.values
534
+ end
535
+ end
536
+
537
+ logger.info "Done. Check for #{filename}"
538
+
539
+ logger.info "Making neighborhood_pairs.csv"
540
+
541
+ filename = 'neighborhood_pairs.csv'
542
+ headers = ['data_type', attributes[:similar_neighborhoods].first.keys].flatten
543
+ CSV.open(filename,'w') do |csv|
544
+ csv << headers
545
+ attributes[:similar_neighborhoods].each do |neighborhood|
546
+ csv << ['similar_neighborhoods', neighborhood.values].flatten(1)
547
+ end
548
+ attributes[:neighborhoods_by_similar_neighborhood].each do |neighborhood|
549
+ csv << ['neighborhoods_by_similar_neighborhood', neighborhood.values].flatten(1)
550
+ end
551
+ end
552
+
553
+ logger.info "Done. Check for #{filename}"
554
+
555
+ csv_to_xlsx(['control_points.csv', 'cp_pairs.csv', 'neighborhood_pairs.csv'], 'attributes')
556
+
557
+ Dir.chdir '..'
558
+ end
559
+
560
+ def import_control_point_csv
561
+ logger.info 'importing control point csv'
562
+ panorama = Panorama.new(@input_file, @options)
563
+
564
+ filename = @csv || 'control_points.csv'
565
+ csv = CSV.read(filename)
566
+ headers = csv.first
567
+ csv = csv[1..(csv.length - 1)]
568
+ data = csv.map { |s| Hash[headers.zip(s)] }
569
+
570
+ @lines = @input_file.each_line.map do |line|
571
+ cp = panorama.control_points.find { |c| c.raw == line }
572
+ if cp
573
+ kept_cp = data.find do |d|
574
+ cp.i1.id == d['image_1_id'].to_i && cp.i2.id == d['image_2_id'].to_i &&
575
+ cp.x1.round(4) == d['x1'].to_f.round(4) && cp.x2.round(4) == d['x2'].to_f.round(4) &&
576
+ cp.y1.round(4) == d['y1'].to_f.round(4) && cp.y2.round(4) == d['y2'].to_f.round(4)
577
+ end
578
+ cp.to_s if kept_cp
579
+ else
580
+ next line
581
+ end
582
+ end.compact
583
+
584
+ save_file
585
+ end
586
+
462
587
  def merge_image_parameters
463
588
  logger.info 'merging image parameters'
464
589
  control_points = ControlPoint.parse(@compare_file)
@@ -552,12 +677,17 @@ module Panomosity
552
677
  logger.info 'preparing for pr0ntools'
553
678
 
554
679
  images = Image.parse(@input_file)
680
+ # ds --> X (cols)
681
+ # es --> Y (rows)
555
682
  ds = images.map(&:d).uniq.sort
556
683
  es = images.map(&:e).uniq.sort
557
684
 
558
- # within two pixels of each other
559
- d_groups = ds.map { |d| ds.select { |n| n.between?(d-2, d+2) }.sort }
560
- e_groups = es.map { |e| es.select { |n| n.between?(e-2, e+2) }.sort }
685
+ # split ds and es into groups within two pixels of each other,
686
+ # required because d/e can vary a few pixels based on system precision
687
+ # each group of d/e represents an acceptable pixel range
688
+ # for images to be considered part of a col/row
689
+ d_groups = ds.map { |d| ds.select { |n| n.between?(d-2, d+2) }.sort }.uniq
690
+ e_groups = es.map { |e| es.select { |n| n.between?(e-2, e+2) }.sort }.uniq
561
691
 
562
692
  fov = images.map(&:v).uniq.find { |v| v != 0.0 }
563
693
  images.each do |i|
@@ -583,7 +713,12 @@ module Panomosity
583
713
  FileUtils.mkdir_p(new_dir)
584
714
 
585
715
  logger.info 'renaming files to format cX_rY.jpg'
586
- images.each { |image| FileUtils.cp(image[:original_n], new_dir + image.n) }
716
+ images.each do |image|
717
+ original_name = image[:original_n]
718
+ new_name = new_dir + image.n
719
+ FileUtils.cp(original_name, new_name)
720
+ logger.debug "copied #{original_name} to #{new_name}"
721
+ end
587
722
 
588
723
  logger.info 'creating scan.json'
589
724
  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.35'
2
+ VERSION = '0.1.40'
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
@@ -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,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: panomosity
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.35
4
+ version: 0.1.40
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oliver Garcia
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-06-13 00:00:00.000000000 Z
11
+ date: 2020-10-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: write_xlsx
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  description: Custom scripts to help with PTO parsing and different strategies.
56
70
  email:
57
71
  - ogarci5@gmail.com
@@ -77,7 +91,10 @@ files:
77
91
  - exe/panomosity
78
92
  - lib/panomosity.rb
79
93
  - lib/panomosity/control_point.rb
94
+ - lib/panomosity/errors.rb
95
+ - lib/panomosity/generalized_neighborhood.rb
80
96
  - lib/panomosity/image.rb
97
+ - lib/panomosity/measure.rb
81
98
  - lib/panomosity/neighborhood.rb
82
99
  - lib/panomosity/neighborhood_group.rb
83
100
  - lib/panomosity/optimisation_variable.rb
@@ -88,6 +105,7 @@ files:
88
105
  - lib/panomosity/runner.rb
89
106
  - lib/panomosity/utils.rb
90
107
  - lib/panomosity/version.rb
108
+ - lib/panomosity/xlsx_writer.rb
91
109
  - panomosity.gemspec
92
110
  homepage: https://github.com/elevatesystems/panomosity
93
111
  licenses:
@@ -108,8 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
126
  - !ruby/object:Gem::Version
109
127
  version: '0'
110
128
  requirements: []
111
- rubyforge_project:
112
- rubygems_version: 2.7.3
129
+ rubygems_version: 3.0.8
113
130
  signing_key:
114
131
  specification_version: 4
115
132
  summary: Wrapper for the PTO file parsing needed for PanoTools.