panomosity 0.1.2 → 0.1.4

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: 55acfc2af5503c105113d6f4b22464b2052915ebc265f64369f28a37733cd811
4
- data.tar.gz: 7edea562a3ecd287ee4ec60bc209636d7494b52be234d2edb106b016b5d749fc
3
+ metadata.gz: 1e32fb89b5812b7f726cf6c322423d4e816aa103bde59bd5deef4bd6fb2279d4
4
+ data.tar.gz: 2aae7a094ad7bccd73af5d6042a799e29cd4ce1bd0e1bdd81d187b24e82b33f5
5
5
  SHA512:
6
- metadata.gz: 227237855e4a0c4e38282837fed553b7add73ffd92d439bbf8a3678196589b0bd91b36c013e415a27e4699fc94f97fbe56fc946b3d5c94486eae1e9edf920ec5
7
- data.tar.gz: 40b14486b54d7efaf753943c1886bb147a2646b6f10e6ea5e75907c575367db27ce3ad22e0a36a06178ee27160a126f4f16b0d623e2cb47a68ae465748438825
6
+ metadata.gz: d7d24392ffec1de2c6fb59b3ed8c4e03a59e91529ecc3a2a7c5d4bcca26383ea2a089dfd0f5e044fe2604c57b802324f6f4859532211b212808ea5a97aadc918
7
+ data.tar.gz: f41fae931324dc129bb4f9b8499f86d0d209dd9bcf9c396b768e988d9ab8abe62eec1032601335da697a7df69ebb9f26ae5c271b1fd040b66a6b3e8d6a021611
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- panomosity (0.1.2)
4
+ panomosity (0.1.3)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env perl
1
+ #!/usr/bin/perl
2
2
 
3
3
  use strict;
4
4
  use warnings;
@@ -4,6 +4,7 @@
4
4
  module Panomosity
5
5
  class ControlPoint
6
6
  @@attributes = %i(n N x y X Y t)
7
+ @@calculated_attributes = %i(dist px py pdist conn_type)
7
8
 
8
9
  def self.parse(pto_file, cp_type: nil, compact: false)
9
10
  @control_points = pto_file.each_line.map do |line|
@@ -28,13 +29,44 @@ module Panomosity
28
29
  else
29
30
  @control_points
30
31
  end
32
+
33
+ @control_points
31
34
  end
32
35
 
33
36
  def self.get_detailed_info(pto_file_path, cp_type: nil)
34
- result = `control_point_info.pl --input #{pto_file_path}`
37
+ exe_dir = File.expand_path('../../exe', File.dirname(__FILE__))
38
+ control_point_info_executable = File.join(exe_dir, 'control_point_info.pl')
39
+ result = `#{control_point_info_executable} --input #{pto_file_path}`
35
40
  parse(result, cp_type: cp_type, compact: true)
36
41
  end
37
42
 
43
+ def self.calculate_distances(images, panorama_variable)
44
+ @control_points.each do |cp|
45
+ image1 = images.find { |i| cp.n1 == i.id }
46
+ image2 = images.find { |i| cp.n2 == i.id }
47
+ point1 = image1.to_cartesian(cp.x1, cp.y1)
48
+ point2 = image2.to_cartesian(cp.x2, cp.y2)
49
+
50
+ angle = Math.acos(point1[0] * point2[0] + point1[1] * point2[1] + point1[2] * point2[2])
51
+ radius = (panorama_variable.w / 2.0) / Math.tan((panorama_variable.v * Math::PI / 180) / 2)
52
+
53
+ distance = angle * radius
54
+ cp.dist = distance
55
+
56
+ # Pixel distance
57
+ x1 = (image1.w / 2.0) - cp.x1 + image1.d
58
+ y1 = (image1.h / 2.0) - cp.y1 + image1.e
59
+ x2 = (image2.w / 2.0) - cp.x2 + image2.d
60
+ y2 = (image2.h / 2.0) - cp.y2 + image2.e
61
+
62
+ pixel_distance = Math.sqrt((x1 - x2)**2 + (y1 - y2)**2)
63
+ cp.px = x1 - x2
64
+ cp.py = y1 - y2
65
+ cp.pdist = pixel_distance
66
+ cp.conn_type = image1.d == image2.d ? :vertical : :horizontal
67
+ end
68
+ end
69
+
38
70
  def self.all
39
71
  @control_points
40
72
  end
@@ -55,11 +87,20 @@ module Panomosity
55
87
  end
56
88
  end
57
89
 
90
+ def self.merge(first_set_control_points, second_set_control_points)
91
+ @control_points = first_set_control_points.map do |cp1|
92
+ similar_control_point = second_set_control_points.find { |cp2| cp1 == cp2 }
93
+ cp1.dist = similar_control_point.dist
94
+ cp1
95
+ end
96
+ end
97
+
58
98
  def initialize(attributes)
59
99
  @attributes = attributes
60
100
  # conform data types
61
101
  @attributes.each do |key, value|
62
102
  next if %i(raw).include?(key)
103
+ next unless value.is_a?(String)
63
104
  if value.respond_to?(:include?) && value.include?('.')
64
105
  @attributes[key] = value.to_f
65
106
  else
@@ -76,7 +117,7 @@ module Panomosity
76
117
  @attributes[key] = value
77
118
  end
78
119
 
79
- (@@attributes + %i(raw dist)).each do |attr|
120
+ (@@attributes + @@calculated_attributes + %i(raw)).each do |attr|
80
121
  define_method(attr) do
81
122
  @attributes[attr]
82
123
  end
@@ -114,5 +155,14 @@ module Panomosity
114
155
  line_values = @@attributes.map { |attribute| "#{attribute}#{self.send(attribute)}" }
115
156
  "c #{line_values.join(' ')}\n"
116
157
  end
158
+
159
+ def ==(o)
160
+ n1 == o.n1 &&
161
+ n2 == o.n2 &&
162
+ x1.floor == o.x1.floor &&
163
+ x2.floor == o.x2.floor &&
164
+ y1.floor == o.y1.floor &&
165
+ y2.floor == o.y2.floor
166
+ end
117
167
  end
118
168
  end
@@ -106,10 +106,10 @@ module Panomosity
106
106
  self[:TrY] * self.class.panosphere * height
107
107
  end
108
108
 
109
- def to_s
109
+ def to_s(options = {})
110
110
  subline_values = (@@attributes - %i(Vm n)).map do |attribute|
111
111
  value = self.send(attribute)
112
- if @@equaled_attributes.include?(attribute)
112
+ if @@equaled_attributes.include?(attribute) && !options[:without_equal_signs]
113
113
  if value == 0.0
114
114
  "#{attribute}=#{value.to_i}"
115
115
  else
@@ -139,5 +139,22 @@ module Panomosity
139
139
  self[:e] = try
140
140
  self
141
141
  end
142
+
143
+ def to_cartesian(x1, y1)
144
+ px = (w / 2.0) - x1 + d
145
+ py = (h / 2.0) - y1 + e
146
+ rad = (w / 2.0) / Math.tan((v * Math::PI / 180) / 2)
147
+ r = self.r * Math::PI / 180
148
+ p = self.p * Math::PI / 180
149
+ y = self.y * Math::PI / 180
150
+
151
+ # Derived from multiplication of standard roll, pitch, and yaw matrices by the point vector (rad, px, py)
152
+ point = [Math.cos(p) * Math.cos(y) * rad - Math.sin(y) * Math.cos(p) * px + Math.sin(p) * py,
153
+ Math.sin(r) * Math.sin(p) * Math.cos(y) * rad + Math.sin(y) * Math.cos(r) * rad - Math.sin(r) * Math.sin(p) * Math.sin(y) * px + Math.cos(r) * Math.cos(y) * px - Math.sin(r) * Math.cos(p) * py,
154
+ -Math.sin(p) * Math.cos(r) * Math.cos(y) * rad + Math.sin(r) * Math.sin(y) * rad + Math.sin(p) * Math.sin(y) * Math.cos(r) * px + Math.sin(r) * Math.cos(y) * px + Math.cos(r) * Math.cos(p) * py]
155
+ magnitude = Math.sqrt(point[0] ** 2 + point[1] ** 2 + point[2] ** 2)
156
+ normalized_point = [point[0] / magnitude, point[1] / magnitude, point[2] / magnitude]
157
+ normalized_point
158
+ end
142
159
  end
