Skip to content

Instantly share code, notes, and snippets.

@aalhour
Forked from nst/eca.py
Last active October 29, 2017 14:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aalhour/533b8ea1a1d71bbfb026c16d956aa6d2 to your computer and use it in GitHub Desktop.
Save aalhour/533b8ea1a1d71bbfb026c16d956aa6d2 to your computer and use it in GitHub Desktop.
Elementary Cellular Automata PNG images generator. Based on Nicolas Seriot's gist (https://goo.gl/fh4d5Y).
#!/usr/bin/env python3
__author__ = "Ahmad Alhour"
__date__ = "2017-10-29"
__website__ = "https://gist.github.com/aalhour/533b8ea1a1d71bbfb026c16d956aa6d2"
"""
eca.py
Elementary Cellular Automata PNG images generator. Based on Nicolas Seriot's gist (https://goo.gl/fh4d5Y).
How to use:
python3 eca.py -h
Resources:
- http://mathworld.wolfram.com/CellularAutomaton.html
- http://mathworld.wolfram.com/ElementaryCellularAutomaton.html
- https://en.wikipedia.org/wiki/Elementary_cellular_automaton
- https://en.wikipedia.org/wiki/Rule_110
"""
import os
import argparse
from typing import List
import png
SUPPORTED_ECA_RULES = [
30, # 0b00011110
54, # 0b00110110
60, # 0b00111100
62, # 0b00111110
90, # 0b01011010
94, # 0b01011110
102, # 0b01100110
110, # 0b01101110
122, # 0b01111010
126, # 0b01111110
150, # 0b10010110
158, # 0b10011110
182, # 0b10110110
188, # 0b10111100
190, # 0b10111110
220, # 0b11011100
222, # 0b11011110
250 # 0b11111010
]
def create_argument_parser() -> argparse.ArgumentParser:
"""
Creates an argument parser for the command line.
"""
arg_parser = argparse.ArgumentParser(prog="eca")
arg_parser.add_argument(
"--width",
type=int, default=1000, help="Image width in pixels, i.e.: 1000.")
arg_parser.add_argument(
"--height",
type=int, default=1000, help="Image height in pixels, i.e.: 1000.")
arg_parser.add_argument(
"--rule",
type=int, default=110, choices=SUPPORTED_ECA_RULES, help="Elementary Cellular Automaton Rule.")
arg_parser.add_argument(
"--name",
type=str, action="store", default=None,
help="Desired name of the image. If not specified a random name will be generated.")
return arg_parser
def generate_random_str() -> str:
"""
Generates a pseudo-random hexadecimal string.
"""
return os.urandom(6).hex()
def write_data_to_png_file(matrix: List[List[int]], filename: str) -> None:
"""
Writes a given PNG matrix (2D array) to a file with the specified filename string.
"""
ll = [[255 - b * 255 for b in row] for row in matrix]
png.from_array(ll, 'L').save(filename)
def compute_eca_matrix(width: int=1000, height: int=1000, rule: int=110) -> List[List[int]]:
"""
Given an 2D image size, in addition to an ECA rule in integer representation,
generate an ECA matrix to be written to an image file.
"""
###
# Helper functions
#
def next_row(row: List[int], rule_bits: List[int]) -> List[int]:
"""
Generates the next row in the PNG matrix given the current row vector and
the bytearray of the ECA rule.
"""
r = row[-1:] + row + row[:1]
return [rule_bits[r[i]*4 + b*2 + r[i+2]] for (i, b) in enumerate(row)]
# Get the rule's bits from its decimal representation.
rule_bits = [int(x) for x in '{0:08b}'.format(rule)[::-1]]
# Setup the first row in the matrix.
row = [0] * width
row[width//2] = 1
eca_matrix = [row]
for i in range(height-1):
row = next_row(row, rule_bits)
eca_matrix.append(row)
return eca_matrix
def main():
"""
Entry point. Parses the command line for parameters and generates the PNG image accordingly.
"""
# Create a new argument parser.
arg_parser = create_argument_parser()
# Parse teh command line arguments.
args = arg_parser.parse_args()
# Compute the ECA Matrix.
eca_matrix = compute_eca_matrix(args.width, args.height, args.rule)
# Construct the image and file names.
image_name = args.name if args.name else generate_random_str()
file_name = "{name}__r{rule}_w{width}_h{height}.png".format(
name=image_name, rule=args.rule, width=args.width, height=args.height)
# Write the matrix to a new PNG file.
write_data_to_png_file(eca_matrix, file_name)
# Print the file path back to the user.
print("Image was saved at: {}".format(os.path.abspath(file_name)))
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment