source-function-F_decodeOMRPage

It appears that you are using AdBlocking software. The cost of running this website is covered by advertisements. If you like it please feel free to a small amount of money to secure the future of this website.
Overview

Classes

Interfaces

Exceptions

Functions

  1: <?php
  2: //============================================================+
  3: // File name   : tce_functions_omr.php
  4: // Begin       : 2011-05-17
  5: // Last Update : 2014-06-11
  6: //
  7: // Description : Functions to import test data from scanned
  8: //               OMR (Optical Mark Recognition) sheets.
  9: //
 10: // Author: Nicola Asuni
 11: //
 12: // (c) Copyright:
 13: //               Nicola Asuni
 14: //               Tecnick.com LTD
 15: //               www.tecnick.com
 16: //               info@tecnick.com
 17: //
 18: // License:
 19: //    Copyright (C) 2004-2014 Nicola Asuni - Tecnick.com LTD
 20: //    See LICENSE.TXT file for more information.
 21: //============================================================+
 22: 
 23: /**
 24:  * @file
 25:  * Functions to import test data from scanned OMR (Optical Mark Recognition) sheets.
 26:  * @package com.tecnick.tcexam.shared
 27:  * @author Nicola Asuni
 28:  * @since 2011-05-17
 29:  */
 30: 
 31: /**
 32:  * Encode OMR test data array as a string to be printed on QR-Code.
 33:  * @param $data (array) array to be encoded
 34:  * @return encoded string.
 35:  */
 36: function F_encodeOMRTestData($data)
 37: {
 38:     $str = serialize($data);
 39:     $str = gzcompress($str, 9); // requires php-zlib extension
 40:     $str = base64_encode($str);
 41:     $str = urlencode($str);
 42:     return $str;
 43: }
 44: 
 45: /**
 46:  * Decode OMR test data string (read from QR-Code) as array.
 47:  * @param $str (string) string to be decoded.
 48:  * @return array with test data (0 => test_id, n => array(0 => question_n_ID, 1 => array(answers_IDs)), or false in case of error.
 49:  */
 50: function F_decodeOMRTestData($str)
 51: {
 52:     if (empty($str)) {
 53:         return false;
 54:     }
 55:     $data = $str;
 56:     $data = urldecode($data);
 57:     $data = base64_decode($data);
 58:     $data = gzuncompress($data);
 59:     $data = unserialize($data);
 60:     return $data;
 61: }
 62: 
 63: /**
 64:  * Read QR-Code from OMR page and return Test data.
 65:  * This function uses the external application zbarimg (http://zbar.sourceforge.net/).
 66:  * @param $image (string) image file to be decoded (scanned OMR page).
 67:  * @return array with test data or false in case o error
 68:  */
 69: function F_decodeOMRTestDataQRCode($image)
 70: {
 71:     require_once('../config/tce_config.php');
 72:     if (empty($image)) {
 73:         return false;
 74:     }
 75:     $command = K_OMR_PATH_ZBARIMG.' --raw -Sdisable -Sqrcode.enable -q '.escapeshellarg($image);
 76:     $str = exec($command);
 77:     return F_decodeOMRTestData($str);
 78: }
 79: 
 80: /**
 81:  * Decode a single OMR Page and return data array.
 82:  * This function requires ImageMagick library and zbarimg (http://zbar.sourceforge.net/).
 83:  * @param $image (string) image file to be decoded (scanned OMR page at 200 DPI with full color range).
 84:  * @return array of answers data or false in case of error.
 85:  */
 86: function F_decodeOMRPage($image)
 87: {
 88:     require_once('../config/tce_config.php');
 89:     // decode barcode containing first question number
 90:     $command = K_OMR_PATH_ZBARIMG.' --raw -Sdisable -Scode128.enable -q '.escapeshellarg($image);
 91:     $qstart = exec($command);
 92:     $qstart = intval($qstart);
 93:     if ($qstart == 0) {
 94:         return false;
 95:     }
 96:     $img = new Imagick();
 97:     $img->readImage($image);
 98:     $imginfo = $img->identifyImage();
 99:     if ($imginfo['type'] == 'TrueColor') {
100:         // remove red color
101:         $img->separateImageChannel(Imagick::CHANNEL_RED);
102:     } else {
103:         // desaturate image
104:         $img->modulateImage(100, 0, 100);
105:     }
106:     // get image width and height
107:     $w = $imginfo['geometry']['width'];
108:     $h = $imginfo['geometry']['height'];
109:     if ($h > $w) {
110:         // crop header and footer
111:         $y = round(($h - $w) / 2);
112:         $img->cropImage($w, $w, 0, $y);
113:         $img->setImagePage(0, 0, 0, 0);
114:     }
115:     $img->normalizeImage(Imagick::CHANNEL_ALL);
116:     $img->enhanceImage();
117:     $img->despeckleImage();
118:     $img->blackthresholdImage('#808080');
119:     $img->whitethresholdImage('#808080');
120:     $img->trimImage(85);
121:     $img->deskewImage(15);
122:     $img->trimImage(85);
123:     $img->resizeImage(1028, 1052, Imagick::FILTER_CUBIC, 1);
124:     $img->setImagePage(0, 0, 0, 0);
125:     //$img->writeImage(K_PATH_CACHE.'_DEBUG_OMR_.PNG'); // DEBUG
126:     // scan block width
127:     $blkw = 16;
128:     // starting column in pixels
129:     $scol = 106;
130:     // starting row in pixels
131:     $srow = 49;
132:     // column distance in pixels between two answers
133:     $dcol = 75.364;
134:     // column distance in pixels between True/false circles
135:     $dtf = 25;
136:     // row distance in pixels between two questions
137:     $drow = 32.38;
138:     // verify image pattern
139:     $imgtmp = clone $img;
140:     $imgtmp->cropImage(1028, 10, 0, 10);
141:     $imgtmp->setImagePage(0, 0, 0, 0);
142:     // create reference block pattern
143:     $impref = new Imagick();
144:     $impref->newImage(3, 10, new ImagickPixel('black'));
145:     $psum = 0;
146:     for ($c = 0; $c < 12; ++$c) {
147:         $x = round(112 + ($c * $dcol));
148:         // get square region inside the current grid position
149:         $imreg = $img->getImageRegion(3, 10, $x, 0);
150:         $imreg->setImagePage(0, 0, 0, 0);
151:         // get root-mean-square-error with reference image
152:         $rmse = $imreg->compareImages($impref, Imagick::METRIC_ROOTMEANSQUAREDERROR);
153:         // count reference blocks
154:         $psum += round(1.25 - $rmse[1]);
155:     }
156:     $imreg->clear();
157:     $impref->clear();
158:     if ($psum != 12) {
159:         return false;
160:     }
161:     // create reference block
162:     $imref = new Imagick();
163:     $imref->newImage($blkw, $blkw, new ImagickPixel('black'));
164:     // array to be returned
165:     $omrdata = array();
166:     // for each row (question)
167:     for ($r = 0; $r < 30; ++$r) {
168:         $omrdata[($r + $qstart)] = array();
169:         $y = round($srow + ($r * $drow));
170:         // for each column (answer)
171:         for ($c = 0; $c < 12; ++$c) {
172:             // read true option
173:             $x = round($scol + ($c * $dcol));
174:             // get square region inside the current grid position
175:             $imreg = $img->getImageRegion($blkw, $blkw, $x, $y);
176:             $imreg->setImagePage(0, 0, 0, 0);
177:             // get root-mean-square-error with reference image
178:             $rmse = $imreg->compareImages($imref, Imagick::METRIC_ROOTMEANSQUAREDERROR);
179:             // true option
180:             $opt_true = (2 * round(1.25 - $rmse[1]));
181:             // read false option
182:             $x += $dtf;
183:             // get square region inside the current grid position
184:             $imreg = $img->getImageRegion($blkw, $blkw, $x, $y);
185:             $imreg->setImagePage(0, 0, 0, 0);
186:             // get root-mean-square-error with reference image
187:             $rmse = $imreg->compareImages($imref, Imagick::METRIC_ROOTMEANSQUAREDERROR);
188:             // false option
189:             $opt_false = round(1.25 - $rmse[1]);
190:             // set array to be returned (-1 = unset, 0 = false, 1 = true)
191:             $val = ($opt_true + $opt_false - 1);
192:             if ($val > 1) {
193:                 $val = 1;
194:             }
195:             $omrdata[($r + $qstart)][($c + 1)] = $val;
196:         }
197:     }
198:     $imreg->clear();
199:     $imref->clear();
200:     return $omrdata;
201: }
202: 
203: /**
204:  * Import user's test data from OMR.
205:  * @param $user_id (int) user ID.
206:  * @param $date (string) date-time field.
207:  * @param $omr_testdata (array) Array containing test data.
208:  * @param $omr_answers (array) Array containing test answers (from OMR).
209:  * @param $overwrite (boolean) If true overwrites the previous answers on non-repeatable tests.
210:  * @return boolean TRUE in case of success, FALSE otherwise.
211:  */
212: function F_importOMRTestData($user_id, $date, $omr_testdata, $omr_answers, $overwrite = false)
213: {
214:     require_once('../config/tce_config.php');
215:     require_once('../../shared/code/tce_functions_test.php');
216:     global $db, $l;
217:     // check arrays
218:     if (count($omr_testdata) > (count($omr_answers) + 1)) {
219:         // arrays must contain the same amount of questions
220:         return false;
221:     }
222:     $test_id = intval($omr_testdata[0]);
223:     $user_id = intval($user_id);
224:     $time = strtotime($date);
225:     $date = date(K_TIMESTAMP_FORMAT, $time);
226:     $dateanswers = date(K_TIMESTAMP_FORMAT, ($time + 1));
227:     // check user's group
228:     if (F_count_rows(K_TABLE_USERGROUP.', '.K_TABLE_TEST_GROUPS.' WHERE usrgrp_group_id=tstgrp_group_id AND tstgrp_test_id='.$test_id.' AND usrgrp_user_id='.$user_id.' LIMIT 1') == 0) {
229:         return false;
230:     }
231:     // get test data
232:     $testdata = F_getTestData($test_id);
233:     // 1. check if test is repeatable
234:     $sqls = 'SELECT test_id FROM '.K_TABLE_TESTS.' WHERE test_id='.$test_id.' AND test_repeatable=\'1\' LIMIT 1';
235:     if ($rs = F_db_query($sqls, $db)) {
236:         if ($ms = F_db_fetch_array($rs)) {
237:             // 1a. update previous test data if repeatable
238:             $sqld = 'UPDATE '.K_TABLE_TEST_USER.' SET testuser_status=testuser_status+1 WHERE testuser_test_id='.$test_id.' AND testuser_user_id='.$user_id.' AND testuser_status>3';
239:             if (!$rd = F_db_query($sqld, $db)) {
240:                 F_display_db_error();
241:             }
242:         } else {
243:             if ($overwrite) {
244:                 // 1b. delete previous test data if not repeatable
245:                 $sqld = 'DELETE FROM '.K_TABLE_TEST_USER.' WHERE testuser_test_id='.$test_id.' AND testuser_user_id='.$user_id.'';
246:                 if (!$rd = F_db_query($sqld, $db)) {
247:                     F_display_db_error();
248:                 }
249:             } else {
250:                 // 1c. check if this data already exist
251:                 if (F_count_rows(K_TABLE_TEST_USER, 'WHERE testuser_test_id='.$test_id.' AND testuser_user_id='.$user_id.'') > 0) {
252:                     return false;
253:                 }
254:             }
255:         }
256:     } else {
257:         F_display_db_error();
258:     }
259:     // 2. create new user's test entry
260:     // ------------------------------
261:     $sql = 'INSERT INTO '.K_TABLE_TEST_USER.' (
262:         testuser_test_id,
263:         testuser_user_id,
264:         testuser_status,
265:         testuser_creation_time,
266:         testuser_comment
267:         ) VALUES (
268:         '.$test_id.',
269:         '.$user_id.',
270:         4,
271:         \''.$date.'\',
272:         \'OMR\'
273:         )';
274:     if (!$r = F_db_query($sql, $db)) {
275:         F_display_db_error(false);
276:         return false;
277:     } else {
278:         // get inserted ID
279:         $testuser_id = F_db_insert_id($db, K_TABLE_TEST_USER, 'testuser_id');
280:         F_updateTestuserStat($date);
281:     }
282:     // 3. create test log entries
283:     $num_questions = count($omr_testdata) - 1;
284:     // for each question on array
285:     for ($q = 1; $q <= $num_questions; ++$q) {
286:         $question_id = intval($omr_testdata[$q][0]);
287:         $num_answers = count($omr_testdata[$q][1]);
288:         // get question data
289:         $sqlq = 'SELECT question_type, question_difficulty FROM '.K_TABLE_QUESTIONS.' WHERE question_id='.$question_id.' LIMIT 1';
290:         if ($rq = F_db_query($sqlq, $db)) {
291:             if ($mq = F_db_fetch_array($rq)) {
292:                 // question scores
293:                 $question_right_score = ($testdata['test_score_right'] * $mq['question_difficulty']);
294:                 $question_wrong_score = ($testdata['test_score_wrong'] * $mq['question_difficulty']);
295:                 $question_unanswered_score = ($testdata['test_score_unanswered'] * $mq['question_difficulty']);
296:                 // add question
297:                 $sqll = 'INSERT INTO '.K_TABLE_TESTS_LOGS.' (
298:                     testlog_testuser_id,
299:                     testlog_question_id,
300:                     testlog_score,
301:                     testlog_creation_time,
302:                     testlog_display_time,
303:                     testlog_reaction_time,
304:                     testlog_order,
305:                     testlog_num_answers
306:                     ) VALUES (
307:                     '.$testuser_id.',
308:                     '.$question_id.',
309:                     '.$question_unanswered_score.',
310:                     \''.$date.'\',
311:                     \''.$date.'\',
312:                     1,
313:                     '.$q.',
314:                     '.$num_answers.'
315:                     )';
316:                 if (!$rl = F_db_query($sqll, $db)) {
317:                     F_display_db_error(false);
318:                     return false;
319:                 }
320:                 $testlog_id = F_db_insert_id($db, K_TABLE_TESTS_LOGS, 'testlog_id');
321:                 // set initial question score
322:                 if ($mq['question_type'] == 1) { // MCSA
323:                     $qscore = $question_unanswered_score;
324:                 } else { // MCMA
325:                     $qscore = 0;
326:                 }
327:                 $unanswered = true;
328:                 $numselected = 0; // count the number of MCSA selected answers
329:                 // for each answer on array
330:                 for ($a = 1; $a <= $num_answers; ++$a) {
331:                     $answer_id = intval($omr_testdata[$q][1][$a]);
332:                     if (isset($omr_answers[$q][$a])) {
333:                         $answer_selected = $omr_answers[$q][$a]; //-1, 0, 1
334:                     } else {
335:                         $answer_selected = -1;
336:                     }
337:                     // add answer
338:                     $sqli = 'INSERT INTO '.K_TABLE_LOG_ANSWER.' (
339:                         logansw_testlog_id,
340:                         logansw_answer_id,
341:                         logansw_selected,
342:                         logansw_order
343:                         ) VALUES (
344:                         '.$testlog_id.',
345:                         '.$answer_id.',
346:                         '.$answer_selected.',
347:                         '.$a.'
348:                         )';
349:                     if (!$ri = F_db_query($sqli, $db)) {
350:                         F_display_db_error(false);
351:                         return false;
352:                     }
353:                     // calculate question score
354:                     if ($mq['question_type'] < 3) { // MCSA or MCMA
355:                         // check if the answer is right
356:                         $answer_isright = false;
357:                         $sqla = 'SELECT answer_isright FROM '.K_TABLE_ANSWERS.' WHERE answer_id='.$answer_id.' LIMIT 1';
358:                         if ($ra = F_db_query($sqla, $db)) {
359:                             if (($ma = F_db_fetch_array($ra))) {
360:                                 $answer_isright = F_getBoolean($ma['answer_isright']);
361:                                 switch ($mq['question_type']) {
362:                                     case 1: { // MCSA - Multiple Choice Single Answer
363:                                         if ($answer_selected == 1) {
364:                                             ++$numselected;
365:                                             if ($numselected == 1) {
366:                                                 $unanswered = false;
367:                                                 if ($answer_isright) {
368:                                                     $qscore = $question_right_score;
369:                                                 } else {
370:                                                     $qscore = $question_wrong_score;
371:                                                 }
372:                                             } else {
373:                                                 // multiple answer selected
374:                                                 $unanswered = true;
375:                                                 $qscore = $question_unanswered_score;
376:                                             }
377:                                         }
378:                                         break;
379:                                     }
380:                                     case 2: { // MCMA - Multiple Choice Multiple Answer
381:                                         if ($answer_selected == -1) {
382:                                             $qscore += $question_unanswered_score;
383:                                         } elseif ($answer_selected == 0) {
384:                                             $unanswered = false;
385:                                             if ($answer_isright) {
386:                                                 $qscore += $question_wrong_score;
387:                                             } else {
388:                                                 $qscore += $question_right_score;
389:                                             }
390:                                         } elseif ($answer_selected == 1) {
391:                                             $unanswered = false;
392:                                             if ($answer_isright) {
393:                                                 $qscore += $question_right_score;
394:                                             } else {
395:                                                 $qscore += $question_wrong_score;
396:                                             }
397:                                         }
398:                                         break;
399:                                     }
400:                                 }
401:                             }
402:                         } else {
403:                             F_display_db_error(false);
404:                             return false;
405:                         }
406:                     }
407:                 } // end for each answer
408:                 if ($mq['question_type'] == 2) { // MCMA
409:                     // normalize score
410:                     if (F_getBoolean($testdata['test_mcma_partial_score'])) {
411:                         // use partial scoring for MCMA and ORDER questions
412:                         $qscore = round(($qscore / $num_answers), 3);
413:                     } else {
414:                         // all-or-nothing points
415:                         if ($qscore >= ($question_right_score * $num_answers)) {
416:                             // right
417:                             $qscore = $question_right_score;
418:                         } elseif ($qscore == ($question_unanswered_score * $num_answers)) {
419:                             // unanswered
420:                             $qscore = $question_unanswered_score;
421:                         } else {
422:                             // wrong
423:                             $qscore = $question_wrong_score;
424:                         }
425:                     }
426:                 }
427:                 if ($unanswered) {
428:                     $change_time = '';
429:                 } else {
430:                     $change_time = $dateanswers;
431:                 }
432:                 // update question score
433:                 $sqll = 'UPDATE '.K_TABLE_TESTS_LOGS.' SET
434:                     testlog_score='.$qscore.',
435:                     testlog_change_time='.F_empty_to_null($change_time).',
436:                     testlog_reaction_time=1000
437:                     WHERE testlog_id='.$testlog_id.'';
438:                 if (!$rl = F_db_query($sqll, $db)) {
439:                     F_display_db_error();
440:                     return false;
441:                 }
442:             }
443:         } else {
444:             F_display_db_error(false);
445:             return false;
446:         }
447:     } // end for each question
448:     return true;
449: }
450: 
451: //============================================================+
452: // END OF FILE
453: //============================================================+
454: 
 

© 2004-2018 – Nicola Asuni - Tecnick.com - All rights reserved.
about - disclaimer - privacy