Discussion:
public domain - automatic multiple choice test generator and scanner sources
(too old to reply)
wzab
2010-02-15 09:32:40 UTC
Permalink
Archive-name: automatic_test_scanner
Submited-by: ***@ise.pw.edu.pl

Checking of multiple choice test is always boring and (therefore?) error prone.
I always wanted to make it automatically. There are different commercial
programs able to make it automatically, but I wanted to have something
open and free.
The presented system uses the DataMatrix 2D bar codes to allow automatic
scanning of the test answers.
The idea is very simple.
The test sheet contains the DMTX codes corresponding to each answer.
The student checks (blurs) the code corresponding to the selected answer.
The scanned test sheet is then analyzed and all remaining codes are detected.
The lacking codes are those which have been selected.

This archive contains two Python scripts:
1) test_gen.py - this script generates in the "patterns" subdirectory
the PNG files which should be included in your test.
The LaTeX demo_test.tex file is an example how to do it.
2) test_scan.py - this script analyses the filled and scanned test
The values " min_edge=80, max_edge=150 " are good for the
scan with 600 DPI. You should adjust them for 300DPI scan.
The archive contains also the sample test "demo_test.tex"
To check how does it all work, you should:
a) Generate the PNG files with encoded answers:
$python test_gen.py
b) Generate the test PDF: $pdflatex demo_test
Print it, fill it, scan it with 600DPI to "scanned_test.png" file
c) Run the analyzer:
$python test_scan.py

For correct operation you need the PIL library for Python
(http://www.pythonware.com/products/pil/ )
and the libdmtx library with Python wrapper
(http://libdmtx.sf.net)

Please note, that this software is provided as PUBLIC DOMAIN,
without any warranty. You can use it only on your own risk.
I don't warrant you that it won't destroy something in your system.
I also don't know whether its use may infringe any patents
in any country in the world.

Wojciech M. Zabolotny
wzab<at>ise.pw.edu.pl

#!/bin/sh
# This is a shell archive (produced by GNU sharutils 4.6.3).
# To extract the files from this archive, save it to some FILE, remove
# everything before the `#!/bin/sh' line above, then type `sh FILE'.
#
lock_dir=_sh01044
# Made on 2010-02-15 10:31 CET by <***@wzlaphp>.
# Source directory was `/tmp/ffff'.
#
# Existing files will *not* be overwritten, unless `-c' is specified.
#
# This shar contains:
# length mode name
# ------ ---------- ------------------------------------------
# 3644 -rw-r--r-- demo_test.tex
# 64 -rw-r--r-- patterns/DELETE.ME
# 3100 -rw-r--r-- test_gen.py
# 2917 -rw-r--r-- test_scan.py
#
MD5SUM=${MD5SUM-md5sum}
f=`${MD5SUM} --version | egrep '^md5sum .*(core|text)utils'`
test -n "${f}" && md5check=true || md5check=false
${md5check} || \
echo 'Note: not verifying md5sums. Consider installing GNU coreutils.'
save_IFS="${IFS}"
IFS="${IFS}:"
gettext_dir=FAILED
locale_dir=FAILED
first_param="$1"
for dir in $PATH
do
if test "$gettext_dir" = FAILED && test -f $dir/gettext \
&& ($dir/gettext --version >/dev/null 2>&1)
then
case `$dir/gettext --version 2>&1 | sed 1q` in
*GNU*) gettext_dir=$dir ;;
esac
fi
if test "$locale_dir" = FAILED && test -f $dir/shar \
&& ($dir/shar --print-text-domain-dir >/dev/null 2>&1)
then
locale_dir=`$dir/shar --print-text-domain-dir`
fi
done
IFS="$save_IFS"
if test "$locale_dir" = FAILED || test "$gettext_dir" = FAILED
then
echo=echo
else
TEXTDOMAINDIR=$locale_dir
export TEXTDOMAINDIR
TEXTDOMAIN=sharutils
export TEXTDOMAIN
echo="$gettext_dir/gettext -s"
fi
if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null
then if (echo -n test; echo 1,2,3) | grep n >/dev/null
then shar_n= shar_c='
'
else shar_n=-n shar_c= ; fi
else shar_n= shar_c='\c' ; fi
f=shar-touch.$$
st1=200112312359.59
st2=123123592001.59
st2tr=123123592001.5 # old SysV 14-char limit
st3=1231235901

if touch -am -t ${st1} ${f} >/dev/null 2>&1 && \
test ! -f ${st1} && test -f ${f}; then
shar_touch='touch -am -t $1$2$3$4$5$6.$7 "$8"'

elif touch -am ${st2} ${f} >/dev/null 2>&1 && \
test ! -f ${st2} && test ! -f ${st2tr} && test -f ${f}; then
shar_touch='touch -am $3$4$5$6$1$2.$7 "$8"'

elif touch -am ${st3} ${f} >/dev/null 2>&1 && \
test ! -f ${st3} && test -f ${f}; then
shar_touch='touch -am $3$4$5$6$2 "$8"'

else
shar_touch=:
echo
${echo} 'WARNING: not restoring timestamps. Consider getting and'
${echo} 'installing GNU `touch'\'', distributed in GNU coreutils...'
echo
fi
rm -f ${st1} ${st2} ${st2tr} ${st3} ${f}
#
if test ! -d ${lock_dir}
then : ; else ${echo} 'lock directory '${lock_dir}' exists'
exit 1
fi
if mkdir ${lock_dir}
then ${echo} 'x - created lock directory `'${lock_dir}\''.'
else ${echo} 'x - failed to create lock directory `'${lock_dir}\''.'
exit 1
fi
# ============= demo_test.tex ==============
if test -f 'demo_test.tex' && test "$first_param" != -c; then
${echo} 'x -SKIPPING demo_test.tex (file already exists)'
else
${echo} 'x - extracting demo_test.tex (text)'
sed 's/^X//' << 'SHAR_EOF' > 'demo_test.tex' &&
\documentclass[11pt]{article}
\usepackage[pdftex]{graphicx}
\usepackage[utf8]{inputenc}
\usepackage[a4paper,left=1cm, right=1cm, top=1cm, bottom=1cm]{geometry}
\setlength{\parindent}{0pt}
\begin{document}
\noindent
Please mark the correct answer for the questions below.
The correct answer should be marked
by blurring the pattern on the right side of the corresponding letter.
If you want to skip the question, blur the pattern on the right side
of the question mark.\\
X~\\
\includegraphics[height=0.7cm]{patterns/01.png}
The superposition theorem may be applied only: a)~To the linear circuits,
b)~To the circuits containing only passive elements,
c)~To the DC circuits,
d)~To the AC circuits
X
X~\\
X
\includegraphics[height=0.7cm]{patterns/02.png}
The impedance of the capacitor: a)~does not depend on the frequency,
b)~is proportional to the frequency, c)~is inversely proportional to the
frequency, d) is always infinite
X
X~\\
X
\includegraphics[height=0.7cm]{patterns/03.png}
The impedance of the inductance: a)~does not depend on the frequency,
b)~is proportional to the frequency, c)~is inversely proportional to the
frequency, d) is always infinite
X
X~\\
X
\includegraphics[height=0.7cm]{patterns/04.png}
The impedance of the resistance: a)~does not depend on the frequency,
b)~is proportional to the frequency, c)~is inversely proportional to the
frequency, d) is always infinite
X
X~\\
X
\includegraphics[height=0.7cm]{patterns/05.png}
Which of the below connections is not acceptable in the model of the circut:
X a)~parallel connection of two ideal current sources
X with different output currents,
X b)~series connection of two ideal voltage sources with
X different output voltages,
X c)~parallel connection of an ideal current source and an ideal voltage source,
X d)~series connection of two ideal current sources with different
X output currents,
X
X
X~\\
X
\includegraphics[height=0.7cm]{patterns/06.png}
X Which from the listed amplifiers is {\bf not} suitable to amplify the
X DC component of the input signal:
X a)~Differential amplifier,
X b)~Common emitter or common source amplifier,
X c)~Noniverting amplifier with op-amp,
X d)~Inverting amplifier with op-amp
X
X~\\
X
\includegraphics[height=0.7cm]{patterns/07.png}
X The load current is meassured as the voltage drop across the small resistor
X connected in series with the load.
X The accuracy of the metter is equal to 2\%, and
X the tolerance of the resistor is equal to 1.5\%.
X The accuracy of the measurement is
X equal to:
X a)~3\%, b)~2\%, c)~3.5\%, d)~1.5\%
X
X~\\
X
\includegraphics[height=0.7cm]{patterns/08.png}
In order to measure the current in a circuit, an ammeter must:
X a)~be placed across the source;
X b)~be placed across the load;
X c)~be inserted into the circuit so the current flows through it;
X d)~all of these
X
X~\\
X
\includegraphics[height=0.7cm]{patterns/09.png}
X In the bipolar NPN transistor, $\beta$=200A/A, $I_C$=1mA, $I_B$=10$\mu$A:
X a) transistor works in saturation state,
X b) transistor works in active state,
X c) this is impossible,
X d) transistor works in cutoff state,
X
X~\\
X
\includegraphics[height=0.7cm]{patterns/10.png}
The real current source has internal resistance of 50$\Omega$. When shorted, it
X provides the current of 100mA. This source may be replaced with:
X a)~an ideal voltage source E=5V connected
X in parallel with resistance R=50$\Omega$,
X b)~an ideal voltage source E=5V connected
X in series with resistance R=50$\Omega$,
X c)~an ideal voltage source E=0.5V connected
X in parallel with resistance R=50$\Omega$,
X d)~an ideal voltage source E=0.5V connected
X in series with resistance R=50$\Omega$
X
\end{document}
SHAR_EOF
(set 20 10 02 15 10 30 17 'demo_test.tex'; eval "$shar_touch") &&
chmod 0644 'demo_test.tex'
if test $? -ne 0
then ${echo} 'restore of demo_test.tex failed'
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'demo_test.tex: MD5 check failed'
) << SHAR_EOF
1957a6a77e1985e38e5c3f417131ce2d demo_test.tex
SHAR_EOF
else
test `LC_ALL=C wc -c < 'demo_test.tex'` -ne 3644 && \
${echo} 'restoration warning: size of demo_test.tex is not 3644'
fi
fi
# ============= patterns/DELETE.ME ==============
if test ! -d 'patterns'; then
mkdir 'patterns'
if test $? -eq 0
then ${echo} 'x - created directory `patterns'\''.'
else ${echo} 'x - failed to create directory `patterns'\''.'
exit 1
fi
fi
if test -f 'patterns/DELETE.ME' && test "$first_param" != -c; then
${echo} 'x -SKIPPING patterns/DELETE.ME (file already exists)'
else
${echo} 'x - extracting patterns/DELETE.ME (text)'
sed 's/^X//' << 'SHAR_EOF' > 'patterns/DELETE.ME' &&
This file is only to ensure creation of the "patterns" directory
SHAR_EOF
(set 20 10 02 15 10 18 46 'patterns/DELETE.ME'; eval "$shar_touch") &&
chmod 0644 'patterns/DELETE.ME'
if test $? -ne 0
then ${echo} 'restore of patterns/DELETE.ME failed'
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'patterns/DELETE.ME: MD5 check failed'
) << SHAR_EOF
1890892e32f7cd217b7108a88729aa3c patterns/DELETE.ME
SHAR_EOF
else
test `LC_ALL=C wc -c < 'patterns/DELETE.ME'` -ne 64 && \
${echo} 'restoration warning: size of patterns/DELETE.ME is not 64'
fi
fi
# ============= test_gen.py ==============
if test -f 'test_gen.py' && test "$first_param" != -c; then
${echo} 'x -SKIPPING test_gen.py (file already exists)'
else
${echo} 'x - extracting test_gen.py (text)'
sed 's/^X//' << 'SHAR_EOF' > 'test_gen.py' &&
# This is the PUBLIC DOMAIN software, written
# by Wojciech M. Zabolotny
# wzab<at>ise.pw.edu.pl 14.02.2010
# This software may be used to generate the PNG files
# with patterns useful for generation of multiple
# choice tests well suited for automatic analysis
#
# The student should blur with black pen the DataMatrix pattern
# after the chosen answer.
# The filled and scanned test may then be analysed with the
# test_scan.py script.
#
# This software is provided without any warranty
# You can use it only on your own risk.
# I don't know whether its use may infringe any patents
# in any country in the world...
#
# This software uses the libdmtx library with Python wrapper
# You can find it at http://libdmtx.sourceforge.net/
# Options (in fact they should be read from command line)
num_answers=4
num_questions=30
num_digits=len(str(num_questions))
# Code below should not be changed unless you know what
# are you doing
from pydmtx import DataMatrix
from PIL import Image, ImageFont, ImageDraw
num_format="%"+str(num_digits)+"."+str(num_digits)+"d"
for i in range(1,num_questions+1):
X #Generate the test answer pattern for the question #i
X # Write a Data Matrix barcode
X ans_codes=[]
X max_x=0;
X max_y=0;
X for j in range(0,num_answers+1):
X #Generate the DataMatrix code for each answer
X if j<num_answers:
X answer=chr(ord("A")+j)
X else:
X answer="?"
X dm_write = DataMatrix()
X dm_write.encode((num_format % i)+answer)
X ans_codes.append(dm_write.image)
X imsize=dm_write.image.size
X if imsize[0]>max_x:
X max_x = imsize[0]
X if imsize[1]>max_y:
X max_y = imsize[1]
X # Now we add the text descriptions of the question and answers
X # You may need to adjust the font location below
X font=ImageFont.truetype("/usr/share/fonts/truetype/msttcorefonts/arial.ttf",int(0.7*max_y))
X # Here we should generate the bitmap containing first the question number
X # then the letters and encoded answers
X # e.g.: "001 A:XX,B:XX,C:XX,D:XX,?:XX
X #Now we calculate the sizes of descriptions
X desc_sizes=[]
X desc_text=[]
X for j in range(0,num_answers+1):
X if j==0:
X txt=(num_format % i)+" A:"
X elif j<num_answers:
X txt=","+chr(ord("A")+j)+":"
X else:
X txt=","+"?:"
X desc_sizes.append(font.getsize(txt))
X desc_text.append(txt)
X #Calculate the size of the output image
X img_x=0
X img_y=0
X for j in range(0,num_answers+1):
X img_x+= desc_sizes[j][0]+ans_codes[j].size[0]
X img_y = max(img_y, desc_sizes[j][1],ans_codes[j].size[1])
X #Now create the desired output image
X out_img=Image.new("RGB",(img_x, img_y),(255,255,255))
X drw=ImageDraw.Draw(out_img)
X img_x=0
X img_y=0
X for j in range(0,num_answers+1):
X drw.text((img_x, img_y),desc_text[j],font=font, fill=(0,0,0))
X img_x += desc_sizes[j][0]
X out_img.paste(ans_codes[j],(img_x,img_y))
X img_x += ans_codes[j].size[0]
X fname="patterns/"+(num_format % i)+".png"
X out_img.save(fname)
X
SHAR_EOF
(set 20 10 02 15 09 33 14 'test_gen.py'; eval "$shar_touch") &&
chmod 0644 'test_gen.py'
if test $? -ne 0
then ${echo} 'restore of test_gen.py failed'
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'test_gen.py: MD5 check failed'
) << SHAR_EOF
74ad7fda84dfcb519500918f2cb08306 test_gen.py
SHAR_EOF
else
test `LC_ALL=C wc -c < 'test_gen.py'` -ne 3100 && \
${echo} 'restoration warning: size of test_gen.py is not 3100'
fi
fi
# ============= test_scan.py ==============
if test -f 'test_scan.py' && test "$first_param" != -c; then
${echo} 'x -SKIPPING test_scan.py (file already exists)'
else
${echo} 'x - extracting test_scan.py (text)'
sed 's/^X//' << 'SHAR_EOF' > 'test_scan.py' &&
#!/usr/bin/python
# This is PUBLIC DOMAIN software written by
# Wojciech M. Zabolotny wzab<at>ise.pw.edu.pl 14.02.2010
# This script analyzes the scanned tests and finds the checked
# answers. It also detects if the tests has been filled
# correctly.
# This software is provided without any warranty
# You can use it only on your own risk.
# I don't know whether its use may infringe any patents
# in any country in the world...
#
# This software uses the libdmtx library with Python wrapper
# You can find it at http://libdmtx.sourceforge.net/
#
# Options (in fact they should be read from command line)
num_answers=4
num_questions=10
file_name="scanned_test.png"
# Code below should not be changed unless you know what
# are you doing
from pydmtx import DataMatrix
from PIL import Image
num_digits=len(str(num_questions))
num_format="%"+str(num_digits)+"."+str(num_digits)+"d"
# Read the scanned test
dm_read = DataMatrix()
img = Image.open(file_name)
if img.mode != 'RGB':
X img = img.convert('RGB')
# The timeout value (and other options) below may need adjustment
# If you know any better way how to reasonable control
# precision of the dmtx decoding, please let me know
dm_read.decode(img.size[0], img.size[1], \
X buffer(img.tostring()),\
X min_edge=80, max_edge=150, deviation=10,\
X timeout=10000)
#print dm_read.count()
# Now we build the dictionary with all possible answers in analyzed test
answers={}
for i in range(1,num_questions+1):
X for j in range(0,num_answers+1):
X if j< num_answers:
X pat=chr(ord('A')+j)
X else:
X pat="?"
X ans=(num_format % i)+pat
X #print ans
X answers[ans]=(i,pat)
# Now we iterate through the detected codes dictionary and delete them
# from the dictionary. The codes left should be those checked (blurred)
# by the student
for i in range(1,dm_read.count()+1):
X code=dm_read.message(i)
X if answers.has_key(code):
X answers.pop(code)
X else:
X print "The scanned test contains the unknown code: "+code
X exit(1)
# Now we check if the test has been filled correctly (in each questions one
# option should be checked)
#print answers
answers2={}
for j in answers:
X key=answers[j][0]
X if answers2.has_key(key):
X print "The question "+str(key)+" has more then 1 answers checked"
X print "The test has been filled incorrectly"
X exit(2)
X else:
X answers2[key]=answers[j][1]
# Now we have all answers in a dictionary indexed with question numbers
# Check if all questions are answered
#print answers2
for i in range(1,num_questions+1):
X if not answers2.has_key(i):
X print "In the question "+str(i)+" no answer has been selected"
X print "The test has been filled incorrectly"
X exit(2)
# Now we know, that the test has been filled correctly, and we can
# Evaluate the answers
# In this demo version we just print them out
for i in range(1,num_questions+1):
X print str(i)+":"+answers2[i]
X
X
SHAR_EOF
(set 20 10 02 15 10 16 40 'test_scan.py'; eval "$shar_touch") &&
chmod 0644 'test_scan.py'
if test $? -ne 0
then ${echo} 'restore of test_scan.py failed'
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'test_scan.py: MD5 check failed'
) << SHAR_EOF
6d85c9a09550513eeb92636dfe186eb7 test_scan.py
SHAR_EOF
else
test `LC_ALL=C wc -c < 'test_scan.py'` -ne 2917 && \
${echo} 'restoration warning: size of test_scan.py is not 2917'
fi
fi
if rm -fr ${lock_dir}
then ${echo} 'x - removed lock directory `'${lock_dir}\''.'
else ${echo} 'x - failed to remove lock directory `'${lock_dir}\''.'
exit 1
fi
exit 0
Wojciech M. Zabołotny
2019-06-15 17:08:09 UTC
Permalink
Archive-name: automatic_test_scanner
Submited-by: ***@gmail.com
Version: 1.1

