In mathematics, the Thue–Morse sequence, or Prouhet–Thue–Morse sequence, is the binary sequence (an infinite sequence of 0s and 1s) obtained by starting with 0 and successively appending the Boolean complement of the sequence obtained thus far.
I wanted to create images using the Thue–Morse sequence, my idea is very simple, since the sequence is binary, group each eight bits into a byte, and then each three bytes into a pixel, and then make the list of pixels two-dimensional, done.
It is indeed that simple, but the result is of low contrast, so I increased contrast after the image was created.
Code
import numpy as np
from PIL import Image, ImageEnhance
from typing import List
def abbabaab(n: int) -> str:
return "".join(str(i.bit_count() % 2) for i in range(n))
def Thue_Morse(n: int) -> str:
s = [0]
for _ in range(n):
s += [i ^ 1 for i in s]
return "".join(map(str, s))
def abbabaab_list(n: int) -> List[int]:
return [i.bit_count() % 2 for i in range(n)]
def abbabaab_array(n: int) -> np.array:
bits = np.unpackbits(np.arange(n).view(np.uint8))
bits = bits.reshape((n, bits.shape[0] // n))
return np.sum(bits, axis=1) % 2
def Thue_Morse_array(n: int) -> np.array:
s = np.array([False], dtype=bool)
for _ in range(n):
s = np.concatenate([s, s ^ True])
return s
def log2_ceil(n: int) -> int:
log = n.bit_length()
return log - (1 << log - 1 == n)
def Thue_Morse_image(width: int, height: int) -> Image:
total = width * height * 24
pixels = Thue_Morse_array(log2_ceil(total))[:total].reshape((total // 8, 8))
pixels = np.sum(pixels * 1 << np.arange(8)[::-1], axis=1)
img = Image.fromarray(pixels.reshape((height, width, 3)).astype(np.uint8))
return ImageEnhance.Contrast(img).enhance(4)
def Thue_Morse_grayscale(width: int, height: int) -> Image:
total = width * height
pixels = Thue_Morse_array(log2_ceil(total))[:total].reshape((height, width))
return Image.fromarray(pixels)
def main(width: str, height: str, path: str, grayscale: str = '') -> None:
[Thue_Morse_image, Thue_Morse_grayscale][bool(grayscale)](int(width), int(height)).save(path)
if __name__ == "__main__":
import sys
main(*sys.argv[1:])
As you can see I have found three ways to generate the sequence, and this is the first time I have utilized NumPy thoroughly to do something.
How can it be improved?
Edit
I removed a bug where the array generation will run for one extra iteration if the number is extractly a power of 2. I also added a grayscale version of image creation function.