image can't be displayed from within function [duplicate] - python-3.x

This question already has answers here:
Why does Tkinter image not show up if created in a function?
(5 answers)
Closed 5 years ago.
I have a simple GUI tryout where I want to display few images on my screen. I start with setting the window with gray squares allover it and resizing the desired image and now I want to put it on the frame:
img = [img_resize(yellow_square, img_size), "text"]
base_panel = Label(root, image = img[0])
base_txt = Label(root, text = img[1])
base_panel.grid(row = x, column = y)
base_txt.grid (row = x, column = y)
...
for im in ls:
panel = Label(root, image = im[0])
panel.grid(row = x+im[1][1], column = y+im[1][2])
txt = Label(root, text = im[2])
txt.grid(row = x+im[1][1], column = y+im[1][2], sticky = 'S')
ls is a list [image (using Image.open(img)), location offset, text]
When I copy these lines to my main script, it all goes well and I get the desired
but when trying to do so from a function, I only get the text part to work
I guess something is wrong with my image = ... but I don't understand what, as I have the code working after I just copied these lines to main. The main has another image, so maybe this is affecting somehow?
This is the code from main:
for im in background: # background is a list of gray squares
# some manipulation an a,b
panel = Label(root, image = im)
panel.grid(row = a, column = b)
Here should come the function call or the lines themselves

This is a common problem. You have to keep a reference to the image, or else python deletes it from memory. If you aren't in a function then the variable is a global variable, which keeps the reference. But in a function you need to do something like this:
panel = Label(root)
panel.img = im[0]
panel.config(image = panel.img)

Related

How to make border for my table using python tkinter?

Apparently, my application can display the excel file but it is a bit messy without border for the table.
import pandas as pd
import xlrd
import tkinter as tk
from tkinter import*
from tkinter import ttk, filedialog
root = tk.Tk()
root.title("My Application")
width = 1000
height = 500
def browseFile():
global workbook, copyWorkbook, excel_file, sheetName, worksheet, df_table
fileName = filedialog.askopenfilename(initialdir = '/', title = 'New File', filetypes = (('excel file', '.xlsx'), ('excel file', '.xls'), ('all files', '*.*')))
excel_file = pd.ExcelFile(fileName)
workbook = xlrd.open_workbook(fileName)
sheetCount = workbook.nsheets
sheetName = []
tab = []
for x in range(workbook.nsheets):
tab.append(ttk.Frame(tabControl))
sheetName = workbook.sheet_names()
tabControl.add(tab[x], text = sheetName[x])
df_table = excel_file.parse(sheetName[x])
lblTable = Label(tab[x], text = df_table.to_string(index = False)).pack()
toolbar = Frame(root)
btnOpen = Button(toolbar, text = "Open", command = browseFile).pack(side = LEFT)
btnQuit = Button(toolbar, text = "Quit", command = root.quit).pack(side = RIGHT)
toolbar.pack(side = TOP, fill = X)
tabControl = ttk.Notebook(root)
tabHome = ttk.Frame(tabControl)
tabControl.pack(expand = 1, fill = 'both', side = LEFT)
root.mainloop()
I have tried search statements that can display the table with border, but no result found. How can I add border to the table? Is it possible to add border? If not, what other method that I can use?
The problem
Your problem here is that, your code puts the entire dataframe into a single Label widget. When I tried putting a border around this label, it appeared around the whole dataframe, rather than around each cell.
My solution
My solution to this is to go through all of the rows and create an individual Label widget for each cell, giving each of these its own border. I then organised them using .grid() rather than .pack(). For the border, I used the settings borderwidth=2, relief="ridge", but you can choose others.
I also added a feature that sets the width of each column to its longest value to prevent the label's contents overflowing.
I did not include anything to include row and column headers, but you replacing i.pop(0) with i[0] and changing header=none should add this feature.
Modified code
I have only included the for loop in your browseFile() function as I have not made any changes to the rest of your code.
for x in range(workbook.nsheets):
tab.append(ttk.Frame(tabControl))
sheetName = workbook.sheet_names()
tabControl.add(tab[x], text = sheetName[x])
df_table = excel_file.parse(sheetName[x], header=None) # header=None stops the first line of the data table being used as the column header, making it appear in the data table
# Iterates through each row, creating a Label widget for each cell on that row.
for i in df_table.itertuples():
i=list(i) # Converts the row to a more helpful list format
row = i.pop(0) # pop returns value at position, then removes it, as we don't wan't first value of tuple (row number) in spreadsheet
for j in range(len(i)):
# Next two lines get the current column in a list format and convert all items to a string, in order to determine the longest item to make all label widgets the correct width.
current_column = list(df_table.iloc[:, j])
for k in range(len(current_column)): current_column[k] = str(current_column[k])
# Makes label widget
lbl = Label(tab[x], text = i[j], borderwidth=2, relief="ridge", width=len(max(current_column, key=len)), justify=LEFT).grid(row=row, column=j+1)
PS: I really enjoyed solving this question. Thanks for asking!

