Captions for matshow()s in multiple-page pdf - python-3.x

I have been around this problem for quite a long time but I'm not able to find an answer.
So, I have a list with matrices which I want to plot (for the sake of this question I'm just having 2 random matrices:
list = [np.random.random((500, 500)), np.random.random((500, 500))]
I then want to plot each element of the list using matshow in a separate page of a pdf file:
with PdfPages('file.pdf') as pdf:
plt.rc('figure', figsize=(3,3), dpi=40)
for elem in list:
plt.matshow(elem, fignum=1)
plt.title("title")
plt.colorbar()
plt.text(0,640,"Caption")
pdf.savefig() # saves the current figure into a pdf page
plt.close()
The result is the following:
My problem is with the caption. You can see I put "Caption" in the edge of the document on purpose. This is because sometimes the actual captions I want to insert are too big to fit in one single pdf page.
So, how can I make each pdf page adjustable to the caption's content (that might vary in each page)? For example, would it be possible to set each page size to A4 or A3, and then plot/write everything in each page?
I've already tried setting up plt.figure(figsize=(X, X)) with a variable X size, but it just changes the resolution of the pdf I guess.

You may want to use the bbox_inches="tight" option when saving the file. This will adapt the figure size to its content. So it then suffices to place some text at position (0,0) in figure coordinates and align it to the top. This will then extent towards the bottom and outside the figure (so the figure when shown on screen would not contain that text), but with the bbox_inches="tight" option of savefig, the saved figure will become large enough to contain that text.
The use of the textwrap package will then also allow to limit the text in horizontal direction.
import numpy as np; np.random.seed(1)
import textwrap
import matplotlib.pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages
p = np.ones(12); p[0] = 7
text2 = "".join(np.random.choice(list(" abcdefghijk"),p=p/p.sum(), size=1000))
text2 = textwrap.fill(text2, width=80)
texts = ["Caption: Example", "Caption 2: " + text2 ]
lis = [np.random.random((500, 500)), np.random.random((500, 500))]
with PdfPages('file.pdf') as pdf:
for elem,text in zip(lis,texts):
fig = plt.figure(dpi=100)
grid_size = (3,1)
plt.imshow(elem)
plt.title("title")
plt.colorbar()
fig.text(0,0, text, va="top")
plt.tight_layout()
pdf.savefig(bbox_inches="tight")
plt.close()

I think I have come up with an answer to this question myself, which solves the problem of having enough space for my text:
However, a perfect answer would be making each page's size dynamic, according to the amount of caption I put.
Anyway, my answer is the following (I essentially divided each page in a grid with 3 rows, making the upper 2 rows for the plots, and the last just for the caption) :
with PdfPages('file.pdf') as pdf:
for elem in list:
fig = plt.figure(figsize=(8.27, 11.69), dpi=100)
grid_size = (3,1)
plt.subplot2grid(grid_size, (0, 0), rowspan=2, colspan=1)
plt.imshow(elem)
plt.title("title")
plt.colorbar()
plt.subplot2grid(grid_size, (2, 0), rowspan=2, colspan=1)
plt.axis('off')
plt.text(0,1,"Caption")
plt.tight_layout()
pdf.savefig()
plt.close()
Which produces the following in each page:
Could someone find a better solution? :)

Related

Sorting algorithm visulizer: how to highlight the current element being accessed and compared in the algorithm?

