Generate Choropleth 20210702




Pay Notebook Creator: Roy Hyunjin Han0
Set Container: Numerical CPU with TINY Memory for 10 Minutes 0
Total0

Represent Values using Colors in an Image

Step 1: Create and Upload Your Image

Create an image using GIMP by drawing different colors using the PENCIL.

  • Make sure to use the PENCIL because the PAINTBRUSH will add interpolated colors around the edges where you draw and will complicate color replacement.
  • Use a different color for each part of the image that you want to color-code.
  • Don't forget to IMAGE > ZEALOUS CROP to remove unnecessary whitespace.

{source_image : Source Image ? Image Template to Use}

Step 2: Define Values in a Table

For each part of your image that has a different color, define a value. You can update the table directly or upload a new table.

You may need to reorder the sequence of parts in the table in order to match the color of the part in your uploaded image. Check the color histogram below for the sequence.

{part_table : Part Values ? Values to Represent in the Image using Colors}

Step 3: Choose a Color Map

Then, choose a color map to use for representing the values.

{color_map_options : Color Map ? Color Map to Use for Representing the Values}

Step 4: Choose the Zero Color

Finally, choose a color that will represent the value zero. You can use standard HTML color names or hex codes.

{zero_color_name_text : Zero Color ? Color to Use for Representing the Value Zero}

In [62]:
# CrossCompute
source_image_path = 'body-template-20210702.png'
part_table_path = 'parts.csv'
color_map_options_path = 'colormaps.txt'
zero_color_name_text = 'gainsboro'
target_folder = '/tmp'
In [63]:
from PIL import Image
image = Image.open(source_image_path)
image.mode
Out[63]:
'RGB'
In [64]:
image
Out[64]:
In [65]:
import numpy as np
image_array = np.array(image)
image_array.shape
Out[65]:
(382, 340, 3)

Next, let's reshape image_array into a list of rgb colors. Since image_array has 3 dimensions and we are giving reshape() two arguments, we are telling reshape() to turn a 3 dimensional array into a 2 dimensional array. The -1 in the first argument tells reshape() to determine the size of the first dimension automatically based on the sizes of the other dimensions.

In [66]:
# https://stackoverflow.com/a/48904991
color_array = image_array.reshape(-1, image_array.shape[2])
color_array.shape
Out[66]:
(129880, 3)
In [67]:
# Count the number of unique colors
colors, counts = np.unique(color_array, axis=0, return_counts=True)
colors.shape
Out[67]:
(7, 3)
In [68]:
colors.reshape(-1, *colors.shape).shape
Out[68]:
(1, 7, 3)
In [69]:
%matplotlib inline
import matplotlib.pyplot as plt
plt.imshow(colors.reshape(-1, *colors.shape))
plt.show()

Here is the distribution of colors in our image. As you can see, the white background dominates.

In [89]:
from os.path import join
color_histogram_image_path = join(target_folder, 'color_histogram.png')

figure = plt.figure()
plt.bar(range(len(colors)), counts, color=colors/256, edgecolor='black')
plt.xlabel('Part Index')
plt.ylabel('Pixel Count')
figure.savefig(color_histogram_image_path)

print('color_histogram_image_path = ' + color_histogram_image_path)
color_histogram_image_path = /tmp/color_histogram.png
In [71]:
from pandas import read_csv
part_table = read_csv(part_table_path)
part_table
Out[71]:
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } </style>
name score
0 torso 7
1 head 9
2 right leg 5
3 left leg 3
4 left arm 0
5 right arm 2

Finally, we replace the dummy colors with real colors using a customized color map.

In [72]:
color_map_name = open(color_map_options_path, 'rt').readlines()[0].strip()
color_map_name
Out[72]:
'Blues'
In [73]:
from matplotlib import cm
color_map = cm.get_cmap(color_map_name)
np.array(color_map(0)) * 256
Out[73]:
array([247.96862745, 251.98431373, 256.        , 256.        ])
In [74]:
(np.array(color_map(0)) * 256)[:3]
Out[74]:
array([247.96862745, 251.98431373, 256.        ])
In [75]:
image_array.T.shape
Out[75]:
(3, 340, 382)
In [76]:
red_band, green_band, blue_band = image_array.T
red_band.shape
Out[76]:
(340, 382)
In [77]:
color = colors[0]
red_value, green_value, blue_value = color
color_area = (red_band == red_value) & (green_band == green_value) & (blue_band == blue_value)
color_area.T.shape
Out[77]:
(382, 340)
In [78]:
image_array.shape
Out[78]:
(382, 340, 3)
In [79]:
old_image_array = image_array
new_image_array = old_image_array.copy()
new_image_array[color_area.T] = (np.array(color_map(0.1)) * 256)[:3]
In [80]:
Image.fromarray(old_image_array)
Out[80]:
In [81]:
Image.fromarray(new_image_array)
Out[81]:
In [82]:
from matplotlib.colors import to_rgb
np.array(to_rgb(zero_color_name_text)) * 256
Out[82]:
array([220.8627451, 220.8627451, 220.8627451])
In [83]:
# https://stackoverflow.com/a/3753428/192092
new_image_array = old_image_array.copy()
r_band, g_band, b_band = old_image_array.T
maximum_score = part_table.score.max()

for (part_index, part_row), color in zip(part_table.iterrows(), colors):
    score = part_row.score
    normalized_score = score / maximum_score

    r_value, g_value, b_value = color
    color_area = (r_band == r_value) & (g_band == g_value) & (b_band == b_value)
    
    if normalized_score:
        new_color = (np.array(color_map(normalized_score)) * 256)[:3]
    else:
        new_color = np.array(to_rgb(zero_color_name_text)) * 256
    new_image_array[color_area.T] = new_color

new_image = Image.fromarray(new_image_array)
new_image
Out[83]:
In [84]:
from os.path import join
target_image_path = join(target_folder, 'choropleth.png')
new_image.save(target_image_path)
print('target_image_path = ' + target_image_path)
target_image_path = /tmp/choropleth.png

Generated Choropleth

{color_histogram_image : Color Histogram ? Color Distribution in Your Original Image}

{target_image : Generated Choropleth ? Your Color Coded Image}