Commit be11249c authored by Jojo Boulix's avatar Jojo Boulix

Can now use a barcode stuck in the name field to identify students.

Refs #355.
parent 86e343c1
Pipeline #84489537 failed with stage
in 12 minutes and 50 seconds
#! /usr/bin/perl
#
# Copyright (C) 2019 Alexis Bienvenue <paamc@passoire.fr>
#
# This file is part of Auto-Multiple-Choice
#
# Auto-Multiple-Choice is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 2 of
# the License, or (at your option) any later version.
#
# Auto-Multiple-Choice is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Auto-Multiple-Choice. If not, see
# <http://www.gnu.org/licenses/>.
use Getopt::Long;
use AMC::Basic;
use AMC::Data;
use AMC::DataModule::capture qw/:zone/;
use AMC::DataModule::scoring qw/:direct/;
use AMC::Queue;
use AMC::Gui::Avancement;
use Module::Load;
my $zone_type = ZONE_NAME;
my $tag = 'names';
my $decoder_name = '';
my $all = 0;
my $projects_dir = $ENV{'HOME'}.'/'.__("MC-Projects");
my $project_dir = '';
my $cr_dir = '';
my $data_dir = '';
my $debug = '';
my $n_procs=0;
my $progress=0;
my $progress_id=0;
GetOptions("cr=s" => \$cr_dir,
"project=s" => \$project_dir,
"projects-dir=s",\$projects_dir,
"data=s" => \$data_dir,
"zone-type=s" => \$zone_type,
"tag=s" => \$tag,
"all!" => \$all,
"decoder=s" => \$decoder_name,
"debug=s" => \$debug,
"n-procs=s" => \$n_procs,
"progression=s"=>\$progress,
"progression-id=s"=>\$progress_id,
);
$project_dir=$projects_dir.'/'.$project_dir if($project_dir !~ /\//);
$cr_dir=$project_dir."/cr" if(! $cr_dir);
$data_dir=$project_dir."/data" if(! $data_dir);
set_debug($debug);
my $queue='';
my $progress_h=AMC::Gui::Avancement::new($progress, 'id'=>$progress_id);
sub catch_signal {
my $signame = shift;
debug "*** AMC-decode : signal $signame, transfered to $pid...";
kill 2, $pid if($pid);
$queue->killall() if($queue);
die "Killed";
}
$SIG{INT} = \&catch_signal;
my $data = AMC::Data->new($data_dir);
$data->require_module('capture');
$data->require_module('scoring');
$data->begin_transaction('Deco');
my @all_zones = @{$data->module('capture')->zone_images_available($zone_type)};
my $last_decoded = $data->module('capture')->variable('last_decoded_'.$tag);
$last_decoded = 0 if(!$last_decoded);
$data->module('scoring')->clear_code_direct(DIRECT_NAMEFIELD)
if($all);
if(!$decoder_name) {
$data->end_transaction('Deco');
debug("No decoder!");
exit(0);
}
my $t=time();
load("AMC::Decoder::$decoder_name");
my $decoder = "AMC::Decoder::$decoder_name"->new();
$queue=AMC::Queue::new('max.procs', $n_procs);
my $delta;
sub decode_one {
my ($z) = @_;
my $path = $z->{image};
$path = "$cr_dir/$path" if($path);
my $d = $decoder->decode_image($path, $z->{imagedata});
debug("Zone $z->{zoneid}: $d->{ok} ($d->{status}) [$d->{value}]");
print "Student $z->{student}/$z->{copy}: "
.($d->{ok} ? "success" : "FAILED")
." ($d->{status})"
.($d->{ok} ? " -> $d->{value}" : "")
."\n";
$data->connect;
my $scoring = $data->module('scoring');
$scoring->begin_transaction('DecS');
$scoring->new_code( $z->{student}, $z->{copy}, "_namefield", $d->{value},
DIRECT_NAMEFIELD );
$scoring->end_transaction('DecS');
$progress_h->progres($delta);
}
my $n_zones = 0;
for my $z (@all_zones) {
debug("$z->{zoneid} $z->{image} $z->{timestamp_auto}".
($z->{timestamp_auto} >= $last_decoded ? " [X]" : ""));
if($all || $z->{timestamp_auto} >= $last_decoded) {
$n_zones += 1;
$queue->add_process(\&decode_one, $z);
}
}
$data->end_transaction('Deco');
$data->disconnect();
$delta = ($n_zones>1 ? 1/$n_zones : 1);
$queue->run();
$progress_h->fin();
debug("New last_decoded time for $tag: $t");
$data->connect;
$capture = $data->module('capture');
$capture->variable_transaction('last_decoded_' . $tag, $t);
......@@ -1902,6 +1902,34 @@
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label150">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Name field type</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">4</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="pref_c_defaut_name_field_type">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext_nft"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">4</property>
</packing>
</child>
</object>
</child>
</object>
......@@ -3717,6 +3745,34 @@
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label151">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Name field type</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="pref_projet_c_name_field_type">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext_nft0"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
</object>
</child>
</object>
......
......@@ -2931,6 +2931,20 @@
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkModelButton" id="decode_menu">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="text" translatable="yes">Decode name fields</property>
<signal name="clicked" handler="decode_name_fields_again" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkModelButton" id="cleanup_menu">
<property name="visible">True</property>
......@@ -2942,7 +2956,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="position">2</property>
</packing>
</child>
<child>
......@@ -2956,7 +2970,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">3</property>
</packing>
</child>
<child>
......@@ -2970,7 +2984,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
<property name="position">4</property>
</packing>
</child>
<child>
......@@ -2981,7 +2995,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
<property name="position">5</property>
</packing>
</child>
<child>
......@@ -2995,7 +3009,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
<property name="position">6</property>
</packing>
</child>
</object>
......
......@@ -183,6 +183,16 @@ sub hex_color {
my @export_modules=@{$config->{export_modules}};
# Reads decoders list
my @decoders=perl_module_search('AMC::Decoder::register');
for my $m (@decoders) {
load("AMC::Decoder::register::$m");
}
@decoders=sort { "AMC::Decoder::register::$a"->weight
<=> "AMC::Decoder::register::$b"->weight }
@decoders;
# Reads filter plugins list
my @filter_modules=perl_module_search('AMC::Filter::register');
......@@ -845,6 +855,8 @@ my $rounding_store=cb_model('inf',__"floor",
# TRANSLATORS: One of the rounding method for marks. This is a menu entry.
'sup',__"ceiling");
my $nftype_store = cb_model(''=>__"Image",map { $_=>"AMC::Decoder::register::$_"->name() } (@decoders));
$prefs->store_register(
# TRANSLATORS: One option for decimal point: use a comma. This is a menu entry
'delimiteur_decimal'=>cb_model(',',__", (comma)",
......@@ -869,6 +881,8 @@ $prefs->store_register(
'assoc_code'=>$cb_model_vide_code,
'format_export'=>cb_model(map { $_=>"AMC::Export::register::$_"->name() } (@export_modules)),
'filter'=>cb_model(map { $_=>"AMC::Filter::register::$_"->name() } (@filter_modules)),
'defaut_name_field_type'=>$nftype_store,
'name_field_type'=>$nftype_store,
# TRANSLATORS: One of the actions that can be done after exporting the marks. Here, do nothing more. This is a menu entry.
'after_export'=>cb_model(""=>__"that's all",
# TRANSLATORS: One of the actions that can be done after exporting the marks. Here, open the exported file. This is a menu entry.
......@@ -3120,6 +3134,60 @@ sub analyse_call_go {
);
}
sub decode_name_fields_again {
decode_name_fields(1);
}
sub decode_name_fields {
my ($all) = @_;
my $type = $config->get('name_field_type');
if($type) {
my $reg = "AMC::Decoder::register::$type"->new();
my $deps = $reg->check_dependencies();
if(! $deps->{ok}) {
my $message = sprintf(__("You selected the decoder \"<i>%s</i>\", which requires some tools that are missing on your system:"), $reg->name())."\n";
if(@{$deps->{perl_modules}}) {
$message .= __("<b>Perl module(s):</b>")." "
.join(", ", @{$deps->{perl_modules}})
."\n";
}
if(@{$deps->{commands}}) {
$message .= __("<b>Command(s):</b>")." "
.join(", ", @{$deps->{commands}})
."\n";
}
$message .= __"Install these dependencies and try again";
my $dialog = Gtk3::MessageDialog
->new($w{'main_window'},
'destroy-with-parent',
'error','ok','');
$dialog->set_markup($message);
$dialog->run;
$dialog->destroy;
return();
}
}
commande('commande'=>["auto-multiple-choice", "decode",
"--debug", debug_file(),
"--data", $config->get_absolute('data'),
"--cr", $config->get_absolute('cr'),
"--project", $shortcuts->absolu('%PROJET/'),
($all ? "--all" : "--no-all"),
"--decoder", $type,
"--progression-id",'decode',
"--progression",1,
],
'signal'=>2,
'texte'=>__"Decoding name field images...",
'progres.id'=>'decode',
'fin'=>\&update_available_codes,
);
}
sub clean_gtk2_filenames {
my @f=@_;
return(
......@@ -3171,6 +3239,7 @@ sub saisie_auto_ok {
'fin'=>sub {
my ($c,%data)=@_;
close($c->{'o'}->{'fh'});
decode_name_fields();
detecte_analyse('apprend'=>1);
assoc_state();
if(!$data{cancelled}) {
......@@ -3958,6 +4027,7 @@ sub noter_calcul {
sub noter_resultat {
$projet{'_scoring'}->begin_read_transaction('MARK');
my $avg=$projet{'_scoring'}->average_mark;
$projet{'_scoring'}->end_transaction('MARK');
my $ok;
my $text;
......@@ -3970,11 +4040,17 @@ sub noter_resultat {
$text=__("No marks computed");
}
set_state('marking',$ok,$text);
update_available_codes();
$w{'onglet_reports'}->set_sensitive(defined($avg));
}
sub update_available_codes {
$projet{'_scoring'}->begin_read_transaction('CODE');
my @codes=$projet{'_scoring'}->codes;
my $pre_assoc=$projet{'_layout'}->pre_association();
$projet{'_scoring'}->end_transaction('MARK');
$projet{'_scoring'}->end_transaction('CODE');
debug "Codes : ".join(',',@codes);
......@@ -3990,10 +4066,8 @@ sub noter_resultat {
debug "Adding pre-association item";
}
$prefs->store_register('assoc_code'=>cb_model(@cbs));
$prefs->transmet_pref($gui,prefix=>'pref_assoc',
$prefs->transmet_pref($gui, prefix=>'pref_assoc',
keys=>['project:assoc_code']);
$w{'onglet_reports'}->set_sensitive(defined($avg));
}
sub assoc_state {
......@@ -4508,6 +4582,29 @@ sub accepte_preferences {
}
}
}
# Run again decoding if the name field type has changed
if($pm{name_field_type}) {
my $response;
if($config->get('name_field_type')) {
my $dialog = Gtk3::MessageDialog
->new($w{'main_window'},
'destroy-with-parent',
'question','ok-cancel','');
$dialog->set_markup(
__("You modified the name field type, so that the name fields has to be decoded again."));
$dialog->get_widget_for_response('ok')->get_style_context()->add_class("suggested-action");
$response = $dialog->run;
$dialog->destroy;
} else {
$response = 'ok';
}
if($response eq 'ok') {
decode_name_fields(1);
}
}
}
if ($projet{'nom'}) {
......
......@@ -325,6 +325,9 @@ for my $sc (@captured_studentcopy) {
$avance->progres($delta);
}
# The end!
$data->end_transaction('MARK');
$avance->fin();
# -*- perl -*-
#
# Copyright (C) 2008-2017 Alexis Bienvenue <paamc@passoire.fr>
# Copyright (C) 2008-2019 Alexis Bienvenue <paamc@passoire.fr>
#
# This file is part of Auto-Multiple-Choice
#
......@@ -44,7 +44,7 @@ BEGIN {
our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
@ISA = qw(Exporter);
@EXPORT = qw( &perl_module_search &amc_specdir &get_sty &file2id &id2idf &get_ep &get_epo &get_epc &get_qr &file_triable &sort_from_columns &sort_string &sort_num &attention &model_id_to_iter &commande_accessible &magick_module &use_gm_command &magick_perl_module &debug &debug_raw &debug_and_stderr &debug_pm_version &set_debug &get_debug &debug_file &use_gettext &clear_old &new_filename &pack_args &unpack_args &__ &__p &translate_column_title &translate_id_name &pageids_string &studentids_string &studentids_string_filename &format_date &cb_model &get_active_id &COMBO_ID &COMBO_TEXT &check_fonts &amc_user_confdir &use_amc_plugins &find_latex_file &file_mimetype &file_content &amc_component &annotate_source_change &join_nonempty &string_to_usascii &string_to_filename &show_utf8 &path_to_filename &free_disk_mo);
@EXPORT = qw( &perl_module_search &amc_specdir &get_sty &file2id &id2idf &get_ep &get_epo &get_epc &get_qr &file_triable &sort_from_columns &sort_string &sort_num &attention &model_id_to_iter &commande_accessible &magick_module &use_gm_command &magick_perl_module &debug &debug_raw &debug_and_stderr &debug_pm_version &set_debug &get_debug &debug_file &use_gettext &clear_old &new_filename &pack_args &unpack_args &__ &__p &translate_column_title &translate_id_name &pageids_string &studentids_string &studentids_string_filename &format_date &cb_model &get_active_id &COMBO_ID &COMBO_TEXT &check_fonts &amc_user_confdir &use_amc_plugins &find_latex_file &file_mimetype &file_content &blob_to_file &amc_component &annotate_source_change &join_nonempty &string_to_usascii &string_to_filename &show_utf8 &path_to_filename &free_disk_mo);
%EXPORT_TAGS = ( ); # eg: TAG => [ qw!name1 name2! ],
# your exported package globals go here,
......@@ -771,6 +771,17 @@ sub file_content {
return($c);
}
sub blob_to_file {
my ($blob) = @_;
my $file = new File::Temp(TEMPLATE =>'AMC-IMAGE-XXXXXXXX',
UNLINK => 0,
DIR => File::Spec->tmpdir);
binmode($file);
print $file $blob;
close $file;
return($file->filename);
}
sub string_to_usascii {
my ($s)=@_;
utf8::decode($s);
......
......@@ -360,6 +360,8 @@ sub defaults {
view_invalid_color=>"#FFEF3B",
view_empty_color=>"#78FFED",
defaut_name_field_type=>'',
};
$self->{project_default} =
......@@ -432,6 +434,8 @@ sub defaults {
email_use_html=>'',
pdfform=>0,
name_field_type=>'',
};
# MacOSX universal command to open files or directories : /usr/bin/open
......
# -*- perl -*-
#
# Copyright (C) 2011-2017 Alexis Bienvenue <paamc@passoire.fr>
# Copyright (C) 2011-2019 Alexis Bienvenue <paamc@passoire.fr>
#
# This file is part of Auto-Multiple-Choice
#
......@@ -37,6 +37,7 @@ sub new {
'timeout'=>300000,
'dbh'=>'',
'modules'=>{},
'version_checked' => {},
'files'=>{},
'on_error'=>'stdout,stderr,die',
'progress'=>'',
......@@ -68,16 +69,26 @@ sub connect {
$self->sql_error(shift);
};
my @mods=(keys %{$self->{'modules'}});
$self->{'modules'}={};
for(@mods) { $self->require_module($_); }
$self->{modules}={};
for my $m (keys %{$self->{version_checked}}) {
debug("Connects module $m ($self->{version_checked}->{$m})");
$self->require_module($m, version_checked => $self->{version_checked}->{$m});
}
}
# Disconnects...
sub disconnect {
my ($self)=@_;
$self->{version_checked} = {};
if($self->{'dbh'}) {
# record the loaded modules, to be able to load them back with connect()
for my $m (keys %{$self->{modules}}) {
$self->{version_checked}->{$m}
= $self->{modules}->{$m}->{version_checked};
}
# disconnect and drop all references
$self->{modules} = {};
$self->{'dbh'}->disconnect;
$self->{'dbh'}='';
}
......@@ -173,7 +184,7 @@ sub sql_tables {
# defined in AMC::DataModule::$module perl package.
sub require_module {
my ($self,$module)=@_;
my ($self, $module, %oo)=@_;
if(!$self->{'modules'}->{$module}) {
my $filename=$self->{'directory'}."/".$module.".sqlite";
utf8::downgrade($filename);
......@@ -190,7 +201,7 @@ sub require_module {
debug "Loading perl module $module...";
load("AMC::DataModule::$module");
$self->{'modules'}->{$module}="AMC::DataModule::$module"->new($self);
$self->{'modules'}->{$module}="AMC::DataModule::$module"->new($self, %oo);
$self->{'files'}->{$module}=$filename;
debug "Module $module loaded.";
......@@ -201,8 +212,8 @@ sub require_module {
# $module (call the methods from module $module from this object).
sub module {
my ($self,$module)=@_;
$self->require_module($module);
my ($self, $module, %oo)=@_;
$self->require_module($module, %oo);
return($self->{'modules'}->{$module});
}
......
# -*- perl -*-
#
# Copyright (C) 2011-2017 Alexis Bienvenue <paamc@passoire.fr>
# Copyright (C) 2011-2019 Alexis Bienvenue <paamc@passoire.fr>
#
# This file is part of Auto-Multiple-Choice
#
......@@ -34,14 +34,15 @@ use AMC::Basic;
# stores its root in $self->{'data'}
sub new {
my ($class,$data,%oo)=@_;
my ($class, $data, %oo)=@_;
my $self=
{
'data'=>$data,
'name'=>'',
'statements'=>{},
immutable=>{},
data => $data,
name => '',
statements => {},
immutable => {},
version_checked => 0,
};
for(keys %oo) {
......@@ -55,8 +56,11 @@ sub new {
bless($self,$class);
$self->define_statements;
debug "Checking database version...";
$self->version_check;
if(! $self->{version_checked}) {
debug "Checking database version...";
$self->version_check;
}
return $self;
}
......@@ -80,15 +84,15 @@ sub path {
# module(name) returns another module from same data
sub module {
my ($self,$name)=@_;
return($self->{data}->module($name));
my ($self, $name, %oo)=@_;
return($self->{data}->module($name, %oo));
}
# require_module(name) loads the module for the same data
sub require_module {
my ($self,$name)=@_;
return($self->{data}->require_module($name));
my ($self, $name, %oo)=@_;
return($self->{data}->require_module($name, %oo));
}
# vacuum() loads the SQLite database separately, and asks for VACUUM
......@@ -429,6 +433,8 @@ sub version_check {
}
$self->end_transaction('imtb');
}
$self->{version_checked} = 1;
}
# immutable_variables() returns the list of variables that has to be
......
# -*- perl -*-
#
# Copyright (C) 2011-2017 Alexis Bienvenue <paamc@passoire.fr>
# Copyright (C) 2011-2019 Alexis Bienvenue <paamc@passoire.fr>
#
# This file is part of Auto-Multiple-Choice
#
......@@ -142,7 +142,6 @@ package AMC::DataModule::capture;
#
# * timestamp is the time when processing was done.
use Exporter qw(import);
use constant {
......@@ -605,7 +604,13 @@ sub define_statements {
." WHERE zoneid=? AND type=?"},
'zoneDist'=>{'sql'=>"SELECT AVG((x-?)*(x-?)+(y-?)*(y-?))"
." FROM $t_position"
." WHERE zoneid=? AND type=?"},
." WHERE zoneid=? AND type=?"},
'zoneImages'=>
{sql=>"SELECT zoneid, p.student as student, p.copy as copy, image, imagedata, timestamp_auto"
." FROM ".$self->table("zone")." as z,"
." ".$self->table("page")." as p"
." ON z.student=p.student AND z.page=p.page AND z.copy=p.copy"
." WHERE type=?"},
'getAnnotated'=>{'sql'=>"SELECT annotated,timestamp_annotate,student,page,copy"
." FROM $t_page"
." WHERE timestamp_annotate>0"
......@@ -689,7 +694,7 @@ sub define_statements {
." ON z.zoneid=p.zoneid"
." WHERE z.student=? AND z.page=? AND z.copy=?"
." AND z.type=? AND p.type=?"
." ORDER BY z.zoneid,p.corner"},
." ORDER BY z.zoneid,p.corner"},
};
$self->{'statements'}->{'pageSummary'}=
{'sql'=>$self->{'statements'}->{'pagesSummary'}->{'sql'}
......@@ -1579,4 +1584,14 @@ sub zooms_cleanup_transaction {
return($n);
}
# Get zone images from a particular type, with timestamp of creation
sub zone_images_available {
my ($self, $type) = @_;
$type = ZONE_NAME if(!$type);
return($self->dbh->selectall_arrayref($self->statement('zoneImages'),
{ Slice=>{} },
$type));
}
1;
......@@ -153,6 +153,9 @@ package AMC::DataModule::scoring;
# * code is the code name.
#
# * value is the code value.
#
# * direct is 1 if the score comes directly from a decoded zone image,
# and 0 if it is computed while scoring.