matplotlib subplot shrinks automatically - python-3.x

I am currently using matplotlib to plot my images to subplots and as there are many images (as much as 100), the subplots shrinks automatically as there are too many subplots.
subplots of 63 images
As you can see from the image, this is the issue I am currently facing.
I am wondering if there are any ways to fix the size of the subplots to prevent the automatic shrinking so they can be seen and also including a scrollable bar so that if there are over 100 images and all couldn't be fitted onto the figure, it will still maintain the size of the subplots and just allow the user to scroll and view all these images.
I am using TKagg backend.
I've tried doing:
fig = plt.figure(figsize=(8,8))
to maintain the size of the subplots but it seems that this doesn't fix the images as the images still shrunk.

I don't think matplotlib is the best module if you want to show a large number of images.
A good alternative might be Plotly, combined with dash.
pip install dash
This will make it possible to generate many images onto a webpage, which automatically enables scrolling. Also you can now add a manual slider, such that you can select the part that you want.
In order to give some reference I will display two minimal working examples.
Option 1
This is the solution closest to your specifications of generating a grid of images, through which you can scroll (when they become too big), but also requires dash_bootstrap for the formatting and pillow to convert images to base64.
It is build from two parts:
A numpy image convertor to base64, this is for serving the image as url to the html.Img component.
The dash app that creates a grid layout that will wrap around when changing the width or height value.
Part 1:
import base64
from io import BytesIO
from PIL import Image
def arr_to_b64(array, ext='jpeg'):
""" Convert an array to an image source that can be displayed."""
image = Image.fromarray(array)
if ext == 'jpg': ext = 'jpeg'
if ext == 'jpeg' and image.mode in ('RGBA', 'A'):
background = Image.new(image.mode[:-1], image.size, (255, 255, 255))
background.paste(image, image.split()[-1])
image = background
buffer = BytesIO()
image.save(buffer, format=ext)
encoded = base64.b64encode(buffer.getvalue()).decode('utf-8')
return f'data:image/{ext};base64, ' + encoded
part 2 (this is the dash server)
import numpy as np
import dash
import dash_html_components as html
import dash_bootstrap_components as dbc # Extra import besides `dash`
app = dash.Dash(__name__,
external_stylesheets=[dbc.themes.BOOTSTRAP])
images = np.random.randint(0, 255, (60, 30, 30, 3), dtype=np.uint8)
app.layout = html.Div(children=[
html.Div(id='image-output', className='d-flex flex-wrap justify-content-start m-5',
children=[html.Img(src=arr_to_b64(image), className='m-1', width=100, height=100)
for image in images])
])
if __name__ == '__main__':
app.run_server(port='8051', debug=True)
Output:
Option 2
This is a slightly simpler to create example without requiring any knowledge of bootstrap. But doesn't display a grid, but a slider. This slider makes it possible to select a specific image.
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go
from dash.dependencies import Input, Output
app = dash.Dash(__name__)
images = np.random.randint(0, 255, (60, 30, 30, 3), dtype=np.uint8)
app.layout = html.Div(children=[
dcc.Slider(id='image-number', min=0, max=60, value=0,
marks={key: str(key) for key in range(0, len(images), 5)},
tooltip=dict(always_visible=True, placement='top')),
html.Div(id='image-output'),
])
#app.callback(Output('image-output', 'children'),
[Input('image-number', 'value')])
def update_image(idx):
return dcc.Graph(figure=go.Figure(go.Image(z=images[idx])))
if __name__ == '__main__':
app.run_server(port='8051', debug=True)
It might be possible to do with matplotlib, but sometimes the alternatives are easier.
Hopefully this will help you further.

Related

Color diffusion when merging multiple images in a folder using PIL in python