How to adaptively split an image into regions and set a different text orientation for each one?

Input-Sample
I am trying to pre-process my images in order to improve the ocr quality. However, I am stuck with a problem.
The Images I am dealing with contain different text orientations within the same image (2 pages, 1st is vertical, the 2nd one is horizontally oriented and they are scanned to the same image.
The text direction is automatically detected for the first part. nevertheless, the rest of the text from the other page is completely missed up.
I was thinking of creating a zonal template to detect the regions of interest but I don't know how.
Or automatically detect the border and split the image adaptively then flip the splitted part to achieve the required result.
I could set splitting based on a fixed pixel height but it is not constant as well.
from tesserocr import PyTessBaseAPI, RIL
import cv2
from PIL import Image
with PyTessBaseAPI() as api:
filePath = r'sample.jpg'
img = Image.open(filePath)
api.SetImage(img)
boxes = api.GetComponentImages(RIL.TEXTLINE, True)
print('Found {} textline image components.'.format(len(boxes)))
for i, (im, box, _, _) in enumerate(boxes):
# im is a PIL image object
# box is a dict with x, y, w and h keys
api.SetRectangle(box['x'], box['y'], box['w'], box['h'])
ocrResult = api.GetUTF8Text()
conf = api.MeanTextConf()
for box in boxes:
box = boxes[0][1]
x = box.get('x')
y = box.get('y')
h = box.get('h')
w = box.get('w')
cimg = cv2.imread(filePath)
crop_img = cimg[y:y+h, x:x+w]
cv2.imshow("cropped", crop_img)
cv2.waitKey(0)
output image
as you can see i can apply an orientation detection but I wount get any meaningful text out of such an image.
Try Tesseract API method GetComponentImages and then DetectOrientationScript on each component image.

tesseract ocr is not working on image which have text length of only 2 or less. Works fine for Image with text length greater than 3

import pytesseract
from PIL import Image
def textFromTesseractOCR(croppedImage):
for i in range(14):
text = pytesseract.image_to_string(croppedImage, lang = 'eng', boxes = False ,config = '--psm '+ str(i) +' --oem 3')
print("PSM Mode", i)
print("Text detected: ",text)
imgPath = "ImagePath" #you can use image I have uploaded
img = Image.open(imgPath)
textFromTesseractOCR(img)
I am working on extracting Table data from PDF. For this I am converting pdf to png. Detecting Lines, ascertaining table by line intersection and then cropping individual cells to get their text.
This all works fine, but tesseract is not working on cells image which has text of length 2 or less.
Works for this image:
Result from tesseract:
Does not work for this image:
Result from tesseract: return empty string.
It also returns empty for numbers of text length 2 or less.
I have tried resizing the image(which I knew wouldn't work), also tried appending dummy text to the image but the result was bad(was working only for few and I didn't the exact location to append the dummy text in the image)
It would be great if someone could help me with this.
So I finally came with a workaround for this situation. The situation being tesseract-OCR giving empty string when the image contains only 1 or 2 length string(eg "1" or "25").
To get output in this situation I appended the same image multiple time at the original image so as to make its length greater than 2. For example, if the original image contained only "3", I appended "3" image(the same image) 4 more times and thereby making it an image which contains the text "33333". We then give this image to tesseract which gives output "33333"(most of the times).Then we just have to replace space with blank in the text output from the Tesseract and divide the resulting string length by 5 to get the index up to which we would want to text out from the whole text.
Please see code for reference, hope this helps:
import pytesseract ## pip3 install pytesseract
Method which calls tesseract for OCR or calls our workaround code if we get an empty string from tesseract output.
def textFromTesseractOCR(croppedImage):
text = pytesseract.image_to_string(croppedImage)
if text.strip() == '': ### program that handles our problem
if 0 not in croppedImage:
return ""
yDir = 3
xDir = 3
iterations = 4
img = generate_blocks_dilation(croppedImage, yDir, xDir, iterations)
## we dilation to get only the text portion of the image and not the whole image
kernelH = np.ones((1,5),np.uint8)
kernelV = np.ones((5,1),np.uint8)
img = cv2.dilate(img,kernelH,iterations = 1)
img = cv2.dilate(img,kernelV,iterations = 1)
image = cropOutMyImg(img, croppedImage)
concateImg = np.concatenate((image, image), axis = 1)
concateImg = np.concatenate((concateImg, image), axis = 1)
concateImg = np.concatenate((concateImg, image), axis = 1)
concateImg = np.concatenate((concateImg, image), axis = 1)
textA = pytesseract.image_to_string(concateImg)
textA = textA.strip()
textA = textA.replace(" ","")
textA = textA[0:int(len(textA)/5)]
return textA
return text
Method for dilation.This method is used to dilate only the text region of the image
def generate_blocks_dilation(img, yDir, xDir, iterations):
kernel = np.ones((yDir,xDir),np.uint8)
ret,img = cv2.threshold(img, 0, 1, cv2.THRESH_BINARY_INV)
return cv2.dilate(img,kernel,iterations = iterations)
Method to crop the dilated part of the image
def cropOutMyImg(gray, OrigImg):
mask = np.zeros(gray.shape,np.uint8) # mask image the final image without small pieces
_ , contours, hierarchy = cv2.findContours(gray,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
if cv2.contourArea(cnt)!=0:
cv2.drawContours(mask,[cnt],0,255,-1) # the [] around cnt and 3rd argument 0 mean only the particular contour is drawn
# Build a ROI to crop the QR
x,y,w,h = cv2.boundingRect(cnt)
roi=mask[y:y+h,x:x+w]
# crop the original QR based on the ROI
QR_crop = OrigImg[y:y+h,x:x+w]
# use cropped mask image (roi) to get rid of all small pieces
QR_final = QR_crop * (roi/255)
return QR_final
I tried running tesseract on given 2 image but it does not returns text in shorter text image.
Another thing you can try is "Train a machine learning model (probably neural net) to on alphabets, numbers and special character, then when you want to get text from image, feed that image to model and it will predict text/characters."
Training dataset would look like :
Pair of (Image of character, 'character').
First element of pair is independent variable for model.
Second element of pair is corresponding character present in that image. It will be dependent variable for model.

Is filling a grid with images so I could control location a good practice?

I'm trying to locate a few images on a grid, but when I try to do something like
panel = Label(root, image = img)
panel.grid(row = a, column = b)
where a,b are values that should bring the image to the center of the grid, I always get the image on the top left corner, because empty columns/rows don't fill the space. So, using a friend's advice, I added a blank image and did something like
a = 0
b = 0
for img in ls2: # ls2 is a list containing instances of the blank image
a += 1
if a == 11:
a = 1
b += 1
panel = Label(root, image = img)
panel.grid(row = a, column = b)
Now, when I'm trying to locate a new image on row = x, column = y it goes as I wanted and this is my current solution. My question is, is this a good way to enable me use the whole grid?
No, it's not a good way. There is no need to create invisible widgets. It's hard to say what the good way is, however, because "use the whole grid" is somewhat vague.
If by "use the whole grid" you mean want to create a grid where all rows are the same height, and all of the columns are the same width, and the grid fills its containing window, the right solution is to create uniform rows columns using rowconfigure and columnconfigure.
For example, if you want to create a 4x4 grid of equal sized cells, you could do something like this:
import tkinter as tk
root = tk.Tk()
for row in range(4):
root.grid_rowconfigure(row, uniform="default", weight=1)
for column in range(4):
root.grid_columnconfigure(column, uniform="default", weight=1)
label_2_2 = tk.Label(root, text="row 2, column 2", borderwidth=2, relief="groove")
label_1_0 = tk.Label(root, text="row1, column 0", borderwidth=2, relief="groove")
label_1_0.grid(row=1, column=0)
label_2_2.grid(row=2, column=2)
root.mainloop()
The uniform option takes any arbitrary string. Every row (or column) with the same value will have the same dimensions.
The weight option tells grid how to allocate any extra space. By giving each row and each column an identical non-zero weight, all extra space will be apportioned equally, causing the rows and columns to grow or shrink to fit the window.
Just found the place geometry manager. It is more explicit and lets me control the exact locations of my images inside the widget.
panel.place(x=100, y=100) gives pixel precision location which was what I looked for in the first place.

Convert string into a Tkinter notebook frame

Ok so I am trying to find the frame which Tkinter is using, then take its width and height and resize the window so that everything fits nicely without ugly spaces left. So far I have gotten the following...
convert = {"tab1_name", "tab1"; "tab2_name", "tab2"; "tab3_name", "tab3") ##(it goes on)
a = mainframe.tab(mainframe.select(), "text")
b = convert[a]
w = b.winfo_reqwidth()
h = b.winfo_reqheight()
mainframe.configure(width=w, height=h)
The names of each frame in the notebook are tab1, tab2, tab3, etc., but the labels on them are unique because they describe what happens in the tab. I want to be able to take the string returned from the convert dictionary function and use it as the frame's name. I am not sure if the frame is a class or what else. Is there a way to convert the string b into the frame's name and somehow use it in the .winfo_reqheight()? I do not want to have to make a thing which says...
if b=="tab1":
w = tab1.winfo_reqwidth()
h = tab1.winfo_reqheight()
mainframe.configure(width=w, height=h)
for each frame because I want it to be easy to add new frames without having to add so much code.
Thank you
Option 1:
You can store actual objects in dictionaries. So try:
convert = {"tab1_name": tab1, "tab2_name": tab2, "tab3_name": tab3}
a = mainframe.tab(mainframe.select(), "text")
b = convert[a]
w = b.winfo_reqwidth()
h = b.winfo_reqheight()
mainframe.configure(width=w, height=h)
Option 2:
Executing strings is possible with the 'exec('arbitrary code in a string')' function
See How do I execute a string containing Python code in Python?.
You could do this: (with just text in the dictionary or whatever convert is)
convert = {"tab1_name": "tab1", "tab2_name": "tab2", "tab3_name": "tab3"}
a = mainframe.tab(mainframe.select(), "text")
b = convert[a]
code1 = "w = %s.winfo_reqwidth()" % b
code2 = "h = %s.winfo_reqheight()" % b
exec(code1) # for python 2 it is: exec code1
exec(code2) # python 3 changed the exec statement to a function
mainframe.configure(width=w, height=h)
Be careful that you don't let malicious code into the exec statement, because python will run it. This is usually only a problem if an end user can input things into the function(it sounds like you don't have to worry about this).
btw, I think your first line is incorrect. You open with a { but close with ). Proper dictionary syntax would be:
convert = {"tab1_name": "tab1", "tab2_name": "tab2", "tab3_name": "tab3"}
Notice the colons separating key and value, and commas in-between entries.

Resources