CooCoo 0.1.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 (105) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/CooCoo.gemspec +47 -0
  4. data/Gemfile +4 -0
  5. data/Gemfile.lock +88 -0
  6. data/README.md +123 -0
  7. data/Rakefile +81 -0
  8. data/bin/cuda-dev-info +25 -0
  9. data/bin/cuda-free +28 -0
  10. data/bin/cuda-free-trend +7 -0
  11. data/bin/ffi-gen +267 -0
  12. data/bin/spec_runner_html.sh +42 -0
  13. data/bin/trainer +198 -0
  14. data/bin/trend-cost +13 -0
  15. data/examples/char-rnn.rb +405 -0
  16. data/examples/cifar/cifar.rb +94 -0
  17. data/examples/img-similarity.rb +201 -0
  18. data/examples/math_ops.rb +57 -0
  19. data/examples/mnist.rb +365 -0
  20. data/examples/mnist_classifier.rb +293 -0
  21. data/examples/mnist_dream.rb +214 -0
  22. data/examples/seeds.rb +268 -0
  23. data/examples/seeds_dataset.txt +210 -0
  24. data/examples/t10k-images-idx3-ubyte +0 -0
  25. data/examples/t10k-labels-idx1-ubyte +0 -0
  26. data/examples/train-images-idx3-ubyte +0 -0
  27. data/examples/train-labels-idx1-ubyte +0 -0
  28. data/ext/buffer/Rakefile +50 -0
  29. data/ext/buffer/buffer.pre.cu +727 -0
  30. data/ext/buffer/matrix.pre.cu +49 -0
  31. data/lib/CooCoo.rb +1 -0
  32. data/lib/coo-coo.rb +18 -0
  33. data/lib/coo-coo/activation_functions.rb +344 -0
  34. data/lib/coo-coo/consts.rb +5 -0
  35. data/lib/coo-coo/convolution.rb +298 -0
  36. data/lib/coo-coo/core_ext.rb +75 -0
  37. data/lib/coo-coo/cost_functions.rb +91 -0
  38. data/lib/coo-coo/cuda.rb +116 -0
  39. data/lib/coo-coo/cuda/device_buffer.rb +240 -0
  40. data/lib/coo-coo/cuda/device_buffer/ffi.rb +109 -0
  41. data/lib/coo-coo/cuda/error.rb +51 -0
  42. data/lib/coo-coo/cuda/host_buffer.rb +117 -0
  43. data/lib/coo-coo/cuda/runtime.rb +157 -0
  44. data/lib/coo-coo/cuda/vector.rb +315 -0
  45. data/lib/coo-coo/data_sources.rb +2 -0
  46. data/lib/coo-coo/data_sources/xournal.rb +25 -0
  47. data/lib/coo-coo/data_sources/xournal/bitmap_stream.rb +197 -0
  48. data/lib/coo-coo/data_sources/xournal/document.rb +377 -0
  49. data/lib/coo-coo/data_sources/xournal/loader.rb +144 -0
  50. data/lib/coo-coo/data_sources/xournal/renderer.rb +101 -0
  51. data/lib/coo-coo/data_sources/xournal/saver.rb +99 -0
  52. data/lib/coo-coo/data_sources/xournal/training_document.rb +78 -0
  53. data/lib/coo-coo/data_sources/xournal/training_document/constants.rb +15 -0
  54. data/lib/coo-coo/data_sources/xournal/training_document/document_maker.rb +89 -0
  55. data/lib/coo-coo/data_sources/xournal/training_document/document_reader.rb +105 -0
  56. data/lib/coo-coo/data_sources/xournal/training_document/example.rb +37 -0
  57. data/lib/coo-coo/data_sources/xournal/training_document/sets.rb +76 -0
  58. data/lib/coo-coo/debug.rb +8 -0
  59. data/lib/coo-coo/dot.rb +129 -0
  60. data/lib/coo-coo/drawing.rb +4 -0
  61. data/lib/coo-coo/drawing/cairo_canvas.rb +100 -0
  62. data/lib/coo-coo/drawing/canvas.rb +68 -0
  63. data/lib/coo-coo/drawing/chunky_canvas.rb +101 -0
  64. data/lib/coo-coo/drawing/sixel.rb +214 -0
  65. data/lib/coo-coo/enum.rb +17 -0
  66. data/lib/coo-coo/from_name.rb +58 -0
  67. data/lib/coo-coo/fully_connected_layer.rb +205 -0
  68. data/lib/coo-coo/generation_script.rb +38 -0
  69. data/lib/coo-coo/grapher.rb +140 -0
  70. data/lib/coo-coo/image.rb +286 -0
  71. data/lib/coo-coo/layer.rb +67 -0
  72. data/lib/coo-coo/layer_factory.rb +26 -0
  73. data/lib/coo-coo/linear_layer.rb +59 -0
  74. data/lib/coo-coo/math.rb +607 -0
  75. data/lib/coo-coo/math/abstract_vector.rb +121 -0
  76. data/lib/coo-coo/math/functions.rb +39 -0
  77. data/lib/coo-coo/math/interpolation.rb +7 -0
  78. data/lib/coo-coo/network.rb +264 -0
  79. data/lib/coo-coo/neuron.rb +112 -0
  80. data/lib/coo-coo/neuron_layer.rb +168 -0
  81. data/lib/coo-coo/option_parser.rb +18 -0
  82. data/lib/coo-coo/platform.rb +17 -0
  83. data/lib/coo-coo/progress_bar.rb +11 -0
  84. data/lib/coo-coo/recurrence/backend.rb +99 -0
  85. data/lib/coo-coo/recurrence/frontend.rb +101 -0
  86. data/lib/coo-coo/sequence.rb +187 -0
  87. data/lib/coo-coo/shell.rb +2 -0
  88. data/lib/coo-coo/temporal_network.rb +291 -0
  89. data/lib/coo-coo/trainer.rb +21 -0
  90. data/lib/coo-coo/trainer/base.rb +67 -0
  91. data/lib/coo-coo/trainer/batch.rb +82 -0
  92. data/lib/coo-coo/trainer/batch_stats.rb +27 -0
  93. data/lib/coo-coo/trainer/momentum_stochastic.rb +59 -0
  94. data/lib/coo-coo/trainer/stochastic.rb +47 -0
  95. data/lib/coo-coo/transformer.rb +272 -0
  96. data/lib/coo-coo/vector_layer.rb +194 -0
  97. data/lib/coo-coo/version.rb +3 -0
  98. data/lib/coo-coo/weight_deltas.rb +23 -0
  99. data/prototypes/convolution.rb +116 -0
  100. data/prototypes/linear_drop.rb +51 -0
  101. data/prototypes/recurrent_layers.rb +79 -0
  102. data/www/images/screamer.png +0 -0
  103. data/www/images/screamer.xcf +0 -0
  104. data/www/index.html +82 -0
  105. metadata +373 -0