143
160
  end
@@ -0,0 +1,64 @@
1
+ module Panomosity
2
+ class PanoramaVariable
3
+ @@attributes = %i(p w h f v n E R)
4
+
5
+ def self.parse(pto_file)
6
+ @panorama_variables = pto_file.each_line.map { |line| parse_line(line) }.compact
7
+ end
8
+
9
+ def self.all
10
+ @panorama_variables
11
+ end
12
+
13
+ def self.parse_line(line)
14
+ parts = line.split(' ')
15
+ if parts.first == 'p'
16
+ data = parts.each_with_object({}) do |part, hash|
17
+ attribute = @@attributes.find { |attr| part[0] == attr.to_s }
18
+ next unless attribute
19
+ hash[attribute] = part.sub(attribute.to_s, '')
20
+ end
21
+
22
+ data[:raw] = line
23
+
24
+ new data
25
+ end
26
+ end
27
+
28
+ def initialize(attributes)
29
+ @attributes = attributes
30
+ # conform data types
31
+ @attributes.each do |key, value|
32
+ next if %i(n raw).include?(key)
33
+ if value.respond_to?(:include?) && value.include?('.')
34
+ @attributes[key] = value.to_f
35
+ else
36
+ @attributes[key] = value.to_i
37
+ end
38
+ end
39
+ end
40
+
41
+ def [](key)
42
+ @attributes[key]
43
+ end
44
+
45
+ def []=(key, value)
46
+ @attributes[key] = value
47
+ end
48
+
49
+ (@@attributes + %i(raw)).each do |attr|
50
+ define_method(attr) do
51
+ @attributes[attr]
52
+ end
53
+
54
+ define_method(:"#{attr}=") do |value|
55
+ @attributes[attr] = value
56
+ end
57
+ end
58
+
59
+ def to_s
60
+ line_values = @@attributes.map { |attribute| "#{attribute}#{self.send(attribute)}" }
61
+ "p #{line_values.join(' ')}\n"
62
+ end
63
+ end
64
+ end
@@ -33,9 +33,9 @@ module Panomosity
33
33
  @logger = Logger.new(STDOUT)
34
34
 
35
35
  if options[:verbose]
36
- @logger.level = Logger::INFO
37
- else
38
36
  @logger.level = Logger::DEBUG
37
+ else
38
+ @logger.level = Logger::INFO
39
39
  end
40
40
 
41
41
  @logger.formatter = proc do |severity, datetime, progname, msg|
@@ -89,7 +89,11 @@ module Panomosity
89
89
  @lines = @input_file.each_line.map do |line|
90
90
  image = images.find { |i| i.raw == line }
91
91
  if image
92
- image.to_s
92
+ if @options[:remove_equal_signs]
93
+ image.to_s(without_equal_signs: true)
94
+ else
95
+ image.to_s
96
+ end
93
97
  else
94
98
  next line
95
99
  end
@@ -154,16 +158,16 @@ module Panomosity
154
158
  # Since all images have been cropped, we need to change d,e params to move images based on how much was cropped
155
159
  # Re-run commands that have been run at this point
156
160
  logger.info 'rerunning commands'
157
- @new_input_file_path = 'project_converted_translation_cropped.pto'
161
+ @new_input = 'project_converted_translation_cropped.pto'
158
162
  `match-n-shift --input #{@csv} -o project_cropped.pto`
159
163
  `pto_var --opt=TrX,TrY project_cropped.pto -o project_pto_var_cropped.pto`
