tracksperanto 1.0.0

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.
Files changed (39) hide show
  1. data/Manifest.txt +37 -0
  2. data/README.txt +56 -0
  3. data/Rakefile +13 -0
  4. data/bin/tracksperanto +74 -0
  5. data/lib/export/base.rb +35 -0
  6. data/lib/export/flame_stabilizer.rb +50 -0
  7. data/lib/export/mux.rb +33 -0
  8. data/lib/export/pftrack.rb +30 -0
  9. data/lib/export/shake_text.rb +29 -0
  10. data/lib/export/syntheyes.rb +32 -0
  11. data/lib/import/base.rb +24 -0
  12. data/lib/import/flame_stabilizer.rb +34 -0
  13. data/lib/import/shake_script.rb +127 -0
  14. data/lib/import/shake_text.rb +29 -0
  15. data/lib/import/syntheyes.rb +32 -0
  16. data/lib/middleware/base.rb +28 -0
  17. data/lib/middleware/scaler.rb +24 -0
  18. data/lib/middleware/slipper.rb +12 -0
  19. data/lib/pipeline.rb +54 -0
  20. data/lib/tracksperanto.rb +131 -0
  21. data/test/.DS_Store +0 -0
  22. data/test/helper.rb +5 -0
  23. data/test/samples/.DS_Store +0 -0
  24. data/test/samples/flyover2DP_syntheyes.txt +12687 -0
  25. data/test/samples/megaTrack.action.3dtrack.stabilizer +29793 -0
  26. data/test/samples/one_shake_tracker.txt +49 -0
  27. data/test/samples/one_shake_tracker_from_first.txt +188 -0
  28. data/test/samples/shake_tracker_nodes.shk +921 -0
  29. data/test/samples/shake_tracker_nodes_to_syntheyes.txt +4091 -0
  30. data/test/samples/three_tracks_in_one_stabilizer.shk +320 -0
  31. data/test/samples/two_shake_trackers.txt +78 -0
  32. data/test/samples/two_tracks_in_one_tracker.shk +319 -0
  33. data/test/test_flame_block.rb +19 -0
  34. data/test/test_keyframe.rb +32 -0
  35. data/test/test_shake_export.rb +58 -0
  36. data/test/test_shake_script_import.rb +50 -0
  37. data/test/test_shake_text_import.rb +44 -0
  38. data/test/test_syntheyes_import.rb +25 -0
  39. metadata +111 -0
