3

I have a numpy 3D array from Image(PIL/Pillow) object.

 [[178 214 235]
  [180 215 236]
  [180 215 235]
  ..., 
  [146 173 194]
  [145 172 193]
  [146 173 194]]
 ..., 
 [[126 171 203]
  [125 169 203]
  [128 171 205]
  ..., 
  [157 171 182]
  [144 167 182]
  [131 160 180]]]

Image size about 500x500 px. I need to apply two functions for each pixel.

  1. Convert RGB to LAB (using functions from python-colormath) This function takes 1D array like [157, 171, 182] and return 1D array with LAB color, e.g. [53.798345635, -10.358443685, 100.358443685].
  2. Find nearest color from custom palette using scipy.spatial.cKDTree.

Custom palette is kd-tree.

palette = [[0,0,0], [127,127,127], [255,255,255]] #  or [[0.,0.,0.], [50.,0.,0.], [100.,0.,0.]] for LAB color
tree = scipy.spatial.cKDTree(palette)
def find nearest(pixel):
    distance, result = tree.query(pixel)
    new_pixel = palette[result]
    return new_pixel

Is there a faster solution than iterating with Python? E.g.

for row in array:
    for pixel in row:
        apply_fuction1(pixel) # where pixel is one dimensional array like [157 171 182]
        apply_fuction2(pixel)

UPD1 I dont know what I am doing wrong, but:

python3 -mtimeit -s'import test' 'test.find_nearest()' # my variant with 2 loops and Image.putdata()
10 loops, best of 3: 3.35 sec per loop
python3 -mtimeit -s'import test' 'test.find_nearest_with_map()' # list comprehension with map and Image.fromarray() by traceur
10 loops, best of 3: 3.67 sec per loop
python3 -mtimeit -s'import test' 'test.along_axis()' # np.apply_along_axis() and Image.fromarray() by AdrienG
10 loops, best of 3: 5.25 sec per loop

def find_nearest(array=test_array):
    new_image = []
    for row in array:
        for pixel in row:
            distance, result = tree.query(pixel)
            new_pixel = palette[result]
            new_image.append(new_pixel)
    im = Image.new('RGB', (300, 200))
    im.putdata(new_image)


def _find_nearest(pixel):
    distance, result = tree.query(pixel)
    new_pixel = palette[result]
    return new_pixel


def along_axis(array=test_array):
    array = np.apply_along_axis(_find_nearest, 2, array)
    im = Image.fromarray(np.uint8(array))


def find_nearest_with_map(array=test_array):
    array = [list(map(_find_nearest, row)) for row in array]
    im = Image.fromarray(np.uint8(array))
2
  • Can you explain what you need to do in a bit more detail? EG Does #1 want a 1D or 2D array, how are you indexing the pixel array, and what does the "custom palette" look like? Commented Mar 15, 2014 at 13:54
  • @Ophion All my functions take one pixel color as argument (1D array, e.g. [157, 171, 182]) and return 1D array. First function returns LAB color, e.g. [53.798345635, -10.358443685, 100.358443685], second function I will explain more detail in question in 5 min Commented Mar 15, 2014 at 17:35

2 Answers 2

8

sorry for the previous answer,

use numpy.apply_along_axis

a = np.arange(12).reshape((4,3))
def sum(array):
    return np.sum(array)

np.apply_along_axis(sum, 1, a)
>>> array([ 3, 12, 21, 30])
Sign up to request clarification or add additional context in comments.

6 Comments

How to get the same 3D array after apply_along_axis? I got KeyError: ((1, 1, 3), '<i4') after Image.fromarray(numpy.apply_along_axis(function, 2, pixels))
actually, this is your function that returns a 2D from 1D array, so the final array will be 3D
@Pylyp, apply_along_axis is going to replace each 1D array it acts on with whatever function returns; make sure your function returns a 1D array.
@Pylyp also, apply_along_axis is going to return a new array. If you need the original, make sure to keep a reference to it. If you're making many, many images at once, keep an eye on memory usage.
@SlightlyCuban It was a problem with PIL/Pillow im = Image.fromarray(np.uint8(array)) works correctly
|
3
import numpy as np

# Example of an image. 2x2x3
a = np.array([ [ [1,2,3], [4,5,6] ], 
              [ [7,8,9], [10,11,12] ] ])

# Our function. This swap first and last items of 3-item array
def rgb_to_bgr (pixel):                        
    pixel[0], pixel[2] = pixel[2], pixel[0] 
    return pixel

x,y,z = a.shape[0], a.shape[1], a.shape[2]

a = a.reshape(x*y,z)
a = np.apply_along_axis(rgb_to_bgr, 1, a)
a = a.reshape(x,y,z)

print(a)

3 Comments

Why reshaping is better then i , j for loop for all entries in the matrix? whats the deal here? Thanks!
Loops much more slower.
Is it because numpy has parallel optimization ? for example with 4 cores we could assign 4 parts of the array to each core and work on them in parallel.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.