160
- runner = Runner.new(@options.merge(input: 'project_pto_var_cropped.pto', output: @new_input_file_path))
164
+ runner = Runner.new(@options.merge(input: 'project_pto_var_cropped.pto', output: @new_input))
161
165
  runner.run('convert_translation_parameters')
162
- `pano_modify -p 0 --fov=AUTO -o #{@new_input_file_path} #{@new_input_file_path}`
166
+ `pano_modify -p 0 --fov=AUTO -o #{@new_input} #{@new_input}`
163
167
 
164
- logger.info "Read new #{@new_input_file_path}"
168
+ logger.info "Read new #{@new_input}"
165
169
  # Read new input pto file
166
- @input_file = File.new(@new_input_file_path, 'r').read
170
+ @input_file = File.new(@new_input, 'r').read
167
171
  images = Image.parse(@input_file)
168
172
  ds = images.map(&:d).uniq.sort
169
173
  es = images.map(&:e).uniq.sort
@@ -220,7 +224,108 @@ module Panomosity
220
224
  def fix_unconnected_image_pairs
221
225
  logger.info 'fixing unconnected image pairs'
222
226
  images = Image.parse(@input_file)
227
+ panorama_variable = PanoramaVariable.parse(@input_file).first
228
+ ControlPoint.parse(@input_file)
229
+ control_points = ControlPoint.calculate_distances(images, panorama_variable)
230
+
231
+ horizontal_control_points, vertical_control_points = *control_points.partition { |cp| cp.conn_type == :horizontal }
232
+ control_points_of_pair = horizontal_control_points.group_by { |cp| [cp.n1, cp.n2] }.sort_by { |_, members| members.count }.last.last
233
+ logger.debug "found horizontal pair #{control_points_of_pair.first.n1} <> #{control_points_of_pair.first.n2} with #{control_points_of_pair.count} connections"
234
+ average_distance = control_points_of_pair.map(&:pdist).reduce(:+).to_f / control_points_of_pair.count
235
+ logger.debug "average distance #{average_distance}"
236
+ dist_std = Math.sqrt(control_points_of_pair.map { |cp| (cp.pdist - average_distance) ** 2 }.reduce(:+) / (control_points_of_pair.count - 1))
237
+ logger.debug "dist std: #{dist_std}"
238
+ horizontal_control_points_of_pair = control_points_of_pair.select { |cp| (cp.pdist - average_distance).abs < dist_std }
239
+ logger.info "removed #{control_points_of_pair.count - horizontal_control_points_of_pair.count} outliers"
240
+ new_average_distance = horizontal_control_points_of_pair.map(&:pdist).reduce(:+).to_f / horizontal_control_points_of_pair.count
241
+ logger.info "new average #{new_average_distance}"
242
+
243
+ control_points_of_pair = vertical_control_points.group_by { |cp| [cp.n1, cp.n2] }.sort_by { |_, members| members.count }.last.last
244
+ logger.debug "found vertical pair #{control_points_of_pair.first.n1} <> #{control_points_of_pair.first.n2} with #{control_points_of_pair.count} connections"
245
+ average_distance = control_points_of_pair.map(&:pdist).reduce(:+).to_f / control_points_of_pair.count
246
+ logger.debug "average distance #{average_distance}"
247
+ dist_std = Math.sqrt(control_points_of_pair.map { |cp| (cp.pdist - average_distance) ** 2 }.reduce(:+) / (control_points_of_pair.count - 1))
248
+ logger.debug "dist std: #{dist_std}"
249
+ vertical_control_points_of_pair = control_points_of_pair.select { |cp| (cp.pdist - average_distance).abs < dist_std }
250
+ logger.info "removed #{control_points_of_pair.count - vertical_control_points_of_pair.count} outliers"
251
+ new_average_distance = vertical_control_points_of_pair.map(&:pdist).reduce(:+).to_f / vertical_control_points_of_pair.count
252
+ logger.info "new average #{new_average_distance}"
253
+
254
+ logger.info 'finding unconnected image pairs'
255
+ ds = images.map(&:d).uniq.sort
256
+ es = images.map(&:e).uniq.sort
257
+
258
+ unconnected_image_pairs = []
259
+ # horizontal connection checking
260
+ es.each do |e|
261
+ ds.each_with_index do |d, index|
262
+ next if index == (ds.count - 1)
263
+ image_1 = images.find { |i| i.e == e && i.d == d }
264
+ image_2 = images.find { |i| i.e == e && i.d == ds[index+1] }
265
+ connected = control_points.any? { |cp| (cp.n1 == image_1.id && cp.n2 == image_2.id) || (cp.n1 == image_2.id && cp.n2 == image_1.id) }
266
+ unconnected_image_pairs << { type: :horizontal, pair: [image_1, image_2].sort_by(&:id) } unless connected
267
+ end
268
+ end
269
+
270
+ # vertical connection checking
271
+ ds.each do |d|
272
+ es.each_with_index do |e, index|
273
+ next if index == (es.count - 1)
274
+ image_1 = images.find { |i| i.d == d && i.e == e }
275
+ image_2 = images.find { |i| i.d == d && i.e == es[index+1] }
276
+ connected = control_points.any? { |cp| (cp.n1 == image_1.id && cp.n2 == image_2.id) || (cp.n1 == image_2.id && cp.n2 == image_1.id) }
277
+ unconnected_image_pairs << { type: :vertical, pair: [image_1, image_2].sort_by(&:id) } unless connected
278
+ end
279
+ end
280
+
281
+ logger.info unconnected_image_pairs.map { |i| { type: i[:type], pair: i[:pair].map(&:id) } }
282
+
283
+ logger.info 'finding control points with unrealistic distances (<1)'
284
+ fake_control_points = control_points.select { |cp| cp.pdist <= 1.0 }
285
+
286
+ logger.info 'writing new control points'
287
+ control_point_lines_started = false
288
+ @lines = @input_file.each_line.map do |line|
289
+ cp = ControlPoint.parse_line(line)
290
+ if cp.nil?
291
+ # Control point lines ended
292
+ if control_point_lines_started
293
+ control_point_lines_started = false
294
+ unconnected_image_pairs.map do |pair|
295
+ if pair[:type] == :horizontal
296
+ control_point = horizontal_control_points_of_pair.first
297
+ else
298
+ control_point = vertical_control_points_of_pair.first
299
+ end
300
+
301
+ control_point[:n] = pair[:pair].first.id
302
+ control_point[:N] = pair[:pair].last.id
303
+ logger.debug "adding control point connecting #{control_point.n1} <> #{control_point.n2}"
304
+ control_point.to_s
305
+ end + [line]
306
+ else
307
+ next line
308
+ end
309
+ else
310
+ control_point_lines_started = true
311
+ fake_control_point = fake_control_points.find { |cp| cp.raw == line }
312
+ if fake_control_point
313
+ if fake_control_point.conn_type == :horizontal
314
+ control_point = horizontal_control_points_of_pair.first
315
+ else
316
+ control_point = vertical_control_points_of_pair.first
317
+ end
318
+ control_point[:n] = fake_control_point[:n]
319
+ control_point[:N] = fake_control_point[:N]
320
+ logger.debug "replacing unrealistic control point connecting #{control_point.n1} <> #{control_point.n2}"
321
+ control_point.to_s
322
+ else
323
+ next line
324
+ end
325
+ end
326
+ end.compact.flatten
223
327
 
