panomosity 0.1.4 → 0.1.5

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: 1e32fb89b5812b7f726cf6c322423d4e816aa103bde59bd5deef4bd6fb2279d4
4
- data.tar.gz: 2aae7a094ad7bccd73af5d6042a799e29cd4ce1bd0e1bdd81d187b24e82b33f5
3
+ metadata.gz: d7d65793e972cbf9d8367f1530b60910163841a24a5517bbfea254a21e0a583c
4
+ data.tar.gz: cac1ed82d1567b8b0ede45b4697dd273d9383d1f951ee5eace80b95734d7e1f5
5
5
  SHA512:
6
- metadata.gz: d7d24392ffec1de2c6fb59b3ed8c4e03a59e91529ecc3a2a7c5d4bcca26383ea2a089dfd0f5e044fe2604c57b802324f6f4859532211b212808ea5a97aadc918
7
- data.tar.gz: f41fae931324dc129bb4f9b8499f86d0d209dd9bcf9c396b768e988d9ab8abe62eec1032601335da697a7df69ebb9f26ae5c271b1fd040b66a6b3e8d6a021611
6
+ metadata.gz: 8476d6c4ef2615c878b18232b929828ac03db88d133c654b9b04104db64ef370e5c13feb5f47573f8513397f612cc48a8c19d3fa551b01e9586c9332bdee68e9
7
+ data.tar.gz: f411883b85d56232f9b38f67161c80e32d682f56c842d87b1d0b31352c157748065922751f1ce99315e9cf67151c2ff173e09def1c6da8b08f3ca1295aa43d5e
data/.gitignore CHANGED
@@ -12,3 +12,6 @@
12
12
 
13
13
  # RubyMine
14
14
  /.idea/
15
+
16
+ # Gem build
17
+ panomosity*.gem
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- panomosity (0.1.3)
4
+ panomosity (0.1.5)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -36,10 +36,14 @@ module Panomosity
36
36
  options[:without_cropping] = wc
37
37
  end
38
38
 
39
- parser.on('--remove-equal-signs', 'Do not crop when running "crop_centers" (usually when the original run failed)') do |eq|
39
+ parser.on('--remove-equal-signs', 'Remove equal signs when running "convert_equaled_image_parameters" (necessary when parsing the PTO file using Panotools)') do |eq|
40
40
  options[:remove_equal_signs] = eq
41
41
  end
42
42
 
43
+ parser.on('--max-removal FRAC', Float, 'Max fraction of control points to be removed when running "clean_control_points" that are statistical outliers') do |mr|
44
+ options[:max_removal] = mr
45
+ end
46
+
43
47
  parser.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
44
48
  options[:verbose] = v
45
49
  end
@@ -47,7 +47,9 @@ module Panomosity
47
47
  point1 = image1.to_cartesian(cp.x1, cp.y1)
48
48
  point2 = image2.to_cartesian(cp.x2, cp.y2)
49
49
 
50
- angle = Math.acos(point1[0] * point2[0] + point1[1] * point2[1] + point1[2] * point2[2])
50
+ product = point1[0] * point2[0] + point1[1] * point2[1] + point1[2] * point2[2]
51
+ product = 1.0 if product > 1.0
52
+ angle = Math.acos(product)
51
53
  radius = (panorama_variable.w / 2.0) / Math.tan((panorama_variable.v * Math::PI / 180) / 2)
52
54
 
53
55
  distance = angle * radius
@@ -59,9 +61,9 @@ module Panomosity
59
61
  x2 = (image2.w / 2.0) - cp.x2 + image2.d
60
62
  y2 = (image2.h / 2.0) - cp.y2 + image2.e
61
63
 
62
- pixel_distance = Math.sqrt((x1 - x2)**2 + (y1 - y2)**2)
63
64
  cp.px = x1 - x2
64
65
  cp.py = y1 - y2
66
+ pixel_distance = Math.sqrt(cp.px**2 + cp.py**2)
65
67
  cp.pdist = pixel_distance
66
68
  cp.conn_type = image1.d == image2.d ? :vertical : :horizontal
67
69
  end
@@ -6,6 +6,7 @@ module Panomosity
6
6
 
