Image generator using prime numbers in polar coordinates

Related

This is a Python script that generates images using prime numbers up to a given positive integer, it generates prime numbers using the Sieve of Eratosthenes with some rudimentary Wheel factorization optimization, and then converts each prime number p to a polar coordinate (p, p), which is p unit distance away from the origin and rotated p radians from the x axis.

The program then converts the coordinates to an image, in two modes: 'scatter' and 'triangles'. Scatter mode just puts the points on a black background, each coordinate makes the corresponding spot in the image non-black. In triangles mode, Delaunay triangulation is performed on the points to find triangles, and the resultant image will contain triangles instead of points.

There is also a colorize option, if it is set to False, the image created with 'scatter' mode will be white dots on black background, else the dots will be randomly colored. In the other mode, the image will be white edges of triangles on a black background if colorize is False, else the triangles will be filled with random colors.

All primes except 2 and 3 are of the form 6k+1 and 6k-1, in other words have a modulo 6 remainder of either 1 or 5, because if it is 2, 4 and 0 then it is a even number and thus composite if it is not 2, and if it is 3 then the number is an odd multiple of 3 thus a composite number. So I used the above information to optimize the sieving, I start the marking of composites at n² to make sure the number isn't already marked, and mark each number that is 2n more than the previous composite, to only mark odd multiples of n, and I process numbers up to the square root of the limit to reduce unnecessary operations.

Code

import matplotlib.pyplot as plt
import numpy as np
import typer
from itertools import cycle
from matplotlib.collections import PolyCollection
from PIL import Image
from scipy.spatial import Delaunay
from typing import Literal, Optional, Tuple
from typing_extensions import Annotated

def prime_wheel_sieve(n: int) -> np.ndarray:
wheel = cycle([4, 2, 4, 2, 4, 6, 2, 6])
primes = np.ones(n + 1, dtype=bool)
primes[:2] = False
for square, step in ((4, 2), (9, 6), (25, 10)):
primes[square::step] = False
k = 7
while (square := k * k) <= n:
if primes[k]:
primes[square :: 2 * k] = False
k += next(wheel)
return np.where(primes)[0]

def get_coordinates(n: int) -> Tuple[np.ndarray]:
primes = prime_wheel_sieve(n)
y = primes * np.sin(primes)
x = primes * np.cos(primes)
return x, y

def get_figure(
length: int, x: np.ndarray, y: np.ndarray
) -> Tuple[plt.Axes, plt.Figure]:
fig = plt.figure(figsize=(length / 100, length / 100), dpi=100, facecolor="black")
ax.set_axis_off()
fig.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=0, hspace=0)
plt.axis("scaled")
ax.axis([min(x), max(x), min(y), max(y)])
return ax, fig

def triangulate(x: np.ndarray, y: np.ndarray) -> np.ndarray:
points = np.vstack((x, y)).T
triangles = Delaunay(points)
return triangles.points[triangles.simplices]

def scatter_plot(ax: plt.Axes, x: np.ndarray, y: np.ndarray, colorize: bool) -> None:
colors = np.random.random((x.size, 3)) if colorize else "white"
ax.scatter(x, y, marker=".", c=colors, s=1)

def triangle_plot(ax: plt.Axes, x: np.ndarray, y: np.ndarray, colorize: bool) -> None:
if colorize:
triangles = triangulate(x, y)
count = triangles.shape[0]
colors = np.random.random((count, 3))
else:
ax.triplot(x, y, lw=1, c="white")

def get_image(fig: plt.Figure) -> Image:
fig.canvas.draw()
image = Image.frombytes(
"RGB", fig.canvas.get_width_height(), fig.canvas.tostring_rgb()
)
plt.close(fig)
return image

def prime_image(
n: int,
length: int = 1080,
colorize: bool = False,
mode: Literal["scatter", "triangles"] = "scatter",
) -> Image:
x, y = get_coordinates(n)
ax, fig = get_figure(length, x, y)
if mode == "scatter":
scatter_plot(ax, x, y, colorize)
elif mode == "triangles":
triangle_plot(ax, x, y, colorize)
return get_image(fig)

def annotate(arg_type: type, optional: bool, help_msg: str) -> Annotated:
if not optional:
return Annotated[arg_type, typer.Argument(help=help_msg)]

return Annotated[Optional[arg_type], typer.Option(help=help_msg)]

HELP = (
"specifies the number up to which the primality of natural numbers should be checked",
"specifies the path where the output image should be saved",
"specifies the side length of the output square image, in pixels, default 1080",
"specifies whether the image should be colorized or not, default False",
"""specifies the mode of the visualization, must be either 'scatter' or 'triangles',
if set to 'scatter', the resultant image will be a black background with some with points,
in which each point is a prime number distance away from the origin, and the same prime number radians rotated from x-axis.
If colorize is True, the points will be randomly colorized, else they will be white.
If the mode is 'triangles', the program will take the prime coordinates and perform Delaunay triangulation on them,
and then output the result. If colorize is False, the resultant image will be the white edges of many triangles on a black background,
else the triangles will be filled with random colors.""",
)
N = annotate(int, False, HELP[0])
PATH = annotate(str, False, HELP[1])
LENGTH = annotate(int, True, HELP[2])
COLORIZE = annotate(bool, True, HELP[3])
MODE = annotate(str, True, HELP[4])

def main(
n: N,
path: PATH,
length: LENGTH = 1080,
colorize: COLORIZE = False,
mode: MODE = "scatter",
) -> None:
"""This program generates all prime numbers up to a given number,
then converts the prime numbers to coordinates in the polar coordinate system,
so that the resultant point when rotated around the origin certain radians clockwise,
will coincide the point (x, 0), where x is a prime number and the number that corresponds to the point.
The program will then convert the data to an image visualization base on the paramters specified
"""
prime_image(n, length, colorize, mode).save(path)

if __name__ == "__main__":
typer.run(main)


Some example images:

2048

4096

8192

16384

256

512

1024

2048

Help message

How can it be improved?