Compare a list of lists and display result using OpenCV - python-3.x

I have two list of lists that I want to compare element by element first.
a=[[0,0,0,0,3],[1,2]] # This is the key (list of list)
b=[[0,0,0,2,3],[1,0]] # This is the list of list I want to compare against the key list
I also have an image that has 5 rows (first rectangle) (because a[0] has 5 elements) and 2 rows (second rectangle) (because a[1] has 2 elements)
For the following image, I know the corner points and the height of each individual rows. These are stored in another list. xyh=[[115,60,70],[350,55,70]]
What I would like to do is compare my list b against a and if they match eg a[0][0]=b[0][0] then the text "match" will be displayed in the first first column's first row, if they don't match then the element from the list a will be displayed.
This is the expected result at the end.
My MWE:
import cv2
import numpy as np
total_elements=7
a=[[0,0,0,0,3],[1,2]] # This is the key (list of list)
b=[[0,0,0,2,3],[1,0]] # This is the list of list I want to compare against the key list
xyh=[[115,60,70],[350,55,70]] # List for the top-right corner points of the columns and the height of each rows.
img=img=cv2.imread('1.jpg')
imgFinal=img.copy()
# How do I loop over all three list of lists and then display the result in imgFinal?
cv2.imshow('Expected Result',imgFinal)

For the iterating, I'd use Python's built-in zip function, so you can iterate the elements of all lists at the same time. The rest is simple value checking and putting the correct text to the image via cv2.putText. I first thought about using NumPy for the value checking, but since you have to use loops anyway for putting the text, all logic can be placed inside the loops.
Here's my full code:
import cv2
# Lists
a = [[0, 0, 0, 0, 3], [1, 2]]
b = [[0, 0, 0, 2, 3], [1, 0]]
xyh = [[115, 60, 70], [350, 55, 70]]
# Original image and copy
img = cv2.imread('1.jpg')
imgFinal = img.copy()
# Iterate elements of all lists at the same time
for first_image, second_image, coords in zip(a, b, xyh):
# Get initial x, y coordinates for the current image
x = coords[0] - 60
y = coords[1] + int(coords[2] / 2)
# Iterate elements in each image
for image_a, image_b in zip(first_image, second_image):
# Put correct text to the image
if image_a == image_b:
imgFinal = cv2.putText(imgFinal, 'Match', (x, y), cv2.FONT_HERSHEY_COMPLEX_SMALL, 0.5, (0, 0, 0), 1)
else:
imgFinal = cv2.putText(imgFinal, str(image_a), (x, y), cv2.FONT_HERSHEY_COMPLEX_SMALL, 0.5, (0, 0, 0), 1)
# Increment y coordinate to next row
y = y + coords[2]
# Output
cv2.imshow('Expected Result', imgFinal)
cv2.waitKey(0)
cv2.destroyAllWindows()
And that'd be output:
OpenCV has only limited support for text rendering, so the text doesn't look that nice.
----------------------------------------
System information
----------------------------------------
Platform: Windows-10-10.0.16299-SP0
Python: 3.8.5
OpenCV: 4.4.0
----------------------------------------

Related

Efficiently merge all the contours present in a list in python

I have a list of contours present in a list (around 12k). Many of the contours in this list overlap with each other. The task I need to perform is to efficiently merge all the contours which overlap with each other and get a new list with all the merged contours. The contours are merged based on a given condition such as intersection over union(IoU).
My current approach involves converting the list of contours into Geometry type in pygeos first, then finding the IoU and finally creating a dictionary containing which contours merge with which other contours. The code is as follows:
detection_contours = np.asarray([Geometry(str(Polygon(np.squeeze(val)))) for val in contours_list])
area_val = area(intersection(detection_contours[:, np.newaxis], detection_contours[np.newaxis, :]))
union_val = area(union(detection_contours[:, np.newaxis]), area(detection_contours[np.newaxis, :]))
iou = area_val / union_val
for row_no in range(len(iou)):
keep_to_merge_list[row_no] = np.where(ios[row_no, :] > match_threshold)[0].tolist()
sorted_merge_list = {k: v for k, v in sorted(keep_to_merge_list.items(), key=lambda item: len(item[1]), reverse=True)}
dest = {}
seen = set()
for k, v in sorted_merge_list.items():
if k in seen: continue
seen.add(k)
new_v = [i for i in v if i not in seen]
seen.update(new_v)
dest[k] = new_v
The dest dictionary contains all the contours that merge with a given contour. For e.g.:
{1: [3, 4, 5],
2: [6, 7],
8: []
}
I then merge the contour present in the dictionary's key with the list present as it's value. If the value list is empty, it means the contour does not have any overlapping contour.
I want to surpass the dictionary creation step and directly merge the contour with it's overlapping contour.