I have set of 17 images and one of them has a highlighted pixel for my use. But, when I merge these 17 images, I get the color but it diffuses out of the pixel boundaries and I start seeing some colored pixel in black background.
I am using PIL library for the merging. I am attaching my code and the images for the reference. Any help would be appreciated.
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
# Cretaing the Pixel array
from PIL import Image
from PIL import ImageColor
img_path = '/Volumes/MY_PASSPORT/JRF/cancer_genome/gopal_gen/png_files/'
image_list = []
for entry in os.listdir(img_path):
if entry.endswith('.png'):
entry = int(entry.rstrip('.csv.png'))
image_list.append(entry)
image_list.sort()
list_img = []
for j in range(len(image_list)):
stuff = str(image_list[j])+'.csv.png'
list_img.append(stuff)
#print(list_img[0])
images = [Image.open(img_path+x) for x in list_img]
widths, heights = zip(*(i.size for i in images))
total_width = sum(widths)
max_height = max(heights)
#print(total_width, max_height)
new_im = Image.new('RGB', (total_width, max_height))
x_offset = 0
for im in images:
new_im.paste(im, (x_offset,0))
#print(im.size)
x_offset += im.size[0]
#print(x_offset)
new_im.save(img_path+'final_result_image.jpg')
Here is the combined image:
The third column has a pixel highlighted.
Here is the zoomed in part with the problem.
The JPEG format is lossy - it is allowed to change your pixels to make the file smaller. If your image is a conventional photo of a real-life scene, this doesn't normally matter. If your data is a blocky, computer-generated image, or a set of classes from a classification process, it can go horribly wrong if you use JPEG.
So, the answer is to use PNG (or potentially TIFF) format for images that need to be lossless.

holoviews doesn't display PIL image format

I am trying to import the MNIST data set and just display it using Holoviews. When I run the following:
import holoviews as hv
from torchvision import datasets, transforms
hv.extension('bokeh')
mnist_images = datasets.MNIST('data', train=True, download=True)
image_list = []
for k, (image, label) in enumerate(mnist_images):
if k >= 18:
break
image.show()
bounds = (0,0,1,1)
temp = hv.Image(image, bounds=bounds)
image_list.append(temp)
layout = hv.Layout(image_list).cols(2)
layout
I get the following error at the line with 'temp = hv.Image(...)':
holoviews.core.data.interface.DataError: None of the available storage backends were able to support the supplied data format.
The 'image' variable is the following object: <PIL.Image.Image image mode=L size=28x28 at 0x7F7F28567910>
and image.show() renders the image correctly. Also if I use matplotlib's .imshow() I can get a correct render.
What I want is to see the image rendered in Holoviews and I expected the Holoviews.Image() would do that. Is that not a correct assumption? If it is, then what is wrong with the code/approach?
HoloViews works with numerical arrays rather than images, so hv.Image is for constructing an image out of a 2D array, not for showing things that are already images. But you can get numerical arrays out of PIL objects, e.g. hv.RGB(np.array(image), bounds=bounds) to display it as an RGB image or something similar to pull out just the grayscale values to pass to hv.Image.

Highlighting specific text in an image using python

I want to highlight specific words/sentences in a website screenshot.
Once the screenshot is taken, I extract the text using pytesseract and cv2. That works well and I can get text and data about it.
import pytesseract
import cv2
if __name__ == "__main__":
img = cv2.imread('test.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
result = pytesseract.image_to_data(img, lang='eng', nice=0, output_type=pytesseract.Output.DICT)
print(result)
Using the results object I can find needed words and sentences.
The question is how to go back to the image and highlight those word?
Should I be looking at other libraries or there is a way to get pixel values and then highlight the text?
Ideally, I would like to get start and end coordinates of each word, how can that be done?
You can use pytesseract.image_to_boxes method to get the bounding box position of each character identified in your image. You can also use the method to draw bounding box around some specific characters if you want. Below code draws rectangles around my identified image.
import cv2
import pytesseract
import matplotlib.pyplot as plt
filename = 'sf.png'
# read the image and get the dimensions
img = cv2.imread(filename)
h, w, _ = img.shape # assumes color image
# run tesseract, returning the bounding boxes
boxes = pytesseract.image_to_boxes(img)use
print(pytesseract.image_to_string(img)) #print identified text
# draw the bounding boxes on the image
for b in boxes.splitlines():
b = b.split()
cv2.rectangle(img, ((int(b[1]), h - int(b[2]))), ((int(b[3]), h - int(b[4]))), (0, 255, 0), 2)
plt.imshow(img)

Wrong colours with cv2.imdecode (python opencv)

I try to display the image located here: http://skyservice.pha.jhu.edu/DR12/ImgCutout/getjpeg.aspx?ra=118.70299999999999&dec=45.721000000000004&width=10&height=10&scale=0.6
The image looks like this:
I use this code:
import matplotlib.pyplot as plt
import numpy as np
import urllib
import cv2
url = 'http://skyservice.pha.jhu.edu/DR12/ImgCutout/getjpeg.aspx?ra=118.70299999999999&dec=45.721000000000004&width=10&height=10&scale=0.6'
def url_to_image(url):
resp = urllib.request.urlopen(url)
image = np.asarray(bytearray(resp.read()), dtype="uint8")
image = cv2.imdecode(image, cv2.IMREAD_COLOR)
return image
img = url_to_image(url)
plt.imshow(img)
And it displays this:
Where all colours are too blue. I have tried various possibilities to change cv2.IMREAD_COLOR with values found in the manual, on StackOverflow or elsewhere on the net, like -1, 0, 1, cv2.COLOR_BGR2RGB, ... but I haven't been able to get the right colours. I have tried cv2.COLOR_BGR2GRAY, it didn't even show in gray scales. I even tried this answer, but cv2.CV_LOAD_IMAGE_COLORdoesn't seem to exist anymore...
Is there a correct value of cv2.imdecode() flag, or a special colormap of plt.imshow(), which would give me the initial colours?
Thanks to Mark Setchell, it now works. I quote him:
matplotlib requires RGB ordering whereas OpenCV (perversely) uses BGR
Therefore, the correct code is
def url_to_image(url):
resp = urllib.request.urlopen(url)
image = np.asarray(bytearray(resp.read()), dtype="uint8")
imageBGR = cv2.imdecode(image, cv2.IMREAD_COLOR)
imageRGB = cv2.cvtColor(imageBGR , cv2.COLOR_BGR2RGB)
return image

Responsive text in Matplotlib in Python

I am developing a simple graph visualizer using networkX and Matplotlib in Python. I also have some buttons plotted with text in them. As a whole the design is responsive which means that the graph and the buttons scale when I resize the window. However, the text size remains the same which makes the whole visualizer look very bad when not resized enough. Do you know how I can make the text also responsive?
Thank you in advance!!!
You update the fontsize of a matplotlib.text.Text using text.set_fontsize(). You can use a "resize_event" to call a function that sets a new fontsize. In order to do this with every text in a plot, it might be helpful to define a class that stores initial figure height and fontsizes and updates the fontsizes once the figure is resized, scaled by the new figure height divided by the initial one.
You may then also define a minimal readable fontsize, below which the text should not be resized.
A full example:
import matplotlib.pyplot as plt
import numpy as np
class TextResizer():
def __init__(self, texts, fig=None, minimal=4):
if not fig: fig = plt.gcf()
self.fig=fig
self.texts = texts
self.fontsizes = [t.get_fontsize() for t in self.texts]
_, self.windowheight = fig.get_size_inches()*fig.dpi
self.minimal= minimal
def __call__(self, event=None):
scale = event.height / self.windowheight
for i in range(len(self.texts)):
newsize = np.max([int(self.fontsizes[i]*scale), self.minimal])
self.texts[i].set_fontsize(newsize)
fontsize=11
text = plt.text(0.7, 0.6, "Some text", fontsize=fontsize,
bbox={'facecolor':'skyblue', 'alpha':0.5, 'pad':10})
cid = plt.gcf().canvas.mpl_connect("resize_event", TextResizer([text]))
plt.show()

Resources