nametrainer 0.0.1
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.
- data/README.md +89 -0
- data/Rakefile +30 -0
- data/bin/nametrainer +38 -0
- data/demo_collection/Albert_Einstein.png +0 -0
- data/demo_collection/Erwin_Schroedinger.png +0 -0
- data/demo_collection/Erwin_Schroedinger.txt +1 -0
- data/demo_collection/Felix_Bloch.png +0 -0
- data/demo_collection/Max_Born.png +0 -0
- data/demo_collection/Max_Planck.png +0 -0
- data/demo_collection/Paul_Dirac.png +0 -0
- data/demo_collection/Werner_Heisenberg.png +0 -0
- data/lib/nametrainer.rb +94 -0
- data/lib/nametrainer/collection.rb +144 -0
- data/lib/nametrainer/gui.rb +234 -0
- data/lib/nametrainer/optionparser.rb +70 -0
- data/lib/nametrainer/person.rb +37 -0
- data/lib/nametrainer/statistics.rb +42 -0
- data/lib/nametrainer/version.rb +13 -0
- data/nametrainer.gemspec +43 -0
- data/screenshot.png +0 -0
- data/test/collection/001.jpg +0 -0
- data/test/collection/001.txt +1 -0
- data/test/collection/002.JPG +0 -0
- data/test/collection/002.txt +1 -0
- data/test/collection/Max_Born.png +0 -0
- data/test/collection/nametrainer.dat +4 -0
- data/test/test_collection.rb +76 -0
- data/test/test_optionparser.rb +32 -0
- data/test/test_person.rb +32 -0
- data/test/test_statistics.rb +48 -0
- metadata +124 -0
data/README.md
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
nametrainer
|
2
|
+
===========
|
3
|
+
|
4
|
+
`nametrainer` is a name learning trainer using [Ruby][Ruby] and the [Qt][Qt] GUI toolkit.
|
5
|
+
|
6
|
+
It will assist you in learning people's names from a collection of images.
|
7
|
+
|
8
|
+
Quick Start
|
9
|
+
----------
|
10
|
+
|
11
|
+
Start the application from the command line with
|
12
|
+
`nametrainer` or with `nametrainer path_to_collection`.
|
13
|
+
The program can also be started with a demo collection:
|
14
|
+
use `nametrainer --demo` or `nametrainer -d`.
|
15
|
+
|
16
|
+
With a collection loaded, the application window will look similar to this:
|
17
|
+
|
18
|
+
<img src="https://github.com/stomar/nametrainer/raw/master/screenshot.png" alt="Screenshot" width="431" height="405" />
|
19
|
+
|
20
|
+
`nametrainer` shows a randomly chosen image.
|
21
|
+
You can then display the corresponding name and tell the application
|
22
|
+
whether you know the person's name or you don't know it yet.
|
23
|
+
Persons that you do not know will be shown more often.
|
24
|
+
|
25
|
+
Note that for reasons of speed and convenience it is not necessary
|
26
|
+
to type in the name (so be honest to yourself).
|
27
|
+
There are also keyboard shortcuts for "power users".
|
28
|
+
|
29
|
+
Collections
|
30
|
+
-----------
|
31
|
+
|
32
|
+
A collection is simply a directory with image files.
|
33
|
+
`nametrainer` searches in the specified directory for all JPG or PNG files.
|
34
|
+
It uses the file name as the person's display name unless a corresponding TXT file
|
35
|
+
is also present. In this case it will use the content of the TXT file as name.
|
36
|
+
|
37
|
+
For example, a collection might contain the following files:
|
38
|
+
|
39
|
+
> Albert_Einstein.jpg
|
40
|
+
> img1234.jpg
|
41
|
+
> img1234.txt
|
42
|
+
> Werner_Heisenberg.JPG
|
43
|
+
|
44
|
+
Please note:
|
45
|
+
|
46
|
+
- Do not use special characters in file names; use underscore instead of space.
|
47
|
+
- Use UTF-8 encoding for TXT files.
|
48
|
+
- **On Windows, JPG files might not be supported** (depending on the Qt version).
|
49
|
+
In this case use PNG images instead.
|
50
|
+
|
51
|
+
Installation
|
52
|
+
------------
|
53
|
+
|
54
|
+
Install with `gem install nametrainer`.
|
55
|
+
|
56
|
+
You will also have to install Qt bindings for Ruby:
|
57
|
+
|
58
|
+
- On Windows, the `qtbindings` gem should get you up and running.
|
59
|
+
|
60
|
+
- On Linux, install the appropriate package for your distribution,
|
61
|
+
e. g. `qt4-ruby` on Ubuntu.
|
62
|
+
|
63
|
+
You can also compile the bindings using the `qtbindings` gem
|
64
|
+
(see the [qtbindings README](https://github.com/ryanmelt/qtbindings#readme)
|
65
|
+
for instructions).
|
66
|
+
|
67
|
+
Acknowledgements
|
68
|
+
----------------
|
69
|
+
|
70
|
+
The images for the demo collection were taken from [robohash.org](http://robohash.org/).
|
71
|
+
|
72
|
+
Reporting bugs
|
73
|
+
--------------
|
74
|
+
|
75
|
+
Report bugs on the `nametrainer` home page: <https://github.com/stomar/nametrainer/>
|
76
|
+
|
77
|
+
License
|
78
|
+
-------
|
79
|
+
|
80
|
+
Copyright © 2012, Marcus Stollsteimer
|
81
|
+
|
82
|
+
`nametrainer` is free software: you can redistribute it and/or modify
|
83
|
+
it under the terms of the GNU General Public License version 3 or later (GPLv3+),
|
84
|
+
see [www.gnu.org/licenses/gpl.html](http://www.gnu.org/licenses/gpl.html).
|
85
|
+
There is NO WARRANTY, to the extent permitted by law.
|
86
|
+
|
87
|
+
|
88
|
+
[Ruby]: http://www.ruby-lang.org/
|
89
|
+
[Qt]: http://qt-project.org/
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# Rakefile for the nametrainer program.
|
2
|
+
#
|
3
|
+
# Copyright (C) 2012 Marcus Stollsteimer
|
4
|
+
|
5
|
+
require 'rake/testtask'
|
6
|
+
|
7
|
+
BINDIR = '/usr/local/bin'
|
8
|
+
|
9
|
+
BINARY = 'bin/nametrainer'
|
10
|
+
|
11
|
+
|
12
|
+
def gemspec_file
|
13
|
+
'nametrainer.gemspec'
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
task :default => [:test]
|
18
|
+
|
19
|
+
Rake::TestTask.new do |t|
|
20
|
+
t.pattern = 'test/**/test_*.rb'
|
21
|
+
t.ruby_opts << '-rubygems'
|
22
|
+
t.verbose = true
|
23
|
+
t.warning = true
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
desc 'Build gem'
|
28
|
+
task :build do
|
29
|
+
sh "gem build #{gemspec_file}"
|
30
|
+
end
|
data/bin/nametrainer
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
|
3
|
+
require 'Qt'
|
4
|
+
|
5
|
+
require 'nametrainer'
|
6
|
+
|
7
|
+
SRCPATH = File.dirname(__FILE__)
|
8
|
+
|
9
|
+
module Nametrainer
|
10
|
+
|
11
|
+
ERRORCODE = {:general => 1, :usage => 2}
|
12
|
+
|
13
|
+
# Prints an error message and a short help information, then exits.
|
14
|
+
def self.usage_fail(message) # :nodoc:
|
15
|
+
warn "#{PROGNAME}: #{message}"
|
16
|
+
warn "Use `#{PROGNAME} --help' for valid options."
|
17
|
+
exit ERRORCODE[:usage]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
### main program
|
22
|
+
|
23
|
+
# parse options
|
24
|
+
begin
|
25
|
+
options = Nametrainer::Optionparser.parse!(ARGV)
|
26
|
+
rescue => e
|
27
|
+
Nametrainer.usage_fail(e.message)
|
28
|
+
end
|
29
|
+
|
30
|
+
# set up demo mode
|
31
|
+
if options[:demo]
|
32
|
+
options[:collection] = File.expand_path("#{SRCPATH}/../demo_collection")
|
33
|
+
end
|
34
|
+
|
35
|
+
# start
|
36
|
+
app = Qt::Application.new ARGV
|
37
|
+
Nametrainer::GUI.new(options[:collection])
|
38
|
+
app.exec
|
Binary file
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
Erwin Schrödinger
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/lib/nametrainer.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# == Name
|
2
|
+
#
|
3
|
+
# nametrainer - name learning trainer
|
4
|
+
#
|
5
|
+
# == Synopsis
|
6
|
+
#
|
7
|
+
# nametrainer [options] [collection]
|
8
|
+
#
|
9
|
+
# == Description
|
10
|
+
#
|
11
|
+
# +nametrainer+ is a name learning trainer using Ruby and the Qt GUI toolkit.
|
12
|
+
# It will assist you in learning people's names from a collection of images.
|
13
|
+
#
|
14
|
+
# See the project home page for additional information:
|
15
|
+
# https://github.com/stomar/nametrainer/
|
16
|
+
#
|
17
|
+
# == Options
|
18
|
+
#
|
19
|
+
# -d, --demo:: Starts the application with a demo collection.
|
20
|
+
#
|
21
|
+
# -h, --help:: Prints a brief help message and exits.
|
22
|
+
#
|
23
|
+
# -v, --version:: Prints a brief version information and exits.
|
24
|
+
#
|
25
|
+
# == Author
|
26
|
+
#
|
27
|
+
# Copyright (C) 2012 Marcus Stollsteimer
|
28
|
+
#
|
29
|
+
# License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
|
30
|
+
|
31
|
+
require 'nametrainer/gui'
|
32
|
+
require 'nametrainer/person'
|
33
|
+
require 'nametrainer/collection'
|
34
|
+
require 'nametrainer/optionparser'
|
35
|
+
require 'nametrainer/statistics'
|
36
|
+
require 'nametrainer/version'
|
37
|
+
|
38
|
+
# This module contains the classes for the +nametrainer+ tool
|
39
|
+
module Nametrainer
|
40
|
+
|
41
|
+
FILE_EXTENSIONS = %w{png jpg jpeg}
|
42
|
+
|
43
|
+
# Returns a string with all accepted image file extensions.
|
44
|
+
def self.extension_string
|
45
|
+
"#{FILE_EXTENSIONS[0...-1].join(', ').upcase}, or #{FILE_EXTENSIONS.last.upcase}"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the warning message for an empty collection.
|
49
|
+
def self.collection_empty_message
|
50
|
+
'Could not load collection.<br><br>' +
|
51
|
+
"Maybe the specified directory does not exist or contains no #{extension_string} files."
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the text for the help window.
|
55
|
+
def self.help_message
|
56
|
+
<<-HELPTEXT
|
57
|
+
<big>Basic Functions / Keyboard Shortcuts</big><br>
|
58
|
+
|
59
|
+
<table>
|
60
|
+
<tr><td>Display name </td>
|
61
|
+
<td>Shows the person's name (<strong>D</strong>)</td></tr>
|
62
|
+
<tr><td>Correct</td>
|
63
|
+
<td>You know this person (<strong>S</strong>)</td></tr>
|
64
|
+
<tr><td>Wrong</td>
|
65
|
+
<td>You do not know this person yet (<strong>F</strong>)</td></tr>
|
66
|
+
<tr><td>Quit</td>
|
67
|
+
<td>Quit (<strong>Ctrl+W</strong>)</td></tr>
|
68
|
+
</tr>
|
69
|
+
</table>
|
70
|
+
<br><br>
|
71
|
+
|
72
|
+
<big>Collections</big><br><br>
|
73
|
+
|
74
|
+
A collection is simply a diretory with image files.
|
75
|
+
#{PROGNAME} uses the file name as the person's display name
|
76
|
+
or the content of a corresponding TXT file.<br><br>
|
77
|
+
|
78
|
+
Accepted file formats: #{extension_string}.<br><br>
|
79
|
+
|
80
|
+
Please note: JPG files may not be supported on Windows;
|
81
|
+
in this case use PNG images instead.<br><br>
|
82
|
+
|
83
|
+
<big>About</big><br><br>
|
84
|
+
|
85
|
+
<b>#{PROGNAME}</b> version #{VERSION} (#{DATE})<br><br>
|
86
|
+
|
87
|
+
Project home page:
|
88
|
+
<a href='#{HOMEPAGE}'>#{HOMEPAGE.gsub(%r{https?://}, '')}</a><br><br>
|
89
|
+
|
90
|
+
#{COPYRIGHT.lines.first.gsub(/\(C\)/, '©')}<br>
|
91
|
+
For license information use <code>#{PROGNAME} -v</code>.
|
92
|
+
HELPTEXT
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
require 'nametrainer/person'
|
5
|
+
|
6
|
+
module Nametrainer
|
7
|
+
|
8
|
+
# A Class for a collection of persons, each with a name,
|
9
|
+
# a corresponding (image) file, and a score.
|
10
|
+
# A collection is an array of instances of the Person class.
|
11
|
+
#
|
12
|
+
# Open a collection with
|
13
|
+
# collection = Collection.new(directory, extensions)
|
14
|
+
#
|
15
|
+
# For each file with an extension from the +extensions+ array a Person
|
16
|
+
# instance is created, using the file name as the person's name
|
17
|
+
# (extension is removed, underscore is converted to space)
|
18
|
+
# or the content of a corresponding `txt' file, and the file name as
|
19
|
+
# the +image+ attribute.
|
20
|
+
#
|
21
|
+
# You can get a random person from a collection with
|
22
|
+
# person = collection.sample
|
23
|
+
#
|
24
|
+
# Persons with a lower score are chosen more often
|
25
|
+
# than persons with a higher score.
|
26
|
+
class Collection < Array
|
27
|
+
|
28
|
+
SCORE_FILE = 'nametrainer.dat'
|
29
|
+
|
30
|
+
# Creates a Collection instance.
|
31
|
+
# It searches in +directory+ for files with the given
|
32
|
+
# file extensions (also in upper case) and populates
|
33
|
+
# the collection with corresponding Person instances.
|
34
|
+
#
|
35
|
+
# +directory+ - collection directory
|
36
|
+
# +extension+ - array of file extensions
|
37
|
+
def initialize(directory, extensions)
|
38
|
+
super()
|
39
|
+
@directory = directory
|
40
|
+
@extensions = extensions.to_set
|
41
|
+
@extensions.merge extensions.map {|i| i.upcase}
|
42
|
+
self.concat self.class.load(@directory, @extensions.to_a)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns an array of all names.
|
46
|
+
def names
|
47
|
+
all_names = Array.new
|
48
|
+
self.each do |person|
|
49
|
+
all_names << person.name
|
50
|
+
end
|
51
|
+
|
52
|
+
all_names
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns a hash with the score of all persons (name => score).
|
56
|
+
def scores
|
57
|
+
all_scores = {}
|
58
|
+
self.each do |person|
|
59
|
+
all_scores[person.name] = person.score
|
60
|
+
end
|
61
|
+
|
62
|
+
all_scores
|
63
|
+
end
|
64
|
+
|
65
|
+
# Sets the score of some or all persons.
|
66
|
+
#
|
67
|
+
# +new_scores+ - hash with (name => score) values
|
68
|
+
def set_scores(new_scores)
|
69
|
+
self.each do |person|
|
70
|
+
person.score = new_scores[person.name] unless new_scores[person.name].nil?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Loads the scores from file (YAML).
|
75
|
+
def import_scores
|
76
|
+
filename = File.expand_path("#{@directory}/#{SCORE_FILE}")
|
77
|
+
new_scores = YAML::load(File.read filename)
|
78
|
+
set_scores(new_scores)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Export all scores to file (YAML).
|
82
|
+
def export_scores
|
83
|
+
filename = File.expand_path("#{@directory}/#{SCORE_FILE}")
|
84
|
+
File.open(filename, 'w') do |f|
|
85
|
+
f.write(scores.to_yaml)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns a random element, preferring persons with a smaller score.
|
90
|
+
def sample
|
91
|
+
self.shuffle.sort[weighted_random_index] # shuffle first, so that elements with equal scores get mixed up
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
# Returns a random index (obsolete).
|
97
|
+
def random_index
|
98
|
+
rand(self.size)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns a random index, preferring smaller indices.
|
102
|
+
def weighted_random_index
|
103
|
+
max_index = self.size - 1
|
104
|
+
weighting_factor = 6 # index 0 will be about weighting_factor times more probable than max_index
|
105
|
+
|
106
|
+
# construct an array with indices by repeatedly adding different parts of a range of indices
|
107
|
+
indices = Array.new
|
108
|
+
range = Array.new(self.size) {|i| i }
|
109
|
+
1.upto(weighting_factor) do
|
110
|
+
|i| indices += range[0..(i.to_f/weighting_factor * max_index).ceil]
|
111
|
+
end
|
112
|
+
|
113
|
+
indices[rand(indices.size)]
|
114
|
+
end
|
115
|
+
|
116
|
+
# class methods
|
117
|
+
|
118
|
+
# Load a collection. Returns an array of Person instances.
|
119
|
+
def self.load(directory, extensions)
|
120
|
+
extension_list = extensions.join(',')
|
121
|
+
files = Dir.glob("#{directory}/*.{#{extension_list}}")
|
122
|
+
result = Array.new
|
123
|
+
files.each do |file|
|
124
|
+
name = get_name(file, extensions)
|
125
|
+
result << Person.new(name, file)
|
126
|
+
end
|
127
|
+
|
128
|
+
result
|
129
|
+
end
|
130
|
+
|
131
|
+
# Get name from corresponding `txt' file or
|
132
|
+
# from file name (remove extension, convert underscores).
|
133
|
+
def self.get_name(file, extensions)
|
134
|
+
name = file.dup
|
135
|
+
extensions.each {|ext| name.gsub!(/\.#{ext}$/, '') } # remove file extension
|
136
|
+
begin
|
137
|
+
info_file = "#{name}.txt"
|
138
|
+
File.read(info_file).chomp
|
139
|
+
rescue
|
140
|
+
File.basename(name).tr('_', ' ')
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,234 @@
|
|
1
|
+
require 'Qt'
|
2
|
+
require 'iconv' if RUBY_VERSION < '1.9' # utf-8 -> latin1 for QLabel
|
3
|
+
|
4
|
+
module Nametrainer
|
5
|
+
|
6
|
+
# Class for the application window.
|
7
|
+
class GUI < Qt::Widget
|
8
|
+
|
9
|
+
# Initializes the application window.
|
10
|
+
# You can specify a collection that is loaded at startup.
|
11
|
+
#
|
12
|
+
# +collection_dir+ - path to collection
|
13
|
+
def initialize(collection_dir = nil)
|
14
|
+
super()
|
15
|
+
|
16
|
+
set_window_title 'Name Trainer'
|
17
|
+
resize 600, 500
|
18
|
+
|
19
|
+
# the common variables
|
20
|
+
@collection_dir = nil
|
21
|
+
@collection = nil
|
22
|
+
@person = nil
|
23
|
+
@statistics = Nametrainer::Statistics.new
|
24
|
+
|
25
|
+
# the common widgets
|
26
|
+
@collection_label = Qt::Label.new '<i>No collection loaded</i>'
|
27
|
+
@image_label = Qt::Label.new
|
28
|
+
@image = Qt::Pixmap.new
|
29
|
+
@name_label = Qt::Label.new
|
30
|
+
@statistics_label = Qt::Label.new @statistics.to_s
|
31
|
+
|
32
|
+
init_gui
|
33
|
+
show
|
34
|
+
|
35
|
+
init_collection File.expand_path(collection_dir) unless collection_dir.nil?
|
36
|
+
end
|
37
|
+
|
38
|
+
# Initializes the GUI layout and functions.
|
39
|
+
def init_gui
|
40
|
+
name_fontsize = 24
|
41
|
+
name_font = Qt::Font.new { setPointSize name_fontsize }
|
42
|
+
@name_label.set_font name_font
|
43
|
+
@image_label.set_size_policy Qt::SizePolicy::Ignored, Qt::SizePolicy::Ignored
|
44
|
+
|
45
|
+
# create buttons
|
46
|
+
@show = Qt::PushButton.new '&Display Name', self do
|
47
|
+
#set_tool_tip 'S'
|
48
|
+
set_shortcut Qt::KeySequence.new(Qt::Key_D.to_i)
|
49
|
+
end
|
50
|
+
@correct = Qt::PushButton.new 'Correct', self do
|
51
|
+
#set_tool_tip 'F'
|
52
|
+
set_shortcut Qt::KeySequence.new(Qt::Key_S.to_i)
|
53
|
+
end
|
54
|
+
@wrong = Qt::PushButton.new 'Wrong', self do
|
55
|
+
#set_tool_tip 'D'
|
56
|
+
set_shortcut Qt::KeySequence.new(Qt::Key_F.to_i)
|
57
|
+
end
|
58
|
+
load_collection = Qt::PushButton.new '&Load Collection', self do
|
59
|
+
#set_tool_tip 'L'
|
60
|
+
set_shortcut Qt::KeySequence.new(Qt::Key_L.to_i)
|
61
|
+
end
|
62
|
+
help = Qt::PushButton.new '&Help', self do
|
63
|
+
#set_tool_tip 'H'
|
64
|
+
set_shortcut Qt::KeySequence.new(Qt::Key_H.to_i)
|
65
|
+
end
|
66
|
+
quit = Qt::PushButton.new 'Quit', self do
|
67
|
+
#set_tool_tip 'Ctrl+W'
|
68
|
+
set_shortcut Qt::KeySequence.new(Qt::CTRL.to_i + Qt::Key_W.to_i)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Connect the buttons to slots.
|
72
|
+
connect load_collection, SIGNAL(:clicked), self, SLOT(:load_collection)
|
73
|
+
connect quit, SIGNAL(:clicked), self, SLOT(:quit)
|
74
|
+
connect help, SIGNAL(:clicked), self, SLOT(:show_help)
|
75
|
+
connect @correct, SIGNAL(:clicked), self, SLOT(:correct_answer)
|
76
|
+
connect @wrong, SIGNAL(:clicked), self, SLOT(:wrong_answer)
|
77
|
+
connect @show, SIGNAL(:clicked), self, SLOT(:display_name)
|
78
|
+
|
79
|
+
# Start with buttons disabled.
|
80
|
+
disable_buttons
|
81
|
+
|
82
|
+
# Create the layout.
|
83
|
+
vbox = Qt::VBoxLayout.new self
|
84
|
+
mainbox = Qt::HBoxLayout.new
|
85
|
+
|
86
|
+
vbox.add_widget @collection_label
|
87
|
+
|
88
|
+
left = Qt::VBoxLayout.new
|
89
|
+
left.add_widget @image_label, 1
|
90
|
+
left.add_widget @name_label
|
91
|
+
|
92
|
+
answer_buttons = Qt::HBoxLayout.new
|
93
|
+
answer_buttons.add_widget @correct
|
94
|
+
answer_buttons.add_widget @wrong
|
95
|
+
|
96
|
+
right = Qt::VBoxLayout.new
|
97
|
+
right.add_widget @show
|
98
|
+
right.add_layout answer_buttons
|
99
|
+
right.add_stretch 1
|
100
|
+
right.add_widget Qt::Label.new 'Statistics:'
|
101
|
+
right.add_widget @statistics_label
|
102
|
+
right.add_stretch 3
|
103
|
+
right.add_widget load_collection
|
104
|
+
right.add_widget help
|
105
|
+
right.add_widget quit
|
106
|
+
|
107
|
+
mainbox.add_layout left, 1
|
108
|
+
mainbox.add_layout right
|
109
|
+
|
110
|
+
vbox.add_layout mainbox
|
111
|
+
|
112
|
+
set_layout vbox
|
113
|
+
end
|
114
|
+
|
115
|
+
slots :load_collection, :quit, :show_help, :display_name, :correct_answer, :wrong_answer
|
116
|
+
|
117
|
+
# Opens a file dialog and tries to load a collection.
|
118
|
+
def load_collection
|
119
|
+
collection_dir = Qt::FileDialog.get_existing_directory self, 'Load Collection'
|
120
|
+
return if collection_dir.nil?
|
121
|
+
init_collection File.expand_path(collection_dir)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Tries to load a collection (does not change anything if load fails).
|
125
|
+
#
|
126
|
+
# +collection_dir+ - path to collection
|
127
|
+
def init_collection(collection_dir)
|
128
|
+
collection = Nametrainer::Collection.new(collection_dir, Nametrainer::FILE_EXTENSIONS)
|
129
|
+
if collection.nil? or collection.empty?
|
130
|
+
Qt::MessageBox.warning self, 'Error', Nametrainer.collection_empty_message
|
131
|
+
return
|
132
|
+
end
|
133
|
+
@collection_dir = collection_dir
|
134
|
+
@collection = collection
|
135
|
+
@collection_label.set_text "Collection: #{File.basename(@collection_dir)}"
|
136
|
+
@statistics.reset
|
137
|
+
@statistics_label.set_text @statistics.to_s
|
138
|
+
choose_person
|
139
|
+
end
|
140
|
+
|
141
|
+
# Disables the buttons that are only needed when a collection is loaded.
|
142
|
+
def disable_buttons
|
143
|
+
@correct.set_enabled false
|
144
|
+
@wrong.set_enabled false
|
145
|
+
@show.set_enabled false
|
146
|
+
end
|
147
|
+
|
148
|
+
# Enables the buttons that are only needed when a collection is loaded.
|
149
|
+
def enable_buttons
|
150
|
+
@correct.set_enabled true
|
151
|
+
@wrong.set_enabled true
|
152
|
+
@show.set_enabled true
|
153
|
+
end
|
154
|
+
|
155
|
+
# Quits application.
|
156
|
+
def quit
|
157
|
+
Qt::Application.instance.quit
|
158
|
+
end
|
159
|
+
|
160
|
+
# Shows the help window.
|
161
|
+
def show_help
|
162
|
+
Qt::MessageBox.about self, 'Help', Nametrainer.help_message
|
163
|
+
end
|
164
|
+
|
165
|
+
# Displays the name of the shown person and disables the <tt>Display Name</tt> button.
|
166
|
+
def display_name
|
167
|
+
# convert name string from utf-8 to latin1 (for QLabel)
|
168
|
+
if RUBY_VERSION < '1.9'
|
169
|
+
name_latin1 = Iconv.conv('LATIN1', 'UTF-8', @person.name)
|
170
|
+
else
|
171
|
+
name_latin1 = @person.name.encode('ISO-8859-1', 'UTF-8')
|
172
|
+
end
|
173
|
+
@name_label.set_text name_latin1
|
174
|
+
@show.set_enabled false
|
175
|
+
end
|
176
|
+
|
177
|
+
# Called when +Correct+ button is clicked.
|
178
|
+
def correct_answer
|
179
|
+
handle_answer(:correct)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Called when +Wrong+ button is clicked.
|
183
|
+
def wrong_answer
|
184
|
+
handle_answer(:wrong)
|
185
|
+
end
|
186
|
+
|
187
|
+
# Handles correct and wrong answers, and chooses the next person.
|
188
|
+
#
|
189
|
+
# +answer+ - <tt>:correct</tt> or <tt>:wrong</tt>
|
190
|
+
def handle_answer(answer)
|
191
|
+
update_statistics(answer)
|
192
|
+
update_scores(answer)
|
193
|
+
choose_person
|
194
|
+
end
|
195
|
+
|
196
|
+
# Updates the statistics depending on answer.
|
197
|
+
def update_statistics(answer)
|
198
|
+
case answer
|
199
|
+
when :correct
|
200
|
+
@statistics.correct += 1
|
201
|
+
when :wrong
|
202
|
+
@statistics.wrong += 1
|
203
|
+
end
|
204
|
+
@statistics_label.set_text @statistics.to_s
|
205
|
+
end
|
206
|
+
|
207
|
+
# Increases the score for correctly recognized persons.
|
208
|
+
def update_scores(answer)
|
209
|
+
@person.increase_score if answer == :correct
|
210
|
+
end
|
211
|
+
|
212
|
+
# Displays the image, scaled as large as possible.
|
213
|
+
def show_image
|
214
|
+
@image_label.set_pixmap @image.scaled(@image_label.size, Qt::KeepAspectRatio)
|
215
|
+
end
|
216
|
+
|
217
|
+
# Chooses and displays the next person,
|
218
|
+
# erases the displayed name and enables the <tt>Display Name</tt> button.
|
219
|
+
def choose_person
|
220
|
+
@name_label.set_text ''
|
221
|
+
@person = @collection.sample
|
222
|
+
@image.load @person.image or @image = Qt::Pixmap.new # delete image when load fails
|
223
|
+
show_image
|
224
|
+
enable_buttons
|
225
|
+
end
|
226
|
+
|
227
|
+
# Repaints the image when the application window is resized.
|
228
|
+
def resizeEvent(event)
|
229
|
+
return if @image.null? || @image.nil?
|
230
|
+
scaledSize = @image.size.scale(@image_label.size, Qt::KeepAspectRatio)
|
231
|
+
show_image if scaledSize != @image_label.pixmap.size
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
require 'nametrainer/version'
|
4
|
+
|
5
|
+
module Nametrainer
|
6
|
+
|
7
|
+
# Parser for the command line options.
|
8
|
+
# The class method parse! does the job.
|
9
|
+
class Optionparser
|
10
|
+
|
11
|
+
# Parses the command line options from +argv+.
|
12
|
+
# (+argv+ is cleared).
|
13
|
+
# Might print out help or version information.
|
14
|
+
#
|
15
|
+
# +argv+ - array with the command line options
|
16
|
+
#
|
17
|
+
# Returns a hash containing the option parameters.
|
18
|
+
def self.parse!(argv)
|
19
|
+
|
20
|
+
options = {
|
21
|
+
:collection => nil,
|
22
|
+
:demo => false
|
23
|
+
}
|
24
|
+
|
25
|
+
opt_parser = OptionParser.new do |opt|
|
26
|
+
opt.banner = "Usage: #{PROGNAME} [options] [collection]"
|
27
|
+
opt.separator ''
|
28
|
+
opt.separator "#{PROGNAME} is a name learning trainer using Ruby and the Qt GUI toolkit."
|
29
|
+
opt.separator "It will assist you in learning people's names from a collection of images."
|
30
|
+
opt.separator ''
|
31
|
+
opt.separator 'See the project home page for additional information.'
|
32
|
+
opt.separator ''
|
33
|
+
opt.separator 'Options'
|
34
|
+
opt.separator ''
|
35
|
+
|
36
|
+
# process --version and --help first,
|
37
|
+
# exit successfully (GNU Coding Standards)
|
38
|
+
opt.on_tail('-h', '--help', 'Prints a brief help message and exits.') do
|
39
|
+
puts opt_parser
|
40
|
+
puts "\nReport bugs on the #{PROGNAME} home page: <#{HOMEPAGE}>"
|
41
|
+
exit
|
42
|
+
end
|
43
|
+
|
44
|
+
opt.on_tail('-v', '--version',
|
45
|
+
'Prints a brief version information and exits.') do
|
46
|
+
puts "#{PROGNAME} #{VERSION}"
|
47
|
+
puts COPYRIGHT
|
48
|
+
exit
|
49
|
+
end
|
50
|
+
|
51
|
+
opt.on('-d', '--demo', 'Starts the application with a demo collection.') do
|
52
|
+
options[:demo] = true
|
53
|
+
end
|
54
|
+
|
55
|
+
opt.separator ''
|
56
|
+
end
|
57
|
+
opt_parser.parse!(argv)
|
58
|
+
|
59
|
+
# only collection directory should be left in argv
|
60
|
+
if options[:demo]
|
61
|
+
raise(ArgumentError, 'wrong number of arguments') if (argv.size != 0 || argv[0] == '')
|
62
|
+
else
|
63
|
+
raise(ArgumentError, 'wrong number of arguments') if (argv.size > 1 || argv[0] == '')
|
64
|
+
options[:collection] = argv.pop if argv.size == 1
|
65
|
+
end
|
66
|
+
|
67
|
+
options
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Nametrainer
|
2
|
+
|
3
|
+
# A class for persons with name, image, and score.
|
4
|
+
#
|
5
|
+
# Create an instance with
|
6
|
+
# person = Person.new(name, image)
|
7
|
+
#
|
8
|
+
# Increase the score with
|
9
|
+
# person.increase_score
|
10
|
+
#
|
11
|
+
# You can sort Person instances by score:
|
12
|
+
# person1 = Person.new('Albert', nil)
|
13
|
+
# person2 = Person.new('Isaac', nil)
|
14
|
+
# person1.increase_score
|
15
|
+
# puts [person1, person2].sort.map{|p| p.name} => Isaac, Albert
|
16
|
+
class Person
|
17
|
+
|
18
|
+
attr_reader :name, :image
|
19
|
+
attr_accessor :score
|
20
|
+
|
21
|
+
def initialize(name, image)
|
22
|
+
@name = name
|
23
|
+
@image = image
|
24
|
+
@score = 0
|
25
|
+
end
|
26
|
+
|
27
|
+
# Increases the score by 1.
|
28
|
+
def increase_score
|
29
|
+
@score += 1
|
30
|
+
end
|
31
|
+
|
32
|
+
# Uses score for sorting.
|
33
|
+
def <=>(other)
|
34
|
+
self.score <=> other.score
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Nametrainer
|
2
|
+
|
3
|
+
# A class for statistics.
|
4
|
+
#
|
5
|
+
# It keeps track of the number of correct and wrong answers.
|
6
|
+
#
|
7
|
+
# Create an instance with
|
8
|
+
# stats = Statistics.new
|
9
|
+
#
|
10
|
+
# Set the values
|
11
|
+
# stats.correct = 6
|
12
|
+
# stats.wrong = 2
|
13
|
+
#
|
14
|
+
# Print the percentage
|
15
|
+
# puts stats.to_s => 75 % (6/8)
|
16
|
+
class Statistics
|
17
|
+
|
18
|
+
attr_accessor :correct, :wrong
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
reset
|
22
|
+
end
|
23
|
+
|
24
|
+
# Resets all values to zero.
|
25
|
+
def reset
|
26
|
+
@correct = 0
|
27
|
+
@wrong = 0
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the total number of answers.
|
31
|
+
def total
|
32
|
+
@correct + @wrong
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns a string with percentage and correct and total answers.
|
36
|
+
def to_s
|
37
|
+
percent = (total == 0) ? 0 : (@correct.to_f / total.to_f * 100).to_i
|
38
|
+
|
39
|
+
"#{percent} % (#{@correct}/#{total})"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Nametrainer
|
2
|
+
|
3
|
+
PROGNAME = 'nametrainer'
|
4
|
+
VERSION = '0.0.1'
|
5
|
+
DATE = '2012-04-06'
|
6
|
+
HOMEPAGE = 'https://github.com/stomar/nametrainer/'
|
7
|
+
|
8
|
+
COPYRIGHT = "Copyright (C) 2012 Marcus Stollsteimer.\n" +
|
9
|
+
"License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n" +
|
10
|
+
"This is free software: you are free to change and redistribute it.\n" +
|
11
|
+
"There is NO WARRANTY, to the extent permitted by law."
|
12
|
+
|
13
|
+
end
|
data/nametrainer.gemspec
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'lib/nametrainer/version'
|
2
|
+
|
3
|
+
version = Nametrainer::VERSION
|
4
|
+
date = Nametrainer::DATE
|
5
|
+
homepage = Nametrainer::HOMEPAGE
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = 'nametrainer'
|
9
|
+
s.version = version
|
10
|
+
s.date = date
|
11
|
+
s.rubyforge_project = 'nametrainer'
|
12
|
+
|
13
|
+
s.summary = 'nametrainer is a name learning trainer using the Qt GUI toolkit.'
|
14
|
+
s.description = s.summary + ' ' +
|
15
|
+
'It will assist you in learning people’s names from a collection of images.'
|
16
|
+
|
17
|
+
s.authors = ['Marcus Stollsteimer']
|
18
|
+
s.email = 'sto.mar@web.de'
|
19
|
+
s.homepage = homepage
|
20
|
+
|
21
|
+
s.license = 'GPL-3'
|
22
|
+
|
23
|
+
s.requirements << 'the Qt toolkit and Qt bindings for Ruby'
|
24
|
+
|
25
|
+
s.executables = ['nametrainer']
|
26
|
+
s.bindir = 'bin'
|
27
|
+
s.require_path = 'lib'
|
28
|
+
s.test_files = Dir.glob('test/**/test_*.rb')
|
29
|
+
|
30
|
+
s.rdoc_options = ['--charset=UTF-8']
|
31
|
+
|
32
|
+
s.files = %w{
|
33
|
+
README.md
|
34
|
+
screenshot.png
|
35
|
+
Rakefile
|
36
|
+
nametrainer.gemspec
|
37
|
+
} +
|
38
|
+
Dir.glob('{bin,demo_collection,lib,man,test}/**/*')
|
39
|
+
|
40
|
+
#s.add_dependency('iconv') # only for 1.8; problematic on Windows (build tools necessary)
|
41
|
+
s.add_development_dependency('rake')
|
42
|
+
s.add_development_dependency('minitest')
|
43
|
+
end
|
data/screenshot.png
ADDED
Binary file
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
Albert Einstein
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
Paul Dirac
|
File without changes
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'minitest/spec'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
|
4
|
+
require 'nametrainer/collection'
|
5
|
+
|
6
|
+
SRCPATH = File.dirname(__FILE__)
|
7
|
+
|
8
|
+
describe Nametrainer::Collection do
|
9
|
+
|
10
|
+
before do
|
11
|
+
extensions = %w{png jpg}
|
12
|
+
@collection = Nametrainer::Collection.new("#{SRCPATH}/collection", extensions)
|
13
|
+
@sample_scores = {
|
14
|
+
'Albert Einstein' => 3,
|
15
|
+
'Paul Dirac' => 1,
|
16
|
+
'Max Born' => 2
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'can return a name list' do
|
21
|
+
@collection.names.must_include 'Albert Einstein'
|
22
|
+
@collection.names.must_include 'Paul Dirac'
|
23
|
+
@collection.names.must_include 'Max Born'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'has a size' do
|
27
|
+
@collection.size.must_equal 3
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'can return all the scores' do
|
31
|
+
scores = {
|
32
|
+
'Albert Einstein' => 0,
|
33
|
+
'Paul Dirac' => 0,
|
34
|
+
'Max Born' => 0
|
35
|
+
}
|
36
|
+
@collection.scores.must_equal scores
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'can set all the scores' do
|
40
|
+
@collection.set_scores(@sample_scores)
|
41
|
+
@collection.scores.must_equal @sample_scores
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'can save the scores to a file and load them again' do
|
45
|
+
@collection.set_scores(@sample_scores)
|
46
|
+
@collection.export_scores
|
47
|
+
@collection.first.score = nil
|
48
|
+
@collection.import_scores
|
49
|
+
@collection.scores.must_equal @sample_scores
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'can get sorted by the scores' do
|
53
|
+
@collection.set_scores(@sample_scores)
|
54
|
+
@collection.sort.first.name.must_equal 'Paul Dirac'
|
55
|
+
@collection.sort.last.name.must_equal 'Albert Einstein'
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'can return a random index' do
|
59
|
+
samples = 100
|
60
|
+
indices = Array.new(samples) { @collection.send(:weighted_random_index) }
|
61
|
+
indices.size.must_equal samples
|
62
|
+
indices.sort.first.must_be :>=, 0
|
63
|
+
indices.sort.last.must_be :<, @collection.size
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'can return a random person' do
|
67
|
+
@collection.sample.must_be_instance_of Nametrainer::Person
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'can increase the score for a specified person' do
|
71
|
+
person = @collection.last
|
72
|
+
person.score.must_equal 0
|
73
|
+
person.increase_score
|
74
|
+
@collection.last.score.must_equal 1
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'minitest/spec'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
|
4
|
+
require 'nametrainer/optionparser'
|
5
|
+
|
6
|
+
describe Nametrainer::Optionparser do
|
7
|
+
|
8
|
+
it 'should return the correct default values' do
|
9
|
+
options = Nametrainer::Optionparser.parse!(['collection'])
|
10
|
+
expected = {
|
11
|
+
:collection => 'collection',
|
12
|
+
:demo => false
|
13
|
+
}
|
14
|
+
options.must_equal expected
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should recognize the -d option' do
|
18
|
+
options = Nametrainer::Optionparser.parse!(['-d'])
|
19
|
+
options[:collection].must_be_nil
|
20
|
+
options[:demo].must_equal true
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should not accept wrong number of arguments' do
|
24
|
+
lambda { Nametrainer::Optionparser.parse!(['collection1', 'collection2']) }.must_raise ArgumentError
|
25
|
+
lambda { Nametrainer::Optionparser.parse!(['']) }.must_raise ArgumentError
|
26
|
+
lambda { Nametrainer::Optionparser.parse!(['-d', 'collection']) }.must_raise ArgumentError
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should not accept invalid options' do
|
30
|
+
lambda { Nametrainer::Optionparser.parse!(['-x']) }.must_raise OptionParser::InvalidOption
|
31
|
+
end
|
32
|
+
end
|
data/test/test_person.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'minitest/spec'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
|
4
|
+
require 'nametrainer/person'
|
5
|
+
|
6
|
+
describe Nametrainer::Person do
|
7
|
+
|
8
|
+
before do
|
9
|
+
@person = Nametrainer::Person.new('Albert Einstein', 'image01.jpg')
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'has a name, an associated image file and a score' do
|
13
|
+
@person.name.must_equal 'Albert Einstein'
|
14
|
+
@person.image.must_equal 'image01.jpg'
|
15
|
+
@person.score.must_equal 0
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'can increase its score' do
|
19
|
+
@person.score.must_equal 0
|
20
|
+
@person.increase_score
|
21
|
+
@person.score.must_equal 1
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'can get sorted by score' do
|
25
|
+
p1 = Nametrainer::Person.new('Albert', nil)
|
26
|
+
p2 = Nametrainer::Person.new('Isaac', nil)
|
27
|
+
p1.score = 1
|
28
|
+
[p1, p2].sort.map{|p| p.name}.must_equal ['Isaac', 'Albert']
|
29
|
+
p2.score = 2
|
30
|
+
[p1, p2].sort.map{|p| p.name}.must_equal ['Albert', 'Isaac']
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'minitest/spec'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
|
4
|
+
require 'nametrainer/statistics'
|
5
|
+
|
6
|
+
describe Nametrainer::Statistics do
|
7
|
+
|
8
|
+
before do
|
9
|
+
@stats = Nametrainer::Statistics.new
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'has the correct initial values' do
|
13
|
+
@stats.correct.must_equal 0
|
14
|
+
@stats.wrong.must_equal 0
|
15
|
+
@stats.total.must_equal 0
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'can set its attributes' do
|
19
|
+
@stats.correct = 3
|
20
|
+
@stats.correct.must_equal 3
|
21
|
+
@stats.wrong = 2
|
22
|
+
@stats.wrong.must_equal 2
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'knows the total' do
|
26
|
+
@stats.correct = 8
|
27
|
+
@stats.wrong = 2
|
28
|
+
@stats.total.must_equal 10
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'can be reset' do
|
32
|
+
@stats.correct = 12
|
33
|
+
@stats.wrong = 8
|
34
|
+
@stats.total.must_equal 20
|
35
|
+
@stats.reset
|
36
|
+
@stats.total.must_equal 0
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'can print the statistics for total = 0' do
|
40
|
+
@stats.to_s.must_equal '0 % (0/0)'
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'can print the statistics' do
|
44
|
+
@stats.correct = 9
|
45
|
+
@stats.wrong = 11
|
46
|
+
@stats.to_s.must_equal '45 % (9/20)'
|
47
|
+
end
|
48
|
+
end
|
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nametrainer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Marcus Stollsteimer
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-04-06 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rake
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: minitest
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
description: "nametrainer is a name learning trainer using the Qt GUI toolkit. It will assist you in learning people\xE2\x80\x99s names from a collection of images."
|
49
|
+
email: sto.mar@web.de
|
50
|
+
executables:
|
51
|
+
- nametrainer
|
52
|
+
extensions: []
|
53
|
+
|
54
|
+
extra_rdoc_files: []
|
55
|
+
|
56
|
+
files:
|
57
|
+
- README.md
|
58
|
+
- screenshot.png
|
59
|
+
- Rakefile
|
60
|
+
- nametrainer.gemspec
|
61
|
+
- bin/nametrainer
|
62
|
+
- demo_collection/Erwin_Schroedinger.png
|
63
|
+
- demo_collection/Erwin_Schroedinger.txt
|
64
|
+
- demo_collection/Felix_Bloch.png
|
65
|
+
- demo_collection/Max_Planck.png
|
66
|
+
- demo_collection/Max_Born.png
|
67
|
+
- demo_collection/Paul_Dirac.png
|
68
|
+
- demo_collection/Werner_Heisenberg.png
|
69
|
+
- demo_collection/Albert_Einstein.png
|
70
|
+
- lib/nametrainer/optionparser.rb
|
71
|
+
- lib/nametrainer/gui.rb
|
72
|
+
- lib/nametrainer/version.rb
|
73
|
+
- lib/nametrainer/person.rb
|
74
|
+
- lib/nametrainer/collection.rb
|
75
|
+
- lib/nametrainer/statistics.rb
|
76
|
+
- lib/nametrainer.rb
|
77
|
+
- test/test_person.rb
|
78
|
+
- test/test_statistics.rb
|
79
|
+
- test/test_collection.rb
|
80
|
+
- test/test_optionparser.rb
|
81
|
+
- test/collection/002.txt
|
82
|
+
- test/collection/001.jpg
|
83
|
+
- test/collection/001.txt
|
84
|
+
- test/collection/Max_Born.png
|
85
|
+
- test/collection/002.JPG
|
86
|
+
- test/collection/nametrainer.dat
|
87
|
+
homepage: https://github.com/stomar/nametrainer/
|
88
|
+
licenses:
|
89
|
+
- GPL-3
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options:
|
92
|
+
- --charset=UTF-8
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
hash: 3
|
101
|
+
segments:
|
102
|
+
- 0
|
103
|
+
version: "0"
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
hash: 3
|
110
|
+
segments:
|
111
|
+
- 0
|
112
|
+
version: "0"
|
113
|
+
requirements:
|
114
|
+
- the Qt toolkit and Qt bindings for Ruby
|
115
|
+
rubyforge_project: nametrainer
|
116
|
+
rubygems_version: 1.7.2
|
117
|
+
signing_key:
|
118
|
+
specification_version: 3
|
119
|
+
summary: nametrainer is a name learning trainer using the Qt GUI toolkit.
|
120
|
+
test_files:
|
121
|
+
- test/test_person.rb
|
122
|
+
- test/test_statistics.rb
|
123
|
+
- test/test_collection.rb
|
124
|
+
- test/test_optionparser.rb
|