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.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/CooCoo.gemspec +47 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +88 -0
- data/README.md +123 -0
- data/Rakefile +81 -0
- data/bin/cuda-dev-info +25 -0
- data/bin/cuda-free +28 -0
- data/bin/cuda-free-trend +7 -0
- data/bin/ffi-gen +267 -0
- data/bin/spec_runner_html.sh +42 -0
- data/bin/trainer +198 -0
- data/bin/trend-cost +13 -0
- data/examples/char-rnn.rb +405 -0
- data/examples/cifar/cifar.rb +94 -0
- data/examples/img-similarity.rb +201 -0
- data/examples/math_ops.rb +57 -0
- data/examples/mnist.rb +365 -0
- data/examples/mnist_classifier.rb +293 -0
- data/examples/mnist_dream.rb +214 -0
- data/examples/seeds.rb +268 -0
- data/examples/seeds_dataset.txt +210 -0
- data/examples/t10k-images-idx3-ubyte +0 -0
- data/examples/t10k-labels-idx1-ubyte +0 -0
- data/examples/train-images-idx3-ubyte +0 -0
- data/examples/train-labels-idx1-ubyte +0 -0
- data/ext/buffer/Rakefile +50 -0
- data/ext/buffer/buffer.pre.cu +727 -0
- data/ext/buffer/matrix.pre.cu +49 -0
- data/lib/CooCoo.rb +1 -0
- data/lib/coo-coo.rb +18 -0
- data/lib/coo-coo/activation_functions.rb +344 -0
- data/lib/coo-coo/consts.rb +5 -0
- data/lib/coo-coo/convolution.rb +298 -0
- data/lib/coo-coo/core_ext.rb +75 -0
- data/lib/coo-coo/cost_functions.rb +91 -0
- data/lib/coo-coo/cuda.rb +116 -0
- data/lib/coo-coo/cuda/device_buffer.rb +240 -0
- data/lib/coo-coo/cuda/device_buffer/ffi.rb +109 -0
- data/lib/coo-coo/cuda/error.rb +51 -0
- data/lib/coo-coo/cuda/host_buffer.rb +117 -0
- data/lib/coo-coo/cuda/runtime.rb +157 -0
- data/lib/coo-coo/cuda/vector.rb +315 -0
- data/lib/coo-coo/data_sources.rb +2 -0
- data/lib/coo-coo/data_sources/xournal.rb +25 -0
- data/lib/coo-coo/data_sources/xournal/bitmap_stream.rb +197 -0
- data/lib/coo-coo/data_sources/xournal/document.rb +377 -0
- data/lib/coo-coo/data_sources/xournal/loader.rb +144 -0
- data/lib/coo-coo/data_sources/xournal/renderer.rb +101 -0
- data/lib/coo-coo/data_sources/xournal/saver.rb +99 -0
- data/lib/coo-coo/data_sources/xournal/training_document.rb +78 -0
- data/lib/coo-coo/data_sources/xournal/training_document/constants.rb +15 -0
- data/lib/coo-coo/data_sources/xournal/training_document/document_maker.rb +89 -0
- data/lib/coo-coo/data_sources/xournal/training_document/document_reader.rb +105 -0
- data/lib/coo-coo/data_sources/xournal/training_document/example.rb +37 -0
- data/lib/coo-coo/data_sources/xournal/training_document/sets.rb +76 -0
- data/lib/coo-coo/debug.rb +8 -0
- data/lib/coo-coo/dot.rb +129 -0
- data/lib/coo-coo/drawing.rb +4 -0
- data/lib/coo-coo/drawing/cairo_canvas.rb +100 -0
- data/lib/coo-coo/drawing/canvas.rb +68 -0
- data/lib/coo-coo/drawing/chunky_canvas.rb +101 -0
- data/lib/coo-coo/drawing/sixel.rb +214 -0
- data/lib/coo-coo/enum.rb +17 -0
- data/lib/coo-coo/from_name.rb +58 -0
- data/lib/coo-coo/fully_connected_layer.rb +205 -0
- data/lib/coo-coo/generation_script.rb +38 -0
- data/lib/coo-coo/grapher.rb +140 -0
- data/lib/coo-coo/image.rb +286 -0
- data/lib/coo-coo/layer.rb +67 -0
- data/lib/coo-coo/layer_factory.rb +26 -0
- data/lib/coo-coo/linear_layer.rb +59 -0
- data/lib/coo-coo/math.rb +607 -0
- data/lib/coo-coo/math/abstract_vector.rb +121 -0
- data/lib/coo-coo/math/functions.rb +39 -0
- data/lib/coo-coo/math/interpolation.rb +7 -0
- data/lib/coo-coo/network.rb +264 -0
- data/lib/coo-coo/neuron.rb +112 -0
- data/lib/coo-coo/neuron_layer.rb +168 -0
- data/lib/coo-coo/option_parser.rb +18 -0
- data/lib/coo-coo/platform.rb +17 -0
- data/lib/coo-coo/progress_bar.rb +11 -0
- data/lib/coo-coo/recurrence/backend.rb +99 -0
- data/lib/coo-coo/recurrence/frontend.rb +101 -0
- data/lib/coo-coo/sequence.rb +187 -0
- data/lib/coo-coo/shell.rb +2 -0
- data/lib/coo-coo/temporal_network.rb +291 -0
- data/lib/coo-coo/trainer.rb +21 -0
- data/lib/coo-coo/trainer/base.rb +67 -0
- data/lib/coo-coo/trainer/batch.rb +82 -0
- data/lib/coo-coo/trainer/batch_stats.rb +27 -0
- data/lib/coo-coo/trainer/momentum_stochastic.rb +59 -0
- data/lib/coo-coo/trainer/stochastic.rb +47 -0
- data/lib/coo-coo/transformer.rb +272 -0
- data/lib/coo-coo/vector_layer.rb +194 -0
- data/lib/coo-coo/version.rb +3 -0
- data/lib/coo-coo/weight_deltas.rb +23 -0
- data/prototypes/convolution.rb +116 -0
- data/prototypes/linear_drop.rb +51 -0
- data/prototypes/recurrent_layers.rb +79 -0
- data/www/images/screamer.png +0 -0
- data/www/images/screamer.xcf +0 -0
- data/www/index.html +82 -0
- 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
|
+
|