2

I am trying to reduce the color of a ppm image file from 255 colors to 5 colors(red, blue, green, black and white). When I initiate my test file, the pixel_list generated is only reds, which is not correct. I'm using the Euclidean distance formula to find which of the 5 colors the pixel is closest too and changing the values to that color. (Color Reduction)

Here is an example of my data: (The first three rows are file type, dimensions and total colors. The following rows are pixel data ordered in r, g, b.)

P3
200 200
255
0
48
255
216
52
180
252
255
176
212
96
4
0
108
20
40
64
80
140
0
80

My code:

import math

with open('test_pattern.ppm','r') as f:
    output = f.read().split("\n")

i = 0
r_point = 3 + i
g_point = 4 + i
b_point = 5 + i
pixel_list = []

resolution = []
resolution.append(output[1].split(" "))
file_size = resolution[0]
file_size = int(file_size[0]) * int(file_size[1])
file_size = int(file_size*3)
print(file_size)

while file_size >= i:
    r = int(output[r_point])
    g = int(output[g_point])
    b = int(output[b_point])
    if all(math.sqrt((r-255)**2 + (g - 0)**2 + (b-0)**2) < x for x in [math.sqrt((r-0)**2 + (g - 255)**2 + (b-0)**2) , math.sqrt((r-0)**2 + (g - 0)**2 + (b-255)**2) , math.sqrt((r-0)**2 + (g - 0)**2 + (b-0)**2) , math.sqrt((r-255)**2 + (g - 255)**2 + (b-255)**2)]):
        r = 255
        g = 0
        b = 0
        pixel_list.append(r)
        pixel_list.append(g)
        pixel_list.append(b)
        i += 3


    elif all(math.sqrt((r-0)**2 + (g - 255)**2 + (b-0)**2) < x for x in [math.sqrt((r-0)**2 + (g - 0)**2 + (b-255)**2) , math.sqrt((r-255)**2 + (g - 0)**2 + (b-0)**2) , math.sqrt((r-0)**2 + (g - 0)**2 + (b-0)**2) , math.sqrt((r-255)**2 + (g - 255)**2 + (b-255)**2)]):
        r = 0
        g = 255
        b = 0
        pixel_list.append(r)
        pixel_list.append(g)
        pixel_list.append(b)
        i +=3


    elif all(math.sqrt((r-0)**2 + (g - 0)**2 + (b-255)**2) < x for x in [math.sqrt((r-0)**2 + (g - 255)**2 + (b-0)**2) , math.sqrt((r-255)**2 + (g - 0)**2 + (b-0)**2) , math.sqrt((r-0)**2 + (g - 0)**2 + (b-0)**2) , math.sqrt((r-255)**2 + (g - 255)**2 + (b-255)**2)]):
        r = 0
        g = 0
        b = 255
        pixel_list.append(r)
        pixel_list.append(g)
        pixel_list.append(b)
        i += 3


    elif all(math.sqrt((r-0)**2 + (g - 0)**2 + (b-0)**2) < x for x in [math.sqrt((r-0)**2 + (g - 255)**2 + (b-0)**2) , math.sqrt((r-255)**2 + (g - 0)**2 + (b-0)**2) , math.sqrt((r-0)**2 + (g - 0)**2 + (b-255)**2) , math.sqrt((r-255)**2 + (g - 255)**2 + (b-255)**2)]):
        r = 0
        g = 0
        b = 0
        pixel_list.append(r)
        pixel_list.append(g)
        pixel_list.append(b)
        i += 3


    elif all(math.sqrt((r-255)**2 + (g - 255)**2 + (b-255)**2) < x for x in [math.sqrt((r-0)**2 + (g - 255)**2 + (b-0)**2) , math.sqrt((r-255)**2 + (g - 0)**2 + (b-0)**2) , math.sqrt((r-0)**2 + (g - 0)**2 + (b-0)**2) , math.sqrt((r-0)**2 + (g - 0)**2 + (b-255)**2)]):
        r = 255
        g = 255
        b = 255
        pixel_list.append(r)
        pixel_list.append(g)
        pixel_list.append(b)
        i += 3


print(pixel_list)

Second Try:

import math

with open('test_pattern.ppm','r') as f:
    output = f.read().split("\n")
i = 0
r_point = 3 + i
g_point = 4 + i
b_point = 5 + i
pixel_list = []
resolution = []
resolution.append(output[1].split(" "))
file_size = resolution[0]
file_size = int(file_size[0]) * int(file_size[1])
file_size = int(file_size*3)
print(file_size)


