initial commit

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

55
python/base.py Normal file
View File

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

68
python/code39.py Normal file
View File

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

116
python/ean.py Normal file
View File

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

199
python/error_correction.txt Normal file
View File

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

75
python/hamming.py Normal file
View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

284
python/lycacode_gen.py Normal file
View File

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

164
python/lycacode_gen_mini.py Normal file
View File

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

296
python/lycacode_scanner.py Normal file
View File

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

View File

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

891
python/qr_generator.py Normal file
View File

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

882
python/qr_scanner.py Normal file
View File

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

199
python/qr_versions.txt Normal file
View File

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

View File

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