panomosity 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8a24ffd1b51c93f06e28be5531393d9ef4fa2390dfabc1a3a0cebd92e0fe8ea2
4
+ data.tar.gz: 6f01ff9088ce77b29b1d0b75c7ecc419962fa81b988d3b8c41b4f13a4ff29d61
5
+ SHA512:
6
+ metadata.gz: d8ce529cfbcfc347df7ff571a585d4f081ed35cc0c7ac7890f613a0d7e83a0da58d2c6cf5656571690eef8d793f65eb50277fd886763fbad34bc74f4e50c7ec9
7
+ data.tar.gz: 43f8377b355628dc3b896deec813be6c56ca6969d1c9f7731e774ca08abe01bd5694d66ac87e4a63ceefde84e787e262e50624fea73574312e92336492a9a38b
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ # RubyMine
14
+ /.idea/
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.5.0
5
+ before_install: gem install bundler -v 1.16.1
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at ogarci5@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ gem 'pry'
6
+
7
+ # Specify your gem's dependencies in panomosity.gemspec
8
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,41 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ panomosity (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ coderay (1.1.2)
10
+ diff-lcs (1.3)
11
+ method_source (0.9.0)
12
+ pry (0.11.3)
13
+ coderay (~> 1.1.0)
14
+ method_source (~> 0.9.0)
15
+ 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)
23
+ diff-lcs (>= 1.2.0, < 2.0)
24
+ rspec-support (~> 3.8.0)
25
+ rspec-mocks (3.8.0)
26
+ diff-lcs (>= 1.2.0, < 2.0)
27
+ rspec-support (~> 3.8.0)
28
+ rspec-support (3.8.0)
29
+
30
+ PLATFORMS
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ bundler (~> 1.16)
35
+ panomosity!
36
+ pry
37
+ rake (~> 10.0)
38
+ rspec (~> 3.0)
39
+
40
+ BUNDLED WITH
41
+ 1.16.1
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Oliver Garcia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # Panomosity
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/panomosity`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'panomosity'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install panomosity
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/panomosity. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
40
+
41
+ ## Code of Conduct
42
+
43
+ Everyone interacting in the Panomosity project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/panomosity/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'panomosity'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ require 'pry'
10
+ Pry.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/panomosity ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'panomosity'
4
+ Panomosity.parse(ARGV)
@@ -0,0 +1,118 @@
1
+ # Example of a control point line
2
+ # c n26 N26 x887.4 y1056.72 X1128.12 Y1077.12 t2
3
+
4
+ module Panomosity
5
+ class ControlPoint
6
+ @@attributes = %i(n N x y X Y t)
7
+
8
+ def self.parse(pto_file, cp_type: nil, compact: false)
9
+ @control_points = pto_file.each_line.map do |line|
10
+ if compact
11
+ cp_data = line.split(',').map { |part| part.split(' ') }.flatten
12
+ n1, x1, y1, n2, x2, y2, type, dist = *cp_data.to_a.map(&:to_f)
13
+ new(n: n1, N: n2, x: x1, y: y1, X: x2, Y: y2, t: type, dist: dist, raw: line)
14
+ else
15
+ parse_line(line)
16
+ end
17
+ end.compact
18
+
19
+ case cp_type
20
+ when :line
21
+ @control_points.select!(&:line?)
22
+ when :normal
23
+ @control_points.select!(&:normal?)
24
+ when :vertical
25
+ @control_points.select!(&:vertical?)
26
+ when :horizontal
27
+ @control_points.select!(&:horizontal?)
28
+ else
29
+ @control_points
30
+ end
31
+ end
32
+
33
+ def self.get_detailed_info(pto_file_path, cp_type: nil)
34
+ result = `perl control_point_info.pl --input #{pto_file_path}`
35
+ parse(result, cp_type: cp_type, compact: true)
36
+ end
37
+
38
+ def self.all
39
+ @control_points
40
+ end
41
+
42
+ def self.parse_line(line)
43
+ parts = line.split(' ')
44
+ if parts.first == 'c'
45
+ data = parts.each_with_object({}) do |part, hash|
46
+ attribute = @@attributes.find { |attr| part[0] == attr.to_s }
47
+ next unless attribute
48
+ hash[attribute] = part.sub(attribute.to_s, '')
49
+ end
50
+
51
+ data[:raw] = line
52
+ data[:dist] = nil
53
+
54
+ new data
55
+ end
56
+ end
57
+
58
+ def initialize(attributes)
59
+ @attributes = attributes
60
+ # conform data types
61
+ @attributes.each do |key, value|
62
+ next if %i(raw).include?(key)
63
+ if value.respond_to?(:include?) && value.include?('.')
64
+ @attributes[key] = value.to_f
65
+ else
66
+ @attributes[key] = value.to_i
67
+ end
68
+ end
69
+ end
70
+
71
+ def [](key)
72
+ @attributes[key]
73
+ end
74
+
75
+ def []=(key, value)
76
+ @attributes[key] = value
77
+ end
78
+
79
+ (@@attributes + %i(raw dist)).each do |attr|
80
+ define_method(attr) do
81
+ @attributes[attr]
82
+ end
83
+
84
+ define_method(:"#{attr}=") do |value|
85
+ @attributes[attr] = value
86
+ end
87
+ end
88
+
89
+ alias_method :type, :t
90
+ alias_method :n1, :n
91
+ alias_method :n2, :N
92
+ alias_method :x1, :x
93
+ alias_method :x2, :X
94
+ alias_method :y1, :y
95
+ alias_method :y2, :Y
96
+
97
+ def normal?
98
+ type == 0
99
+ end
100
+
101
+ def vertical?
102
+ type == 1
103
+ end
104
+
105
+ def horizontal?
106
+ type == 2
107
+ end
108
+
109
+ def line?
110
+ vertical? || horizontal?
111
+ end
112
+
113
+ def to_s
114
+ line_values = @@attributes.map { |attribute| "#{attribute}#{self.send(attribute)}" }
115
+ "c #{line_values.join(' ')}\n"
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,143 @@
1
+ # Exmaple of an image line
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
+
4
+ module Panomosity
5
+ class Image
6
+ @@attributes = %i(w h f v Ra Rb Rc Rd Re Eev Er Eb r p y TrX TrY TrZ Tpy Tpp j a b c d e g t Va Vb Vc Vd Vx Vy Vm n)
7
+ @@equaled_attributes = %i(v Ra Rb Rc Rd Re a b c d e Va Vb Vc Vd Vx Vy)
8
+
9
+ def self.parse(pto_file)
10
+ id = 0
11
+ @images = pto_file.each_line.map do |line|
12
+ image = parse_line(line, id)
13
+ if image
14
+ id += 1
15
+ image
16
+ end
17
+ end.compact
18
+
19
+ calculate_dimensions
20
+ @images
21
+ end
22
+
23
+ def self.calculate_dimensions(fov = nil)
24
+ @fov = fov || 45.2
25
+ lam = (90 - (@fov / 2.0)) * (Math::PI / 180.0)
26
+ @panosphere = (Math.tan(lam) * 0.5 * 0.5)
27
+ _, @x_offset = @images.map(&:TrX).minmax
28
+ _, @y_offset = @images.map(&:TrY).minmax
29
+ end
30
+
31
+ def self.all
32
+ @images
33
+ end
34
+
35
+ def self.panosphere
36
+ @panosphere
37
+ end
38
+
39
+ def self.x_offset
40
+ @x_offset
41
+ end
42
+
43
+ def self.y_offset
44
+ @y_offset
45
+ end
46
+
47
+ def self.parse_line(line, id = 0)
48
+ parts = line.split(' ')
49
+ if parts.first == 'i'
50
+ data = parts.each_with_object({}) do |part, hash|
51
+ attribute = @@attributes.find { |attr| part[0..(attr.to_s.length-1)] == attr.to_s }
52
+ next unless attribute
53
+ hash[attribute] = part.sub(attribute.to_s, '')
54
+ end
55
+
56
+ # Sanitization
57
+ data.each { |key, value| data[key] = value.sub(/\A=/, '') }
58
+ data[:n] = data[:n].gsub('"', '')
59
+ data[:id] = id
60
+ data[:raw] = line
61
+
62
+ new data
63
+ end
64
+ end
65
+
66
+ def initialize(attributes)
67
+ @attributes = attributes
68
+ # conform data types
69
+ @attributes.each do |key, value|
70
+ next if %i(n id raw).include?(key)
71
+ if value.respond_to?(:include?) && value.include?('.')
72
+ @attributes[key] = value.to_f
73
+ else
74
+ @attributes[key] = value.to_i
75
+ end
76
+ end
77
+ end
78
+
79
+ def [](key)
80
+ @attributes[key]
81
+ end
82
+
83
+ def []=(key, value)
84
+ @attributes[key] = value
85
+ end
86
+
87
+ (@@attributes + %i(raw id)).each do |attr|
88
+ define_method(attr) do
89
+ @attributes[attr]
90
+ end
91
+
92
+ define_method(:"#{attr}=") do |value|
93
+ @attributes[attr] = value
94
+ end
95
+ end
96
+
97
+ alias_method :width, :w
98
+ alias_method :height, :h
99
+ alias_method :name, :n
100
+
101
+ def normal_x
102
+ self[:TrX] * self.class.panosphere * width
103
+ end
104
+
105
+ def normal_y
106
+ self[:TrY] * self.class.panosphere * height
107
+ end
108
+
109
+ def to_s
110
+ subline_values = (@@attributes - %i(Vm n)).map do |attribute|
111
+ value = self.send(attribute)
112
+ if @@equaled_attributes.include?(attribute)
113
+ if value == 0.0
114
+ "#{attribute}=#{value.to_i}"
115
+ else
116
+ "#{attribute}#{value}"
117
+ end
118
+ else
119
+ "#{attribute}#{value}"
120
+ end
121
+ end
122
+ %Q(i #{subline_values.join(' ')} Vm#{self[:Vm]} n"#{self[:n]}"\n)
123
+ end
124
+
125
+ # Gets the value of TrX and TrY and sets them as d and e
126
+ def convert_position!
127
+ trx = self[:TrX]
128
+ try = self[:TrY]
129
+
130
+ self[:TrX] = 0
131
+ self[:TrY] = 0
132
+ self[:TrZ] = 0
133
+
134
+ # To fix an issue with pto reading files ignoring attributes that have a =0
135
+ trx = '0.0' if trx == 0.0
136
+ try = '0.0' if try == 0.0
137
+
138
+ self[:d] = trx
139
+ self[:e] = try
140
+ self
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,64 @@
1
+ module Panomosity
2
+ class OptimisationVariable
3
+ @@attributes = %i(w h f v Ra Rb Rc Rd Re Eev Er Eb r p y TrX TrY TrZ Tpy Tpp j a b c d e g t Va Vb Vc Vd Vx Vy Vm n)
4
+
5
+ def self.parse(pto_file)
6
+ @optimization_variables = pto_file.each_line.map { |line| parse_line(line) }.compact
7
+ end
8
+
9
+ def self.all
10
+ @optimization_variables
11
+ end
12
+
13
+ def self.parse_line(line)
14
+ parts = line.split(' ')
15
+ if parts.first == 'v'
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(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
+ "v #{line_values.join(' ')}\n"
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,477 @@
1
+ require 'logger'
2
+
3
+ module Panomosity
4
+ class Runner
5
+ attr_reader :logger
6
+
7
+ AVAILABLE_COMMANDS = %w(
8
+ check_position_changes
9
+ convert_equaled_image_parameters
10
+ convert_horizontal_lines
11
+ convert_translation_parameters
12
+ crop_centers
13
+ fix_conversion_errors
14
+ generate_border_line_control_points
15
+ merge_image_parameters
16
+ prepare_for_pr0ntools
17
+ remove_long_lines
18
+ remove_anchor_variables
19
+ standardize_roll
20
+ )
21
+
22
+ def initialize(options)
23
+ @options = options
24
+ @input = options[:input]
25
+ @output = options[:output]
26
+ @csv = options[:csv]
27
+ @compare = options[:compare]
28
+ @input_file = File.new(@input, 'r').read rescue puts('You must have at least one argument')
29
+ @output_file = File.new(@output, 'w') if @output
30
+ @csv_file = File.new(@csv, 'r').read if @csv
31
+ @compare_file = File.new(@compare, 'r').read if @compare
32
+ @logger = Logger.new(STDOUT)
33
+
34
+ if options[:verbose]
35
+ @logger.level = Logger::INFO
36
+ else
37
+ @logger.level = Logger::DEBUG
38
+ end
39
+
40
+ @logger.formatter = proc do |severity, datetime, progname, msg|
41
+ "[#{datetime}][#{severity}] #{msg}\n"
42
+ end
43
+ end
44
+
45
+ def run(command)
46
+ if AVAILABLE_COMMANDS.include?(command)
47
+ send(command)
48
+ else
49
+ logger.info "commands include:\n#{AVAILABLE_COMMANDS.join("\n")}"
50
+ end
51
+ end
52
+
53
+ def check_position_changes
54
+ logger.info 'checking position changes'
55
+ original_images = Image.parse(@input_file)
56
+ changed_images = Image.parse(@compare_file)
57
+ threshold = 0.10
58
+ images = original_images.zip(changed_images)
59
+ changes_x = images.select do |original, changed|
60
+ ratio_x = original.d / changed.d
61
+ changed_x = (1 - ratio_x.abs).abs
62
+ changed_x > threshold
63
+ end
64
+ changes_y = images.select do |original, changed|
65
+ ratio_y = original.e / changed.e
66
+ changed_y = (1 - ratio_y.abs).abs
67
+ changed_y > threshold
68
+ end
69
+
70
+ @lines = @input_file.each_line.map do |line|
71
+ variable = OptimisationVariable.parse_line(line)
72
+ next line unless variable
73
+ if variable.d && changes_x.find { |original, _| original.id.to_s == variable.d }
74
+ logger.debug "Removing #{variable.to_s}"
75
+ elsif variable.e && changes_y.find { |original, _| original.id.to_s == variable.e }
76
+ logger.debug "Removing #{variable.to_s}"
77
+ else
78
+ next line
79
+ end
80
+ end
81
+
82
+ save_file
83
+ end
84
+
85
+ def convert_equaled_image_parameters
86
+ logger.info 'converting equaled image parameters'
87
+ images = Image.parse(@input_file)
88
+ @lines = @input_file.each_line.map do |line|
89
+ image = images.find { |i| i.raw == line }
90
+ if image
91
+ image.to_s
92
+ else
93
+ next line
94
+ end
95
+ end.compact
96
+
97
+ save_file
98
+ end
99
+
100
+ def convert_horizontal_lines
101
+ logger.info 'converting horizontal lines'
102
+ @lines = @input_file.each_line.map do |line|
103
+ cp = ControlPoint.parse_line(line)
104
+ if cp && cp.vertical?
105
+ cp.t = 2
106
+ cp.to_s
107
+ else
108
+ next line
109
+ end
110
+ end.compact
111
+
112
+ save_file
113
+ end
114
+
115
+ def convert_translation_parameters
116
+ logger.info 'converting translation parameters'
117
+ images = Image.parse(@input_file)
118
+
119
+ @lines = @input_file.each_line.map do |line|
120
+ image = images.find { |i| i.raw == line }
121
+ next if line[0] == 'v'
122
+ next line unless image
123
+ image.convert_position!
124
+ if image.id == images.sort_by(&:id).last.id
125
+ image.to_s +
126
+ "\n" +
127
+ "# Variable lines\n" +
128
+ images.map { |i| i.id == 0 ? nil : "v d#{i.id} e#{i.id}\n" }.compact.join +
129
+ "v\n" +
130
+ "\n"
131
+ else
132
+ image.to_s
133
+ end
134
+
135
+ end.compact
136
+
137
+ save_file
138
+ end
139
+
140
+ # Uses image magick to crop centers
141
+ def crop_centers
142
+ logger.info 'cropping centers'
143
+ unless @options[:without_cropping]
144
+ images = Image.parse(@input_file)
145
+ images.each do |image|
146
+ geometry = `identify -verbose #{image.name} | grep Geometry`.strip
147
+ _, width, height = *geometry.match(/(\d{2,5})x(\d{2,5})(\+|\-)\d{1,5}(\+|\-)\d{1,5}/)
148
+ logger.debug "cropping #{image.name}"
149
+ `convert #{image.name} -crop "50%x50%+#{(width.to_f/4).round}+#{(height.to_f/4).round}" #{image.name}`
150
+ end
151
+ end
152
+
153
+ # Since all images have been cropped, we need to change d,e params to move images based on how much was cropped
154
+ # Re-run commands that have been run at this point
155
+ logger.info 'rerunning commands'
156
+ @new_input_file_path = 'project_converted_translation_cropped.pto'
157
+ `match-n-shift --input #{@csv} -o project_cropped.pto`
158
+ `pto_var --opt=TrX,TrY project_cropped.pto -o project_pto_var_cropped.pto`
159
+ runner = Runner.new(@options.merge(input: 'project_pto_var_cropped.pto', output: @new_input_file_path))
160
+ runner.run('convert_translation_parameters')
161
+ `pano_modify -p 0 --fov=AUTO -o #{@new_input_file_path} #{@new_input_file_path}`
162
+
163
+ logger.info "Read new #{@new_input_file_path}"
164
+ # Read new input pto file
165
+ @input_file = File.new(@new_input_file_path, 'r').read
166
+ images = Image.parse(@input_file)
167
+ ds = images.map(&:d).uniq.sort
168
+ es = images.map(&:e).uniq.sort
169
+
170
+ d_diffs = []
171
+ ds.each_with_index do |_, i|
172
+ next if i == 0
173
+ d_diffs.push((ds[i] - ds[i-1]).abs / 2)
174
+ end
175
+
176
+ e_diffs = []
177
+ es.each_with_index do |_, i|
178
+ next if i == 0
179
+ e_diffs.push((es[i] - es[i-1]).abs / 2)
180
+ end
181
+
182
+ d_map = Hash[ds.map.with_index { |d, i| i == 0 ? [d, d] : [d, d - d_diffs[0..(i-1)].reduce(:+)] }]
183
+ e_map = Hash[es.map.with_index { |e, i| i == 0 ? [e, e] : [e, e - e_diffs[0..(i-1)].reduce(:+)] }]
184
+
185
+ d_min, d_max = d_map.values.minmax
186
+ e_min, e_max = e_map.values.minmax
187
+ d_offset = ((d_max - d_min) / 2.0) + d_min
188
+ e_offset = ((e_max - e_min) / 2.0) + e_min
189
+
190
+ logger.info 'saving new d,e values'
191
+ @lines = @input_file.each_line.map do |line|
192
+ image = images.find { |i| i.raw == line }
193
+ if image
194
+ image.d = (d_map[image.d] - d_offset).round(8).to_s
195
+ image.e = (e_map[image.e] - e_offset).round(8).to_s
196
+ image.to_s
197
+ else
198
+ next line
199
+ end
200
+ end.compact
201
+
202
+ save_file
203
+ end
204
+
205
+ def fix_conversion_errors
206
+ logger.info 'fixing conversion errors'
207
+ @lines = @input_file.each_line.map do |line|
208
+ cp = ControlPoint.parse_line(line)
209
+ if cp && cp.horizontal?
210
+ cp.to_s
211
+ else
212
+ next line
213
+ end
214
+ end.compact
215
+
216
+ save_file
217
+ end
218
+
219
+ def generate_border_line_control_points
220
+ logger.info 'generating border line control points'
221
+ images = Image.parse(@input_file)
222
+ line_control_points = ControlPoint.parse(@input_file, cp_type: :line)
223
+
224
+ # Set vertical and horizontal control points for each image
225
+ images.each do |image|
226
+ vertical_control_points = line_control_points.select { |cp| cp[:n] == image[:id] && cp[:N] == image[:id] && cp[:t] == 1 }
227
+ horizontal_control_points = line_control_points.select { |cp| cp[:n] == image[:id] && cp[:N] == image[:id] && cp[:t] == 2 }
228
+ image[:vertical_control_points] = vertical_control_points
229
+ image[:horizontal_control_points] = horizontal_control_points
230
+ end
231
+
232
+ # Iterate through the edges to create vertical control points across overlapping images
233
+ logger.info 'finding common vertical control points'
234
+ vertical_edges, horizontal_edges = images.map { |image| image[:d] }.minmax, images.map { |image| image[:e] }.minmax
235
+ vertical_control_points = []
236
+ vertical_edges.each do |edge|
237
+ edge_images = images.select { |image| image[:d] == edge }.sort_by { |image| image[:e] }
238
+ edge_images.each_with_index do |image, index|
239
+ next if index == edge_images.length - 1
240
+ next_image = edge_images[index+1]
241
+
242
+ control_points = image[:vertical_control_points].map do |control_point|
243
+ average_x = (control_point[:x] + control_point[:X]) / 2.0
244
+ next_found_control_point = next_image[:vertical_control_points].find do |next_control_point|
245
+ next_average_x = (next_control_point[:x] + next_control_point[:X]) / 2.0
246
+ next_average_x.between?(average_x * 0.98, average_x * 1.02)
247
+ end
248
+ [control_point, next_found_control_point] if next_found_control_point
249
+ end.compact
250
+
251
+ # Special logic for anchor image
252
+ if image[:id] == 0
253
+ control_points = next_image[:vertical_control_points].map do |next_control_point|
254
+ next_average_x = (next_control_point[:x] + next_control_point[:X]) / 2.0
255
+ [{n: 0, x: next_average_x, y: (image[:h]/2.0).round }, next_control_point]
256
+ end
257
+ elsif next_image[:id] == 0
258
+ control_points = image[:vertical_control_points].map do |next_control_point|
259
+ next_average_x = (next_control_point[:x] + next_control_point[:X]) / 2.0
260
+ [{n: 0, x: next_average_x, y: (image[:h]/2.0).round }, next_control_point]
261
+ end
262
+ end
263
+
264
+ control_points.each do |current_control_point, next_control_point|
265
+ vertical_control_points << ControlPoint.new({
266
+ n: current_control_point[:n],
267
+ N: next_control_point[:n],
268
+ x: current_control_point[:x],
269
+ y: current_control_point[:y],
270
+ X: next_control_point[:X],
271
+ Y: next_control_point[:Y],
272
+ t: 1
273
+ })
274
+ end
275
+ end
276
+ end
277
+
278
+ # Iterate through the edges to create horizontal control points across overlapping images
279
+ logger.info 'finding common horizontal control points'
280
+ horizontal_control_points = []
281
+ horizontal_edges.each do |edge|
282
+ edge_images = images.select { |image| image[:e] == edge }.sort_by { |image| image[:d] }
283
+ edge_images.each_with_index do |image, index|
284
+ next if index == edge_images.length - 1
285
+ next_image = edge_images[index+1]
286
+
287
+ control_points = image[:horizontal_control_points].map do |control_point|
288
+ average_y = (control_point[:y] + control_point[:Y]) / 2.0
289
+ next_found_control_point = next_image[:horizontal_control_points].find do |next_control_point|
290
+ next_average_y = (next_control_point[:y] + next_control_point[:Y]) / 2.0
291
+ next_average_y.between?(average_y * 0.98, average_y * 1.02)
292
+ end
293
+ [control_point, next_found_control_point] if next_found_control_point
294
+ end.compact
295
+
296
+ # Special logic for anchor image
297
+ if image[:id] == 0
298
+ control_points = next_image[:horizontal_control_points].map do |next_control_point|
299
+ next_average_y = (next_control_point[:y] + next_control_point[:Y]) / 2.0
300
+ [{n: 0, x: (image[:w]/2.0).round, y: next_average_y }, next_control_point]
301
+ end
302
+ end
303
+
304
+ control_points.each do |current_control_point, next_control_point|
305
+ horizontal_control_points << ControlPoint.new({
306
+ n: current_control_point[:n],
307
+ N: next_control_point[:n],
308
+ x: current_control_point[:x],
309
+ y: current_control_point[:y],
310
+ X: next_control_point[:X],
311
+ Y: next_control_point[:Y],
312
+ t: 2
313
+ })
314
+ end
315
+ end
316
+ end
317
+
318
+ logger.info "writing #{vertical_control_points.count} vertical control points"
319
+ logger.info "writing #{horizontal_control_points.count} horizontal control points"
320
+ control_point_lines_started = false
321
+ @lines = @input_file.each_line.map do |line|
322
+ cp = ControlPoint.parse_line(line)
323
+ if cp.nil?
324
+ # Control point lines ended
325
+ if control_point_lines_started
326
+ control_point_lines_started = false
327
+ (vertical_control_points + horizontal_control_points).map do |control_point|
328
+ logger.debug "image #{control_point[:n]} <> #{control_point[:N]} #{control_point[:t] == 1 ? 'vertical' : 'horizontal'} control point"
329
+ control_point.to_s
330
+ end + [line]
331
+ else
332
+ next line
333
+ end
334
+ else
335
+ control_point_lines_started = true
336
+ next line
337
+ end
338
+ end.compact.flatten
339
+
340
+ save_file
341
+ end
342
+
343
+ def merge_image_parameters
344
+ logger.info 'merging image parameters'
345
+ control_points = ControlPoint.parse(@compare_file)
346
+ v_lines_started = false
347
+ @lines = @input_file.each_line.map do |line|
348
+ if line[0] == 'v'
349
+ v_lines_started = true
350
+ next line
351
+ elsif v_lines_started
352
+ v_lines_started = false
353
+ control_points.map(&:to_s).join
354
+ else
355
+ next line
356
+ end
357
+ end.compact
358
+
359
+ save_file
360
+ end
361
+
362
+ def prepare_for_pr0ntools
363
+ logger.info 'preparing for pr0ntools'
364
+
365
+ images = Image.parse(@input_file)
366
+ ds = images.map(&:d).uniq.sort
367
+ es = images.map(&:e).uniq.sort
368
+ fov = images.map(&:v).uniq.find { |v| v != 0.0 }
369
+ images.each do |i|
370
+ i[:original_n] = i.n
371
+ i.n = "c#{ds.reverse.index(i.d)}_r#{es.reverse.index(i.e)}.jpg"
372
+ i.v = fov
373
+ end
374
+ images = images.sort_by(&:n)
375
+
376
+ index = -1
377
+ @lines = @input_file.each_line.map do |line|
378
+ image = images.find { |i| i.raw == line }
379
+ next line unless image
380
+ index += 1
381
+ images[index].to_s
382
+ end.compact
383
+
384
+ dir = '/pr0ntools/'
385
+ logger.info "creating #{dir}"
386
+ new_dir = Pathname.new(@output).dirname.to_s + dir
387
+ FileUtils.mkdir_p(new_dir)
388
+
389
+ logger.info 'renaming files to format cX_rY.jpg'
390
+ images.each { |image| FileUtils.cp(image[:original_n], new_dir + image.n) }
391
+
392
+ logger.info 'creating scan.json'
393
+ image_first_col = images.find { |image| image.n == 'c0_r0.jpg' }
394
+ image_second_col = images.find { |image| image.n == 'c1_r0.jpg' }
395
+ overlap = ((image_second_col.d - image_first_col.d).abs / image_first_col.w.to_f).round(4)
396
+ File.open(new_dir+'scan.json', 'w') { |f| f.puts %Q({"overlap":#{overlap}}) }
397
+
398
+ save_file(dir: dir)
399
+ end
400
+
401
+ def remove_anchor_variables
402
+ logger.info 'removing anchor variables'
403
+ variables = OptimisationVariable.parse(@input_file)
404
+
405
+ @lines = @input_file.each_line.map do |line|
406
+ variable = variables.find { |v| v.raw == line }
407
+ if variable && (variable.d == 0 || variable.e == 0)
408
+ next
409
+ else
410
+ next line
411
+ end
412
+ end.compact
413
+
414
+ save_file
415
+ end
416
+
417
+ def remove_long_lines
418
+ logger.info 'removing long lines'
419
+
420
+ images = Image.parse(@input_file)
421
+ control_points = ControlPoint.get_detailed_info(@input, cp_type: :normal)
422
+ control_points.each do |cp|
423
+ image1 = images.find { |i| cp.n1 == i.id }
424
+ image2 = images.find { |i| cp.n2 == i.id }
425
+ # dist = ((image1.normal_x + cp.x1) - (image2.normal_x + cp.x2)) ** 2 + ((image1.normal_y + cp.y1) - (image2.normal_y + cp.y2)) ** 2
426
+ dx = (image1.d - cp.x1) - (image2.d - cp.x2)
427
+ dy = (image1.e - cp.y1) - (image2.e - cp.y2)
428
+ logger.debug "#{cp.to_s} distrt #{Math.sqrt(dx**2+dy**2)} iy1 #{image1.normal_y} iy2 #{image2.normal_y}"
429
+ end
430
+ logger.debug "avg #{control_points.map(&:dist).reduce(:+)/control_points.count.to_f}"
431
+ end
432
+
433
+ def standardize_roll
434
+ logger.info 'standardizing roll'
435
+ images = Image.parse(@input_file)
436
+ rolls = images.map(&:r)
437
+ average_roll = rolls.reduce(:+).to_f / rolls.count
438
+ logger.debug "average roll: #{average_roll}"
439
+ roll_std = Math.sqrt(rolls.map { |r| (r - average_roll) ** 2 }.reduce(:+) / (rolls.count - 1))
440
+ logger.debug "roll std: #{roll_std}"
441
+ new_rolls = rolls.select { |r| (r - average_roll).abs < roll_std }
442
+ logger.info "removed #{rolls.count - new_rolls.count} outliers"
443
+ new_average_roll = new_rolls.reduce(:+).to_f / new_rolls.count
444
+ logger.info "converting all rolls to #{new_average_roll}"
445
+
446
+ @lines = @input_file.each_line.map do |line|
447
+ image = images.find { |i| i.raw == line }
448
+ if image
449
+ image.r = new_average_roll
450
+ image.to_s
451
+ else
452
+ next line
453
+ end
454
+ end.compact
455
+
456
+ save_file
457
+ end
458
+
459
+ private
460
+
461
+ def save_file(dir: nil)
462
+ logger.info 'saving file'
463
+ if dir
464
+ new_dir = Pathname.new(@output).dirname.to_s + dir
465
+ FileUtils.mkdir_p(new_dir)
466
+ @output = new_dir + Pathname.new(@output).basename.to_s
467
+ @output_file = File.new(@output, 'w')
468
+ end
469
+
470
+ @lines.each do |good_line|
471
+ @output_file.write good_line
472
+ end
473
+
474
+ @output_file.close
475
+ end
476
+ end
477
+ end
@@ -0,0 +1,3 @@
1
+ module Panomosity
2
+ VERSION = '0.1.1'
3
+ end
data/lib/panomosity.rb ADDED
@@ -0,0 +1,53 @@
1
+ require 'panomosity/control_point'
2
+ require 'panomosity/image'
3
+ require 'panomosity/optimisation_variable'
4
+ require 'panomosity/runner'
5
+ require 'panomosity/version'
6
+ require 'pathname'
7
+ require 'fileutils'
8
+ require 'optparse'
9
+
10
+ module Panomosity
11
+ def self.parse(arguments)
12
+ options = {}
13
+ OptionParser.new do |parser|
14
+ parser.banner = 'Usage: panomosity command [options]'
15
+ parser.separator ''
16
+ parser.separator 'Specific options:'
17
+
18
+ parser.on('-i', '--input PTO', 'Input PTO file') do |pto|
19
+ options[:input] = pto
20
+ end
21
+
22
+ parser.on('-o', '--output PTO', 'Output PTO file') do |pto|
23
+ options[:output] = pto
24
+ end
25
+
26
+ parser.on('-c', '--csv [CSV]', 'CSV file for reference') do |csv|
27
+ options[:csv] = csv
28
+ end
29
+
30
+ parser.on('-k', '--compare [PTO]', 'Compare PTO file for reference') do |pto|
31
+ options[:compare] = pto
32
+ end
33
+
34
+ parser.on('--without-cropping', 'Do not crop when running "crop_centers" (usually when the original run failed)') do |wc|
35
+ options[:without_cropping] = wc
36
+ end
37
+
38
+ parser.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
39
+ options[:verbose] = v
40
+ end
41
+
42
+ parser.on('-h', '--help', 'Display this screen') do
43
+ puts parser
44
+ exit
45
+ end
46
+
47
+ parser.parse!(arguments)
48
+ end
49
+
50
+ runner = Runner.new(options)
51
+ runner.run(ARGV[0])
52
+ end
53
+ end
@@ -0,0 +1,27 @@
1
+
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'panomosity/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'panomosity'
8
+ spec.version = Panomosity::VERSION
9
+ spec.authors = ['Oliver Garcia']
10
+ spec.email = ['ogarci5@gmail.com']
11
+
12
+ spec.summary = %q{Wrapper for the PTO file parsing needed for PanoTools.}
13
+ spec.description = %q{Custom scripts to help with PTO parsing and different strategies.}
14
+ spec.homepage = 'https://github.com/elevatesystems/panomosity'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = 'exe'
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = %w(lib)
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.16'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'rspec', '~> 3.0'
27
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: panomosity
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Oliver Garcia
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-08-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: Custom scripts to help with PTO parsing and different strategies.
56
+ email:
57
+ - ogarci5@gmail.com
58
+ executables:
59
+ - panomosity
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - ".rspec"
65
+ - ".travis.yml"
66
+ - CODE_OF_CONDUCT.md
67
+ - Gemfile
68
+ - Gemfile.lock
69
+ - LICENSE.txt
70
+ - README.md
71
+ - Rakefile
72
+ - bin/console
73
+ - bin/setup
74
+ - exe/panomosity
75
+ - lib/panomosity.rb
76
+ - lib/panomosity/control_point.rb
77
+ - lib/panomosity/image.rb
78
+ - lib/panomosity/optimisation_variable.rb
79
+ - lib/panomosity/runner.rb
80
+ - lib/panomosity/version.rb
81
+ - panomosity.gemspec
82
+ homepage: https://github.com/elevatesystems/panomosity
83
+ licenses:
84
+ - MIT
85
+ metadata: {}
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ requirements: []
101
+ rubyforge_project:
102
+ rubygems_version: 2.7.3
103
+ signing_key:
104
+ specification_version: 4
105
+ summary: Wrapper for the PTO file parsing needed for PanoTools.
106
+ test_files: []