This is the updated version of my automated test generator and scanner.
I have updated it to:
1) Work with Python 3
2) Use Python virtaul environment and automatically install necessary
extensions
3) Use pilibdmtx instead of old pydmtx

The old (slightly updated) desctription follows:

Checking of multiple choice test is always boring and (therefore?) error prone.
I always wanted to make it automatically. There are different commercial
programs able to make it automatically, but I wanted to have something
open and free.
The presented system uses the DataMatrix 2D bar codes to allow automatic
scanning of the test answers.
The idea is very simple.
The test sheet contains the DMTX codes corresponding to each answer.
The student checks (blurs) the code corresponding to the selected answer.
The scanned test sheet is then analyzed and all remaining codes are detected.
The lacking codes are those which have been selected.

The archive contains the "build.sh" script, that creates the Python
virtual environment, downloads and installs necessary extensions
and then runs the test_gen.py that generates in the "patterns" subdirectory
the PNG files which should be included in your test.

The LaTeX demo_test.tex file is an example of test that uses the generated
patterns. You may generate it with "pdflatex demo_test.tex", and then print
the created demo_test.pdf file.
You should solve the test by blurring the codes of right answers.
After that scan your file with 600 DPI resolution to the scanned_test.png file
It may be good to convert it to black and white image.

Then you should run "test_scan.py" - that analyses the filled
and scanned test.
The values " min_edge=70, max_edge=120 " are good for the
scan with 600 DPI. You should adjust them for 300DPI scan.