while file_size >= i:
    r = int(output[r_point])
    g = int(output[g_point])
    b = int(output[b_point])
    a = math.sqrt((r-255)**2 + (g - 0)**2 + (b-0)**2)
    b = math.sqrt((r-0)**2 + (g - 255)**2 + (b-0)**2)
    c = math.sqrt((r-0)**2 + (g - 0)**2 + (b-255)**2)
    d = math.sqrt((r-0)**2 + (g - 0)**2 + (b-0)**2)
    e = math.sqrt((r-255)**2 + (g - 255)**2 + (b-255)**2)

    L = [a, b, c, d, e]
    idx = min(range(len(L)), key=L.__getitem__)

    if idx == 0:
        # red
        r = 255
        g = 0
        b = 0
        pixel_list.append(r)
        pixel_list.append(g)
        pixel_list.append(b)
        i += 3

    if idx == 1:
        # green
        r = 0
        g = 255
        b = 0
        pixel_list.append(r)
        pixel_list.append(g)
        pixel_list.append(b)
        i += 3


    if idx == 2:
        # blue
        r = 0
        g = 0
        b = 255
        pixel_list.append(r)
        pixel_list.append(g)
        pixel_list.append(b)
        i += 3


    if idx == 3:
        # white
        r = 0
        g = 0
        b = 0
        pixel_list.append(r)
        pixel_list.append(g)
        pixel_list.append(b)
        i += 3


    if idx == 4:
        # black
        r = 255
        g = 255
        b = 255
        pixel_list.append(r)
        pixel_list.append(g)
        pixel_list.append(b)
        i += 3


print(pixel_list)
Wakedude
  • 53
  • 2
  • 11
  • If you move `r_point = 3 + i` `g_point = 4 + i` `b_point = 5 + i` inside the while loop, the program will work, but you should continue to refactor it, as it can still be made much easier to read – John La Rooy Nov 16 '15 at 03:34

5 Answers5

3

your first if statement is equivalent to:

math.sqrt((r-255)**2 + (g - 0)**2 + (b-0)**2) < math.sqrt((r-255)**2 + (g - 255)**2 + (b-255)**2): #your last boolean in your brackets

This is because of how python evaluates booleans:

>>> 4 and 5
5

I assume that you wanted to assure the the first statement is less than all of the expressions inside of the right hand of the < operator. you should do something like:

if all(firstexpression < x for x in [all your expressions in here]):

this makes use of pythons all function which takes a list and returns whether or not every element in it would assert to True.

R Nar
  • 5,465
  • 1
  • 16
  • 32
  • Does this still include the ands between the expressions or should they be separated by commas? – Wakedude Nov 16 '15 at 01:12
  • change them to commas because you want them to be seperate items in a list. your use of `and` was, fundamentally, not right – R Nar Nov 16 '15 at 01:13
  • I went ahead and made the changes that you recommended but I'm still getting all red. – Wakedude Nov 16 '15 at 01:33
  • i was assuming that your math was right, it may not be. what is the intended equation? – R Nar Nov 16 '15 at 01:37
  • It is the Euclidean distance formula: sqrt((x2 - x1)^2 + (y2-y1)^2 + (z2 - z1)^2) – Wakedude Nov 16 '15 at 01:45
  • I have posted an update of the code from your suggestions. Any thoughts? I'm thinking it could be in the loop somewhere that is not allowing the r,g,b to change, but I'm not finding it. – Wakedude Nov 16 '15 at 02:14
2

What you seem to be trying to say here

a < b and c and d and e

is

a < b and a < c and a < d and a < e

But it's pretty inefficient to compute and compare those values over and over anyway, when all you are really interested in is the minimum.

so consider:

a = math.sqrt((r-255)**2 + (g - 0)**2 + (b-0)**2)
b = math.sqrt((r-0)**2 + (g - 255)**2 + (b-0)**2)
c = math.sqrt((r-0)**2 + (g - 0)**2 + (b-255)**2)
d = math.sqrt((r-0)**2 + (g - 0)**2 + (b-0)**2)
e = math.sqrt((r-255)**2 + (g - 255)**2 + (b-255)**2)

and

L = [a, b, c, d, e]
idx = min(range(len(L)), key=L.__getitem__)

Now idx is the index of the smallest item in L, so you can use that index to select the colour

if idx == 0:
    # red
if idx == 1:
    # green
if idx == 2:
    # blue
if idx == 3:
    # white
if idx == 4:
    # black

Aside: It's a good optimisation not to bother with the sqrt as it doesn't affect the ordering.

Here is an example run using the first pixel of you question (r=0, g=48, b=255)

>>> import math
>>> r = 0
>>> g = 48
>>> b = 255
>>> a = math.sqrt((r-255)**2 + (g - 0)**2 + (b-0)**2)
>>> b = math.sqrt((r-0)**2 + (g - 255)**2 + (b-0)**2)
>>> c = math.sqrt((r-0)**2 + (g - 0)**2 + (b-255)**2)
>>> d = math.sqrt((r-0)**2 + (g - 0)**2 + (b-0)**2)
>>> e = math.sqrt((r-255)**2 + (g - 255)**2 + (b-255)**2)
>>> L = [a, b, c, d, e]
>>> idx = min(range(len(L)), key=L.__getitem__)
>>> idx
2
John La Rooy
  • 295,403
  • 53
  • 369
  • 502
  • Ok I am trying your suggestion as well. I have posted my code using your method at the bottom of the question, but I am still getting only red numbers added to the pixel list. – Wakedude Nov 16 '15 at 01:59
  • @Wakedude, try printing `r`, `g`, and `b` as well as `idx`. It's easy enough to run them through by hand to see that the output pixels won't all be "red" – John La Rooy Nov 16 '15 at 03:19
  • @Wakedude, you're not actually moving `r_point`, `g_point`, `b_point` through the file. They are always they same pixel! – John La Rooy Nov 16 '15 at 03:24