So im trying to write a sorting algorithm visualizer. Code bellow. I am basically using matplotlib to plot the figure. My problem is that i want to also highlight the current element in the array being accessed, compared, and swaped. all of my attempts have failed at this. Please do also let me know if there is a better way of writing a visulizer in python. I have seen some tutorials using pygame but wanted to stick to basics. Also when the program runs till the end and everthing is sorted the plot goes blank. Is this because of the plt.clf() command and is there a way for the sorted plot to not close. Thanks!!!
from matplotlib import pyplot as plt
import numpy as np
# generate sudo-random list of numbers
lst = np.random.randint(0, 100, 20)
# x values for the bar plot
x = range(0, len(lst))
def insertion_sort(lst):
# loop through the list
# incrementally check which index to the left should i be placed in
for i in range(1, len(lst)):
while lst[i-1] > lst[i] and i>0:
lst[i], lst[i-1] = lst[i-1], lst[i]
i = i-1
# plot
plt.bar(x,lst)
plt.pause(0.1)
plt.clf()
plt.show()
return lst
print(lst)
print(insertion_sort(lst))
So the solution i came up with for this problem was to create a second list containing the current i and i-1 indexes and basically plot a second barchart over the main one set to a different color. Bad solution and failed indeed. Another idea i tried was to pass a conditional argument for the color paramater of plt.bar()
colors = ['red' if lst[i-1]>lst[i] else for element in lst 'blue']
plt.bar(x, lst, color=colors)
This did not work aswell. dont know if am on the right track and just need to keep at it or this is whole setup is futile to begin with. thank you for your time!!

How to detect an object in an image rather than screen with pyautogui?

I am using pyautogui.locateOnScreen() function to locate elements in chrome and get their x,y coordinates and click them. But at some point I need to take a screenshot of a part of the screen and search for the object I want in this screenshot. Then I get coordinates of it. Is it possible to do it with pyautogui?
My example code:
coord_one = pyautogui.locateOnScreen("first_image.png",confidence=0.95)
scshoot = pyautogui.screenshot(region=coord_one)
coord_two = # search second image in scshoot and if it can be detected get coordinates of it.
If it is not possible with pyautogui, can you advice the easiest-smartest way?
Thanks in advance.
I don't believe there is a built-in direct way to do what you need but the python-opencv library does the job.
The following code sample assumes you have an screen capture you just took "capture.png" and you want to find "logo.png" in that capture, which you know is an subsection of "capture.png".
Minimal example
"""Get bounding box of cropped image from original image."""
import cv2 as cv
import numpy as np
img_rgb = cv.imread(r'res/original.png')
# the cropped image, expected to be smaller
target_img = cv.imread(r'res/crop.png')
_, w, h = target_img.shape[::-1]
res = cv.matchTemplate(img_rgb,target_img,cv.TM_CCOEFF_NORMED)
# with the method used, the date in res are top left pixel coords
min_val, max_val, min_loc, max_loc = cv.minMaxLoc(res)
top_left = max_loc
# if we add to it the width and height of the target, then we get the bbox.
bottom_right = (top_left[0] + w, top_left[1] + h)
cv.rectangle(img_rgb,top_left, bottom_right, 255, 2)
cv.imshow('', img_rgb)
MatchTemplate
From the docs, MatchTemplate "simply slides the template image over the input image (as in 2D convolution) and compares the template and patch of input image under the template image." Under the hood, this offers methods such as square difference to compare the images represented as arrays.
See more
For a more in-depth explanation, check the opencv docs as the code is entirely based off their example.

Checkerboard pattern in saved image

I am trying to save a png image using the following commands:
fig = plt.figure(figsize=(14, 8))
ax1 = fig.add_subplot(221)
subplt1=(usub1_sfc-usub2_sfc).plot(vmin=-2.5e-2,vmax=2.5e-2,add_colorbar=False)
cb=plt.colorbar(subplt1,extend='both')
cb.ax.set_title('m/s', size=14)
cb.ax.tick_params(labelsize=12)
ax1.tick_params(labelsize=12)
ax1.set_xticks(np.arange(0,3500,500))
ax1.set_yticks(np.arange(0,2500,500))
#plt.xticks(fontsize=10)
#fig.colorbar(subplt1)
plt.title('USUBM$_{\mathrm{1km}}$ - USUBM$_{\mathrm{5km}}$')
plt.xlabel('nlon',fontsize=16)
plt.ylabel('nlat',fontsize=16)
ax2 = fig.add_subplot(222)
subplt2=(usub3_sfc-usub2_sfc).plot(vmin=-2.5e-2,vmax=2.5e-2,add_colorbar=False)
cb=plt.colorbar(subplt2,extend='both')
cb.ax.set_title(label='m/s', size=14)
cb.ax.tick_params(labelsize=12)
ax2.tick_params(labelsize=12)
ax2.set_xticks(np.arange(0,3500,500))
ax2.set_yticks(np.arange(0,2500,500))
plt.title('USUBM$_{\mathrm{200m}}$ - USUBM$_{\mathrm{5km}}$')
plt.xlabel('nlon',fontsize=16)
plt.ylabel('nlat',fontsize=16)
fig.savefig('./test.png',dpi=130)
My png file ends up having a checkerboard pattern everywhere around the bounding boxes of the plots. Inside the boxes I can see the fields, but everywhere around it the checkerboard pattern covers the axis ticks, axis labels, plot titles, etc.
The file I create looks very much like the third image at this link. The only difference is that there you see the checkerboard everywhere.
Question: How to save the png image without this checkerboard pattern?
Here is the answer to my original question (based on the other thread I linked to):
fig = plt.figure(facecolor="w")
This removed the checkerboard pattern surrounding the plotted area.