Please note, that this software is provided as PUBLIC DOMAIN,
without any warranty. You can use it only on your own risk.
I don't warrant you that it won't destroy something in your system.
I also don't know whether its use may infringe any patents
in any country in the world.

Wojciech M. Zabolotny
wzab<at>ise.pw.edu.pl
wzab01<at>gmail.com

#!/bin/sh
# This is a shell archive (produced by GNU sharutils 4.15.2).
# To extract the files from this archive, save it to some FILE, remove
# everything before the '#!/bin/sh' line above, then type 'sh FILE'.
#
lock_dir=_sh07714
# Made on 2019-06-15 18:51 CEST by <***@wzab>.
# Source directory was '/tmp/tgen'.
#
# Existing files will *not* be overwritten, unless '-c' is specified.
#
# This shar contains:
# length mode name
# ------ ---------- ------------------------------------------
# 65 -rw-r--r-- patterns/DELETE.ME
# 3381 -rw-r--r-- test_gen.py
# 3644 -rw-r--r-- demo_test.tex
# 3014 -rw-r--r-- test_scan.py
# 115 -rw-r--r-- build.sh
#
MD5SUM=${MD5SUM-md5sum}
f=`${MD5SUM} --version | egrep '^md5sum .*(core|text)utils'`
test -n "${f}" && md5check=true || md5check=false
${md5check} || \
echo 'Note: not verifying md5sums. Consider installing GNU coreutils.'
if test "X$1" = "X-c"
then keep_file=''
else keep_file=true
fi
echo=echo
save_IFS="${IFS}"
IFS="${IFS}:"
gettext_dir=
locale_dir=
set_echo=false