1

You're using and wrong. It's combining floats with and, which ends up either 0.0 or the last one evaluated. You're also computing and recomputing the same values more than you need to. You're also computing square roots, which is expensive, when you don't need to.

Compute the squares of the five distances, once, then see which one is the least.

Tom Zych
  • 13,329
  • 9
  • 36
  • 53
0

I'll just put this out there as supplementary info only, because it uses NumPy:

Screen shot reduced colors

import numpy as np
import matplotlib.pyplot as plt

a = np.random.random((50, 50, 3))
do = np.ones_like(a, dtype=bool)
do[0], do[-1], do[:,0], do[:,-1] = False, False, False, False

a2 = 0.25*(np.roll(a, 1, axis=0) + np.roll(a, -1, axis=0) +
           np.roll(a, 1, axis=1) + np.roll(a, -1, axis=1)) #smooth a little
b = a.copy()
b[do] = a2[do]

a, b = a[1:-1,1:-1], b[1:-1,1:-1] # trim the edge

c = (b - b.min()) / (b.max() - b.min())  # rescale a bit

colors = np.array([[1,0,0], [0,1,0], [0,0,1], [1,1,1], [0,0,0]], dtype=float)

dsqa = ((a[...,None,:]-colors[None,None,...])**2).sum(axis=-1)
dsqb = ((b[...,None,:]-colors[None,None,...])**2).sum(axis=-1)
dsqc = ((c[...,None,:]-colors[None,None,...])**2).sum(axis=-1)

i_closesta = np.argmin(dsqa, axis=-1)
i_closestb = np.argmin(dsqb, axis=-1)
i_closestc = np.argmin(dsqc, axis=-1)

a_reduced = colors[i_closesta]
b_reduced = colors[i_closestb]
c_reduced = colors[i_closestc]

fs = 18
plt.figure()
plt.subplot(2,3,1)
plt.imshow(a, interpolation="nearest")
plt.title("raw noise", fontsize=fs)
plt.subplot(2,3,4)
plt.imshow(a_reduced, interpolation="nearest")
plt.title("raw noise reduced", fontsize=fs)
plt.subplot(2,3,2)
plt.imshow(b, interpolation="nearest")
plt.title("smoothed noise", fontsize=fs)
plt.subplot(2,3,5)
plt.imshow(b_reduced, interpolation="nearest")
plt.title("smooth noise reduced", fontsize=fs)
plt.subplot(2,3,3)
plt.imshow(c, interpolation="nearest")
plt.title("smoothed, rescaled noise", fontsize=fs)
plt.subplot(2,3,6)
plt.imshow(c_reduced, interpolation="nearest")
plt.title("smoothed, rescaled noise reduced", fontsize=fs)
plt.show()
uhoh
  • 3,713
  • 6
  • 42
  • 95
0

You seem to have a couple of misconceptions about things. One is that if expression0 < (expression1, expression2, etc) will compare the first value to each of the others. It's necessary to explicitly compare the first to each one: if expression0 < expression1 and expression0 < expression2 and ....

The other is that if you have:

i = 0
r_point = 3 + i
g_point = 4 + i
b_point = 5 + i

and change set i += 3 that r_point and the other values will automatically change. Again you have to do it explicitly.

Here's a modified version of you code that does what you want. It also makes use of a couple of functions to simplify the code.

from math import fsum, sqrt

def dist(c1, c2):
    " Euclidean distance between two colors. "
    diffs = (b-a for a, b in zip(c1, c2))
#    return sqrt(fsum(diff*diff for diff in diffs))
    return fsum(diff*diff for diff in diffs)  # distance squared will work

def clostest_color(color, base_colors):
    " Return the color in 'base_colors' that is closest to 'color'. "
    dists = [dist(color, base_color) for base_color in base_colors]
    min_dist = min(dists)
    return base_colors[dists.index(min_dist)]

BASE_COLORS = (255,0,0), (0,255,0), (0,0,255), (0,0,0), (255,255,255)

with open('test_pattern.ppm', 'r') as f:
    output = f.read().splitlines()

width, height = (int(v) for v in output[1].split(' '))
num_pixels = width * height
print('num_pixels: {}'.format(num_pixels))

pixel_list = []
pixel_count = 0
OFFSET = 3
r_point = pixel_count*3 + OFFSET
g_point = r_point + 1
b_point = r_point + 2
while pixel_count < num_pixels:
    r, g, b = int(output[r_point]), int(output[g_point]), int(output[b_point])
    r, g, b = clostest_color((r, g, b), BASE_COLORS)
    pixel_list.extend((r, g, b))
    pixel_count += 1
    r_point, g_point, b_point = r_point+3, g_point+3, b_point+3
martineau
  • 119,623
  • 25
  • 170
  • 301