nametrainer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 &copy; 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/
@@ -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
@@ -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
@@ -0,0 +1 @@
1
+ Erwin Schrödinger
@@ -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 &nbsp;&nbsp;&nbsp;</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\)/, '&copy;')}<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
@@ -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
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,4 @@
1
+ ---
2
+ Paul Dirac: 1
3
+ Max Born: 2
4
+ Albert Einstein: 3
@@ -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
@@ -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