7
7
  AVAILABLE_COMMANDS = %w(
8
8
  check_position_changes
9
+ clean_control_points
9
10
  convert_equaled_image_parameters
10
11
  convert_horizontal_lines
11
12
  convert_translation_parameters
@@ -83,6 +84,52 @@ module Panomosity
83
84
  save_file
84
85
  end
85
86
 
87
+ def clean_control_points
88
+ logger.info 'cleaning control points'
89
+ # Since this is very exact, having many outliers in control points distances will cause errors
90
+ images = Image.parse(@input_file)
91
+ panorama_variable = PanoramaVariable.parse(@input_file).first
92
+ ControlPoint.parse(@input_file)
93
+ control_points = ControlPoint.calculate_distances(images, panorama_variable)
94
+
95
+ bad_control_points = []
96
+ min_control_points = 5
97
+ control_points.group_by { |cp| [cp.n1, cp.n2] }.select { |_, cps| cps.count > min_control_points }.each do |pair, cps|
98
+ logger.debug "cleaning pair #{pair.first} <> #{pair.last}"
99
+ average_x, x_std = *calculate_average_and_std(name: :x, values: cps.map(&:px))
100
+ average_y, y_std = *calculate_average_and_std(name: :y, values: cps.map(&:py))
101
+
102
+ max_removal = ((@options[:max_removal] || 0.2) * cps.count).floor
103
+ min_cps = 10
104
+ max_iterations = 10
105
+ iterations = 0
106
+ bad_cps = cps.select { |cp| (cp.px - average_x).abs >= x_std || (cp.py - average_y).abs >= y_std }
107
+ while bad_cps.count > max_removal && (cps.count - bad_cps.count) >= min_cps && iterations <= max_iterations
108
+ x_std *= 1.1
109
+ y_std *= 1.1
110
+ iterations += 1
111
+ bad_cps = cps.select { |cp| (cp.px - average_x).abs >= x_std || (cp.py - average_y).abs >= y_std }
112
+ end
113
+
114
+ logger.info "found #{bad_cps.count} outliers"
115
+ bad_control_points << bad_cps if bad_cps.count <= max_removal
116
+ end
117
+ bad_control_points.flatten!
118
+
119
+ logger.info "removing #{bad_control_points.count} control points"
120
+ @lines = @input_file.each_line.map do |line|
121
+ control_point = ControlPoint.parse_line(line)
122
+ # skip this control point if we found it
123
+ if control_point && bad_control_points.find { |bad_cp| bad_cp.raw == control_point.raw }
124
+ next
125
+ else
126
+ next line
127
+ end
128
+ end.compact
129
+
130
+ save_file
131
+ end
132
+
86
133
  def convert_equaled_image_parameters
87
134
  logger.info 'converting equaled image parameters'
88
135
  images = Image.parse(@input_file)
@@ -231,25 +278,19 @@ module Panomosity
231
278
  horizontal_control_points, vertical_control_points = *control_points.partition { |cp| cp.conn_type == :horizontal }
232
279
  control_points_of_pair = horizontal_control_points.group_by { |cp| [cp.n1, cp.n2] }.sort_by { |_, members| members.count }.last.last
233
280
  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 }
281
+ average_distance, distance_std = *calculate_average_and_std(name: :distance, values: control_points_of_pair.map(&:pdist))
282
+ horizontal_control_points_of_pair = control_points_of_pair.select { |cp| (cp.pdist - average_distance).abs < distance_std }
239
283
  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}"
284
+ # For logging
285
+ calculate_average_and_std(name: :distance, values: horizontal_control_points_of_pair.map(&:pdist))
242
286
 
243
287
  control_points_of_pair = vertical_control_points.group_by { |cp| [cp.n1, cp.n2] }.sort_by { |_, members| members.count }.last.last
244
288
  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 }
289
+ average_distance, distance_std = *calculate_average_and_std(name: :distance, values: control_points_of_pair.map(&:pdist))
290
+ vertical_control_points_of_pair = control_points_of_pair.select { |cp| (cp.pdist - average_distance).abs < distance_std }
250
291
  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}"
292
+ # For logging
293
+ calculate_average_and_std(name: :distance, values: vertical_control_points_of_pair.map(&:pdist))
253
294
 
254
295
  logger.info 'finding unconnected image pairs'
255
296
  ds = images.map(&:d).uniq.sort
@@ -298,10 +339,35 @@ module Panomosity
298
339
  control_point = vertical_control_points_of_pair.first
299
340
  end
300
341
 
342
+ x_diff = control_point.x2 - control_point.x1
343
+ y_diff = control_point.y2 - control_point.y1
344
+
345
+ x1 = x_diff <= 0 ? -x_diff + 15 : 0
346
+ y1 = y_diff <= 0 ? -y_diff + 15 : 0
347
+
301
348
  control_point[:n] = pair[:pair].first.id
302
349
  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
350
+ control_point[:x] = x1
351
+ control_point[:X] = x1 + x_diff
352
+ control_point[:y] = y1
353
+ control_point[:Y] = y1 + y_diff
354
+
355
+ logger.debug "adding control points connecting #{control_point.n1} <> #{control_point.n2}"
356
+ i = images.first
357
+ 3.times.map do
358
+ if control_point.conn_type == :horizontal
359
+ control_point[:x] += 5
360
+ control_point[:X] += 5
361
+ control_point[:y] += i.h * 0.25
362
+ control_point[:Y] += i.h * 0.25
363
+ else
364
+ control_point[:x] += i.w * 0.25
365
+ control_point[:X] += i.w * 0.25
366
+ control_point[:y] += 5
367
+ control_point[:Y] += 5
368
+ end
369
+ control_point.to_s
370
+ end.join
305
371
  end + [line]
