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))
Pylyp
  • 404
  • 1
  • 7
  • 15
  • 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? – Daniel Mar 15 '14 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 – Pylyp Mar 15 '14 at 17:35

2 Answers2

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])
AdrienG
  • 180
  • 8
  • How to get the same 3D array after `apply_along_axis`? I got `KeyError: ((1, 1, 3), ' – Pylyp Mar 15 '14 at 13:25
  • actually, this is your function that returns a 2D from 1D array, so the final array will be 3D – AdrienG Mar 15 '14 at 13:27
  • @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. – SlightlyCuban Mar 15 '14 at 14:19
  • @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 Mar 15 '14 at 14:24
  • @SlightlyCuban It was a problem with PIL/Pillow `im = Image.fromarray(np.uint8(array))` works correctly – Pylyp Mar 17 '14 at 12:19
  • @AdrienG please look at UPD1 – Pylyp Mar 17 '14 at 14:13
2
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)
dnogin
  • 21
  • 1
  • Why reshaping is better then i , j for loop for all entries in the matrix? whats the deal here? Thanks! – LidorA Dec 16 '17 at 17:00
  • Loops much more slower. – dnogin Dec 17 '17 at 21:50
  • 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. – LidorA Dec 18 '17 at 22:19