Issue
I have following cells:
cells = np.array([[1, 1, 1],
[1, 1, 0],
[1, 0, 0],
[1, 0, 1],
[1, 0, 0],
[1, 1, 1]])
and I want to calculate horizontal and vertical adjacencies to come to this result:
# horizontal adjacency
array([[3, 2, 1],
[2, 1, 0],
[1, 0, 0],
[1, 0, 1],
[1, 0, 0],
[3, 2, 1]])
# vertical adjacency
array([[6, 2, 1],
[5, 1, 0],
[4, 0, 0],
[3, 0, 1],
[2, 0, 0],
[1, 1, 1]])
The actual sollution looks like this:
def get_horizontal_adjacency(cells):
adjacency_horizontal = np.zeros(cells.shape, dtype=int)
for y in range(cells.shape[0]):
span = 0
for x in reversed(range(cells.shape[1])):
if cells[y, x] > 0:
span += 1
else:
span = 0
adjacency_horizontal[y, x] = span
return adjacency_horizontal
def get_vertical_adjacency(cells):
adjacency_vertical = np.zeros(cells.shape, dtype=int)
for x in range(cells.shape[1]):
span = 0
for y in reversed(range(cells.shape[0])):
if cells[y, x] > 0:
span += 1
else:
span = 0
adjacency_vertical[y, x] = span
return adjacency_vertical
The Algorithm is basically (for horizontal adjacency):
- loop throgh rows
- loop backward throgh columns
- if the x, y value of cells is not zero, add 1 to the actual span
- if the x, y value of cells is zero, reset actual span to zero
- set the span as new x, y value of the resulting array
Since I need to loop two times over all array elements this is slow for bigger arrays (e.g. images).
Is there a way to improve the algorithm using vectorization or some other numpy magic?
Summary:
Great suggestions have been made by joni and Mark Setchell!
I created a small Repo with a sample image and a python file with the comparisons. The results are astonishing:
- Original Approach: 3.675 s
- Using Numba: 0.002 s
- Using Cython: 0.005 s
Solution
I had a really quick attempt at this with Numba but have not checked it too thoroughly though the results seem about right:
#!/usr/bin/env python3
# https://stackoverflow.com/q/69854335/2836621
# magick -size 1920x1080 xc:black -fill white -draw "circle 960,540 960,1040" -fill black -draw "circle 960,540 960,800" a.png
import cv2
import numpy as np
import numba as nb
def get_horizontal_adjacency(cells):
adjacency_horizontal = np.zeros(cells.shape, dtype=int)
for y in range(cells.shape[0]):
span = 0
for x in reversed(range(cells.shape[1])):
if cells[y, x] > 0:
span += 1
else:
span = 0
adjacency_horizontal[y, x] = span
return adjacency_horizontal
@nb.jit('void(uint8[:,::1], int32[:,::1])',parallel=True)
def nb_get_horizontal_adjacency(cells, result):
for y in nb.prange(cells.shape[0]):
span = 0
for x in range(cells.shape[1]-1,-1,-1):
if cells[y, x] > 0:
span += 1
else:
span = 0
result[y, x] = span
return
# Load image
im = cv2.imread('a.png', cv2.IMREAD_GRAYSCALE)
%timeit get_horizontal_adjacency(im)
result = np.zeros((im.shape[0],im.shape[1]),dtype=np.int32)
%timeit nb_get_horizontal_adjacency(im, result)
The timings are good, showing 4000x speed-up, if it works correctly:
In [15]: %timeit nb_get_horizontal_adjacency(im, result)
695 µs ± 9.12 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [17]: %timeit get_horizontal_adjacency(im)
2.78 s ± 44.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Input
Input image was created in 1080p dimensions, i.e. 1920x1080, with ImageMagick using:
magick -size 1920x1080 xc:black -fill white -draw "circle 960,540 960,1040" -fill black -draw "circle 960,540 960,800" a.png
Output (contrast adjusted)
Answered By - Mark Setchell
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.