306
372
  else
307
373
  next line
@@ -315,10 +381,36 @@ module Panomosity
315
381
  else
316
382
  control_point = vertical_control_points_of_pair.first
317
383
  end
384
+
385
+ x_diff = control_point.x2 - control_point.x1
386
+ y_diff = control_point.y2 - control_point.y1
387
+
388
+ x1 = x_diff <= 0 ? -x_diff + 15 : 0
389
+ y1 = y_diff <= 0 ? -y_diff + 15 : 0
390
+
318
391
  control_point[:n] = fake_control_point[:n]
319
392
  control_point[:N] = fake_control_point[:N]
393
+ control_point[:x] = x1
394
+ control_point[:X] = x1 + x_diff
395
+ control_point[:y] = y1
396
+ control_point[:Y] = y1 + y_diff
397
+
320
398
  logger.debug "replacing unrealistic control point connecting #{control_point.n1} <> #{control_point.n2}"
321
- control_point.to_s
399
+ i = images.first
400
+ 3.times.map do
401
+ if control_point.conn_type == :horizontal
402
+ control_point[:x] += 5
403
+ control_point[:X] += 5
404
+ control_point[:y] += i.h * 0.25
405
+ control_point[:Y] += i.h * 0.25
406
+ else
407
+ control_point[:x] += i.w * 0.25
408
+ control_point[:X] += i.w * 0.25
409
+ control_point[:y] += 5
410
+ control_point[:Y] += 5
411
+ end
412
+ control_point.to_s
413
+ end.join
322
414
  else
323
415
  next line
324
416
  end
@@ -566,20 +658,24 @@ module Panomosity
566
658
  def standardize_roll
567
659
  logger.info 'standardizing roll'
568
660
  images = Image.parse(@input_file)
569
- rolls = images.map(&:r)
570
- average_roll = rolls.reduce(:+).to_f / rolls.count
571
- logger.debug "average roll: #{average_roll}"
572
- roll_std = Math.sqrt(rolls.map { |r| (r - average_roll) ** 2 }.reduce(:+) / (rolls.count - 1))
573
- logger.debug "roll std: #{roll_std}"
661
+ panorama_variable = PanoramaVariable.parse(@input_file).first
662
+ ControlPoint.parse(@input_file)
663
+ control_points = ControlPoint.calculate_distances(images, panorama_variable)
664
+ max_count = (images.count * 0.5).ceil - 1
665
+ pairs = control_points.group_by { |cp| [cp.n1, cp.n2] }.sort_by { |_, members| -members.count }[0..max_count]
666
+ image_ids = pairs.map { |image_ids, _| image_ids }.flatten.uniq
667
+ rolls = images.select { |image| image_ids.include?(image.id) }.map(&:r)
668
+
669
+ average_roll, roll_std = *calculate_average_and_std(name: :roll, values: rolls)
574
670
  new_rolls = rolls.select { |r| (r - average_roll).abs < roll_std }
575
671
  logger.info "removed #{rolls.count - new_rolls.count} outliers"
576
- new_average_roll = new_rolls.reduce(:+).to_f / new_rolls.count
577
- logger.info "converting all rolls to #{new_average_roll}"
672
+ average_roll, _ = *calculate_average_and_std(name: :roll, values: new_rolls)
673
+ logger.info "converting all rolls to #{average_roll}"
578
674
 
579
675
  @lines = @input_file.each_line.map do |line|
580
676
  image = images.find { |i| i.raw == line }
581
677
  if image
582
- image.r = new_average_roll
678
+ image.r = average_roll
583
679
  image.to_s
584
680
  else
585
681
  next line
@@ -606,5 +702,13 @@ module Panomosity
606
702
 
607
703
  @output_file.close
608
704
  end
705
+
706
+ def calculate_average_and_std(name:, values: [])
707
+ average_value = values.reduce(:+).to_f / values.count
708
+ logger.debug "average #{name}: #{average_value}"
709
+ value_std = Math.sqrt(values.map { |v| (v - average_value) ** 2 }.reduce(:+) / (values.count - 1))
710
+ logger.debug "#{name} std: #{value_std}"
711
+ [average_value, value_std]
712
+ end
609
713
  end
610
714
  end
@@ -1,3 +1,3 @@
1
1
  module Panomosity
2
- VERSION = '0.1.4'
2
+ VERSION = '0.1.5'
3
3
  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.4
4
+ version: 0.1.5
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-09-18 00:00:00.000000000 Z
11
+ date: 2018-09-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler