Plotting RGB colors using ratios

Viewed 135

I'm currently working on a project that requires plotting RGB colors as a figure. The colors are stored in a nx3 array where n is the number of colors and 3 is the RGB values.

For example:

colors = [[0.70248465, 0.10704011, 0.00900125], [0.0067905 , 0.27228963, 0.6428365 ], [0.00859376, 0.36948201, 0.18334154], [0.85125032, 0.65469019, 0.04035713]

Let's also say that I have an array that stores size ratios in respect of the first color.

For example:

ratios = [1,1/2,1/3,1/5]

What I want to do is to plot these colors but keeping the size ratios. My current function is:

def plot(colors):

    plt.imshow([colors])
    plt.axis('off')

    plt.show()

This plots the colors like this:

imshow

whereas I would like something like this:

enter image description here

2 Answers

This should do the trick:

EXPAND_BY = 30

expanded_colors = []
for c, r in zip(colors, ratios):
    expanded_colors += [c] * int(EXPAND_BY * r)    
colors = expanded_colors

Be careful in choosing a value for EXPAND_BY. The greatest common denominator of the ratios is correct, but any large-ish value should give visually convincing results.

How does it work?

Imshow assumes each RGB triple it gets is one "pixel". This solution simply repeats each entry (pixel) according to its size ratio.

Probably not very memory efficient for larger data, but very code efficient - only a handful of additional lines :)

The following approach uses a ListedColormap. The ratios are multiplied by some sufficiently large factor, rounded and used as a repetition factor.

import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
import numpy as np

colors = [[0.70248465, 0.10704011, 0.00900125], [0.0067905, 0.27228963, 0.6428365], [0.00859376, 0.36948201, 0.18334154], [0.85125032, 0.65469019, 0.04035713]]

ratios = [1, 1/2, 1/3, 1/5]
ratios_int = (np.array(ratios) * 60).round().astype(int)

plt.imshow(np.repeat(np.arange(len(colors)), ratios_int).reshape(1, -1),
           cmap=ListedColormap(colors), # the given list of colors
           aspect=ratios_int.sum()/10)  # 1:10 aspect ratio for the image
plt.axis('off')
plt.show()

colors via ListedColormap

ratio_int is an array with only integers, but which have similar ratios as the given fractional ratios. If all the denominators are integers, one could multiply by the smallest common multiple of the denominators. In the example, 60 is used as a multiplier. In general, with fractions that wouldn't be expressible as ratios between small integers, a sufficiently large multiplier would do the job. Even if this would generate a small difference between the exact ratios, such small difference wouldn't be notable in the resulting image.

np.arange(len(colors)) gives an array 0,1,2,3,.... Matplotlib's color mapping will map these to the corresponding color in the ListedColormap. np.repeat([0,1,2,3], [60,30,20,12]) will repeat 0 for 60 times, 1 for 30, etc. So, this will create an array with each number repeated corresponding to the given ratios. .reshape(1,-1) converts this 1D array to a 2D array suitable for imshow. The first dimension is 1, and the second equal to the length of the 1D array (-1 means "take as much as needed such that the initial array and the reshaped array have the same number of elements").

Related