328
+ save_file
224
329
  end
225
330
 
226
331
  def generate_border_line_control_points
@@ -348,19 +453,40 @@ module Panomosity
348
453
  end
349
454
 
350
455
  def get_detailed_control_point_info
351
- logger.info 'removing long lines'
456
+ logger.info 'getting detailed control point info'
352
457
 
353
458
  images = Image.parse(@input_file)
354
- control_points = ControlPoint.get_detailed_info(@input, cp_type: :normal)
459
+ panorama_variable = PanoramaVariable.parse(@input_file).first
460
+ first_set_control_points = ControlPoint.parse(@input_file)
461
+
462
+ @new_input = 'project_remove_equal_signs.pto'
463
+ runner = Runner.new(@options.merge(input: @input, output: @new_input, remove_equal_signs: true))
464
+ runner.run('convert_equaled_image_parameters')
465
+ @input_file = File.new(@new_input, 'r').read
466
+
467
+ second_set_control_points = ControlPoint.get_detailed_info(@new_input)
468
+ control_points = ControlPoint.merge(first_set_control_points, second_set_control_points)
355
469
  control_points.each do |cp|
356
470
  image1 = images.find { |i| cp.n1 == i.id }
357
471
  image2 = images.find { |i| cp.n2 == i.id }
358
- # dist = ((image1.normal_x + cp.x1) - (image2.normal_x + cp.x2)) ** 2 + ((image1.normal_y + cp.y1) - (image2.normal_y + cp.y2)) ** 2
359
- dx = (image1.d - cp.x1) - (image2.d - cp.x2)
360
- dy = (image1.e - cp.y1) - (image2.e - cp.y2)
361
- logger.debug "#{cp.to_s} distrt #{Math.sqrt(dx**2+dy**2)} iy1 #{image1.normal_y} iy2 #{image2.normal_y}"
472
+ point1 = image1.to_cartesian(cp.x1, cp.y1)
473
+ point2 = image2.to_cartesian(cp.x2, cp.y2)
474
+
475
+ angle = Math.acos(point1[0] * point2[0] + point1[1] * point2[1] + point1[2] * point2[2])
476
+ radius = (panorama_variable.w / 2.0) / Math.tan((panorama_variable.v * Math::PI / 180) / 2)
477
+
478
+ x1 = (image1.w / 2.0) - cp.x1 + image1.d
479
+ y1 = (image1.h / 2.0) - cp.y1 + image1.e
480
+ x2 = (image2.w / 2.0) - cp.x2 + image2.d
481
+ y2 = (image2.h / 2.0) - cp.y2 + image2.e
482
+
483
+ dist = Math.sqrt((x1 - x2)**2 + (y1 - y2)**2)
484
+
485
+ dr = angle * radius
486
+
487
+ type = image1.d == image2.d ? :vertical : :horizontal
488
+ logger.debug "#{cp.to_s.sub(/\n/, '')} dist #{dr} pixel_dist #{x1-x2},#{y1-y2},#{dist} type #{type}"
362
489
  end