Convert a list of labels into number given a defined dictionary

I have the following dictionary defined in my code:
label_dict = {'positive': 1, 'negative': 0}
I also have a label_list that contains two possible values: "positive" and "negative".
I want to essentially map each label in label_list to the respective numeric value defined by label_dict.
I have the following for loop defined as well: for label in range(len(label_list)): for iterating through label_list.
How can I accomplish this? Any help is much appreciated.
One solution is to convert your label_list to Series and use mapping and then return it back to list again like that:
import pandas as pd
label_dict = {'positive': 1, 'negative': 0}
label_list = ["positive","negative","negative","positive",
"negative","positive","negative"]
new_lst = pd.Series(label_list).map(label_dict).tolist()
#output
print(new_lst) # [1, 0, 0, 1, 0, 1, 0]

How can I count the number of times a specific number appears in list vertically in python 3?

I'm trying to count the number of times a specific number appears in serval lists vertically.
My code:
import csv
with open('Superheroes.csv', 'r') as csvfile:
first_line = csvfile.readline()
super_reader = csv.reader(csvfile, delimiter=',')
result = []
for vote in super_reader:
vote.pop(0)
result.append([int(x) if x else 0 for x in vote])
result = [vote.count(1) for i in zip(*result)]
print(result)
example picture
So from the example picture, say I wanted to know how many times the number 11 appeared in every column of all the lists. I would expect an output of [0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 4, 2]
Thanks
You can use sum with a generator expression that outputs whether each item in a column matches the target number to perform the counting after transposing the rows into columns with zip:
def count(rows, num):
return [sum(i == num for i in map(int, col)) for col in zip(*rows)]
so that given the content of test.csv as follows:
2,1,3
3,2,1
1,3,3
1,3,2
count(csv.reader(open('test.csv')), 3) would return:
[1, 2, 2]
Demo: https://repl.it/#blhsing/IllAffectionateQuarks#main.py

Roll of different amount along a single axis in a 3D matrix [duplicate]

