Pinky mandelbrot set fractal

iteration_limit = 89

np.uint8(indexed * 3.6), # RED

np.uint8(int(np.sin(indexed * np.pi) + 1) * 65), # GREEN

np.uint8(indexed * 4) # BLUE


Fractaling

With python3 and the numpy, numba and pillow packages.
Also imagemagick will need to be installed from whatever distro's package repository.

The Mandelbrot Set. The code below will display and / or save as a png image file. The idea is to open it in a text editor and experiment with operations on the RGB to get different colouring. Run in the terminal then it is posible to zoom and offset with -m flag followed by some magnification factor and an -x flag followed by a complex figure respectively. Running in an editor will just use the defaults and display the output.
An example command with times ten magnification and offset -1.45 real, 0.1 imaginary:-

python3 fractaling.py -m 10 -x -1.45 0.1

Supply a file name .png after the -o flag to save as an image.
An -n flag will skip the display of output. --help will show help message.

The Mandelbrot Set uses the coordinates of each "pixel" in the complex plane as if it were mapped out to your screen. X values being real numbers and Y values being imaginary numbers. The X or real extent being -2.0 to 1.0 and Y or imaginary extent -1.0 to 1.0. Now, for every pixel denoted by c, in the expression
z = z2 + c, in the mandlebrot() function below. The expression iterates on itself until z exceeds the bounds
z real2 + z imaginary2 >= 4. In which case the number of iterations will be returned unless a predetermined iteration_limit is reached ( 85 in this case ) and returned. The returned values are stored in a 2d array result[ row_index, column_index ]. These numbers are used to build the mandelbrot set image.
There is a good visualization of this process at www.geogebra.org
Imagine these values being maped to shades of grey : O being dark and 85 being light. Thats basically how it works.
Now imagine these values being maped to RGB.
Now imagine these values being maped to RGB via your very own custom blend of mathematics. The possibilities are endless.

#RED #GREEN #BLUE ... read on.

      
import numpy as np
from PIL import Image
from numba import jit
import argparse


parser = argparse.ArgumentParser(prog="FRACTAL")
parser.add_argument("-n", "--nodisplay", action="store_false",
                    help="do not display output")
parser.add_argument("-m", "--magnification", nargs="?", metavar='MAGNIFICATION',
                    help="required magnification", type=float, default=1.0)
parser.add_argument("-s", "--size", nargs="?", metavar="SIZE",
                    help="Size by y pixels default=700", type=int, default=700)
parser.add_argument("-x", "--xyoffset", action='store', nargs=2, metavar="OFFSET",
                    help="row and column offset", type=float, default=[0.0, 0.0])
parser.add_argument("-o", "--outfile", nargs="?", metavar="SAVE_AS",
                    help="save as <path to filename.whateva>", type=str)

args = parser.parse_args()
if args.magnification < 1.0:
    parser.error("magnification must have a minimum value of 1")
if abs(args.xyoffset[1]) > 1 or args.xyoffset[0] < -2 or args.xyoffset[0] > 1:
    parser.error("xyoffset must range between -2 to 1 and -1 to 1 respectively")


@jit
def mandelbrot(re, im, iter_limit):
    c = complex(re, im)
    z = complex(0.0, 0.0)
    for i in range(iter_limit):
        z = z**2 + c
        if (z.real**2) + (z.imag**2) >= 4:
            return i
    return iter_limit


try:
    iteration_limit = 85
    columns = args.size
    rows = int(columns * 1.5)
    magnification = args.magnification
    row_offset = args.xyoffset[0]
    col_offset = args.xyoffset[1]

    row_extent = (np.array([-2.0, 1.0]) + row_offset * magnification) * 1 / magnification
    column_extent = (np.array([1.0, -1.0]) + col_offset * magnification) * 1 / magnification
    result = np.zeros([rows, columns])
    for row_index, re in enumerate(np.linspace(row_extent[0], row_extent[1], rows)):
        for column_index, im in enumerate(np.linspace(column_extent[0], column_extent[1], columns)):
            result[row_index, column_index] = mandelbrot(re, im, iteration_limit)

    result = result.T
    print(f"image size: {result.shape[1]}, {result.shape[0]}")
    image_array = np.zeros((columns, rows, 3), dtype=np.uint8)
    for real in range(rows):
        for imaginary in range(columns):
            indexed = result[imaginary, real]
            image_array[imaginary, real] = (np.uint8((np.sin((indexed) * np.pi / 85) * 255)), # RED
                                            np.uint8(80 + (indexed * 2)),                     # GREEN
                                            np.uint8(170 - (indexed * 2))                     # BLUE
                                            )

    new_image = Image.fromarray(image_array, "RGB")
    if args.outfile:
        new_image.save(args.outfile)
        print("image saved")
    if args.nodisplay:
        new_image.show()

except KeyboardInterrupt:
    parser.exit("\n Interupt by user")
except Exception as e:
    parser.exit(f"{type(e).__name__}: {str(e)}")

      
    


The returned iterations resut[ ] from z = z**2 + c are used for all three colors: Red, green and blue. A different operation is performed on the indexed result[ ] value for each colour. You can do whatever operation you like as long as the answer is between 0 and 255. Although going higher than 255 is the same as 0, so it can be useful.
( This includes increasing the iteration_limit a tiny little bit )

For example:-

      
indexed = result[imaginary, real]
image_array[imaginary, real] = (np.uint8(170 - (indexed * 2)),                           # RED
                                np.uint8((np.sin((indexed) * np.pi / 85) * 255)),        # GREEN
                                np.uint8(80 + (indexed * 2.1))                           # BLUE >255
                                )
      
    

As you can see np.uint8(whatever operation to indexed)

Any old random crap will do. You will be surprised. Or a histogram of the returned values can give guidance.
Image size can be changed by assigning new values to rows and column variables. for example: rows = 1920 and columns = 1280
A copy of the code is also on codeberg.org
If you do not Know numpy just stick np. in front of all your trigonomic funcions and pi or whatever, you will be fine. It will either work or not work.

Histogram of mandelbrot function results

After some experimenting (below left) with sine and cosine. And another (below right) with iteration_limit = 86. Later the center is erased and replaced with some layers in GIMP photo editor.

Image - zoomed mandelbrot set
Image - zoomed mandelbrot set art with faces
      
image_array[imaginary, real] = (np.uint8((np.cos(indexed * np.pi * 3 /85) + 1) * 60),   # RED
                                np.uint8((np.sin(indexed * np.pi * 4 /85) + 1) * 30),   # GREEN
                                np.uint8(indexed * 0.7)                                 # BLUE
                                )
# Any old crap.
      
    

Sound ?

A few months ago or more, I had recorded 3 words, with a microphone into a numpy array, grabbed the envelope for each word with the envelope_grabber.py code from Experimentations Then resampled it with scipy.signal.resample to match the iteration limit of the mandlebrot(re, im, iteration_limit) function (85 iterations in this case). Then the values in the resampled sound array are scaled to range between 0 and 255. Then its np.uint8(sound_array[int(indexed)]).
Interesting. I even tried it with musical instruments too. Trimming them in Audacity first. But i soon got bored.

Recording, grab envelope and resampling

      
import numpy as np
import scipy.signal as sig
import sounddevice as sd
import matplotlib.pyplot as plt
import time


class ThyEnveloper():
    """
    Get the envelope of a sound.
    data - 1d numpy array.
    window_size - int, optional n samples. default=700.
    invert - boolean, optional invert envelope. default=False.
    """
    def __init__(self, data, window_size=700, invert=False):
        self.data = data
        self.window_size = window_size
        self.invert = invert

    def get_envelope(self):
        "Returns the envelope"
        self.data_size = np.size(self.data)
        self.data = np.abs(self.data)
        self.result = np.zeros(self.data_size)
        self.iterations = int(self.data_size // self.window_size)
        self.extra = self.data_size % self.window_size

        for i in range(self.iterations):
            self.ave = np.max(self.data[i * self.window_size:i * self.window_size + self.window_size])
            self.slice = np.linspace(self.result[i * self.window_size-1] if i > 0 else np.max(
                                     self.data[i:self.window_size]), self.ave, self.window_size)
            self.result[i * self.window_size:i * self.window_size + self.window_size] = self.slice

        self.slice = np.linspace(self.result[-self.extra - 1],
                                 np.max(self.data[-self.extra:]), self.extra)
        if self.invert:
            self.result = 1 - self.result
        if self.extra == 0:
            return self.result
        else:
            self.result[-self.extra:] = self.slice
            return self.result


sample_rate = 44100
duration = 1.0          # Recording duration

# Count down to start of recording
print("3")
time.sleep(0.5)
print("2")
time.sleep(0.5)
print("1")
time.sleep(0.5)
print("GO")

# Record
sound = sd.rec(int(duration * sample_rate), samplerate=sample_rate, channels=1)
sd.wait()

# Normalize
sound = sound / np.max(np.abs(sound))

# Play back recording
sd.play(sound)
sd.wait()

# Grab envelope
envelope_obj = ThyEnveloper(sound)
envelope = envelope_obj.get_envelope()

# Resample
new_array_size = 86
sound_envelope_resampled = sig.resample(envelope, new_array_size)
sound_envelope_resampled = sound_envelope_resampled * 255
print(np.max(sound_envelope_resampled))
print(np.min(sound_envelope_resampled))

plt.figure()
plt.plot(sound_envelope_resampled)
plt.show()
      
    

Then something like This

      
image_array[imaginary, real] = (np.uint8(sound_envelope_resampled[int(indexed)]),         #RED
                                np.uint8(sound_envelope_resampled[int(indexed)] * 0.5),   #GREEN
                                np.uint8(255- (sound_envelope_resampled[int(indexed)]))   #BLUE
                                )
      
    

Julia Set

And then there is the Julia Set. It is basically the same code as the mandelbrot only z gets passed the complex coordinates and c is a constant. A whatever complex number you like constant. But it is probably best to start with some numbers from halfway down the WikipediA page.
The rows and columns will need equal numbers and the extent is from -2 to 2 on both axis.

------+------

There is no reason why you can't mess with the z = z 2 + c expression either.

Back To Home Page
Random unrelated stuff about a friendly wasp and moths 'n' stuff