initial commit
This commit is contained in:
55
python/base.py
Normal file
55
python/base.py
Normal 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
68
python/code39.py
Normal 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
116
python/ean.py
Normal 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
199
python/error_correction.txt
Normal 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
75
python/hamming.py
Normal 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
66
python/img_gen/hamming.py
Normal 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)))
|
276
python/img_gen/lycacode_data_layout_gen.py
Normal file
276
python/img_gen/lycacode_data_layout_gen.py
Normal 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")
|
92
python/img_gen/lycacode_frame.py
Normal file
92
python/img_gen/lycacode_frame.py
Normal 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")
|
71
python/img_gen/lycacode_layout_gen.py
Normal file
71
python/img_gen/lycacode_layout_gen.py
Normal 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")
|
43
python/img_gen/lycacode_mask_gen.py
Normal file
43
python/img_gen/lycacode_mask_gen.py
Normal 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")
|
256
python/img_gen/qr_mask_eval_gen.py
Normal file
256
python/img_gen/qr_mask_eval_gen.py
Normal 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()
|
33
python/img_gen/qr_mask_gen.py
Normal file
33
python/img_gen/qr_mask_gen.py
Normal 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")
|
101
python/img_gen/qr_plcmt_path.py
Normal file
101
python/img_gen/qr_plcmt_path.py
Normal 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")
|
95
python/latex_gen/alignment_gen.py
Normal file
95
python/latex_gen/alignment_gen.py
Normal 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)
|
104
python/latex_gen/ec_converter.py
Normal file
104
python/latex_gen/ec_converter.py
Normal 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)
|
199
python/latex_gen/error_correction.txt
Normal file
199
python/latex_gen/error_correction.txt
Normal 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
|
99
python/latex_gen/hamming_gen.py
Normal file
99
python/latex_gen/hamming_gen.py
Normal 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)
|
199
python/latex_gen/qr_versions.txt
Normal file
199
python/latex_gen/qr_versions.txt
Normal 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
|
282
python/latex_gen/reed_solomon_gen.py
Normal file
282
python/latex_gen/reed_solomon_gen.py
Normal 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))
|
78
python/latex_gen/version_converter.py
Normal file
78
python/latex_gen/version_converter.py
Normal 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
284
python/lycacode_gen.py
Normal 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
164
python/lycacode_gen_mini.py
Normal 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
296
python/lycacode_scanner.py
Normal 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)
|
207
python/lycacode_scanner_mini.py
Normal file
207
python/lycacode_scanner_mini.py
Normal 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
891
python/qr_generator.py
Normal 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
882
python/qr_scanner.py
Normal 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
199
python/qr_versions.txt
Normal 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
|
32
python/valid_format_str.txt
Normal file
32
python/valid_format_str.txt
Normal 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
|
Reference in New Issue
Block a user