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.
@@ -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