initial commit

This commit is contained in:
Louis Heredero 2022-09-22 13:13:15 +02:00
commit ea1fbe4e94
93 changed files with 9161 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
__pycache__
ec_params.txt

View File

@ -0,0 +1,102 @@
\def\arraystretch{1.2}
\begin{center}
\begin{longtabu}{|[2pt]c|c|c|c|c|c|c|c|[2pt]}
\caption{Alignment pattern locations}
\label{tab:qr_alignment}\\
\tabucline[2pt]{-}
Version & \multicolumn{7}{c|[2pt]}{Central x and y coordinates} \\
\tabucline[2pt]{-}
\endfirsthead
\multicolumn{8}{r}{\emph{Continued from last page}}\\
\hline
Version & \multicolumn{7}{c|[2pt]}{Central x and y coordinates} \\
\endhead
Version & \multicolumn{7}{c|[2pt]}{Central x and y coordinates} \\
\hline
\multicolumn{8}{r}{\emph{Continued on next page}}\\
\endfoot
\tabucline[2pt]{-}
\endlastfoot
1 & & & & & & & \\
\hline
2 & 6 & 18 & & & & & \\
\hline
3 & 6 & 22 & & & & & \\
\hline
4 & 6 & 26 & & & & & \\
\hline
5 & 6 & 30 & & & & & \\
\hline
6 & 6 & 34 & & & & & \\
\hline
7 & 6 & 22 & 38 & & & & \\
\hline
8 & 6 & 24 & 42 & & & & \\
\hline
9 & 6 & 26 & 46 & & & & \\
\hline
10 & 6 & 28 & 50 & & & & \\
\hline
11 & 6 & 30 & 54 & & & & \\
\hline
12 & 6 & 32 & 58 & & & & \\
\hline
13 & 6 & 34 & 62 & & & & \\
\hline
14 & 6 & 26 & 46 & 66 & & & \\
\hline
15 & 6 & 26 & 48 & 70 & & & \\
\hline
16 & 6 & 26 & 50 & 74 & & & \\
\hline
17 & 6 & 30 & 54 & 78 & & & \\
\hline
18 & 6 & 30 & 56 & 82 & & & \\
\hline
19 & 6 & 30 & 58 & 86 & & & \\
\hline
20 & 6 & 34 & 62 & 90 & & & \\
\hline
21 & 6 & 28 & 50 & 72 & 94 & & \\
\hline
22 & 6 & 26 & 50 & 74 & 98 & & \\
\hline
23 & 6 & 30 & 54 & 78 & 102 & & \\
\hline
24 & 6 & 28 & 54 & 80 & 106 & & \\
\hline
25 & 6 & 32 & 58 & 84 & 110 & & \\
\hline
26 & 6 & 30 & 58 & 86 & 114 & & \\
\hline
27 & 6 & 34 & 62 & 90 & 118 & & \\
\hline
28 & 6 & 26 & 50 & 74 & 98 & 122 & \\
\hline
29 & 6 & 30 & 54 & 78 & 102 & 126 & \\
\hline
30 & 6 & 26 & 52 & 78 & 104 & 130 & \\
\hline
31 & 6 & 30 & 56 & 82 & 108 & 134 & \\
\hline
32 & 6 & 34 & 60 & 86 & 112 & 138 & \\
\hline
33 & 6 & 30 & 58 & 86 & 114 & 142 & \\
\hline
34 & 6 & 34 & 62 & 90 & 118 & 146 & \\
\hline
35 & 6 & 30 & 54 & 78 & 102 & 126 & 150 \\
\hline
36 & 6 & 24 & 50 & 76 & 102 & 128 & 154 \\
\hline
37 & 6 & 28 & 54 & 80 & 106 & 132 & 158 \\
\hline
38 & 6 & 32 & 58 & 84 & 110 & 136 & 162 \\
\hline
39 & 6 & 26 & 54 & 82 & 110 & 138 & 166 \\
\hline
40 & 6 & 30 & 58 & 86 & 114 & 142 & 170 \\
\hline
\end{longtabu}
\end{center}
\def\arraystretch{1}

View File

@ -0,0 +1,222 @@
\def\arraystretch{1.2}
\begin{center}
\begin{longtabu}{|[2pt]c|c|c|c|c|c|c|c|[2pt]}
\caption{Error correction characteristics}
\label{tab:qr_error_correction}\\
\tabucline[2pt]{-}
\rot{Version} & \rot{Correction level} & \rot{Data codewords} & \rot{\shortstack[l]{Error correction \\ codewords per block}} & \rot{Blocks in group 1} & \rot{\shortstack[l]{Data codewords per \\ group 1 blocks}} & \rot{Blocks in group 2} & \rot{\shortstack[l]{Data codewords per \\ group 2 blocks}} \\
\tabucline[2pt]{-}
\endfirsthead
\multicolumn{8}{r}{\emph{Continued from last page}}\\
\hline
Ver & Level & Data CW & EC CW /B & Blocks G1 & CW G1 & Blocks G2 & CW G2 \\
\endhead
Ver & Level & Data CW & EC CW /B & Blocks G1 & CW G1 & Blocks G2 & CW G2 \\
\hline
\multicolumn{8}{r}{\emph{Continued on next page}}\\
\endfoot
\tabucline[2pt]{-}
\endlastfoot
\multirow{4}{*}{ 1} & L & 19 & 7 & 1 & 19 & 0 & 0 \\*
& M & 16 & 10 & 1 & 16 & 0 & 0 \\*
& Q & 13 & 13 & 1 & 13 & 0 & 0 \\*
& H & 9 & 17 & 1 & 9 & 0 & 0 \\
\hline
\multirow{4}{*}{ 2} & L & 34 & 10 & 1 & 34 & 0 & 0 \\*
& M & 28 & 16 & 1 & 28 & 0 & 0 \\*
& Q & 22 & 22 & 1 & 22 & 0 & 0 \\*
& H & 16 & 28 & 1 & 16 & 0 & 0 \\
\hline
\multirow{4}{*}{ 3} & L & 55 & 15 & 1 & 55 & 0 & 0 \\*
& M & 44 & 26 & 1 & 44 & 0 & 0 \\*
& Q & 34 & 18 & 2 & 17 & 0 & 0 \\*
& H & 26 & 22 & 2 & 13 & 0 & 0 \\
\hline
\multirow{4}{*}{ 4} & L & 80 & 20 & 1 & 80 & 0 & 0 \\*
& M & 64 & 18 & 2 & 32 & 0 & 0 \\*
& Q & 48 & 26 & 2 & 24 & 0 & 0 \\*
& H & 36 & 16 & 4 & 9 & 0 & 0 \\
\hline
\multirow{4}{*}{ 5} & L & 108 & 26 & 1 & 108 & 0 & 0 \\*
& M & 86 & 24 & 2 & 43 & 0 & 0 \\*
& Q & 62 & 18 & 2 & 15 & 2 & 16 \\*
& H & 46 & 22 & 2 & 11 & 2 & 12 \\
\hline
\multirow{4}{*}{ 6} & L & 136 & 18 & 2 & 68 & 0 & 0 \\*
& M & 108 & 16 & 4 & 27 & 0 & 0 \\*
& Q & 76 & 24 & 4 & 19 & 0 & 0 \\*
& H & 60 & 28 & 4 & 15 & 0 & 0 \\
\hline
\multirow{4}{*}{ 7} & L & 156 & 20 & 2 & 78 & 0 & 0 \\*
& M & 124 & 18 & 4 & 31 & 0 & 0 \\*
& Q & 88 & 18 & 2 & 14 & 4 & 15 \\*
& H & 66 & 26 & 4 & 13 & 1 & 14 \\
\hline
\multirow{4}{*}{ 8} & L & 194 & 24 & 2 & 97 & 0 & 0 \\*
& M & 154 & 22 & 2 & 38 & 2 & 39 \\*
& Q & 110 & 22 & 4 & 18 & 2 & 19 \\*
& H & 86 & 26 & 4 & 14 & 2 & 15 \\
\hline
\multirow{4}{*}{ 9} & L & 232 & 30 & 2 & 116 & 0 & 0 \\*
& M & 182 & 22 & 3 & 36 & 2 & 37 \\*
& Q & 132 & 20 & 4 & 16 & 4 & 17 \\*
& H & 100 & 24 & 4 & 12 & 4 & 13 \\
\hline
\multirow{4}{*}{10} & L & 274 & 18 & 2 & 68 & 2 & 69 \\*
& M & 216 & 26 & 4 & 43 & 1 & 44 \\*
& Q & 154 & 24 & 6 & 19 & 2 & 20 \\*
& H & 122 & 28 & 6 & 15 & 2 & 16 \\
\hline
\multirow{4}{*}{11} & L & 324 & 20 & 4 & 81 & 0 & 0 \\*
& M & 254 & 30 & 1 & 50 & 4 & 51 \\*
& Q & 180 & 28 & 4 & 22 & 4 & 23 \\*
& H & 140 & 24 & 3 & 12 & 8 & 13 \\
\hline
\multirow{4}{*}{12} & L & 370 & 24 & 2 & 92 & 2 & 93 \\*
& M & 290 & 22 & 6 & 36 & 2 & 37 \\*
& Q & 206 & 26 & 4 & 20 & 6 & 21 \\*
& H & 158 & 28 & 7 & 14 & 4 & 15 \\
\hline
\multirow{4}{*}{13} & L & 428 & 26 & 4 & 107 & 0 & 0 \\*
& M & 334 & 22 & 8 & 37 & 1 & 38 \\*
& Q & 244 & 24 & 8 & 20 & 4 & 21 \\*
& H & 180 & 22 & 12 & 11 & 4 & 12 \\
\hline
\multirow{4}{*}{14} & L & 461 & 30 & 3 & 115 & 1 & 116 \\*
& M & 365 & 24 & 4 & 40 & 5 & 41 \\*
& Q & 261 & 20 & 11 & 16 & 5 & 17 \\*
& H & 197 & 24 & 11 & 12 & 5 & 13 \\
\hline
\multirow{4}{*}{15} & L & 523 & 22 & 5 & 87 & 1 & 88 \\*
& M & 415 & 24 & 5 & 41 & 5 & 42 \\*
& Q & 295 & 30 & 5 & 24 & 7 & 25 \\*
& H & 223 & 24 & 11 & 12 & 7 & 13 \\
\hline
\multirow{4}{*}{16} & L & 589 & 24 & 5 & 98 & 1 & 99 \\*
& M & 453 & 28 & 7 & 45 & 3 & 46 \\*
& Q & 325 & 24 & 15 & 19 & 2 & 20 \\*
& H & 253 & 30 & 3 & 15 & 13 & 16 \\
\hline
\multirow{4}{*}{17} & L & 647 & 28 & 1 & 107 & 5 & 108 \\*
& M & 507 & 28 & 10 & 46 & 1 & 47 \\*
& Q & 367 & 28 & 1 & 22 & 15 & 23 \\*
& H & 283 & 28 & 2 & 14 & 17 & 15 \\
\hline
\multirow{4}{*}{18} & L & 721 & 30 & 5 & 120 & 1 & 121 \\*
& M & 563 & 26 & 9 & 43 & 4 & 44 \\*
& Q & 397 & 28 & 17 & 22 & 1 & 23 \\*
& H & 313 & 28 & 2 & 14 & 19 & 15 \\
\hline
\multirow{4}{*}{19} & L & 795 & 28 & 3 & 113 & 4 & 114 \\*
& M & 627 & 26 & 3 & 44 & 11 & 45 \\*
& Q & 445 & 26 & 17 & 21 & 4 & 22 \\*
& H & 341 & 26 & 9 & 13 & 16 & 14 \\
\hline
\multirow{4}{*}{20} & L & 861 & 28 & 3 & 107 & 5 & 108 \\*
& M & 669 & 26 & 3 & 41 & 13 & 42 \\*
& Q & 485 & 30 & 15 & 24 & 5 & 25 \\*
& H & 385 & 28 & 15 & 15 & 10 & 16 \\
\hline
\multirow{4}{*}{21} & L & 932 & 28 & 4 & 116 & 4 & 117 \\*
& M & 714 & 26 & 17 & 42 & 0 & 0 \\*
& Q & 512 & 28 & 17 & 22 & 6 & 23 \\*
& H & 406 & 30 & 19 & 16 & 6 & 17 \\
\hline
\multirow{4}{*}{22} & L & 1006 & 28 & 2 & 111 & 7 & 112 \\*
& M & 782 & 28 & 17 & 46 & 0 & 0 \\*
& Q & 568 & 30 & 7 & 24 & 16 & 25 \\*
& H & 442 & 24 & 34 & 13 & 0 & 0 \\
\hline
\multirow{4}{*}{23} & L & 1094 & 30 & 4 & 121 & 5 & 122 \\*
& M & 860 & 28 & 4 & 47 & 14 & 48 \\*
& Q & 614 & 30 & 11 & 24 & 14 & 25 \\*
& H & 464 & 30 & 16 & 15 & 14 & 16 \\
\hline
\multirow{4}{*}{24} & L & 1174 & 30 & 6 & 117 & 4 & 118 \\*
& M & 914 & 28 & 6 & 45 & 14 & 46 \\*
& Q & 664 & 30 & 11 & 24 & 16 & 25 \\*
& H & 514 & 30 & 30 & 16 & 2 & 17 \\
\hline
\multirow{4}{*}{25} & L & 1276 & 26 & 8 & 106 & 4 & 107 \\*
& M & 1000 & 28 & 8 & 47 & 13 & 48 \\*
& Q & 718 & 30 & 7 & 24 & 22 & 25 \\*
& H & 538 & 30 & 22 & 15 & 13 & 16 \\
\hline
\multirow{4}{*}{26} & L & 1370 & 28 & 10 & 114 & 2 & 115 \\*
& M & 1062 & 28 & 19 & 46 & 4 & 47 \\*
& Q & 754 & 28 & 28 & 22 & 6 & 23 \\*
& H & 596 & 30 & 33 & 16 & 4 & 17 \\
\hline
\multirow{4}{*}{27} & L & 1468 & 30 & 8 & 122 & 4 & 123 \\*
& M & 1128 & 28 & 22 & 45 & 3 & 46 \\*
& Q & 808 & 30 & 8 & 23 & 26 & 24 \\*
& H & 628 & 30 & 12 & 15 & 28 & 16 \\
\hline
\multirow{4}{*}{28} & L & 1531 & 30 & 3 & 117 & 10 & 118 \\*
& M & 1193 & 28 & 3 & 45 & 23 & 46 \\*
& Q & 871 & 30 & 4 & 24 & 31 & 25 \\*
& H & 661 & 30 & 11 & 15 & 31 & 16 \\
\hline
\multirow{4}{*}{29} & L & 1631 & 30 & 7 & 116 & 7 & 117 \\*
& M & 1267 & 28 & 21 & 45 & 7 & 46 \\*
& Q & 911 & 30 & 1 & 23 & 37 & 24 \\*
& H & 701 & 30 & 19 & 15 & 26 & 16 \\
\hline
\multirow{4}{*}{30} & L & 1735 & 30 & 5 & 115 & 10 & 116 \\*
& M & 1373 & 28 & 19 & 47 & 10 & 48 \\*
& Q & 985 & 30 & 15 & 24 & 25 & 25 \\*
& H & 745 & 30 & 23 & 15 & 25 & 16 \\
\hline
\multirow{4}{*}{31} & L & 1843 & 30 & 13 & 115 & 3 & 116 \\*
& M & 1455 & 28 & 2 & 46 & 29 & 47 \\*
& Q & 1033 & 30 & 42 & 24 & 1 & 25 \\*
& H & 793 & 30 & 23 & 15 & 28 & 16 \\
\hline
\multirow{4}{*}{32} & L & 1955 & 30 & 17 & 115 & 0 & 0 \\*
& M & 1541 & 28 & 10 & 46 & 23 & 47 \\*
& Q & 1115 & 30 & 10 & 24 & 35 & 25 \\*
& H & 845 & 30 & 19 & 15 & 35 & 16 \\
\hline
\multirow{4}{*}{33} & L & 2071 & 30 & 17 & 115 & 1 & 116 \\*
& M & 1631 & 28 & 14 & 46 & 21 & 47 \\*
& Q & 1171 & 30 & 29 & 24 & 19 & 25 \\*
& H & 901 & 30 & 11 & 15 & 46 & 16 \\
\hline
\multirow{4}{*}{34} & L & 2191 & 30 & 13 & 115 & 6 & 116 \\*
& M & 1725 & 28 & 14 & 46 & 23 & 47 \\*
& Q & 1231 & 30 & 44 & 24 & 7 & 25 \\*
& H & 961 & 30 & 59 & 16 & 1 & 17 \\
\hline
\multirow{4}{*}{35} & L & 2306 & 30 & 12 & 121 & 7 & 122 \\*
& M & 1812 & 28 & 12 & 47 & 26 & 48 \\*
& Q & 1286 & 30 & 39 & 24 & 14 & 25 \\*
& H & 986 & 30 & 22 & 15 & 41 & 16 \\
\hline
\multirow{4}{*}{36} & L & 2434 & 30 & 6 & 121 & 14 & 122 \\*
& M & 1914 & 28 & 6 & 47 & 34 & 48 \\*
& Q & 1354 & 30 & 46 & 24 & 10 & 25 \\*
& H & 1054 & 30 & 2 & 15 & 64 & 16 \\
\hline
\multirow{4}{*}{37} & L & 2566 & 30 & 17 & 122 & 4 & 123 \\*
& M & 1992 & 28 & 29 & 46 & 14 & 47 \\*
& Q & 1426 & 30 & 49 & 24 & 10 & 25 \\*
& H & 1096 & 30 & 24 & 15 & 46 & 16 \\
\hline
\multirow{4}{*}{38} & L & 2702 & 30 & 4 & 122 & 18 & 123 \\*
& M & 2102 & 28 & 13 & 46 & 32 & 47 \\*
& Q & 1502 & 30 & 48 & 24 & 14 & 25 \\*
& H & 1142 & 30 & 42 & 15 & 32 & 16 \\
\hline
\multirow{4}{*}{39} & L & 2812 & 30 & 20 & 117 & 4 & 118 \\*
& M & 2216 & 28 & 40 & 47 & 7 & 48 \\*
& Q & 1582 & 30 & 43 & 24 & 22 & 25 \\*
& H & 1222 & 30 & 10 & 15 & 67 & 16 \\
\hline
\multirow{4}{*}{40} & L & 2956 & 30 & 19 & 118 & 6 & 119 \\*
& M & 2334 & 28 & 18 & 47 & 31 & 48 \\*
& Q & 1666 & 30 & 34 & 24 & 34 & 25 \\*
& H & 1276 & 30 & 20 & 15 & 61 & 16 \\
\hline
\end{longtabu}
\end{center}
\def\arraystretch{1}

View File

@ -0,0 +1,218 @@
\def\arraystretch{1.2}
\begin{center}
\begin{longtabu}{|[2pt]c|c|c:c:c:c|[2pt]}
\caption{Version capacities}
\label{tab:qr_versions}\\
\tabucline[2pt]{-}
Version & Correction level & Numerical & Alphanumerical & Byte & Kanji \\
\tabucline[2pt]{-}
\endfirsthead
\multicolumn{6}{r}{\emph{Continued from last page}}\\
\endhead
\multicolumn{6}{r}{\emph{Continued on next page}}\\
\endfoot
\tabucline[2pt]{-}
\endlastfoot
\multirow{4}{*}{ 1} & L & 41 & 25 & 17 & 10 \\*
& M & 34 & 20 & 14 & 8 \\*
& Q & 27 & 16 & 11 & 7 \\*
& H & 17 & 10 & 7 & 4 \\
\hline
\multirow{4}{*}{ 2} & L & 77 & 47 & 32 & 20 \\*
& M & 63 & 38 & 26 & 16 \\*
& Q & 48 & 29 & 20 & 12 \\*
& H & 34 & 20 & 14 & 8 \\
\hline
\multirow{4}{*}{ 3} & L & 127 & 77 & 53 & 32 \\*
& M & 101 & 61 & 42 & 26 \\*
& Q & 77 & 47 & 32 & 20 \\*
& H & 58 & 35 & 24 & 15 \\
\hline
\multirow{4}{*}{ 4} & L & 187 & 114 & 78 & 48 \\*
& M & 149 & 90 & 62 & 38 \\*
& Q & 111 & 67 & 46 & 28 \\*
& H & 82 & 50 & 34 & 21 \\
\hline
\multirow{4}{*}{ 5} & L & 255 & 154 & 106 & 65 \\*
& M & 202 & 122 & 84 & 52 \\*
& Q & 144 & 87 & 60 & 37 \\*
& H & 106 & 64 & 44 & 27 \\
\hline
\multirow{4}{*}{ 6} & L & 322 & 195 & 134 & 82 \\*
& M & 255 & 154 & 106 & 65 \\*
& Q & 178 & 108 & 74 & 45 \\*
& H & 139 & 84 & 58 & 36 \\
\hline
\multirow{4}{*}{ 7} & L & 370 & 224 & 154 & 95 \\*
& M & 293 & 178 & 122 & 75 \\*
& Q & 207 & 125 & 86 & 53 \\*
& H & 154 & 93 & 64 & 39 \\
\hline
\multirow{4}{*}{ 8} & L & 461 & 279 & 192 & 118 \\*
& M & 365 & 221 & 152 & 93 \\*
& Q & 259 & 157 & 108 & 66 \\*
& H & 202 & 122 & 84 & 52 \\
\hline
\multirow{4}{*}{ 9} & L & 552 & 335 & 230 & 141 \\*
& M & 432 & 262 & 180 & 111 \\*
& Q & 312 & 189 & 130 & 80 \\*
& H & 235 & 143 & 98 & 60 \\
\hline
\multirow{4}{*}{10} & L & 652 & 395 & 271 & 167 \\*
& M & 513 & 311 & 213 & 131 \\*
& Q & 364 & 221 & 151 & 93 \\*
& H & 288 & 174 & 119 & 74 \\
\hline
\multirow{4}{*}{11} & L & 772 & 468 & 321 & 198 \\*
& M & 604 & 366 & 251 & 155 \\*
& Q & 427 & 259 & 177 & 109 \\*
& H & 331 & 200 & 137 & 85 \\
\hline
\multirow{4}{*}{12} & L & 883 & 535 & 367 & 226 \\*
& M & 691 & 419 & 287 & 177 \\*
& Q & 489 & 296 & 203 & 125 \\*
& H & 374 & 227 & 155 & 96 \\
\hline
\multirow{4}{*}{13} & L & 1022 & 619 & 425 & 262 \\*
& M & 796 & 483 & 331 & 204 \\*
& Q & 580 & 352 & 241 & 149 \\*
& H & 427 & 259 & 177 & 109 \\
\hline
\multirow{4}{*}{14} & L & 1101 & 667 & 458 & 282 \\*
& M & 871 & 528 & 362 & 223 \\*
& Q & 621 & 376 & 258 & 159 \\*
& H & 468 & 283 & 194 & 120 \\
\hline
\multirow{4}{*}{15} & L & 1250 & 758 & 520 & 320 \\*
& M & 991 & 600 & 412 & 254 \\*
& Q & 703 & 426 & 292 & 180 \\*
& H & 530 & 321 & 220 & 136 \\
\hline
\multirow{4}{*}{16} & L & 1408 & 854 & 586 & 361 \\*
& M & 1082 & 656 & 450 & 277 \\*
& Q & 775 & 470 & 322 & 198 \\*
& H & 602 & 365 & 250 & 154 \\
\hline
\multirow{4}{*}{17} & L & 1548 & 938 & 644 & 397 \\*
& M & 1212 & 734 & 504 & 310 \\*
& Q & 876 & 531 & 364 & 224 \\*
& H & 674 & 408 & 280 & 173 \\
\hline
\multirow{4}{*}{18} & L & 1725 & 1046 & 718 & 442 \\*
& M & 1346 & 816 & 560 & 345 \\*
& Q & 948 & 574 & 394 & 243 \\*
& H & 746 & 452 & 310 & 191 \\
\hline
\multirow{4}{*}{19} & L & 1903 & 1153 & 792 & 488 \\*
& M & 1500 & 909 & 624 & 384 \\*
& Q & 1063 & 644 & 442 & 272 \\*
& H & 813 & 493 & 338 & 208 \\
\hline
\multirow{4}{*}{20} & L & 2061 & 1249 & 858 & 528 \\*
& M & 1600 & 970 & 666 & 410 \\*
& Q & 1159 & 702 & 482 & 297 \\*
& H & 919 & 557 & 382 & 235 \\
\hline
\multirow{4}{*}{21} & L & 2232 & 1352 & 929 & 572 \\*
& M & 1708 & 1035 & 711 & 438 \\*
& Q & 1224 & 742 & 509 & 314 \\*
& H & 969 & 587 & 403 & 248 \\
\hline
\multirow{4}{*}{22} & L & 2409 & 1460 & 1003 & 618 \\*
& M & 1872 & 1134 & 779 & 480 \\*
& Q & 1358 & 823 & 565 & 348 \\*
& H & 1056 & 640 & 439 & 270 \\
\hline
\multirow{4}{*}{23} & L & 2620 & 1588 & 1091 & 672 \\*
& M & 2059 & 1248 & 857 & 528 \\*
& Q & 1468 & 890 & 611 & 376 \\*
& H & 1108 & 672 & 461 & 284 \\
\hline
\multirow{4}{*}{24} & L & 2812 & 1704 & 1171 & 721 \\*
& M & 2188 & 1326 & 911 & 561 \\*
& Q & 1588 & 963 & 661 & 407 \\*
& H & 1228 & 744 & 511 & 315 \\
\hline
\multirow{4}{*}{25} & L & 3057 & 1853 & 1273 & 784 \\*
& M & 2395 & 1451 & 997 & 614 \\*
& Q & 1718 & 1041 & 715 & 440 \\*
& H & 1286 & 779 & 535 & 330 \\
\hline
\multirow{4}{*}{26} & L & 3283 & 1990 & 1367 & 842 \\*
& M & 2544 & 1542 & 1059 & 652 \\*
& Q & 1804 & 1094 & 751 & 462 \\*
& H & 1425 & 864 & 593 & 365 \\
\hline
\multirow{4}{*}{27} & L & 3517 & 2132 & 1465 & 902 \\*
& M & 2701 & 1637 & 1125 & 692 \\*
& Q & 1933 & 1172 & 805 & 496 \\*
& H & 1501 & 910 & 625 & 385 \\
\hline
\multirow{4}{*}{28} & L & 3669 & 2223 & 1528 & 940 \\*
& M & 2857 & 1732 & 1190 & 732 \\*
& Q & 2085 & 1263 & 868 & 534 \\*
& H & 1581 & 958 & 658 & 405 \\
\hline
\multirow{4}{*}{29} & L & 3909 & 2369 & 1628 & 1002 \\*
& M & 3035 & 1839 & 1264 & 778 \\*
& Q & 2181 & 1322 & 908 & 559 \\*
& H & 1677 & 1016 & 698 & 430 \\
\hline
\multirow{4}{*}{30} & L & 4158 & 2520 & 1732 & 1066 \\*
& M & 3289 & 1994 & 1370 & 843 \\*
& Q & 2358 & 1429 & 982 & 604 \\*
& H & 1782 & 1080 & 742 & 457 \\
\hline
\multirow{4}{*}{31} & L & 4417 & 2677 & 1840 & 1132 \\*
& M & 3486 & 2113 & 1452 & 894 \\*
& Q & 2473 & 1499 & 1030 & 634 \\*
& H & 1897 & 1150 & 790 & 486 \\
\hline
\multirow{4}{*}{32} & L & 4686 & 2840 & 1952 & 1201 \\*
& M & 3693 & 2238 & 1538 & 947 \\*
& Q & 2670 & 1618 & 1112 & 684 \\*
& H & 2022 & 1226 & 842 & 518 \\
\hline
\multirow{4}{*}{33} & L & 4965 & 3009 & 2068 & 1273 \\*
& M & 3909 & 2369 & 1628 & 1002 \\*
& Q & 2805 & 1700 & 1168 & 719 \\*
& H & 2157 & 1307 & 898 & 553 \\
\hline
\multirow{4}{*}{34} & L & 5253 & 3183 & 2188 & 1347 \\*
& M & 4134 & 2506 & 1722 & 1060 \\*
& Q & 2949 & 1787 & 1228 & 756 \\*
& H & 2301 & 1394 & 958 & 590 \\
\hline
\multirow{4}{*}{35} & L & 5529 & 3351 & 2303 & 1417 \\*
& M & 4343 & 2632 & 1809 & 1113 \\*
& Q & 3081 & 1867 & 1283 & 790 \\*
& H & 2361 & 1431 & 983 & 605 \\
\hline
\multirow{4}{*}{36} & L & 5836 & 3537 & 2431 & 1496 \\*
& M & 4588 & 2780 & 1911 & 1176 \\*
& Q & 3244 & 1966 & 1351 & 832 \\*
& H & 2524 & 1530 & 1051 & 647 \\
\hline
\multirow{4}{*}{37} & L & 6153 & 3729 & 2563 & 1577 \\*
& M & 4775 & 2894 & 1989 & 1224 \\*
& Q & 3417 & 2071 & 1423 & 876 \\*
& H & 2625 & 1591 & 1093 & 673 \\
\hline
\multirow{4}{*}{38} & L & 6479 & 3927 & 2699 & 1661 \\*
& M & 5039 & 3054 & 2099 & 1292 \\*
& Q & 3599 & 2181 & 1499 & 923 \\*
& H & 2735 & 1658 & 1139 & 701 \\
\hline
\multirow{4}{*}{39} & L & 6743 & 4087 & 2809 & 1729 \\*
& M & 5313 & 3220 & 2213 & 1362 \\*
& Q & 3791 & 2298 & 1579 & 972 \\*
& H & 2927 & 1774 & 1219 & 750 \\
\hline
\multirow{4}{*}{40} & L & 7089 & 4296 & 2953 & 1817 \\*
& M & 5596 & 3391 & 2331 & 1435 \\*
& Q & 3993 & 2420 & 1663 & 1024 \\*
& H & 3057 & 1852 & 1273 & 784 \\
\hline
\end{longtabu}
\end{center}
\def\arraystretch{1}

324
latex/appendix.tex Normal file
View File

@ -0,0 +1,324 @@
\appendix
\chapter{Python code base display module}
\label{app:base_py}
\textbf{\emph{All figures, \LaTeX\ and Python files are available on the dedicated \hreffn{https://github.com/LordBaryhobal/5D\_Heredero\_Louis\_TM2022}{GitHub repository}}}
\vspace{12pt}
This module is used by the other generator scripts to display codes.
\begin{tcolorbox}[breakable,colback=white,title=base.py]
\begin{minted}{python}
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This module provides a base class to display codes and enable saving
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
import pygame
class Base:
def __init__(self, width, height, caption):
pygame.init()
pygame.display.set_caption(caption)
self.w = pygame.display.set_mode([width, height])
self.controls([
"CTRL + S: save as",
"ESC: quit"
])
def controls(self, controls, margin=2):
longest = max(list(map(len, controls))+[10])
print("┌─" + "─"*(longest+margin) + "─┐")
_ = "\x1b[1;4mControls:\x1b[0m"
_ += " "*(longest+margin-9)
print(f"│ " + _ + " │")
for c in controls:
print("│ " + " "*margin + c.ljust(longest) + " │")
print("└─" + "─"*(longest+margin) + "─┘")
def main(self):
pygame.display.flip()
stop = False
while not stop:
event = pygame.event.wait()
# ESC or close button -> quit
if event.type == pygame.QUIT:
stop = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
stop = True
# CTRL+S -> save image
elif event.key == pygame.K_s and \
event.mod & pygame.KMOD_CTRL:
self.save()
def save(self):
path = input("Save as: ")
pygame.image.save(self.w, path)
\end{minted}
\end{tcolorbox}
\chapter{Code 39 python implementation}
\label{app:code39_py}
\begin{tcolorbox}[breakable,colback=white,title=code39.py]
\begin{minted}{python}
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This module can generate Code-39 barcodes
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
import pygame
code39_dict = {
"A": "100001001", "B": "001001001",
"C": "101001000", "D": "000011001",
"E": "100011000", "F": "001011000",
"G": "000001101", "H": "100001100",
"I": "001001100", "J": "000011100",
"K": "100000011", "L": "001000011",
"M": "101000010", "N": "000010011",
"O": "100010010", "P": "001010010",
"Q": "000000111", "R": "100000110",
"S": "001000110", "T": "000010110",
"U": "110000001", "V": "011000001",
"W": "111000000", "X": "010010001",
"Y": "110010000", "Z": "011010000",
"0": "000110100", "1": "100100001",
"2": "001100001", "3": "101100000",
"4": "000110001", "5": "100110000",
"6": "001110000", "7": "000100101",
"8": "100100100", "9": "001100100",
" ": "011000100", "-": "010000101",
"$": "010101000", "%": "000101010",
".": "110000100", "/": "010100010",
"+": "010001010", "*": "010010100"
}
def code39(text):
text = text.upper()
text = list(map(lambda c: code39_dict[c], text))
return "0".join(text)
def draw_barcode(barcode, win):
barcode = list(map(int, barcode))
width = win.get_width()*0.8
height = win.get_height()*0.5
thicks = sum(barcode)
thins = len(barcode)-thicks
bar_w = width/(thicks*2+thins)
win.fill((255,255,255))
x = win.get_width()*0.1
y = win.get_height()*0.25
for i, c in enumerate(barcode):
w = 2*bar_w if c else bar_w
if i%2 == 0:
pygame.draw.rect(win, (0,0,0), [x, y, w, height])
x += w
if __name__ == "__main__":
import base
b = base.Base(800, 500, "Code-39 barcode generator")
barcode = code39("*CODE-39*")
draw_barcode(barcode, b.w)
b.main()
\end{minted}
\end{tcolorbox}
\chapter{EAN python implementation}
\label{app:ean_py}
\begin{tcolorbox}[breakable,colback=white,title=ean.py]
\begin{minted}{python}
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This module can generate EAN-8 and EAN-13 barcodes
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
import pygame
A = [
0b0001101,
0b0011001,
0b0010011,
0b0111101,
0b0100011,
0b0110001,
0b0101111,
0b0111011,
0b0110111,
0b0001011
]
# XOR 0b1111111
C = list(map(lambda a: a^127, A))
# Reverse bit order
B = list(map(lambda c: int(f"{c:07b}"[::-1],2), C))
ean13_patterns = [
"AAAAAA",
"AABABB",
"AABBAB",
"AABBBA",
"ABAABB",
"ABBAAB",
"ABBBAA",
"ABABAB",
"ABABBA",
"ABBABA"
]
def bin_list(n):
return list(map(int,f"{n:07b}"))
def luhn(digits):
checksum = sum([
digits[-i-1]*(3-i%2*2)
for i in range(len(digits))
])
ctrl_key = 10 - checksum%10
if ctrl_key == 10:
ctrl_key = 0
return ctrl_key
def ean8(digits):
digits.append(luhn(digits))
elmts = []
elmts += [1,0,1] #delimiter
for digit in digits[:4]:
elmts += bin_list(A[digit])
elmts += [0,1,0,1,0] #middle delimiter
for digit in digits[4:]:
elmts += bin_list(C[digit])
elmts += [1,0,1] #delimiter
return elmts
def ean13(digits):
pattern = ean13_patterns[digits[0]]
digits.append(luhn(digits))
elmts = []
elmts += [1,0,1] #delimiter
for d in range(1,7):
_ = A if pattern[d-1] == "A" else B
digit = digits[d]
elmts += bin_list(_[digit])
elmts += [0,1,0,1,0] #middle delimiter
for digit in digits[7:]:
elmts += bin_list(C[digit])
elmts += [1,0,1] #delimiter
return elmts
def draw_barcode(barcode, win):
width = win.get_width()*0.8
height = win.get_height()*0.5
bar_w = width/len(barcode)
rnd_bar_w = round(bar_w)
win.fill((255,255,255))
x = win.get_width()*0.1
y = win.get_height()*0.25
for c in barcode:
if c:
pygame.draw.rect(win, (0,0,0), [x, y, rnd_bar_w, height])
x += bar_w
if __name__ == "__main__":
import base
b = base.Base(800, 500, "EAN-8 / EAN-13 barcode generator")
#barcode = ean8([8,4,2,7,3,7,2])
barcode = ean13([9,7,8,2,9,4,0,6,2,1,0,5])
draw_barcode(barcode, b.w)
b.main()
\end{minted}
\end{tcolorbox}
\chapter{QR-Code tables}
\label{app:qr_tabs}
\def\arraystretch{1.2}
\begin{table}[H]
\centering
\begin{tabu}{|[2pt]c|c|[2pt]c|c|[2pt]c|c|[2pt]}
\tabucline[2pt]{-}
Index & Char & Index & Char & Index & Char \\
\hline
0 & 0 & 15 & F & 30 & U \\
\hline
1 & 1 & 16 & G & 31 & V \\
\hline
2 & 2 & 17 & H & 32 & W \\
\hline
3 & 3 & 18 & I & 33 & X \\
\hline
4 & 4 & 19 & J & 34 & Y \\
\hline
5 & 5 & 20 & K & 35 & Z \\
\hline
6 & 6 & 21 & L & 36 & \emph{space} \\
\hline
7 & 7 & 22 & M & 37 & \$ \\
\hline
8 & 8 & 23 & N & 38 & \% \\
\hline
9 & 9 & 24 & O & 39 & * \\
\hline
10 & A & 25 & P & 40 & + \\
\hline
11 & B & 26 & Q & 41 & - \\
\hline
12 & C & 27 & R & 42 & . \\
\hline
13 & D & 28 & S & 43 & / \\
\hline
14 & E & 29 & T & 44 & : \\
\tabucline[2pt]{-}
\end{tabu}
\caption{List of alphanumerical characters}
\label{tab:qr_alphanum}
\end{table}
\def\arraystretch{1}
\input{appendices/qr_versions}
% Rotated headers
% https://tex.stackexchange.com/a/98439
\input{appendices/error_correction}
\input{appendices/alignment}

View File

@ -0,0 +1,398 @@
\section{How it works}
\label{sec:barcode_functioning}
In barcodes, black and white stripes encode data visually. For certain types of codes, they represent 1s and 0s, while for other types such as Code-39, it is the width of the stripes which determines the value. The following sections will explain more thoroughly some of the most used types of barcodes.
\subsection{Code-39}
\label{ssec:code39}
First of all, there is Code-39, one of the simplest type of barcode. It can encode 43 different alphanumerical characters plus the special '*' delimiter. Each character is assigned a particular group of 9 bits, 3 of which are 1s, hence the name. Table \ref{tab:code39} lists all encodable characters and their respective codes.
\def\arraystretch{1.5}
\begin{table}[H]
\centering
\begin{tabu}{|[2pt]c|c|[2pt]c|c|[2pt]c|c|[2pt]}
\tabucline[2pt]{-}
Char & Code & Char & Code & Char & Code \\
\tabucline[2pt]{-}
A & 100001001 & P & 001010010 & 4 & 000110001 \\
\hline
B & 001001001 & Q & 000000111 & 5 & 100110000 \\
\hline
C & 101001000 & R & 100000110 & 6 & 001110000 \\
\hline
D & 000011001 & S & 001000110 & 7 & 000100101 \\
\hline
E & 100011000 & T & 000010110 & 8 & 100100100 \\
\hline
F & 001011000 & U & 110000001 & 9 & 001100100 \\
\hline
G & 000001101 & V & 011000001 & \emph{space} & 011000100 \\
\hline
H & 100001100 & W & 111000000 & - & 010000101 \\
\hline
I & 001001100 & X & 010010001 & \$ & 010101000 \\
\hline
J & 000011100 & Y & 110010000 & \% & 000101010 \\
\hline
K & 100000011 & Z & 011010000 & . & 110000100 \\
\hline
L & 001000011 & 0 & 000110100 & / & 010100010 \\
\hline
M & 101000010 & 1 & 100100001 & + & 010001010 \\
\hline
N & 000010011 & 2 & 001100001 & * & 010010100 \\
\hline
O & 100010010 & 3 & 101100000 & & \\
\tabucline[2pt]{-}
\end{tabu}
\caption{Code-39 characters}
\label{tab:code39}
\end{table}
\def\arraystretch{1}
To create a Code-39 barcode, we just have to take the codes corresponding to each character and join them end to end, adding a 0 in between each group. Finally, we add the '*' character at both ends (also spaced by a 0). It is a delimiter and should always be present at the beginning and end of the code, to signal scanners that it is a Code-39 barcode, as well as providing a reference for the normal bar width.
Ones represent wide bars and zeros thin bars\footnote{The ratio wide/thin must be comprised between 2:1 and 3:1 \cite{ISO16388}}. Black and white stripes alternate, starting with black.
Let's encode the string "CODE-39"
\def\arraystretch{1.5}
\begin{table}[H]
\centering
\begin{tabu}{|[2pt]c|c|[2pt]}
\tabucline[2pt]{-}
Char & Code \\
\tabucline[2pt]{-}
* & 010010100 \\
\hline
C & 101001000 \\
\hline
O & 100010010 \\
\hline
D & 000011001 \\
\hline
E & 100011000 \\
\hline
- & 010000101 \\
\hline
3 & 101100000 \\
\hline
9 & 001100100 \\
\hline
* & 010010100 \\
\tabucline[2pt]{-}
\end{tabu}
\caption{Code-39 for "*CODE-39*"}
\label{tab:code39_ex_codes}
\end{table}
\def\arraystretch{1}
For example, the first delimiter is encoded as:
\begin{center}
%\begin{tabular}{p{10pt}p{10pt}p{10pt}p{10pt}p{10pt}p{10pt}p{10pt}p{10pt}p{10pt}p{10pt}p{10pt}p{10pt}}
%\bbar & & & \bbar & & \bbar & \bbar & & \bbar & \bbar & & \bbar \\
%0 & \multicolumn{2}{c}{1} & 0 & 0 & \multicolumn{2}{c}{1} & 0 & \multicolumn{2}{c}{1} & 0 & 0 \\
\begin{tabular}{C{10pt}C{20pt}C{10pt}C{10pt}C{20pt}C{10pt}C{20pt}C{10pt}C{10pt}}
\bbar & & \bbar & & \bbar & & \bbar & & \bbar \\
0 & 1 & 0 & 0 & 1 & 0 & 1 & 0 & 0 \\
\end{tabular}
\end{center}
The entire barcode would look like figure \ref{fig:code_39_ex}
\begin{figure}[H]
\centering
\includegraphics[width=0.5\linewidth]{images/code_39_example}
\caption{Code-39 example ("\texttt{*CODE-39*}")}
\label{fig:code_39_ex}
\end{figure}
Code-39 doesn't provide any error detection mechanism. If a character is read as another character, the reading device won't know that it made a mistake. Additionally, it is not the most compact type of encoding, but it can easily be used for small amounts of data, for example to encode the identification number on students' absence booklet or sheet.
\subsection{EAN}
\label{ssec:ean}
Another barcode type is the EAN\footnote{European Article Numbering} standard. It is used for product identification and can be found on anything bought from a store. It exists in two main formats: EAN-8 and EAN-13, encoding respectively 7 and 12 digits. EAN codes use what is called a check digit to detect erroneous readings. This digit is the result of the Luhn Formula. Say we want to encode the number \texttt{978294062105}. To find the checksum digit, we have to multiply each digit by the alternating factors 1 and 3, and add all the products.
\def\arraystretch{1.5}
\begin{table}[H]
\centering
\begin{tabu}{c|c|c|c|c|c|c|c|c|c|c|c|c|c}
%\hline
& 9 & 7 & 8 & 2 & 9 & 4 & 0 & 6 & 2 & 1 & 0 & 5 & \\
%\hline
x & 1 & 3 & 1 & 3 & 1 & 3 & 1 & 3 & 1 & 3 & 1 & 3 & \\
\tabucline[2pt]{-}
+ & 9 &21 & 8 & 6 & 9 &12 & 0 &18 & 2 & 3 & 0 &15 & = 103 \\
%\hline
\end{tabu}
\caption{Luhn Formula}
\label{tab:luhn}
\end{table}
\def\arraystretch{1}
We then take the modulo ten, which is the same as saying we keep the unit's digit, and subtract it from $10$. In our example, $103 \equiv 3\ (\textrm{mod}\ 10)$ so the checksum is $10 - 3 = 7$. If we get $10$ we change it to $0$ to keep a single digit.
This is now the 13th digit of our code. For an EAN-8 code, the process is the same with the factors 1 and 3 inverted, meaning the first digit is multiplied by 3, the second by 1, etc.
The barcode itself is built out of a series of "elements".
Each element encodes a character, as described by table \ref{tab:ean_elmts}.
Ones are black stripes and zeros are white.
\def\arraystretch{1.5}
\begin{table}[h]
\centering
\begin{tabu}{|[2pt]c|c|c|c|[2pt]}
\tabucline[2pt]{-}
Char. & A & B & C \\
\tabucline[2pt]{-}
0 & 0001101 & 0100111 & 1110010 \\
\hline
1 & 0011001 & 0110011 & 1100110 \\
\hline
2 & 0010011 & 0110011 & 1101100 \\
\hline
3 & 0111101 & 0110011 & 1000010 \\
\hline
4 & 0100011 & 0110011 & 1011100 \\
\hline
5 & 0110001 & 0110011 & 1001110 \\
\hline
6 & 0101111 & 0110011 & 1010000 \\
\hline
7 & 0111011 & 0110011 & 1000100 \\
\hline
8 & 0110111 & 0110011 & 1001000 \\
\hline
9 & 0001011 & 0110011 & 1110100 \\
\tabucline[2pt]{-}
\end{tabu}
\caption{EAN elements}
\label{tab:ean_elmts}
\end{table}
\def\arraystretch{1}
There are 3 types of elements: A, B and C. Type C is obtained by swapping 1s and 0s from type A. Type B is obtained by flipping type C from left to right.
\begin{comment}
For example:
\def\arraystretch{1.5}
\begin{table}[h]
\centering
\begin{tabu}{|[2pt]c|c|c|c|[2pt]}
\tabucline[2pt]{-}
Char. & A & B & C \\
\tabucline[2pt]{-}
0 & 0001101 & 0100111 & 1110010 \\
\hline
1 & 0011001 & 0110011 & 1100110 \\
\tabucline[2pt]{-}
\end{tabu}
\caption{Example of EAN elements}
\label{tab:ean_elmt_ex}
\end{table}
\def\arraystretch{1}
\end{comment}
This structure makes it easy to find the type of a certain element.
Bs and Cs have an even number of 1s, while As have an odd number. Additionally, As an Bs start with 0 and end with 1, whereas Cs are the opposite.
In this way, if the code is read in the wrong way, C elements will appear as Bs, and A elements will be invalid because no element starts with a 1 and has an odd number of 1s. Similarly, if the barcode is printed with inverted colors (white on black), A elements will be read as Cs, and B elements will be invalid, since no element starts with a 1 and has an odd number of 1s.
EAN barcodes are thus very practical since they can be scanned in any orientation and support color inversion.
\subsubsection{EAN-8}
\label{sss:ean8}
An EAN-8 barcode has the following structure:
\def\arraystretch{1.5}
\begin{table}[H]
\centering
\begin{tabu}{|[2pt]c|c|c|c|c|c|c|c|c|c|c|[2pt]}
\tabucline[2pt]{-}
left & A & A & A & A & mid & C & C & C & C & right\\
\tabucline[2pt]{-}
\end{tabu}
\caption{EAN-8 structure}
\label{tab:ean8_struct}
\end{table}
\def\arraystretch{1}
In table \ref{tab:ean8_struct}, "left" and "right" are the end delimiters "101". "mid" is the center delimiter "01010".
To illustrate, let's encode the value "8427372".
\begin{enumerate}
\item Calculate the checksum digit:
\def\arraystretch{1.5}
\begin{table}[H]
\centering
\begin{tabu}{c|c|c|c|c|c|c|c|c}
%\hline
& 8 & 4 & 2 & 7 & 3 & 7 & 2 & \\
%\hline
x & 3 & 1 & 3 & 1 & 3 & 1 & 3 & \\
\tabucline[2pt]{-}
+ &24 & 4 & 6 & 7 & 9 & 7 & 6 & = 63 \\
%\hline
\end{tabu}
\caption{Luhn Formula (EAN-8 example)}
\label{tab:luhn_ean8_ex}
\end{table}
\def\arraystretch{1}
Thus the last digit is $10 - (63 \textrm{ mod } 10) = 10 - 3 = 7$.
%\item Take the corresponding element for each digit (As for the first 4, Cs for the last 4)
\item Take each digit's corresponding element: As for the first 4, Cs for the rest (table \ref{tab:ean8_elmt_ex}).
\item Add the delimiters "101" at each end and "01010" between both halves of the code.
\item For each 1, draw a black bar and for each 0 a white one (figure \ref{fig:ean8_ex}).
\end{enumerate}
\begin{minipage}[b]{\textwidth}
\centering
\begin{minipage}[c]{0.26\linewidth}
\centering
\def\arraystretch{1.5}
\begin{tabu}{|[2pt]c|c|[2pt]}
\tabucline[2pt]{-}
Char. & Element \\
\tabucline[2pt]{-}
8 & 0110111 \\
\hline
4 & 0100011 \\
\hline
2 & 0010011 \\
\hline
7 & 0111011 \\
\tabucline[1pt]{-}
3 & 1000010 \\
\hline
7 & 1000100 \\
\hline
2 & 1101100 \\
\hline
7 & 1000100 \\
\tabucline[2pt]{-}
\end{tabu}
\def\arraystretch{1}
\captionof{table}{EAN-8 example elements}
\label{tab:ean8_elmt_ex}
\end{minipage}
\hfill
\begin{minipage}[c]{0.7\linewidth}
\centering
\includegraphics[width=\linewidth]{images/ean8_example_2}
\captionof{figure}{EAN-8 example ("\texttt{84273727}")}
\label{fig:ean8_ex}
\end{minipage}
\end{minipage}
\subsubsection{EAN-13}
\label{sss:ean13}
EAN-13 follows the same principles as EAN-8. The structure of such a code is the following:
\def\arraystretch{1.5}
\begin{table}[H]
\centering
\begin{tabu}{|[2pt]c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|[2pt]}
\tabucline[2pt]{-}
\multirow{2}{*}{left} & A & A & A & A & A & A & \multirow{2}{*}{mid} & \multirow{2}{*}{C} & \multirow{2}{*}{C} & \multirow{2}{*}{C} & \multirow{2}{*}{C} & \multirow{2}{*}{C} & \multirow{2}{*}{C} & \multirow{2}{*}{right}\\
\cline{2-7}
& B & B & B & B & B & B & & & & & & & & \\
\tabucline[2pt]{-}
\end{tabu}
\caption{EAN-13 structure}
\label{tab:ean13_struct}
\end{table}
\def\arraystretch{1}
This has only 12 places for elements, 6 A/B and 6 C. The 13th digit (in reality the first) is encoded in the pattern of A and B elements. Here is the list of patterns corresponding to each digit:
\def\arraystretch{1.5}
\begin{table}[H]
\centering
\begin{tabu}{|[2pt]c|c|[2pt]c|c|[2pt]}
\tabucline[2pt]{-}
Digit & Pattern & Digit & Pattern\\
\tabucline[2pt]{-}
0 & AAAAAA & 5 & ABBAAB \\
\hline
1 & AABABB & 6 & ABBBAA \\
\hline
2 & AABBAB & 7 & ABABAB \\
\hline
3 & AABBBA & 8 & ABABBA \\
\hline
4 & ABAABB & 9 & ABBABA \\
\tabucline[2pt]{-}
\end{tabu}
\caption{EAN-13 1st digit patterns}
\label{tab:ean13_patterns}
\end{table}
\def\arraystretch{1}
It can be noticed that the first element is always an A so that reading direction can easily be determined.
Let's illustrate EAN-13 by encoding the value "978294062105".
\begin{enumerate}
\item Calculate the checksum digit:
\def\arraystretch{1.5}
\begin{table}[H]
\centering
\begin{tabu}{c|c|c|c|c|c|c|c|c|c|c|c|c|c}
%\hline
& 9 & 7 & 8 & 2 & 9 & 4 & 0 & 6 & 2 & 1 & 0 & 5 & \\
%\hline
x & 1 & 3 & 1 & 3 & 1 & 3 & 1 & 3 & 1 & 3 & 1 & 3 & \\
\tabucline[2pt]{-}
+ & 9 &21 & 8 & 6 & 9 &12 & 0 &18 & 2 & 3 & 0 &15 & = 103 \\
%\hline
\end{tabu}
\caption{Luhn Formula (EAN-13 example)}
\label{tab:luhn_ean13_ex}
\end{table}
\def\arraystretch{1}
Thus the last digit is $10 - (103 \textrm{ mod } 10) = 10 - 3 = 7$.
\item Get the pattern corresponding to the first digit: 9 $\rightarrow$ ABBABA
\item Take the corresponding element for each digit
\def\arraystretch{1.5}
\begin{table}[H]
\centering
\begin{tabu}{|[2pt]c|c|c|[2pt]|[2pt]c|c|c|[2pt]}
\tabucline[2pt]{-}
Char. & Type & Element & Char. & Type & Element \\
\tabucline[2pt]{-}
7 & A & 0111011 & 6 & C & 1010000\\
\hline
8 & B & 0001001 & 2 & C & 1101100\\
\hline
2 & B & 0011011 & 1 & C & 1100110\\
\hline
9 & A & 0001011 & 0 & C & 1110010\\
\hline
4 & B & 0011101 & 5 & C & 1001110\\
\hline
0 & A & 0001101 & 7 & C & 1000100\\
\tabucline[2pt]{-}
\end{tabu}
\caption{EAN-13 example elements}
\label{tab:ean13_elmt_ex}
\end{table}
\def\arraystretch{1}
\item Add the delimiters "101" at each end and "01010" between both halves of the code.
\item For each 1, draw a black bar and for each 0 a white one.
\begin{figure}[H]
\centering
\includegraphics[width=0.5\linewidth]{images/ean13_example}
\caption{EAN-13 example ("\texttt{9782940621057}")}
\label{fig:ean13_ex}
\end{figure}
\end{enumerate}

14
latex/barcode_origin.tex Normal file
View File

@ -0,0 +1,14 @@
\chapter{Barcodes}
\label{chap:barcode_origin}
\section{Origin}
\label{sec:barcode_origin}
The patent for the first barcode\cite{barcode_patent} was filed slightly more than 70 years ago, in 1949, by then Drexel University students Norman J. Woodland and Bernard Silver. Officially established in 1952, this patent described the first barcode, a reading device and a circular design. However, success did not strike immediately, mainly because of the impractical and limited resources of the time.
Its first use was on trains, as an identification system (KarTrak\cite{kartrak}). It was soon abandonned however because of readability and maintenance problems.
A few years later, in the 1970s, IBM, which now counted Mr. Woodland as an associate, had developed the linear UPC barcode. The Universal Product Code is still in use today, with some modifications and standardizations. It was and is used in stores to identify groceries. This kind of tagging allowed for shorter waits at registers when checking out and greatly influenced the capitalist society we live in nowadays. As well as improving time efficiency, it also reduced the number of human errors which could happen when manually entering the prices of items bought by customers.
Since then, many other types of barcodes have been created for more specialized purposes, like postal mail, warehouse inventories, libraries or medicines. The main advantage of barcodes is that they can be read very quickly with a single laser scan. The first barcodes didn't even need a computer and could be decoded with only relatively simple electronic circuits \cite[p.3]{barcode_patent}.
In 1974, the GS1 was founded. This international group is responsible for managing encoding standards in the field of logistics and sale of goods.

235
latex/barcode_python.tex Normal file
View File

@ -0,0 +1,235 @@
\section{Application in Python}
\label{sec:barcode_python}
In this section, we will implement barcode generation in Python. We will first program a "Code-39" encoder, then an EAN-8 and finally an EAN-13.
\subsection{Code-39}
\label{ssec:code39_py}
This type of code being just a matter of translating each character to a particular group of wide and narrow stripes, the implementation is quite simple.
We first create a dictionary holding the codes for each character.
\begin{minted}[frame=single]{python}
code39_dict = {
"A": "100001001", "B": "001001001",
"C": "101001000", "D": "000011001",
"E": "100011000", "F": "001011000",
"G": "000001101", "H": "100001100",
"I": "001001100", "J": "000011100",
"K": "100000011", "L": "001000011",
"M": "101000010", "N": "000010011",
"O": "100010010", "P": "001010010",
"Q": "000000111", "R": "100000110",
"S": "001000110", "T": "000010110",
"U": "110000001", "V": "011000001",
"W": "111000000", "X": "010010001",
"Y": "110010000", "Z": "011010000",
"0": "000110100", "1": "100100001",
"2": "001100001", "3": "101100000",
"4": "000110001", "5": "100110000",
"6": "001110000", "7": "000100101",
"8": "100100100", "9": "001100100",
" ": "011000100", "-": "010000101",
"$": "010101000", "%": "000101010",
".": "110000100", "/": "010100010",
"+": "010001010", "*": "010010100"
}
\end{minted}
To convert a string, we map each character to its corresponding binary representation and join the resulting codes with "0" in between.
\begin{minted}[frame=single]{python}
def code39(text):
text = text.upper()
text = map(lambda c: code39_dict[c], text)
return "0".join(text)
\end{minted}
We will also need a function to render the barcode. For this, we will use the Pygame module.
\begin{minted}[frame=single]{python}
def draw_barcode(barcode, win):
barcode = list(map(int, barcode))
width = win.get_width()*0.8
height = win.get_height()*0.5
thicks = sum(barcode)
thins = len(barcode)-thicks
bar_w = width/(thicks*2+thins)
win.fill((255,255,255))
x = win.get_width()*0.1
y = win.get_height()*0.25
for i, c in enumerate(barcode):
w = 2*bar_w if c else bar_w
if i%2 == 0:
pygame.draw.rect(win, (0,0,0), [x, y, w, height])
x += w
\end{minted}
The full python script can be found in appendix \ref{app:code39_py} or on \hreffn{https://github.com/LordBaryhobal/5D\_Heredero\_Louis\_TM2022/blob/main/python/code39.py}{GitHub}.
\subsection{EAN-8}
\label{ssec:ean8_py}
The first step to create an EAN-8 barcode is to compute the check digit with Luhn's formula.
To make this function also usable for EAN-13, we need to redefine the formula as such:
\begin{enumerate}
\item Multiply each digit by the alternating factors 1 and 3 starting with 3 \textbf{from the end}.
\item Add them together then take the modulo ten and subtract the result from 10.
\item If the result is equal to 10, change it to 0.
\end{enumerate}
%Since this function will also be used for EAN-13, with only the factors changing, we will make it general enough.
In python, the function multiplies the i\textsuperscript{th} to last digit by: \[
\text{factor} = 3 - (i\ mod\ 2) * 2
\]
which basicly is step 1 above.
\def\arraystretch{1.5}
\begin{table}[H]
\centering
\begin{tabu}{|[2pt]c|[2pt]c|c|c|c|c|c|[2pt]}
\tabucline[2pt]{-}
i & ... & 4 & 3 & 2 & 1 & 0 \\
\hline
factor & ... & 3 & 1 & 3 & 1 & 3 \\
\tabucline[2pt]{-}
\end{tabu}
\caption{Python Luhn formula example}
\label{tab:luhn_py_ex}
\end{table}
\def\arraystretch{1}
%\begin{minipage}{\linewidth}
\begin{minted}[frame=single]{python}
def luhn(digits):
checksum = sum([
digits[-i-1]*(3-i%2*2)
for i in range(len(digits))
])
ctrl_key = 10 - checksum%10
if ctrl_key == 10:
ctrl_key = 0
return ctrl_key
\end{minted}
%\end{minipage}
Both code types also need the table of elements:
\begin{minted}[frame=single]{python}
A = [
0b0001101,
0b0011001,
0b0010011,
0b0111101,
0b0100011,
0b0110001,
0b0101111,
0b0111011,
0b0110111,
0b0001011
]
# XOR 0b1111111
C = list(map(lambda a: a^127, A))
# Reverse bit order
B = list(map(lambda c: int(f"{c:07b}"[::-1], 2), C))
\end{minted}
The following function converts a number to the list of its bits:
\begin{minted}[frame=single]{python}
def bin_list(n):
return list(map(int, f"{n:07b}"))
\end{minted}
Finally, the encoding function:
\begin{minted}[frame=single]{python}
def ean8(digits):
digits.append(luhn(digits))
elmts = []
elmts += [1,0,1] #delimiter
for digit in digits[:4]:
elmts += bin_list(A[digit])
elmts += [0,1,0,1,0] #middle delimiter
for digit in digits[4:]:
elmts += bin_list(C[digit])
elmts += [1,0,1] #delimiter
return elmts
\end{minted}
We will use a similar function as in \autoref{ssec:code39_py} to render the barcode
\begin{minted}[frame=single]{python}
def draw_barcode(barcode, win):
width = win.get_width()*0.8
height = win.get_height()*0.5
bar_w = width/len(barcode)
win.fill((255,255,255))
x = win.get_width()*0.1
y = win.get_height()*0.25
for c in barcode:
if c:
pygame.draw.rect(win, (0,0,0), [x, y, bar_w, height])
x += bar_w
\end{minted}
The full python script can be found in appendix \ref{app:ean_py} or on \hreffn{https://github.com/LordBaryhobal/5D\_Heredero\_Louis\_TM2022/blob/main/python/ean.py}{GitHub}
\subsection{EAN-13}
\label{ssec:ean13_py}
The main difference with EAN-8 is the encoding of the first digit, using an A/B pattern. We will create a list of these patterns:
\begin{minted}[frame=single]{python}
ean13_patterns = [
"AAAAAA",
"AABABB",
"AABBAB",
"AABBBA",
"ABAABB",
"ABBAAB",
"ABBBAA",
"ABABAB",
"ABABBA",
"ABBABA"
]
\end{minted}
And the appropriate encoding function:
\begin{minted}[frame=single]{python}
def ean13(digits):
pattern = ean13_patterns[digits[0]]
digits.append(luhn(digits))
elmts = []
elmts += [1,0,1] #delimiter
for d in range(1,7):
_ = A if pattern[d-1] == "A" else B
digit = digits[d]
elmts += bin_list(_[digit])
elmts += [0,1,0,1,0] #middle delimiter
for digit in digits[7:]:
elmts += bin_list(C[digit])
elmts += [1,0,1] #delimiter
return elmts
\end{minted}
The full python script can be found in appendix \ref{app:ean_py} or on \hreffn{https://github.com/LordBaryhobal/5D\_Heredero\_Louis\_TM2022/blob/main/python/ean.py}{GitHub}

10
latex/bibliography.tex Normal file
View File

@ -0,0 +1,10 @@
%https://tex.stackexchange.com/a/98995
\cleardoublepage
\phantomsection
\addcontentsline{toc}{chapter}{Bibliography}
%\bibliographystyle{abbrv}
\printbibliography

10
latex/conclusion.tex Normal file
View File

@ -0,0 +1,10 @@
\chapter{Conclusion}
\label{chap:conclusion}
We have seen how engineers such as Woodland and Silver have built the basis for a barcode system that optimises many processes, and how these barcodes provide an easy, fast and reliable way of encoding data. These values, ease of use, speed and reliability, are most certainly what the development of new technologies is all about.
The sheer number of barcodes currently in use around the world is proof of their ingenuity.
Following the same success, QR-Codes conquered the world and became part of our daily lives as we encounter them everywhere.
Because they have become standard elements in our society, they are often disregarded and wrapped in mystery, like magical tags instantly recognizable by our devices. This work is obviously not an exhaustive list of all codes that exist. Many other types can be commonly found, such as PDF-417 or Aztec codes, both of which also use the Reed-Solomon algorithm for error-correction, or Postnet, a specialized type of barcodes used by the United States Postal Service.
The Lycacode described in \autoref{chap:custom_code} was primarily designed for education purposes, as a way to put in practice the formerly explained principles, but could well have a real application in the Collège.

500
latex/custom_code.tex Normal file
View File

@ -0,0 +1,500 @@
\chapter{Custom code}
\label{chap:custom_code}
%\emph{creation of a new type of 2D code, based on previously seen concepts}
In this chapter, we will create a new type of 2D code, based on concepts discussed in this work: the Lycacode.
Some design choices have been made for ease of use and others for aesthetic purposes, each explained in their relevant section.
The basic format is in the form of a trefoil cross, the blazon of Saint-Maurice and of the Collège de l'Abbaye.
\begin{figure}[H]
\centering
\includegraphics[width=0.4\textwidth]{images/lycacode_cross}
\includegraphics[width=0.4\textwidth]{images/lycacode_squares}
\caption{Lycacode: trefoil cross and squares}
\label{fig:lycacode_cross}
\end{figure}
%\begin{figure}[H]
% \centering
% \includegraphics[width=0.5\textwidth]{images/lycacode_cross}
% \caption{Custom code: trefoil cross}
% \label{fig:lycacode_cross}
%\end{figure}
This cross is split into 25 squares which hold data. Each square is made of a 3x3 grid of dots and blanks, representing bits. The right of figure \ref{fig:lycacode_cross} shows how data (white dots) is put in the squares (highlighted in black)\footnote{the black squares are simply visual aids and are not part of the final code}. A dot represents a 1 while the absence of one is a 0.
The decision of using a grid-like pattern is convient for data placement as well as for reading the code.
The central square is reserved for the orientation pattern, as described in \autoref{ssec:lycacode_ex_layout}.
Additionally, the three top- and bottom-most dots are saved for the mask id (see \autoref{ssec:lycacode_ex_mask}).
\section{Encoding}
\label{sec:lycacode_encoding}
This code can work in one of four modes:
\begin{enumerate}
\setcounter{enumi}{-1}
\item Person
\item Location
\item Link
\item Text
\end{enumerate}
\subsection{Person - mode 0}
\label{ssec:lycacode_mode0}
In mode 0, the code represents a person from the school. It can either be a student (type 0), a teacher (type 1) or someone else (type 2), such as the cleaning staff or caretaker. Table \ref{tab:lycacode_person} lists the different values encoded by each type and their corresponding bit size. Column "bit size" is the number of bits the value is encoded on. These have been chosen to use as few bits as possible to leave room for possible future additional data.
\def\arraystretch{1.5}
\begin{table}[H]
\centering
\begin{tabu}{|[2pt]l|l|c|[2pt]}
\tabucline[2pt]{-}
Type & Value & Bit size\\
\tabucline[1pt]{-}
\multirow{5}{*}{Student} & type & 2 \\
\cline{2-3}
& id & 20 \\
\cline{2-3}
& year & 3 \\
\cline{2-3}
& class & 4 \\
\cline{2-3}
& initials & 10 \\
\tabucline[1pt]{-}
\multirow{2}{*}{Teacher} & type & 2 \\
\cline{2-3}
& id & 20 \\
\tabucline[1pt]{-}
\multirow{2}{*}{Other} & type & 2 \\
\cline{2-3}
& id & 20 \\
\tabucline[2pt]{-}
\end{tabu}
\caption{Lycacode: person mode - values}
\label{tab:lycacode_person}
\end{table}
\def\arraystretch{1}
\textbf{For students}
\texttt{year} is the year number. DUBS is represented by the value 0.\\
\texttt{class} is the index of the class letter in the alphabet, starting from 0. For example, D is 3.\\
\texttt{initials} represent the initial of the firstname and that of the lastname, each as 5 bit numbers. The value of each letter is their index in the alphabet, starting from 0.
\paragraph{Example}
\begin{center}
\begin{tabular}{c|c|c|c|c}
type & id & year & class & initials \\
\hline
Student & 16048 & 5 & D & LH\\
\end{tabular}
Bits: 00 / 00 / 00000011111010110000 / 101 / 0011 / 01011 / 00111
\end{center}
\subsection{Location - mode 1}
\label{ssec:lycacode_mode1}
In mode 1, the code represents a location in the school. The section is encoded on 3 bits according to the following table:
\def\arraystretch{1.5}
\begin{table}[H]
\centering
\begin{tabu}{|[2pt]c|c|[2pt]}
\tabucline[2pt]{-}
Section & Value \\
\tabucline[1pt]{-}
A & 0 \\
\hline
B & 1 \\
\hline
C & 2 \\
\hline
D & 3 \\
\hline
Boarding school & 4 \\
\hline
"Bateau" & 5 \\
\hline
Sports alley & 6 \\
\hline
Football fields & 7 \\
\tabucline[2pt]{-}
\end{tabu}
\caption{Lycacode: location mode - sections}
\label{tab:lycacode_loc_sections}
\end{table}
\def\arraystretch{1}
Additionally the room number (or other id) is encoded on 9 bits.
\paragraph{Example}
\begin{center}
\begin{tabular}{c|c}
section & room \\
\hline
4 & 209 \\
\end{tabular}
Bits: 01 / 100 / 011010001
\end{center}
\subsection{Link - mode 2}
\label{ssec:lycacode_mode2}
In mode 2, the code represents a URL. The actual URLs are stored in a database and only the id is saved on the code as a 32 bit number. Scanners then fetch the URL info from the server's database.
\subsection{Text - mode 3}
\label{ssec:lycacode_mode3}
In mode 3, the code represents normal text. Text data is simply converted to UTF-8. The number of encoded characters is first encoded on 4 bits and added before text data. Due to its limited capacity, a Lycacode can only store up to 14 characters.
\paragraph{Example}
\begin{center}
\begin{tabular}{c|c}
length & text \\
\hline
4 & Lyca \\
\end{tabular}
Bits: 11 / 0100 / 01001100 / 01111001 / 01100011 / 01100001
\end{center}
\section{Error correction}
\label{sec:lycacode_err_corr}
It goes without saying that this code uses some kind of error correction. To keep it simple enough, Hamming(7, 4) codes have been chosen to fulfil this role. Encoded data is first padded to the maximum number of data bits $M$:
\[
M = T * R
\]
where $T$ is the total number of bits which can be encoded on the cross and $R$ is the ratio of data bits over blocksize (here, $R = \frac{4}{7}$). $T$ can be calculated as follows:
\[
T = \underbrace{3^2}_{\substack{\text{number of dots} \\ \text{in a square}}} * 24 - \underbrace{6}_{\text{mask id}}
\]
\section{Example}
\label{sec:lycacode_example}
Let's create a Lycacode to illustrate. We will make a student code using the values from the example in \autoref{ssec:lycacode_mode0}.
\subsection{Data encoding}
\label{ssec:lycacode_ex_encoding}
Table \ref{tab:lycacode_ex_values} lists all values to encode and their respective binary strings.
\def\arraystretch{1.5}
\begin{table}[H]
\centering
\begin{tabu}{|[2pt]l|c|l|[2pt]}
\tabucline[2pt]{-}
Property & Value & Binary\\
\tabucline[1pt]{-}
Mode & 0 & 00 \\
\hline
Type & 0 (=student) & 00 \\
\hline
Id & 16048 & 00000011111010110000 \\
\hline
Year & 5 & 101 \\
\hline
Class & 3 (=D) & 0011 \\
\hline
Initials & LH & 01011 00111 \\
\hline
\tabucline[2pt]{-}
\end{tabu}
\caption{Lycacode: example values}
\label{tab:lycacode_ex_values}
\end{table}
\def\arraystretch{1}
The raw data bit string is thus:
\[
\underbrace{00}_{\text{mode}} \underbrace{00}_{\text{type}} \underbrace{00000011111010110000}_{\text{id}} \underbrace{101}_{\text{year}} \underbrace{0011}_{\text{class}} \underbrace{0101100111}_{\text{initials}}
\]
We then need to pad it to fill the remaining free bits. First we pad with zeros to the nearest multiple of 4 (data bits per block).
Then we fill the rest with a pattern of consecutive binary numbers\footnote{the pattern is the series of natural numbers in binary starting from 0, e.g. 0, 1, 10, 11, 100, ...}, like this:
\[
01101110010111011110001001101010111100110111101111...
\]
%We then pad it on the right with zeros to fill the remaining free bits M=124 (see section \ref{sec:lycacode_err_corr}) and construct the Hamming codes:
This pattern has the sole purpose of adding pseudo-random data so that there is data on the whole code. This is only an aesthetic choice.
\pagebreak
\subsection{Hamming codes}
\label{ssec:lycacode_ex_hamming}
Finally we construct the Hamming codes:
\def\arraystretch{1.5}
\begin{table}[H]
\centering
\resizebox{0.4\textwidth}{!}{
\begin{tabu}{|[2pt]c|c|c|c|c|c|c|c|[2pt]}
\tabucline[2pt]{-}
& 1 & 2 & 3 & 4 & 5 & 6 & 7 \\
\tabucline[1pt]{-}
Group 1 & \_ & \_ & 0 & \_ & 0 & 0 & 0 \\
\hline
Group 2 & \_ & \_ & 0 & \_ & 0 & 0 & 0 \\
\hline
Group 3 & \_ & \_ & 0 & \_ & 0 & 1 & 1 \\
\hline
Group 4 & \_ & \_ & 1 & \_ & 1 & 1 & 0 \\
\hline
Group 5 & \_ & \_ & 1 & \_ & 0 & 1 & 1 \\
\hline
Group 6 & \_ & \_ & 0 & \_ & 0 & 0 & 0 \\
\hline
Group 7 & \_ & \_ & 1 & \_ & 0 & 1 & 0 \\
\hline
Group 8 & \_ & \_ & 0 & \_ & 1 & 1 & 0 \\
\hline
Group 9 & \_ & \_ & 1 & \_ & 0 & 1 & 1 \\
\hline
Group 10 & \_ & \_ & 0 & \_ & 0 & 1 & 1 \\
\hline
Group 11 & \_ & \_ & 1 & \_ & 0 & 0 & 0 \\
\hline
Group 12 & \_ & \_ & 0 & \_ & 1 & 1 & 0 \\
\hline
Group 13 & \_ & \_ & 1 & \_ & 1 & 1 & 0 \\
\hline
Group 14 & \_ & \_ & 0 & \_ & 1 & 0 & 1 \\
\hline
Group 15 & \_ & \_ & 1 & \_ & 1 & 0 & 1 \\
\hline
Group 16 & \_ & \_ & 1 & \_ & 1 & 1 & 0 \\
\hline
Group 17 & \_ & \_ & 0 & \_ & 0 & 1 & 0 \\
\hline
Group 18 & \_ & \_ & 0 & \_ & 1 & 1 & 0 \\
\hline
Group 19 & \_ & \_ & 1 & \_ & 0 & 1 & 0 \\
\hline
Group 20 & \_ & \_ & 1 & \_ & 1 & 1 & 1 \\
\hline
Group 21 & \_ & \_ & 0 & \_ & 0 & 1 & 1 \\
\hline
Group 22 & \_ & \_ & 0 & \_ & 1 & 1 & 1 \\
\hline
Group 23 & \_ & \_ & 1 & \_ & 0 & 1 & 1 \\
\hline
Group 24 & \_ & \_ & 1 & \_ & 1 & 1 & 0 \\
\hline
Group 25 & \_ & \_ & 0 & \_ & 0 & 0 & 1 \\
\hline
Group 26 & \_ & \_ & 0 & \_ & 0 & 0 & 1 \\
\hline
Group 27 & \_ & \_ & 1 & \_ & 0 & 0 & 1 \\
\hline
Group 28 & \_ & \_ & 0 & \_ & 1 & 0 & 0 \\
\hline
Group 29 & \_ & \_ & 1 & \_ & 1 & 1 & 0 \\
\hline
Group 30 & \_ & \_ & 1 & \_ & 0 & 0 & 1 \\
\tabucline[2pt]{-}
\end{tabu}}
\resizebox{0.4\textwidth}{!}{
\begin{tabu}{|[2pt]c|c|c|c|c|c|c|c|[2pt]}
\tabucline[2pt]{-}
& 1 & 2 & 3 & 4 & 5 & 6 & 7 \\
\tabucline[1pt]{-}
Group 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\
\hline
Group 2 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\
\hline
Group 3 & 1 & 0 & 0 & 0 & 0 & 1 & 1 \\
\hline
Group 4 & 0 & 0 & 1 & 0 & 1 & 1 & 0 \\
\hline
Group 5 & 0 & 1 & 1 & 0 & 0 & 1 & 1 \\
\hline
Group 6 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\
\hline
Group 7 & 1 & 0 & 1 & 1 & 0 & 1 & 0 \\
\hline
Group 8 & 1 & 1 & 0 & 0 & 1 & 1 & 0 \\
\hline
Group 9 & 0 & 1 & 1 & 0 & 0 & 1 & 1 \\
\hline
Group 10 & 1 & 0 & 0 & 0 & 0 & 1 & 1 \\
\hline
Group 11 & 1 & 1 & 1 & 0 & 0 & 0 & 0 \\
\hline
Group 12 & 1 & 1 & 0 & 0 & 1 & 1 & 0 \\
\hline
Group 13 & 0 & 0 & 1 & 0 & 1 & 1 & 0 \\
\hline
Group 14 & 0 & 1 & 0 & 0 & 1 & 0 & 1 \\
\hline
Group 15 & 1 & 0 & 1 & 0 & 1 & 0 & 1 \\
\hline
Group 16 & 0 & 0 & 1 & 0 & 1 & 1 & 0 \\
\hline
Group 17 & 0 & 1 & 0 & 1 & 0 & 1 & 0 \\
\hline
Group 18 & 1 & 1 & 0 & 0 & 1 & 1 & 0 \\
\hline
Group 19 & 1 & 0 & 1 & 1 & 0 & 1 & 0 \\
\hline
Group 20 & 1 & 1 & 1 & 1 & 1 & 1 & 1 \\
\hline
Group 21 & 1 & 0 & 0 & 0 & 0 & 1 & 1 \\
\hline
Group 22 & 0 & 0 & 0 & 1 & 1 & 1 & 1 \\
\hline
Group 23 & 0 & 1 & 1 & 0 & 0 & 1 & 1 \\
\hline
Group 24 & 0 & 0 & 1 & 0 & 1 & 1 & 0 \\
\hline
Group 25 & 1 & 1 & 0 & 1 & 0 & 0 & 1 \\
\hline
Group 26 & 1 & 1 & 0 & 1 & 0 & 0 & 1 \\
\hline
Group 27 & 0 & 0 & 1 & 1 & 0 & 0 & 1 \\
\hline
Group 28 & 1 & 0 & 0 & 1 & 1 & 0 & 0 \\
\hline
Group 29 & 0 & 0 & 1 & 0 & 1 & 1 & 0 \\
\hline
Group 30 & 0 & 0 & 1 & 1 & 0 & 0 & 1 \\
\tabucline[2pt]{-}
\end{tabu}}
\caption{Lycacode: example hamming codes}
\label{tab:lycacode_ex_hamming}
\end{table}
\def\arraystretch{1}
\subsection{Laying out data}
\label{ssec:lycacode_ex_layout}
The matrix layout is shown in figure \ref{fig:lycacode_layout}. Notice the center square; it is used for rotation and mirror image detection.
The middle pattern has to be asymmetrical both in reflection and rotation. Here, the top dot helps determine rotation, while the left one is used to check whether the code is mirrored or not.
The central dot indicates that this is a Lycacode. Indeed, another type of code, Mini Lycacodes, has been created. Those don't have this dot, signaling that they are Mini Lycacodes.\footnote{Mini Lycacodes are not described here but are implemented in Python in the files \texttt{lycacode\_gen\_mini.py} and \texttt{lycacode\_scanner\_mini.py}}
The top and bottom gray areas are reserved for the mask id as explained later.
Also note that white means 1 and black 0.
Starting from the top left going in reading direction, the bits are layed out in the free areas. As for QR-Codes, the first bit of each group is first layed, then the second, the third and so on. Figure \ref{fig:lycacode_ex_data_layout} shows the result of this step. The interleaving process allow a division of data in such a way that if a portion of the code is unreadable, the errors are distributed accross multiple data blocks, increasing the chance of recovery (since each block can only correct one bit).
\begin{figure}[H]
\centering
\begin{subfigure}{0.4\textwidth}
\centering
\includegraphics[width=\textwidth]{images/lycacode_layout}
\caption{Empty}
\label{fig:lycacode_layout}
\end{subfigure}
\begin{subfigure}{0.4\textwidth}
\centering
\includegraphics[width=\textwidth]{images/lycacode_data_layout}
\caption{With data}
\label{fig:lycacode_ex_data_layout}
\end{subfigure}
\caption{Lycacode layout}
\end{figure}
\subsection{Mask}
\label{ssec:lycacode_ex_mask}
As a last step, a mask is applied. The 8 masks are described in figure \ref{fig:lycacode_masks}. The best fitting one is selected based upon similar criteria as for QR-Codes\footnote{the exact criteria are defined in the python script \texttt{lycacode\_gen.py}}. Once applied to the data bits, the mask's id is encoded on the 3 reserved bits at the top and bottom of the code.
The purpose of masking in this context is purely aesthetical. It is a mean to avoid unpleasant visual patterns in the final code.
\begin{figure}[H]
\centering
\begin{subfigure}{0.4\textwidth}
\centering
\includegraphics[width=0.4\textwidth]{images/lycacode_mask_0}
\caption{x mod 3 = 0}
\label{fig:lycacode_mask_0}
\end{subfigure}
\begin{subfigure}{0.4\textwidth}
\centering
\includegraphics[width=0.4\textwidth]{images/lycacode_mask_1}
\caption{y mod 3 = 0}
\label{fig:lycacode_mask_1}
\end{subfigure}
\begin{subfigure}{0.4\textwidth}
\centering
\includegraphics[width=0.4\textwidth]{images/lycacode_mask_2}
\caption{(x+y) mod 3 = 0}
\label{fig:lycacode_mask_2}
\end{subfigure}
\begin{subfigure}{0.4\textwidth}
\centering
\includegraphics[width=0.4\textwidth]{images/lycacode_mask_3}
\caption{(x mod 3)*(y mod 3) = 0}
\label{fig:lycacode_mask_3}
\end{subfigure}
\begin{subfigure}{0.4\textwidth}
\centering
\includegraphics[width=0.4\textwidth]{images/lycacode_mask_4}
\caption{(y//3+x//3) mod 2 = 0}
\label{fig:lycacode_mask_4}
\end{subfigure}
\begin{subfigure}{0.4\textwidth}
\centering
\includegraphics[width=0.4\textwidth]{images/lycacode_mask_5}
\caption{[(y mod 3)-1]*[(x mod 3)-(y mod 3)-2]*[(y mod 3)-(x mod 3)-2] = 0}
\label{fig:lycacode_mask_5}
\end{subfigure}
\begin{subfigure}{0.4\textwidth}
\centering
\includegraphics[width=0.4\textwidth]{images/lycacode_mask_6}
\caption{(|13-x|+|13-y|) mod 3 = 1}
\label{fig:lycacode_mask_6}
\end{subfigure}
\begin{subfigure}{0.4\textwidth}
\centering
\includegraphics[width=0.4\textwidth]{images/lycacode_mask_7}
\caption{[1-(x mod 2) + max(0, |13-y|-|13-x|)] * [1-(y mod 2) + max(0,|13-x|-|13-y|)] = 0}
\label{fig:lycacode_mask_7}
\end{subfigure}
\caption{Lycacode masks}
\label{fig:lycacode_masks}
\end{figure}
Note: "//" is integer division
For our example, the best mask is mask 3. The final binary matrix is shown in figure \ref{fig:lycacode_ex_final_mat}.
\begin{figure}[H]
\centering
\includegraphics[width=0.5\textwidth]{images/lycacode_ex_final_mat}
\caption{Lycacode example: masked matrix}
\label{fig:lycacode_ex_final_mat}
\end{figure}
Finally, the matrix is converted to white dots (for 1s) on the red trefoil cross shown in figure \ref{fig:lycacode_cross}, giving the final code in figure \ref{fig:lycacode_ex_final}.
\begin{figure}[H]
\centering
\includegraphics[width=0.5\textwidth]{images/lycacode_ex_final}
\caption{Lycacode example: final code}
\label{fig:lycacode_ex_final}
\end{figure}
The external square is used to detect the code and to correct perspective.
Its position and dimensions, which have been chosen quite arbitrarily, are visualized on figure \ref{fig:lycacode_frame}. In fact, only the position and size of the inner border is relevant in the decoding process.
\begin{figure}[H]
\centering
\includegraphics[width=0.5\textwidth]{images/lycacode_frame}
\caption{Lycacode frame dimensions}
\label{fig:lycacode_frame}
\end{figure}

162
latex/db.bib Normal file
View File

@ -0,0 +1,162 @@
@Patent{barcode_patent,
author = {Norman Woodland and Bernard Silver},
number = {2612994},
year = {1952},
type = {patentus},
month = oct,
url = {https://worldwide.espacenet.com/patent/search/family/022402610/publication/US2612994A?q=pn%3DUS2612994},
day = {7},
dayfiled = {20},
language = {EN},
monthfiled = {oct},
nationality = {US},
yearfiled = {1949},
}
@Online{wiki_code_39,
author = {{Wikipedia contributors}},
date = {2022},
title = {Code 39 --- {Wikipedia}{,} The Free Encyclopedia},
url = {https://en.wikipedia.org/w/index.php?title=Code_39&oldid=1091770428},
language = {en},
urldate = {2022-08-03},
}
@Online{qrcode_history,
title = {History of QR Code},
url = {https://www.qrcode.com/en/history/},
language = {en},
urldate = {2022-04-06},
}
@TechReport{ISO18004,
title = {Information technology — Automatic identification and data capture techniques — Bar code symbology — QR Code},
institution = {International Organization for Standardization},
year = {2015},
type = {Standard},
url = {https://www.iso.org/standard/62021.html},
shorttitle = {{ISO}/{IEC} 18004:2015},
}
@Online{hamming_2,
author = {3Blue1Brown},
date = {2020-09-04},
title = {Hamming codes part 2, the elegance of it all},
url = {https://www.youtube.com/watch?v=b3NxrZOu_CE},
language = {en},
urldate = {2022-04-07},
}
@Online{hamming_1,
author = {3Blue1Brown},
date = {2020-09-04},
language = {en},
title = {How to send a self-correcting message (Hamming codes)},
url = {https://www.youtube.com/watch?v=X8jsijhllIA},
urldate = {2022-04-07},
}
@TechReport{ISO16388,
date = {2007},
institution = {International Organization for Standardization},
title = {Information technology — Automatic identification and data capture techniques — Code 39 bar code symbology specification},
type = {Standard},
url = {https://www.iso.org/standard/43897.html},
shorttitle = {{ISO}/{IEC} 16388:2007},
}
@TechReport{nasa_rs,
author = {William A. Geisel},
date = {1990-08-01},
institution = {NASA Lyndon B. Johnson Space Center Houston, TX, United States},
title = {Tutorial on Reed-Solomon Error Correction Coding},
language = {en},
type = {techreport},
url = {https://ntrs.nasa.gov/api/citations/19900019023/downloads/19900019023.pdf},
abstract = {This tutorial attempts to provide a frank, step-by-step approach to Reed-Solomon (RS) error correction coding. RS encoding and RS decoding both with and without erasing code symbols are emphasized. There is no need to present rigorous proofs and extreme mathematical detail. Rather, the simple concepts of groups and fields, specifically Galois fields, are presented with a minimum of complexity. Before RS codes are presented, other block codes are presented as a technical introduction into coding. A primitive (15, 9) RS coding example is then completely developed from start to finish, demonstrating the encoding and decoding calculations and a derivation of the famous error-locator polynomial. The objective is to present practical information about Reed-Solomon coding in a manner such that it can be easily understood.},
organization = {NASA},
}
@Online{computerphile_rs,
author = {Computerphile},
date = {2019-02-20},
title = {Reed Solomon Encoding - Computerphile},
url = {https://www.youtube.com/watch?v=fBRMaEAFLE0},
language = {en},
}
@Article{reed_solomon,
author = {Reed, I. S. and Solomon, G.},
date = {1960},
journaltitle = {Journal of the Society for Industrial and Applied Mathematics},
title = {Polynomial Codes Over Certain Finite Fields},
doi = {10.1137/0108018},
eprint = {https://doi.org/10.1137/0108018},
number = {2},
pages = {300-304},
url = {https://doi.org/10.1137/0108018},
volume = {8},
}
@Online{reed_solomon_wiki,
author = {{Wikipedia Contributors}},
date = {2022},
title = {ReedSolomon error correction --- {Wikipedia}{,} The Free Encyclopedia},
url = {https://en.wikipedia.org/w/index.php?title=Reed%E2%80%93Solomon_error_correction&oldid=1100051467},
urldate = {2022-08-03},
}
@Online{kartrak,
author = {{Wikipedia contributors}},
date = {2021},
title = {KarTrak --- {Wikipedia}{,} The Free Encyclopedia},
url = {https://en.wikipedia.org/w/index.php?title=KarTrak&oldid=1037680217},
urldate = {2022-08-15},
}
@Online{hamming_wiki,
author = {{Wikipedia contributors}},
date = {2022},
title = {Hamming code --- {Wikipedia}{,} The Free Encyclopedia},
url = {https://en.wikipedia.org/w/index.php?title=Hamming_code&oldid=1065900025},
urldate = {2022-08-15},
}
@Online{nayuki_qr_js,
author = {Nayuki},
date = {2022-08-30},
title = {Nayuki-web-published-code/creating-qr-code-steps.js at dfb110475327271e3b7279a432e2d1a1298815ad · nayuki/Nayuki-web-published-code},
url = {https://github.com/nayuki/Nayuki-web-published-code/blob/dfb110475327271e3b7279a432e2d1a1298815ad/creating-a-qr-code-step-by-step/creating-qr-code-steps.js},
}
@Online{wiki_chien_search,
author = {{Wikipedia contributors}},
date = {2020},
title = {Chien search --- {Wikipedia}{,} The Free Encyclopedia},
url = {https://en.wikipedia.org/w/index.php?title=Chien_search&oldid=990999150},
urldate = {2022-09-03},
}
@Online{information_theory,
author = {George Markowsky},
editor = {{Encyclopedia Britannica}},
title = {information theory},
url = {https://www.britannica.com/science/information-theory},
urldate = {2022-09-04},
abstract = {information theory, a mathematical representation of the conditions and parameters affecting the transmission and processing of information. Most closely associated with the work of the American electrical engineer Claude Shannon in the mid-20th century, information theory is chiefly of interest to communication engineers, though some of the concepts have been adopted and used in such fields as psychology and linguistics. Information theory overlaps heavily with communication theory, but it is more oriented toward the fundamental limitations on the processing and communication of information and less oriented toward the detailed operation of particular devices. Interest in the concept of information grew},
langid = {english},
}
@Online{rs_for_coders,
author = {Wikiversity},
date = {2022},
title = {ReedSolomon codes for coders --- Wikiversity{,}},
url = {https://en.wikiversity.org/w/index.php?title=ReedSolomon_codes_for_coders&oldid=2387659},
urldate = {2022-04-06},
}
@Comment{jabref-meta: databaseType:biblatex;}

461
latex/error_correction.tex Normal file
View File

@ -0,0 +1,461 @@
\chapter{Error detection and correction}
\label{chap:err_corr}
This chapter introduces two methods to create self-correcting messages: Hamming codes and Reed-Solomon codes. The former is based on parity bits while the latter takes advantage of advanced mathematical properties of modular arithmetic and polynomials.
\section{Hamming Codes}
\label{sec:hamming}
When working with binary data, one way of checking if a received message is corrupted or not is to add a parity bit. The parity of a binary number is even if it has an even number of 1s and odd otherwise. A parity check bit is added such that the total parity of the number is even, i.e. 0 if it is already even, 1 otherwise.
\def\arraystretch{1.2}
\begin{center}
\begin{tabular}{|c|c|c|c|c||c|c}
\multicolumn{5}{c}{} & \multicolumn{1}{c}{parity bit} & \\
\cline{1-6}
bit 1 & bit 2 & bit 3 & bit 4 & bit 5 & bit 6 & \multirow{2}{*}{parity: even} \\
\cline{1-6}
1 & 1 & 0 & 0 & 1 & 1 & \\
\cline{1-6}
\end{tabular}
\end{center}
\def\arraystretch{1}
With this, a single bit error (that is, one bit is wrong) is easy to detect because the parity of the message becomes odd.
\def\arraystretch{1.2}
\begin{center}
\begin{tabular}{|c|c|c|c|c|c|c}
\cline{1-6}
bit 1 & bit 2 & bit 3 & bit 4 & bit 5 & bit 6 & \multirow{2}{*}{parity: odd} \\
\cline{1-6}
1 & 1 & \textbf{1} & 0 & 1 & 1 & \\
\cline{1-6}
\end{tabular}
\end{center}
\def\arraystretch{1}
However, a single parity bit doesn't provide enough information to allow locating the error or detecting multiple errors, because an even number of errors would keep an even parity overall.
\def\arraystretch{1.2}
\begin{center}
\begin{tabular}{|c|c|c|c|c|c|c}
\cline{1-6}
bit 1 & bit 2 & bit 3 & bit 4 & bit 5 & bit 6 & \multirow{2}{*}{parity: even} \\
\cline{1-6}
1 & 1 & \textbf{1} & 0 & \textbf{0} & 1 & \\
\cline{1-6}
\end{tabular}
\end{center}
\def\arraystretch{1}
Hamming codes are a kind of parity check codes.
Instead of using only one parity bit however, they include several so that locating becomes possible, as well as detecting (not always) multiple errors.
When creating a Hamming code from a message, data first has to be split into blocks of a given size. For each block a certain number of parity bits is assigned. These two variables (blocksize and number of parity bits) determine the type of Hamming code.
For example a Hamming code with 3 parity bits will form 7-bit blocks, meaning each block can hold 4 data bits. It can thus be called Hamming(7, 4).
Smaller blocksizes allow more errors to be corrected, because each block can correct one error, but have a lower data density\footnote{data density is the ratio of data bits over blocksize}. On the other hand, larger blocksizes allow less errors to be corrected but have a higher data density.
Hamming codes are created in such a way that when a bit is flipped, the parity bits indicate exactly where the error occured. For that, each position in the code which is a power of two is a parity bits. Then, each parity bit covers the parity of all bits at positions containing its power in their binary representation. For example, the parity bit at position 4 (0b\ul{1}00) covers bits 5 (0b\ul{1}01), 6 (0b\ul{1}10), 7 (0b\ul{1}11), 12 (0b1\ul{1}00), 13 (0b1\ul{1}01), 14 (0b1\ul{1}10), 15 (0b1\ul{1}11), ...
Table \ref{tab:hamming_struct} taken from \citetitle{hamming_wiki}\cite{hamming_wiki} offers a good visual representation of this structure:
\def\arraystretch{1.5}
\begin{table}[H]
\centering
\resizebox{\textwidth}{!}{
\begin{tabular}{|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|}
\hline
\rowcolor{tabgrey} \multicolumn{2}{|c|}{Bit position} & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 & 10 & 11 & 12 & 13 & 14 & 15 \\
\hhline{-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|}
\rowcolor{tabgrey} \multicolumn{2}{|c|}{Encoded data bits} & \cellcolor{paritybg} p1 & \cellcolor{paritybg} p2 & d1 & \cellcolor{paritybg} p4 & d2 & d3 & d4 & \cellcolor{paritybg} p8 & d5 & d6 & d7 & d8 & d9 & d10 & d11 \\
\hhline{-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|}
\multirow{4}{*}{\shortstack{Parity\\ bit\\ coverage}} & \cellcolor{paritybg} p1 & \tick & & \tick & & \tick & & \tick & & \tick & & \tick & & \tick & & \tick \\
\hhline{~|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|}
& \cellcolor{paritybg} p2 & & \tick & \tick & & & \tick & \tick & & & \tick & \tick & & & \tick & \tick \\
\hhline{~|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|}
& \cellcolor{paritybg} p4 & & & & \tick & \tick & \tick & \tick & & & & & \tick & \tick & \tick & \tick \\
\hhline{~|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|}
& \cellcolor{paritybg} p8 & & & & & & & & \tick & \tick & \tick & \tick & \tick & \tick & \tick & \tick \\
\hline
\end{tabular}}
\caption{Hamming code structure}
\label{tab:hamming_struct}
\end{table}
\def\arraystretch{1}
Here we can see that each data bit (d1, d2, d3, ...) is covered by a unique set of parity bits.
\pagebreak
Let's create a Hamming(15, 11) code for the message \texttt{11101100010}.
The first step is to lay out the bits in table \ref{tab:hamming_struct} like so:
\def\arraystretch{1.5}
\begin{table}[H]
\centering
\resizebox{\textwidth}{!}{
\begin{tabular}{|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|}
\hline
\rowcolor{tabgrey} \multicolumn{2}{|c|}{Bit position} & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 & 10 & 11 & 12 & 13 & 14 & 15 \\
\hline
\multicolumn{2}{|c|}{\cellcolor{tabgrey} Encoded data bits} & p1 & p2 & 1 & p4 & 1 & 1 & 0 & p8 & 1 & 1 & 0 & 0 & 0 & 1 & 0 \\
\hline
\multirow{4}{*}{\shortstack{Parity\\ bit\\ coverage}} & \cellcolor{paritybg} p1 & - & & \tick & & \tick & & \cross & & \tick & & \cross & & \cross & & \cross \\
\hhline{~|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|}
& \cellcolor{paritybg} p2 & & - & \tick & & & \tick & \cross & & & \tick & \cross & & & \tick & \cross \\
\hhline{~|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|}
& \cellcolor{paritybg} p4 & & & & - & \tick & \tick & \cross & & & & & \cross & \cross & \tick & \cross \\
\hhline{~|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|}
& \cellcolor{paritybg} p8 & & & & & & & & - & \tick & \tick & \cross & \cross & \cross & \tick & \cross \\
\hline
\end{tabular}}
\medskip
\begin{tabular}{|c|c|c|c|}
\hline
\rowcolor{tabgrey} Parity bit & Covered 1s & Parity of covered bits & Value \\
\hline
p1 & 3 & odd & 1 \\
\hline
p2 & 4 & even & 0 \\
\hline
p4 & 3 & odd & 1 \\
\hline
p8 & 3 & odd & 1 \\
\hline
\end{tabular}
\caption{Hamming code example}
\label{tab:hamming_ex}
\end{table}
\def\arraystretch{1}
Placing the parity bits in their relevant positions, we get the hamming code \texttt{101111011100010}.
%\bigskip
To illustrate the decoding process, let's alter bit 11 and change it to a 1.
Now, recalculating the parity bits and comparing the results with the received message, we can find the location of the error.
\def\arraystretch{1.5}
\begin{table}[H]
\centering
\resizebox{\textwidth}{!}{
\begin{tabular}{|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|}
\hline
\rowcolor{tabgrey} \multicolumn{2}{|c|}{Bit position} & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 & 10 & 11 & 12 & 13 & 14 & 15 \\
\hline
\multicolumn{2}{|c|}{\cellcolor{tabgrey} Received data bits} & 1 & 0 & 1 & 1 & 1 & 1 & 0 & 1 & 1 & 1 & \textbf{1} & 0 & 0 & 1 & 0 \\
\hline
\multirow{4}{*}{\shortstack{Parity\\ bit\\ coverage}} & \cellcolor{paritybg} p1 & - & & \tick & & \tick & & \cross & & \tick & & \tick & & \cross & & \cross \\
\hhline{~|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|}
& \cellcolor{paritybg} p2 & & - & \tick & & & \tick & \cross & & & \tick & \tick & & & \tick & \cross \\
\hhline{~|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|}
& \cellcolor{paritybg} p4 & & & & - & \tick & \tick & \cross & & & & & \cross & \cross & \tick & \cross \\
\hhline{~|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|}
& \cellcolor{paritybg} p8 & & & & & & & & - & \tick & \tick & \tick & \cross & \cross & \tick & \cross \\
\hline
\end{tabular}}
\medskip
\begin{tabular}{|c|c|c|c|c|}
\hline
\rowcolor{tabgrey} Parity bit & Covered 1s & Parity of covered bits & Value & Received value \\
\hline
p1 & 4 & even & 0 & 1 \\
\hline
p2 & 5 & odd & 1 & 0 \\
\hline
p4 & 3 & odd & 1 & 1 \\
\hline
p8 & 4 & even & 0 & 1 \\
\hline
\end{tabular}
\caption{Hamming code example decoding}
\label{tab:hamming_ex_decoding}
\end{table}
\def\arraystretch{1}
The difference (XOR) between columns "Value" and "Received value" forms the binary number \texttt{0b1101} = 11, the location of the error.
\section{Reed-Solomon algorithm}
\label{sec:reed_solomon}
The Reed-Solomon algorithm is a mathematical process allowing the decoding of a partially corrupted message. It is used in many domains for its strength and reliability, such as for spatial communication, CD/DVD players, some television broadcasts and QR-Codes.
Reed-Solomon codes were developed by Irving S. Reed and Gustave Solomon in 1960 \cite{reed_solomon}
As they rely on abstract and complex mathematical concepts, this section will not go in specific details about the actual decoding process. For curious readers, see
\citetitle{computerphile_rs}\cite{computerphile_rs} (presentation, general concept), \citetitle{reed_solomon_wiki}\cite{reed_solomon_wiki} (in depth article) and \citetitle{nasa_rs}\cite{nasa_rs} (complete manual and explanations)
\subsection{Error detection}
\label{ssec:rs_error_detection}
Before considering error correction, it is necessary to talk about error detection. Several methods have been developed for this purpose.
The most basic, as seen in \autoref{sec:hamming}, is a parity check. It consists of appending one or more "parity bits" to a binary message, such that the overall parity is known. For example, let table \ref{tab:err_det_raw} be our raw message
\def\arraystretch{1.5}
\begin{table}[H]
\centering
\begin{tabu}{|cccccccc|}
\hline
0 & 1 & 0 & 0 & 0 & 0 & 1 & 1 \\
\hline
\end{tabu}
\caption{Error detection: raw message}
\label{tab:err_det_raw}
\end{table}
\def\arraystretch{1}
The parity of this byte is odd -- because there are 3 1s -- so an additional 1 is added to the end.
In this way, if the message is corrupted -- by 1 bit maximum -- it becomes even, and we know there is an error.
Now obviously it doesn't provide any information on the exact location of the error in the message and can't detect an even number of errors.
However, this principle can be extended to include a parity bit for every byte.
If represented as a table in which each row is a byte, it can also include parity bits for each column.
Table \ref{tab:err_det_tab} is an example of such usage of parity bits. This implementation is even able to correct a single error as the row and column would be odd.
\def\arraystretch{1.4}
\begin{table}[H]
\centering
\begin{tabu}{|[2pt]c|cccccccc|c|[2pt]}
\tabucline[2pt]{-}
& bit 0 & bit 1 & bit 2 & bit 3 & bit 4 & bit 5 & bit 6 & bit 7 & parity \\
\hline
byte 0 & 0 & 1 & 0 & 0 & 0 & 0 & 1 & 1 & 1 \\
byte 1 & 0 & 1 & 1 & 0 & 1 & 1 & 1 & 1 & 0 \\
byte 2 & 0 & 1 & 1 & 0 & 0 & 1 & 0 & 0 & 1 \\
byte 3 & 0 & 1 & 1 & 0 & 0 & 1 & 0 & 1 & 0 \\
byte 4 & 0 & 1 & 1 & 1 & 0 & 0 & 1 & 1 & 1 \\
\hline
parity & 0 & 1 & 0 & 1 & 1 & 1 & 1 & 0 & 1 \\
\tabucline[2pt]{-}
\end{tabu}
\caption{Error detection: bytes table parity}
\label{tab:err_det_tab}
\end{table}
\def\arraystretch{1}
Such codes however don't provide enough error correction capability - at most 1 bit. Other methods, like the previously explained Hamming codes, allow a more efficient use of parity bits, increasing to some degree the number of fixable errors.
Reed-Solomon codes use properties of polynomials and modular arithmetic to produce more efficient and more robust correction data.
\subsection{Binary to polynomials}
\label{ssec:rs_bin_to_poly}
Instead of working directly with binary data, Reed-Solomon codes treat messages as polynomials.
These are formed from binary data as follows: each byte is converted to a decimal integer, representing coefficients of the polynomial.
For example:
\def\arraystretch{1.4}
\begin{tabu}{|[1pt]c|c|c|c|c|c|[1pt]}
\tabucline[1pt]{-}
raw binary & $01000011$ & $01101111$ & $01100100$ & $01100101$ & $01110011$ \\
\hline
decimal & $67$ & $111$ & $100$ & $101$ & $115$ \\
\hline
polynomial & \multicolumn{5}{c|[1pt]}{$67x^4 + 111x^3 + 100x^2 + 101x + 115$}\\
\tabucline[1pt]{-}
\end{tabu}
\def\arraystretch{1}
\subsection{Galois Fields}
\label{ssec:rs_galois}
Since the main applications of Reed-Solomon codes are related to digital devices, it is relevant to use bits and bytes. As such, all calculations are performed in a Galois field. A Galois field is basicly a finite set of numbers on which arithmetic operations results in numbers of the set. In the case of QR-Codes, G(256) -- a Galois field of the integers 0 to 255 incl. -- is used. This means every operation between numbers results in a value between 0 and 255 incl., which is an eight-bit positive integer. In this field, addition and subtraction are equivalent and defined as the binary XOR operation. For example:
\begin{gather*}
17 + 13 = 17 - 13 = 28 \\
\Leftrightarrow 0\text{b}10001 \oplus 0\text{b}1101 = 0\text{b}1101 \oplus 0\text{b}10001 = 0\text{b}11100
\end{gather*}
Multiplication is more complex though. One property of this Galois field is that every number can be represented as a power of two, XOR 285.
For example:
\begin{equation*}
\begin{split}
2^{17} =& (\underbrace{2^8}_{256} \oplus 285) * 2^9 = 29 * 2^9 \\
=& [\underbrace{(29 * 2^4)}_{464} \oplus 285] * 2^5 = 205 * 2^5 \\
=& [\underbrace{(205 * 2)}_{410} \oplus 285] * 2^4 = 135 * 2^4 \\
=& [\underbrace{(135 * 2)}_{270} \oplus 285] * 2^3 = 19 * 2^3 \\
=& 152 \\
\Rightarrow& \exp_2(17) = 152 \\
\Rightarrow& \log_2(152) = 17
\end{split}
\end{equation*}
To multiply two numbers $a$ and $b$ in the Galois field:\footnote{from now on, $\exp$ and $\log$ are assumed to be base 2} \[
a * b = \exp(\log(a) + \log(b))
\]
which also works in regular arithmetic (in $\mathbb{N}^*_+$).
Division works similarly, but because there are no negative or fractional number in the field, the exponent is kept in the range 0-255 incl. like so: \[
\frac{a}{b} = \exp([\log(a) - \log(b) + 255]\ mod \ 255)
\]
And powers too: \[
a^b = \exp([\log(a) * b]\ mod\ 255)
\]
\subsection{Generating error correction}
\label{ssec:rs_gen}
To create Reed-Solomon error correction bytes, a generator polynomial $g(x)$ is needed.
This polynomial is created using equation \ref{equ:rs_gen_poly}:
\begin{equation}
g(x) = \prod_{i=0}^{d-1} (x + 2^i)
\label{equ:rs_gen_poly}
\end{equation}
where $d$ is one more than the degree of the polynomial, equivalent to the number of error correction bytes.
Let $m(x)$ be our message polynomial (see \autoref{ssec:rs_bin_to_poly}) and $g(x)$ the generator polynomial. The error correction polynomial $E_c(x)$ is then the remainder of the long polynomial division $m(x)/g(x)$.
Let's illustrate this by creating error correction for the string "Codes". In UTF-8, the message bytes are 67, 111, 100, 101, 115, thus $m(x) = 67x^4+ 111x^3+ 100x^2+ 101x+ 115$.
% We will take the generator polynomial of degree 3, that is: \[
% g(x) = (x + 1)*(x + 2)*(x + 4)\\
% = x^3 + 7x^2 + 14x + 8
% \]
We will take the generator polynomial of degree 4, that is: \[
g(x) = (x + 1)*(x + 2)*(x + 4)*(x + 8)\\
= x^4 + 15x^3 + 54x^2 + 120x + 64
\]
And thus (reminder that addition and subtraction in the galois field is the binary XOR operation):
\begin{comment}
\[
\def\arraystretch{1.5}
\begin{array}{rrrrrrrrrrrrr}
67 & 111 & 100 & 101 & 115 & 0 & 0 & 0 &\divline{}& 1 & 7 & 14 & 8 \\
\cline{9-13}
-67 & -212 & -181 & -34 & & & & & 67 & 187 & 215 & 84 & 77 \\
\cline{1-4}
& 187 & 209 & 71 & 115 & & & & & & & & \\
& -187 & -6 & -12 & -177 & & & & & & & & \\
\cline{2-5}
& & 215 & 75 & 194 & 0 & & & & & & & \\
& & -215 & -31 & -52 & -246 & & & & & & & \\
\cline{3-6}
& & & 84 & 252 & 246 & 0 & & & & & & \\
& & & -84 & -177 & -127 & -154 & & & & & & \\
\cline{4-7}
& & & & 77 & 137 & 154 & 0 & & & & & \\
& & & & -77 & -254 & -225 & -82 & & & & & \\
\cline{5-8}
& & & & & 119 & 123 & 82 & & & & &
\end{array}
\def\arraystretch{1}
\]
\[
\Rightarrow E_c(x) = 119x^2 + 123x + 82
\]
\end{comment}
\[
\def\arraystretch{1.5}
\begin{array}{rrrrrrrrrrrrrr}
67 & 111 & 100 & 101 & 115 & 0 & 0 & 0 & 0 &\divline{1}& 15 & 54 & 120 & 64 \\
\cline{10-14}
-67 & -246 & -91 & -227 & -13 & & & & & 67 & 153 & 107 & 43 & 8 \\
\cline{1-5}
& 153 & 63 & 134 & 126 & & & & & & & & & \\
& -153 & -84 & -222 & -154 & -137 & & & & & & & & \\
\cline{2-6}
& & 107 & 88 & 228 & 137 & & & & & & & & \\
& & -107 & -115 & -120 & -191 & -223 & & & & & & & \\
\cline{3-7}
& & & 43 & 156 & 54 & 223 & & & & & & & \\
& & & -43 & -148 & -121 & -212 & -18 & & & & & & \\
\cline{4-8}
& & & & 8 & 79 & 11 & 18 & & & & & & \\
& & & & -8 & -120 & -173 & -231 & -58 & & & & & \\
\cline{5-9}
& & & & & 55 & 166 & 245 & 58 & & & & &
\end{array}
\def\arraystretch{1}
\]
\[
\Rightarrow E_c(x) = 55x^3 + 166x^2 + 245x + 58
\]
Details of the first step:
\begin{align*}
67 * 1 =& \exp(\log(67) + \log(1)) = \exp(98 + 0) = \exp(98) = 67\\
67 * 15 =& \exp(\log(67) + \log(15)) = \exp(98 + 75) = \exp(41) = 246\\
67 * 54 =& \exp(\log(67) + \log(54)) = \exp(98 + 249) = \exp(155) = 91\\
67 * 120 =& \exp(\log(67) + \log(120)) = \exp(98 + 78) = \exp(44) = 227\\
67 * 64 =& \exp(\log(67) + \log(64)) = \exp(98 + 6) = \exp(100) = 13
\end{align*}
%Then, to communicate our message, $E_c(x)$ is converted to binary and appended to our raw message data, in our case, the final message would be: 67, 111, 100, 101, 115, 119, 123, 82.
Then, to communicate our message, $E_c(x)$ is converted to binary and appended to our raw message data, in our case, the final message would be: 67, 111, 100, 101, 115, 55, 166, 245, 58.
This is the actual data sent by a device, or in the case of QR-Codes, the actual data encoded on the symbol. Let it be a polynomial named $s(x)$ (for sent data).
Unfortunately, this is not always what is received by the recipient (or read by the scanner). Some interference may happen during transmission and data may be altered. Let the received data be the polynomial $r(x) = s(x) + e(x)$ (where $e(x)$ is the error polynomial).
In the next section, we will outline the main steps and basic mathematical principles required for error correction and detection through the Reed-Solomon algorithm.
\subsection{Detecting and correcting errors}
\label{ssec:rs_error_correction}
The first step to locating potential errors in a received Reed-Solomon code is to calculate its "syndrome polynomial" $S(x)$. The coefficient of the $i^{th}$ degree term of this polynomial is the value of $r(2^i)$ (the degree of $S(x)$ is equal to the number of error correction bytes minus 1, in our case 2). This means:
\[
S(x) = \sum_{i=0}^{d-1} r(2^i) * x^i
\]
To illustrate the algorithm, we will take
%\[ r(x) = 67x^7 + 111x^6 + \textbf{110}x^5 + 101x^4 + 115x^3 + 119x^2 + 123x + 82 \]
%\[ \Rightarrow e(x) = 10x^5 \]
\[ r(x) = 67x^8 + 111x^7 + \textbf{110}x^6 + 101x^5 + 115x^4 + \textbf{50}x^3 + 166x^2 + 245x + 58 \]
\[ \Rightarrow e(x) = 10x^6 + 5x^3 \]
%Thus, \[
% S(x) = 111x^2 + 93x + 10
%\]
Thus, \[
S(x) = 253x^3 + 252x^2 + 146x + 15
\]
Reed-Solomon codes provide a very useful mathematical property. In fact, if $s(x) = r(x) \Rightarrow e(x) = 0$, then $S(x) = 0$, enabling a fast return if there is no corruption.
In the case where $S(x) \not= 0$, we need to compute two other polynomials, the locator and evaluator polynomials. The former helps determine positions of errors whilst the latter is used to find the magnitude of each error, that is, the difference with the real value.
These can be found with the help of the euclidean algorithm. The exact methods used will not be described here as the mathematical implications behind them are much above the level of this work, but there functioning and alternatives are well documented in \citetitle{nasa_rs}\cite{nasa_rs} (from p.65, section 4.3.1).
From our example, we would get the following polynomials:
%\[ E_{locator}(x) = 32x + 1 \]
%\[ E_{evalutor}(x) = 10 \]
\[ E_{locator}(x) = 58x^2 + 72x + 1 \]
\[ E_{evalutor}(x) = 13x + 15 \]
\paragraph{Locator polynomial}
Once the locator polynomial has been computed, it can be used to get the precise position of each error, as long as the number of errors is not greater than the correction capacity.
The error location polynomial\footnote{not to be confused with the error locator polynomial} is first calculated from the locator polynomial using Chien search (not described here), a "fast algorithm for determining roots of polynomials defined over a finite field"\cite{wiki_chien_search}.
In this polynomial, each coefficient's log (in the Galois field) is the byte index of an error in the received message (starting from the end) -- or degree of a wrong coefficient in $r(x)$.
%Continuing the example, we obtain: \[ E_{location}(x) = 32 \]
Continuing the example, we obtain: \[ E_{location}(x) = 64x + 8 \]
\paragraph{Evaluator polynomial}
Using the error location and evaluator polynomial in Forney's algorithm, it is possible to find the magnitude of each error, that is the coefficients of $e(x)$.
%Our result: \[ E_{mag}(x) = 10 \]
Our result: \[ E_{mag}(x) = 10x + 5 \]
\paragraph{Correction}
We now have all the information needed to correct the received message. For that, we need to add the magnitudes to their corresponding locations. Again, the locations are the logarithms of each coefficient in the error location polynomial and magnitudes are the coefficients of $E_{mag}$.
Our example has two errors, since both $E_{location}$ and $E_{mag}$ are second degree polynomials.
For the first error, we add $10$ to $r_6$ ($6$ being $\log(64)$).
For the second error, we add $5$ to $r_3$ ($3$ being $\log(8)$).
We can finally recover the original message: 67, 111, 100, 101, 115, 55, 166, 245, 58.

Binary file not shown.

After

Width:  |  Height:  |  Size: 910 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
latex/images/qr_end.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
latex/images/qr_mask_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

BIN
latex/images/qr_mask_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

BIN
latex/images/qr_mask_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 B

BIN
latex/images/qr_mask_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 B

BIN
latex/images/qr_mask_4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 B

BIN
latex/images/qr_mask_5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 B

BIN
latex/images/qr_mask_6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 B

BIN
latex/images/qr_mask_7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
latex/images/qr_plcmt.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 949 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 763 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
latex/images/qr_timing.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

22
latex/introduction.tex Normal file
View File

@ -0,0 +1,22 @@
\chapter{Introduction}
\label{chap:intro}
%Telecommunication has certainly been one of the most important development in the last century. From pigeons to Earth-Mars transmissions, a lot has happened. Nowadays, our society is all about information. Whether it is one's credit card number, the price of a dress or taxes, everything revolves around numbers and digital data.
%While the first computers and calculators used analog systems and could store any arbitrary type of data, our current devices are digital and only contain binary information. Inventors and developers were hence confronted to the challenge of encoding certain types of data in binary.
%We developed character encodings such as ASCII, UTF-8 or CP-1252, formats to store pictures, formats to store sounds, formats to store videos, etc. Many of these encodings have been standardized to facilitate communication between devices and are free to be used by anyone.
Computers and microprocessors are certainly the defining innovations of the end of the XX\textsuperscript{th} and beginning of the XXI\textsuperscript{st} centuries.
Invented to perform tasks faster and more reliably than humans, they truly have surpassed our mental capacities in many areas.
Although they overcome a great number of our shortcomings, especially in terms of speed, they are not infallible.
While many computer related bugs can be tracked down to a human error, some are inherent to the physical infrastructure of our technologies.
One major material limitation is the network connecting computers to each other.
For example, WiFi and mobile data use radio transmissions which are not perfectly reliable. Similarly, space communication implies a lot of interferences due to the atmosphere, space debris and all sorts of radiations.
Invalid data may also come from external sensors, would that be because they are malfunctioning or simply not able to correctly interpret there inputs.
As such, engineers and programmers have to devise methods to check that the data received is unaltered and provide a way of recovering the original information, or at least guess it.
These methods are most useful in fields where data is, or was, manually input.
As the saying goes, to err is human, and machines help us correct these errors.
Of course, these concerns are not new. Indeed, Claude Shannon had already started taking an interest in information theory in 1948\cite{information_theory}.
This scientific field which studies the characteristics and behaviors of information led to many technological improvements and fundamental theories, especially in computer science.
Barcodes and QR-Codes are two instances of the consequences and use of information theory. Their main benefit is to allow reliable identification of objects by computers. Prior to these inventions, it was the task of humans to manually tell machines what an object was, for example in supermarkets or factories. Now, a simple camera can quickly recognize items, without the use of artificial intelligence, which requires substantially higher computing capacity. Additionally, their simple designs make them very easy and efficient to implement, even on limited hardware.

BIN
latex/main.pdf Normal file

Binary file not shown.

122
latex/main.tex Normal file
View File

@ -0,0 +1,122 @@
\documentclass[10pt,a4paper]{report}
\usepackage{geometry}
%\usepackage[utf8]{inputenc} % Apprently not useful with LuaLaTeX
\usepackage[T1]{fontenc}
\usepackage[british]{babel}
\usepackage{amsmath} % mathematical symbols
\usepackage{amsfonts} % mathematical symbols
\usepackage{amssymb} % mathematical symbols
\usepackage{graphicx} % figures
\usepackage{array} % for new column type
\usepackage{tabu} % for custom line width in tables
\usepackage{longtable} % multi-page tables
\usepackage[table,xcdraw]{xcolor}
\usepackage{fancyhdr} % headers and footers
\usepackage{lastpage} % total page count
\usepackage{float}
\usepackage{multirow}
\usepackage[bottom]{footmisc} % footnotes at the bottom of the page
\usepackage{subcaption} % subfigures
\usepackage{verbatim} % comment environment
\usepackage[backend=bibtex]{biblatex}
%\usepackage{bbding} % checkmark
\usepackage{pifont} % checkmark and cross
%\usepackage{nicematrix} % to fix colorcell overalpping borders
\usepackage{hhline} % partial hline for long division
\usepackage{minted} % python code highlight
\usepackage[minted,breakable]{tcolorbox} % minted title
\usepackage{csquotes} % localized quotes
\usepackage{caption} % caption for side by side fig and tab
\bibliography{db.bib}
%THIS NEEDS TO BE LOADED AFTER `longtable` TO PREVENT CONFLICTS
\usepackage{arydshln} % dashed lines in tables
\usepackage{fontspec} % custom font
\setmainfont{Ubuntu-R}[
BoldFont = Ubuntu-B,
ItalicFont = Ubuntu-RI,
BoldItalicFont = Ubuntu-BI
]
\setmonofont{UbuntuMono-R}[
BoldFont = UbuntuMono-B,
ItalicFont = UbuntuMono-RI,
BoldItalicFont = UbuntuMono-BI
]
\usepackage{hyperref} % links
\hypersetup{
pdftitle={Barcodes and QR-Codes},
pdfauthor={Louis Heredero},
colorlinks=true,
linkcolor=black,
citecolor=black,
filecolor=black,
urlcolor=blue
}
\pagestyle{fancy}
\setlength{\headheight}{15pt}
\renewcommand{\headrulewidth}{1pt}
\fancyhead[L]{\leftmark}
\fancyhead[C]{}
\fancyhead[R]{\rightmark}
\renewcommand{\footrulewidth}{1pt}
\fancyfoot[L]{Louis Heredero 5D}
\fancyfoot[C]{\thepage/\pageref{LastPage}}
\fancyfoot[R]{September 2022}
% Black cell for representing barcodes in tables
\newcommand*{\bbar}{\cellcolor{black}}
\newcolumntype{C}[1]{>{\centering\arraybackslash}p{#1}}
% Rotate table header
\newcommand{\rot}[1]{\hspace{5pt}\rotatebox{90}{#1\hspace{10pt}}\hspace{5pt}}
% Long division vertical bar
\newcommand{\divline}[1]{\multicolumn{1}{|r}{#1}}
% Hamming structure tick
\definecolor{tickbg}{HTML}{DDFFDD}
\definecolor{tickfg}{HTML}{005E00}
\definecolor{paritybg}{HTML}{90FF90}
\definecolor{tabgrey}{HTML}{EAECF0}
\definecolor{crossbg}{HTML}{FFDDDD}
\definecolor{crossfg}{HTML}{5E0000}
\newcommand{\cmark}{\ding{51}}
\newcommand{\xmark}{\ding{55}}
\newcommand{\tick}{\cellcolor{tickbg}\textcolor{tickfg}{\cmark}}
\newcommand{\cross}{\cellcolor{crossbg}\textcolor{crossfg}{\xmark}}
\newcommand{\ul}[1]{\underline{#1}}
\newcommand*{\hreffn}[2]{\href{#1}{#2}\footnote{#1}}
\begin{document}
\parindent=0cm
\parskip=0.5em
\include{title_toc}
\parskip=1em
\include{introduction}
\include{barcode_origin}
\include{barcode_functioning}
\include{barcode_python}
\include{qr_origin}
\include{qr_functioning}
\include{qr_python}
\include{error_correction}
\include{custom_code}
\include{conclusion}
\include{review}
\include{bibliography}
\include{appendix}
\end{document}

4
latex/other_2d.tex Normal file
View File

@ -0,0 +1,4 @@
\chapter{Other 2D codes}
\label{chap:other_2d}
\emph{short chapter about other 2D codes such as Aztec Code, Data Matrix, PDF417}

612
latex/qr_functioning.tex Normal file
View File

@ -0,0 +1,612 @@
\section{How it works}
\label{sec:qr_functioning}
Graphically, QR-Codes are composed of black and white squares called "modules". Each module represents a binary bit, black meaning 1, white meaning 0.
Similarly to \autoref{sec:barcode_functioning}, we will only describe the encoding phase.
To make the following explanations easier to follow, we will use a concrete value.
Let's encode the string "Hello, World!" with level "M" of error correction.
\subsection{Data type}
\label{ssec:qr_data_type}
The first step is to choose the appropriate data type for encoding. In our case, we can't use the numerical format since there are letters, nor can we use the alphanumerical, because of the exclamation mark and lowercase letters. Thus, the most suitable encoding is the byte format.
Note that these different formats exist to optimize encoding to take the least possible space, so it is recommended to choose the minimum required format to avoid unecessarily big QR-Codes.
The first 4 bits of data in our code will be the format used. Numerical is 1, alphanumerical is 2, byte is 4 and kanji is 8. In our case, the format indicator will be "0100".
\subsection{Version}
\label{ssec:qr_version}
QR-Codes come in a number of sizes, called "versions". Version 1 (the smallest) is a 21x21 grid, version 2 a 25x25, version 3 29x29, and so on, up to version 40 (the largest) which is a 157x157 matrix.
To know which size our code will be, we have to refer to table \ref{tab:qr_versions} indicating which version is needed for a certain amount of data.
Our string contains 13 characters, that is 13 bytes (in ISO-8859-1) and will be encoded using the byte format with level "M" of error correction. Thus the final code will be of version 1.
\subsection{Character count indicator}
\label{ssec:qr_char_count_ind}
Before encoding our data, we need to create a header stating the total character count. For that, the length is converted to its binary representation of $n$ bits, where $n$ depends on the version and encoding mode, as shown in table \ref{tab:qr_char_count_len}.
\def\arraystretch{1.5}
\begin{table}[H]
\centering
\begin{tabu}{|[1pt]c|c:c:c:c|[1pt]}
\tabucline[1pt]{-}
Version & Num. & Alpha. & Byte & Kanji\\
\tabucline[1pt]{-}
1 to 9 & 10 & 9 & 8 & 8 \\
\hline
10 to 26 & 12 & 11 & 16 & 10 \\
\hline
27 to 40 & 14 & 13 & 16 & 12 \\
\tabucline[1pt]{-}
\end{tabu}
\caption{Bit length of character count indicator}
\label{tab:qr_char_count_len}
\end{table}
\def\arraystretch{1}
In our example, the number of characters is 13, which means the character count indicator is "00001101".
\subsection{Data encoding}
\label{ssec:qr_encoding}
The following step is to convert the data to binary. The method used differs for each format.
\subsubsection{Numerical}
\label{sssec:qr_encoding_num}
\begin{enumerate}
\item Split the number into 3-digit groups
\item For each group, convert the number to binary, padded to:
\begin{itemize}
\item 10 bits if there are 3 digits (most groups)
\item 7 bits if there are 2 digits (only sometimes for the last group)
\item 4 bits if there is only one digit (only sometimes for the last group)
\end{itemize}
\item Join the resulting bits end to end
\end{enumerate}
\subsubsection{Alphanumerical}
\label{sssec:qr_encoding_alpha}
\begin{enumerate}
\item Split the string into 2-character groups
\item For each group:
\begin{enumerate}
\item Take the first character's index in list \ref{tab:qr_alphanum} and multiply it by 45
\item Take the second character's index in list \ref{tab:qr_alphanum}
\item Add them together and convert the result to an 11-bit number
\end{enumerate}
\item If the string has an odd number of characters, take the index of the last character and convert it to a 6-bit number
\end{enumerate}
\subsubsection{Byte}
\label{sssec:qr_encoding_byte}
\begin{enumerate}
\item Encode the data in ISO-8859-1 (latin-1)
\item Join the 8-bit binary representation of each character end to end
\end{enumerate}
\subsubsection{Kanji}
\label{sssec:qr_encoding_kanji}
\begin{enumerate}
\item Encode the data in JIS X 0208 (each character is encoded on 2 bytes)
\item For each character (= pair of bytes):
\begin{enumerate}
\item If the value is between 0x8140 and 0x9FFC, subtract 0x8140 \\
Otherwise, if the value is between 0xE040 and 0xEBBF, subtract 0xC140
\item Multiply the most significant byte by 0xC0
\item Add the most significant byte to the least significant
\item Join the 13-bit binary representation of each sum end to end
\end{enumerate}
\end{enumerate}
\subsubsection{Example}
\label{sssec:qr_encoding_ex}
For our example, following the byte encoding format, we get:
\def\arraystretch{1.2}
\begin{center}
\begin{tabu}{|c|c|c|c|c|}
\hline
\textbf{01000000} & \textbf{1101}0100 & 10000110 & 01010110 & 11000110 \\
\hline
11000110 & 11110010 & 11000010 & 00000101 & 01110110 \\
\hline
11110111 & 00100110 & 11000110 & 01000010 & 0001 \\
\hline
\end{tabu}
\end{center}
\def\arraystretch{1}
Note that the format and character count indicator have been added at the begginning (bolded bits).
The resulting binary string needs to be padded before continuing to the next step. This is done in a fourfold process:
\begin{enumerate}
\item Get the total number of bits in the final code by multiplying column "Data codewords" of table \ref{tab:qr_error_correction} by eight\footnote{A codeword is equivalent to an 8-bit byte}. Let that be $B$ and let $b$ be the number of data bits we already have
\item Add $B-b$ 0s, but at most 4
\item Add 0s so that $b$ is a multiple of 8, if not already
\item If $b < B$, fill the remaining bits with the alternating bytes "\texttt{11101100}" and "\texttt{00010001}"
\end{enumerate}
In our example, we already have 116 data bits on 128 as per table \ref{tab:qr_error_correction} (version 1, level M). Thus we add four 0s, increasing $b$ to 120. We don't need to add other 0s since it is already a multiple of 8. Finally we fill the remaining 8 bits with the alternating padding bytes. The result is:
\def\arraystretch{1.2}
\begin{center}
\begin{tabu}{|c|c|c|c|c|}
\hline
01000000 & 11010100 & 10000110 & 01010110 & 11000110 \\
\hline
11000110 & 11110010 & 11000010 & 00000101 & 01110110 \\
\hline
11110111 & 00100110 & 11000110 & 01000010 & 00010000 \\
\hline
11101100 & & & & \\
\hline
\end{tabu}
\end{center}
\def\arraystretch{1}
\subsection{Error correction}
\label{ssec:qr_error_corection}
Now that we have encoded our data, we need to create additional error correction codewords. QR-Codes use what is called the Reed-Solomon algorithm to detect and correct potential errors in a scanned code. This algorithm is explained in more details in \autoref{sec:reed_solomon}.
According to table \ref{tab:qr_error_correction}, we need a certain number of error correction codewords (column "Error correction codewords per block"). Let this number be n.
For that, we create a generator polynomial: \[
\prod_{i = 0}^{n-1} (x + 2^i)
\]
Note that the calculation are done in a Galois field, as explained in \autoref{sec:reed_solomon}.
Encoded data now needs to be split in B1 blocks (see table \ref{tab:qr_error_correction}, column "Blocks in group 1"). Each block contains C1 data codewords (see table \ref{tab:qr_error_correction}, column "Data codewords per group 1 blocks").
For each block:
\begin{enumerate}
\item Convert each codeword to its decimal value (in the Galois field) and let that be the coefficients of a "message" polynomial.
\item Divide this polynomial by the generator polynomial created earlier.
\item Convert the coefficients of the remainder to their 8-bit binary representation and let these be the error correction codewords for this block.
\end{enumerate}
If group 2 has a non-null amount of data codewords, do the same steps for column "Blocks in group 2" (B2) and "Data codewords per group 2 blocks" (C2).
For our example, n = 10, B1 = 1, C1 = 16, B2 = 0, C2 = 0.
The generator polynomial has the coefficients: \[
1, 216, 194, 159, 111, 199, 94, 95, 113, 157, 193
\]
We only have one block with 16 codewords, so our "message" polynomial will have the coefficients: \[
64, 212, 134, 86, 198, 198, 242, 194, 5, 118, 247, 38, 198, 66, 16, 236
\]
Dividing it by the generator polynomial, we get a remainder with the coefficients: \[
215, 92, 247, 55, 155, 152, 59, 246, 87, 124
\]
that we convert to binary bytes, giving us:
\def\arraystretch{1.2}
\begin{center}
\begin{tabu}{|c|c|c|c|c|}
\hline
11010111 & 01011100 & 11110111 & 00110111 & 10011011 \\
\hline
10011000 & 00111011 & 11110110 & 01010111 & 01111100 \\
\hline
\end{tabu}
\end{center}
\def\arraystretch{1}
\subsection{Interleaving}
\label{ssec:qr_interleaving}
Now that we have computed the error correction codewords, we need to arrange them in a certain manner with the data codewords. The codewords go in the following order:
%TODO: improve this table
\def\arraystretch{1.2}
\begin{center}
\begin{tabu}{|[2pt]c|c|c|c|c||c|c|c|c|[2pt]}
\tabucline[2pt]{-}
Data & \rot{Codeword 1} & \rot{Codeword 2} & \rot{Codeword 3} & \rot{Codeword 4} & \shortstack[c]{Error \\ correction} & \rot{Codeword 1} & \rot{Codeword 2} & \rot{Codeword 3}\\
\hline
Block 1 & 1 & 5 & 9 & x & Block 1 & 15 & 19 & 23 \\
\hline
Block 2 & 2 & 6 & 10 & x & Block 2 & 16 & 20 & 24 \\
\hline
Block 3 & 3 & 7 & 11 & 13 & Block 3 & 17 & 21 & 25 \\
\hline
Block 4 & 4 & 8 & 12 & 14 & Block 4 & 18 & 22 & 26 \\
%\hline
%... & ... & ... & ... & ... & ... & ... & ... & ... \\
\tabucline[2pt]{-}
\end{tabu}
\end{center}
\def\arraystretch{1}
In this example, B1 = 2, C1 = 3, B2 = 2, C2 = 4.
In our case, since we only have 1 data block, the error correction codewords are simply appended after the data codewords.
Similarly to step \ref{ssec:qr_encoding} (\nameref{ssec:qr_encoding}), we need to pad the end result with a certain number of 0s before continuing. This number is given by the following table:
\def\arraystretch{1.2}
\begin{center}
\begin{tabu}{|[2pt]c|c|[2pt]}
\tabucline[2pt]{-}
Version & Number of 0s to add\\
\hline
2 to 6 & 7 \\
\hline
14 to 20 & 3 \\
\hline
21 to 27 & 4 \\
\hline
28 to 34 & 3 \\
\tabucline[2pt]{-}
\end{tabu}
\end{center}
\def\arraystretch{1}
Our example code is a version 1 so we don't need to add any 0.
\subsection{Separators and finder patterns}
\label{ssec:qr_sep_finder}
Data has been encoded and now starts the placement phase, that is the creation of the black and white matrix.
First of all, the matrix' size (in number of modules) is given by the following formula: \[
(V-1)*4 + 21
\]
where $V$ is the version.
The ISO standard\cite{ISO18004} also states that a 4-module wide margin (silence zone) must be respected all around the code. This allows scanners to easily identify and locate a QR-Code in an image.
The distinctive elements of QR-Codes are of course their three large corner squares. These are called "finder patterns" and are used by the reading device to find the code and correct the perspective. They also provide information on the rotation of the image and the width of individual modules. Finder patterns are 7x7 black squares, containing a 5x5 white square, encircling itself a 3x3 black square. They are put in the top-left, top-right and bottom-left corners of the matrix.
Additionally, they are separated from the rest of the code by a 1-module thick white line called a separator.
For our version 1 QR-Code, this steps yields the following 21x21 matrix:
\begin{figure}[H]
\centering
\includegraphics[width=0.4\textwidth]{images/qr_sep_finder}
\caption{QR-Code example: separators and finder patterns}
\label{fig:qr_sep_finder}
\end{figure}
\subsection{Alignment patterns}
\label{ssec:qr_alignment}
The next element to add are the alignment patterns. These are similar to finder patterns but are only 5x5. They are spread across the whole code and provide reference points for the scannnig device to improve reliability. The larger the code, the more alignment patterns are needed. Their positions depend on the version and are referenced in table \ref{tab:qr_alignment}. This table lists all possible x and y coordinates for the patterns. This means that for version 2, the alignment patterns are located at (6,6), (6,18), (18,6) and (18,18). A pattern will only be present if the area it covers is still empty (i.e. it doesn't overlap with the separators and finder patterns).
Since our example is a version 1 QR-Code, no alignment pattern is needed (see figure \ref{fig:qr_reserved} for version 10 code with alignment patterns).
\subsection{Timing pattern}
\label{ssec:qr_timing}
An additional element helping the scanner and improving readability is the timing pattern. It consists of a alternating black and white stripe joining the bottom-left and top-left finder pattern, and the top-left and top-right.
The pattern is aligned to the right and bottom borders of the top-left finder patterns, starting with white on the separators.
\begin{figure}[H]
\centering
\includegraphics[width=0.4\textwidth]{images/qr_timing}
\caption{QR-Code example: timing patterns}
\label{fig:qr_timing}
\end{figure}
\subsection{Reserved area}
\label{ssec:qr_reserved}
Some areas of the matrix are also reserved for format information which will be added later. This corresponds to the modules around the top-left finder pattern, the modules on the right of the bottom-left pattern and those just below the top-right one.
Additionally, for versions greater than 6, a 3x6 zone on the left of the top-right finder pattern plus a 6x3 above the bottom-left one are reserved for version information.
Figure \ref{fig:qr_reserved} shows an example of these reserved areas (in light gray) for a version 10 QR-Code (left) and for our example (right).
\begin{figure}[H]
\centering
\raisebox{-0.5\height}{\includegraphics[width=0.5\textwidth]{images/qr_reserved_example}}
\raisebox{-0.5\height}{\includegraphics[width=0.4\textwidth]{images/qr_reserved}}
\caption{QR-Code example: reserved areas}
\label{fig:qr_reserved}
\end{figure}
A black module is also set next to the bottom-left finder pattern, on the right of its top-right corner. Its coordinates are the following:
\begin{math}
\begin{cases}
x = 8 \\
y = 4V + 9
\end{cases}
\end{math}
where $V$ is the version.
\subsection{Data placement}
\label{ssec:qr_placement}
The matrix is now ready to receive the data bit string. The placement is done in zigzags, starting from the bottom-right, going up. Each byte is placed in a 2 modules wide region in a staggered manner. When a pattern, separator or reserved area is encountered, the position is skipped and the process continues further.
Figures \ref{fig:qr_plcmt_byte_up} and \ref{fig:qr_plcmt_byte_down} represent the way a byte is layed out when going up or down.
Figures \ref{fig:qr_plcmt_reserved} and \ref{fig:qr_plcmt_turning} show how skipping and turning are processed.
When encountering the vertical timing pattern, that column is entirely skipped and the placement continues on the next one. For each byte, the bits are layed from Most Significant bit (MSB) to Least Significant Bit (LSB).
\begin{figure}[H]
\centering
\begin{subfigure}{0.3\textwidth}
\centering
\includegraphics[width=0.5\textwidth]{images/qr_plcmt_byte_up}
\caption{Going up}
\label{fig:qr_plcmt_byte_up}
\end{subfigure}
\begin{subfigure}{0.3\textwidth}
\centering
\includegraphics[width=0.5\textwidth]{images/qr_plcmt_byte_down}
\caption{Going down}
\label{fig:qr_plcmt_byte_down}
\end{subfigure}
\begin{subfigure}{0.3\textwidth}
\centering
\includegraphics[width=0.5\textwidth]{images/qr_plcmt_reserved}
\caption{Skipping reserved areas}
\label{fig:qr_plcmt_reserved}
\end{subfigure}
\begin{subfigure}{0.3\textwidth}
\centering
\includegraphics[width=0.7\textwidth]{images/qr_plcmt_turning}
\caption{Turning at border}
\label{fig:qr_plcmt_turning}
\end{subfigure}
\caption{QR-Code byte placement}
\label{fig:qr_plcmt_byte}
\end{figure}
\begin{comment}
\begin{figure}[H]
\centering
\begin{subfigure}{0.3\textwidth}
\centering
\includegraphics[width=0.3\textwidth]{images/qr_plcmt_reserved}
\caption{Skipping reserved areas}
\label{fig:qr_plcmt_reserved}
\end{subfigure}
\begin{subfigure}{0.6\textwidth}
\centering
\includegraphics[width=0.5\textwidth]{images/qr_plcmt_turning}
\caption{Turning at border}
\label{fig:qr_plcmt_turning}
\end{subfigure}
\caption{QR-Code byte placement}
\label{fig:qr_plcmt_byte_special}
\end{figure}
\end{comment}
If the available space if not fully filled after placing the data, the rest is filled with 0s\footnote{it shall be recalled that 0 means white and 1 means black}.
Our example QR-Code, once filled with data, looks like this:
\begin{figure}[H]
\centering
\includegraphics[width=0.4\textwidth]{images/qr_plcmt}
\caption{QR-Code example: data placement}
\label{fig:qr_plcmt}
\end{figure}
\subsection{Masking}
\label{ssec:qr_masking}
For optimal readability, it is important that certain patterns of modules don't appear inside the code. For example, there shouldn't be any shape resembling the finder or alignment patterns (i.e. modules with the ratio 1:1:3:1:1). Furthermore, a balanced amount of black compared to white is preferred for better decoding.
For this purpose, we need to apply a mask on the code, switching white for black modules and vice versa where it applies. QR-Codes have 8 different masks which can be used. Obviously, these are only applicable on the data area and should not modify the timing, finder and alignment patterns.
To choose one, we will apply them one after the other on our current QR-Code, and evaluate the resulting code, giving it a penalty score for each undesired feature. Then, the mask with the lowest score will be chosen.
Figure \ref{fig:qr_masks} lists the different possible masks.
A black mask module means that the corresponding data module needs to be inverted.
The operator "//" is integer division.
\begin{figure}[H]
\centering
\begin{subfigure}{0.4\textwidth}
\centering
\includegraphics[width=0.4\textwidth]{images/qr_mask_0}
\caption{(x+y) mod 2 = 0}
\label{fig:qr_mask_0}
\end{subfigure}
\begin{subfigure}{0.4\textwidth}
\centering
\includegraphics[width=0.4\textwidth]{images/qr_mask_1}
\caption{y mod 2 = 0}
\label{fig:qr_mask_1}
\end{subfigure}
\begin{subfigure}{0.4\textwidth}
\centering
\includegraphics[width=0.4\textwidth]{images/qr_mask_2}
\caption{(x) mod 3 = 0}
\label{fig:qr_mask_2}
\end{subfigure}
\begin{subfigure}{0.4\textwidth}
\centering
\includegraphics[width=0.4\textwidth]{images/qr_mask_3}
\caption{(x+y) mod 3 = 0}
\label{fig:qr_mask_3}
\end{subfigure}
\begin{subfigure}{0.4\textwidth}
\centering
\includegraphics[width=0.4\textwidth]{images/qr_mask_4}
\caption{(y//2+x//3) mod 2 = 0}
\label{fig:qr_mask_4}
\end{subfigure}
\begin{subfigure}{0.4\textwidth}
\centering
\includegraphics[width=0.4\textwidth]{images/qr_mask_5}
\caption{((x*y) mod 2 + (x*y) mod 3) = 0}
\label{fig:qr_mask_5}
\end{subfigure}
\begin{subfigure}{0.4\textwidth}
\centering
\includegraphics[width=0.4\textwidth]{images/qr_mask_6}
\caption{((x*y) mod 2 + (x*y) mod 3) mod 2 = 0}
\label{fig:qr_mask_6}
\end{subfigure}
\begin{subfigure}{0.4\textwidth}
\centering
\includegraphics[width=0.4\textwidth]{images/qr_mask_7}
\caption{((x+y) mod 2 + (x*y) mod 3) mod 2 = 0}
\label{fig:qr_mask_7}
\end{subfigure}
\caption{QR-Code masks}
\label{fig:qr_masks}
\end{figure}
\subsubsection{Evaluation}
\label{sssec:qr_mask_eval}
Evaluation of a mask is done thanks to 4 criteria.
They evaluate whether the code is easy to read or not. For example, criterion 3 gives a high penalty for every pattern with the same proportions as finder patterns to avoid confusion for the reading device.
Before applying a mask for evaluation, format and version information have do be added on the code, as described in sections \ref{ssec:qr_fmt_info} and \ref{ssec:qr_ver_info}.
\begin{enumerate}
\item 5+ consecutive modules of the same color:
If a line or column of 5 or more modules of the same color is found in the code, a penalty score is added. For a strip of $5 + i$ same colored modules, the penalty is worth $3 + i$ points.
\item 2x2 blocks:
Each 2x2 block of similar modules adds 3 points to the penalty score. Overlapping blocks are taken into account.
\item 1:1:3:1:1:4 patterns:
For each pattern with the ratios 1:1:3:1:1:4 or 4:1:1:3:1:1, a penalty of 40 points is given. This criterion takes into account the 4-module wide margins all around the code\footnote{see \autoref{ssec:qr_sep_finder}}. This means there are at least 18 correspondances in every code.
\item Proportion of black and white modules:
A penalty is attributed according to the deviation from a 50/50 distribution in black and white modules across the whole QR-Code. The calculation method is the following:
\[ P = \lfloor 100 * B / (W*H) \rfloor \]
\[ P_1 = P - P \textrm{ mod } 5 \]
\[ P_2 = P_1 + 5 \]
\[ S = \textrm{min}\left(\frac{|P_1 - 50|}{5}, \frac{|P_2 - 50|}{5}\right) * 10 \]
\end{enumerate}
where $B$ is the total number of black modules, $W$ and $H$ are the width and height of the QR-Code, and $S$ is the penalty score given for this criterion.
Applying this to our QR-Code, we can determine that the best mask is mask \ref{fig:qr_mask_5} with a score of 442. Figures \ref{fig:qr_mask_ex_eval_1} to \ref{fig:qr_mask_ex_eval_4} detail the penalties for each criterion.
\begin{figure}[H]
\centering
\begin{subfigure}{0.45\textwidth}
\centering
\includegraphics[width=\textwidth]{images/qr_mask_ex_eval_1}
\caption{QR-Code example: mask evaluation (1)}
\label{fig:qr_mask_ex_eval_1}
\end{subfigure}
\begin{subfigure}{0.45\textwidth}
\centering
\includegraphics[width=\textwidth]{images/qr_mask_ex_eval_2}
\caption{QR-Code example: mask evaluation (2)}
\label{fig:qr_mask_ex_eval_2}
\end{subfigure}
\begin{subfigure}{0.45\textwidth}
\centering
\includegraphics[width=\textwidth]{images/qr_mask_ex_eval_3}
\caption{QR-Code example: mask evaluation (3)}
\label{fig:qr_mask_ex_eval_3}
\end{subfigure}
\begin{subfigure}{0.45\textwidth}
\centering
\includegraphics[width=\textwidth]{images/qr_mask_ex_eval_4}
\caption{QR-Code example: mask evaluation (4)}
\label{fig:qr_mask_ex_eval_4}
\end{subfigure}
\caption{QR-Code example: mask evaluation}
\label{fig:qr_mask_ex_eval}
\end{figure}
\subsection{Format information}
\label{ssec:qr_fmt_info}
The last step to complete a fully functional QR-Code is to add the format information, and version information for versions bigger than 6.
First we need to create a format string containing the level of error correction and the mask used. The correction level is encoded on 2 bits as follows:
\begin{table}[H]
\centering
\begin{tabu}{|c|c|}
\hline
Level & Value (bin) \\
\hline
L & 01 \\
\hline
M & 00 \\
\hline
Q & 11 \\
\hline
H & 10 \\
\hline
\end{tabu}
\caption{QR-Code error correction level indicator}
\label{tab:qr_ec_ind}
\end{table}
Then the mask id is converted to a 3-bit binary number and appended to the error correction level indicator.
The string is padded by an additional 10 0s to make it 15 bits long.
Similarly to what has been done with data previously, the format string is complemented with error correction bits, this time using Bose-Chaudhuri-Hocquenghem (BCH) codes.
The principle is similar to the Reed-Solomon algorithm in that a message polynomial is divided by a generator polynomial. To create the message polynomial, each bit of the format string represents the coefficient of a term. The same applies for the generator polynomial, which is always derived from the binary number \texttt{10100110111}.
The generator polynomial is thus \[
x^{10} + x^8 + x^5 + x^4 + x^2 + x + 1
\]
The division's remainder is then padded on the left with 0s to make it 10 bits long.
The final bit string is constructed by concatenating the format string with the error correction bits, and XORing\footnote{With the binary XOR operator} the result with the mask string \texttt{101010000010010}. This mask ensures the final format string is not made of only 0s.
Once calculated, the bit string is layed out in the reserved strips around the finder patterns, as shown in figure \ref{fig:qr_fmt_layout} (0 being the LSB and 14 the MSB)
\begin{figure}[H]
\centering
\includegraphics[width=0.4\textwidth]{images/qr_fmt_layout}
\caption{QR-Code format information string layout}
\label{fig:qr_fmt_layout}
\end{figure}
It is to be noted that format information appears twice, since its decoding is essential for reading the whole code.
In our case, the error correction indicator for level M is 00 and we used the mask with id 2, so our format string is \texttt{000100000000000}. Dividing it by the generator polynomial yields the remainder \texttt{1001101110}.
Adding it to the format string and XORing it with the mask string, we get \texttt{101111001111100}.
These bits are then put in the reserved areas of the matrix, making figure \ref{fig:qr_fmt_info}
\begin{figure}[H]
\centering
\includegraphics[width=0.4\textwidth]{images/qr_fmt_info}
\caption{QR-Code example: format information}
\label{fig:qr_fmt_info}
\end{figure}
Our QR-Code is now fully finished and can be scanned.
\subsection{Version information}
\label{ssec:qr_ver_info}
For QR-Codes of version 7 and bigger, additional data is added to state the code's version.
To generate the version string, first convert the version to its 6 bit binary representation. Then append the remainder of the division by the generator polynomial \[
x^{12} + x^{11} + x^{10} + x^9 + x^8 + x^5 + x^2 + 1
\] padded on the left to 12 bits, following the same methods as for format information.
This string is then put in the two reserved 6x3 and 3x6 rectangles. The LSB is placed in the top-left corner of the rectangles. For the top-right area, the string goes down, then to the right. For the bottom-left rectangle, the string goes to the right, then down.

72
latex/qr_origin.tex Normal file
View File

@ -0,0 +1,72 @@
\chapter{QR-Codes}
\label{chap:qr_origin}
\section{Origin}
\label{sec:qr_origin}
Eventhough barcodes have conquered the world, they still have some major issues.
First of all, their capacity is rather limited, allowing only a maximum of about twenty characters to be encoded in a practical format.
Secondly, they can only store a small group of characters, some even only numbers.
And finally, they require the reading device to be in a roughly parallel orientation with respect to the code in order to read it.
For these reasons, the Japanese company named DENSO WAVE started developing a new type of 2D code.
Indeed, a barcode is one-dimensional - that is, the information is encoded on a single axis.
What DENSO WAVE tried was a two-dimensional matrix of data.
The result of their research and development became the well-known "QR-Code", which stands for Quick-Response code. They were first used in Toyota factories to track car parts\cite{qrcode_history}.
With the desire to offer this technology to the largest number of people, the company decided not to keep it private but rather make it open-source.
It later became a norm in many countries and is now specified by the ISO/IEC~18004 standard \cite{ISO18004}.
QR-Codes address all the above-mentioned problems related to barcodes.
\subsection*{Data type}
They allow the encoding of either numbers, text, raw binary data and even Kanji characters\footnote{Kanji are the characters used to write Japanese}. Therefore they can be used in a great variety of contexts, for example to identify objects, to conviniently share a URL or even a small image.
\subsection*{Information density}
They allow dense information storage.
\def\arraystretch{1.5}
\begin{table}[H]
\centering
\begin{tabu}{|[2pt]c|c|[2pt]}
\tabucline[2pt]{-}
Type & Number of "characters" \\
\tabucline[2pt]{-}
Numerical & 7'089 \\
\hline
Alphanumerical & 4'296 \\
\hline
Bytes & 2'953 \\
\hline
Kanji & 1'817 \\
\tabucline[2pt]{-}
\end{tabu}
\caption{Maximum amount of data in a QR-Code \cite{ISO18004}}
\label{tab:qr_max_data}
\end{table}
\def\arraystretch{1}
The alphanumerical type can only encode the following characters:
\begin{center}
\texttt{0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ \$\%*+-./:}
\end{center}
\subsection*{Reading freedom}
Thanks to finder patterns, they can be read in any orientation, even with perspective. Taking into account the great progress which happened during the last 30 years in the field of cameras and mobile phones, QR-Codes can now be scanned in a matter of milliseconds, no matter the angle of the camera.
\subsection*{Error detection and correction}
The other main advantage of QR-Codes is the embedded error detection and correction system.
They come in four different levels of error correction:
\begin{itemize}
\item L (low): 7\%
\item M (medium): 15\%
\item Q (quartile): 25\%
\item H (high): 30\%
\end{itemize}
A higher level indicates a greater amount of redundancy and an ability to recover a greater part of a damaged code. This principle is often used to add a custom icon at the center of the QR-Code, since the reader will still be able to scan it and reconstruct the hidden part.

163
latex/qr_python.tex Normal file
View File

@ -0,0 +1,163 @@
\section{Application in Python}
\label{sec:qr_python}
In this section, we will look at my Python QR-Code generator implementation.
For the sake of brevity, only some specific parts of the program will be commented.
\subsection{Python features}
\label{ssec:qr_py_features}
The script takes advantage of several Python-specific features.
\subsubsection{Dunder methods}
\label{sssec:qr_py_dunder}
The most important is the "dunder methods", short for "double underscore methods".
These are special overridable methods used for builtin behaviors.\\
For example, the \texttt{\_\_add\_\_}, \texttt{\_\_sub\_\_}, \texttt{\_\_mul\_\_}, \texttt{\_\_truediv\_\_} and \texttt{\_\_pow\_\_} methods respectively
define the behavior of the "+", "-", "*", "/" and "**" operators.
This is particulary useful to create the Galois field's arithmetic used for QR-Codes.
For example, multiplication is defined by this method:
\begin{minted}[frame=single]{python}
def __mul__(self, n):
if self.val == 0 or n.val == 0:
return GF(0)
return GF.EXP[GF.LOG[self.val].val + GF.LOG[n.val].val].copy()
\end{minted}
where \texttt{val} is the element's value and \texttt{GF.LOG} and \texttt{GF.EXP} are arrays containing the values of exponents and logarithms for the field (see \autoref{ssec:qr_py_precomp}).
\subsubsection{Anonymous functions}
\label{sssec:qr_py_lambda}
Anonymous, or lambda, functions are short unnamed functions. They are often used for very basic operations. In our case, they are utilized for masks.
For example, the first mask is defined as \mintinline{python}{lambda x,y: (x+y)%2 == 0}, a function taking two arguments x and y and returning whether the coordinates should be masked or not.
\subsection{Precomputed data}
\label{ssec:qr_py_precomp}
Some values related to the creation of QR-Codes are precomputed, such as the capacities for each data type or the number of error correction codewords, as determining them is done through reverse engineering and no simple direct formula can be established.
These values are stored in text files (\texttt{error\_correction.txt} and \texttt{qr\_versions.txt}) and loaded into tables at the beginning of the scripts.
Regarding Galois fields, all powers and logs are also calculated beforehand, for the sake of ease of use, using the following loops:
\begin{minted}[frame=single]{python}
class GF:
def __init__(self, val):
self.val = val
...
GF.EXP = [GF(0)]*512
GF.LOG = [GF(0)]*256
value = 1
for exponent in range(255):
GF.LOG[value] = GF(exponent)
GF.EXP[exponent] = GF(value)
value = ((value << 1) ^ 285) if value > 127 else value << 1
for i in range(255, 512):
GF.EXP[i] = GF.EXP[i-255].copy()
\end{minted}
Credits for this method goes to \citetitle{rs_for_coders}\cite[Multiplication with logarithms]{rs_for_coders}
\subsection{Data placement}
\label{ssec:qr_py_plcmt}
One of the challenges to overcome was the data placement phase. To avoid lengthening this particular part, mathematical tricks are used.
For visual aid, see figure \ref{fig:qr_plcmt_byte} in \autoref{ssec:qr_placement}.
Before starting, the data bit string which will be placed is stored in a string variable named "\texttt{self.final\_data\_bits}". The position is set to the lower-right corner of the matrix.
The matrix (\texttt{self.matrix}) is a 2D array in which -1 indicates a free module.
A variable named \texttt{dir\_} is also set to -1 and is responsible to keep track wether we are going up or down. A variable \texttt{i} initialized to 0 will hold the index of the current bit to be placed. The variable \texttt{zigzag} manages the zigzag pattern.
\begin{minted}[linenos=true,frame=single]{python}
dir_ = -1 #-1 = up | 1 = down
x, y = size-1, size-1
i = 0
zigzag = 0
while x >= 0:
if self.matrix[y,x] == -1:
self.matrix[y,x] = self.final_data_bits[i]
i += 1
if ((dir_+1)/2 + zigzag)%2 == 0:
x -= 1
else:
y += dir_
x += 1
if y == -1 or y == size:
dir_ = -dir_
y += dir_
x -= 2
else:
zigzag = 1-zigzag
if x == 6:
x -= 1
\end{minted}
The algorithm runs until it reaches the left side (line 6).
For each loop, if the module is free, the current bit is placed and \texttt{i} is incremented.
If we are going up and \texttt{zigzag} equals 0, or if we are going down and zigzag equals 1, then we move to the left (line 11-12).\\
Otherwise, we move forward in the current direction and one module to the right.
If we reach the top or bottom side (current position is outside of the matrix), the direction is flipped, we come back one step and move to the left.
Lines 26-27 make the placement entirely skip column 6, which is where the vertical timing pattern is located.
Table \ref{tab:qr_py_plcmt} shows the evolution of the different variables during placement.
\def\arraystretch{1.2}
\begin{table}[H]
\centering
\begin{tabu}{|[2pt]c|c|c|c|[2pt]}
\tabucline[2pt]{c}
x & y & dir\_ & zigzag \\
\tabucline[2pt]{c}
20 & 20 & -1 & 0 \\
\hline
19 & 20 & -1 & 1 \\
\hline
20 & 19 & -1 & 0 \\
\hline
19 & 19 & -1 & 1 \\
\hline
20 & 18 & -1 & 0 \\
\hline
... & ... & ... & ... \\
\hline
20 & 0 & -1 & 0 \\
\hline
19 & 0 & -1 & 1 \\
\hline
18 & 0 & 1 & 1 \\
\hline
17 & 0 & 1 & 0 \\
\hline
18 & 1 & 1 & 1 \\
\tabucline[2pt]{c}
\end{tabu}
\caption{QR-Code data placement algorithm}
\label{tab:qr_py_plcmt}
\end{table}
\def\arraystretch{1}
\subsection{Mask evaluation}
\label{ssec:qr_py_mask}
Mask evaluation is quite straight-forward especially for criteria 1, 2 and 4 (see \autoref{sssec:qr_mask_eval})
Criterion n° 3 is a bit more complex. To keep track of the patterns encountered in each row (or column), a \texttt{History} object is used. This object holds a list of widths of the different color zones.

13
latex/review.tex Normal file
View File

@ -0,0 +1,13 @@
\chapter{Personal review}
\label{chap:review}
My interest for computers and programming has led me to choose this topic for my work and it is not a choice I regret.
QR-Codes were a subject I personally wanted to understand for some time but never got round to it. Having the possibility to use my knowledge in Python to apply what I had researched was also very satisfying and self-rewarding. I hope I succeeded in trying to explain the inner workings and principles making these great inventions possible. I appreciated the great freedom both on the subject's choice and the realization. Apart from learning about barcodes and QR-Codes, I have also had the opportunity to put to use my English language skills.
Using \LaTeX\ helped practising problem solving as many things can be done in multiple ways and I needed to find the best or easiest method, especially regarding tables.
The Python scripts also represent a great part of this work. While not perfect implementations, they offer a better understanding of the sometimes abstract concepts discussed and allow the generation steps to be visualized personally, using any value.
Overall, I am pleased with the fruit of my research and understanding. My only regret would be that I have not yet been able to fully understand the intricacies of Reed-Solomon codes, but I hope that one day I will.
Finally, I would like to thank Mr. Erspamer for his availability, his support and his insightful feedbacks. I must also credit my brother, who allowed me to obtain some documents such as the ISO standards\cite{ISO16388}\cite{ISO18004} and who helped with the printing of my work.

33
latex/title_toc.tex Normal file
View File

@ -0,0 +1,33 @@
\begin{titlepage}
\begin{center}
\Huge \textbf{Barcodes and QR-Codes}
\large \textbf{Examples of error detection and correction}
\vspace{1cm}
\LARGE Louis Heredero
\large 4D-5D
\includegraphics[width=0.7\linewidth]{images/lycacode_ex_final}
\Large Accompanying teacher: Daniel Erspamer
\vspace{2cm}
\LARGE Maturity work 2022-2023
\vspace{1cm}
\Large Lycée-Collège de l'Abbaye \\ 1890 St-Maurice
\end{center}
\end{titlepage}
\begin{abstract}
This work focuses on the creation of barcodes and QR-Codes. It describes and explains the different data encodings and algorithms which make such technologies possible. Following the introduction, the second part is about Code-39 and EAN barcodes, and the third about QR-Codes. Then, the fourth chapter presents in more details some methods for error detection and correction. The final section introduces a new custom type of code named Lycacode which relies upon some aspects seen in the previous three chapters. Additionally, many parts are implemented in Python like the generation of QR-Codes and barcodes for example. These can either be found in the appendices or in the associated \hreffn{https://github.com/LordBaryhobal/5D\_Heredero\_Louis\_TM2022/tree/main/python}{GitHub repository}.
\end{abstract}
\tableofcontents
\listoffigures
\listoftables

55
python/base.py Normal file
View File

@ -0,0 +1,55 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This module provides a base class to display codes and enable saving
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
import pygame
class Base:
def __init__(self, width, height, caption):
pygame.init()
pygame.display.set_caption(caption)
self.w = pygame.display.set_mode([width, height])
self.controls([
"CTRL + S: save as",
"ESC: quit"
])
def controls(self, controls, margin=2):
longest = max(list(map(len, controls))+[10])
print("┌─" + ""*(longest+margin) + "─┐")
_ = "\x1b[1;4mControls:\x1b[0m"
_ += " "*(longest+margin-9)
print(f"" + _ + "")
for c in controls:
print("" + " "*margin + c.ljust(longest) + "")
print("└─" + ""*(longest+margin) + "─┘")
def main(self):
pygame.display.flip()
stop = False
while not stop:
event = pygame.event.wait()
# ESC or close button -> quit
if event.type == pygame.QUIT:
stop = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
stop = True
# CTRL+S -> save image
elif event.key == pygame.K_s and \
event.mod & pygame.KMOD_CTRL:
self.save()
def save(self):
path = input("Save as: ")
pygame.image.save(self.w, path)

68
python/code39.py Normal file
View File

@ -0,0 +1,68 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This module can generate Code-39 barcodes
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
import pygame
code39_dict = {
"A": "100001001", "B": "001001001",
"C": "101001000", "D": "000011001",
"E": "100011000", "F": "001011000",
"G": "000001101", "H": "100001100",
"I": "001001100", "J": "000011100",
"K": "100000011", "L": "001000011",
"M": "101000010", "N": "000010011",
"O": "100010010", "P": "001010010",
"Q": "000000111", "R": "100000110",
"S": "001000110", "T": "000010110",
"U": "110000001", "V": "011000001",
"W": "111000000", "X": "010010001",
"Y": "110010000", "Z": "011010000",
"0": "000110100", "1": "100100001",
"2": "001100001", "3": "101100000",
"4": "000110001", "5": "100110000",
"6": "001110000", "7": "000100101",
"8": "100100100", "9": "001100100",
" ": "011000100", "-": "010000101",
"$": "010101000", "%": "000101010",
".": "110000100", "/": "010100010",
"+": "010001010", "*": "010010100"
}
def code39(text):
text = text.upper()
text = map(lambda c: code39_dict[c], text)
return "0".join(text)
def draw_barcode(barcode, win):
barcode = list(map(int, barcode))
width = win.get_width()*0.8
height = win.get_height()*0.5
thicks = sum(barcode)
thins = len(barcode)-thicks
bar_w = width/(thicks*2+thins)
win.fill((255,255,255))
x = win.get_width()*0.1
y = win.get_height()*0.25
for i, c in enumerate(barcode):
w = 2*bar_w if c else bar_w
if i%2 == 0:
pygame.draw.rect(win, (0,0,0), [x, y, w, height])
x += w
if __name__ == "__main__":
import base
b = base.Base(800, 500, "Code-39 barcode generator")
barcode = code39("*CODE-39*")
draw_barcode(barcode, b.w)
b.main()

116
python/ean.py Normal file
View File

@ -0,0 +1,116 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This module can generate EAN-8 and EAN-13 barcodes
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
import pygame
A = [
0b0001101,
0b0011001,
0b0010011,
0b0111101,
0b0100011,
0b0110001,
0b0101111,
0b0111011,
0b0110111,
0b0001011
]
# XOR 0b1111111
C = list(map(lambda a: a^127, A))
# Reverse bit order
B = list(map(lambda c: int(f"{c:07b}"[::-1], 2), C))
ean13_patterns = [
"AAAAAA",
"AABABB",
"AABBAB",
"AABBBA",
"ABAABB",
"ABBAAB",
"ABBBAA",
"ABABAB",
"ABABBA",
"ABBABA"
]
def bin_list(n):
return list(map(int, f"{n:07b}"))
def luhn(digits):
checksum = sum([
digits[-i-1]*(3-i%2*2)
for i in range(len(digits))
])
ctrl_key = 10 - checksum%10
if ctrl_key == 10:
ctrl_key = 0
return ctrl_key
def ean8(digits):
digits.append(luhn(digits))
elmts = []
elmts += [1,0,1] #delimiter
for digit in digits[:4]:
elmts += bin_list(A[digit])
elmts += [0,1,0,1,0] #middle delimiter
for digit in digits[4:]:
elmts += bin_list(C[digit])
elmts += [1,0,1] #delimiter
return elmts
def ean13(digits):
pattern = ean13_patterns[digits[0]]
digits.append(luhn(digits))
elmts = []
elmts += [1,0,1] #delimiter
for d in range(1,7):
_ = A if pattern[d-1] == "A" else B
digit = digits[d]
elmts += bin_list(_[digit])
elmts += [0,1,0,1,0] #middle delimiter
for digit in digits[7:]:
elmts += bin_list(C[digit])
elmts += [1,0,1] #delimiter
return elmts
def draw_barcode(barcode, win):
width = win.get_width()*0.8
height = win.get_height()*0.5
bar_w = width/len(barcode)
rnd_bar_w = round(bar_w)
win.fill((255,255,255))
x = win.get_width()*0.1
y = win.get_height()*0.25
for c in barcode:
if c:
pygame.draw.rect(win, (0,0,0), [x, y, rnd_bar_w, height])
x += bar_w
if __name__ == "__main__":
import base
b = base.Base(800, 500, "EAN-8 / EAN-13 barcode generator")
#barcode = ean8([8,4,2,7,3,7,2])
barcode = ean13([9,7,8,2,9,4,0,6,2,1,0,5])
draw_barcode(barcode, b.w)
b.main()

199
python/error_correction.txt Normal file
View File

@ -0,0 +1,199 @@
19 7 1 19
16 10 1 16
13 13 1 13
9 17 1 9
34 10 1 34
28 16 1 28
22 22 1 22
16 28 1 16
55 15 1 55
44 26 1 44
34 18 2 17
26 22 2 13
80 20 1 80
64 18 2 32
48 26 2 24
36 16 4 9
108 26 1 108
86 24 2 43
62 18 2 15 2 16
46 22 2 11 2 12
136 18 2 68
108 16 4 27
76 24 4 19
60 28 4 15
156 20 2 78
124 18 4 31
88 18 2 14 4 15
66 26 4 13 1 14
194 24 2 97
154 22 2 38 2 39
110 22 4 18 2 19
86 26 4 14 2 15
232 30 2 116
182 22 3 36 2 37
132 20 4 16 4 17
100 24 4 12 4 13
274 18 2 68 2 69
216 26 4 43 1 44
154 24 6 19 2 20
122 28 6 15 2 16
324 20 4 81
254 30 1 50 4 51
180 28 4 22 4 23
140 24 3 12 8 13
370 24 2 92 2 93
290 22 6 36 2 37
206 26 4 20 6 21
158 28 7 14 4 15
428 26 4 107
334 22 8 37 1 38
244 24 8 20 4 21
180 22 12 11 4 12
461 30 3 115 1 116
365 24 4 40 5 41
261 20 11 16 5 17
197 24 11 12 5 13
523 22 5 87 1 88
415 24 5 41 5 42
295 30 5 24 7 25
223 24 11 12 7 13
589 24 5 98 1 99
453 28 7 45 3 46
325 24 15 19 2 20
253 30 3 15 13 16
647 28 1 107 5 108
507 28 10 46 1 47
367 28 1 22 15 23
283 28 2 14 17 15
721 30 5 120 1 121
563 26 9 43 4 44
397 28 17 22 1 23
313 28 2 14 19 15
795 28 3 113 4 114
627 26 3 44 11 45
445 26 17 21 4 22
341 26 9 13 16 14
861 28 3 107 5 108
669 26 3 41 13 42
485 30 15 24 5 25
385 28 15 15 10 16
932 28 4 116 4 117
714 26 17 42
512 28 17 22 6 23
406 30 19 16 6 17
1006 28 2 111 7 112
782 28 17 46
568 30 7 24 16 25
442 24 34 13
1094 30 4 121 5 122
860 28 4 47 14 48
614 30 11 24 14 25
464 30 16 15 14 16
1174 30 6 117 4 118
914 28 6 45 14 46
664 30 11 24 16 25
514 30 30 16 2 17
1276 26 8 106 4 107
1000 28 8 47 13 48
718 30 7 24 22 25
538 30 22 15 13 16
1370 28 10 114 2 115
1062 28 19 46 4 47
754 28 28 22 6 23
596 30 33 16 4 17
1468 30 8 122 4 123
1128 28 22 45 3 46
808 30 8 23 26 24
628 30 12 15 28 16
1531 30 3 117 10 118
1193 28 3 45 23 46
871 30 4 24 31 25
661 30 11 15 31 16
1631 30 7 116 7 117
1267 28 21 45 7 46
911 30 1 23 37 24
701 30 19 15 26 16
1735 30 5 115 10 116
1373 28 19 47 10 48
985 30 15 24 25 25
745 30 23 15 25 16
1843 30 13 115 3 116
1455 28 2 46 29 47
1033 30 42 24 1 25
793 30 23 15 28 16
1955 30 17 115
1541 28 10 46 23 47
1115 30 10 24 35 25
845 30 19 15 35 16
2071 30 17 115 1 116
1631 28 14 46 21 47
1171 30 29 24 19 25
901 30 11 15 46 16
2191 30 13 115 6 116
1725 28 14 46 23 47
1231 30 44 24 7 25
961 30 59 16 1 17
2306 30 12 121 7 122
1812 28 12 47 26 48
1286 30 39 24 14 25
986 30 22 15 41 16
2434 30 6 121 14 122
1914 28 6 47 34 48
1354 30 46 24 10 25
1054 30 2 15 64 16
2566 30 17 122 4 123
1992 28 29 46 14 47
1426 30 49 24 10 25
1096 30 24 15 46 16
2702 30 4 122 18 123
2102 28 13 46 32 47
1502 30 48 24 14 25
1142 30 42 15 32 16
2812 30 20 117 4 118
2216 28 40 47 7 48
1582 30 43 24 22 25
1222 30 10 15 67 16
2956 30 19 118 6 119
2334 28 18 47 31 48
1666 30 34 24 34 25
1276 30 20 15 61 16

75
python/hamming.py Normal file
View File

@ -0,0 +1,75 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This module provides encoding and decoding functions for Hamming codes
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
class HammingError(Exception):
pass
def encode(data, blocksize=7):
result = []
datasize = blocksize-blocksize.bit_length()
data = list(map(int, data))
if len(data) % datasize:
raise HammingError(f"Length of data is not a multiple of {datasize}")
nblocks = int(len(data)/datasize)
for b in range(nblocks):
for i in range(blocksize):
# Power of 2
if (i+1)&i == 0 or i == 0:
result.append(0)
else:
result.append(data.pop(0))
for i in range(blocksize.bit_length()):
p = 1 << i
c = sum([result[b*blocksize+j] for j in range(blocksize) if (j+1)&p])
if c%2:
result[b*blocksize+p-1] = 1
return "".join(list(map(str, result)))
def decode(data, blocksize=7):
result = []
datasize = blocksize-blocksize.bit_length()
data = list(map(int, data))
if len(data) % blocksize:
raise HammingError(f"Length of data is not a multiple of {blocksize}")
nblocks = int(len(data)/blocksize)
errors = 0
for b in range(nblocks):
pos = 0
for i in range(blocksize.bit_length()):
p = 1 << i
c = sum([data[b*blocksize+j] for j in range(blocksize) if (j+1)&p])
if c%2:
pos |= p
if pos != 0:
if pos > blocksize:
raise HammingError("Too many errors")
return
errors += 1
data[b*blocksize+pos-1] = 1-data[b*blocksize+pos-1]
for i in range(1, blocksize):
if (i+1)&i != 0:
result.append(data[b*blocksize+i])
return "".join(list(map(str, result))), errors
if __name__ == "__main__":
#print("10011010")
print(encode("10011010"))
#print(decode("011100101010"))
print(decode("00110011011010101101001010101011010101010111001100110011"))
print(decode("01000011011010101100001010101011110101000111001100111011"))

66
python/img_gen/hamming.py Normal file
View File

@ -0,0 +1,66 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This module provides encoding and decoding functions for Hamming codes
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
class HammingError(Exception):
pass
def encode(data, blocksize=7):
result = []
datasize = blocksize-blocksize.bit_length()
data = list(map(int, data))
if len(data) % datasize:
raise HammingError(f"Length of data is not a multiple of {datasize}")
nblocks = int(len(data)/datasize)
for b in range(nblocks):
for i in range(blocksize):
# Power of 2
if (i+1)&i == 0 or i == 0:
result.append(0)
else:
result.append(data.pop(0))
for i in range(blocksize.bit_length()):
p = 1 << i
c = sum([result[b*blocksize+j] for j in range(blocksize) if (j+1)&p])
if c%2:
result[b*blocksize+p-1] = 1
return "".join(list(map(str, result)))
def decode(data, blocksize=7):
result = []
datasize = blocksize-blocksize.bit_length()
data = list(map(int, data))
if len(data) % blocksize:
raise HammingError(f"Length of data is not a multiple of {blocksize}")
nblocks = int(len(data)/blocksize)
for b in range(nblocks):
pos = 0
for i in range(blocksize.bit_length()):
p = 1 << i
c = sum([data[b*blocksize+j] for j in range(blocksize) if (j+1)&p])
if c%2:
pos |= p
if pos != 0:
if pos > blocksize:
raise HammingError("Too many errors")
return
data[b*blocksize+pos-1] = 1-data[b*blocksize+pos-1]
for i in range(1, blocksize):
if (i+1)&i != 0:
result.append(data[b*blocksize+i])
return "".join(list(map(str, result)))

View File

@ -0,0 +1,276 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Generates Lycacode data layout matrix figure
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
import pygame
import numpy as np
import hamming
S = 600
class LycacodeError(Exception):
pass
class Lycacode:
RES = 3
BLOCKSIZE = 7
MODE_PERSON = 0
MODE_LOC = 1
MODE_LINK = 2
MODE_TEXT = 3
PERSON_STUDENT = 0
PERSON_TEACHER = 1
PERSON_OTHER = 2
BLACK = (158,17,26)
#BLACK = (0,0,0)
WHITE = (255,255,255)
OFFSETS = [(0,-1), (1,0), (0,1), (-1,0)]
MASKS = [
lambda x, y: x%3 == 0,
lambda x, y: y%3 == 0,
lambda x, y: (x+y)%3 == 0,
lambda x, y: (x%3)*(y%3)==0,
lambda x, y: (y//3+x//3)%2==0,
lambda x, y: (y%3-1)*(x%3-y%3-2)*(y%3-x%3-2)==0,
lambda x, y: (abs(13-x)+abs(13-y))%3==1,
lambda x, y: (1-x%2 + max(0, abs(13-y)-abs(13-x))) * (1-y%2 + max(0,abs(13-x)-abs(13-y))) == 0
]
MASK = True
def __init__(self, data, mode):
self.data = data
self.mode = mode
self.encode()
self.create_matrix()
def encode(self):
self.bits = f"{self.mode:02b}"
if self.mode == self.MODE_PERSON:
type_ = self.data["type"]
id_ = self.data["id"]
self.bits += f"{type_:02b}"
self.bits += f"{id_:020b}"
if type_ == self.PERSON_STUDENT:
year = self.data["year"]
class_ = self.data["class"]
self.bits += f"{year:03b}"
self.bits += f"{class_:04b}"
in1, in2 = self.data["initials"]
in1, in2 = ord(in1)-ord("A"), ord(in2)-ord("A")
self.bits += f"{in1:05b}"
self.bits += f"{in2:05b}"
# 83 left
elif type_ == self.PERSON_TEACHER:
# 100 left
pass
elif type_ == self.PERSON_OTHER:
# 100 left
pass
elif self.mode == self.MODE_LOC:
section = self.data["section"]
room = self.data["room"]
self.bits += f"{section:03b}"
self.bits += f"{room:08b}"
# 107 left
elif self.mode == self.MODE_LINK:
self.bits += f"{self.data:032b}"
# 86 left
elif self.mode == self.MODE_TEXT: # max 13 chars
data = self.data.encode("utf-8")
self.bits += f"{len(data):07b}"
self.bits += "".join(list(map(lambda b: f"{b:08b}", data)))
# 7 left
else:
raise LycacodeError(f"Invalid mode {self.mode}")
ds = self.BLOCKSIZE-self.BLOCKSIZE.bit_length()
total_bits = (self.RES**2 * 24 - 6)
data_bits = total_bits * ds // self.BLOCKSIZE
self.bits += "0"*(ds-len(self.bits)%ds)
s = ""
i = 0
left = data_bits-len(self.bits)
while len(s) < left:
s += f"{i:0b}"
i += 1
s = s[:left]
self.bits += s
self.bits = hamming.encode(self.bits, self.BLOCKSIZE)
def create_matrix(self):
R = self.RES
self.matrix = np.zeros([R*9, R*9])-3
self.matrix[R*4:R*5, :] = -1
self.matrix[:, R*4:R*5] = -1
self.matrix[R:R*2, R*3:R*6] = -1
self.matrix[R*3:R*6, R:R*2] = -1
self.matrix[-R*2:-R, -R*6:-R*3] = -1
self.matrix[-R*6:-R*3, -R*2:-R] = -1
self.matrix[R*4:R*5,R*4:R*5] = 0
self.matrix[0, R*4:R*5] = -2 # mask
self.matrix[-1, R*4:R*5] = -2 # mask
mask_area = np.where(self.matrix == -1, 1, 0)
self.matrix[R*4, R*4+1:R*5-1] = 1
self.matrix[R*5-1, R*4] = 1
self.matrix[R*4+1:R*5-1, R*4+1:R*5-1] = 1
bits = list(map(int, self.bits))
bits = np.reshape(bits, [-1,self.BLOCKSIZE]).T
bits = np.reshape(bits, [-1]).tolist()
for y in range(R*9):
for x in range(R*9):
if self.matrix[y,x] == -1:
self.matrix[y,x] = bits.pop(0)
if len(bits) == 0:
break
if len(bits) == 0:
break
if self.MASK:
best = [None, None, None]
for i, mask in enumerate(self.MASKS):
score, matrix = self.evaluate(mask, mask_area)
if best[0] is None or score < best[0]:
best = (score, matrix, i)
self.matrix = best[1]
id_ = list(map(int, f"{best[2]:03b}"))
self.matrix[0, R*4:R*5] = id_ # mask
self.matrix[-1, R*4:R*5] = id_ # mask
def evaluate(self, mask, mask_area):
matrix = self.matrix.copy()
for y in range(self.matrix.shape[0]):
for x in range(self.matrix.shape[1]):
if mask_area[y][x] and mask(x,y):
matrix[y][x] = 1-matrix[y][x]
score = 0
# 3 or more of the same color (horizontal)
for y in range(self.matrix.shape[0]):
c = 0
col = -1
for x in range(self.matrix.shape[1]):
if matrix[y][x] == -1: continue
if col != matrix[y][x]:
c = 0
col = matrix[y][x]
c += 1
if c == 3:
score += 4
elif c > 3:
score += 2
# 3 or more of the same color (vertical)
for x in range(self.matrix.shape[1]):
c = 0
col = -1
for y in range(self.matrix.shape[0]):
if matrix[y][x] == -1: continue
if col != matrix[y][x]:
c = 0
col = matrix[y][x]
c += 1
if c == 3:
score += 4
elif c > 3:
score += 2
# 2x2 blocks of the same color
for y in range(matrix.shape[0]-1):
for x in range(matrix.shape[1]-1):
if matrix[y][x] == -1: continue
zone = matrix[y:y+2, x:x+2]
if np.all(zone == zone[0,0]):
score += 2
# more dots/1s => higher score
total = matrix.shape[0]*matrix.shape[1]
dots = np.sum(matrix == 1)
percent = 100*dots//total
score += percent//5 * 2
return score, matrix
def display(self, surf):
R = self.RES
S = min(surf.get_size())
s = int(S/12/R)*R
O = (S-s*9)/2
surf.fill((255,255,255))
for y in range(R*9):
for x in range(R*9):
col = self.matrix[y,x]
if col == -3:
X, Y = O+x*s/R, O+y*s/R
size = s/R
pygame.draw.line(surf, (0,0,0), [X+size/2, Y], [X, Y+size/2])
pygame.draw.line(surf, (0,0,0), [X+size, Y+size/2], [X+size/2, Y+size])
else:
if col == -2:
col = (190,190,190)
elif col == -1:
col = (127,127,127)
elif col == 0:
col = (0,0,0)
elif col == 1:
col = (255,255,255)
pygame.draw.rect(surf, col, [O+x*s/R, O+y*s/R, s/R, s/R])
pts = [
(4, 4), (4, 2), (3, 2), (3, 1), (4, 1), (4, 0), (5, 0), (5, 1), (6, 1), (6, 2), (5, 2),
(5, 4), (7, 4), (7, 3), (8, 3), (8, 4), (9, 4), (9, 5), (8, 5), (8, 6), (7, 6), (7, 5),
(5, 5), (5, 7), (6, 7), (6, 8), (5, 8), (5, 9), (4, 9), (4, 8), (3, 8), (3, 7), (4, 7),
(4, 5), (2, 5), (2, 6), (1, 6), (1, 5), (0, 5), (0, 4), (1, 4), (1, 3), (2, 3), (2, 4)
]
pygame.draw.polygon(surf, (0,0,0), [(O+s*p[0], O+s*p[1]) for p in pts], 1)
if __name__ == "__main__":
pygame.init()
surf = pygame.Surface([S, S])
code = Lycacode({
"type": Lycacode.PERSON_STUDENT,
"id": 16048,
"year": 5,
"class": 3,
"initials": "LH"
}, Lycacode.MODE_PERSON)
#code = Lycacode("Embarquement", Lycacode.MODE_TEXT)
"""code = Lycacode({
"section": 4,
"room": 209
}, Lycacode.MODE_LOC)"""
#code = Lycacode(1, Lycacode.MODE_LINK)
code.display(surf)
pygame.image.save(surf, "lycacode_data_layout.png")

View File

@ -0,0 +1,92 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Generates lycacode frame dimensions figure
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
import pygame
import numpy as np
S = 600
class Lycacode:
RES = 3
BLACK = (158,17,26)
#BLACK = (0,0,0)
WHITE = (255,255,255)
OFFSETS = [(0,-1), (1,0), (0,1), (-1,0)]
def __init__(self):
R = self.RES
self.matrix = np.zeros([R*9, R*9])-1
self.matrix[R*4:R*5, :] = 0
self.matrix[:, R*4:R*5] = 0
self.matrix[R:R*2, R*3:R*6] = 0
self.matrix[R*3:R*6, R:R*2] = 0
self.matrix[-R*2:-R, -R*6:-R*3] = 0
self.matrix[-R*6:-R*3, -R*2:-R] = 0
self.font = pygame.font.SysFont("arial", 30, bold=True)
def display(self, surf):
R = self.RES
S = min(surf.get_size())*2
s = int(S/12/R)*R
O = (S-s*9)/2
surf.fill(self.WHITE)
# Frame
pygame.draw.rect(surf, self.BLACK, [O-s, O-s, s*11, s*11])
pygame.draw.rect(surf, self.WHITE, [O-s*0.5, O-s*0.5, s*10, s*10])
# Cross
for i in range(4):
dx, dy = self.OFFSETS[i]
X, Y = S/2 + dx*s*3, S/2 + dy*s*3
for j in range(3):
dx2, dy2 = self.OFFSETS[(i+j-1)%4]
pygame.draw.circle(surf, self.BLACK, [X+dx2*s, Y+dy2*s], 0.75*s)
pygame.draw.rect(surf, self.BLACK, [X-(1.5-abs(dx))*s, Y-(1.5-abs(dy))*s, s*(3-abs(dx)*2), s*(3-abs(dy)*2)])
pygame.draw.rect(surf, self.BLACK, [O, S/2-s/2, 9*s, s])
pygame.draw.rect(surf, self.BLACK, [S/2-s/2, O, s, 9*s])
for y in range(9):
for x in range(9):
if self.matrix[y*R, x*R] != -1:
pygame.draw.rect(surf, (0,0,0), [O+x*s, O+y*s, s+1, s+1], 2)
col = (17,158,147)
col = (0,0,0)
pygame.draw.line(surf, col, [O-s, O+s*3], [O, O+s*3], 3)
pygame.draw.line(surf, col, [O-s, O+s*3-s/6], [O-s, O+s*3+s/6], 3)
pygame.draw.line(surf, col, [O, O+s*3-s/6], [O, O+s*3+s/6], 3)
txt = self.font.render("S", True, col)
surf.blit(txt, [O-s/2-txt.get_width()/2, O+s*3-s/6-txt.get_height()])
pygame.draw.line(surf, col, [O+s, O+s*3-s/6], [O+s*2, O+s*3-s/6], 3)
pygame.draw.line(surf, col, [O+s, O+s*3-s/3], [O+s, O+s*3], 3)
pygame.draw.line(surf, col, [O+s*2, O+s*3-s/3], [O+s*2, O+s*3], 3)
txt = self.font.render("S", True, col)
surf.blit(txt, [O+3*s/2-txt.get_width()/2, O+s*3-s/3-txt.get_height()])
pygame.draw.line(surf, col, [O+s, O-s], [O+s, O-s/2], 3)
pygame.draw.line(surf, col, [O+s-s/6, O-s], [O+s+s/6, O-s], 3)
pygame.draw.line(surf, col, [O+s-s/6, O-s/2], [O+s+s/6, O-s/2], 3)
txt = self.font.render("S/2", True, col)
surf.blit(txt, [O+s-s/6-txt.get_width(), O-s*0.75-txt.get_height()/2])
if __name__ == "__main__":
pygame.init()
surf = pygame.Surface([S, S])
code = Lycacode()
code.display(surf)
pygame.image.save(surf, "lycacode_frame.png")

View File

@ -0,0 +1,71 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Generates Lycacode layout figure
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
import pygame
import numpy as np
S = 600
class Lycacode:
RES = 3
def __init__(self, *args):
self.create_matrix()
def create_matrix(self):
R = self.RES
self.matrix = np.zeros([R*9, R*9])-3
self.matrix[R*4:R*5, :] = -1
self.matrix[:, R*4:R*5] = -1
self.matrix[R:R*2, R*3:R*6] = -1
self.matrix[R*3:R*6, R:R*2] = -1
self.matrix[-R*2:-R, -R*6:-R*3] = -1
self.matrix[-R*6:-R*3, -R*2:-R] = -1
self.matrix[R*4:R*5,R*4:R*5] = 0
self.matrix[0, R*4:R*5] = -2 # mask
self.matrix[-1, R*4:R*5] = -2 # mask
self.matrix[R*4, R*4+1:R*5-1] = 1
self.matrix[R*5-1, R*4] = 1
self.matrix[R*4+1:R*5-1,R*4+1:R*5-1] = 1
def display(self, surf):
R = self.RES
S = min(surf.get_size())
s = int(S/12/R)*R
O = (S-s*9)/2
surf.fill((255,255,255))
for y in range(R*9):
for x in range(R*9):
col = self.matrix[y,x]
if col == -3:
X, Y = O+x*s/R, O+y*s/R
size = s/R
pygame.draw.line(surf, (0,0,0), [X+size/2, Y], [X, Y+size/2])
pygame.draw.line(surf, (0,0,0), [X+size, Y+size/2], [X+size/2, Y+size])
else:
if col == -2:
col = (190,190,190)
elif col == -1:
col = (127,127,127)
elif col == 0:
col = (0,0,0)
elif col == 1:
col = (255,255,255)
pygame.draw.rect(surf, col, [O+x*s/R, O+y*s/R, s/R, s/R])
if __name__ == "__main__":
pygame.init()
surf = pygame.Surface([S, S])
code = Lycacode()
code.display(surf)
pygame.image.save(surf, "lycacode_layout.png")

View File

@ -0,0 +1,43 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Generates Lycacode mask figures
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
import numpy as np
from PIL import Image
MASKS = [
lambda x, y: x%3 == 0,
lambda x, y: y%3 == 0,
lambda x, y: (x+y)%3 == 0,
lambda x, y: (x%3)*(y%3)==0,
lambda x, y: (y//3+x//3)%2==0,
lambda x, y: (y%3-1)*(x%3-y%3-2)*(y%3-x%3-2)==0,
lambda x, y: (abs(13-x)+abs(13-y))%3==1,
lambda x, y: (1-x%2 + max(0, abs(13-y)-abs(13-x))) * (1-y%2 + max(0,abs(13-x)-abs(13-y))) == 0
]
if __name__ == '__main__':
R = 3
for i, mask in enumerate(MASKS):
a = np.ones([27, 27], dtype="uint8")
a[R*4:R*5, :] = 2
a[:, R*4:R*5] = 2
a[R:R*2, R*3:R*6] = 2
a[R*3:R*6, R:R*2] = 2
a[-R*2:-R, -R*6:-R*3] = 2
a[-R*6:-R*3, -R*2:-R] = 2
a[R*4:R*5, R*4:R*5] = 1
for y in range(a.shape[0]):
for x in range(a.shape[1]):
if mask(x, y) and a[y,x] == 2:
a[y, x] = 0
a *= 0x7f
img = Image.fromarray(a)
img.save(f"lycacode_mask_{i}.png")

View File

@ -0,0 +1,256 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Generates QR Code mask evaluation figures
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
import pygame
import numpy as np
matrix = np.array([[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], [1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0], [1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0], [1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0], [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0], [0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0], [1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0], [1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0], [1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0], [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0], [1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0], [1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0], [1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0], [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0]])
matrix = np.where(matrix == -0.5, 0, matrix)
class History:
def __init__(self):
self.widths = [0]*7
self.widths[-1] = 4
self.color = 0
def add(self, col):
a, b, w = False, False, self.widths.copy()
if col != self.color:
self.color = col
a,b,w = self.check()
self.widths.pop(0)
self.widths.append(0)
self.widths[-1] += 1
return (a, b, w)
def check(self):
n = self.widths[1]
# if 1:1:3:1:1
if n > 0 and self.widths[2] == n and self.widths[3] == n*3 and self.widths[4] == n and self.widths[5] == n:
# add 40 if 4:1:1:3:1:1 + add 40 if 1:1:3:1:1:4
return (self.widths[0] >= 4, self.widths[6] >= 4, self.widths.copy())
return (False, False, self.widths.copy())
def final(self):
for i in range(4):
self.add(0)
return self.check()
def generate_imgs():
m = ((matrix.copy()+2)%3)*127
mat = np.ones((m.shape[0]+8, m.shape[1]+8))*255
mat[4:-4, 4:-4] = m
size = 30
surf = pygame.Surface([mat.shape[0]*size, mat.shape[0]*size])
surf.fill((255,255,255))
for y in range(mat.shape[0]):
for x in range(mat.shape[1]):
col = mat[y, x]
col = (col, col, col)
pygame.draw.rect(surf, col, [x*size, y*size, size, size])
img1, img2, img3, img4 = [surf.copy() for i in range(4)]
f = pygame.font.SysFont("sans", 20)
f2 = pygame.font.SysFont("sans", 48, bold=True)
f3 = pygame.font.SysFont("sans", 32, bold=True)
#Condition 1 (horizontal)
counts = []
scores = []
for y in range(matrix.shape[0]):
col = -1
row = []
s = 0
for x in range(matrix.shape[1]):
if matrix[y,x] != col:
if len(row) > 0 and row[-1] < 5:
n = row[-1]
row = row[:-n]+[0]*n
row.append(1)
col = matrix[y,x]
else:
row.append(row[-1]+1)
if row[-1] == 5:
s += 3
elif row[-1] > 5:
s += 1
counts.append(row)
scores.append(s)
for i, c in enumerate(counts[0]):
if c:
txt = f.render(str(c), True, (255,255,255))
img1.blit(txt, [(i+4.5)*size-txt.get_width()/2, 4.5*size-txt.get_height()/2])
for i, s in enumerate(scores):
txt = f.render(str(s), True, (255,0,0))
img1.blit(txt, [size*(5+matrix.shape[1])-txt.get_width()/2, (i+4.5)*size-txt.get_height()/2])
#Condition 1 (vertical)
scores2 = []
for x in range(matrix.shape[1]):
col, count = -1, 0
s = 0
for y in range(matrix.shape[0]):
if matrix[y,x] != col:
count = 0
col = matrix[y,x]
count += 1
if count == 5:
s += 3
elif count > 5:
s += 1
scores2.append(s)
for i, s in enumerate(scores2):
txt = f.render(str(s), True, (255,0,0))
img1.blit(txt, [(i+4.5)*size-txt.get_width()/2, size*(5+matrix.shape[0])-txt.get_height()/2])
txt = f2.render(f"= {sum(scores)+sum(scores2)}", True, (255,0,0))
img1.blit(txt, [size*(5+matrix.shape[1])-txt.get_width()/2, size*(6+matrix.shape[1])-txt.get_height()/2])
pygame.image.save(img1, "qr_mask_ex_eval_1.png")
#Condition 2
score = 0
txtR = f.render("3", True, (255,0,0))
txtW = f.render("3", True, (255,255,255))
for y in range(matrix.shape[0]-1):
for x in range(matrix.shape[1]-1):
zone = matrix[y:y+2, x:x+2]
if np.all(zone == zone[0,0]):
score += 3
txt = [txtR, txtW][int(zone[0,0])]
img2.blit(txt, [(x+5)*size-txt.get_width()/2, (y+5)*size-txt.get_height()/2])
txt = f2.render(f"= {score}", True, (255,0,0))
img2.blit(txt, [size*(5+matrix.shape[1])-txt.get_width()/2, size*(6+matrix.shape[1])-txt.get_height()/2])
pygame.image.save(img2, "qr_mask_ex_eval_2.png")
i = 0
cols = [(255,0,0),(44,219,99),(26,135,240),(229,205,44)]
cols = []
for j in range(36):
c = pygame.Color(0)
hsla = [j*40%360, 60, 50, 100]
c.hsla = hsla
cols.append(c)
for y in range(matrix.shape[0]):
hist = History()
for x in range(matrix.shape[1]):
a,b,w = hist.add(matrix[y,x])
if a:
col = cols[min(len(cols)-1,i)]
X = x-sum(w[1:]) #+4-4
draw_line(img3, col, size, X, y)
i += 1
if b:
col = cols[min(len(cols)-1,i)]
X = x-sum(w[1:])+4
draw_line(img3, col, size, X, y)
i += 1
a,b,w = hist.final()
if a:
col = cols[min(len(cols)-1,i)]
X = matrix.shape[1]-sum(w[1:])
draw_line(img3, col, size, X, y)
i += 1
if b:
col = cols[min(len(cols)-1,i)]
X = matrix.shape[1]+4-sum(w[1:-1])
draw_line(img3, col, size, X, y)
i += 1
for x in range(matrix.shape[1]):
hist = History()
for y in range(matrix.shape[0]):
a,b,w = hist.add(matrix[y,x])
if a:
col = cols[min(len(cols)-1,i)]
Y = y-sum(w[1:])
draw_line(img3, col, size, x, Y, True)
i += 1
if b:
col = cols[min(len(cols)-1,i)]
Y = y-sum(w[1:])+4
draw_line(img3, col, size, x, Y, True)
i += 1
a,b,w = hist.final()
if a:
col = cols[min(len(cols)-1,i)]
Y = matrix.shape[0]-sum(w[1:])
draw_line(img3, col, size, x, Y, True)
i += 1
if b:
col = cols[min(len(cols)-1,i)]
Y = matrix.shape[0]+4-sum(w[1:-1])
draw_line(img3, col, size, x, Y, True)
i += 1
txt = f2.render(f"= {i}*40 = {i*40}", True, (255,0,0))
img3.blit(txt, [size*(4+matrix.shape[1]/2)-txt.get_width()/2, size*(6+matrix.shape[1])-txt.get_height()/2])
pygame.image.save(img3, "qr_mask_ex_eval_3.png")
#Condition 4
total = matrix.shape[0]*matrix.shape[1]
dark = np.sum(matrix == 1)
percent = 100*dark//total
p1 = percent-(percent%5)
p2 = p1+5
p1, p2 = abs(p1-50)/5, abs(p2-50)/5
score = min(p1,p2)*10
txt = f3.render(f"P = {percent}%", True, (255,0,0))
img4.blit(txt, [size, size])
txt = f3.render(f"P1 = {percent-(percent%5)}% / P2 = {percent-(percent%5)+5}%", True, (255,0,0))
img4.blit(txt, [surf.get_width() - size - txt.get_width(), size])
txt = f2.render(f"S = min({int(p1)}, {int(p2)})*10 = {int(score)}", True, (255,0,0))
img4.blit(txt, [size*(4+matrix.shape[1]/2)-txt.get_width()/2, size*(6+matrix.shape[1])-txt.get_height()/2])
pygame.image.save(img4, "qr_mask_ex_eval_4.png")
def draw_line(surf, col, size, x, y, vert=False):
a, b = [(x+0.25)*size, (4.5+y)*size], [(x+10.75)*size, (4.5+y)*size]
if vert:
a, b = [(4.5+x)*size, (y+0.25)*size], [(4.5+x)*size, (y+10.75)*size]
dx, dy = [size/3, 0] if vert else [0, size/3]
pygame.draw.line(surf, col, a, b, 6)
pygame.draw.line(surf, col, [a[0]-dx, a[1]-dy], [a[0]+dx, a[1]+dy], 6)
pygame.draw.line(surf, col, [b[0]-dx, b[1]-dy], [b[0]+dx, b[1]+dy], 6)
if __name__ == "__main__":
pygame.init()
generate_imgs()

View File

@ -0,0 +1,33 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Generates QR Code mask figures
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
import numpy as np
from PIL import Image
MASKS = [
lambda x, y: (x+y) % 2 == 0,
lambda x, y: y % 2 == 0,
lambda x, y: (x) % 3 == 0,
lambda x, y: (x+y) % 3 == 0,
lambda x, y: (y//2+x//3) % 2 == 0,
lambda x, y: ((x*y) % 2 + (x*y) % 3) == 0,
lambda x, y: ((x*y) % 2 + (x*y) % 3) % 2 == 0,
lambda x, y: ((x+y) % 2 + (x*y) % 3) % 2 == 0
]
if __name__ == '__main__':
for i, mask in enumerate(MASKS):
a = np.ones([21, 21], dtype="uint8")
for y in range(a.shape[0]):
for x in range(a.shape[1]):
if mask(x, y):
a[y, x] = 0
a *= 0xffffff
img = Image.fromarray(a)
img.save(f"qr_mask_{i}.png")

View File

@ -0,0 +1,101 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Generates QR Code placement path figure
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
import pygame
import numpy as np
WIDTH, HEIGHT = 580, 580
matrix = np.array([
[ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, -0.5, -1.0, -1.0, -1.0, -1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
[ 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, -0.5, -1.0, -1.0, -1.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0],
[ 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, -0.5, -1.0, -1.0, -1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0],
[ 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, -0.5, -1.0, -1.0, -1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0],
[ 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, -0.5, -1.0, -1.0, -1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0],
[ 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, -0.5, -1.0, -1.0, -1.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0],
[ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
[ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5, -1.0, -1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[-0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 1.0, -0.5, -0.5, -1.0, -1.0, -1.0, -1.0, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5],
[-1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 0.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0],
[-1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0],
[-1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 0.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0],
[-1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0],
[ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0],
[ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, -0.5, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0],
[ 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, -0.5, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0],
[ 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, -0.5, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0],
[ 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, -0.5, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0],
[ 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, -0.5, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0],
[ 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, -0.5, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0],
[ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, -0.5, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0]
])
if __name__ == "__main__":
pygame.init()
surf = pygame.Surface([WIDTH, HEIGHT])
W, H = WIDTH/(matrix.shape[1]+8), HEIGHT/(matrix.shape[0]+8)
hW, hH = W/2, H/2
surf.fill((255,255,255))
m = ((matrix.copy()+2)%3)*127
mat = np.ones((m.shape[0]+8, m.shape[1]+8))*255
mat[4:-4, 4:-4] = m
for y in range(mat.shape[0]):
for x in range(mat.shape[1]):
col = mat[y, x]
col = (col, col, col)
pygame.draw.rect(surf, col, [x*W, y*H, W, H])
points = []
all_points = []
#Place data
dir_ = -1 #-1 = up | 1 = down
x, y = matrix.shape[1]-1, matrix.shape[0]-1
zigzag = 0
while x >= 0:
all_points.append([(x+4)*W+hW, (y+4)*H+hH])
if matrix[y,x] == -1:
points.append([(x+4)*W+hW, (y+4)*H+hH])
if ((dir_+1)/2 + zigzag)%2 == 0:
x -= 1
else:
y += dir_
x += 1
if y == -1 or y == matrix.shape[0]:
all_points.append([(x+4)*W+hW, (y+4)*H+hH])
dir_ = -dir_
y += dir_
x -= 2
else:
zigzag = 1-zigzag
#Vertical timing pattern
if x == 6:
x -= 1
all_points.append([(x+4)*W+hW, (y+4)*H+hH])
for p in all_points:
pygame.draw.circle(surf, (255,0,0), p, 3)
pygame.draw.lines(surf, (255,0,0), False, all_points)
for p in points:
pygame.draw.circle(surf, (255,255,255), p, 3)
pygame.draw.lines(surf, (255,255,255), False, points)
pygame.image.save(surf, "qr_plcmt_path.png")

View File

@ -0,0 +1,95 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Generates latex table for alignment pattern locations
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
LOCATIONS = [
[],
[6, 18],
[6, 22],
[6, 26],
[6, 30],
[6, 34],
[6, 22, 38],
[6, 24, 42],
[6, 26, 46],
[6, 28, 50],
[6, 30, 54],
[6, 32, 58],
[6, 34, 62],
[6, 26, 46, 66],
[6, 26, 48, 70],
[6, 26, 50, 74],
[6, 30, 54, 78],
[6, 30, 56, 82],
[6, 30, 58, 86],
[6, 34, 62, 90],
[6, 28, 50, 72, 94],
[6, 26, 50, 74, 98],
[6, 30, 54, 78, 102],
[6, 28, 54, 80, 106],
[6, 32, 58, 84, 110],
[6, 30, 58, 86, 114],
[6, 34, 62, 90, 118],
[6, 26, 50, 74, 98, 122],
[6, 30, 54, 78, 102, 126],
[6, 26, 52, 78, 104, 130],
[6, 30, 56, 82, 108, 134],
[6, 34, 60, 86, 112, 138],
[6, 30, 58, 86, 114, 142],
[6, 34, 62, 90, 118, 146],
[6, 30, 54, 78, 102, 126, 150],
[6, 24, 50, 76, 102, 128, 154],
[6, 28, 54, 80, 106, 132, 158],
[6, 32, 58, 84, 110, 136, 162],
[6, 26, 54, 82, 110, 138, 166],
[6, 30, 58, 86, 114, 142, 170]
]
start = r"""\def\arraystretch{1.2}
\begin{center}
\begin{longtabu}{|[2pt]c|c|c|c|c|c|c|c|[2pt]}
\caption{Alignment pattern locations}
\label{tab:qr_alignment}\\
\tabucline[2pt]{-}
Version & \multicolumn{7}{c|[2pt]}{Central x and y coordinates} \\
\tabucline[2pt]{-}
\endfirsthead
\multicolumn{8}{r}{\emph{Continued from last page}}\\
\hline
Version & \multicolumn{7}{c|[2pt]}{Central x and y coordinates} \\
\endhead
Version & \multicolumn{7}{c|[2pt]}{Central x and y coordinates} \\
\hline
\multicolumn{8}{r}{\emph{Continued on next page}}\\
\endfoot
\tabucline[2pt]{-}
\endlastfoot
"""
end = r""" \hline
\end{longtabu}
\end{center}
\def\arraystretch{1}
"""
if __name__ == "__main__":
with open("alignment.tex", "w") as f_tex:
f_tex.write(start)
for i, row in enumerate(LOCATIONS):
if i > 0:
f_tex.write(" \\hline\n")
f_tex.write(f" {i+1:2}")
for j in range(7):
val = row[j] if j < len(row) else ""
f_tex.write(f" & {val:3}")
f_tex.write(" \\\\\n")
f_tex.write(end)

View File

@ -0,0 +1,104 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Generates latex table for error correction information
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
levels = "LMQH"
start = r"""\def\arraystretch{1.5}
\begin{table}[H]
\centering
\begin{longtabu}{|[2pt]c|c|c|c|c|c|c|c|[2pt]}
\tabucline[2pt]{-}
Version & Correction level & Data codewords & Error correction codewords per block & Blocks in group 1 & Data codewords per group 1 blocks & Blocks in group 2 & Data codewords per group 2 blocks \\
\tabucline[2pt]{-}
"""
end = r""" \tabucline[2pt]{-}
\end{longtabu}
\caption{Error correction characteristics}
\label{tab:qr_error_correction}
\end{table}
\def\arraystretch{1}
"""
start = r"""\def\arraystretch{1.2}
\begin{center}
\begin{longtabu}{|[2pt]c|c|c|c|c|c|c|c|[2pt]}
\caption{Error correction characteristics}
\label{tab:qr_error_correction}\\
\tabucline[2pt]{-}
\rot{Version} & \rot{Correction level} & \rot{Data codewords} & \rot{\shortstack[l]{Error correction \\ codewords per block}} & \rot{Blocks in group 1} & \rot{\shortstack[l]{Data codewords per \\ group 1 blocks}} & \rot{Blocks in group 2} & \rot{\shortstack[l]{Data codewords per \\ group 2 blocks}} \\
\tabucline[2pt]{-}
\endfirsthead
\multicolumn{8}{r}{\emph{Continued from last page}}\\
\hline
Ver & Level & Data CW & EC CW /B & Blocks G1 & CW G1 & Blocks G2 & CW G2 \\
\endhead
Ver & Level & Data CW & EC CW /B & Blocks G1 & CW G1 & Blocks G2 & CW G2 \\
\hline
\multicolumn{8}{r}{\emph{Continued on next page}}\\
\endfoot
\tabucline[2pt]{-}
\endlastfoot
"""
end = r""" \hline
\end{longtabu}
\end{center}
\def\arraystretch{1}
"""
if __name__ == "__main__":
with open("error_correction.txt", "r") as f_txt, open("error_correction.tex", "w") as f_tex:
ecs = f_txt.read().split("\n\n")
f_tex.write(start)
"""for i, version in enumerate(versions):
lvls = version.split("\n")
if i > 0:
f_tex.write(" \\hline\n")
f_tex.write(f" \\multirow{{4}}{{*}}{{{i+1:2}}}")
# f_tex.write(f" {i+1:2}")
for j, lvl in enumerate(lvls):
values = " & ".join(
map(lambda s: f"{s:>4}", lvl.split("\t"))
)
if j > 0:
f_tex.write(" ")
# f_tex.write(" ")
f_tex.write(
f" & {levels[j]} & {values} \\\\{'*' if j < 3 else ''}\n")"""
for i, ec in enumerate(ecs):
lvls = [list(map(int, lvl.split("\t"))) for lvl in ec.split("\n")]
lvls = [lvl + [0]*(6-len(lvl)) for lvl in lvls]
if i > 0:
f_tex.write(" \\hline\n")
f_tex.write(f" \\multirow{{4}}{{*}}{{{i+1:2}}}")
for j, lvl in enumerate(lvls):
values = " & ".join(
map(lambda s: f"{s:>4}", lvl)
)
if j > 0:
f_tex.write(" ")
# f_tex.write(" ")
f_tex.write(
f" & {levels[j]} & {values} \\\\{'*' if j < 3 else ''}\n")
f_tex.write(end)

View File

@ -0,0 +1,199 @@
19 7 1 19
16 10 1 16
13 13 1 13
9 17 1 9
34 10 1 34
28 16 1 28
22 22 1 22
16 28 1 16
55 15 1 55
44 26 1 44
34 18 2 17
26 22 2 13
80 20 1 80
64 18 2 32
48 26 2 24
36 16 4 9
108 26 1 108
86 24 2 43
62 18 2 15 2 16
46 22 2 11 2 12
136 18 2 68
108 16 4 27
76 24 4 19
60 28 4 15
156 20 2 78
124 18 4 31
88 18 2 14 4 15
66 26 4 13 1 14
194 24 2 97
154 22 2 38 2 39
110 22 4 18 2 19
86 26 4 14 2 15
232 30 2 116
182 22 3 36 2 37
132 20 4 16 4 17
100 24 4 12 4 13
274 18 2 68 2 69
216 26 4 43 1 44
154 24 6 19 2 20
122 28 6 15 2 16
324 20 4 81
254 30 1 50 4 51
180 28 4 22 4 23
140 24 3 12 8 13
370 24 2 92 2 93
290 22 6 36 2 37
206 26 4 20 6 21
158 28 7 14 4 15
428 26 4 107
334 22 8 37 1 38
244 24 8 20 4 21
180 22 12 11 4 12
461 30 3 115 1 116
365 24 4 40 5 41
261 20 11 16 5 17
197 24 11 12 5 13
523 22 5 87 1 88
415 24 5 41 5 42
295 30 5 24 7 25
223 24 11 12 7 13
589 24 5 98 1 99
453 28 7 45 3 46
325 24 15 19 2 20
253 30 3 15 13 16
647 28 1 107 5 108
507 28 10 46 1 47
367 28 1 22 15 23
283 28 2 14 17 15
721 30 5 120 1 121
563 26 9 43 4 44
397 28 17 22 1 23
313 28 2 14 19 15
795 28 3 113 4 114
627 26 3 44 11 45
445 26 17 21 4 22
341 26 9 13 16 14
861 28 3 107 5 108
669 26 3 41 13 42
485 30 15 24 5 25
385 28 15 15 10 16
932 28 4 116 4 117
714 26 17 42
512 28 17 22 6 23
406 30 19 16 6 17
1006 28 2 111 7 112
782 28 17 46
568 30 7 24 16 25
442 24 34 13
1094 30 4 121 5 122
860 28 4 47 14 48
614 30 11 24 14 25
464 30 16 15 14 16
1174 30 6 117 4 118
914 28 6 45 14 46
664 30 11 24 16 25
514 30 30 16 2 17
1276 26 8 106 4 107
1000 28 8 47 13 48
718 30 7 24 22 25
538 30 22 15 13 16
1370 28 10 114 2 115
1062 28 19 46 4 47
754 28 28 22 6 23
596 30 33 16 4 17
1468 30 8 122 4 123
1128 28 22 45 3 46
808 30 8 23 26 24
628 30 12 15 28 16
1531 30 3 117 10 118
1193 28 3 45 23 46
871 30 4 24 31 25
661 30 11 15 31 16
1631 30 7 116 7 117
1267 28 21 45 7 46
911 30 1 23 37 24
701 30 19 15 26 16
1735 30 5 115 10 116
1373 28 19 47 10 48
985 30 15 24 25 25
745 30 23 15 25 16
1843 30 13 115 3 116
1455 28 2 46 29 47
1033 30 42 24 1 25
793 30 23 15 28 16
1955 30 17 115
1541 28 10 46 23 47
1115 30 10 24 35 25
845 30 19 15 35 16
2071 30 17 115 1 116
1631 28 14 46 21 47
1171 30 29 24 19 25
901 30 11 15 46 16
2191 30 13 115 6 116
1725 28 14 46 23 47
1231 30 44 24 7 25
961 30 59 16 1 17
2306 30 12 121 7 122
1812 28 12 47 26 48
1286 30 39 24 14 25
986 30 22 15 41 16
2434 30 6 121 14 122
1914 28 6 47 34 48
1354 30 46 24 10 25
1054 30 2 15 64 16
2566 30 17 122 4 123
1992 28 29 46 14 47
1426 30 49 24 10 25
1096 30 24 15 46 16
2702 30 4 122 18 123
2102 28 13 46 32 47
1502 30 48 24 14 25
1142 30 42 15 32 16
2812 30 20 117 4 118
2216 28 40 47 7 48
1582 30 43 24 22 25
1222 30 10 15 67 16
2956 30 19 118 6 119
2334 28 18 47 31 48
1666 30 34 24 34 25
1276 30 20 15 61 16

View File

@ -0,0 +1,99 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Generates latex tables for hamming codes
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
start = r""" \begin{tabu}{|[2pt]c|c|c|c|c|c|c|c|[2pt]}
\tabucline[2pt]{-}
& 1 & 2 & 3 & 4 & 5 & 6 & 7 \\
\tabucline[1pt]{-}
"""
end = r""" \tabucline[2pt]{-}
\end{tabu}
"""
class HammingError(Exception):
pass
def encode(data, blocksize=7):
A = start
B = start
result = []
datasize = blocksize-blocksize.bit_length()
data = list(map(int, data))
if len(data) % datasize:
raise HammingError(f"Length of data is not a multiple of {datasize}")
nblocks = int(len(data)/datasize)
last = 0
for b in range(nblocks):
if b > 0:
A += " \\hline\n"
B += " \\hline\n"
A += f" Group {b+1}"
B += f" Group {b+1}"
count = 0
for i in range(blocksize):
A += " & "
count += data[0]
# Power of 2
if (i+1)&i == 0 or i == 0:
A += "\_"
result.append(0)
else:
A += str(data[0])
result.append(data.pop(0))
A += " \\\\\n"
for i in range(blocksize.bit_length()):
p = 1 << i
c = sum([result[b*blocksize+j] for j in range(blocksize) if (j+1)&p])
if c%2:
result[b*blocksize+p-1] = 1
for i in range(blocksize):
B += " & "
B += str(result[b*blocksize+i])
B += " \\\\\n"
if count == 0:
if last >= 2:
A = A.rsplit("\n",2)[0]
A += "\n ... & ... & ... & ... & ... & ... & ... & ... \\\\\n"
B = B.rsplit("\n",2)[0]
B += "\n ... & ... & ... & ... & ... & ... & ... & ... \\\\\n"
break
last += 1
else:
last = 0
#return "".join(list(map(str, result)))
A += end
B += end
return A, B
if __name__ == "__main__":
data = "00000000001111101011000010100110101100111"
ds = 4
total_bits = (3**2 * 24 - 6)
data_bits = total_bits * ds // 7
data += "0"*(ds-len(data)%ds)
s = ""
i = 0
left = data_bits-len(data)
while len(s) < left:
s += f"{i:0b}"
i += 1
s = s[:left]
data += s
a, b = encode(data)
print(a)
print(b)

View File

@ -0,0 +1,199 @@
41 25 17 10
34 20 14 8
27 16 11 7
17 10 7 4
77 47 32 20
63 38 26 16
48 29 20 12
34 20 14 8
127 77 53 32
101 61 42 26
77 47 32 20
58 35 24 15
187 114 78 48
149 90 62 38
111 67 46 28
82 50 34 21
255 154 106 65
202 122 84 52
144 87 60 37
106 64 44 27
322 195 134 82
255 154 106 65
178 108 74 45
139 84 58 36
370 224 154 95
293 178 122 75
207 125 86 53
154 93 64 39
461 279 192 118
365 221 152 93
259 157 108 66
202 122 84 52
552 335 230 141
432 262 180 111
312 189 130 80
235 143 98 60
652 395 271 167
513 311 213 131
364 221 151 93
288 174 119 74
772 468 321 198
604 366 251 155
427 259 177 109
331 200 137 85
883 535 367 226
691 419 287 177
489 296 203 125
374 227 155 96
1022 619 425 262
796 483 331 204
580 352 241 149
427 259 177 109
1101 667 458 282
871 528 362 223
621 376 258 159
468 283 194 120
1250 758 520 320
991 600 412 254
703 426 292 180
530 321 220 136
1408 854 586 361
1082 656 450 277
775 470 322 198
602 365 250 154
1548 938 644 397
1212 734 504 310
876 531 364 224
674 408 280 173
1725 1046 718 442
1346 816 560 345
948 574 394 243
746 452 310 191
1903 1153 792 488
1500 909 624 384
1063 644 442 272
813 493 338 208
2061 1249 858 528
1600 970 666 410
1159 702 482 297
919 557 382 235
2232 1352 929 572
1708 1035 711 438
1224 742 509 314
969 587 403 248
2409 1460 1003 618
1872 1134 779 480
1358 823 565 348
1056 640 439 270
2620 1588 1091 672
2059 1248 857 528
1468 890 611 376
1108 672 461 284
2812 1704 1171 721
2188 1326 911 561
1588 963 661 407
1228 744 511 315
3057 1853 1273 784
2395 1451 997 614
1718 1041 715 440
1286 779 535 330
3283 1990 1367 842
2544 1542 1059 652
1804 1094 751 462
1425 864 593 365
3517 2132 1465 902
2701 1637 1125 692
1933 1172 805 496
1501 910 625 385
3669 2223 1528 940
2857 1732 1190 732
2085 1263 868 534
1581 958 658 405
3909 2369 1628 1002
3035 1839 1264 778
2181 1322 908 559
1677 1016 698 430
4158 2520 1732 1066
3289 1994 1370 843
2358 1429 982 604
1782 1080 742 457
4417 2677 1840 1132
3486 2113 1452 894
2473 1499 1030 634
1897 1150 790 486
4686 2840 1952 1201
3693 2238 1538 947
2670 1618 1112 684
2022 1226 842 518
4965 3009 2068 1273
3909 2369 1628 1002
2805 1700 1168 719
2157 1307 898 553
5253 3183 2188 1347
4134 2506 1722 1060
2949 1787 1228 756
2301 1394 958 590
5529 3351 2303 1417
4343 2632 1809 1113
3081 1867 1283 790
2361 1431 983 605
5836 3537 2431 1496
4588 2780 1911 1176
3244 1966 1351 832
2524 1530 1051 647
6153 3729 2563 1577
4775 2894 1989 1224
3417 2071 1423 876
2625 1591 1093 673
6479 3927 2699 1661
5039 3054 2099 1292
3599 2181 1499 923
2735 1658 1139 701
6743 4087 2809 1729
5313 3220 2213 1362
3791 2298 1579 972
2927 1774 1219 750
7089 4296 2953 1817
5596 3391 2331 1435
3993 2420 1663 1024
3057 1852 1273 784

View File

@ -0,0 +1,282 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Prints steps of Reed-Solomon decoding
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
class GF:
def __init__(self, val):
self.val = val
def copy(self):
return GF(self.val)
def __add__(self, n):
return GF(self.val ^ n.val)
def __sub__(self, n):
return GF(self.val ^ n.val)
def __mul__(self, n):
if self.val == 0 or n.val == 0:
return GF(0)
return GF.EXP[GF.LOG[self.val].val + GF.LOG[n.val].val].copy()
def __truediv__(self, n):
if n.val == 0:
raise ZeroDivisionError
if self.val == 0:
return GF(0)
return GF.EXP[(GF.LOG[self.val].val + 255 - GF.LOG[n.val].val)%255].copy()
def __pow__(self, n):
return GF.EXP[(GF.LOG[self.val].val * n.val)%255].copy()
def __repr__(self):
return self.val.__repr__()
#return f"GF({self.val})"
def log(self):
return GF.LOG[self.val]
GF.EXP = [GF(0)]*512
GF.LOG = [GF(0)]*256
value = 1
for exponent in range(255):
GF.LOG[value] = GF(exponent)
GF.EXP[exponent] = GF(value)
value = ((value << 1) ^ 285) if value > 127 else value << 1
for i in range(255, 512):
GF.EXP[i] = GF.EXP[i-255].copy()
class Poly:
def __init__(self, coefs):
self.coefs = coefs.copy()
@property
def deg(self):
return len(self.coefs)
def copy(self):
return Poly(self.coefs)
def __add__(self, p):
d1, d2 = self.deg, p.deg
deg = max(d1,d2)
result = [GF(0) for i in range(deg)]
for i in range(d1):
result[i + deg - d1] = self.coefs[i]
for i in range(d2):
result[i + deg - d2] += p.coefs[i]
return Poly(result)
def __mul__(self, p):
result = [GF(0) for i in range(self.deg+p.deg-1)]
for i in range(p.deg):
for j in range(self.deg):
result[i+j] += self.coefs[j] * p.coefs[i]
return Poly(result)
def __truediv__(self, p):
dividend = self.coefs.copy()
dividend += [GF(0) for i in range(p.deg-1)]
quotient = []
for i in range(self.deg):
coef = dividend[i] / p.coefs[0]
quotient.append(coef)
print("sub:", p*Poly([coef]))
for j in range(p.deg):
dividend[i+j] -= p.coefs[j] * coef
print("rem:", dividend)
print()
while dividend[0].val == 0:
dividend.pop(0)
return [Poly(quotient), Poly(dividend)]
def __repr__(self):
return f"<Poly {self.coefs}>"
def eval(self, x):
y = GF(0)
for i in range(self.deg):
y += self.coefs[i] * x**GF(self.deg-i-1)
return y
def del_lead_zeros(self):
while len(self.coefs) > 1 and self.coefs[0].val == 0:
self.coefs.pop(0)
if len(self.coefs) == 0:
self.coefs = [GF(0)]
return self
def get_generator_poly(n):
poly = Poly([GF(1)])
for i in range(n):
poly *= Poly([GF(1), GF(2)**GF(i)])
return poly
class ReedSolomonException(Exception):
pass
def correct(data, ec):
n = len(ec)
data = Poly([GF(int(cw, 2)) for cw in data+ec])
##print("data", list(map(lambda c:c.val, data.coefs)))
print("r(x)", data)
syndrome = [0]*n
corrupted = False
for i in range(n):
syndrome[i] = data.eval(GF.EXP[i])
if syndrome[i].val != 0:
corrupted = True
if not corrupted:
print("No errors")
return data
syndrome = Poly(syndrome[::-1])
print("syndrome", syndrome)
#Find locator poly
sigma, omega = euclidean_algorithm(Poly([GF(1)]+[GF(0) for i in range(n)]), syndrome, n)
print("locator", sigma)
print("evaluator", omega)
error_loc = find_error_loc(sigma)
print("location", error_loc)
error_mag = find_error_mag(omega, error_loc)
print("mag", error_mag)
for i in range(len(error_loc)):
pos = GF(error_loc[i]).log()
pos = data.deg - pos.val - 1
if pos < 0:
raise ReedSolomonException("Bad error location")
data.coefs[pos] += GF(error_mag[i])
return data
def euclidean_algorithm(a, b, R):
if a.deg < b.deg:
a, b = b, a
r_last = a
r = b
t_last = Poly([GF(0)])
t = Poly([GF(1)])
while r.deg-1 >= int(R/2):
r_last_last = r_last
t_last_last = t_last
r_last = r
t_last = t
if r_last.coefs[0] == 0:
raise ReedSolomonException("r_{i-1} was zero")
r = r_last_last
q = Poly([GF(0)])
denom_lead_term = r_last.coefs[0]
dlt_inv = denom_lead_term ** GF(-1)
I = 0
while r.deg >= r_last.deg and r.coefs[0] != 0:
I += 1
deg_diff = r.deg - r_last.deg
scale = r.coefs[0] * dlt_inv
q += Poly([scale]+[GF(0) for i in range(deg_diff)])
r += r_last * Poly([scale]+[GF(0) for i in range(deg_diff)])
q.del_lead_zeros()
r.del_lead_zeros()
if I > 100:
raise ReedSolomonException("Too long")
t = (q * t_last).del_lead_zeros() + t_last_last
t.del_lead_zeros()
if r.deg >= r_last.deg:
raise ReedSolomonException("Division algorithm failed to reduce polynomial")
sigma_tilde_at_zero = t.coefs[-1]
if sigma_tilde_at_zero.val == 0:
raise ReedSolomonException("sigma_tilde(0) was zero")
inv = Poly([sigma_tilde_at_zero ** GF(-1)])
sigma = t * inv
omega = r * inv
return [sigma, omega]
def find_error_loc(error_loc):
num_errors = error_loc.deg-1
if num_errors == 1:
return [error_loc.coefs[-2].val]
result = [0]*num_errors
e = 0
i = 1
while i < 256 and e < num_errors:
if error_loc.eval(GF(i)).val == 0:
result[e] = (GF(i) ** GF(-1)).val
e += 1
i += 1
if e != num_errors:
raise ReedSolomonException("Error locator degree does not match number of roots")
return result
def find_error_mag(error_eval, error_loc):
s = len(error_loc)
result = [0]*s
for i in range(s):
xi_inv = GF(error_loc[i]) ** GF(-1)
denom = GF(1)
for j in range(s):
if i != j:
denom *= GF(1) + GF(error_loc[j]) * xi_inv
result[i] = ( error_eval.eval(xi_inv) * (denom ** GF(-1)) ).val
return result
if __name__ == "__main__":
m = Poly([GF(67),GF(111),GF(100),GF(101),GF(115)])
g = get_generator_poly(4)
print()
print(g)
print(m/g)
print()
data = [67,111,110,101,115]
#ec = [119,123,82]
ec = [50,166,245,58]
data = [f"{n:08b}" for n in data]
ec = [f"{n:08b}" for n in ec]
print(correct(data, ec))

View File

@ -0,0 +1,78 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Generates latex table for version capacity information
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
levels = "LMQH"
start = r"""\def\arraystretch{1.5}
\begin{table}[H]
\centering
\begin{longtabu}{|[2pt]c|c|c:c:c:c|[2pt]}
\tabucline[2pt]{-}
Version & Correction level & Numerical & Alphanumerical & Byte & Kanji \\
\tabucline[2pt]{-}
"""
end = r""" \tabucline[2pt]{-}
\end{longtabu}
\caption{Version capacities}
\label{tab:qr_versions}
\end{table}
\def\arraystretch{1}
"""
start = r"""\def\arraystretch{1.2}
\begin{center}
\begin{longtabu}{|[2pt]c|c|c:c:c:c|[2pt]}
\caption{Version capacities}
\label{tab:qr_versions}\\
\tabucline[2pt]{-}
Version & Correction level & Numerical & Alphanumerical & Byte & Kanji \\
\tabucline[2pt]{-}
\endfirsthead
\multicolumn{6}{r}{\emph{Continued from last page}}\\
\endhead
\multicolumn{6}{r}{\emph{Continued on next page}}\\
\endfoot
\tabucline[2pt]{-}
\endlastfoot
"""
end = r""" \hline
\end{longtabu}
\end{center}
\def\arraystretch{1}
"""
if __name__ == "__main__":
with open("qr_versions.txt", "r") as f_txt, open("qr_versions.tex", "w") as f_tex:
versions = f_txt.read().split("\n\n")
f_tex.write(start)
for i, version in enumerate(versions):
lvls = version.split("\n")
if i > 0:
f_tex.write(" \\hline\n")
f_tex.write(f" \\multirow{{4}}{{*}}{{{i+1:2}}}")
#f_tex.write(f" {i+1:2}")
for j, lvl in enumerate(lvls):
values = " & ".join(
map(lambda s: f"{s:>4}", lvl.split("\t"))
)
if j > 0:
f_tex.write(" ")
#f_tex.write(" ")
f_tex.write(f" & {levels[j]} & {values} \\\\{'*' if j < 3 else ''}\n")
f_tex.write(end)

284
python/lycacode_gen.py Normal file
View File

@ -0,0 +1,284 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This module can be used to create and display Lycacodes
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
import pygame
import numpy as np
import hamming
S = 600
class LycacodeError(Exception):
pass
class Lycacode:
RES = 3
BLOCKSIZE = 7
MODE_PERSON = 0
MODE_LOC = 1
MODE_LINK = 2
MODE_TEXT = 3
PERSON_STUDENT = 0
PERSON_TEACHER = 1
PERSON_OTHER = 2
BLACK = (158,17,26)
#BLACK = (0,0,0)
WHITE = (255,255,255)
OFFSETS = [(0,-1), (1,0), (0,1), (-1,0)]
MASKS = [
lambda x, y: x%3 == 0,
lambda x, y: y%3 == 0,
lambda x, y: (x+y)%3 == 0,
lambda x, y: (x%3)*(y%3)==0,
lambda x, y: (y//3+x//3)%2==0,
lambda x, y: (y%3-1)*(x%3-y%3-2)*(y%3-x%3-2)==0,
lambda x, y: (abs(13-x)+abs(13-y))%3==1,
lambda x, y: (1-x%2 + max(0, abs(13-y)-abs(13-x))) * (1-y%2 + max(0,abs(13-x)-abs(13-y))) == 0
]
FRAME = True
CIRCLES = True
DOTS = True
DB_SQUARES = False
def __init__(self, data, mode):
self.data = data
self.mode = mode
self.encode()
self.create_matrix()
def encode(self):
self.bits = f"{self.mode:02b}"
if self.mode == self.MODE_PERSON:
type_ = self.data["type"]
id_ = self.data["id"]
self.bits += f"{type_:02b}"
self.bits += f"{id_:020b}"
if type_ == self.PERSON_STUDENT:
year = self.data["year"]
class_ = self.data["class"]
self.bits += f"{year:03b}"
self.bits += f"{class_:04b}"
in1, in2 = self.data["initials"]
in1, in2 = ord(in1)-ord("A"), ord(in2)-ord("A")
self.bits += f"{in1:05b}"
self.bits += f"{in2:05b}"
# 83 left
elif type_ == self.PERSON_TEACHER:
# 100 left
pass
elif type_ == self.PERSON_OTHER:
# 100 left
pass
elif self.mode == self.MODE_LOC:
section = self.data["section"]
room = self.data["room"]
self.bits += f"{section:03b}"
self.bits += f"{room:09b}"
# 106 left
elif self.mode == self.MODE_LINK:
self.bits += f"{self.data:032b}"
# 86 left
elif self.mode == self.MODE_TEXT: # max 14 chars
data = self.data.encode("utf-8")
self.bits += f"{len(data):04b}"
self.bits += "".join(list(map(lambda b: f"{b:08b}", data)))
# 2 left
else:
raise LycacodeError(f"Invalid mode {self.mode}")
ds = self.BLOCKSIZE-self.BLOCKSIZE.bit_length()
total_bits = (self.RES**2 * 24 - 6)
data_bits = total_bits * ds // self.BLOCKSIZE
self.bits += "0"*(ds-len(self.bits)%ds)
s = ""
i = 0
left = data_bits-len(self.bits)
while len(s) < left:
s += f"{i:0b}"
i += 1
s = s[:left]
self.bits += s
self.bits = hamming.encode(self.bits, self.BLOCKSIZE)
def create_matrix(self):
R = self.RES
self.matrix = np.zeros([R*9, R*9])-1
self.matrix[R*4:R*5, :] = 0
self.matrix[:, R*4:R*5] = 0
self.matrix[R:R*2, R*3:R*6] = 0
self.matrix[R*3:R*6, R:R*2] = 0
self.matrix[-R*2:-R, -R*6:-R*3] = 0
self.matrix[-R*6:-R*3, -R*2:-R] = 0
self.matrix[R*4:R*5,R*4:R*5] = -2
self.matrix[0, R*4:R*5] = -2 # mask
self.matrix[-1, R*4:R*5] = -2 # mask
mask_area = np.where(self.matrix == 0, 1, 0)
self.matrix[R*4, R*4+1:R*5-1] = 1
self.matrix[R*5-1, R*4] = 1
self.matrix[R*4+1:R*5-1, R*4+1:R*5-1] = 1
bits = list(map(int, self.bits))
bits = np.reshape(bits, [-1,self.BLOCKSIZE]).T
bits = np.reshape(bits, [-1]).tolist()
for y in range(R*9):
for x in range(R*9):
if self.matrix[y,x] == 0:
self.matrix[y,x] = bits.pop(0)
if len(bits) == 0:
break
if len(bits) == 0:
break
self.matrix = np.where(self.matrix==-2, 0, self.matrix)
best = [None, None, None]
for i, mask in enumerate(self.MASKS):
score, matrix = self.evaluate(mask, mask_area)
if best[0] is None or score < best[0]:
best = (score, matrix, i)
self.matrix = best[1]
id_ = list(map(int, f"{best[2]:03b}"))
self.matrix[0, R*4:R*5] = id_ # mask
self.matrix[-1, R*4:R*5] = id_ # mask
def evaluate(self, mask, mask_area):
matrix = self.matrix.copy()
for y in range(self.matrix.shape[0]):
for x in range(self.matrix.shape[1]):
if mask_area[y][x] and mask(x,y):
matrix[y][x] = 1-matrix[y][x]
score = 0
# 3 or more of the same color (horizontal)
for y in range(self.matrix.shape[0]):
c = 0
col = -1
for x in range(self.matrix.shape[1]):
if matrix[y][x] == -1: continue
if col != matrix[y][x]:
c = 0
col = matrix[y][x]
c += 1
if c == 3:
score += 4
elif c > 3:
score += 2
# 3 or more of the same color (vertical)
for x in range(self.matrix.shape[1]):
c = 0
col = -1
for y in range(self.matrix.shape[0]):
if matrix[y][x] == -1: continue
if col != matrix[y][x]:
c = 0
col = matrix[y][x]
c += 1
if c == 3:
score += 4
elif c > 3:
score += 2
# 2x2 blocks of the same color
for y in range(matrix.shape[0]-1):
for x in range(matrix.shape[1]-1):
if matrix[y][x] == -1: continue
zone = matrix[y:y+2, x:x+2]
if np.all(zone == zone[0,0]):
score += 2
# more dots/1s => higher score
total = matrix.shape[0]*matrix.shape[1]
dots = np.sum(matrix == 1)
percent = 100*dots//total
score += percent//5 * 2
return score, matrix
def display(self, surf):
R = self.RES
S = min(surf.get_size())
s = int(S/12/R)*R
O = (S-s*9)/2
surf.fill(self.WHITE)
# Frame
if self.FRAME:
pygame.draw.rect(surf, self.BLACK, [O-s, O-s, s*11, s*11])
pygame.draw.rect(surf, self.WHITE, [O-s*0.5, O-s*0.5, s*10, s*10])
# Cross
for i in range(4):
dx, dy = self.OFFSETS[i]
X, Y = S/2 + dx*s*3, S/2 + dy*s*3
if self.CIRCLES:
for j in range(3):
dx2, dy2 = self.OFFSETS[(i+j-1)%4]
pygame.draw.circle(surf, self.BLACK, [X+dx2*s, Y+dy2*s], 0.75*s)
pygame.draw.rect(surf, self.BLACK, [X-(1.5-abs(dx))*s, Y-(1.5-abs(dy))*s, s*(3-abs(dx)*2), s*(3-abs(dy)*2)])
pygame.draw.rect(surf, self.BLACK, [O, S/2-s/2, 9*s, s])
pygame.draw.rect(surf, self.BLACK, [S/2-s/2, O, s, 9*s])
# Dots
if self.DOTS:
for y in range(R*9):
for x in range(R*9):
if self.matrix[y, x] == 1:
pygame.draw.circle(surf, self.WHITE, [O+(x+0.5)*s/R, O+(y+0.5)*s/R], s/R/3)
if self.DB_SQUARES:
for y in range(9):
for x in range(9):
if self.matrix[y*R, x*R] != -1:
pygame.draw.rect(surf, (0,0,0), [O+x*s, O+y*s, s+1, s+1], 2)
if __name__ == "__main__":
import base
b = base.Base(S, S, "Lycacode generator")
code = Lycacode({
"type": Lycacode.PERSON_STUDENT,
"id": 16048,
"year": 5,
"class": 3,
"initials": "LH"
}, Lycacode.MODE_PERSON)
#code = Lycacode("Embarquement12", Lycacode.MODE_TEXT)
"""code = Lycacode({
"section": 4,
"room": 209
}, Lycacode.MODE_LOC)"""
#code = Lycacode(1, Lycacode.MODE_LINK)
code.display(b.w)
b.main()

164
python/lycacode_gen_mini.py Normal file
View File

@ -0,0 +1,164 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This module can be used to create and display Mini-Lycacodes
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
import pygame
import numpy as np
import hamming
S = 600
class LycacodeError(Exception):
pass
class LycacodeMini:
BLACK = (158,17,26)
#BLACK = (0,0,0)
WHITE = (255,255,255)
OFFSETS = [(0,-1), (1,0), (0,1), (-1,0)]
FRAME = True
CIRCLES = True
DOTS = True
DB_SQUARES = False
def __init__(self, id_):
self.id = id_
self.encode()
self.create_matrix()
def encode(self):
self.bits = f"{self.id:020b}"
self.bits = list(map(int, self.bits))
parity = [sum(self.bits[i*5:i*5+5])%2 for i in range(4)]
for i in range(4):
self.bits.insert((4-i)*5, parity[3-i])
def create_matrix(self):
self.matrix = np.zeros([9, 9])-1
self.matrix[4:5, :] = 0
self.matrix[:, 4:5] = 0
self.matrix[1:2, 3:6] = 0
self.matrix[3:6, 1:2] = 0
self.matrix[-2:-1, -6:-3] = 0
self.matrix[-6:-3, -2:-1] = 0
self.matrix[4,4] = -1
for y in range(9):
for x in range(9):
if self.matrix[y,x] == 0:
self.matrix[y,x] = self.bits.pop(0)
if len(self.bits) == 0:
break
if len(self.bits) == 0:
break
def display(self, surf):
S = min(surf.get_size())
s = int(S/12/3)*3
O = (S-s*9)/2
surf.fill(self.WHITE)
# Frame
if self.FRAME:
pygame.draw.rect(surf, self.BLACK, [O-s, O-s, s*11, s*11])
pygame.draw.rect(surf, self.WHITE, [O-s*0.5, O-s*0.5, s*10, s*10])
# Cross
for i in range(4):
dx, dy = self.OFFSETS[i]
X, Y = S/2 + dx*s*3, S/2 + dy*s*3
if self.CIRCLES:
for j in range(3):
dx2, dy2 = self.OFFSETS[(i+j-1)%4]
pygame.draw.circle(surf, self.BLACK, [X+dx2*s, Y+dy2*s], 0.75*s)
pygame.draw.rect(surf, self.BLACK, [X-(1.5-abs(dx))*s, Y-(1.5-abs(dy))*s, s*(3-abs(dx)*2), s*(3-abs(dy)*2)])
pygame.draw.rect(surf, self.BLACK, [O, S/2-s/2, 9*s, s])
pygame.draw.rect(surf, self.BLACK, [S/2-s/2, O, s, 9*s])
# Dots
if self.DOTS:
for y in range(9):
for x in range(9):
if self.matrix[y, x] == 1:
pygame.draw.circle(surf, self.WHITE, [O+(x+0.5)*s, O+(y+0.5)*s], s/3)
# Center
pygame.draw.circle(surf, self.WHITE, [O+4.5*s, O+4.25*s], s/6)
pygame.draw.circle(surf, self.WHITE, [O+4.25*s, O+4.75*s], s/6)
def save(self, path):
S = 600
s = int(S/12)
O = (S-s*9)/2
BLACK = "#9E111A"
WHITE = "#FFFFFF"
with open(path, "w") as f:
f.write(f"<svg width='{S}px' height='{S}px' viewbox='0 0 {S} {S}'>\n")
# Background
f.write(f"<rect x='0' y='0' width='{S}' height='{S}' fill='{WHITE}' />\n")
# Frame
f.write(f"<rect x='{O-s*0.75}' y='{O-s*0.75}' width='{s*10.5}' height='{s*10.5}' style='fill:none;stroke:{BLACK};stroke-width:{0.5*s};' />\n")
# Cross
for i in range(4):
dx, dy = self.OFFSETS[i]
X, Y = S/2 + dx*s*3, S/2 + dy*s*3
if self.CIRCLES:
for j in range(3):
dx2, dy2 = self.OFFSETS[(i+j-1)%4]
f.write(f"<circle cx='{X+dx2*s}' cy='{Y+dy2*s}' r='{0.75*s}' style='fill:{BLACK};stroke:none;stroke-width:0;' />\n")
f.write(f"<rect x='{X-(1.5-abs(dx))*s}' y='{Y-(1.5-abs(dy))*s}' width='{s*(3-abs(dx)*2)}' height='{s*(3-abs(dy)*2)}' style='fill:{BLACK};stroke:none;stroke-width:0;' />\n")
# Cross
f.write(f"<rect x='{O}' y='{S/2-s/2}' width='{9*s}' height='{s}' style='fill:{BLACK};stroke:none;stroke-width:0;' />\n")
f.write(f"<rect x='{S/2-s/2}' y='{O}' width='{s}' height='{9*s}' style='fill:{BLACK};stroke:none;stroke-width:0;' />\n")
# Dots
if self.DOTS:
for y in range(9):
for x in range(9):
if self.matrix[y, x] == 1:
f.write(f"<circle cx='{O+(x+0.5)*s}' cy='{O+(y+0.5)*s}' r='{s/3}' style='fill:{WHITE};stroke:none;stroke-width:0;' />\n")
# Center
f.write(f"<circle cx='{O+4.5*s}' cy='{O+4.25*s}' r='{s/6}' style='fill:{WHITE};stroke:none;stroke-width:0;' />\n")
f.write(f"<circle cx='{O+4.25*s}' cy='{O+4.75*s}' r='{s/6}' style='fill:{WHITE};stroke:none;stroke-width:0;' />\n")
f.write("</svg>")
def save(self):
path = input("Save as (.png or .svg): ")
if path.endswith(".svg"):
code.save(path)
else:
pygame.image.save(w, path)
if __name__ == "__main__":
import base
b = base.Base(S, S, "Mini-Lycacode generator")
code = LycacodeMini(16048)
code.display(b.w)
b.save = save
b.main()

296
python/lycacode_scanner.py Normal file
View File

@ -0,0 +1,296 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This module can be used to scan Lycacodes
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
import cv2
import numpy as np
from math import sqrt
import hamming
DB_WIN = True
TOL_CNT_DIST = 20
R = 3
MASKS = [
lambda x, y: x%3 == 0,
lambda x, y: y%3 == 0,
lambda x, y: (x+y)%3 == 0,
lambda x, y: (x%3)*(y%3)==0,
lambda x, y: (y//3+x//3)%2==0,
lambda x, y: (y%3-1)*(x%3-y%3-2)*(y%3-x%3-2)==0,
lambda x, y: (abs(13-x)+abs(13-y))%3==1,
lambda x, y: (1-x%2 + max(0, abs(13-y)-abs(13-x))) * (1-y%2 + max(0,abs(13-x)-abs(13-y))) == 0
]
def center(c):
M = cv2.moments(c)
if M["m00"] != 0:
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
return (cX, cY)
return (None, None)
def dist(p1, p2):
return sqrt((p2[0]-p1[0])**2 + (p2[1]-p1[1])**2)
def is_symbol(i, cnts, hrcy):
c1 = cnts[i]
h1 = hrcy[0][i]
cX1, cY1 = center(c1)
if len(c1) != 4:
return False
if cX1 is None:
return False
if h1[2] == -1:
return False
i2 = h1[2]
c2 = cnts[i2]
h2 = hrcy[0][i2]
cX2, cY2 = center(c2)
if cX2 is None:
return False
if len(c2) != 8:
return False
if abs(dist((cX1, cY1), (cX2, cY2))) > TOL_CNT_DIST:
return False
return True
def decode(img):
grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
grey = cv2.GaussianBlur(grey, (5,5), 0)
#if DB_WIN: cv2.imshow("grey", grey)
#bw = cv2.threshold(grey, np.mean(grey), 255, cv2.THRESH_BINARY)[1]
#bw = cv2.adaptiveThreshold(grey, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 5, 0)
bw = cv2.threshold(grey, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
if DB_WIN: cv2.imshow("bw", bw)
#laplacian = cv2.Laplacian(bw, cv2.CV_8U, 15)
#cv2.imshow("laplacian", laplacian)
contours, hierarchy = cv2.findContours(bw, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
if DB_WIN:
img2 = img.copy()
cv2.drawContours(img2, contours, -1, (0,255,0), 1)
candidates = []
contours = list(contours)
for i, cnt in enumerate(contours):
peri = cv2.arcLength(cnt, True)
contours[i] = cv2.approxPolyDP(cnt, 0.04 * peri, True)
for i in range(len(contours)):
if is_symbol(i, contours, hierarchy):
candidates.append(i)
if DB_WIN:
for i in candidates:
cv2.drawContours(img2, contours, i, (0,0,255), 1)
cv2.drawContours(img2, contours, hierarchy[0][i][2], (0,0,255), 1)
cv2.imshow("contours", img2)
if DB_WIN:
img3 = img.copy()
cv2.drawContours(img3, contours, -1, (0,0,255), 1)
cv2.imshow("contours-all", img3)
if len(candidates) == 0:
return
for i in candidates:
i = candidates[0]
j = hierarchy[0][i][2]
cnt1, cnt2 = contours[i][::-1], contours[j]
from_ = [ cnt1[0], cnt1[1], cnt1[2], cnt1[3] ]
to = [ (0,0), (320,0), (320,320), (0,320) ]
M = cv2.getPerspectiveTransform(np.array(from_, dtype="float32"), np.array(to, dtype="float32"))
#_ = cv2.dilate(bw, (11,11))
#warped = cv2.warpPerspective(_, M, (320,320))
warped = cv2.warpPerspective(bw, M, (320,320))
if DB_WIN:
cv2.imshow("warped", warped)
s = 320/10/R
matrix = np.zeros([R*9, R*9])-1
matrix[R*4:R*5, 0:] = 0
matrix[0:, R*4:R*5] = 0
matrix[R:R*2, R*3:R*6] = 0
matrix[R*3:R*6, R:R*2] = 0
matrix[-R*2:-R, -R*6:-R*3] = 0
matrix[-R*6:-R*3, -R*2:-R] = 0
dots = warped.copy()
dots = cv2.cvtColor(dots, cv2.COLOR_GRAY2BGR)
for y in range(R*9):
cv2.line(dots, (0, int(s*R/2+(y+1)*s)), (320, int(s*R/2+(y+1)*s)), (0,255,0), 1)
cv2.line(dots, (int(s*R/2+(y+1)*s), 0), (int(s*R/2+(y+1)*s), 320), (0,255,0), 1)
for x in range(R*9):
if matrix[y, x] == 0:
X, Y = (x+R/2)*s, (y+R/2)*s
val = np.mean(warped[int(Y+s/2)-1:int(Y+s/2)+2, int(X+s/2)-1:int(X+s/2)+2])
matrix[y, x] = int(round(val/255))
cv2.circle(dots, (int(Y+s/2), int(X+s/2)), 2, (0,0,255), 1)
if DB_WIN:
cv2.imshow("dots", dots)
v = _decode(matrix)
if not v is None:
return v
return None
#return _decode(matrix)
def _decode(matrix):
OFFSETS = [[(1,0), (R-1,1)], [(R-1,1), (R,R-1)], [(1,R-1), (R-1,R)], [(0,1), (1,R-1)]]
I = None
for i in range(4):
s, e = OFFSETS[i]
dx1, dy1 = s
dx2, dy2 = e
# Find top
if (matrix[R*4+dy1:R*4+dy2, R*4+dx1:R*4+dx2] == 1).all():
I = i
if I is None:
return
# Put top on top
matrix = np.rot90(matrix, I)
# If left on right, flip
if matrix[R*5-1, R*5-1] == 1:
matrix = np.fliplr(matrix)
# If not left on left -> problem
elif matrix[R*5-1, R*4] != 1:
return
matrix[R*4:R*5, R*4:R*5] = -1
mask_i = "".join([str(int(b)) for b in matrix[0, R*4:R*5]])
mask_i = int(mask_i, 2)
matrix[0, R*4:R*5] = -1
matrix[-1, R*4:R*5] = -1
for y in range(R*9):
for x in range(R*9):
if MASKS[mask_i](x,y) and matrix[y][x] != -1:
matrix[y][x] = 1-matrix[y][x]
if DB_WIN:
img = ((matrix+2)%3)/2*255
cv2.namedWindow("matrix", cv2.WINDOW_NORMAL)
cv2.imshow("matrix", np.array(img, dtype="uint8"))
bits = []
for y in range(R*9):
for x in range(R*9):
if matrix[y, x] != -1:
bits.append(int(matrix[y,x]))
bits = np.reshape(bits, [-1, int(len(bits)/7)]).T
bits = np.reshape(np.array(bits), [-1])
bits = "".join(list(map(str, bits)))
data, errors = hamming.decode(bits, 7)
if errors > 6:
return
mode, data = int(data[:2],2), data[2:]
if mode == 0:
person = {}
type_, data = int(data[:2],2), data[2:]
id_, data = int(data[:20],2), data[20:]
person["type"] = type_
person["id"] = id_
# Student
if type_ == 0:
year, data = int(data[:3],2), data[3:]
class_, data = int(data[:4],2), data[4:]
person["year"] = year
person["class"] = class_
in1, in2, data = int(data[:5],2), int(data[5:10],2), data[10:]
in1, in2 = chr(in1+ord("A")), chr(in2+ord("A"))
person["initials"] = in1+in2
# Teacher
elif type_ == 1:
pass
# Other
elif type_ == 2:
pass
else:
print(f"Invalid person type {type_}")
data = person
elif mode == 1:
loc = {}
section, data = int(data[:3],2), data[3:]
room, data = int(data[:9],2), data[9:]
loc["section"] = section
loc["room"] = room
data = loc
elif mode == 2:
data = int(data[:32],2)
elif mode == 3:
length, data = int(data[:4],2), data[4:]
if length*8 > len(data): return
data = bytes([int(data[i*8:i*8+8],2) for i in range(length)])
try:
data = data.decode("utf-8")
except UnicodeDecodeError:
return
else:
#raise LycacodeError(f"Invalid mode {self.mode}")
print(f"Invalid mode {self.mode}")
return
return data
if __name__ == "__main__":
np.set_printoptions(linewidth=200)
cam = cv2.VideoCapture(0)
while True:
ret, img = cam.read()
data = decode(img)
if not data is None:
print(data)
cv2.imshow("src", img)
cv2.waitKey(1)

View File

@ -0,0 +1,207 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This module can be used to scan Mini-Lycacodes
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
import cv2
import numpy as np
from math import sqrt
import hamming
DB_WIN = False
TOL_CNT_DIST = 20
def center(c):
M = cv2.moments(c)
if M["m00"] != 0:
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
return (cX, cY)
return (None, None)
def dist(p1, p2):
return sqrt((p2[0]-p1[0])**2 + (p2[1]-p1[1])**2)
def is_symbol(i, cnts, hrcy):
c1 = cnts[i]
h1 = hrcy[0][i]
cX1, cY1 = center(c1)
if len(c1) != 4:
return False
if cX1 is None:
return False
if h1[2] == -1:
return False
i2 = h1[2]
c2 = cnts[i2]
h2 = hrcy[0][i2]
cX2, cY2 = center(c2)
if cX2 is None:
return False
if len(c2) != 8:
return False
if abs(dist((cX1, cY1), (cX2, cY2))) > TOL_CNT_DIST:
return False
return True
def decode(img):
#grey = img[:,:,2]
grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#grey = cv2.GaussianBlur(grey, (5,5), 0)
#if DB_WIN: cv2.imshow("grey", grey)
#bw = cv2.threshold(grey, np.mean(grey), 255, cv2.THRESH_BINARY)[1]
bw = cv2.threshold(grey, 127, 255, cv2.THRESH_BINARY)[1]
#bw = cv2.adaptiveThreshold(grey, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 5, 0)
#bw = cv2.threshold(grey, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
if DB_WIN: cv2.imshow("bw", bw)
#laplacian = cv2.Laplacian(bw, cv2.CV_8U, 15)
#cv2.imshow("laplacian", laplacian)
contours, hierarchy = cv2.findContours(bw, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
if DB_WIN:
img2 = img.copy()
cv2.drawContours(img2, contours, -1, (0,255,0), 1)
candidates = []
contours = list(contours)
for i, cnt in enumerate(contours):
peri = cv2.arcLength(cnt, True)
contours[i] = cv2.approxPolyDP(cnt, 0.04 * peri, True)
for i in range(len(contours)):
if is_symbol(i, contours, hierarchy):
candidates.append(i)
if DB_WIN:
for i in candidates:
cv2.drawContours(img2, contours, i, (0,0,255), 1)
cv2.drawContours(img2, contours, hierarchy[0][i][2], (0,0,255), 1)
cv2.imshow("contours", img2)
if DB_WIN:
img3 = img.copy()
cv2.drawContours(img3, contours, -1, (0,0,255), 1)
cv2.imshow("contours-all", img3)
if len(candidates) == 0:
return
for i in candidates:
i = candidates[0]
j = hierarchy[0][i][2]
cnt1, cnt2 = contours[i][::-1], contours[j]
from_ = [ cnt1[0], cnt1[1], cnt1[2], cnt1[3] ]
to = [ (0,0), (320,0), (320,320), (0,320) ]
M = cv2.getPerspectiveTransform(np.array(from_, dtype="float32"), np.array(to, dtype="float32"))
#_ = cv2.dilate(bw, (11,11))
#warped = cv2.warpPerspective(_, M, (320,320))
warped = cv2.warpPerspective(bw, M, (320,320))
if DB_WIN:
cv2.imshow("warped", warped)
s = 320/10
matrix = np.zeros([9, 9])-1
matrix[4:5, 0:] = 0
matrix[0:, 4:5] = 0
matrix[1:2, 3:6] = 0
matrix[3:6, 1:2] = 0
matrix[-2:-1, -6:-3] = 0
matrix[-6:-3, -2:-1] = 0
dots = warped.copy()
dots = cv2.cvtColor(dots, cv2.COLOR_GRAY2BGR)
for y in range(9):
cv2.line(dots, (0, int(s/2+(y+1)*s)), (320, int(s/2+(y+1)*s)), (0,255,0), 1)
cv2.line(dots, (int(s/2+(y+1)*s), 0), (int(s/2+(y+1)*s), 320), (0,255,0), 1)
for x in range(9):
if matrix[y, x] == 0:
X, Y = (x+0.5)*s, (y+0.5)*s
val = np.mean(warped[int(Y+s/2)-1:int(Y+s/2)+2, int(X+s/2)-1:int(X+s/2)+2])
matrix[y, x] = int(round(val/255))
cv2.circle(dots, (int(Y+s/2), int(X+s/2)), 2, (0,0,255), 1)
OFFSETS = [(0,-1),(1,0),(0,1),(-1,0)]
I = None
for i in range(4):
dx, dy = OFFSETS[i]
X, Y = 320/2+dx*s/3, 320/2+dy*s/3
cv2.circle(dots, (int(Y), int(X)), 2, (0,255,255), 1)
if np.mean(warped[int(Y)-1:int(Y)+2, int(X)-1:int(X)+2]) > 127:
I = i
if I is None:
continue
matrix = np.rot90(matrix, I)
dx, dy = [(1,1), (-1, 1), (-1,-1), (1,-1)][I]
X, Y = 320/2+dx*s/3, 320/2+dy*s/3
if np.mean(warped[int(Y)-1:int(Y)+2, int(X)-1:int(X)+2]) > 127:
matrix = np.fliplr(matrix)
if DB_WIN:
cv2.imshow("dots", dots)
v = _decode(matrix)
if not v is None:
return v
return None
#return _decode(matrix)
def _decode(matrix):
matrix[4:5, 4:5] = -1
if DB_WIN:
img = ((matrix+2)%3)/2*255
cv2.namedWindow("matrix", cv2.WINDOW_NORMAL)
cv2.imshow("matrix", np.array(img, dtype="uint8"))
bits = []
for y in range(9):
for x in range(9):
if matrix[y, x] != -1:
bits.append(int(matrix[y,x]))
for i in range(4):
if sum(bits[i*6:i*6+6])%2:
return
data = "".join(list(map(str, bits)))
id_ = int(data[0:5]+data[6:11]+data[12:17]+data[18:23],2)
return id_
if __name__ == "__main__":
np.set_printoptions(linewidth=200)
cam = cv2.VideoCapture(0)
while True:
ret, img = cam.read()
data = decode(img)
if not data is None:
print(data)
cv2.imshow("src", img)
cv2.waitKey(10)

891
python/qr_generator.py Normal file
View File

@ -0,0 +1,891 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import numpy as np
import pygame
import time
import sys
S_EMPTY = 1
S_SEP = 2
S_FINDER = 4
S_ALIGN = 8
S_TIMING = 16
S_RESERVED = 32
S_DATA = 64
S_BYTES = 128
S_MASK = 256
STEPS = S_MASK|S_DATA|S_BYTES|S_RESERVED|S_ALIGN|S_TIMING|S_FINDER|S_SEP|S_EMPTY
#STEPS = S_MASK|S_RESERVED|S_ALIGN|S_TIMING|S_FINDER|S_EMPTY
#STEPS = 0
#STEPS = S_MASK|S_DATA
pygame.init()
font = pygame.font.SysFont("ubuntu", 16)
win = None
coords = True # turn on coordinates
def log(msg):
print(f"<[---]> {msg} <[---]>")
class GF:
"""Galois field element"""
def __init__(self, val):
self.val = val
def copy(self):
return GF(self.val)
# Addition
def __add__(self, n):
return GF(self.val ^ n.val)
# Subtraction
def __sub__(self, n):
return GF(self.val ^ n.val)
# Multiplication
def __mul__(self, n):
if self.val == 0 or n.val == 0:
return GF(0)
return GF.EXP[GF.LOG[self.val].val + GF.LOG[n.val].val].copy()
# Division
def __truediv__(self, n):
if n.val == 0:
raise ZeroDivisionError
if self.val == 0:
return GF(0)
return GF.EXP[(GF.LOG[self.val].val + 255 - GF.LOG[n.val].val)%255].copy()
# Power
def __pow__(self, n):
return GF.EXP[(GF.LOG[self.val].val * n.val)%255].copy()
# Representation -> string
def __repr__(self):
return self.val.__repr__()
# Compute exponents and logs for all element of the Galois field
GF.EXP = [GF(0)]*512
GF.LOG = [GF(0)]*256
value = 1
for exponent in range(255):
GF.LOG[value] = GF(exponent)
GF.EXP[exponent] = GF(value)
value = ((value << 1) ^ 285) if value > 127 else value << 1
for i in range(255, 512):
GF.EXP[i] = GF.EXP[i-255].copy()
class Poly:
"""
Polynomial
Coefficients are in the order of largest to lowest degree:
ax^2 + bx + c -> coefs = [a, b, c]
"""
def __init__(self, coefs):
self.coefs = coefs.copy()
@property
def deg(self):
return len(self.coefs)
def copy(self):
return Poly(self.coefs)
# Addition
def __add__(self, p):
d1, d2 = self.deg, p.deg
deg = max(d1,d2)
result = [GF(0) for i in range(deg)]
for i in range(d1):
result[i + deg - d1] = self.coefs[i]
for i in range(d2):
result[i + deg - d2] += p.coefs[i]
return Poly(result)
# Multiplication
def __mul__(self, p):
result = [GF(0) for i in range(self.deg+p.deg-1)]
for i in range(p.deg):
for j in range(self.deg):
result[i+j] += self.coefs[j] * p.coefs[i]
return Poly(result)
# Division
def __truediv__(self, p):
dividend = self.coefs.copy()
dividend += [GF(0) for i in range(p.deg-1)]
quotient = []
for i in range(self.deg):
coef = dividend[i] / p.coefs[0]
quotient.append(coef)
for j in range(p.deg):
dividend[i+j] -= p.coefs[j] * coef
while dividend[0].val == 0:
dividend.pop(0)
return [Poly(quotient), Poly(dividend)]
# Representation -> string
def __repr__(self):
return f"<Poly {self.coefs}>"
# Inspired by nayuki's Creating a QR Code step by step
# bibtex key: nayuki_qr_js
# https://github.com/nayuki/Nayuki-web-published-code/blob/dfb110475327271e3b7279a432e2d1a1298815ad/creating-a-qr-code-step-by-step/creating-qr-code-steps.js
class History:
"""Widths history for mask evaluation, crit. 3"""
def __init__(self):
self.widths = [0]*7
self.widths[-1] = 4
self.colors = [0]*4
self.color = 0
# Add module to history, returns number of patterns found
def add(self, col):
s = 0
self.colors.append(col)
if col != self.color:
self.color = col
s = self.check()
self.widths.pop(0)
self.colors = self.colors[-sum(self.widths)-1:]
self.widths.append(0)
self.widths[-1] += 1
return s
# Check for patterns in the history
def check(self):
n = self.widths[1]
# Only black on white
if self.colors[self.widths[0]] != 1: return 0
# if 1:1:3:1:1
if n > 0 and self.widths[2] == n and self.widths[3] == n*3 and self.widths[4] == n and self.widths[5] == n:
# check if 4:1:1:3:1:1 + check if 1:1:3:1:1:4
return int(self.widths[0] >= 4) + int(self.widths[6] >= 4)
return 0
# Final check
def final(self):
for i in range(4):
self.add(0)
return self.check()
class QR:
TYPES = ["numeric", "alphanumeric", "byte", "kanji", "?"]
LEVELS = ["L","M","Q","H", "?"]
MODES = ["0001", "0010", "0100", "1000"]
ALPHANUM = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"
VERSIONS = []
ERROR_CORRECTION = []
FINDER = ["1111111","1000001","1011101","1011101","1011101","1000001","1111111"]
FINDER = np.array([list(map(int, _)) for _ in FINDER])
ALIGNMENT_PATTERN_LOCATIONS = [
[],
[6, 18],
[6, 22],
[6, 26],
[6, 30],
[6, 34],
[6, 22, 38],
[6, 24, 42],
[6, 26, 46],
[6, 28, 50],
[6, 30, 54],
[6, 32, 58],
[6, 34, 62],
[6, 26, 46, 66],
[6, 26, 48, 70],
[6, 26, 50, 74],
[6, 30, 54, 78],
[6, 30, 56, 82],
[6, 30, 58, 86],
[6, 34, 62, 90],
[6, 28, 50, 72, 94],
[6, 26, 50, 74, 98],
[6, 30, 54, 78, 102],
[6, 28, 54, 80, 106],
[6, 32, 58, 84, 110],
[6, 30, 58, 86, 114],
[6, 34, 62, 90, 118],
[6, 26, 50, 74, 98, 122],
[6, 30, 54, 78, 102, 126],
[6, 26, 52, 78, 104, 130],
[6, 30, 56, 82, 108, 134],
[6, 34, 60, 86, 112, 138],
[6, 30, 58, 86, 114, 142],
[6, 34, 62, 90, 118, 146],
[6, 30, 54, 78, 102, 126, 150],
[6, 24, 50, 76, 102, 128, 154],
[6, 28, 54, 80, 106, 132, 158],
[6, 32, 58, 84, 110, 136, 162],
[6, 26, 54, 82, 110, 138, 166],
[6, 30, 58, 86, 114, 142, 170]
]
MASKS = [
lambda x,y: (x+y)%2 == 0,
lambda x,y: y%2 == 0,
lambda x,y: (x)%3 == 0,
lambda x,y: (x+y)%3 == 0,
lambda x,y: (y//2+x//3)%2 == 0,
lambda x,y: ((x*y)%2 + (x*y)%3) == 0,
lambda x,y: ((x*y)%2 + (x*y)%3)%2 == 0,
lambda x,y: ((x+y)%2 + (x*y)%3)%2 == 0
]
def __init__(self, data, level=0):
pygame.display.set_caption("QR Gen - Init")
log(f"Content: {data}")
log(f"EC Level: {self.LEVELS[level]}")
self.bits = ""
self.data = data
self.level = level
self.type = -1
self.version = -1
self.analyse_type()
self.compute_version()
self.build_char_count_indicator()
self.encode()
self.separate_codewords()
self.create_matrix()
def load_versions():
with open("qr_versions.txt", "r") as f:
versions = f.read().split("\n\n")
for v in versions:
lvls = [list(map(int, lvl.split("\t"))) for lvl in v.split("\n")]
QR.VERSIONS.append(lvls)
QR.VERSIONS = np.array(QR.VERSIONS)
def load_ec():
with open("error_correction.txt", "r") as f:
ecs = f.read().split("\n\n")
for ec in ecs:
lvls = [list(map(int, lvl.split("\t"))) for lvl in ec.split("\n")]
lvls = [lvl + [0]*(6-len(lvl)) for lvl in lvls]
QR.ERROR_CORRECTION.append(lvls)
QR.ERROR_CORRECTION = np.array(QR.ERROR_CORRECTION)
def __repr__(self):
return "<QR: {} - {} (V{})>".format(
QR.LEVELS[self.level],
QR.TYPES[self.type].title(),
"?" if self.version == -1 else self.version+1
)
def analyse_type(self):
pygame.display.set_caption("QR Gen - Type analysis")
if self.data.isnumeric():
self.type = 0
elif set(self.data).issubset(set(QR.ALPHANUM)):
self.type = 1
else:
try:
self.data.encode("ISO-8859-1")
self.type = 2
except:
self.type = 3
self.bits += self.MODES[self.type]
log(f"Type: {self.TYPES[self.type]}")
def compute_version(self):
pygame.display.set_caption("QR Gen - Version computation")
self.version = min(np.where(QR.VERSIONS[:, self.level, self.type] >= len(self.data))[0])
log(f"Version: {self.version+1}")
def get_char_count_len(self):
if 0 <= self.version < 9:
return [10,9,8,8][self.type]
elif 9 <= self.version < 26:
return [12,11,16,10][self.type]
elif 26 <= self.version < 40:
return [14,13,16,12][self.type]
def build_char_count_indicator(self):
pygame.display.set_caption("QR Gen - Char count ind")
length = self.get_char_count_len()
indicator = f"{{:0{length}b}}".format(len(self.data))
self.bits += indicator
log(f"Char count indicator: {indicator}")
def encode(self):
pygame.display.set_caption("QR Gen - Encoding")
if self.type == 0:
groups = [self.data[i:i+3] for i in range(0,len(self.data),3)]
for group in groups:
group = int(group)
sgroup = str(group)
if len(sgroup) == 3:
s = "{:010b}"
elif len(sgroup) == 2:
s = "{:07b}"
else:
s = "{:04b}"
self.bits += s.format(group)
elif self.type == 1:
data = self.data
last = None
if len(data)%2 == 1:
last = data[-1]
data = data[:-1]
for i in range(0, len(data), 2):
val1 = self.ALPHANUM.index(data[i])
val2 = self.ALPHANUM.index(data[i+1])
val = val1*45 + val2
self.bits += f"{val:011b}"
if not last is None:
self.bits += "{:06b}".format(self.ALPHANUM.index(last))
elif self.type == 2:
data = self.data.encode("ISO-8859-1")
self.bits += "".join(list(map("{:08b}".format, data)))
elif self.type == 3:
data = list(self.data.encode("shift_jis"))
#Combine double bytes
data = [data[i*2]<<8 | data[i*2+1] for i in range(len(data)//2)]
for dbyte in data:
if 0x8140 <= dbyte <= 0x9ffc:
dbyte = dbyte - 0x8140
elif 0xe040 <= dbyte <= 0xebbf:
dbyte = dbyte - 0xc140
msb = dbyte >> 8
lsb = dbyte & 0xff
val = msb * 0xc0 + lsb
self.bits += f"{val:013b}"
log(f"Encoded: {[self.bits[i:i+8] for i in range(0,len(self.bits),8)]}")
ec = self.ERROR_CORRECTION[self.version, self.level]
req_bits = ec[0]*8
#Terminator
self.bits += "0"*(min(4, req_bits-len(self.bits)))
#Pad to multiple of 8
if len(self.bits) % 8 != 0:
self.bits += "0"*(8-len(self.bits)%8)
#Pad to required bits
if len(self.bits) < req_bits:
for i in range((req_bits-len(self.bits))//8):
self.bits += ["11101100","00010001"][i%2]
log(f"Padded: {[self.bits[i:i+8] for i in range(0,len(self.bits),8)]}")
def separate_codewords(self):
pygame.display.set_caption("QR Gen - Separating codewords")
ec = self.ERROR_CORRECTION[self.version, self.level]
blocks = []
ec_codewords = []
codeword = 0
gen_poly = self.get_generator_poly(ec[1])
log(f"Gen poly: {gen_poly}")
#print(self.bits)
for i in range(ec[2]):
block = []
for j in range(ec[3]):
block.append(self.bits[codeword*8:codeword*8+8])
codeword += 1
blocks.append(block)
msg_poly = Poly(list(map(lambda b: GF(int(b,2)), block)))
log(f"Msg poly (1-{i}): {msg_poly}")
quotient, remainder = msg_poly / gen_poly
log(f"EC poly (1-{i}): {remainder}")
ec_cwds = [f"{c.val:08b}" for c in remainder.coefs]
ec_codewords.append(ec_cwds)
#If group 2
if ec[4] != 0:
for i in range(ec[4]):
block = []
for j in range(ec[5]):
block.append(self.bits[codeword*8:codeword*8+8])
codeword += 1
blocks.append(block)
msg_poly = Poly(list(map(lambda b: GF(int(b,2)), block)))
log(f"Msg poly (2-{i}): {msg_poly}")
quotient, remainder = msg_poly / gen_poly
log(f"EC poly (2-{i}): {remainder}")
ec_cwds = [f"{c.val:08b}" for c in remainder.coefs]
ec_codewords.append(ec_cwds)
self.final_data_bits = ""
if len(blocks) == 1:
dbits = "".join(["".join(block) for block in blocks])
ec_bits = "".join(["".join(cwd) for cwd in ec_codewords])
log(f"EC bits: {[ec_bits[i:i+8] for i in range(0,len(ec_bits),8)]}")
self.final_data_bits = dbits + ec_bits
else:
#Interleave data codewords
for i in range(max(ec[3], ec[5])):
for block in blocks:
if i < len(block):
self.final_data_bits += block[i]
#Interleave error correction codewords
for i in range(ec[1]):
for block in ec_codewords:
self.final_data_bits += block[i]
#Add remainder bits
if 1 <= self.version < 6:
self.final_data_bits += "0"*7
log(f"Add 7 remainder bits")
elif 13 <= self.version < 20 or 27 <= self.version < 34:
self.final_data_bits += "0"*3
log(f"Add 3 remainder bits")
elif 20 <= self.version < 27:
self.final_data_bits += "0"*4
log(f"Add 4 remainder bits")
print_bytes(self.final_data_bits)
def get_generator_poly(self, n):
poly = Poly([GF(1)])
for i in range(n):
poly *= Poly([GF(1), GF(2)**GF(i)])
return poly
def get_alignment_pattern_locations(self):
return QR.ALIGNMENT_PATTERN_LOCATIONS[self.version]
def create_matrix(self):
size = self.version*4+21
log(f"Size: {size}")
self.matrix = np.zeros([size, size])-1 #-1: empty | -0.5: reserved | 0: white | 1: black
pygame.display.set_caption("QR Gen - Matrix")
if STEPS & S_EMPTY: self.show(step=True)
#Add separator
self.matrix[0:8, 0:8] = 0
self.matrix[-8:, 0:8] = 0
self.matrix[0:8, -8:] = 0
pygame.display.set_caption("QR Gen - Separator")
if STEPS & S_SEP: self.show(step=True)
#Place finders
self.matrix[0:7, 0:7] = QR.FINDER
self.matrix[-7:, 0:7] = QR.FINDER
self.matrix[0:7, -7:] = QR.FINDER
pygame.display.set_caption("QR Gen - Finder patterns")
if STEPS & S_FINDER: self.show(step=True)
#Add alignment patterns
locations = self.get_alignment_pattern_locations()
log(f"Alignment patterns: {locations}")
if self.version > 0:
for y in locations:
for x in locations:
#Check if not overlapping with finders
if np.all(self.matrix[y-2:y+3, x-2:x+3] == -1):
self.matrix[y-2:y+3, x-2:x+3] = 1
self.matrix[y-1:y+2, x-1:x+2] = 0
self.matrix[y, x] = 1
pygame.display.set_caption("QR Gen - Alignment patterns")
if STEPS & S_ALIGN: self.show(step=True)
#Add timing patterns
timing_length = size-2*8
self.matrix[6, 8:-8] = np.resize([1,0],timing_length)
self.matrix[8:-8, 6] = np.resize([1,0],timing_length)
pygame.display.set_caption("QR Gen - Timing patterns")
if STEPS & S_TIMING: self.show(step=True)
#Add reserved areas
self.matrix[self.version*4+13,8] = 1 #Black module
self.matrix[:9, :9] = np.maximum(self.matrix[:9, :9], -0.5) #Top-left
self.matrix[-8:, 8] = np.maximum(self.matrix[-8:, 8], -0.5) #Bottom-left
self.matrix[8, -8:] = np.maximum(self.matrix[8, -8:], -0.5) #Top-right
if self.version >= 6:
self.matrix[-11:-8, :6] = -0.5
self.matrix[:6, -11:-8] = -0.5
pygame.display.set_caption("QR Gen - Reserved areas")
if STEPS & S_RESERVED: self.show(step=True)
#Place data
dir_ = -1 #-1 = up | 1 = down
x, y = size-1, size-1
i = 0
zigzag = 0
mask_area = self.matrix == -1
pygame.display.set_caption("QR Gen - Data layout")
print(self.matrix.tolist())
while x >= 0:
if self.matrix[y,x] == -1:
self.matrix[y,x] = self.final_data_bits[i]
i += 1
if STEPS & S_DATA:
if not (STEPS & S_BYTES) or i%8==0:
self.show()
time.sleep(0.01)
if ((dir_+1)/2 + zigzag)%2 == 0:
x -= 1
else:
y += dir_
x += 1
if y == -1 or y == size:
dir_ = -dir_
y += dir_
x -= 2
else:
zigzag = 1-zigzag
#Vertical timing pattern
if x == 6:
x -= 1
if STEPS & S_DATA: self.show(step=True)
score, mask, matrix = self.try_masks(mask_area)
self.matrix = np.where(mask_area, matrix, self.matrix)
pygame.display.set_caption("QR Gen - Mask")
if STEPS & S_MASK: self.show(step=True)
#Format string
format_str = f"{(5-self.level)%4:02b}{mask:03b}"
format_str += "0"*10
format_str.lstrip("0")
log(f"Format str: {format_str}")
gen_poly = 0b10100110111
format_poly = int(format_str,2)
while format_poly.bit_length() > 10:
g = gen_poly << (format_poly.bit_length()-gen_poly.bit_length())
format_poly ^= g
log(f"Remainder: {format_poly:b}")
format_data = int(format_str,2) + format_poly
format_data ^= 0b101010000010010
format_data = f"{format_data:015b}"
log(f"XORed: {format_data}")
for i in range(15):
y1, x1 = min(8,15-i), min(7,i)
if i >= 6:
x1 += 1
if i >= 9:
y1 -= 1
y2, x2 = self.matrix.shape[0]-i-1 if i < 7 else 8, 8 if i < 7 else self.matrix.shape[1]+i-15
self.matrix[y1, x1] = format_data[i]
self.matrix[y2, x2] = format_data[i]
#Version information
if self.version >= 6:
gen_poly = 0b1111100100101
version_info_poly = int(self.version+1)<<12
while version_info_poly.bit_length() > 12:
g = gen_poly << (version_info_poly.bit_length()-gen_poly.bit_length())
version_info_poly ^= g
version_info_data = ((self.version+1)<<12) + version_info_poly
version_info_data = f"{version_info_data:018b}"
ox1, oy1 = 5, self.matrix.shape[0]-9
ox2, oy2 = self.matrix.shape[1]-9, 5
for i in range(18):
self.matrix[oy1 - i%3, ox1 - i//3] = version_info_data[i]
self.matrix[oy2 - i//3, ox2 - i%3] = version_info_data[i]
def try_masks(self, mask_area):
best = [None,None,None] #score, i, matrix
for i in range(8):
mask = QR.MASKS[i]
mat = self.matrix.copy()
for y in range(self.matrix.shape[0]):
for x in range(self.matrix.shape[1]):
if mask_area[y,x] and mask(x,y):
mat[y,x] = 1-mat[y,x]
#Format string
format_str = f"{(5-self.level)%4:02b}{i:03b}"
format_str += "0"*10
format_str.lstrip("0")
gen_poly = 0b10100110111
format_poly = int(format_str,2)
while format_poly.bit_length() > 10:
g = gen_poly << (format_poly.bit_length()-gen_poly.bit_length())
format_poly ^= g
format_data = int(format_str,2) + format_poly
format_data ^= 0b101010000010010
format_data = f"{format_data:015b}"
for j in range(15):
y1, x1 = min(8,15-j), min(7,j)
if j >= 6:
x1 += 1
if j >= 9:
y1 -= 1
y2, x2 = mat.shape[0]-j-1 if j < 7 else 8, 8 if j < 7 else mat.shape[1]+j-15
mat[y1, x1] = format_data[j]
mat[y2, x2] = format_data[j]
score = self.evaluate(mat.copy(), i)
if best[0] is None or score < best[0]:
best = [score, i, mat]
return best
def evaluate(self, matrix, i):
score = 0
matrix = np.where(matrix < 0, 0, matrix)
s1, s2, s3, s4 = 0, 0, 0, 0
#Condition 1 (horizontal)
for y in range(matrix.shape[0]):
col, count = -1, 0
for x in range(matrix.shape[1]):
if matrix[y,x] != col:
count = 0
col = matrix[y,x]
count += 1
if count == 5:
score += 3
s1 += 3
elif count > 5:
score += 1
s1 += 1
#Condition 1 (vertical)
for x in range(matrix.shape[1]):
col, count = -1, 0
for y in range(matrix.shape[0]):
if matrix[y,x] != col:
count = 0
col = matrix[y,x]
count += 1
if count == 5:
score += 3
s1 += 3
elif count > 5:
score += 1
s1 += 1
#Condition 2
for y in range(matrix.shape[0]-1):
for x in range(matrix.shape[1]-1):
zone = matrix[y:y+2, x:x+2]
if np.all(zone == zone[0,0]):
score += 3
s2 += 3
#Condition 3 (horizontal)
for y in range(matrix.shape[0]):
hist = History()
for x in range(matrix.shape[1]):
s = hist.add(matrix[y,x])
score += s*40
s3 += s*40
s = hist.final()
score += s*40
s3 += s*40
#Condition 3 (vertical)
for x in range(matrix.shape[1]):
hist = History()
for y in range(matrix.shape[0]):
s = hist.add(matrix[y,x])
score += s*40
s3 += s*40
s = hist.final()
score += s*40
s3 += s*40
#Condition 4
total = matrix.shape[0]*matrix.shape[1]
dark = np.sum(matrix == 1)
percent = 100*dark//total
p1 = percent-(percent%5)
p2 = p1+5
p1, p2 = abs(p1-50)/5, abs(p2-50)/5
score += min(p1,p2)*10
s4 += min(p1,p2)*10
log(f"mask {i}: {s1} + {s2} + {s3} + {s4} = {score}")
return score
def show(self, pos=None, step=False):
global win
events = pygame.event.get()
for event in events:
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_s:
pygame.image.save(win, "/tmp/qr.jpg")
m = ((self.matrix.copy()+2)%3)*127
mat = np.ones((m.shape[0]+8, m.shape[1]+8))*255
mat[4:-4, 4:-4] = m
if not pos is None:
mat[pos[1]+4, pos[0]+4] = 50
size = 15
if win is None:
win = pygame.display.set_mode([mat.shape[0]*size, mat.shape[0]*size])
win.fill((255,255,255))
for y in range(mat.shape[0]):
for x in range(mat.shape[1]):
col = mat[y, x]
col = (col, col, col)
pygame.draw.rect(win, col, [x*size, y*size, size, size])
if coords:
N = 6
space = (mat.shape[0]-8)/(N-1)
margin = 4*size
SIZE = (mat.shape[0]-8)*size
pygame.draw.lines(win, (0,0,0), True, [
(margin, margin),(margin+SIZE, margin),
(margin+SIZE, margin+SIZE),(margin, margin+SIZE)
])
for i in range(N):
n = int(round(space*i))
d = size * n
pygame.draw.line(win, (0,0,0), [margin+d, margin], [margin+d, margin-15])
pygame.draw.line(win, (0,0,0), [margin, margin+d], [margin-15, margin+d])
pygame.draw.line(win, (0,0,0), [margin+d, margin+SIZE], [margin+d, margin+SIZE+15])
pygame.draw.line(win, (0,0,0), [margin+SIZE, margin+d], [margin+SIZE+15, margin+d])
text = font.render(str(n), True, (0,0,0))
win.blit(text, [margin+d-text.get_width()/2, margin-30-text.get_height()/2])
win.blit(text, [margin-30-text.get_width()/2, margin+d-text.get_height()/2])
win.blit(text, [margin+d-text.get_width()/2, margin+SIZE+30-text.get_height()/2])
win.blit(text, [margin+SIZE+30-text.get_width()/2, margin+d-text.get_height()/2])
pygame.display.flip()
if step:
input("Press Enter to continue")
def print_bytes(bytes_, int_=False):
result = ""
for i in range(len(bytes_)//8):
if int_:
result += str(int(bytes_[i*8:i*8+8],2)) + " "
else:
result += bytes_[i*8:i*8+8] + " "
result += bytes_[-(len(bytes_)%8):]
print(result.strip())
QR.load_versions()
QR.load_ec()
if __name__ == "__main__":
np.set_printoptions(linewidth=200)
pygame.display.set_caption("QR Gen")
#qr = QR("8675309", 0)
#qr = QR("HELLO WORLD", 2)
qr = QR("Hello, World!", 1)
#qr = QR("茗荷", 2)
#qr = QR("Hello, world! How are you doing ? I'm doing great, thank you ! Today is quite a sunny day, isn't it ?", 3)
#qr = QR("https://aufildeverre.ch/", 3)
#qr = QR("QR Code Symbol", 1)
#qr = QR("Attention !", 3)
#qr = QR("Lycacode", 0)
print(qr)
pygame.display.set_caption("QR Gen - Final")
qr.show(step=False)
input("Press Enter to quit")
events = pygame.event.get()
for event in events:
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_s:
pygame.image.save(win, "/tmp/qr.jpg")

882
python/qr_scanner.py Normal file
View File

@ -0,0 +1,882 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This module can be used to scan QR-Codes
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
import cv2
import numpy as np
from math import sqrt, degrees, atan2, radians, cos, sin
import imutils
import matplotlib.pyplot as plt
from PIL import Image
DB_WIN = False
TOL_CNT_DIST = 10 # Tolerance distance between finders' centers
MASKS = [
lambda x,y: (x+y)%2 == 0,
lambda x,y: y%2 == 0,
lambda x,y: (x)%3 == 0,
lambda x,y: (x+y)%3 == 0,
lambda x,y: (y//2+x//3)%2 == 0,
lambda x,y: ((x*y)%2 + (x*y)%3) == 0,
lambda x,y: ((x*y)%2 + (x*y)%3)%2 == 0,
lambda x,y: ((x+y)%2 + (x*y)%3)%2 == 0
]
ALIGNMENT_PATTERN_LOCATIONS = [
[],
[6, 18],
[6, 22],
[6, 26],
[6, 30],
[6, 34],
[6, 22, 38],
[6, 24, 42],
[6, 26, 46],
[6, 28, 50],
[6, 30, 54],
[6, 32, 58],
[6, 34, 62],
[6, 26, 46, 66],
[6, 26, 48, 70],
[6, 26, 50, 74],
[6, 30, 54, 78],
[6, 30, 56, 82],
[6, 30, 58, 86],
[6, 34, 62, 90],
[6, 28, 50, 72, 94],
[6, 26, 50, 74, 98],
[6, 30, 54, 78, 102],
[6, 28, 54, 80, 106],
[6, 32, 58, 84, 110],
[6, 30, 58, 86, 114],
[6, 34, 62, 90, 118],
[6, 26, 50, 74, 98, 122],
[6, 30, 54, 78, 102, 126],
[6, 26, 52, 78, 104, 130],
[6, 30, 56, 82, 108, 134],
[6, 34, 60, 86, 112, 138],
[6, 30, 58, 86, 114, 142],
[6, 34, 62, 90, 118, 146],
[6, 30, 54, 78, 102, 126, 150],
[6, 24, 50, 76, 102, 128, 154],
[6, 28, 54, 80, 106, 132, 158],
[6, 32, 58, 84, 110, 136, 162],
[6, 26, 54, 82, 110, 138, 166],
[6, 30, 58, 86, 114, 142, 170]
]
VERSIONS = []
with open("qr_versions.txt", "r") as f:
versions = f.read().split("\n\n")
for v in versions:
lvls = [list(map(int, lvl.split("\t"))) for lvl in v.split("\n")]
VERSIONS.append(lvls)
VERSIONS = np.array(VERSIONS)
ERROR_CORRECTION = []
with open("error_correction.txt", "r") as f:
ecs = f.read().split("\n\n")
for ec in ecs:
lvls = [list(map(int, lvl.split("\t"))) for lvl in ec.split("\n")]
lvls = [lvl + [0]*(6-len(lvl)) for lvl in lvls]
ERROR_CORRECTION.append(lvls)
ERROR_CORRECTION = np.array(ERROR_CORRECTION)
EC_PARAMS = []
with open("ec_params.txt", "r") as f:
ecs = f.read().split("\n\n")
for ec in ecs:
lvls = [list(map(int, lvl.split("\t"))) for lvl in ec.split("\n") if lvl]
EC_PARAMS.append(lvls)
EC_PARAMS = np.array(EC_PARAMS)
ALPHANUM = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"
class GF:
def __init__(self, val):
self.val = val
def copy(self):
return GF(self.val)
def __add__(self, n):
return GF(self.val ^ n.val)
def __sub__(self, n):
return GF(self.val ^ n.val)
def __mul__(self, n):
if self.val == 0 or n.val == 0:
return GF(0)
return GF.EXP[GF.LOG[self.val].val + GF.LOG[n.val].val].copy()
def __truediv__(self, n):
if n.val == 0:
raise ZeroDivisionError
if self.val == 0:
return GF(0)
return GF.EXP[(GF.LOG[self.val].val + 255 - GF.LOG[n.val].val)%255].copy()
def __pow__(self, n):
return GF.EXP[(GF.LOG[self.val].val * n.val)%255].copy()
def __repr__(self):
return self.val.__repr__()
def log(self):
return GF.LOG[self.val]
GF.EXP = [GF(0)]*512
GF.LOG = [GF(0)]*256
value = 1
for exponent in range(255):
GF.LOG[value] = GF(exponent)
GF.EXP[exponent] = GF(value)
value = ((value << 1) ^ 285) if value > 127 else value << 1
for i in range(255, 512):
GF.EXP[i] = GF.EXP[i-255].copy()
class Poly:
def __init__(self, coefs):
self.coefs = coefs.copy()
@property
def deg(self):
return len(self.coefs)
def copy(self):
return Poly(self.coefs)
def __add__(self, p):
d1, d2 = self.deg, p.deg
deg = max(d1,d2)
result = [GF(0) for i in range(deg)]
for i in range(d1):
result[i + deg - d1] = self.coefs[i]
for i in range(d2):
result[i + deg - d2] += p.coefs[i]
return Poly(result)
def __mul__(self, p):
result = [GF(0) for i in range(self.deg+p.deg-1)]
for i in range(p.deg):
for j in range(self.deg):
result[i+j] += self.coefs[j] * p.coefs[i]
return Poly(result)
def __truediv__(self, p):
dividend = self.coefs.copy()
dividend += [GF(0) for i in range(p.deg-1)]
quotient = []
for i in range(self.deg):
coef = dividend[i] / p.coefs[0]
quotient.append(coef)
for j in range(p.deg):
dividend[i+j] -= p.coefs[j] * coef
while dividend[0].val == 0:
dividend.pop(0)
return [Poly(quotient), Poly(dividend)]
def __repr__(self):
return f"<Poly {self.coefs}>"
def eval(self, x):
y = GF(0)
for i in range(self.deg):
y += self.coefs[i] * x**GF(self.deg-i-1)
return y
def del_lead_zeros(self):
while len(self.coefs) > 1 and self.coefs[0].val == 0:
self.coefs.pop(0)
if len(self.coefs) == 0:
self.coefs = [GF(0)]
return self
def center(c):
M = cv2.moments(c)
if M["m00"] != 0:
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
return (cX, cY)
return (None, None)
def dist(p1, p2):
return sqrt((p2[0]-p1[0])**2 + (p2[1]-p1[1])**2)
def rotate(o, p, a):
ox, oy = o
px, py = p
a = radians(a)
c, s = cos(a), sin(a)
return [
int(ox + c*(px-ox) - s * (py-oy)),
int(oy + s*(px-ox) + c * (py-oy))
]
def rotate_cnt(cnt, o, a):
c = cnt
pts = c[:, 0, :]
pts = np.array([rotate(o, p, a) for p in pts])
c[:, 0, :] = pts
return c.astype(np.int32)
def is_finder(i, cnts, hrcy):
c1 = cnts[i]
h1 = hrcy[0][i]
cX1, cY1 = center(c1)
if len(c1) != 4:
return False
if cX1 is None:
return False
if h1[2] == -1:
return False
i2 = h1[2]
c2 = cnts[i2]
h2 = hrcy[0][i2]
cX2, cY2 = center(c2)
if cX2 is None:
return False
if len(c2) != 4:
return False
if abs(dist((cX1, cY1), (cX2, cY2))) > TOL_CNT_DIST:
return False
if h2[2] == -1:
return False
i3 = h2[2]
c3 = cnts[i3]
h3 = hrcy[0][i3]
cX3, cY3 = center(c3)
if len(c3) != 4:
return False
if cX3 is None:
return False
if abs(dist((cX1, cY1), (cX3, cY3))) > TOL_CNT_DIST:
return False
return True
def decode(img):
grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
grey = cv2.GaussianBlur(grey, (5,5), 0)
#if DB_WIN: cv2.imshow("grey", grey)
bw = cv2.threshold(grey, np.mean(grey), 255, cv2.THRESH_BINARY)[1]
if DB_WIN: cv2.imshow("bw", bw)
#laplacian = cv2.Laplacian(bw, cv2.CV_8U, 15)
#cv2.imshow("laplacian", laplacian)
contours, hierarchy = cv2.findContours(bw, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
if DB_WIN:
img2 = img.copy()
cv2.drawContours(img2, contours, -1, (0,255,0), 1)
candidates = []
contours = list(contours)
for i, cnt in enumerate(contours):
peri = cv2.arcLength(cnt, True)
contours[i] = cv2.approxPolyDP(cnt, 0.04 * peri, True)
for i in range(len(contours)):
if is_finder(i, contours, hierarchy):
candidates.append(i)
if DB_WIN:
for i in candidates:
cv2.drawContours(img2, contours, i, (0,0,255), 1)
cv2.imshow("contours", img2)
if DB_WIN: img3 = img.copy()
corners = []
corners_cnts = []
for i1 in candidates:
i2 = hierarchy[0][i1][2]
i3 = hierarchy[0][i2][2]
c1 = contours[i1]
c2 = contours[i2]
c3 = contours[i3]
x1, y1 = center(c1)
x2, y2 = center(c2)
x3, y3 = center(c3)
x, y = (x1+x2+x3)/3, (y1+y2+y3)/3
x, y = int(x), int(y)
corners.append((x,y))
corners_cnts.append([c1,c2,c3])
if DB_WIN:
cv2.line(img3, [x, y-10], [x, y+10], (0,255,0), 1)
cv2.line(img3, [x-10, y], [x+10, y], (0,255,0), 1)
#cv2.drawContours(img3, [c1], 0, (255,0,0), 1)
#cv2.drawContours(img3, [c2], 0, (0,255,0), 1)
#cv2.drawContours(img3, [c3], 0, (0,0,255), 1)
if DB_WIN: cv2.imshow("lines", img3)
if len(corners) != 3:
return
d01 = dist(corners[0], corners[1])
d02 = dist(corners[0], corners[2])
d12 = dist(corners[1], corners[2])
diffs = [abs(d01-d02), abs(d01-d12), abs(d02-d12)]
mdiff = min(diffs)
i = diffs.index(mdiff)
a = corners.pop(i)
b, c = corners
V = [img.shape[1], img.shape[0]]
d = [(b[0]+c[0])/2, (b[1]+c[1])/2]
v = [d[0]-a[0], d[1]-a[1]]
C = [img.shape[1]/2, img.shape[0]/2]
angle = degrees(atan2(v[1], v[0]))
angle_diff = angle-45
if DB_WIN:
img4 = img.copy()
cv2.line(img4, a, [int(d[0]), int(d[1])], (0,255,0), 1)
cv2.imshow("vecs", img4)
cA = corners_cnts[i][0]
cA = rotate_cnt(cA, C, -angle_diff)
rA = cv2.boundingRect(cA)
cA2 = corners_cnts[i][1]
cA2 = rotate_cnt(cA2, C, -angle_diff)
rA2 = cv2.boundingRect(cA2)
cB = corners_cnts[i-1][0]
cB = rotate_cnt(cB, C, -angle_diff)
rB = cv2.boundingRect(cB)
cC = corners_cnts[i-2][0]
cC = rotate_cnt(cC, C, -angle_diff)
rC = cv2.boundingRect(cC)
a, b, c = rotate(C, a, -angle_diff), rotate(C, b, -angle_diff), rotate(C, c, -angle_diff)
if DB_WIN:
img5 = img.copy()
img5 = imutils.rotate(img5, angle_diff)
cv2.rectangle(img5, rA, (255,0,0), 1)
cv2.rectangle(img5, rB, (0,255,0), 1)
cv2.rectangle(img5, rC, (0,0,255), 1)
cv2.line(img5, a, b, (255,255,255), 1)
cv2.line(img5, a, c, (255,255,255), 1)
cv2.imshow("rot", img5)
wul = rA[2]
if rB[1] < rC[1]:
wur = rB[2]
else:
wur = rC[2]
if rB[1] < rC[1]:
D = dist(a, b)
else:
D = dist(a, c)
X = (wul + wur)/14
V = (D/X - 10)/4
V = round(V)
size = V*4+17
grid = np.zeros([size, size])
bw_rot = imutils.rotate(bw, angle_diff)
if DB_WIN:
img6 = img.copy()
img6 = imutils.rotate(img6, angle_diff)
OX, OY = (rA[0]+rA2[0])/2, (rA[1]+rA2[1])/2
#Not fully visible
if (OX + size*X+1 >= bw_rot.shape[1]) or (OY + size*X+1 >= bw_rot.shape[0]):
return None
for y in range(size):
for x in range(size):
zone = bw_rot[
int(OY + y*X-1): int(OY + y*X+2),
int(OX + x*X-1): int(OX + x*X+2)
]
grid[y, x] = 1-round(np.mean(zone)/255)
#cv2.circle(img6, [int(OX+x*X), int(OY+y*X)], 3, (0,0,255), 1)
#print(size)
#cv2.rectangle(img6, [int(OX-X/2), int(OY-X/2), int(X), int(X)], (0,255,0), 1)
#cv2.imshow("grid", img6)
if DB_WIN:
cv2.namedWindow("bw_rot", cv2.WINDOW_NORMAL)
cv2.imshow("bw_rot", bw_rot)
#cv2.imshow("code", cv2.resize(grid, [size*10,size*10]))
value = _decode(grid, V)
if value is None:
value = _decode(1-grid, V)
return value
def _decode(grid, V, flipped=None, f2=False):
if not flipped is None:
grid = flipped
lvl, mask_i = get_fmt(grid, f2)
mask = MASKS[mask_i]
unmasked = grid.copy()
mask_area = np.ones(grid.shape)
mask_area[:9, :9] = 0
mask_area[:9, -8:] = 0
mask_area[-8:, :9] = 0
#Add alignment patterns
locations = ALIGNMENT_PATTERN_LOCATIONS[V-1]
if V > 1:
for y in locations:
for x in locations:
#Check if not overlapping with finders
if np.all(mask_area[y-2:y+3, x-2:x+3] == 1):
mask_area[y-2:y+3, x-2:x+3] = 0
mask_area[y-1:y+2, x-1:x+2] = 0
mask_area[y, x] = 0
#Add timing patterns
timing_length = grid.shape[0]-2*8
mask_area[6, 8:-8] = np.zeros([timing_length])
mask_area[8:-8, 6] = np.zeros([timing_length])
if V >= 7:
mask_area[-11:-8, :6] = 0
mask_area[:6, -11:-8] = 0
for y in range(grid.shape[0]):
for x in range(grid.shape[1]):
if mask_area[y,x] == 1 and mask(x,y):
unmasked[y,x] = 1-unmasked[y,x]
if DB_WIN:
cv2.namedWindow("grid", cv2.WINDOW_NORMAL)
cv2.namedWindow("unmasked", cv2.WINDOW_NORMAL)
#cv2.namedWindow("mask_area", cv2.WINDOW_NORMAL)
cv2.imshow("grid", 1-grid)
cv2.imshow("unmasked", 1-unmasked)
#cv2.imshow("mask_area", mask_area)
#Un-place data
dir_ = -1 #-1 = up | 1 = down
x, y = grid.shape[1]-1, grid.shape[0]-1
i = 0
zigzag = 0
final_data_bits = ""
while x >= 0:
if mask_area[y,x] == 1:
final_data_bits += str(int(unmasked[y, x]))
if ((dir_+1)/2 + zigzag)%2 == 0:
x -= 1
else:
y += dir_
x += 1
if y == -1 or y == grid.shape[0]:
dir_ = -dir_
y += dir_
x -= 2
else:
zigzag = 1-zigzag
#Vertical timing pattern
if x == 6:
x -= 1
#Remove remainder bits
if 2 <= V < 7:
final_data_bits = final_data_bits[:-7]
elif 14 <= V < 21 or 28 <= V < 35:
final_data_bits = final_data_bits[:-3]
elif 21 <= V < 28:
final_data_bits = final_data_bits[:-4]
ec = ERROR_CORRECTION[V-1, lvl]
#print(ec)
codewords = [final_data_bits[i:i+8] for i in range(0,len(final_data_bits),8)]
#Only one block
if ec[2]+ec[4] == 1:
data_codewords = codewords[:ec[3]]
ec_codewords = codewords[ec[3]:]
else:
group1, group2 = [], []
group_ec = []
for b in range(ec[2]):
block = []
for i in range(ec[3]):
block.append(codewords[b+i*(ec[2]+ec[4])])
group1.append(block)
if ec[4] > 0:
for b in range(ec[2], ec[2]+ec[4]):
block = []
for i in range(ec[5]):
off = 0
if i >= ec[3]:
off = (i-ec[3]+1)*ec[2]
block.append(codewords[b+i*(ec[2]+ec[4])-off])
group2.append(block)
codewords = codewords[ec[2]*ec[3]+ec[4]*ec[5]:]
for b in range(ec[2]+ec[4]):
block = []
for i in range(ec[1]):
block.append(codewords[b+i*(ec[2]+ec[4])])
group_ec.append(block)
data_codewords = sum(group1, []) + sum(group2, [])
ec_codewords = sum(group_ec, [])
#print(data_codewords)
#print(ec_codewords)
try:
decoded = correct(data_codewords, ec_codewords)
except ReedSolomonException as e:
#print(e)
if not f2:
return _decode(grid, V, flipped, True)
elif flipped is None:
f = grid.copy()
f = np.rot90(np.fliplr(f))
return _decode(grid, V, f, False)
else:
#raise ReedSolomonException("Cannot decode")
return None
decoded = "".join(list(map(lambda c: f"{c.val:08b}", decoded.coefs)))
mode, decoded = decoded[:4], decoded[4:]
MODES = ["0001", "0010", "0100", "1000"]
mode = MODES.index(mode)
if 1 <= V < 10:
char_count_len = [10,9,8,8][mode]
elif 10 <= V < 27:
char_count_len = [12,11,16,10][mode]
elif 27 <= V < 41:
char_count_len = [14,13,16,12][mode]
length, decoded = decoded[:char_count_len], decoded[char_count_len:]
length = int(length, 2)
value = None
if mode == 0:
value = ""
_ = length//3
l = _ * 10
if 10 - _ == 2:
l += 7
else:
l += 4
data = decoded[:l]
groups = [data[i:i+10] for i in range(0, l, 10)]
for group in groups:
value += str(int(group,2))
value = int(value)
elif mode == 1:
value = ""
data = decoded[:length//2 * 11 + (length%2)*6]
for i in range(0, len(data), 11):
s = data[i:i+11]
val = int(s, 2)
if len(s) == 6:
value += ALPHANUM[val]
else:
value += ALPHANUM[val//45]
value += ALPHANUM[val%45]
elif mode == 2:
data = decoded[:length*8]
data = [data[i:i+8] for i in range(0, length*8, 8)]
data = list(map(lambda b: int(b, 2), data))
value = bytes(data).decode("ISO-8859-1")
elif mode == 3:
value = []
data = decoded[:length*13]
for i in range(0, len(data), 13):
val = int(data[i:i+13], 2)
msb = val // 0xc0
lsb = val % 0xc0
dbyte = (msb << 8) + lsb
if 0 <= dbyte <= 0x9ffc - 0x8140:
dbyte += 0x8140
elif 0xe040 - 0xc140 <= dbyte <= 0xebbf - 0xc140:
dbyte += 0xc140
value.append(dbyte >> 8)
value.append(dbyte & 0xff)
value = bytes(value).decode("shift_jis")
#print("value:", value)
return value
# If unreadable
# -> _decode(f2=True)
# -> mirror image
class ReedSolomonException(Exception):
pass
def correct(data, ec):
n = len(ec)
data = Poly([GF(int(cw, 2)) for cw in data+ec])
##print("data", list(map(lambda c:c.val, data.coefs)))
syndrome = [0]*n
corrupted = False
for i in range(n):
syndrome[i] = data.eval(GF.EXP[i])
if syndrome[i].val != 0:
corrupted = True
if not corrupted:
print("No errors")
return data
syndrome = Poly(syndrome[::-1])
#print("syndrome", syndrome)
#Find locator poly
sigma, omega = euclidean_algorithm(Poly([GF(1)]+[GF(0) for i in range(n)]), syndrome, n)
#print("sigma", sigma)
#print("omega", omega)
error_loc = find_error_loc(sigma)
error_mag = find_error_mag(omega, error_loc)
for i in range(len(error_loc)):
pos = GF(error_loc[i]).log()
pos = data.deg - pos.val - 1
if pos < 0:
raise ReedSolomonException("Bad error location")
data.coefs[pos] += GF(error_mag[i])
return data
def euclidean_algorithm(a, b, R):
if a.deg < b.deg:
a, b = b, a
r_last = a
r = b
t_last = Poly([GF(0)])
t = Poly([GF(1)])
while r.deg-1 >= int(R/2):
r_last_last = r_last
t_last_last = t_last
r_last = r
t_last = t
if r_last.coefs[0] == 0:
raise ReedSolomonException("r_{i-1} was zero")
r = r_last_last
q = Poly([GF(0)])
denom_lead_term = r_last.coefs[0]
dlt_inv = denom_lead_term ** GF(-1)
I = 0
while r.deg >= r_last.deg and r.coefs[0] != 0:
I += 1
deg_diff = r.deg - r_last.deg
scale = r.coefs[0] * dlt_inv
q += Poly([scale]+[GF(0) for i in range(deg_diff)])
r += r_last * Poly([scale]+[GF(0) for i in range(deg_diff)])
q.del_lead_zeros()
r.del_lead_zeros()
if I > 100:
raise ReedSolomonException("Too long")
t = (q * t_last).del_lead_zeros() + t_last_last
t.del_lead_zeros()
if r.deg >= r_last.deg:
raise ReedSolomonException("Division algorithm failed to reduce polynomial")
sigma_tilde_at_zero = t.coefs[-1]
if sigma_tilde_at_zero.val == 0:
raise ReedSolomonException("sigma_tilde(0) was zero")
inv = Poly([sigma_tilde_at_zero ** GF(-1)])
sigma = t * inv
omega = r * inv
return [sigma, omega]
def find_error_loc(error_loc):
num_errors = error_loc.deg-1
if num_errors == 1:
return [error_loc.coefs[-2].val]
result = [0]*num_errors
e = 0
i = 1
while i < 256 and e < num_errors:
if error_loc.eval(GF(i)).val == 0:
result[e] = (GF(i) ** GF(-1)).val
e += 1
i += 1
if e != num_errors:
raise ReedSolomonException("Error locator degree does not match number of roots")
return result
def find_error_mag(error_eval, error_loc):
s = len(error_loc)
result = [0]*s
for i in range(s):
xi_inv = GF(error_loc[i]) ** GF(-1)
denom = GF(1)
for j in range(s):
if i != j:
denom *= GF(1) + GF(error_loc[j]) * xi_inv
result[i] = ( error_eval.eval(xi_inv) * (denom ** GF(-1)) ).val
return result
def get_fmt(grid ,f2=False):
fmt1 = list(grid[0:6, 8]) + [grid[7,8], grid[8,8], grid[8,7]] + list(grid[8, 0:6][::-1])
fmt2 = list(grid[8, -8:][::-1]) + list(grid[-7:, 8])
fmt1 = "".join([str(int(b)) for b in fmt1])[::-1]
fmt2 = "".join([str(int(b)) for b in fmt2])[::-1]
if f2:
return decode_fmt(fmt2)
return decode_fmt(fmt1)
def decode_fmt(fmt):
format_data = int(fmt, 2)
format_data ^= 0b101010000010010
format_data = f"{format_data:015b}"
closest = None
with open("./valid_format_str.txt", "r") as f:
for i, format_str in enumerate(f):
diff = sum(1 for a, b in zip(format_data, format_str) if a != b)
if closest is None or diff < closest[1]:
closest = (i, diff)
lvl = closest[0] >> 3
lvl = (5-lvl)%4
mask = closest[0]&0b111
return [lvl, mask]
if __name__ == "__main__":
cam = cv2.VideoCapture(0)
while True:
ret_val, img = cam.read()
if not ret_val:
continue
cv2.imshow("src", img)
try:
value = decode(img)
if not value is None:
print(value)
except Exception as e:
#pass
#raise
print(e)
cv2.waitKey(1)
cv2.destroyAllWindows()

199
python/qr_versions.txt Normal file
View File

@ -0,0 +1,199 @@
41 25 17 10
34 20 14 8
27 16 11 7
17 10 7 4
77 47 32 20
63 38 26 16
48 29 20 12
34 20 14 8
127 77 53 32
101 61 42 26
77 47 32 20
58 35 24 15
187 114 78 48
149 90 62 38
111 67 46 28
82 50 34 21
255 154 106 65
202 122 84 52
144 87 60 37
106 64 44 27
322 195 134 82
255 154 106 65
178 108 74 45
139 84 58 36
370 224 154 95
293 178 122 75
207 125 86 53
154 93 64 39
461 279 192 118
365 221 152 93
259 157 108 66
202 122 84 52
552 335 230 141
432 262 180 111
312 189 130 80
235 143 98 60
652 395 271 167
513 311 213 131
364 221 151 93
288 174 119 74
772 468 321 198
604 366 251 155
427 259 177 109
331 200 137 85
883 535 367 226
691 419 287 177
489 296 203 125
374 227 155 96
1022 619 425 262
796 483 331 204
580 352 241 149
427 259 177 109
1101 667 458 282
871 528 362 223
621 376 258 159
468 283 194 120
1250 758 520 320
991 600 412 254
703 426 292 180
530 321 220 136
1408 854 586 361
1082 656 450 277
775 470 322 198
602 365 250 154
1548 938 644 397
1212 734 504 310
876 531 364 224
674 408 280 173
1725 1046 718 442
1346 816 560 345
948 574 394 243
746 452 310 191
1903 1153 792 488
1500 909 624 384
1063 644 442 272
813 493 338 208
2061 1249 858 528
1600 970 666 410
1159 702 482 297
919 557 382 235
2232 1352 929 572
1708 1035 711 438
1224 742 509 314
969 587 403 248
2409 1460 1003 618
1872 1134 779 480
1358 823 565 348
1056 640 439 270
2620 1588 1091 672
2059 1248 857 528
1468 890 611 376
1108 672 461 284
2812 1704 1171 721
2188 1326 911 561
1588 963 661 407
1228 744 511 315
3057 1853 1273 784
2395 1451 997 614
1718 1041 715 440
1286 779 535 330
3283 1990 1367 842
2544 1542 1059 652
1804 1094 751 462
1425 864 593 365
3517 2132 1465 902
2701 1637 1125 692
1933 1172 805 496
1501 910 625 385
3669 2223 1528 940
2857 1732 1190 732
2085 1263 868 534
1581 958 658 405
3909 2369 1628 1002
3035 1839 1264 778
2181 1322 908 559
1677 1016 698 430
4158 2520 1732 1066
3289 1994 1370 843
2358 1429 982 604
1782 1080 742 457
4417 2677 1840 1132
3486 2113 1452 894
2473 1499 1030 634
1897 1150 790 486
4686 2840 1952 1201
3693 2238 1538 947
2670 1618 1112 684
2022 1226 842 518
4965 3009 2068 1273
3909 2369 1628 1002
2805 1700 1168 719
2157 1307 898 553
5253 3183 2188 1347
4134 2506 1722 1060
2949 1787 1228 756
2301 1394 958 590
5529 3351 2303 1417
4343 2632 1809 1113
3081 1867 1283 790
2361 1431 983 605
5836 3537 2431 1496
4588 2780 1911 1176
3244 1966 1351 832
2524 1530 1051 647
6153 3729 2563 1577
4775 2894 1989 1224
3417 2071 1423 876
2625 1591 1093 673
6479 3927 2699 1661
5039 3054 2099 1292
3599 2181 1499 923
2735 1658 1139 701
6743 4087 2809 1729
5313 3220 2213 1362
3791 2298 1579 972
2927 1774 1219 750
7089 4296 2953 1817
5596 3391 2331 1435
3993 2420 1663 1024
3057 1852 1273 784

View File

@ -0,0 +1,32 @@
000000000000000
000010100110111
000101001101110
000111101011001
001000111101011
001010011011100
001101110000101
001111010110010
010001111010110
010011011100001
010100110111000
010110010001111
011001000111101
011011100001010
011100001010011
011110101100100
100001010011011
100011110101100
100100011110101
100110111000010
101001101110000
101011001000111
101100100011110
101110000101001
110000101001101
110010001111010
110101100100011
110111000010100
111000010100110
111010110010001
111101011001000
111111111111111