for dir in $PATH
do
if test -f $dir/gettext \
&& ($dir/gettext --version >/dev/null 2>&1)
then
case `$dir/gettext --version 2>&1 | sed 1q` in
*GNU*) gettext_dir=$dir
set_echo=true
break ;;
esac
fi
done

if ${set_echo}
then
set_echo=false
for dir in $PATH
do
if test -f $dir/shar \
&& ($dir/shar --print-text-domain-dir >/dev/null 2>&1)
then
locale_dir=`$dir/shar --print-text-domain-dir`
set_echo=true
break
fi
done

if ${set_echo}
then
TEXTDOMAINDIR=$locale_dir
export TEXTDOMAINDIR
TEXTDOMAIN=sharutils
export TEXTDOMAIN
echo="$gettext_dir/gettext -s"
fi
fi
IFS="$save_IFS"
if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null
then if (echo -n test; echo 1,2,3) | grep n >/dev/null
then shar_n= shar_c='
'
else shar_n=-n shar_c= ; fi
else shar_n= shar_c='\c' ; fi
f=shar-touch.$$
st1=200112312359.59
st2=123123592001.59
st2tr=123123592001.5 # old SysV 14-char limit
st3=1231235901

if touch -am -t ${st1} ${f} >/dev/null 2>&1 && \
test ! -f ${st1} && test -f ${f}; then
shar_touch='touch -am -t $1$2$3$4$5$6.$7 "$8"'

elif touch -am ${st2} ${f} >/dev/null 2>&1 && \
test ! -f ${st2} && test ! -f ${st2tr} && test -f ${f}; then
shar_touch='touch -am $3$4$5$6$1$2.$7 "$8"'

elif touch -am ${st3} ${f} >/dev/null 2>&1 && \
test ! -f ${st3} && test -f ${f}; then
shar_touch='touch -am $3$4$5$6$2 "$8"'

else
shar_touch=:
echo
${echo} 'WARNING: not restoring timestamps. Consider getting and
installing GNU '\''touch'\'', distributed in GNU coreutils...'
echo
fi
rm -f ${st1} ${st2} ${st2tr} ${st3} ${f}
#
if test ! -d ${lock_dir} ; then :
else ${echo} "lock directory ${lock_dir} exists"
exit 1
fi
if mkdir ${lock_dir}
then ${echo} "x - created lock directory ${lock_dir}."
else ${echo} "x - failed to create lock directory ${lock_dir}."
exit 1
fi
# ============= patterns/DELETE.ME ==============
if test ! -d 'patterns'; then
mkdir 'patterns'
if test $? -eq 0
then ${echo} "x - created directory patterns."
else ${echo} "x - failed to create directory patterns."
exit 1
fi
fi
if test -n "${keep_file}" && test -f 'patterns/DELETE.ME'
then
${echo} "x - SKIPPING patterns/DELETE.ME (file already exists)"

