patternpythonMinor
Generating matrix/image kernel from List
Viewed 0 times
imagelistgeneratingkernelfrommatrix
Problem
I have another question from same program where is full code, but this question focus only single process/function of that program.
My problem is performance when creating dynamic Image kernel from list of pixels.
List contains multiple single 4 byte Integer which is grayscale of pixel.
I have tried 3 different approach, still not happy with the time what it takes.
I think my basic idea about generating matrices is wrong.
Approach 1
Function execute time 0.2249772548675537 seconds
With this project I can live with that time, but kernel generation is ugly and "hardcoded" so I cant change size of it.
Approach 2
```
def getKernels(self, pixels, width):
kernels = []
pixels = [x for sublist in pixels for x in sublist] #2D list to 1D
for i in range(0,len(pixels)):
tx = (i % width) #Calc pixel x-cordinate
ty = math.floor(i / width) #Calc y-cordinate
kernel = []
for y, x in self.kernelGenerator(ty,tx,3): #Pass current pixel index cordinates and size of kernel
try:
index = (x)+((y)*width) #Calc index
if index > -1 and index < len(pixels): #Check that index is in list bounds
kernel.append(pixels[in
My problem is performance when creating dynamic Image kernel from list of pixels.
List contains multiple single 4 byte Integer which is grayscale of pixel.
I have tried 3 different approach, still not happy with the time what it takes.
I think my basic idea about generating matrices is wrong.
Approach 1
def getKernels(self, pixels, width):
kernels = []
pixels = [x for sublist in pixels for x in sublist] #2D list to 1D
for i in range(0,len(pixels)):
x = (i % width) #Calc pixel x-cordinate
y = math.floor(i / width) #Calc y-cordinate
try:
kernel = [pixels[(x-1)+((y-1)*width)],pixels[(x)+((y-1)*width)],pixels[(x+1)+((y-1)*width)],pixels[(x-1)+(y*width)],pixels[(x)+(y*width)],pixels[(x+1)+(y*width)],pixels[(x-1)+((y+1)*width)],pixels[(x)+((y+1)*width)],pixels[(x+1)+((y+1)*width)]]
except Exception as e:
kernel = [0] #Cannot create fullsize kernel so just make it black
kernels.append(kernel) #Add kernel to kernels list
return kernelsFunction execute time 0.2249772548675537 seconds
With this project I can live with that time, but kernel generation is ugly and "hardcoded" so I cant change size of it.
Approach 2
```
def getKernels(self, pixels, width):
kernels = []
pixels = [x for sublist in pixels for x in sublist] #2D list to 1D
for i in range(0,len(pixels)):
tx = (i % width) #Calc pixel x-cordinate
ty = math.floor(i / width) #Calc y-cordinate
kernel = []
for y, x in self.kernelGenerator(ty,tx,3): #Pass current pixel index cordinates and size of kernel
try:
index = (x)+((y)*width) #Calc index
if index > -1 and index < len(pixels): #Check that index is in list bounds
kernel.append(pixels[in
Solution
Style
In fact, only changing your first snippet to using
Algorithm
There is one thing that bothers you about this snippet: it is not generic enough to allow for kernels of various radius. There is an other thing that bothers me, it is how it warps around the edges of the image but the bottom. The fact that
Looking at how you handle your kernels in your other question, we can see that kernels that are not full-sized are discarded. Great, let's generate kernels of lower size for the edges of the image. The key idea here is knowing that slicing a list will always return a list:
Also, slicing a list of lists will not copy inner list but only references to them, so it is rather fast:
This version is 5 to 10% slower than your first one but has two advantages:
I chose to parametrize it with the kernel radius rather than its diameter to avoid the parity check and simplify the computations.
I also chose to keep the
at the beginning of the function.
Going further
Your use case of such function seems only to be able to iterate over the returned value. In such case, you would gain more performances by turning this into a generator instead of building a list element by element.
The code is nearly identical, just remove the
Oh yes, you might have noticed that I removed the
- put spaces after comas to improve readability;
- remove unnecessary parenthesis;
- put spaces around operators;
- put two spaces before and one space after the comment sign (
#);
- remove the
0inrange(0, )as it is the default value;
except Exception as eis bad here as you are only actually expectingIndexError; plus theas epart is unnecessary as you don't usee(well, you do in the second snippet, but you could justraisewith nothing more, even though the wholeexcept: raiseis useless as it adds nothing over the default behaviour);
range(len())is often a red flag as it is better to iterate over elements than indices; but here, I don't think you have a choice anyway;
divmodcan help you get rid ofmath.ceil.
In fact, only changing your first snippet to using
divmod yield a 5% speedup on my machine:def get_kernel(pixels, width):
kernels = []
pixels = [x for row in pixels for x in row] # 2D list to 1D
for i in range(len(pixels)):
y, x = divmod(i, width)
try:
kernel = [
pixels[(y-1) * width + x - 1], pixels[(y-1) * width + x], pixels[(y-1) * width + x + 1],
pixels[y * width + x - 1], pixels[y * width + x], pixels[y * width + x + 1],
pixels[(y+1) * width + x - 1], pixels[(y+1) * width + x], pixels[(y+1) * width + x + 1],
]
except IndexError:
kernel = [0]
kernels.append(kernel) # Add kernel to kernels list
return kernelsAlgorithm
There is one thing that bothers you about this snippet: it is not generic enough to allow for kernels of various radius. There is an other thing that bothers me, it is how it warps around the edges of the image but the bottom. The fact that
some_list[-x] will return the \$x^{th}\$ element of some_list starting from the end combined to the flattening of the list will make it so that when at the top row or when reaching a pixel at the right or the left of the image, the coordinates involved will always be a valid index in pixels. But for the last row, (y+1) * width + x will exceed the number of elements in pixels and raise IndexError. This inconsistent behaviour bothers me.Looking at how you handle your kernels in your other question, we can see that kernels that are not full-sized are discarded. Great, let's generate kernels of lower size for the edges of the image. The key idea here is knowing that slicing a list will always return a list:
>>> a = list(range(20))
>>> a[4:8]
[4, 5, 6, 7]
>>> a[18:50]
[18, 19]
>>> a[-8:5]
[]Also, slicing a list of lists will not copy inner list but only references to them, so it is rather fast:
def get_kernels(pixels, width, radius=1):
kernels = []
for y in range(len(pixels)):
y_min = y - radius
y_max = y + radius + 1
for x in range(width):
x_min = x - radius
x_max = x + radius + 1
kernels.append([
pixel for row in pixels[y_min:y_max]
for pixel in row[x_min:x_max]])
return kernelsThis version is 5 to 10% slower than your first one but has two advantages:
- It is more readable;
- It is more generic.
I chose to parametrize it with the kernel radius rather than its diameter to avoid the parity check and simplify the computations.
I also chose to keep the
width parameter since you seemed to already have it handy. But if you want to remove it, you can still add:def get_kernels(pixels, radius=1):
if not pixels:
return []
width = len(pixels[0])at the beginning of the function.
Going further
Your use case of such function seems only to be able to iterate over the returned value. In such case, you would gain more performances by turning this into a generator instead of building a list element by element.
The code is nearly identical, just remove the
kernels list and use the yield keyword instead of the append function. And voilà. What it does is that it will compute the kernels on demand, during the iteration; removing the need for extra storage (and handling of that storage):def get_kernels(pixels, radius=1):
if not pixels:
return
width = len(pixels[0])
for y in range(len(pixels)):
y_min = y - radius
y_max = y + radius + 1
for x in range(width):
x_min = x - radius
x_max = x + radius + 1
yield [
pixel for row in pixels[y_min:y_max]
for pixel in row[x_min:x_max]]Oh yes, you might have noticed that I removed the
self parameter as it is unused. This might interfere with the fact that you are using this as a method on your class. But I probably should explain what is wrong with that in that other question of yours.Code Snippets
def get_kernel(pixels, width):
kernels = []
pixels = [x for row in pixels for x in row] # 2D list to 1D
for i in range(len(pixels)):
y, x = divmod(i, width)
try:
kernel = [
pixels[(y-1) * width + x - 1], pixels[(y-1) * width + x], pixels[(y-1) * width + x + 1],
pixels[y * width + x - 1], pixels[y * width + x], pixels[y * width + x + 1],
pixels[(y+1) * width + x - 1], pixels[(y+1) * width + x], pixels[(y+1) * width + x + 1],
]
except IndexError:
kernel = [0]
kernels.append(kernel) # Add kernel to kernels list
return kernels>>> a = list(range(20))
>>> a[4:8]
[4, 5, 6, 7]
>>> a[18:50]
[18, 19]
>>> a[-8:5]
[]def get_kernels(pixels, width, radius=1):
kernels = []
for y in range(len(pixels)):
y_min = y - radius
y_max = y + radius + 1
for x in range(width):
x_min = x - radius
x_max = x + radius + 1
kernels.append([
pixel for row in pixels[y_min:y_max]
for pixel in row[x_min:x_max]])
return kernelsdef get_kernels(pixels, radius=1):
if not pixels:
return []
width = len(pixels[0])def get_kernels(pixels, radius=1):
if not pixels:
return
width = len(pixels[0])
for y in range(len(pixels)):
y_min = y - radius
y_max = y + radius + 1
for x in range(width):
x_min = x - radius
x_max = x + radius + 1
yield [
pixel for row in pixels[y_min:y_max]
for pixel in row[x_min:x_max]]Context
StackExchange Code Review Q#147959, answer score: 3
Revisions (0)
No revisions yet.