panomosity 0.1.2 → 0.1.4

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