I have a matrix (2d numpy ndarray, to be precise):
A = np.array([[4, 0, 0],
[1, 2, 3],
[0, 0, 5]])
And I want to roll each row of A independently, according to roll values in another array:
r = np.array([2, 0, -1])
That is, I want to do this:
print np.array([np.roll(row, x) for row,x in zip(A, r)])
[[0 0 4]
[1 2 3]
[0 5 0]]
Is there a way to do this efficiently? Perhaps using fancy indexing tricks?
Sure you can do it using advanced indexing, whether it is the fastest way probably depends on your array size (if your rows are large it may not be):
rows, column_indices = np.ogrid[:A.shape[0], :A.shape[1]]
# Use always a negative shift, so that column_indices are valid.
# (could also use module operation)
r[r < 0] += A.shape[1]
column_indices = column_indices - r[:, np.newaxis]
result = A[rows, column_indices]
numpy.lib.stride_tricks.as_strided stricks (abbrev pun intended) again!
Speaking of fancy indexing tricks, there's the infamous - np.lib.stride_tricks.as_strided. The idea/trick would be to get a sliced portion starting from the first column until the second last one and concatenate at the end. This ensures that we can stride in the forward direction as needed to leverage np.lib.stride_tricks.as_strided and thus avoid the need of actually rolling back. That's the whole idea!
Now, in terms of actual implementation we would use scikit-image's view_as_windows to elegantly use np.lib.stride_tricks.as_strided under the hoods. Thus, the final implementation would be -
from skimage.util.shape import view_as_windows as viewW
def strided_indexing_roll(a, r):
# Concatenate with sliced to cover all rolls
a_ext = np.concatenate((a,a[:,:-1]),axis=1)
# Get sliding windows; use advanced-indexing to select appropriate ones
n = a.shape[1]
return viewW(a_ext,(1,n))[np.arange(len(r)), (n-r)%n,0]
Here's a sample run -
In [327]: A = np.array([[4, 0, 0],
...: [1, 2, 3],
...: [0, 0, 5]])
In [328]: r = np.array([2, 0, -1])
In [329]: strided_indexing_roll(A, r)
Out[329]:
array([[0, 0, 4],
[1, 2, 3],
[0, 5, 0]])
Benchmarking
# #seberg's solution
def advindexing_roll(A, r):
rows, column_indices = np.ogrid[:A.shape[0], :A.shape[1]]
r[r < 0] += A.shape[1]
column_indices = column_indices - r[:,np.newaxis]
return A[rows, column_indices]
Let's do some benchmarking on an array with large number of rows and columns -
In [324]: np.random.seed(0)
...: a = np.random.rand(10000,1000)
...: r = np.random.randint(-1000,1000,(10000))
# #seberg's solution
In [325]: %timeit advindexing_roll(a, r)
10 loops, best of 3: 71.3 ms per loop
# Solution from this post
In [326]: %timeit strided_indexing_roll(a, r)
10 loops, best of 3: 44 ms per loop
In case you want more general solution (dealing with any shape and with any axis), I modified #seberg's solution:
def indep_roll(arr, shifts, axis=1):
"""Apply an independent roll for each dimensions of a single axis.
Parameters
----------
arr : np.ndarray
Array of any shape.
shifts : np.ndarray
How many shifting to use for each dimension. Shape: `(arr.shape[axis],)`.
axis : int
Axis along which elements are shifted.
"""
arr = np.swapaxes(arr,axis,-1)
all_idcs = np.ogrid[[slice(0,n) for n in arr.shape]]
# Convert to a positive shift
shifts[shifts < 0] += arr.shape[-1]
all_idcs[-1] = all_idcs[-1] - shifts[:, np.newaxis]
result = arr[tuple(all_idcs)]
arr = np.swapaxes(result,-1,axis)
return arr
I implement a pure numpy.lib.stride_tricks.as_strided solution as follows
from numpy.lib.stride_tricks import as_strided
def custom_roll(arr, r_tup):
m = np.asarray(r_tup)
arr_roll = arr[:, [*range(arr.shape[1]),*range(arr.shape[1]-1)]].copy() #need `copy`
strd_0, strd_1 = arr_roll.strides
n = arr.shape[1]
result = as_strided(arr_roll, (*arr.shape, n), (strd_0 ,strd_1, strd_1))
return result[np.arange(arr.shape[0]), (n-m)%n]
A = np.array([[4, 0, 0],
[1, 2, 3],
[0, 0, 5]])
r = np.array([2, 0, -1])
out = custom_roll(A, r)
Out[789]:
array([[0, 0, 4],
[1, 2, 3],
[0, 5, 0]])
By using a fast fourrier transform we can apply a transformation in the frequency domain and then use the inverse fast fourrier transform to obtain the row shift.
So this is a pure numpy solution that take only one line:
import numpy as np
from numpy.fft import fft, ifft
# The row shift function using the fast fourrier transform
# rshift(A,r) where A is a 2D array, r the row shift vector
def rshift(A,r):
return np.real(ifft(fft(A,axis=1)*np.exp(2*1j*np.pi/A.shape[1]*r[:,None]*np.r_[0:A.shape[1]][None,:]),axis=1).round())
This will apply a left shift, but we can simply negate the exponential exponant to turn the function into a right shift function:
ifft(fft(...)*np.exp(-2*1j...)
It can be used like that:
# Example:
A = np.array([[1,2,3,4],
[1,2,3,4],
[1,2,3,4]])
r = np.array([1,-1,3])
print(rshift(A,r))
Building on divakar's excellent answer, you can apply this logic to 3D array easily (which was the problematic that brought me here in the first place). Here's an example - basically flatten your data, roll it & reshape it after::
def applyroll_30(cube, threshold=25, offset=500):
flattened_cube = cube.copy().reshape(cube.shape[0]*cube.shape[1], cube.shape[2])
roll_matrix = calc_roll_matrix_flattened(flattened_cube, threshold, offset)
rolled_cube = strided_indexing_roll(flattened_cube, roll_matrix, cube_shape=cube.shape)
rolled_cube = triggered_cube.reshape(cube.shape[0], cube.shape[1], cube.shape[2])
return rolled_cube
def calc_roll_matrix_flattened(cube_flattened, threshold, offset):
""" Calculates the number of position along time axis we need to shift
elements in order to trig the data.
We return a 1D numpy array of shape (X*Y, time) elements
"""
# armax(...) finds the position in the cube (3d) where we are above threshold
roll_matrix = np.argmax(cube_flattened > threshold, axis=1) + offset
# ensure we don't have index out of bound
roll_matrix[roll_matrix>cube_flattened.shape[1]] = cube_flattened.shape[1]
return roll_matrix
def strided_indexing_roll(cube_flattened, roll_matrix_flattened, cube_shape):
# Concatenate with sliced to cover all rolls
# otherwise we shift in the wrong direction for my application
roll_matrix_flattened = -1 * roll_matrix_flattened
a_ext = np.concatenate((cube_flattened, cube_flattened[:, :-1]), axis=1)
# Get sliding windows; use advanced-indexing to select appropriate ones
n = cube_flattened.shape[1]
result = viewW(a_ext,(1,n))[np.arange(len(roll_matrix_flattened)), (n - roll_matrix_flattened) % n, 0]
result = result.reshape(cube_shape)
return result
Divakar's answer doesn't do justice to how much more efficient this is on large cube of data. I've timed it on a 400x400x2000 data formatted as int8. An equivalent for-loop does ~5.5seconds, Seberg's answer ~3.0seconds and strided_indexing.... ~0.5second.

numpy selecting elements in sub array using slicing [duplicate]

I have a list like this:
a = [[4.0, 4, 4.0], [3.0, 3, 3.6], [3.5, 6, 4.8]]
I want an outcome like this (EVERY first element in the list):
4.0, 3.0, 3.5
I tried a[::1][0], but it doesn't work
You can get the index [0] from each element in a list comprehension
>>> [i[0] for i in a]
[4.0, 3.0, 3.5]
Use zip:
columns = zip(*rows) #transpose rows to columns
print columns[0] #print the first column
#you can also do more with the columns
print columns[1] # or print the second column
columns.append([7,7,7]) #add a new column to the end
backToRows = zip(*columns) # now we are back to rows with a new column
print backToRows
You can also use numpy:
a = numpy.array(a)
print a[:,0]
Edit:
zip object is not subscriptable. It need to be converted to list to access as list:
column = list(zip(*row))
You could use this:
a = ((4.0, 4, 4.0), (3.0, 3, 3.6), (3.5, 6, 4.8))
a = np.array(a)
a[:,0]
returns >>> array([4. , 3. , 3.5])
You can get it like
[ x[0] for x in a]
which will return a list of the first element of each list in a
Compared the 3 methods
2D list: 5.323603868484497 seconds
Numpy library : 0.3201274871826172 seconds
Zip (Thanks to Joran Beasley) : 0.12395167350769043 seconds
D2_list=[list(range(100))]*100
t1=time.time()
for i in range(10**5):
for j in range(10):
b=[k[j] for k in D2_list]
D2_list_time=time.time()-t1
array=np.array(D2_list)
t1=time.time()
for i in range(10**5):
for j in range(10):
b=array[:,j]
Numpy_time=time.time()-t1
D2_trans = list(zip(*D2_list))
t1=time.time()
for i in range(10**5):
for j in range(10):
b=D2_trans[j]
Zip_time=time.time()-t1
print ('2D List:',D2_list_time)
print ('Numpy:',Numpy_time)
print ('Zip:',Zip_time)
The Zip method works best.
It was quite useful when I had to do some column wise processes for mapreduce jobs in the cluster servers where numpy was not installed.
If you have access to numpy,
import numpy as np
a_transposed = a.T
# Get first row
print(a_transposed[0])
The benefit of this method is that if you want the "second" element in a 2d list, all you have to do now is a_transposed[1]. The a_transposed object is already computed, so you do not need to recalculate.
Description
Finding the first element in a 2-D list can be rephrased as find the first column in the 2d list. Because your data structure is a list of rows, an easy way of sampling the value at the first index in every row is just by transposing the matrix and sampling the first list.
Try using
for i in a :
print(i[0])
i represents individual row in a.So,i[0] represnts the 1st element of each row.

Resources