else
${echo} "x - extracting patterns/DELETE.ME (text)"
sed 's/^X//' << 'SHAR_EOF' > 'patterns/DELETE.ME' &&
This file is only to ensure creation of the "patterns" directory
SHAR_EOF
(set 20 10 02 15 10 18 46 'patterns/DELETE.ME'
eval "${shar_touch}") && \
chmod 0644 'patterns/DELETE.ME'
if test $? -ne 0
then ${echo} "restore of patterns/DELETE.ME failed"
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'patterns/DELETE.ME': 'MD5 check failed'
) << \SHAR_EOF
12face5765f37ef804f45913e95832c3 patterns/DELETE.ME
SHAR_EOF

else
test `LC_ALL=C wc -c < 'patterns/DELETE.ME'` -ne 65 && \
${echo} "restoration warning: size of 'patterns/DELETE.ME' is not 65"
fi
fi
# ============= test_gen.py ==============
if test -n "${keep_file}" && test -f 'test_gen.py'
then
${echo} "x - SKIPPING test_gen.py (file already exists)"

else
${echo} "x - extracting test_gen.py (text)"
sed 's/^X//' << 'SHAR_EOF' > 'test_gen.py' &&
# This is the PUBLIC DOMAIN software, written
# by Wojciech M. Zabolotny
# wzab<at>ise.pw.edu.pl 14.02.2010
# updated by Wojciech M. Zabolotny
# wzab<at>ise.pw.edu.pl or wzab01<at>gmail.com
# at 15.06.2019 for:
# - Python3
# - Python virtual environments
# - pylibdtmx
# This software may be used to generate the PNG files
# with patterns useful for generation of multiple
# choice tests well suited for automatic analysis
#
# The student should blur with black pen the DataMatrix pattern
# after the chosen answer.
# The filled and scanned test may then be analysed with the
# test_scan.py script.
#
# This software is provided without any warranty
# You can use it only on your own risk.
# I don't know whether its use may infringe any patents
# in any country in the world...
#
# This software uses the libdmtx library with Python wrapper
# You can find it at http://libdmtx.sourceforge.net/
# Options (in fact they should be read from command line)
num_answers=4
num_questions=30
num_digits=len(str(num_questions))
# Code below should not be changed unless you know what
# are you doing
from pylibdmtx.pylibdmtx import encode as dmtx_encode
from PIL import Image, ImageFont, ImageDraw
num_format="%"+str(num_digits)+"."+str(num_digits)+"d"
for i in range(1,num_questions+1):
X #Generate the test answer pattern for the question #i
X # Write a Data Matrix barcode
X ans_codes=[]
X max_x=0;
X max_y=0;
X for j in range(0,num_answers+1):
X #Generate the DataMatrix code for each answer
X if j<num_answers:
X answer=chr(ord("A")+j)
X else:
X answer="?"
X ant = (num_format % i)+answer
X #print(ant)
X encoded=dmtx_encode(ant.encode('Ascii'),'Ascii')
X img = Image.frombytes('RGB', (encoded.width, encoded.height), encoded.pixels)
X ans_codes.append(img)
X if encoded.width>max_x:
X max_x = encoded.width
X if encoded.height>max_y:
X max_y = encoded.height
X # Now we add the text descriptions of the question and answers
X # You may need to adjust the font location below
X font=ImageFont.truetype("/usr/share/fonts/truetype/msttcorefonts/arial.ttf",int(0.7*max_y))
X # Here we should generate the bitmap containing first the question number
X # then the letters and encoded answers
X # e.g.: "001 A:XX,B:XX,C:XX,D:XX,?:XX
X #Now we calculate the sizes of descriptions
X desc_sizes=[]
X desc_text=[]
X for j in range(0,num_answers+1):
X if j==0:
X txt=(num_format % i)+" A:"
X elif j<num_answers:
X txt=","+chr(ord("A")+j)+":"
X else:
X txt=","+"?:"
X desc_sizes.append(font.getsize(txt))
X desc_text.append(txt)
X #Calculate the size of the output image
X img_x=0
X img_y=0
X for j in range(0,num_answers+1):
X img_x+= desc_sizes[j][0]+ans_codes[j].size[0]
X img_y = max(img_y, desc_sizes[j][1],ans_codes[j].size[1])
X #Now create the desired output image
X out_img=Image.new("RGB",(img_x, img_y),(255,255,255))
X drw=ImageDraw.Draw(out_img)
X img_x=0
X img_y=0
X for j in range(0,num_answers+1):
X drw.text((img_x, img_y),desc_text[j],font=font, fill=(0,0,0))
X img_x += desc_sizes[j][0]
X out_img.paste(ans_codes[j],(img_x,img_y))
X img_x += ans_codes[j].size[0]
X fname="patterns/"+(num_format % i)+".png"
X out_img.save(fname)
X
SHAR_EOF
(set 20 19 06 15 18 22 18 'test_gen.py'
eval "${shar_touch}") && \
chmod 0644 'test_gen.py'
if test $? -ne 0
then ${echo} "restore of test_gen.py failed"
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'test_gen.py': 'MD5 check failed'
) << \SHAR_EOF
fcfac66564df1bf1e1aaca7219c46129 test_gen.py
SHAR_EOF

else
test `LC_ALL=C wc -c < 'test_gen.py'` -ne 3381 && \
${echo} "restoration warning: size of 'test_gen.py' is not 3381"
fi
fi
# ============= demo_test.tex ==============
if test -n "${keep_file}" && test -f 'demo_test.tex'
then
${echo} "x - SKIPPING demo_test.tex (file already exists)"

else
${echo} "x - extracting demo_test.tex (text)"
sed 's/^X//' << 'SHAR_EOF' > 'demo_test.tex' &&
\documentclass[11pt]{article}
\usepackage[pdftex]{graphicx}
\usepackage[utf8]{inputenc}
\usepackage[a4paper,left=1cm, right=1cm, top=1cm, bottom=1cm]{geometry}
\setlength{\parindent}{0pt}
\begin{document}
\noindent
Please mark the correct answer for the questions below.
The correct answer should be marked
by blurring the pattern on the right side of the corresponding letter.
If you want to skip the question, blur the pattern on the right side
of the question mark.\\
X~\\
\includegraphics[height=0.7cm]{patterns/01.png}
The superposition theorem may be applied only: a)~To the linear circuits,
b)~To the circuits containing only passive elements,
c)~To the DC circuits,
d)~To the AC circuits
X
X~\\
X
\includegraphics[height=0.7cm]{patterns/02.png}
The impedance of the capacitor: a)~does not depend on the frequency,
b)~is proportional to the frequency, c)~is inversely proportional to the
frequency, d) is always infinite
X
X~\\
X
\includegraphics[height=0.7cm]{patterns/03.png}
The impedance of the inductance: a)~does not depend on the frequency,
b)~is proportional to the frequency, c)~is inversely proportional to the
frequency, d) is always infinite
X
X~\\
X
\includegraphics[height=0.7cm]{patterns/04.png}
The impedance of the resistance: a)~does not depend on the frequency,
b)~is proportional to the frequency, c)~is inversely proportional to the
frequency, d) is always infinite
X
X~\\
X
\includegraphics[height=0.7cm]{patterns/05.png}
Which of the below connections is not acceptable in the model of the circut:
X a)~parallel connection of two ideal current sources
X with different output currents,
X b)~series connection of two ideal voltage sources with
X different output voltages,
X c)~parallel connection of an ideal current source and an ideal voltage source,
X d)~series connection of two ideal current sources with different
X output currents,
X
X
X~\\
X
\includegraphics[height=0.7cm]{patterns/06.png}
X Which from the listed amplifiers is {\bf not} suitable to amplify the
X DC component of the input signal:
X a)~Differential amplifier,
X b)~Common emitter or common source amplifier,
X c)~Noniverting amplifier with op-amp,
X d)~Inverting amplifier with op-amp
X
X~\\
X
\includegraphics[height=0.7cm]{patterns/07.png}
X The load current is meassured as the voltage drop across the small resistor
X connected in series with the load.
X The accuracy of the metter is equal to 2\%, and
X the tolerance of the resistor is equal to 1.5\%.
X The accuracy of the measurement is
X equal to:
X a)~3\%, b)~2\%, c)~3.5\%, d)~1.5\%
X
X~\\
X
\includegraphics[height=0.7cm]{patterns/08.png}
In order to measure the current in a circuit, an ammeter must:
X a)~be placed across the source;
X b)~be placed across the load;
X c)~be inserted into the circuit so the current flows through it;
X d)~all of these
X
X~\\
X
\includegraphics[height=0.7cm]{patterns/09.png}
X In the bipolar NPN transistor, $\beta$=200A/A, $I_C$=1mA, $I_B$=10$\mu$A:
X a) transistor works in saturation state,
X b) transistor works in active state,
X c) this is impossible,
X d) transistor works in cutoff state,
X
X~\\
X
\includegraphics[height=0.7cm]{patterns/10.png}
The real current source has internal resistance of 50$\Omega$. When shorted, it
X provides the current of 100mA. This source may be replaced with:
X a)~an ideal voltage source E=5V connected
X in parallel with resistance R=50$\Omega$,
X b)~an ideal voltage source E=5V connected
X in series with resistance R=50$\Omega$,
X c)~an ideal voltage source E=0.5V connected
X in parallel with resistance R=50$\Omega$,
X d)~an ideal voltage source E=0.5V connected
X in series with resistance R=50$\Omega$
X
\end{document}
SHAR_EOF
(set 20 10 02 15 10 30 17 'demo_test.tex'
eval "${shar_touch}") && \
chmod 0644 'demo_test.tex'
if test $? -ne 0
then ${echo} "restore of demo_test.tex failed"
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'demo_test.tex': 'MD5 check failed'
) << \SHAR_EOF
1957a6a77e1985e38e5c3f417131ce2d demo_test.tex
SHAR_EOF

