はじめに
この文書は技術的な点について説明しています。無料で利用できるマークシートツールを探している場合には、Dockerイメージを準備しているので、そちらをご利用ください。
簡易なアンケート集計用のOMRとしてSDAPS(公式サイト https://sdaps.org/)を利用しています。
SDAPSは日本国内で一般的なマークシートに期待される精度を実現することは困難ですが、印字品質や読み取り品質を工夫することで、アンケートなどの用途ではほぼ問題がないと判断し、利用を続けています。
昨年度の実施結果では、250枚以上を読み込ませた結果、入力枚数と認識された結果の枚数に差があったため調査したところ、蛍光ペンで記入されたものが認識されていない事が分かりましたが、これはしかたのない事です。
これまで数千枚のシートをスキャンしてきましたが、2018年以降は大きな問題もなく、読取精度に問題は感じていません。
昨年は、Ubuntu 16.04を使ってsdapsを使ってのように利用しましたが、今年はUbuntu 18.04で作者の公式PPAパッケージ(1.9.5-1)を利用してみることにしました。
試した範囲では、昨年度のような標準偏差のバグなど、あからさまな問題はなさそうです。
前提など
昨年同様にLaTeXを前提としていて、WebやLibreofficeを利用したインタフェースは利用・テストしていません。
利用しているのは次の機能のみです。
$ sdaps setup <project> <tex filepath>
$ sdaps add <project> <tiff filepath>
$ sdaps recognize <project>
$ sdaps report <project>
$ sdaps csv export <project>
この他の機能は、"reset"を含めてテストしていません。というか、1.9.8ではresetでエラーになるので、プロジェクトディレクトリ全体をgitコマンドで管理しています。
環境
Ubuntuのバージョンなどソフトウェア以外では、使用している機材に変更はありません。
- Ubuntu 18.04.2 LTS amd64版
- sdaps debパッケージ (PPA 1.9.5-1)
- レーザープリンター (OKI B841)
- 一般的なPPC用紙 (白色普通紙)
- 印刷品位 - 高精細 (1200x1200)
- ドキュメントスキャナ (Brother ADS-3600W)
- 設定 - TIFFマルチページ・300dpi・モノクロ・長辺とじ
周囲ではTIFFマルチページ出力ができる機材は割と貴重で、オフィス用大型複合機にもスキャン機能は付いていますが、TIFFマルチページ出力はできず、PDFは当然マルチページで保存はできますが、PDF→TIFF変換では精度が得られなかったので、TIFFマルチページをサポートするADS-3600Wを使って、20〜25枚を1セットとして処理しています。
カタログをみる限り主なメーカーのオフィス向け複合機・ドキュメントスキャナーには、だいたいJPEGとTIFFマルチページ出力はセットで付いているようですが、家庭・SMB向けの廉価版では売れ筋であってもTIFFマルチページ出力は省略されている事が多いようです。
コストや使い勝手の面で、Brotherのプリンター・スキャナーは使っていて気に入っています。
パッケージの導入
PPAに記載されている手順でのSDAPSの導入後に、日本語を含むPDFファイルを生成するため、次のパッケージを導入しています。
- texlive-lang-cjk
$ sudo apt install texlive-lang-cjk
変更箇所の洗い出し
まず昨年の変更を適応せずに何が発生するか確認します。
$ cd /tmp
$ gzip -cd /usr/share/doc/sdaps/examples/example.tex.gz > example.tex
$ sdaps setup 20190314_sdapstest example.tex
ここまでの操作で、20190314_sdapstest ディレクトリ内にquestionnaire.pdfが作成されています。
$ ls -l 20190314_sdapstest/*.pdf
-rw-rw-r-- 1 yasu yasu 45653 Mar 14 10:52 20190314_sdapstest/questionnaire.pdf
【確認】example.texに日本語を追記
タイトルや著者欄は英語でも構わないので、\singlemark{〜}や\begin{info}〜\end{info}の”〜”の部分に日本語を追記してから同様の操作をしてみると、PDFファイルは作成されません。
まずは昨年度と同様の変更を行なっていきます。
変更対象となったファイル
使用するインタプリタがpython2.7からpython3に変更になっているため、パッケージの配置場所は/usr/lib/python3に変更になっています。
なおexample.texの変更内容のdiffは次のセクションに掲載しています。
質問用紙の準備に必要な変更
- /usr/lib/python3/dist-packages/sdaps/defs.py
defs.pyでxelatexを使うよう変更し、example.texの冒頭で関連するパッケージ、フォント設定などを行なってあげると無事にPDFファイルに日本語が表示される状態になります。
レポート出力に必要な変更
レポートの出力ではLaTeXを使わずに、Pythonから直接出力しています。
ReportLabが使用しているsetFontなどの命令について、日本語フォントを使用するよう直接Times-Romanなどの指定を上書きしていきます。
- /usr/lib/python3/dist-packages/sdaps/template.py
- /usr/lib/python3/dist-packages/sdaps/report/answers.py
- /usr/lib/python3/dist-packages/sdaps/report/buddies.py
これらのファイルの変更点の内容を踏襲していますが、表示のタイトルが日本語にできないなどの問題があったので、今回はStyle全てにフォントを設定しました。
このためテストした範囲では全ての日本語が問題なく集計レポートに出力できています。
変更の内容
ファイル毎にdiffを掲載しておきます。
diff:example.tex
--- example.tex.20190314 2019-03-14 11:31:32.878527883 +0900
+++ example.tex 2019-03-14 11:30:40.315892706 +0900
@@ -4,7 +4,7 @@
% Use A4 paper size, you can change this to eg. letterpaper if you need
% the letter format. The normal methods to modify the paper size should
% be picked up by SDAPS automatically.
- % a4paper, % setting this might break the example scan unfortunately
+ a4paper, % setting this might break the example scan unfortunately
% letterpaper
%
% If you need it, you can add a custom barcode at the center
@@ -30,6 +30,12 @@
% For demonstration purposes
\usepackage{multicol}
+\usepackage{xltxtra}
+\setmainfont{IPAPMincho}
+\setsansfont{IPAPGothic}
+\setmonofont{IPAGothic}
+\XeTeXlinebreaklocale "ja"
+
\author{The Author}
\title{The Title}
@@ -41,14 +47,14 @@
\begin{questionnaire}
% There is a predefined "info" style to hilight some text.
\begin{info}
- You can create a customized information element similar to the standard
+ 日本語の文言を追記してみる。
one using the \texttt{info} environment. By adding \texttt{[noinfo]} to
the \texttt{questionaire} environment you can replace the predefined
information field with your own.
\end{info}
% Use \addinfo to add metadata (which is printed on the report later on)
- \addinfo{Date}{10.03.2013}
+ \addinfo{Date}{20.03.2019}
% You can structure the document using sections. You should not use
% subsections yourself, as these are used to typeset question text.
@@ -56,7 +62,7 @@
% Lets ask some questions.
% \singlemark creates a single range (1-5) question.
- \singlemark{How often do you use SDAPS?}{never}{daily}
+ \singlemark{SDAPSをどの程度の頻度で使用していますか?}{never}{daily}
% Now we would like to ask multiple range questions that are similar. We
% can use a markgroup environment to typeset many range questions under
diff:defs.py
--- /usr/lib/python3/dist-packages/sdaps/defs.py.20190314 2018-11-06 05:23:27.000000000 +0900
+++ /usr/lib/python3/dist-packages/sdaps/defs.py 2019-03-14 11:27:30.441894726 +0900
@@ -194,7 +194,7 @@
# External commands =======================================
#: The binary used to compile latex documents.
-latex_engine = "pdflatex"
+latex_engine = "xelatex"
#: A function that is called after fork and before exec of the latex engine.
#: This is useful when e.g. the LateX environment should be secured.
diff:sdaps/template.py
--- /usr/lib/python3/dist-packages/sdaps/template.py.20190314 2018-11-03 20:13:21.000000000 +0900
+++ /usr/lib/python3/dist-packages/sdaps/template.py 2019-03-14 15:13:05.698462545 +0900
@@ -25,6 +25,8 @@
"""
from reportlab import platypus
+from reportlab.pdfbase import pdfmetrics
+from reportlab.pdfbase.cidfonts import UnicodeCIDFont
from reportlab.lib import styles
from reportlab.lib import units
from reportlab.lib import pagesizes
@@ -32,6 +34,8 @@
mm = units.mm
PADDING = 15 * mm
+pdfmetrics.registerFont(UnicodeCIDFont("HeiseiKakuGo-W5"))
+
class DocTemplate(platypus.BaseDocTemplate):
def __init__(self, filename, title, metainfo={}, papersize=pagesizes.A4):
@@ -78,7 +82,7 @@
def beforeDrawPage(self, canvas, document):
canvas.saveState()
- canvas.setFont('Times-Bold', 24)
+ canvas.setFont('HeiseiKakuGo-W5', 24)
canvas.drawCentredString(
document.width / 2.0,
document.height - 50 * mm,
@@ -118,7 +122,7 @@
stylesheet['Normal'] = styles.ParagraphStyle(
'Normal',
- fontName='Times-Roman',
+ fontName='HeiseiKakuGo-W5',
fontSize=10,
leading=14,
)
diff:sdaps/report/answers.py
--- /usr/lib/python3/dist-packages/sdaps/report/answers.py.20190314 2018-11-03 20:13:21.000000000 +0900
+++ /usr/lib/python3/dist-packages/sdaps/report/answers.py 2019-03-14 15:09:34.528893169 +0900
@@ -21,6 +21,8 @@
from reportlab import pdfgen
from reportlab import platypus
+from reportlab.pdfbase import pdfmetrics
+from reportlab.pdfbase.cidfonts import UnicodeCIDFont
from reportlab.lib import styles
from reportlab.lib import units
from reportlab.lib import pagesizes
@@ -37,6 +39,7 @@
from . import flowables
+pdfmetrics.registerFont(UnicodeCIDFont("HeiseiKakuGo-W5"))
mm = units.mm
@@ -46,18 +49,21 @@
'Right',
parent=stylesheet['Normal'],
alignment=enums.TA_RIGHT,
+ fontName='HeiseiKakuGo-W5'
)
stylesheet['Right_Highlight'] = styles.ParagraphStyle(
'Right_Highlight',
parent=stylesheet['Right'],
- textColor=colors.Color(255, 0, 0)
+ textColor=colors.Color(255, 0, 0),
+ fontName='HeiseiKakuGo-W5'
)
stylesheet['Normal_Highlight'] = styles.ParagraphStyle(
'Normal_Highlight',
parent=stylesheet['Normal'],
- textColor=colors.Color(255, 0, 0)
+ textColor=colors.Color(255, 0, 0),
+ fontName='HeiseiKakuGo-W5'
)
@@ -205,7 +211,7 @@
def draw(self):
if 0:
assert isinstance(self.canv, pdfgen.canvas.Canvas)
- self.canv.setFont("Times-Roman", 10)
+ self.canv.setFont("HeiseiKakuGo-W5", 10)
# mean
mean = flowables.Box(self.mean_width, self.mean_height, self.box_depth)
mean.transparent = 0
Styleでparentが設定されているのにsetFontしたのは冗長だったかもしれないと思っています。
diff:sdaps/report/buddies.py
--- /usr/lib/python3/dist-packages/sdaps/report/buddies.py.20190314 2018-11-21 06:26:40.000000000 +0900
+++ /usr/lib/python3/dist-packages/sdaps/report/buddies.py 2019-03-14 15:06:03.265417740 +0900
@@ -19,6 +19,8 @@
import math
from reportlab import platypus
+from reportlab.pdfbase import pdfmetrics
+from reportlab.pdfbase.cidfonts import UnicodeCIDFont
from reportlab.lib import styles
from reportlab.lib import colors
from reportlab.lib import units
@@ -37,6 +39,7 @@
mm = units.mm
+pdfmetrics.registerFont(UnicodeCIDFont("HeiseiKakuGo-W5"))
stylesheet = dict(template.stylesheet)
@@ -47,13 +50,14 @@
leading=17,
backColor=colors.lightgrey,
spaceBefore=5 * mm,
+ fontName='HeiseiKakuGo-W5',
)
stylesheet['Question'] = styles.ParagraphStyle(
'Question',
stylesheet['Normal'],
spaceBefore=3 * mm,
- fontName='Times-Bold',
+ fontName='HeiseiKakuGo-W5',
)
stylesheet['Text'] = styles.ParagraphStyle(
@@ -64,6 +68,7 @@
rightIndent=5 * mm,
bulletIndent=2 * mm,
leftIndent=5 * mm,
+ fontName='HeiseiKakuGo-W5',
)
この他のTips
TIFFファイルの認識方法
ブラザーのドキュメントスキャナでUSBメモリに読み込んだtiffファイルをコピーして"add"コマンドで読み込ませています。
現段階では、sdapsの公式ドキュメントでは"add"コマンドの前にプロジェクトディレクトリを指定するよう書かれていますが、PPAの1.9.5では$ sdaps add <project_dir> <tiff file>
のように記述する必要がありました。
$ sdaps add 20190314_sdapstest/ 20190314_sdapstest.tif
この段階ではdefs.py以外のファイルを変更していなくてもエラーになる事はなく、プロジェクトディレクトリにコピーだけがされます。
レポートの出力手順
日本語化の前にdefs.pyとexample.texだけ変更したタイミングで、addしたファイルのレポートを取り出してみます。
1.9.5でもPythonのReportlabを使用して、PDFファイルが生成されているようなので
$ sdaps recognize 20190314_sdapstest/
------------------------------------------------------------------------------
- SDAPS -- recognize
------------------------------------------------------------------------------
2 sheets
|################################################################| 100% 00:00:00
Processed 2 of 2 sheets, took 0.804203 seconds
yasu@ub1804:/tmp$ sdaps report 20190314_sdapstest/
------------------------------------------------------------------------------
- SDAPS -- report
------------------------------------------------------------------------------
画面上には表示されませんが、20190314_sdapstest/report_1.pdf が出力されています。
レポート日本語化前に行なった、レポートの確認について
今回は2枚のデータを読み込んだので、まずその数値を確認していきます。
なお、日本語で書かれた質問項目は図のように読めない文字で出力されています。
ただ、数値に関しては100%意図した通りに動いています。
実際に利用する際に全体の2割程度のサンプルを抜き出してテストするので、今回はとりあえず動いたというところでレポートの日本語化の作業を進めていきます。
レポート日本語化後のイメージ
この前の段階で作成したイメージと比較するために日本語が表示できるようになったreport_2.pdfはこんな感じです。
本格的な確認は昨年使用したファイルを使っていく事にします。
2ページ以上出力時のエラーについて
sdaps setup ...
を実行してから、引数に与えたTeXファイルの内容は昨年と変わっていないもののエラーになりました。
A questionnaire that is printed in duplex needs an even amount of pages!
Error: Some combination of options and project properties do not work. Aborted Setup.
checkmode=checkの設定
別の記事で説明した、日本では一般的な黒塗りマークもチェックと認識するための設定を有効にしました。
このチェックによって一度記入したマークを消す事はできなくなり、消しゴムや修正液を使わないと修正ができなくなります。
ただ、日本でマークシートに慣れた人達にとってはより自然に受け入れられる形式になると思います。
report_texの利用に必要な変更
choicequestion環境に[singlechoice]オプションを指定したり、このエイリアスであるoptionquestion環境を利用した場合には、$ sdap report <project>
でPDFファイルを出力させた際に結果が掲載されなくなります。
これはissueに登録されていますが、積極的には解決されていません。
代替策として$ sdaps report_tex <project>
を利用する方法があります。
いずれの場合でも、CSVデータ自体は出力されるため、独自にレポートを作成する手段もあるかと思います。
このreport_texは、名前のとおりlatexで命令を出力するためxelatexを利用するための変更を中間ファイルであるreport.texに行なう必要があります。
--- /usr/lib/python3/dist-packages/sdaps/reporttex/__init__.py.20200323 2019-01-26 21:54:01.000000000 +0900
+++ /usr/lib/python3/dist-packages/sdaps/reporttex/__init__.py 2020-03-24 00:06:10.580077019 +0900
@@ -124,6 +124,12 @@
\fi
\usepackage[%(language)s]{babel}
+ \usepackage{xltxtra}
+ \setmainfont{IPAPMincho}
+ \setsansfont{IPAPGothic}
+ \setmonofont{IPAGothic}
+ \XeTeXlinebreaklocale "ja"
+
\title{%(title)s}
\subject{%(title)s}
\author{%(author)s}
twosideの指定が問題になる場合には、sdapsreport.clsを変更します。
--- /usr/share/sdaps/tex/sdapsreport.cls.20200323 2020-03-23 16:31:23.377533885 +0900
+++ /usr/share/sdaps/tex/sdapsreport.cls 2020-03-24 10:15:31.058306953 +0900
@@ -38,7 +38,7 @@
%-------------------------------------------------------------------------------
% load base-class
%-------------------------------------------------------------------------------
-\LoadClass[twoside,headings=small]{scrreprt}
+\LoadClass[oneside,headings=small]{scrreprt}
%-------------------------------------------------------------------------------
これらの手動での変更が嫌な場合や、できない場合には、sdaps report_texコマンドを--create-texオプションを指定して実行することで、report.texファイルを出力したところまでで処理を停止させることができます。
$ sdaps report_tex -p a4paper --create-tex my_project
------------------------------------------------------------------------------
- SDAPS -- report_tex
------------------------------------------------------------------------------
The TeX project with the report data is located at '/tmp/sdaps-report-0xhnt0gq'.
$ cd /tmp/sdaps-report-0xhnt0gq
$ xelatex report.tex
....
(see the transcript file for additional information)
Output written on report.pdf (20 pages).
Transcript written on report.log.
CSV形式でのデータ抽出
$ sdaps csv 20190314_sdapstest/ export
$ ls -l 20190314_sdapstest/*.csv
-rw-rw-r-- 1 yasu yasu 252 Mar 19 10:26 20190314_sdapstest/data_1.csv
集計したデータの検証作業
作業ではスキャナーの性能から、約20枚(40ページ)を1つのTIFFファイルに出力しています。確認のために1つのTIFFファイルに対応するレポートファイルを出力し、目視で集計した結果と自動的に集計した内容が一致するか検証しています。
こまでに"add","recognize"した結果の破棄
安全に"add"したデータを削除するために、resetコマンドを使用します。
$ sdaps reset 20190314_sdapstest/
この操作では、**20190314_sdapstest/**ディレクトリに出力された、PDFファイルや、CSVファイル等は削除されません。
TIFFファイルの追加とレポート出力の自動化
add/recognize/report/resetの各コマンドを繰り返し実行することで、各TIFFファイルに対応したレポートファイルを出力することができます。
$ target_dir="20190314_sdapstest/"
$ for file in BRN300*.tif
do
sdaps reset $target_dir
sdaps add $target_dir $file
sdaps recognize $target_dir
sdaps report $target_dir
sdaps csv $target_dir export
done
これを実際に試すと手元のsdapsでは、データが削除されずにサンプル数がどんどん増えていきます。
resetコマンドの本体は、/usr/lib/python3/dist-packages/sdaps/reset/init.pyに記述があります。
def reset(survey):
print((_("Removing stored data...")))
survey.sheets = []
survey.questionnaire_ids = []
survey.save()
print((_("Done")))
sheetsテーブルの内容が全て消えるはずだったのですが、surveyオブジェクトは次のように初期化されています。
survey = model.survey.Survey.load(cmdline['project'])
TRIGGERで消そうとしているつもりなのかSurveyオブジェクトのコードを調べても、このresetコマンドのインプリメンテーションでSheetテーブルを削除
$ sqlite3 -line ${target_dir}/survey.sqlite 'DELETE FROM sheets;'
最終的には次のようなスクリプトになりました。
$ target_dir="20190314_sdapstest/"
$ for file in BRN300*.tif
do
sdaps reset $target_dir
sqlite3 -line ${target_dir}/survey.sqlite 'DELETE FROM sheets;'
sdaps add $target_dir $file
sdaps recognize $target_dir
sdaps report $target_dir
sdaps csv $target_dir export
done
2020/03/10追記: sdaps 1.9.8-1 に更新した際に遭遇したエラー
ほぼ一年が経過して、Ubuntu 18.04.04のsdapsはバージョンが1.9.8-1に更新されていました。
これまでと同様に変更を実施したところ、$ sdaps report
コマンドを実行したタイミングでエラーになりました。
File "/usr/lib/python3/dist-packages/reportlab/platypus/paraparser.py", line 921, in _initial_frag frag.fontName, frag.bold, frag.italic = ps2tt(style.fontName)
File "/usr/lib/python3/dist-packages/reportlab/lib/fonts.py", line 79, in ps2tt
raise ValueError("Can't map determine family/bold/italic for %s" % psfn)
ValueError: Can't map determine family/bold/italic for heiseikakugo-w5
paragraph text '<para>...日本語を含むテキスト...</para>' caused exception
これは全ての変更ができておらず、手動でパッチを当てた時に、次のコードをtemplate.pyに加えるのを忘れたために出力されたものでした。
pdfmetrics.registerFont(UnicodeCIDFont("HeiseiKakuGo-W5"))
適切な場所に追記することで解決しました。
2020/03/24追記: 2ページ以上の質問票を作成した際に遭遇した問題
sdapsは、1ページか最大で6ページまでの偶数ページとなる質問票を作成することができます。奇数(3 or 5)ページまでしか埋められなかった場合には、途中や最後に\textbox*{20cm}{自由記述欄}
のようなボックス指定をいくつか入れることでページを増やすなどして、対応することができます。
ただ、作成する質問項目が増えるとミスも増加する可能性が高くなります。6ページの質問票を作成する際に、次のような現象に遭遇しました。
- 明らかに3ページ分以上の質問があるのに、2ページまでしか出力されない
- 2,4,6ページなど偶数ページとなるはずなのに、奇数ページは出力できないというエラーが出力され質問票PDFファイルが出力されない
- etc.
いずれも、エラーとなるべき質問票のtexファイルがエラーとならずに出力されたり、反対に、できるはずのPDFファイルが生成されないといった期待と異なる動きをするものでした。原因は、\begin{markgroup}などと指定するべきところで、存在しない\begin{markgropus}のような指定をしていた、といった自分のミスに起因するものでした。
もし間違ったPDFファイルが出力されている場合には、project/questionnaire.log が参考になります。
PDFファイルが出力されない場合には、仮の質問票latexファイルを作成して、小さい単位でコンパイルが可能か検証することになります。
以上