@@ -0,0 +1,144 @@
1
+ require 'nokogiri'
2
+ require 'zlib'
3
+ require 'chunky_png'
4
+ require 'base64'
5
+ require 'coo-coo/data_sources/xournal/document'
6
+
7
+ module CooCoo
8
+ module DataSources
9
+ module Xournal
10
+ # Loads a {Document}.
11
+ class Loader
12
+ # General catch all class for load errors.
13
+ class Error < RuntimeError
14
+ end
15
+
16
+ # Error while parsing the Xournal.
17
+ class ParseError < Error
18
+ end
19
+
20
+ def initialize(doc)
21
+ @doc = doc || Document.new
22
+ end
23
+
24
+ # Loads a Xournal document from the file at +path+.
25
+ # @return [Document]
26
+ def self.from_file(path)
27
+ doc = nil
28
+
29
+ Zlib::GzipReader.open(path) do |io|
30
+ doc = from_xml(io)
31
+ end
32
+
33
+ doc
34
+ rescue Zlib::GzipFile::Error
35
+ from_regular_file(path)
36
+ end
37
+
38
+ # Loads a {Document} from XML in a String.
39
+ def self.from_xml(data)
40
+ xml = Nokogiri::XML(data)
41
+ root = xml.xpath('//xournal')[0]
42
+ raise ParseError.new("XML root is not 'xournal'") unless root
43
+ title_el = root.xpath("title")
44
+ title = title_el[0].text if title_el.size > 0
45
+
46
+ self.
47
+ new(Document.new(title, root['version'])).
48
+ from_xml(xml)
49
+ end
50
+
51
+ def from_xml(xml)
52
+ xml.xpath("//page").each do |page|
53
+ @doc.add_page(load_page(page))
54
+ end
55
+
56
+ @doc
57
+ end
58
+
59
+ protected
60
+
61
+ def self.from_regular_file(path)
62
+ File.open(path, 'rb') do |f|
63
+ from_xml(f)
64
+ end
65
+ end
66
+
67
+ def load_page(xml)
68
+ w = xml['width'].to_f
69
+ h = xml['height'].to_f
70
+ bg_xml = xml.xpath('background')
71
+ bg = load_background(bg_xml[0]) if bg_xml[0]
72
+ page = Page.new(w, h, bg)
73
+
74
+ xml.xpath('layer').each do |layer|
75
+ page.add_layer(load_layer(layer))
76
+ end
77
+
78
+ page
79
+ end
80
+
81
+ def load_background(xml)
82
+ case xml['type']
83
+ when 'pixmap' then PixmapBackground.new(xml['filename'], xml['domain'])
84
+ when 'pdf' then PDFBackground.new(xml['filename'], xml['pageno'], xml['domain'])
85
+ when 'solid' then Background.new(xml['color'], xml['style'])
86
+ else raise ParseError.new("Unknown background type #{xml['type']}: #{xml}")
87
+ end
88
+ end
89
+
90
+ def load_layer(xml)
91
+ layer = Layer.new
92
+
93
+ xml.children.select(&:element?).each do |elem|
94
+ case elem.name
95
+ when 'stroke' then layer.add_stroke(load_stroke(elem))
96
+ when 'text' then layer.add_text(load_text(elem))
97
+ when 'image' then layer.add_image(load_image(elem))
98
+ else raise ParseError.new("Unknown element: #{elem}")
99
+ end
100
+ end
101
+
102
+ layer
103
+ end
104
+
105
+ def load_image(xml)
106
+ Image.new(xml['left'],
107
+ xml['top'],
108
+ xml['right'],
109
+ xml['bottom'],
110
+ xml.text)
111
+ end
112
+
113
+ def load_text(xml)
114
+ Text.new(xml.text,
115
+ xml['x'].to_f,
116
+ xml['y'].to_f,
117
+ xml['size'].to_f,
118
+ xml['color'],
119
+ xml['font'])
120
+ end
121
+
122
+ def load_stroke(xml)
123
+ tool = xml['tool']
124
+ tool = Stroke::DefaultTool if tool == nil || tool.empty?
125
+ color = xml['color']
126
+ stroke = Stroke.new(tool, color)
127
+ widths = xml['width'].split.collect(&:to_f)
128
+
129
+ width = nil
130
+ xml.children.to_s.
131
+ split.
132
+ collect(&:to_f).
133
+ each_slice(2).
134
+ zip(widths) do |(x, y), w|
135
+ width ||= w if w
136
+ stroke.add_sample(x, y, width)
137
+ end
138
+
139
+ stroke
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,101 @@
1
+ require 'chunky_png'
2
+ require 'cairo'
3
+ require 'coo-coo/drawing'
4
+
5
+ module CooCoo
6
+ module DataSources
7
+ module Xournal
8
+ class Renderer
9
+ def initialize()
10
+ end
11
+
12
+ def render(*args)
13
+ render_to_chunky(*args)
14
+ end
15
+
16
+ def render_to_canvas(canvas, document, page_num, x = 0, y = 0, w = nil, h = nil, zx = 1.0, zy = 1.0)
17
+ page = document.pages[page_num]
18
+ w ||= (page.width - x).ceil.to_i
19
+ h ||= (page.height - y).ceil.to_i
20
+ render_page(canvas, page, x, y, x + w, y + h, zx, zy)
21
+ canvas
22
+ end
23
+
24
+ def render_to_chunky(document, page_num, x = 0, y = 0, w = nil, h = nil, zx = 1.0, zy = 1.0)
25
+ page = document.pages[page_num]
26
+ w ||= (page.width - x).ceil.to_i
27
+ h ||= (page.height - y).ceil.to_i
28
+ img = ChunkyPNG::Image.new((w * zx).to_i, (h * zy).to_i, chunky_color(page.background.color || :white))
29
+ canvas = Drawing::ChunkyCanvas.new(img)
30
+ render_to_canvas(canvas, document, page_num, x, y, w, h, zx, zy)
31
+ img
32
+ end
33
+
34
+ def render_to_cairo(document, page_num, x = 0, y = 0, w = nil, h = nil, zx = 1.0, zy = 1.0)
35
+ page = document.pages[page_num]
36
+ w ||= (page.width - x).ceil.to_i
37
+ h ||= (page.height - y).ceil.to_i
38
+ surface = Cairo::ImageSurface.new((w * zx).to_i, (h * zy).to_i)
39
+ canvas = Drawing::CairoCanvas.new(surface)
40
+ render_to_canvas(canvas, document, page_num, x, y, w, h, zx, zy)
41
+ surface
42
+ end
43
+
44
+ def chunky_color(color)
45
+ color && ChunkyPNG::Color.parse(color)
46
+ end
47
+
48
+ def render_page(canvas, page, min_x, min_y, max_x, max_y, zx, zy)
49
+ render_background(canvas, page.background, min_x, min_y, max_x, max_y, zx, zy)
50
+ page.each_layer do |layer|
51
+ render_layer(canvas, layer, min_x, min_y, max_x, max_y, zx, zy)
52
+ end
53
+ end
54
+
55
+ def render_background(canvas, bg, min_x, min_y, max_x, max_y, zx, zy)
56
+ color = chunky_color(bg.color || :white)
57
+ canvas.stroke_color = canvas.fill_color = color
58
+ canvas.rect(0, 0, ((max_x - min_x) * zx).to_i, ((max_y - min_y) * zy).to_i)
59
+ end
60
+
61
+ def render_layer(canvas, layer, min_x, min_y, max_x, max_y, zx, zy)
62
+ layer.each do |child|
63
+ #next unless child.within?(min_x, min_y, max_x, max_y)
64
+ case child
65
+ when Image then render_image(canvas, child, min_x, min_y, zx, zy)
66
+ when Stroke then render_stroke(canvas, child, min_x, min_y, max_x, max_y, zx, zy)
67
+ when Text then render_text(canvas, child, min_x, min_y, zx, zy)
68
+ end
69
+ end
70
+ end
71
+
72
+ def render_image(canvas, src, min_x, min_y, zx, zy)
73
+ canvas.blit(src.raw_data, ((src.left - min_x) * zx), ((src.top - min_y) * zy), src.width * zx, src.height * zy)
74
+ end
75
+
76
+ def render_stroke(canvas, stroke, min_x, min_y, max_x, max_y, zx, zy)
77
+ points = stroke.each_sample.inject([]) do |acc, sample|
78
+ #next unless sample.within?(min_x, min_y, max_x, max_y)
79
+ acc << [ (sample.x - min_x) * zx,
80
+ (sample.y - min_y) * zy,
81
+ sample.width * zx
82
+ ]
83
+ end
84
+
85
+ canvas.stroke_color = chunky_color(stroke.color)
86
+ canvas.fill_color = chunky_color(stroke.color)
87
+ canvas.stroke(points)
88
+ end
89
+
90
+ def render_text(canvas, text, min_x, min_y, zx, zy)
91
+ canvas.fill_color = chunky_color(text.color)
92
+ canvas.text(text.text,
93
+ (text.x - min_x) * zx,
94
+ (text.y - min_y) * zy,
95
+ text.font,
96
+ text.size * zy)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,99 @@
1
+ module CooCoo
2
+ module DataSources
3
+ module Xournal
4
+ # Saves a {Document}
5
+ # @todo Keep linked images with the Xournal when it is saved.
6
+ class Saver
7
+ # Saves +doc+ to +io_or_path+ using Xournal's compressed XML format.
8
+ # @param io_or_path [String, IO] File name or an IO
9
+ def self.save(doc, io_or_path)
10
+ new.save(doc, io_or_path)
11
+ end
12
+
13
+ # Saves +doc+ to an XML string.
14
+ def self.to_xml(doc)
15
+ new.to_xml(doc)
16
+ end
17
+
18
+ def initialize
19
+ end
20
+
21
+ def save(doc, io_or_path)
22
+ if io_or_path.respond_to?(:write)
23
+ save_to_io(doc, io_or_path)
24
+ elsif io_or_path.kind_of?(String)
25
+ save_to_file(doc, io_or_path)
26
+ else
27
+ raise ArgumentError.new("Only paths as String and IO are supported outputs. Not #{io_or_path.class}")
28
+ end
29
+ end
30
+
31
+ def to_xml(doc)
32
+ Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
33
+ xml.xournal(version: doc.version) do
34
+ xml.title(doc.title)
35
+ doc.pages.each do |p|
36
+ page_to_xml(p, xml)
37
+ end
38
+ end
39
+ end.to_xml
40
+ end
41
+
42
+ protected
43
+
44
+ def save_to_file(doc, path)
45
+ Zlib::GzipWriter.open(path) do |f|
46
+ save_to_io(doc, f)
47
+ end
48
+ end
49
+
50
+ def save_to_io(doc, io)
51
+ io.write(to_xml(doc))
52
+ end
53
+
54
+ def page_to_xml(p, xml)
55
+ xml.page(width: p.width, height: p.height) do
56
+ background_to_xml(p.background || Background::Default, xml)
57
+ p.layers.each do |l|
58
+ layer_to_xml(l, xml)
59
+ end
60
+ end
61
+ end
62
+
63
+ def background_to_xml(bg, xml)
64
+ case bg
65
+ when PixmapBackground then xml.background(type: 'pixmap', domain: bg.domain, filename: bg.filename)
66
+ when PDFBackground then xml.background(type: 'pdf', domain: bg.domain, filename: bg.filename, pageno: bg.page_number)
67
+ else xml.background(type: 'solid', color: bg.color, style: bg.style)
68
+ end
69
+ end
70
+
71
+ def layer_to_xml(layer, xml)
72
+ xml.layer do
73
+ layer.each do |child|
74
+ case child
75
+ when Image then image_to_xml(child, xml)
76
+ when Stroke then stroke_to_xml(child, xml)
77
+ when Text then text_to_xml(child, xml)
78
+ else raise ParseError.new("Unknown layer child: #{child.class} #{child.inspect}")
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ def image_to_xml(img, xml)
85
+ xml.image(img.data_string, left: img.left, top: img.top, right: img.right, bottom: img.bottom)
86
+ end
87
+
88
+ def stroke_to_xml(stroke, xml)
89
+ xml.stroke(stroke.samples.collect { |s| [ s.x, s.y ] }.flatten.join(' '),
90
+ tool: stroke.tool, color: stroke.color, width: stroke.samples.collect(&:width).join(' '))
91
+ end
92
+
93
+ def text_to_xml(text, xml)
94
+ xml.text_(text.text, x: text.x, y: text.y, size: text.size, color: text.color, font: text.font)
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,78 @@
1
+ require 'coo-coo/data_sources/xournal/training_document/constants'
2
+ require 'coo-coo/data_sources/xournal/training_document/example'
3
+ require 'coo-coo/data_sources/xournal/training_document/document_maker'
4
+ require 'coo-coo/data_sources/xournal/training_document/document_reader'
5
+ require 'coo-coo/data_sources/xournal/training_document/sets'
6
+
7
+ module CooCoo
8
+ module DataSources
9
+ module Xournal
10
+ # The {TrainingDocument} is the source of strokes for the trainer of
11
+ # the Xournal recognizer. Each {TrainingDocument} has a set of labels
12
+ # and associated strokes. Examples are loaded and stored to Xournal
13
+ # documents formatted into a grid with a label and strokes in each cell.
14
+ class TrainingDocument
15
+ attr_reader :examples
16
+
17
+ # @param examples [Array<Example>]
18
+ def initialize(examples = nil)
19
+ @examples = examples || Hash.new { |h, k| h[k] = Example.new(k) }
20
+ end
21
+
22
+ # @return [Integer] Number of examples
23
+ def size
24
+ @examples.size
25
+ end
26
+
27
+ # @return [Array<String>] of every example's label
28
+ def labels
29
+ @examples.keys
30
+ end
31
+
32
+ # Add an example to the set.
33
+ # @param label [String] The label of the example.
34
+ # @param strokes [Array<Stroke>] Strokes associated with this label.
35
+ # @return self
36
+ def add_example(label, strokes = nil)
37
+ ex = @examples[label]
38
+ ex.add_set(strokes) if strokes && !strokes.empty?
39
+ self
40
+ end
41
+
42
+ # Iterates each {Example}.
43
+ # @return [Enumerator]
44
+ def each_example(&block)
45
+ return to_enum(__method__) unless block_given?
46
+
47
+ @examples.each do |label, ex|
48
+ block.call(ex)
49
+ end
50
+ end
51
+
52
+ # Convert the {Example} set into a {Document}.
53
+ # @param columns [Integer] Number of examples across the page.
54
+ # @param rows [Integer] Number of examples down the page.
55
+ # @param page_width [Float] Width of the page in points.
56
+ # @param page_height [Float] Height of the page in points.
57
+ # @return [Document]
58
+ def to_document(columns, rows, cells_per_example = 4, page_width = 612, page_height = 792)
59
+ DocumentMaker.new(self, columns, rows, cells_per_example, page_width, page_height).make_document
60
+ end
61
+
62
+ # Load {TrainingDocument} from a Xournal file.
63
+ # @param io_or_path [IO, String]
64
+ # @return [TrainingDocument]
65
+ def self.from_file(io_or_path)
66
+ DocumentReader.new.load(Xournal.from_file(io_or_path))
67
+ end
68
+
69
+ # Load a {TrainingDocument} from a {Document}.
70
+ # @param doc [Document]
71
+ # @return [TrainingDocument]
72
+ def self.from_document(doc)
73
+ DocumentReader.new.load(doc)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,15 @@
1
+ require 'chunky_png'
2
+
3
+ module CooCoo
4
+ module DataSources
5
+ module Xournal
6
+ class TrainingDocument
7
+ VERSION = '1'
8
+ GRID_COLOR = '#00E0FFFF' # FIXME Xournal places alpha up front
9
+ PARSED_GRID_COLOR = ChunkyPNG::Color.parse(GRID_COLOR)
10
+ META_LABEL = "Training Document"
11
+ META_LABEL_REGEX = /^#{META_LABEL}( +\d+)?: *(\d+)( +\d+)( +\d+)?/
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,89 @@
1
+ require 'coo-coo/data_sources/xournal/document'
2
+
3
+ module CooCoo
4
+ module DataSources
5
+ module Xournal
6
+ class TrainingDocument
7
+ class DocumentMaker
8
+ attr_reader :page_width
9
+ attr_reader :page_height
10
+ attr_reader :cells_per_example
11
+ attr_reader :columns
12
+ attr_reader :rows
13
+
14
+ def initialize(training_doc, columns, rows, cells_per_example, page_width, page_height)
15
+ @doc = training_doc
16
+ @columns = columns
17
+ @rows = rows
18
+ @cells_per_example = cells_per_example
19
+ @page_width = page_width
20
+ @page_height = page_height
21
+ end
22
+
23
+ def make_document
24
+ Document.new do |d|
25
+ @doc.each_example.each_slice(@columns * @rows / @cells_per_example).with_index do |labels, page_num|
26
+ d.add_page(make_page(labels, page_num))
27
+ end
28
+
29
+ d.pages.first.layers.last.add_text(Text.new("#{META_LABEL} #{VERSION}: #{@columns} #{@rows} #{@cells_per_example}", 0, @page_height - 14, 12, 'gray'))
30
+ end
31
+ end
32
+
33
+ def make_cell(layer, label, strokes, x, y, grid_w, grid_h)
34
+ layer.add_text(Text.new(label, x + 1, y + 1, 12, 'black', 'Serif'))
35
+ strokes.each do |stroke|
36
+ layer.add_stroke(stroke.scale(grid_w, grid_h, grid_w).translate(x, y))
37
+ end
38
+ end
39
+
40
+ def make_cells(examples, grid_w, grid_h)
41
+ layer = Layer.new
42
+
43
+ examples = examples.collect { |e|
44
+ e.each_set.collect { |s|
45
+ [ e.label, s ]
46
+ } + Array.new(@cells_per_example - e.size, [ e.label, [] ])
47
+ }.flatten(1)
48
+
49
+ examples.each_slice(@columns).with_index do |row, y|
50
+ row.each_with_index do |(label, strokes), x|
51
+ make_cell(layer, label, strokes, x * grid_w, y * grid_h, grid_w, grid_h)
52
+ end
53
+ end
54
+
55
+ layer
56
+ end
57
+
58
+ def make_grid(grid_w, grid_h)
59
+ grid_layer = Layer.new
60
+
61
+ (1...@rows).each do |y|
62
+ (1...@columns).each do |x|
63
+ grid_layer.add_stroke(Stroke.new('pen', GRID_COLOR).
64
+ add_sample(x * grid_w, 0).
65
+ add_sample(x * grid_w, @page_height))
66
+ end
67
+
68
+ grid_layer.add_stroke(Stroke.new('pen', GRID_COLOR).
69
+ add_sample(0, y * grid_h).
70
+ add_sample(@page_width, y * grid_h))
71
+ end
72
+
73
+ grid_layer
74
+ end
75
+
76
+ def make_page(examples, page_num)
77
+ grid_w = @page_width / @columns
78
+ grid_h = @page_height / @rows
79
+
80
+ Page.new(@page_width, @page_height).
81
+ add_layer(make_grid(grid_w, grid_h)).
82
+ add_layer(make_cells(examples, grid_w, grid_h))
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+