## What will we cover in this tutorial?

In this tutorial you will learn what the Julia set is and understand how it is calculated. Also, how it translates into colorful images. In the process, we will learn how to utilize vectorization with NumPy arrays to achieve it.

## Step 1: Understand the Julia set

Juila set are closely connect to the Mandelbrot set. If you are new to the Mandelbrot set, we recommend you read this tutorial before you proceed, as it will make it easier to understand.

Julia sets can be calculated for a function f. If we consider the function f_c(z) = z^2 + c, for a complex number c, then this function is used in the Mandelbrot set.

Recall the Mandelbrot set is calculated by identifying for a point c whether the function f_c(z) = z^2 + c , for which the sequence f_c(0), f_c(f_c(0)), f_c(f_c(f_c(0))), …., does not diverge.

Said differently, for each point c on the complex plane, if the sequence does not diverge, then that point is in the Mandelbrot set.

The Julia set has c fixed and and calculates the same sequence for z in the complex plane. That is, for each point z in the complex plane if the sequence f_c(0), f_c(f_c(0)), f_c(f_c(f_c(0))), …., does not diverge it is part of the Julia set.

## Step 2: Pseudo code for Julia set of non-vectorization computation

The best way to understand is often to see the non-vectorization method to compute the Julia set.

As we consider the function f_c(z) = z^2 + c for our Julia set, we need to choose a complex number for c. Note, that complex number c can be set differently to get another Julia set.

Then each we can iterate over each point z in the complex plane.

```c = -0.8 + i*0.34
for x in [-1, 1] do:
for y in [-1, 1] do:
z = x + i*y
N = 0
while absolute(z) < 2 and N < MAX_ITERATIONS:
z = z^2 + c
set color for x,y to N
```

This provides beautiful color images of the Julia set.

## Step 3: The vectorization computation using NumPy arrays

How does that translate into code using NumPy?

```import numpy as np
import matplotlib.pyplot as plt

def julia_set(c=-0.4 + 0.6j, height=800, width=1000, x=0, y=0, zoom=1, max_iterations=100):
# To make navigation easier we calculate these values
x_width = 1.5
y_height = 1.5*height/width
x_from = x - x_width/zoom
x_to = x + x_width/zoom
y_from = y - y_height/zoom
y_to = y + y_height/zoom

# Here the actual algorithm starts
x = np.linspace(x_from, x_to, width).reshape((1, width))
y = np.linspace(y_from, y_to, height).reshape((height, 1))
z = x + 1j * y

# Initialize z to all zero
c = np.full(z.shape, c)

# To keep track in which iteration the point diverged
div_time = np.zeros(z.shape, dtype=int)
# To keep track on which points did not converge so far
m = np.full(c.shape, True, dtype=bool)

for i in range(max_iterations):
z[m] = z[m]**2 + c[m]

m[np.abs(z) > 2] = False

div_time[m] = i
return div_time

plt.imshow(julia_set(), cmap='magma')
# plt.imshow(julia_set(x=0.125, y=0.125, zoom=10), cmap='magma')
# plt.imshow(julia_set(c=-0.8j), cmap='magma')
# plt.imshow(julia_set(c=-0.8+0.156j, max_iterations=512), cmap='magma')
# plt.imshow(julia_set(c=-0.7269 + 0.1889j, max_iterations=256), cmap='magma')

plt.show()
```

## What will we cover in this tutorial?

• Understand what the Mandelbrot set it and why it is so fascinating.
• Master how to make images in multiple colors of the Mandelbrot set.
• How to implement it using NumPy vectorization.

## Step 1: What is Mandelbrot?

Mandelbrot is a set of complex numbers for which the function f(z) = z^2 + c does not converge when iterated from z=0 (from wikipedia).

Take a complex number, c, then you calculate the sequence for N iterations:

z_(n+1) = z_n + c for n = 0, 1, …, N-1

If absolute(z_(N-1)) < 2, then it is said not to diverge and is part of the Mandelbrot set.

The Mandelbrot set is part of the complex plane, which is colored by numbers part of the Mandelbrot set and not.

This only gives a block and white colored image of the complex plane, hence often the images are made more colorful by giving it colors by the iteration number it diverged. That is if z_4 diverged for a point in the complex plane, then it will be given the color 4. That is how you end up with colorful maps like this.

## Step 2: Understand the code of the non-vectorized approach to compute the Mandelbrot set

To better understand the images from the Mandelbrot set, think of the complex numbers as a diagram, where the real part of the complex number is x-axis and the imaginary part is y-axis (also called the Argand diagram).

Then each point is a complex number c. That complex number will be given a color depending on which iteration it diverges (if it is not part of the Mandelbrot set).

Now the pseudocode for that should be easy to digest.

```for x in [-2, 2] do:
for y in [-1.5, 1.5] do:
c = x + i*y
z = 0
N = 0
while absolute(z) < 2 and N < MAX_ITERATIONS:
z = z^2 + c
set color for x,y to N
```

Simple enough to understand. That is some of the beauty of it. The simplicity.

## Step 3: Make a vectorized version of the computations

Now we understand the concepts behind we should translate that into to a vectorized version. If you are new to vectorization we can recommend you read this tutorial first.

What do we achieve with vectorization? That we compute all the complex numbers simultaneously. To understand that inspect the initialization of all the points here.

```import numpy as np

def mandelbrot(height, width, x_from=-2, x_to=1, y_from=-1.5, y_to=1.5, max_iterations=100):
x = np.linspace(x_from, x_to, width).reshape((1, width))
y = np.linspace(y_from, y_to, height).reshape((height, 1))
c = x + 1j * y
```

You see that we initialize all the x-coordinates at once using the linespace. It will create an array with numbers from x_from to x_to in width points. The reshape is to fit the plane.

The same happens for y.

Then all the complex numbers are created in c = x + 1j*y, where 1j is the imaginary part of the complex number.

This leaves us to the full implementation.

There are two things we need to keep track of in order to make a colorful Mandelbrot set. First, in which iteration the point diverged. Second, to achieve that, we need to remember when a point diverged.

```import numpy as np
import matplotlib.pyplot as plt

def mandelbrot(height, width, x=-0.5, y=0, zoom=1, max_iterations=100):
# To make navigation easier we calculate these values
x_width = 1.5
y_height = 1.5*height/width
x_from = x - x_width/zoom
x_to = x + x_width/zoom
y_from = y - y_height/zoom
y_to = y + y_height/zoom

# Here the actual algorithm starts
x = np.linspace(x_from, x_to, width).reshape((1, width))
y = np.linspace(y_from, y_to, height).reshape((height, 1))
c = x + 1j * y

# Initialize z to all zero
z = np.zeros(c.shape, dtype=np.complex128)
# To keep track in which iteration the point diverged
div_time = np.zeros(z.shape, dtype=int)
# To keep track on which points did not converge so far
m = np.full(c.shape, True, dtype=bool)

for i in range(max_iterations):
z[m] = z[m]**2 + c[m]

diverged = np.greater(np.abs(z), 2, out=np.full(c.shape, False), where=m) # Find diverging

div_time[diverged] = i      # set the value of the diverged iteration number
m[np.abs(z) > 2] = False    # to remember which have diverged
return div_time

# Default image of Mandelbrot set
plt.imshow(mandelbrot(800, 1000), cmap='magma')
# The image below of Mandelbrot set
# plt.imshow(mandelbrot(800, 1000, -0.75, 0.0, 2, 200), cmap='magma')
# The image below of below of Mandelbrot set
# plt.imshow(mandelbrot(800, 1000, -1, 0.3, 20, 500), cmap='magma')
plt.show()
```

Notice that z[m] = z[m]**2 + c[m] only computes updates on values that are still not diverged.

I have added the following two images from above (the one not commented out is above in previous step.

## What will you learn in this tutorial?

• Where to find interesting data contained in CSV files.
• How to extract a map to plot the data on.
• Use Python to easily plot the data from the CSV file no the map.

## Step 1: Collect the data in CSV format

You can find various interesting data in CSV format on data.world that you can play around with in Python.

In this tutorial we will focus on Shooting Incidents in NYPD from the last year. You can find the data on data.world.

Looking at the data you see that each incident has latitude and longitude coordinates.

```{'INCIDENT_KEY': '184659172', 'OCCUR_DATE': '06/30/2018 12:00:00 AM', 'OCCUR_TIME': '23:41:00', 'BORO': 'BROOKLYN', 'PRECINCT': '75', 'JURISDICTION_CODE': '0', 'LOCATION_DESC': 'PVT HOUSE                     ', 'STATISTICAL_MURDER_FLAG': 'false', 'PERP_AGE_GROUP': '', 'PERP_SEX': '', 'PERP_RACE': '', 'VIC_AGE_GROUP': '25-44', 'VIC_SEX': 'M', 'VIC_RACE': 'BLACK', 'X_COORD_CD': '1020263', 'Y_COORD_CD': '184219', 'Latitude': '40.672250312', 'Longitude': '-73.870176252'}
```

That means we can plot on a map. Let us try to do that.

## Step 2: Export a map to plot the data

We want to plot all the shooting incidents on a map. You can use OpenStreetMap to get an image of a map.

We want a map of New York, which you can find by locating it on OpenStreetMap or pressing the link.

You should press the blue Download in the low right corner of the picture.

Also, remember to get the coordinates of the image in the left side bar, we will need them for the plot.

```map_box = [-74.4461, -73.5123, 40.4166, 41.0359]
```

## Step 3: Writing the Python code that adds data to the map

Importing data from a CVS file is easy and can be done through the standard library csv. Making plot on a graph can be done in matplotlib. If you do not have it installed already, you can do that by typing the following in a command line (or see here).

```pip install matplotlib
```

First you need to transform the CVS data of the longitude and latitude to floats.

```import csv

# The name of the input file might need to be adjusted, or the location needs to be added if it is not located in the same folder as this file.
csv_file = open('nypd-shooting-incident-data-year-to-date-1.csv')
longitude = []
latitude = []
longitude.append(float(row['Longitude']))
latitude.append(float(row['Latitude']))
```

Now you have two lists (longitude and latitude), which contains the coordinates to plot.

Then for the actual plotting into the image.

```import matplotlib.pyplot as plt

# The boundaries of the image map
map_box = [-74.4461, -73.5123, 40.4166, 41.0359]

# The name of the image of the New York map might be different.

fig, ax = plt.subplots()
ax.scatter(longitude, latitude)
ax.set_ylim(map_box, map_box)
ax.set_xlim(map_box, map_box)
ax.imshow(map_img, extent=map_box, alpha=0.9)

plt.show()
```

This will result in the following beautiful map of New York, which highlights where the shooting in the last year has occurred. Shootings in New York in the last year. Plot by Python using matplotlib.

Now that is awesome. If you want to learn more, this and more is covered in my online course. Check it out.

You can also read about how to plot the mood of tweets on a leaflet map.

## The challenge

You have a quote and an image.

```    quote = "Mostly, when you see programmers, they aren’t doing anything.  One of the attractive things about programmers is that you cannot tell whether or not they are working simply by looking at them.  Very often they’re sitting there seemingly drinking coffee and gossiping, or just staring into space.  What the programmer is trying to do is get a handle on all the individual and unrelated ideas that are scampering around in his head."
quote_by = "Charles M. Strauss"

len(quote) = 430
```

The quote is long (430 chars) and the picture might be too bright to write in white text. Also, it will take time to find the right font size.

Can you automate that getting a result that fits the Twitter recommended picture size?

Notice the following.

• The picture is dimmed to make the white text easy readable.
• The font size is automatically adjusted to fit the picture.
• The picture is cropped to fit recommended Twitter size (1024 x 512).

If this is what you are looking for, then this will automate that.

## Step 1: Find your background picture and quote

In this tutorial I will use the background image and quote given above. But you can change that to your needs.

If you chose a shorter quote the font size will adjust accordingly.

Example given here.

Notice that the following.

• The font size of the “quoted by” (here Learn Python With Rune) is adjusted to not fill more that half of the picture width.
• You can modify the margins as you like – say you want more space between the text and the logo.

## Step 2: Install the PILLOW library

The what? Install the pillow library. You can find installation documentation in their docs.

Or just type

```pip install pillow
```

The pillow library is the PIL fork, and PIL is the Python Imaging Library. Basically, you need it for processing images.

You can find various fonts in font.google.com.

For this purpose, I used the Balsamiq Sans font.

I have located the bold and the italic version in a font folder.

## Step 4: The actual Python code

This is the fun part. The actual code.

```from PIL import Image, ImageDraw, ImageFont, ImageEnhance

# picture setup - it is set up for Twitter recommendations
WIDTH = 1024
HEIGHT = 512
# the margin are set by my preferences
MARGIN = 50
MARGIN_TOP = 50
MARGIN_BOTTOM = 150
LOGO_MARGIN = 25

# font variables
FONT_SIZES = [110, 100, 90, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 30, 25, 20]
FONT_QUOTE = 'font-text'
FONT_QUOTED_BY = 'font-quoted-by'
FONT_SIZE = 'font-size'
FONT_QUOTED_BY_SIZE = 'font-quoted-by-size'

# Font colors
WHITE = 'rgb(255, 255, 255)'
GREY = 'rgb(200, 200, 200)'

# output text
OUTPUT_QUOTE = 'quote'
OUTPUT_QUOTED_BY = 'quoted-by'
OUTPUT_LINES = 'lines'

def text_wrap_and_font_size(output, font_style, max_width, max_height):
for font_size in FONT_SIZES:
output[OUTPUT_LINES] = []
font = ImageFont.truetype(font_style[FONT_QUOTE], size=font_size, encoding="unic")
output[OUTPUT_QUOTE] = " ".join(output[OUTPUT_QUOTE].split())
if font.getsize(output[OUTPUT_QUOTE]) <= max_width:
output[OUTPUT_LINES].append(output[OUTPUT_QUOTE])
else:
words = output[OUTPUT_QUOTE].split()
line = ""
for word in words:
if font.getsize(line + " " + word) <= max_width:
line += " " + word
else:
output[OUTPUT_LINES].append(line)
line = word
output[OUTPUT_LINES].append(line)
line_height = font.getsize('lp')

quoted_by_font_size = font_size
quoted_by_font = ImageFont.truetype(font_style[FONT_QUOTED_BY], size=quoted_by_font_size, encoding="unic")
while quoted_by_font.getsize(output[OUTPUT_QUOTED_BY]) > max_width//2:
quoted_by_font_size -= 1
quoted_by_font = ImageFont.truetype(font_style[FONT_QUOTED_BY], size=quoted_by_font_size, encoding="unic")

if line_height*len(output[OUTPUT_LINES]) + quoted_by_font.getsize('lp') < max_height:
font_style[FONT_SIZE] = font_size
font_style[FONT_QUOTED_BY_SIZE] = quoted_by_font_size
return True

# we didn't succeed find a font size that would match within the block of text
return False

def draw_text(image, output, font_style):

draw = ImageDraw.Draw(image)
lines = output[OUTPUT_LINES]
font = ImageFont.truetype(font_style[FONT_QUOTE], size=font_style[FONT_SIZE], encoding="unic")
line_height = font.getsize('lp')

y = MARGIN_TOP
for line in lines:
x = (WIDTH - font.getsize(line)) // 2
draw.text((x, y), line, fill=WHITE, font=font)

y = y + line_height

quoted_by = output[OUTPUT_QUOTED_BY]
quoted_by_font = ImageFont.truetype(font_style[FONT_QUOTED_BY], size=font_style[FONT_QUOTED_BY_SIZE], encoding="unic")
# position the quoted_by in the far right, but within margin
x = WIDTH - quoted_by_font.getsize(quoted_by) - MARGIN
draw.text((x, y), quoted_by, fill=GREY, font=quoted_by_font)
return image

def generate_image_with_quote(input_image, quote, quote_by, font_style, output_image):
image = Image.open(input_image)

# darken the image to make output more visible
enhancer = ImageEnhance.Brightness(image)
image = enhancer.enhance(0.5)

# resize the image to fit Twitter
image = image.resize((WIDTH, HEIGHT))

# set logo on image
logo_im = Image.open("pics/logo.png")
l_width, l_height = logo_im.size
image.paste(logo_im, (WIDTH - l_width - LOGO_MARGIN, HEIGHT - l_height - LOGO_MARGIN), logo_im)

output = {OUTPUT_QUOTE: quote, OUTPUT_QUOTED_BY: quote_by}

# we should check if it returns true, but it is ignorred here
text_wrap_and_font_size(output, font_style, WIDTH - 2*MARGIN, HEIGHT - MARGIN_TOP - MARGIN_BOTTOM)

# now it is time to draw the quote on our image and save it
image = draw_text(image, output, font_style)
image.save(output_image)

def main():
# setup input and output image
input_image = "pics/background.jpg"
output_image = "quote_of_the_day.png"

# setup font type
font_style = {FONT_QUOTE: "font/BalsamiqSans-Bold.ttf", FONT_QUOTED_BY: "font/BalsamiqSans-Italic.ttf"}

quote = "Mostly, when you see programmers, they aren’t doing anything.  One of the attractive things about programmers is that you cannot tell whether or not they are working simply by looking at them.  Very often they’re sitting there seemingly drinking coffee and gossiping, or just staring into space.  What the programmer is trying to do is get a handle on all the individual and unrelated ideas that are scampering around in his head."
# quote = "YOU ARE AWESOME!"
quote_by = "Charles M. Strauss"
# quote_by = "Learn Python With Rune"

# generates the quote image
generate_image_with_quote(input_image, quote, quote_by, font_style, output_image)

if __name__ == "__main__":
main()
```

Notice that you can change the input and output image names and locations in the main() function. Also, there you can setup the font for the quote and the quoted-by.

Finally, and obviously, you can change the quote and quote-by in the main() function.

## Step 1: Install the Pillow library

We need the Pillow library in order to manipulate images. It can be installed by using pip.

```pip install Pillow
```

This enables us to use PIL (Python Imaging Library) library.

You can browse fonts free to download from Google Font Library. In this example I use the Balsamic Sans.

Place the download in the same folder as you want to work from.

## Step 3: Find an awesome picture

I use Pexels to find free awesome pictures to use.

And place that image in your same location.

## Step 4: Create you Python program to add your awesome text

```from PIL import Image, ImageDraw, ImageFont

# Open the image (change name and location if needed)
image = Image.open('pics/background.jpg')
draw = ImageDraw.Draw(image)

# Import the font(change name and location if needed, also change font size if needed)
font = ImageFont.truetype('font/BalsamiqSans-Bold.ttf', size=200)
black = 'rgb(0, 0, 0)'  # black color

# Draw the text - change position if needed
message = "This is great!"
draw.text((1000, 200), message, fill=black, font=font)

# Draw the text - change position if needed
name = 'You are AWESOME!'
draw.text((1100, 500), name, fill=black, font=font)

image.save('greeting_card.png')

```