else
test `LC_ALL=C wc -c < 'demo_test.tex'` -ne 3644 && \
${echo} "restoration warning: size of 'demo_test.tex' is not 3644"
fi
fi
# ============= test_scan.py ==============
if test -n "${keep_file}" && test -f 'test_scan.py'
then
${echo} "x - SKIPPING test_scan.py (file already exists)"

else
${echo} "x - extracting test_scan.py (text)"
sed 's/^X//' << 'SHAR_EOF' > 'test_scan.py' &&
#!/usr/bin/python
# This is PUBLIC DOMAIN software written by
# Wojciech M. Zabolotny wzab<at>ise.pw.edu.pl 14.02.2010
# updated by Wojciech M. Zabolotny
# wzab<at>ise.pw.edu.pl or wzab01<at>gmail.com
# at 15.06.2019 for:
# - Python3
# - Python virtual environments
# - pylibdtmx
# This script analyzes the scanned tests and finds the checked
# answers. It also detects if the tests has been filled
# correctly.
# This software is provided without any warranty
# You can use it only on your own risk.
# I don't know whether its use may infringe any patents
# in any country in the world...
#
# This software uses the libdmtx library with Python wrapper
# You can find it at http://libdmtx.sourceforge.net/
#
# Options (in fact they should be read from command line)
num_answers=4
num_questions=10
file_name="scanned_test.png"
# Code below should not be changed unless you know what
# are you doing
from pylibdmtx.pylibdmtx import decode
from PIL import Image
num_digits=len(str(num_questions))
num_format="%"+str(num_digits)+"."+str(num_digits)+"d"
# Read the scanned test
img = Image.open(file_name)
if img.mode != 'RGB':
X img = img.convert('RGB')
# The timeout value (and other options) below may need adjustment
# If you know any better way how to reasonable control
# precision of the dmtx decoding, please let me know
dm_read=decode(img,\
X min_edge=70, max_edge=120, deviation=10,\
X timeout=10000)
#print(m_read.count())
# Now we build the dictionary with all possible answers in analyzed test
answers={}
for i in range(1,num_questions+1):
X for j in range(0,num_answers+1):
X if j< num_answers:
X pat=chr(ord('A')+j)
X else:
X pat="?"
X ans=(num_format % i)+pat
X #print(ans)
X answers[ans]=(i,pat)
# Now we iterate through the detected codes dictionary and delete them
# from the dictionary. The codes left should be those checked (blurred)
# by the student
for i in range(0,len(dm_read)):
X code=dm_read[i].data.decode('Ascii')
X if code in answers:
X answers.pop(code)
X else:
X print("The scanned test contains the unknown code: "+code)
X exit(1)
# Now we check if the test has been filled correctly (in each questions one
# option should be checked)
#print answers
answers2={}
for j in answers:
X key=answers[j][0]
X if key in answers2:
X print("The question "+str(key)+" has more then 1 answers checked")
X print("The test has been filled incorrectly")
X exit(2)
X else:
X answers2[key]=answers[j][1]
# Now we have all answers in a dictionary indexed with question numbers
# Check if all questions are answered
#print answers2
for i in range(1,num_questions+1):
X if not i in answers2:
X print("In the question "+str(i)+" no answer has been selected")
X print("The test has been filled incorrectly")
X exit(2)
# Now we know, that the test has been filled correctly, and we can
# Evaluate the answers
# In this demo version we just print them out
for i in range(1,num_questions+1):
X print(str(i)+":"+answers2[i])
X
X
SHAR_EOF
(set 20 19 06 15 18 40 24 'test_scan.py'
eval "${shar_touch}") && \
chmod 0644 'test_scan.py'
if test $? -ne 0
then ${echo} "restore of test_scan.py failed"
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'test_scan.py': 'MD5 check failed'
) << \SHAR_EOF
85152e673929d669ae57b79cb944ae0c test_scan.py
SHAR_EOF

else
test `LC_ALL=C wc -c < 'test_scan.py'` -ne 3014 && \
${echo} "restoration warning: size of 'test_scan.py' is not 3014"
fi
fi
# ============= build.sh ==============
if test -n "${keep_file}" && test -f 'build.sh'
then
${echo} "x - SKIPPING build.sh (file already exists)"

else
${echo} "x - extracting build.sh (text)"
sed 's/^X//' << 'SHAR_EOF' > 'build.sh' &&
python3 -m venv testgen
source testgen/bin/activate
pip3 install pylibdmtx
pip3 install Pillow
python3 test_gen.py
SHAR_EOF
(set 20 19 06 15 17 08 43 'build.sh'
eval "${shar_touch}") && \
chmod 0644 'build.sh'
if test $? -ne 0
then ${echo} "restore of build.sh failed"
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'build.sh': 'MD5 check failed'
) << \SHAR_EOF
5992f16e7847d5e56467c8df6e44f997 build.sh
SHAR_EOF

else
test `LC_ALL=C wc -c < 'build.sh'` -ne 115 && \
${echo} "restoration warning: size of 'build.sh' is not 115"
fi
fi
if rm -fr ${lock_dir}
then ${echo} "x - removed lock directory ${lock_dir}."
else ${echo} "x - failed to remove lock directory ${lock_dir}."
exit 1
fi
exit 0

Loading...