panomosity 0.1.35 → 0.1.40

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