Matplotlib and pie/donut chart labels

If yall had seen my previous question, I am coding a Python program to evaluate the data that I collect while playing a game of Clue. I have decided to implement a GUI (tkinter) into my program to make it faster and easier to work with. One of the main window's of the GUI illustrates the different cards that I know each player has in their hand, the cards that I know must be in the middle "the murder cards", and the unknown cards that are inconclusively placed in the above categories. I have decided to implement this data through a matplotlib pie chart, five wedges for each of the previously mentioned categories.
Right now, I am unconcerned with how I implement this matplotlib function into my tkinter widget. I am solely focused on the design of the chart.
So far, I have documented the cards that are within each player's hand within a dictionary, wherein the keys are the player names, and the values are a set of cards that are in their hand. For example...
player_cards = { 'player1':{'Mustard', 'Scarlet', 'Revolver', 'Knife', 'Ballroom', 'Library'}, 'player2':{}, 'player3':{} }
So the data for the first three wedges of the pie chart will be extracted from the dictionary. For the other two wedges, the data will be stored within similarly organized sets.
After looking at the matplotlib.org website I have seen a example that sorta demonstrates what I am looking for...
with the code...
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(6, 3), subplot_kw=dict(aspect="equal"))
recipe = ["225 g flour",
"90 g sugar",
"1 egg",
"60 g butter",
"100 ml milk",
"1/2 package of yeast"]
data = [225, 90, 50, 60, 100, 5]
wedges, texts = ax.pie(data, wedgeprops=dict(width=0.5), startangle=-40)
bbox_props = dict(boxstyle="square,pad=0.3", fc="w", ec="k", lw=0.72)
kw = dict(xycoords='data', textcoords='data', arrowprops=dict(arrowstyle="-"), bbox=bbox_props, zorder=0, va="center")
for i, p in enumerate(wedges):
ang = (p.theta2 - p.theta1)/2. + p.theta1
y = np.sin(np.deg2rad(ang))
x = np.cos(np.deg2rad(ang))
horizontalalignment = {-1: "right", 1: "left"}[int(np.sign(x))]
connectionstyle = "angle,angleA=0,angleB={}".format(ang)
kw["arrowprops"].update({"connectionstyle": connectionstyle})
ax.annotate(recipe[i], xy=(x, y), xytext=(1.35*np.sign(x), 1.4*y),
horizontalalignment=horizontalalignment, **kw)
ax.set_title("Matplotlib bakery: A donut")
plt.show()
However, what is lacking from this example code is... (1) The label for each wedge is a single string rather than a set of strings (which is what stores the cards in each player's hand). (2) I cannot seem to control the color of the wedges. (3) the outline of each wedge is black, rather than white which is the background color of my GUI window. (4) I want to control the exact placement of the labels. And finally (5) I need the change the font/size of the labels. Other than that the example code is perfect.
Just note that the actual size of each wedge in the pie chart will be dictated by the size of each of the five sets (so they will add up to 21).
Just in case that you all need some more substantive code to work with, here are five sets that make up the data needed for this pie chart...
player1_cards = {'Mustard', 'Plum', 'Revolver', 'Rope', 'Ballroom', 'Library'}
player2_cards = {'Scarlet', 'White', 'Candlestick'}
player3_cards = {'Green', 'Library', 'Kitchen', 'Conservatory'}
middle_cards = {'Peacock'}
unknown_cards = {'Lead Pipe', 'Wrench', 'Knife', 'Hall', 'Lounge', 'Dining Room, 'Study'}
Okay that it, sorry for a rather long post, and thanks for those of you viewing and responding :)

Detect Color of particular area of Image Nodejs OpenCV

I'm trying to write code to detect the color of a particular area of an image.
So far I have come across is using OpenCV, we can do this, But still haven't found any particular tutorial to help with this.
I want to do this with javascript, but I can also use python OpenCV to get the results.
can anyone please help me with sharing any useful link or can explain how can I achieve detecting the color of the particular area in the image.
For eg.
The box in red will show a different color. I need to figure out which color it is showing.
What I have tried:
I have tried OpenCV canny images, though I am successful to get area separated with canny images, how to detect the color of that particular canny area is still a challenge.
Also, I tried it with inRange method from OpenCV which works perfect
# find the colors within the specified boundaries and apply
# the mask
mask = cv2.inRange(image, lower, upper)
output = cv2.bitwise_and(image, image, mask = mask)
# show the images
cv2.imshow("images", np.hstack([image, output]))
It works well and extracts the color area from the image But is there any callback which responds if the image has particular color so that it can be all done automatically?
So I am assuming here that, you already know the location of the rect which is going to be dynamically changed and need to find out the single most dominant color in the desired ROI. There are a lot of ways to do the same, one is by getting the average, of all the pixels in the ROI, other is to count all the distinct pixel values in the given ROI, with some tolerance difference.
Method 1:
import cv2
import numpy as np
img = cv2.imread("path/to/img.jpg")
region_of_interest = (356, 88, 495, 227) # left, top, bottom, right
cropped_img = img[region_of_interest[1]:region_of_interest[3], region_of_interest[0]:region_of_interest[2]]
print cv2.mean(cropped_img)
>>> (53.430516018839604, 41.05708814243569, 244.54991977640907, 0.0)
Method 2:
To find out the various dominant clusters in the given image you can use cv2.kmeans() as:
import cv2
import numpy as np
img = cv2.imread("path/to/img.jpg")
region_of_interest = (356, 88, 495, 227)
cropped_img = img[region_of_interest[1]:region_of_interest[3], region_of_interest[0]:region_of_interest[2]]
Z = cropped_img.reshape((-1, 3))
Z = np.float32(Z)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
K = 4
ret, label, center = cv2.kmeans(Z, K, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
# Sort all the colors, as per their frequencies, as:
print center[sorted(range(K), key=lambda x: np.count_nonzero(label == [x]), reverse=True)[0]]
>>> [ 52.96525192 40.93861389 245.02325439]
#Prateek... nice to have the question narrowed down to the core. The code you provided does not address this issue at hand and remains just a question. I'll hint you towards a direction but you have to code it yourself.
steps that guide you towards a scripting result:
1) In your script add two (past & current) pixellists to store values (pixeltype + occurance).
2) Introduce a while-loop with an action true/stop statement (link to "3") for looping purpose because then it becomes a dynamic process.
3) Write a GUI with a flashy warning banner.
4) compare the pixellist with current_pixellist for serious state change (threshhold).
5) If the delta state change at "4" meets threshold throw the alert ("3").
When you've got written the code and enjoyed the trouble of tracking the tracebacks... then edit your question, update it with the code and reshape your question (i can help wiht that if you want). Then we can pick it up from there. Does that sound like a plan?
I am not sure why you need callback in this situation, but maybe this is what you mean?
def test_color(image, lower, upper):
mask = cv2.inRange(image, lower, upper)
return np.any(mask == 255)
Explanations:
cv2.inRange() will return 255 when pixel is in range (lower, upper), 0 otherwise (see docs)
Use np.any() to check if any element in the mask is actually 255

Resources