In [2]:
# Makes sure to install PyPNG image handling module
import sys
!{sys.executable} -m pip install pypng
In [3]:
import png
In [ ]:
In [4]:
r = png.Reader("ex.png")
t = r.asRGB()
img = list(t[2])
# print(img)
A rather simple PNG we'll work with first:
In [5]:
# Let us first define colour red
# We'll work with RGB for colours
# So for accepted variants we'll make a list of 3-lists.
class colourlist(list):
"""Just lists of 3-lists with some fancy methods to work with RGB colours
"""
def add_deviations(self, d=8): # Magical numbers are so magical!
"""Adds deviations for RGB colours to a given list.
Warning! Too huge - it takes forever.
Input: list of 3-lists
Output: None (side-effects - changes the list)
"""
#l = l[:] Nah, let's make it a method
l = self
v = len(l)
max_deviation = d
for i in range(v): # Iterate through the list of colours
for j in range(-max_deviation, max_deviation+1):
# Actually it is the deviation.
#for k in range(3): # RGB! (no "a"s here)
newcolour = self[i][:] # Take one of the original colours
newcolour[0] = abs(newcolour[0]+j) # Create a deviation
l.append(newcolour)
# Append new colour to the end of the list.
# <- Here it is changed!
for j in range(-max_deviation, max_deviation+1):
# Work with all the possibilities with this d
newcolour1 = newcolour[:]
newcolour1[1] = abs(newcolour1[1]+j)
l.append(newcolour1)
# Append new colour to the end of the list. Yeah!
# <- Here it is changed!
for j in range(-max_deviation, max_deviation+1):
# Work with all the possibilities with this d
newcolour2 = newcolour1[:]
newcolour2[2] = abs(newcolour2[2]+j)
l.append(newcolour2) # Append new colour to the end of the list. Yeah!
# <- Here it is changed!
return None
def withinDeviation(colour, cl, d=20):
"""This is much more efficient!
Input: 3-list (colour), colourlist, int
Output: bool
"""
for el in cl:
if (abs(colour[0] - el[0]) <= d and
abs(colour[1] - el[1]) <= d and
abs(colour[2] - el[2]) <= d):
return True
return False
accepted_colours = colourlist([[118, 58, 57], [97, 71, 36], [132, 56, 46], [132, 46, 47], [141, 51, 53]]) # ...
#accepted_colours.add_deviations()
# print(accepted_colours) # -check it! - or better don't - it is a biiiig list....
# print(len(accepted_colours)) # That will take a while... Heh..
In [6]:
def find_first_pixel_of_colour(pixellist, accepted_deviations):
"""Returns the row and column of the pixel
in a converted to list with RGB colours PNG
Input: ..., colourlist
Output: 2-tuple of int (or None)
"""
accepted_deviations = accepted_deviations[:]
rows = len(pixellist)
cols = len(pixellist[0])
for j in range(rows):
for i in range(0, cols, 3):
# if [pixellist[j][i], pixellist[j][i+1], pixellist[j][i+2]] in accepted_deviations:
if withinDeviation([pixellist[j][i], pixellist[j][i+1], pixellist[j][i+2]], accepted_deviations):
return (j, i)
return None
fr = find_first_pixel_of_colour(img, accepted_colours)
if fr is None:
print("Warning a corrupt file or a wrong format!!!")
print(fr)
print(img[fr[0]][fr[1]], img[fr[0]][fr[1]+1], img[fr[0]][fr[1]+2])
print(img[fr[0]])
In [7]:
# [133, 56, 46] in accepted_colours
In [8]:
# Let us now find the length of the red lines that represent the sync signal
def find_next_pixel_in_row(pixel, row, accepted_deviations):
"""Returns the column of the next pixel of a given colour
(with deviations) in a row from a converted to list with RGB
colours PNG
Input: 2-tuple of int, list of int with len%3==0, colourlist
Output: int (returns -1 specifically if none are found)
"""
l = len(row)
if pixel[1] >= l-1:
return -1
for i in range(pixel[1]+3, l, 3):
# if [row[i], row[i+1], row[i+2]] in accepted_deviations:
if withinDeviation([row[i], row[i+1], row[i+2]], accepted_deviations):
return i
return -1
def colour_line_length(pixels, start, colour, deviations=20):
line_length = 1
pr = start[:]
r = (pr[0],
find_next_pixel_in_row(pr, pixels[pr[0]], colour[:]))
# print(pr, r)
if not(r[1] == pr[1]+3):
print("Ooops! Something went wrong!")
else:
line_length += 1
while (r[1] == pr[1]+3):
pr = r
r = (pr[0],
find_next_pixel_in_row(pr,
pixels[pr[0]], colour[:]))
line_length += 1
return line_length
line_length = colour_line_length(img, fr, accepted_colours, deviations=20)
print(line_length) # !!!
We found the sync (clock) line length in our graph!
In [9]:
print("It is", line_length)
Now the information transfer signal itself is ~"black", so we need to find the black colour range as well!
In [10]:
# Let's do just that
black = colourlist([[0, 0, 0], [0, 1, 0], [7, 2, 8]])
# black.add_deviations(60) # experimentally it is somewhere around that
# experimentally the max deviation is somewhere around 60
print(black)
The signal we are currently interested in is Manchester code (as per G.E. Thomas).
It is a self-clocking signal, but since we do have a clock with it - we use it)
Let us find the height of the Manchester signal in our PNG - just because...
In [11]:
fb = find_first_pixel_of_colour(img, black)
In [12]:
def signal_height(pxls, fib):
signal_height = 1
# if ([img[fb[0]+1][fb[1]], img[fb[0]+1][fb[1]+1], img[fb[0]+1][fb[1]+2]] in black):
if withinDeviation([pxls[fib[0]+1][fib[1]], pxls[fib[0]+1][fib[1]+1]
, pxls[fib[0]+1][fib[1]+2]], black, 60):
signal_height += 1
i = 2
rows = len(pxls)
# while([img[fb[0]+i][fb[1]], img[fb[0]+i][fb[1]+1], img[fb[0]+i][fb[1]+2]] in black):
while(withinDeviation([pxls[fib[0]+i][fib[1]]
, pxls[fib[0]+i][fib[1]+1]
, pxls[fib[0]+i][fib[1]+2]], black, 60)):
signal_height += 1
i += 1
if (i >= rows):
break
else:
print("") # TO DO
return signal_height
sheight = signal_height(img, fb)-1
In [13]:
print(sheight)
In [14]:
# Let's quickly find the last red line
...
In [15]:
def manchester(pixels, start, clock,
line_colour, d=60, inv=False):
"""Decodes Manchester code (as per G. E. Thomas)
(or with inv=True Manchester code
(as per IEEE 802.4)).
Input: array of int with len%3==0 (- PNG pixels),
int, int, colourlist, int, bool (optional)
Output: str (of '1' and '0') or None
"""
res = ""
cols = len(pixels[0])
fb = find_first_pixel_of_colour(pixels, line_colour)
m = 2*clock*3-2*3 # Here be dragons!
# Hack: only check it using the upper line
# (or lack thereof)
if not(inv):
for i in range(start, cols-2*3, m):
fromUP = withinDeviation([pixels[fb[0]][i-6],
pixels[fb[0]][i-5],
pixels[fb[0]][i-4]],
line_colour, d)
if fromUP:
res = res + "1"
else:
res = res + "0"
else:
for i in range(start, cols-2*3, m):
fromUP = withinDeviation([pixels[fb[0]][i-6],
pixels[fb[0]][i-5],
pixels[fb[0]][i-4]],
line_colour, d)
if cond:
res = res + "0"
else:
res = res + "1"
return res
In [16]:
def nrz(pixels, start, clock,
line_colour, d=60, inv=False):
"""Decodes NRZ code
(or with inv=True its inversed version).
It is assumed that there is indeed a valid
NRZ code with a valid message.
Input: array of int with len%3==0 (- PNG pixels),
int, int, colourlist, int, bool (optional)
Output: str (of '1' and '0') or (maybe?) None
"""
res = ""
cols = len(pixels[0])
fb = find_first_pixel_of_colour(pixels, line_colour)
m = 2*clock*3-2*3 # Here be dragons!
# Hack: only check it using the upper line
# (or lack thereof)
if not(inv):
for i in range(start, cols, m):
UP = withinDeviation([pixels[fb[0]][i],
pixels[fb[0]][i+1],
pixels[fb[0]][i+2]],
line_colour, d)
if UP:
res = res + "1"
else:
res = res + "0"
else:
for i in range(start, cols-2*3, m):
UP = withinDeviation([pixels[fb[0]][i],
pixels[fb[0]][i+1],
pixels[fb[0]][i+2]],
line_colour, d)
if cond:
res = res + "0"
else:
res = res + "1"
return res
In [41]:
def code2B1Q(pixels, start, clock=None,
line_colour=[[0, 0, 0]], d=60, inv=False):
"""Decodes 2B1Q code. The clock is not used - it
is for compatibility only - really, so put
anything there. Does _NOT_ always work!
WARNING! Right now does not work AT ALL
(apart from one specific case)
Input: array of int with len%3==0 (- PNG pixels),
int, *, colourlist, int
Output: str (of '1' and '0') or None
"""
res = ""
cols = len(pixels[0])
fb = find_first_pixel_of_colour(pixels, line_colour) # (11, 33)
# will only work if the first or second dibit is 0b11
ll = colour_line_length(pixels, fb, line_colour, deviations=20) # 10
sh = signal_height(pixels, fb) - 1 # 17 -1?
m = ll*3-2*3 # will only work if there is a transition
# (after the first dibit)
# We only need to check if the line is
# on the upper, middle upper or middle lower rows...
for i in range(start, cols, m):
UP = withinDeviation([pixels[fb[0]][i],
pixels[fb[0]][i+1],
pixels[fb[0]][i+2]],
line_colour, d)
DOWN = withinDeviation([pixels[fb[0]+sh][i],
pixels[fb[0]+sh][i+1],
pixels[fb[0]+sh][i+2]],
line_colour, d)
almostUP = UP
# if UP:
# res = res + "10"
if DOWN: # elif DOWN:
res = res + "00"
# print("00")
elif almostUP:
res = res + "11"
# print("11")
else:
res = res + "01"
# print("01")
return res
In [18]:
# A-a-and... here is magic!
res = manchester(img, fr[1]+5*3, line_length, black, d=60, inv=False)
In [19]:
ans = []
for i in range(0, len(res), 8):
ans.append(int('0b'+res[i:i+8], 2))
# print(ans)
In [20]:
for i in range(0, len(ans)):
print(ans[i])
Huzzah!
And that is how we decode it.
Let us now look at some specific examples.
In [21]:
# Here is a helper function to automate all that
def parse_code(path_to_file, code, inv=False):
"""Guess what... Parses a line code PNG
Input: str, function
(~coinsides with the name of the code)
Output: str (of '1' and '0') or (maybe?) None
"""
r1 = png.Reader(path_to_file)
t1 = r1.asRGB()
img1 = list(t1[2])
fr1 = find_first_pixel_of_colour(img1, accepted_colours)
line_length1 = colour_line_length(img1,
fr1, accepted_colours, deviations=20)
res1 = code(img1, fr1[1]+5*3, line_length1, black, d=60, inv=inv)
return res1
In [22]:
def print_nums(bitesstr):
"""I hope you get the gist...
Input: str
Output: list (side effects - prints...)
"""
ans1 = []
for i in range(0, len(bitesstr), 8):
ans1.append(int('0b'+bitesstr[i:i+8], 2))
for i in range(0, len(ans1)):
print(ans1[i])
return ans1
In [ ]:
Here is a tricky example of Manchester code - where we have ASCII '0's and '1's with which a 3-letter "word" is encoded.
In [37]:
ans1 = print_nums(parse_code("Line_Code_PNGs/Manchester.png", manchester))
In [24]:
res2d = ""
for i in range(0, len(ans1)):
res2d += chr(ans1[i])
ans2d = []
for i in range(0, len(res2d), 8):
print(int('0b'+res2d[i:i+8], 2))
In [ ]:
In [25]:
ans2 = print_nums(parse_code("Line_Code_PNGs/NRZ.png", nrz))
In [ ]:
Warning! 2B1Q is currently almost completely broken. Pull requests with correct solutions are welcome :)
In [42]:
ans3 = print_nums(parse_code("Line_Code_PNGs/2B1Q.png", code2B1Q))
In [43]:
res2d3 = ""
for i in range(0, len(ans3)):
res2d3 += chr(ans3[i])
ans2d3 = []
for i in range(0, len(res2d3), 8):
print(int('0b'+res2d3[i:i+8], 2))
In [ ]: