#2. Numbers (cont.)

Note

If you have any ideas or enhancements for this page, please edit it on GitHub!

Following documentation is a cooperative result combined from our Discord chat and numerous pull requests. Thanks to everyone who helped!

Image

This image was produced from the second radio transmission using previously contributed code.

Interpretation

Contributed by Discord user @elventian.

We have enough data to conclude that we’ve found the way of encoding natural numbers by raster monochrome pictogram framed at the top and left. There is a square semantic region with side N inside the pictogram. Each pixel of the region corresponds to one bit in the binary notation of the number. Let x and y be column and row numbers in the range [0 … N), then the place value for the cell (x, y) is determined by the following formula:

\[place\_value(x,y) = 2^{y * N + x}\]

Place values for N up to 5:

Decoded

Decoded by Discord users @gltronred and @frictionless.

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
...
506 507 508 509 510 511 512 513 514
...
65535 65536 65537
...

Code

This Python code decodes and annotates numbers on a provided picture.

Contributed by Discord user @pink_snow.

Example output:

#!/usr/bin/env python
#! nix-shell -i python -p "python38.withPackages(p:[p.pillow])"

import sys
from PIL import Image


class Img:
    def __init__(self, fname, zoom):
        self._img = Image.open(fname)
        self._pixels = self._img.load()
        self._zoom = zoom

        self.size = self._img.size[0] // zoom, self._img.size[1] // zoom

    def __getitem__(self, xy):
        xy = xy[0] * self._zoom, xy[1] * self._zoom
        try:
            c = self._pixels[xy]
        except IndexError:
            return False
        return c[0] + c[1] + c[2] > 382

    def dump(self, ix, iy, higlight = set()):
        for y in iy:
            for x in ix:
                if (x,y) in higlight:
                    print(end="\x1b[40;31m")
                print(end=".#"[self[x,y]])
                if (x,y) in higlight:
                    print(end="\x1b[m")
            print()
        print()


class Svg:
    def __init__(self, fname, width, height):
        self._f = open(fname, "w")
        self._print(
            f'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="{width*8}" height="{height*8}">'
        )
        self._print(f'<rect width="{width*8}" height="{height*8}" style="fill:black"/>')

    def _print(self, *args, **kwargs):
        print(*args, **kwargs, file=self._f)

    def point(self, x, y):
        self._print(
            f'<rect x="{x*8}" y="{y*8}" width="7" height="7" style="fill:white"/>'
        )

    def annotation(self, x, y, w, h, text):
        self._print(
            f'<rect x="{x*8}" y="{y*8}" width="{w*8}" height="{h*8}" style="fill:green;opacity:0.5"/>'
        )
        style = "paint-order: stroke; fill: white; stroke: black; stroke-width: 2px; font:24px bold sans;"
        self._print(
            f'<text x="{x*8+w*4}" y="{y*8+h*4}" dominant-baseline="middle" text-anchor="middle" fill="white" style="{style}">{text}</text>'
        )

    def close(self):
        self._print("</svg>")
        self._f.close()


def decode_number(img, x, y):
    if img[x - 1, y - 1] or img[x, y - 1] or img[x - 1, y] or img[x, y]:
        return None

    # Get the size by iterating over top and left edges
    size = 0
    negative = False
    while True:
        items = (
            img[x + size + 1, y - 1],
            img[x + size + 1, y],
            img[x - 1, y + size + 1],
            img[x, y + size + 1],
        )
        if items == (False, True, False, True):
            size += 1
            continue
        if items == (False, False, False, False):
            break
        if items == (False, False, False, True):
            negative = True
            break
        return None

    if size == 0:
        return None

    # Check that right and bottom edges are empty
    for i in range(1,size + 2):
        if img[x + size + 1, y+i] or img[x+i, y + size + 1]:
            return None

    # Decode the number
    result, d = 0, 1
    for iy in range(size):
        for ix in range(size):
            result += d * img[x + ix + 1, y + iy + 1]
            d *= 2

    if negative:
        result = -result

    return (size, size+negative), result


def main(in_fname, out_fname):
    img = Img(in_fname, 4)
    svg = Svg(out_fname, img.size[0], img.size[1])

    for y in range(img.size[1]):
        for x in range(img.size[0]):
            if img[x, y]:
                svg.point(x, y)

    for y in range(img.size[1]):
        for x in range(img.size[0]):
            if (n := decode_number(img, x, y)) is not None:
                svg.annotation(x - 0.5, y - 0.5, n[0][0] + 2, n[0][1] + 2, n[1])
    svg.close()


main(sys.argv[1], sys.argv[2])