@@ -0,0 +1,127 @@
1
+ require 'strscan'
2
+
3
+ class Tracksperanto::Import::ShakeScript < Tracksperanto::Import::Base
4
+
5
+ # Allow literal strings instead of Regexps
6
+ class SloppyParser < StringScanner
7
+ def skip_until(pattern)
8
+ pattern.is_a?(String) ? super(/#{Regexp.escape(pattern)}/m) : super(pattern)
9
+ end
10
+
11
+ def scan_until(pattern)
12
+ pattern.is_a?(String) ? super(/#{Regexp.escape(pattern)}/m) : super(pattern)
13
+ end
14
+ end
15
+
16
+ class ValueAt
17
+ attr_accessor :value, :frame
18
+ def self.from_string(str)
19
+ v = new
20
+ val_s, frame_s = str.scan(/(.+)@(\d+)/).to_a.flatten
21
+ v.value, v.frame = val_s.to_f, frame_s.to_i
22
+ v
23
+ end
24
+ end
25
+
26
+ class CurveParser < SloppyParser
27
+ BLOCK_ENDS = /\d,|\)/
28
+
29
+ attr_reader :values
30
+ def initialize(with_curve_arg)
31
+ @values = []
32
+
33
+ super(with_curve_arg.to_s)
34
+
35
+ # Skip the interpolation name, we assume it to be linear anyway
36
+ skip_until '('
37
+
38
+ # Skip the first defining parameter whatever that might be
39
+ skip_until ','
40
+
41
+ loop do
42
+ break unless (value_at = scan_until(BLOCK_ENDS))
43
+ # Grab the value
44
+ val = ValueAt.from_string(value_at)
45
+
46
+ @values << val
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ class TrackerParser < SloppyParser
53
+ attr_reader :x_curve, :y_curve, :c_curve, :name
54
+
55
+ def initialize(with_tracker_args)
56
+ super(with_tracker_args)
57
+
58
+ # Name me!
59
+ @name = scan_until(/(\w+) /).strip
60
+
61
+ # All the tracker arguments
62
+ 17.times { skip_until ',' } # input data
63
+
64
+ # Grab the curves
65
+ @x_curve, @y_curve, @c_curve = (0..2).map{ CurveParser.new(scan_until('),')).values }
66
+
67
+ # if the next argument is an integer, we reached the end of the tracks. If not - make a nested one.
68
+ end
69
+
70
+ def curves
71
+ [@x_curve, @y_curve, @c_curve]
72
+ end
73
+ end
74
+
75
+ class TrackParser < SloppyParser
76
+ def initialize(with_tracker_block)
77
+ # scan until the first " - name of the track
78
+ skip_until ','
79
+ # scan values
80
+ # discard the 8 box determinators
81
+ 7.times { skip_until ',' }
82
+ # discard the box animation
83
+ 2.times { skip_until ',' }
84
+ end
85
+ end
86
+
87
+ TRACKER_PATTERN = /((\w+) = Tracker\(([^;]+))/m
88
+
89
+ def parse(sript_file_content)
90
+
91
+ trackers = []
92
+
93
+ sript_file_content.scan(TRACKER_PATTERN).each_with_index do | tracker_text_block, idx |
94
+
95
+ parser = TrackerParser.new(tracker_text_block.to_s)
96
+
97
+ tracker = Tracksperanto::Tracker.new{|t| t.name = parser.name }
98
+
99
+ x_keyframes, y_keyframes, residual_keyframes = TrackerParser.new(tracker_text_block.to_s).curves
100
+ x_keyframes.each_with_index do | value_at, kf_index |
101
+
102
+ # Find the Y keyframe with the same frame
103
+ matching_y = y_keyframes.find{|f| f.frame == value_at.frame }
104
+
105
+ # Find the correlation keyframe with the same frame
106
+ matching_residual = residual_keyframes.find{|f| f.frame == value_at.frame }
107
+
108
+ # Skip frame if only one keyframe is present
109
+ if !matching_y
110
+ STDERR.puts "Cannot find matching Y for frame #{value_at.frame} in tracker #{parser.name}, skipping keyframe"
111
+ next
112
+ end
113
+
114
+ tracker.keyframes << Tracksperanto::Keyframe.new do |k|
115
+ k.frame = (value_at.frame - 1)
116
+ k.abs_x = value_at.value
117
+ k.abs_y = matching_y.value
118
+ k.residual = 1 - (matching_residual.value rescue 1.0)
119
+ end
120
+ end
121
+
122
+ trackers << tracker
123
+ end
124
+
125
+ trackers
126
+ end
127
+ end
@@ -0,0 +1,29 @@
1
+ require 'stringio'
2
+
3
+ class Tracksperanto::Import::ShakeText < Tracksperanto::Import::Base
4
+
5
+ def parse(track_file_content)
6
+ trackers = []
7
+ io = StringIO.new(track_file_content)
8
+ until io.eof?
9
+ line = io.gets
10
+ if line =~ /TrackName (.+)/
11
+ trackers << Tracksperanto::Tracker.new{|t| t.name = $1 }
12
+ # Toss the next following string - header
13
+ io.gets
14
+ else
15
+ keyframe_values = line.split
16
+ next if keyframe_values.length < 4
17
+
18
+ trackers[-1].keyframes << Tracksperanto::Keyframe.new do | kf |
19
+ kf.frame = (keyframe_values[0].to_i - 1)
20
+ kf.abs_x = keyframe_values[1]
21
+ kf.abs_y = keyframe_values[2]
22
+ kf.residual = (1 - keyframe_values[3].to_f)
23
+ end
24
+ end
25
+ end
26
+
27
+ trackers
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ class Tracksperanto::Import::Syntheyes < Tracksperanto::Import::Base
2
+ def parse(file_content)
3
+ trackers = []
4
+ file_content.split("\n").each do | line |
5
+ name, frame, x, y, corr = line.split
6
+
7
+ # Do we already have this tracker?
8
+ t = trackers.find {|e| e.name == name}
9
+ if !t
10
+ t = Tracksperanto::Tracker.new{|t| t.name = name }
11
+ trackers << t
12
+ end
13
+
14
+ # Add the keyframe
15
+ t.keyframes << Tracksperanto::Keyframe.new do |e|
16
+ e.frame = frame
17
+ e.abs_x = convert_from_uv(width, x)
18
+ e.abs_y = height - convert_from_uv(height, y) # Convert TL to BL
19
+ e.residual = corr
20
+ end
21
+ end
22
+
23
+ trackers
24
+ end
25
+
26
+ private
27
+ def convert_from_uv(absolute_side, uv_value)
28
+ # First, start from zero (-.1 becomes .4)
29
+ value_off_corner = (uv_value.to_f / 2) + 0.5
30
+ absolute_side * value_off_corner
31
+ end
32
+ end
@@ -0,0 +1,28 @@
1
+ class Tracksperanto::Middleware::Base
2
+ include Tracksperanto::Casts
3
+
4
+ def initialize(exporter)
5
+ @exporter = exporter
6
+ end
7
+
8
+ # Called on export start
9
+ def start_export( img_width, img_height)
10
+ @exporter.start_export(img_width, img_height)
11
+ end
12
+
13
+ # Called on export end
14
+ def end_export
15
+ @exporter.end_export
16
+ end
17
+
18
+ # Called on tracker start, one for each tracker. Start of the next tracker
19
+ # signifies that the previous tracker has passed by
20
+ def start_tracker_segment(tracker_name)
21
+ @exporter.start_tracker_segment(tracker_name)
22
+ end
23
+
24
+ # Called for each tracker keyframe
25
+ def export_point(at_frame_i, abs_float_x, abs_float_y, float_residual)
26
+ @exporter.export_point(at_frame_i, abs_float_x, abs_float_y, float_residual)
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ class Tracksperanto::Middleware::Scaler < Tracksperanto::Middleware::Base
2
+ DEFAULT_FACTOR = 1
3
+
4
+ attr_accessor :x_factor, :y_factor
5
+
6
+ # Called on export start
7
+ def start_export( img_width, img_height)
8
+ # Compute the average factor
9
+ @residual_factor = (x_factor + y_factor) / 2
10
+ super( (img_width * x_factor), (img_height * y_factor))
11
+ end
12
+
13
+ def y_factor
14
+ @y_factor || DEFAULT_FACTOR
15
+ end
16
+
17
+ def x_factor
18
+ @x_factor || DEFAULT_FACTOR
19
+ end
20
+
21
+ def export_point(frame, float_x, float_y, float_residual)
22
+ super(frame, (float_x * x_factor), (float_y * y_factor), (float_residual * @residual_factor))
23
+ end
24
+ end
@@ -0,0 +1,12 @@
1
+ class Tracksperanto::Middleware::Slipper < Tracksperanto::Middleware::Base
2
+ DEFAULT_SLIP = 0
3
+ attr_accessor :slip
4
+
5
+ def slip
6
+ @slip.to_i || DEFAULT_SLIP
7
+ end
8
+
9
+ def export_point(frame, float_x, float_y, float_residual)
10
+ super(frame + slip, float_x, float_y, float_residual)
11
+ end
12
+ end
@@ -0,0 +1,54 @@
1
+ class Tracksperanto::Pipeline
2
+ attr_accessor :converted_points
3
+ attr_accessor :converted_keyframes
4
+ def run(from_input_file, pix_w, pix_h, parser_class)
5
+ # Read the input file
6
+ read_data = File.read(from_input_file)
7
+ file_name = File.basename(from_input_file).gsub(/\.([^\.]+)$/, '')
8
+
9
+ # Assign the parser
10
+ parser = parser_class.new
11
+ parser.width = pix_w
12
+ parser.height = pix_h
13
+
14
+ # Setup a multiplexer
15
+ mux = Tracksperanto::Export::Mux.new(
16
+ Tracksperanto.exporters.map do | exporter_class |
17
+ export_name = "%s_%s" % [file_name, exporter_class.desc_and_extension]
18
+ outfile = File.dirname(from_input_file) + '/' + export_name
19
+ io = File.open(outfile, 'w')
20
+ exporter_class.new(io)
21
+ end
22
+ )
23
+
24
+ # Setup middlewares - skip for now
25
+ scaler = Tracksperanto::Middleware::Scaler.new(mux)
26
+ slipper = Tracksperanto::Middleware::Slipper.new(scaler)
27
+ golden = Tracksperanto::Middleware::Golden.new(slipper)
28
+
29
+ yield(scaler, slipper, golden) if block_given?
30
+
31
+ processor = golden
32
+
33
+ # Run the export
34
+ trackers = parser.parse(read_data)
35
+ processor.start_export(parser.width, parser.height)
36
+ trackers.each do | t |
37
+
38
+ @converted_points ||= 0
39
+ @converted_points += 1
40
+
41
+ processor.start_tracker_segment(t.name)
42
+ t.keyframes.each do | kf |
43
+
44
+ @converted_keyframes ||= 0
45
+ @converted_keyframes += 1
46
+
47
+ processor.export_point(kf.frame, kf.abs_x, kf.abs_y, kf.residual)
48
+ end
49
+ end
50
+
51
+ processor.end_export
52
+ end
53
+ end
54
+
@@ -0,0 +1,131 @@
1
+ module Tracksperanto
2
+ VERSION = '1.0.0'
3
+
4
+ module Import; end
5
+ module Export; end
6
+ module Middleware; end
7
+
8
+ class << self
9
+ # Returns the array of all exporter classes defined
10
+ attr_accessor :exporters
11
+
12
+ # Returns the array of all importer classes defined
13
+ attr_accessor :importers
14
+
15
+ # Returns the array of all available middlewares
16
+ attr_accessor :middlewares
17
+ end
18
+ self.exporters, self.importers, self.middlewares = [], [], []
19
+
20
+ # Helps to define things that will forcibly become floats, integers or strings
21
+ module Casts
22
+ def self.included(into)
23
+ into.extend(self)
24
+ super
25
+ end
26
+
27
+ # Same as attr_accessor but will always convert to Float internally
28
+ def cast_to_float(*attributes)
29
+ attributes.each do | an_attr |
30
+ define_method(an_attr) { instance_variable_get("@#{an_attr}").to_f }
31
+ define_method("#{an_attr}=") { |to| instance_variable_set("@#{an_attr}", to.to_f) }
32
+ end
33
+ end
34
+
35
+ # Same as attr_accessor but will always convert to Integer/Bignum internally
36
+ def cast_to_int(*attributes)
37
+ attributes.each do | an_attr |
38
+ define_method(an_attr) { instance_variable_get("@#{an_attr}").to_i }
39
+ define_method("#{an_attr}=") { |to| instance_variable_set("@#{an_attr}", to.to_i) }
40
+ end
41
+ end
42
+
43
+ # Same as attr_accessor but will always convert to String internally
44
+ def cast_to_string(*attributes)
45
+ attributes.each do | an_attr |
46
+ define_method(an_attr) { instance_variable_get("@#{an_attr}").to_s }
47
+ define_method("#{an_attr}=") { |to| instance_variable_set("@#{an_attr}", to.to_s) }
48
+ end
49
+ end
50
+ end
51
+
52
+ module Safety
53
+ def self.included(into)
54
+ into.extend(self)
55
+ super
56
+ end
57
+
58
+ # Inject a reader that checks for nil
59
+ def safe_reader(*attributes)
60
+ attributes.each do | an_attr |
61
+ alias_method "#{an_attr}_without_nil_protection", an_attr
62
+ define_method(an_attr) do
63
+ val = send("#{an_attr}_without_nil_protection")
64
+ raise "Expected #{an_attr} on #{self} not to be nil" if val.nil?
65
+ val
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ module BlockInit
72
+ def initialize
73
+ yield(self) if block_given?
74
+ end
75
+ end
76
+
77
+ # Internal representation of a tracker
78
+ class Tracker
79
+ include Casts
80
+ include BlockInit
81
+
82
+ # Contains the array of all Keyframe objects for this tracker
83
+ attr_accessor :name
84
+
85
+ # Contains the array of all Keyframe objects for this tracker
86
+ attr_accessor :keyframes
87
+
88
+ cast_to_string :name
89
+
90
+ def initialize
91
+ @name, @keyframes = 'Tracker', []
92
+ super if block_given?
93
+ end
94
+ end
95
+
96
+ # Internal representation of a keyframe
97
+ class Keyframe
98
+ include Casts
99
+ include BlockInit
100
+
101
+ # Absolute integer frame where this keyframe is placed, 0 based
102
+ attr_accessor :frame
103
+
104
+ # Absolute float X value of the point, zero is lower left
105
+ attr_accessor :abs_x
106
+
107
+ # Absolute float Y value of the point, zero is lower left
108
+ attr_accessor :abs_y
109
+
110
+ # Absolute float residual (0 is "spot on")
111
+ attr_accessor :residual
112
+
113
+ cast_to_float :abs_x, :abs_y, :residual
114
+ cast_to_int :frame
115
+ end
116
+ end
117
+
118
+ # Load importers
119
+ Dir.glob(File.dirname(__FILE__) + '/import/*.rb').each do | i |
120
+ require i
121
+ end
122
+
123
+ # Load exporters
124
+ Dir.glob(File.dirname(__FILE__) + '/export/*.rb').each do | i |
125
+ require i
126
+ end
127
+
128
+ # Load middleware
129
+ Dir.glob(File.dirname(__FILE__) + '/middleware/*.rb').each do | i |
130
+ require i
131
+ end
Binary file