tracksperanto 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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,37 @@
1
+ Manifest.txt
2
+ README.txt
3
+ Rakefile
4
+ bin/tracksperanto
5
+ lib/export/base.rb
6
+ lib/export/flame_stabilizer.rb
7
+ lib/export/mux.rb
8
+ lib/export/pftrack.rb
9
+ lib/export/shake_text.rb
10
+ lib/export/syntheyes.rb
11
+ lib/import/base.rb
12
+ lib/import/flame_stabilizer.rb
13
+ lib/import/shake_script.rb
14
+ lib/import/shake_text.rb
15
+ lib/import/syntheyes.rb
16
+ lib/middleware/base.rb
17
+ lib/middleware/scaler.rb
18
+ lib/middleware/slipper.rb
19
+ lib/pipeline.rb
20
+ lib/tracksperanto.rb
21
+ test/.DS_Store
22
+ test/helper.rb
23
+ test/samples/.DS_Store
24
+ test/samples/flyover2DP_syntheyes.txt
25
+ test/samples/megaTrack.action.3dtrack.stabilizer
26
+ test/samples/one_shake_tracker.txt
27
+ test/samples/one_shake_tracker_from_first.txt
28
+ test/samples/shake_tracker_nodes.shk
29
+ test/samples/shake_tracker_nodes_to_syntheyes.txt
30
+ test/samples/three_tracks_in_one_stabilizer.shk
31
+ test/samples/two_shake_trackers.txt
32
+ test/samples/two_tracks_in_one_tracker.shk
33
+ test/test_keyframe.rb
34
+ test/test_shake_export.rb
35
+ test/test_shake_script_import.rb
36
+ test/test_shake_text_import.rb
37
+ test/test_syntheyes_import.rb
@@ -0,0 +1,56 @@
1
+ = Tracksperanto
2
+
3
+ * http://guerilla-di.org/tracksperanto
4
+
5
+ == Description
6
+
7
+ Tracksperanto is a universal 2D-track translator between many apps.
8
+
9
+ Import support:
10
+ * Shake script (one tracker node per tracker)
11
+ * Shake tracker node export (textfile with many tracks per file), also exported by Boujou and others
12
+ * PFTrack 2dt files
13
+ * Syntheyes 2D tracking data exports (UV coordinates)
14
+
15
+ Export support:
16
+
17
+ * Shake text file (many trackers per file), also accepted by Boujou
18
+ * PFTrack 2dt file (with residuals)
19
+ * Syntheyes 2D tracking data import (UV coordinates)
20
+
21
+ The main way to use Tracksperanto is to use the supplied "tracksperanto" binary, like so:
22
+
23
+ tracksperanto -f ShakeScript -w 1920 -h 1080 /Films/Blockbuster/Shots/001/script.shk
24
+
25
+ ShakeScript is the name of the translator that will be used to read the file (many apps export tracks as .txt files so
26
+ there is no way for us to autodetect them all). -w and -h stand for Width and Height and define the size of your comp (different
27
+ tracking apps use different coordinate systems and we need to know the size of the comp to properly convert these). You also have
28
+ additional options like -xs, -ys and --slip - consult the usage info for the tracksperanto binary.
29
+
30
+ The converted files will be saved in the same directory as the source, if resulting converted files already exist ++they will be overwritten without warning++.
31
+
32
+ == Modularity
33
+
34
+ Tracksperanto supports many export and import formats. It also can help when you need to import and export the same format,
35
+ but you need some operation applied to the result (like scaling a proxy track up). Internally, Tracksperanto talks
36
+ Exporters, Importers and Middlewares. Any processing chain usually works like this:
37
+
38
+ 1) The tracker file is read and trackers and their keyframes are extracted, converting them to the internal representation.
39
+ For some formats you need to supply the width and height of the source material
40
+ 2) The trackers and their keyframes are dumped to all export formats Tracksperanto supports, optionally passing through middleware
41
+
42
+ == Internal coordinate system
43
+
44
+ Frame numbers start from zero (frame 0 is first frame of the track).
45
+
46
+ Tracksperanto uses the Shake coordinates as base. Image is Y-positive, X-positive, absolute pixel values up and right (zero is in the
47
+ lower left corner). Some apps use a different coordinate system so translation will take place on import or on export, respectively.
48
+
49
+ We also use residual and not correlation (residual is how far the tracker strolls away, correlation is how sure the tracker is
50
+ about what it's doing). Residual is the inverse of correlation (with total correlation of one the residual excursion becomes zero).
51
+
52
+ == Limitations
53
+
54
+ Information about the search area, reference area and offset is not passed along (outside of scope for the app and different trackers handle
55
+ these differently, if at all). For some modules no residual will be passed along (3D tracking apps generally do not export residual with
56
+ backprojected 3D features).
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'hoe'
3
+ require './lib/tracksperanto'
4
+
5
+
6
+ # Disable spurious warnings when running tests, ActiveMagic cannot stand -w
7
+ Hoe::RUBY_FLAGS.replace ENV['RUBY_FLAGS'] || "-I#{%w(lib test).join(File::PATH_SEPARATOR)}" +
8
+ (Hoe::RUBY_DEBUG ? " #{RUBY_DEBUG}" : '')
9
+
10
+ Hoe.new('tracksperanto', Tracksperanto::VERSION) do |p|
11
+ p.rubyforge_name = 'guerilla-di'
12
+ p.developer('Julik Tarkhanov', 'me@julik.nl')
13
+ end
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env ruby
2
+ # == Synopsis
3
+ #
4
+ # Translate a 2D track file from a single format to many others
5
+ #
6
+ # == Usage
7
+ #
8
+ # tracksperanto -f shakescript /Films/Blockbuster/Shots/001/script.shk -w 1920 -h 1080
9
+ #
10
+ # == Author
11
+ # Julik <me@julik.nl>
12
+
13
+ require File.dirname(__FILE__) + '/../lib/tracksperanto'
14
+ require File.dirname(__FILE__) + '/../lib/pipeline'
15
+ require 'optparse'
16
+
17
+ # Sane defaults
18
+ reader_klass = Tracksperanto::Import::ShakeScript
19
+ width = 1920
20
+ height = 1080
21
+ scale_x = 1.0
22
+ scale_y = 1.0
23
+ slip = 0
24
+ golden_tracks = false
25
+
26
+ readers = Tracksperanto::Import.constants.map{|e| e.to_s }.reject{|e| e == 'Base'}
27
+ informative_readers = readers.reject{|e| e == 'ShakeScript'}
28
+
29
+ parser = OptionParser.new do | p |
30
+ p.banner = "Usage: tracksperanto -f ShakeScript -w 1920 -h 1080 /Films/Blockbuster/Shots/001/script.shk"
31
+ p.on(" -f", "--from TRANSLATOR", String, "Use the specific import translator (defaults to ShakeScript, but can be \
32
+ #{informative_readers.join(', ')})") do | f |
33
+ begin
34
+ reader_klass = Tracksperanto::Import.const_get(f)
35
+ rescue NameError => e
36
+ reader_list = readers.join("\n\t")
37
+ STDERR.puts "Unknown reader #{f.inspect}, available readers:\n\t#{readers}"
38
+ exit(-1)
39
+ end
40
+ end
41
+ p.on(" -w", "--width WIDTH_IN_PIXELS", Integer, "Absolute input comp width in pixels") { |w| width = w }
42
+ p.on(" -h", "--height HEIGHT_IN_PIXELS", Integer, "Absolute input comp height in pixels") {|w| height = w }
43
+ p.on(" -xs", "--xscale X_SCALING_FACTOR", Float, "Scale the result in X by this factor (1.0 is the default)") {|sx| scale_x = sx }
44
+ p.on(" -ys", "--yscale Y_SCALING_FACTOR", Float, "Scale the result in Y by this factor (1.0 is the default)") {|sy| scale_y = sy }
45
+ p.on(" -s", "--slip FRAMES", Integer, "Slip the result by this number of frames, positive is 'later'") {|sy| slip = sy }
46
+ p.on(" -g", "--golden", "Reset the residuals of all trackers to 0 (ignore correlation)") {|g_flag|
47
+ golden_tracks = g_flag
48
+ }
49
+ end
50
+
51
+ begin
52
+ parser.parse!
53
+ rescue OptionParser::MissingArgument => e
54
+ STDERR.puts "Unknown argument: #{e.message}"
55
+ puts parser
56
+ exit(-1)
57
+ end
58
+
59
+ input_file = ARGV.pop
60
+ if !input_file
61
+ STDERR.puts "No input file provided - should be the last argument"
62
+ puts parser
63
+ exit(-1)
64
+ end
65
+
66
+ pipe = Tracksperanto::Pipeline.new
67
+ pipe.run(input_file, width, height, reader_klass) do | scaler, slipper, golden |
68
+ slipper.slip = slip
69
+ scaler.x_factor = scale_x
70
+ scaler.y_factor = scale_y
71
+ golden.enabled = golden_tracks
72
+ end
73
+
74
+ puts "Converted #{pipe.converted_points} trackers"
@@ -0,0 +1,35 @@
1
+ # Base exporter
2
+ class Tracksperanto::Export::Base
3
+
4
+ def self.inherited(by)
5
+ Tracksperanto.exporters << by
6
+ super
7
+ end
8
+
9
+ # Should return the suffix and extension of this export file (like "_flame.stabilizer"). It's a class
10
+ # method because it gets requested before the exporter is instantiated
11
+ def self.desc_and_extension
12
+ "data.txt"
13
+ end
14
+
15
+ def initialize(write_to_io)
16
+ @io = write_to_io
17
+ end
18
+
19
+ # Called on export start
20
+ def start_export( img_width, img_height)
21
+ end
22
+
23
+ # Called on export end
24
+ def end_export
25
+ end
26
+
27
+ # Called on tracker start, one for each tracker. Start of the next tracker
28
+ # signifies that the previous tracker has passed by
29
+ def start_tracker_segment(tracker_name)
30
+ end
31
+
32
+ # Called for each tracker keyframe
33
+ def export_point(at_frame_i, abs_float_x, abs_float_y, float_residual)
34
+ end
35
+ end
@@ -0,0 +1,50 @@
1
+ class Tracksperanto::Export::FlameStabilizer < Tracksperanto::Export::Base
2
+ PREAMBLE = '' #__DATA__.read
3
+
4
+ # Should return the suffix and extension of this export file (like "_flame.stabilizer")
5
+ def self.desc_and_extension
6
+ "flame.stabilizer"
7
+ end
8
+
9
+ def start_export(w, h)
10
+ @width = w, @height = h
11
+ end
12
+
13
+ def start_tracker_segment(tracker_name)
14
+ @tracker_count ||= 0
15
+ @tracker_count += 1
16
+ end
17
+
18
+ def end_export
19
+ preamble = PREAMBLE % [ @tracker_count, @width, @height]
20
+ end
21
+
22
+ def export_point(frame, abs_float_x, abs_float_y, float_residual)
23
+ end
24
+ end
25
+
26
+ __END__
27
+ StabilizerFileVersion 5.0
28
+ CreationDate Tue Dec 9 21:02:02 2008
29
+
30
+
31
+ NbTrackers %d
32
+ Selected 0
33
+ FrameWidth %d
34
+ FrameHeight %d
35
+ AutoKey yes
36
+ MotionPath yes
37
+ Icons yes
38
+ AutoPan no
39
+ EditMode 1
40
+ Format 0
41
+ Padding
42
+ Red 0
43
+ Green 0
44
+ Blue 0
45
+ Oversampling no
46
+ Opacity 50
47
+ Zoom 3
48
+ Field no
49
+ Backward no
50
+ Anim
@@ -0,0 +1,33 @@
1
+ # Multiplexor. Does not inherit Base so that it does not get used as a translator
2
+ class Tracksperanto::Export::Mux
3
+ attr_accessor :outputs
4
+
5
+ def initialize(*outputs)
6
+ @outputs = outputs.flatten
7
+ end
8
+
9
+ # Called on export start
10
+ def start_export( img_width, img_height)
11
+ @outputs.each do | output |
12
+ output.start_export( img_width, img_height)
13
+ end
14
+ end
15
+
16
+ # Called on tracker start, one for each tracker
17
+ def start_tracker_segment(tracker_name)
18
+ @outputs.each do | output |
19
+ output.start_tracker_segment(tracker_name)
20
+ end
21
+ end
22
+
23
+ # Called for each tracker keyframe
24
+ def export_point(at_frame_i, abs_float_x, abs_float_y, float_residual)
25
+ @outputs.each do | output |
26
+ output.export_point(at_frame_i, abs_float_x, abs_float_y, float_residual)
27
+ end
28
+ end
29
+
30
+ def end_export
31
+ @outputs.each{|o| o.end_export }
32
+ end
33
+ end
@@ -0,0 +1,30 @@
1
+ class Tracksperanto::Export::Pftrack < Tracksperanto::Export::Base
2
+
3
+ # Should return the suffix and extension of this export file (like "_flame.stabilizer")
4
+ def self.desc_and_extension
5
+ "pftrack.2dt"
6
+ end
7
+
8
+ def start_tracker_segment(tracker_name)
9
+ # If there was a previous tracker, write it out
10
+ # - now we know how many keyframes it has
11
+ if @prev_tracker && @prev_tracker.any?
12
+ block = [
13
+ "\n",
14
+ @tracker_name.inspect, # "autoquotes"
15
+ @prev_tracker.length,
16
+ @prev_tracker.join("\n")
17
+ ]
18
+ @io.puts block.join("\n")
19
+ end
20
+
21
+ # Setup for the next tracker
22
+ @prev_tracker = []
23
+ @tracker_name = tracker_name
24
+ end
25
+
26
+ def export_point(frame, abs_float_x, abs_float_y, float_residual)
27
+ line = "%s %.3f %.3f %.3f" % [frame, abs_float_x, abs_float_y, float_residual]
28
+ @prev_tracker << line
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ class Tracksperanto::Export::ShakeText < Tracksperanto::Export::Base
2
+ PREAMBLE = "TrackName %s\n Frame X Y Correlation\n"
3
+ POSTAMBLE = "\n"
4
+
5
+ # Should return the suffix and extension of this export file (like "_flame.stabilizer")
6
+ def self.desc_and_extension
7
+ "shake_trackers.txt"
8
+ end
9
+
10
+ def start_tracker_segment(tracker_name)
11
+ if @any_tracks
12
+ @io.puts POSTAMBLE
13
+ else
14
+ @any_tracks = true
15
+ end
16
+
17
+ @io.puts PREAMBLE % tracker_name
18
+ end
19
+
20
+ def end_export
21
+ @io << "\n"
22
+ end
23
+
24
+ def export_point(frame, abs_float_x, abs_float_y, float_residual)
25
+ # Shake starts from frame 1, not 0
26
+ line = " %.2f %.3f %.3f %.3f" % [frame + 1, abs_float_x, abs_float_y, 1 - float_residual]
27
+ @io.puts line
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ class Tracksperanto::Export::SynthEyes < Tracksperanto::Export::Base
2
+
3
+ # Should return the suffix and extension of this export file (like "_flame.stabilizer")
4
+ def self.desc_and_extension
5
+ "syntheyes_2dt.txt"
6
+ end
7
+
8
+ def start_export( img_width, img_height)
9
+ @width, @height = img_width, img_height
10
+ end
11
+
12
+ def start_tracker_segment(tracker_name)
13
+ @tracker_name = tracker_name
14
+ end
15
+
16
+ def export_point(frame, abs_float_x, abs_float_y, float_residual)
17
+ values = [@tracker_name, frame] + syntheyes_coords(abs_float_x, abs_float_y)
18
+ @io.puts("%s %d %.6f %.6f 30" % values)
19
+ end
20
+
21
+ private
22
+
23
+ # Syntheyes wants very special coordinates, Y down X right,
24
+ # 0 is center and values are UV float -1 to 1, doubled
25
+ def syntheyes_coords(abs_x, abs_y)
26
+ w, h = 2560, 1080
27
+ x = (abs_x / @width.to_f) - 0.5
28
+ y = (abs_y / @height.to_f) - 0.5
29
+ # .2 to -.3, y is reversed and coords are double
30
+ [x * 2, y * -2]
31
+ end
32
+ end
@@ -0,0 +1,24 @@
1
+ require 'stringio'
2
+
3
+ class Tracksperanto::Import::Base
4
+ include Tracksperanto::Safety
5
+
6
+ # The original width of the tracked image
7
+ # Some importers need it
8
+ attr_accessor :width
9
+
10
+ # The original height of the original image.
11
+ # Some importers need it
12
+ attr_accessor :height
13
+ safe_reader :width, :height
14
+
15
+ def self.inherited(by)
16
+ Tracksperanto.importers << by
17
+ super
18
+ end
19
+
20
+ # Should return an array of Tracksperanto::Tracker objects
21
+ def parse(track_file_content)
22
+ []
23
+ end
24
+ end
@@ -0,0 +1,34 @@
1
+ require 'stringio'
2
+
3
+ class Tracksperanto::Import::FlameStabilizer < Tracksperanto::Import::Base
4
+ # Flame records channels in it's .stabilizer file. Per tracker, we use tracker1/track/x and tracker1/track/y as the base
5
+ # tracking value. Then, the tracker1/shift/x and tracker1/shift/y are added to it. The problem is that when we track backwards
6
+ # the main reference keyframe for the x and y coordinates that sets the base for the animation might be at the last frame, at
7
+ # a frame in the middle or in the end of the setup. We have to pluck it out and then we compute the shift relative to the values
8
+ # in this main frame. Obviously we discard the track width and search width information as we cannot use it in any meaningful way.
9
+ #
10
+ # Here is how the relevant portions look like this (note the tab indents, not spaces, indentation starts at hashmark)
11
+ #Channel tracker1/shift/x
12
+ # Extrapolation linear
13
+ # Value 0
14
+ # Size 185
15
+ # KeyVersion 1
16
+ # Key 0
17
+ # Frame 1
18
+ # Value 0
19
+ # Interpolation linear
20
+ # RightSlope 0.15802
21
+ # LeftSlope 0.15802
22
+ # End
23
+ # Key 1
24
+ #
25
+ # This is in big lines:
26
+ # (tabs)Key(space)Value
27
+ def parse(stabilizer_setup_content)
28
+ trackers = []
29
+
30
+ io = StringIO.new(stabilizer_setup_content)
31
+
32
+ trackers
33
+ end
34
+ end