363
- logger.debug "avg #{control_points.map(&:dist).reduce(:+)/control_points.count.to_f}"
364
490
  end
365
491
 
366
492
  def merge_image_parameters
@@ -1,3 +1,3 @@
1
1
  module Panomosity
2
- VERSION = '0.1.2'
2
+ VERSION = '0.1.4'
3
3
  end
data/lib/panomosity.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'panomosity/control_point'
2
2
  require 'panomosity/image'
3
3
  require 'panomosity/optimisation_variable'
4
+ require 'panomosity/panorama_variable'
4
5
  require 'panomosity/runner'
5
6
  require 'panomosity/version'
6
7
  require 'pathname'
@@ -35,6 +36,10 @@ module Panomosity
35
36
  options[:without_cropping] = wc
36
37
  end
37
38
 
39
+ parser.on('--remove-equal-signs', 'Do not crop when running "crop_centers" (usually when the original run failed)') do |eq|
40
+ options[:remove_equal_signs] = eq
41
+ end
42
+
38
43
  parser.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
39
44
  options[:verbose] = v
40
45
  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.2
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oliver Garcia
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-08-22 00:00:00.000000000 Z
11
+ date: 2018-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -78,6 +78,7 @@ files:
78
78
  - lib/panomosity/control_point.rb
79
79
  - lib/panomosity/image.rb
80
80
  - lib/panomosity/optimisation_variable.rb
81
+ - lib/panomosity/panorama_variable.rb
81
82
  - lib/panomosity/runner.rb
82
83
  - lib/panomosity/version.rb
83
84
  - panomosity.gemspec