Python Tkinter. Best Fit Ellipse from Data Points - python

I am working on a Python GUI w/ Tkinter. I am trying to save four specified point locations from a BMP image into variables, and create a best-fit ellipse that more or less passes through the saved points. I am still a beginner working w/ Tkinter and GUI's so please bear w/ me!
So far, the code is able to mark the points and print out its position/coordinates. Should I use matplotlib for this kind of situation? Am i able to use that w/ tkinter as well?
Here is my code:
from tkinter import *
from PIL import Image, ImageTk
class Window(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master = master
self.pos = []
self.master.title("GUI")
self.pack(fill=BOTH, expand=1)
self.counter = 0
menu = Menu(self.master)
self.master.config(menu=menu)
file = Menu(menu)
file.add_command(label="Exit", command=self.client_exit)
menu.add_cascade(label="File", menu=file)
analyze = Menu(menu)
analyze.add_command(label="Region of
Interest",command=self.regionOfInterest)
analyze.add_command(label="Erase", command=self.erasePoints)
menu.add_cascade(label="Analyze", menu=analyze)
load = Image.open("ap41.ddr.brf.sdat.bmp")
render = ImageTk.PhotoImage(load)
img = Label(self, image=render)
img.image = render
img.place(x=0, y=0)
def regionOfInterest(self):
root.config(cursor="plus")
canvas.bind("<Button-1>", self.imgClick)
def erasePoints(self):
self.pos = []
def client_exit(self):
exit()
def imgClick(self, event):
if self.counter < 4:
x = canvas.canvasx(event.x)
y = canvas.canvasy(event.y)
self.pos.append((x, y))
print(self.pos)
canvas.create_line(x - 5, y, x + 5, y, fill="red",
tags="crosshair")
canvas.create_line(x, y - 5, x, y + 5, fill="red",
tags="crosshair")
self.counter += 1
else:
canvas.unbind("<Button 1>")
root.config(cursor="arrow")
self.counter = 0
root = Tk()
imgSize = Image.open("ap41.ddr.brf.sdat.bmp")
tkimage = ImageTk.PhotoImage(imgSize)
w, h = imgSize.size
canvas = Canvas(root, width=w, height=h)
canvas.create_image((w/2,h/2),image=tkimage)
canvas.pack()
root.geometry("%dx%d"%(w,h))
app = Window(root)
root.mainloop()

Here is something you can play with and fine tune but I think this will be close to what you are trying to do.
first I created anther menu item labeled Create Ellipse that links to a method to work out the top left cords and bottom right cords and then uses that with the create_ovel() command to create an ellipse on the screen. Let me know if this is close to what you want to do.
The new method below will compare the values of each tuple to a base tuple and if the numbers are lower it will change the top left cords and if the numbers are high it will change the bottom right cords. With those 2 sets of cords figured out it will then create an ellipse to roughly fit what you selected.
def createEllipse(self):
top_left_cords = self.pos[0]
bottom_right_cords = self.pos[0]
for pos in self.pos:
if pos[0] < top_left_cords[0]:
top_left_cords = (pos[0], top_left_cords[1])
if pos[1] < top_left_cords[1]:
top_left_cords = (top_left_cords[0], pos[1])
if pos[0] > bottom_right_cords[0]:
bottom_right_cords = (pos[0], bottom_right_cords[1])
if pos[1] > bottom_right_cords[1]:
bottom_right_cords = (bottom_right_cords[0], pos[1])
Below is the full code:
from tkinter import *
from PIL import Image, ImageTk
class Window(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master = master
self.pos = []
self.master.title("GUI")
self.pack(fill=BOTH, expand=1)
self.counter = 0
menu = Menu(self.master)
self.master.config(menu=menu)
file = Menu(menu)
file.add_command(label="Exit", command=self.client_exit)
menu.add_cascade(label="File", menu=file)
analyze = Menu(menu)
analyze.add_command(label="Region of Interest",command=self.regionOfInterest)
analyze.add_command(label="Erase", command=self.erasePoints)
analyze.add_command(label="Create Ellipse", command=self.createEllipse)
menu.add_cascade(label="Analyze", menu=analyze)
load = Image.open("./Colors/1.png")
render = ImageTk.PhotoImage(load)
img = Label(self, image=render)
img.image = render
img.place(x=0, y=0)
def createEllipse(self):
top_left_cords = self.pos[0]
bottom_right_cords = self.pos[0]
for pos in self.pos:
if pos[0] < top_left_cords[0]:
top_left_cords = (pos[0], top_left_cords[1])
if pos[1] < top_left_cords[1]:
top_left_cords = (top_left_cords[0], pos[1])
if pos[0] > bottom_right_cords[0]:
bottom_right_cords = (pos[0], bottom_right_cords[1])
if pos[1] > bottom_right_cords[1]:
bottom_right_cords = (bottom_right_cords[0], pos[1])
print(top_left_cords, bottom_right_cords)
canvas.create_oval(top_left_cords, bottom_right_cords)
def regionOfInterest(self):
root.config(cursor="plus")
canvas.bind("<Button-1>", self.imgClick)
def erasePoints(self):
self.pos = []
def client_exit(self):
exit()
def imgClick(self, event):
if self.counter < 4:
x = canvas.canvasx(event.x)
y = canvas.canvasy(event.y)
self.pos.append((x, y))
print(self.pos)
canvas.create_line(x - 5, y, x + 5, y, fill="red",
tags="crosshair")
canvas.create_line(x, y - 5, x, y + 5, fill="red",
tags="crosshair")
self.counter += 1
else:
canvas.unbind("<Button 1>")
root.config(cursor="arrow")
self.counter = 0
root = Tk()
imgSize = Image.open("./Colors/1.png")
tkimage = ImageTk.PhotoImage(imgSize)
w, h = imgSize.size
canvas = Canvas(root, width=w, height=h)
canvas.create_image((w/2,h/2),image=tkimage)
canvas.pack()
root.geometry("%dx%d"%(w,h))
app = Window(root)
root.mainloop()
Here is a before and after from a sample image I have for testing.
Before:
After:

Related

Saving a 16x16 image from a tkinter canvas to a png in python

When I am trying to save an image from a tkinter canvas onto an external file, it doesn't keep the 16x16 resolution I would like which makes it blurry.
I was expecting no blur and it to keep the 16x16 resolution. I tried two methods of saving the file
The first method I tried is this:
self.sprite = tk.Canvas(self.root, width=16, height=16)
def get_clicked_pos(self, event):
y, x = event.y, event.x
gap = 288//16
row = y//gap # row and column are the wrong way but i found it easier to keep it that way instead of changing it all
col = x//gap
self.spriteGrid[row][col].tile = self.colour
self.spriteDraw(gap) # draws on the large canvas
self.sprite.create_rectangle(col, row, col+1, row+1, fill=self.colour, outline="")
def save_as_png1(self):
ps = self.sprite.postscript(width=16, height=16)
image = PIL.Image.open(io.BytesIO(ps.encode('utf-8')))
image.save('sprite.png')
In this method I made two canvases. One that is large and visible but a 16x16 grid so you can draw and one that is the correct size and not visible. When you draw on the large canvas it mimics it on the small canvas and when you save it turns the correct size canvas into a postscript and makes it a sprite. However, it doesn't work and creates this:
enter image description here method 1
The second method I tried is:
self.image1 = PIL.Image.new("RGB", (16, 16), "white")
self.draw = PIL.ImageDraw.Draw(self.image1)
def get_clicked_pos(self, event):
y, x = event.y, event.x
gap = 288//16
row = y//gap # row and column are the wrong way but i found it easier to keep it that way instead of changing it all
col = x//gap
self.spriteGrid[row][col].tile = self.colour
self.spriteDraw(gap)
self.draw.rectangle([(col, row), (col+1, row+1)], fill=self.colour)
self.sprite.create_rectangle(col, row, col+1, row+1, fill=self.colour, outline="")
def save_as_png2(self):
self.image1.save("alternate.png")
In this method, it copies what you draw onto a PIL image which then gets saved when you call save_as_image2. This provides similar results but in a 16x16 file
enter image description here method 2
The whole code is:
import tkinter as tk
from tkinter.colorchooser import askcolor
import PIL.Image
import PIL.ImageDraw
import io
class DrawWindow():
def __init__(self):
self.root = tk.Tk()
self.colour = "black"
self.menubar = tk.Menu(self.root)
self.optionsmenu = tk.Menu(self.menubar, tearoff=0)
self.optionsmenu.add_command(label="Save v1", command=self.save_as_png1)
self.optionsmenu.add_command(label="Save v2", command=self.save_as_png2)
self.menubar.add_cascade(menu=self.optionsmenu, label="Options")
self.root.config(menu=self.menubar)
self.sprFrame = tk.Frame(self.root)
self.sprFrame.pack()
self.spriteCanvas = tk.Canvas(self.sprFrame, height=288, width=288, bg="white")
self.spriteCanvas.pack(pady=5, padx=5)
self.pickColour = tk.Button(self.sprFrame, text="Pick colour", command=self.changeColour)
self.pickColour.pack(pady=5)
self.spriteGrid = []
for i in range(16):
self.spriteGrid.append([])
for j in range(16):
spot = Spot(i, j, 1, 16)
self.spriteGrid[i].append(spot)
self.sprite = tk.Canvas(self.root, width=16, height=16)
self.image1 = PIL.Image.new("RGB", (16, 16), "white")
self.draw = PIL.ImageDraw.Draw(self.image1)
self.spriteCanvas.bind("<Button-1>", self.get_clicked_pos)
self.root.mainloop()
def get_clicked_pos(self, event):
y, x = event.y, event.x
gap = 288//16
row = y//gap # row and column are the wrong way but i found it easier to keep it that way instead of changing it all
col = x//gap
self.spriteGrid[row][col].tile = self.colour
self.spriteDraw(gap)
self.draw.rectangle([(col, row), (col+1, row+1)], fill=self.colour)
self.sprite.create_rectangle(col, row, col+1, row+1, fill=self.colour, outline="")
def changeColour(self):
self.colour = askcolor(title="Sprite Colour")[1]
def save_as_png1(self):
ps = self.sprite.postscript(width=16, height=16)
image = PIL.Image.open(io.BytesIO(ps.encode('utf-8')))
image.save('sprite.png')
def save_as_png2(self):
self.image1.save("alternate.png")
def spriteDraw(self, gap):
for i in self.spriteGrid:
for j in i:
if not(j.tile==None):
self.spriteCanvas.create_rectangle(j.y*gap, j.x*gap, j.y*gap+gap, j.x*gap+gap, fill=j.tile)
class Spot:
def __init__(self, row, col, width, total_rows):
self.row = row
self.col = col
self.x = row * width
self.y = col * width
self.tile = None
self.width = width
self.total_rows = total_rows
DrawWindow()
I figured it out, method two works but I was using self.draw.rectangle() when I should've been using self.image1.putpixel().
So for anyone wanting the finished code it is:
import tkinter as tk
from tkinter.colorchooser import askcolor
import PIL.Image
import PIL.ImageDraw
class DrawWindow():
def __init__(self):
self.root = tk.Tk()
self.colour = ((0, 0, 0), "black")
self.menubar = tk.Menu(self.root)
self.optionsmenu = tk.Menu(self.menubar, tearoff=0)
self.optionsmenu.add_command(label="Save image", command=self.save_as_png)
self.menubar.add_cascade(menu=self.optionsmenu, label="Options")
self.root.config(menu=self.menubar)
self.sprFrame = tk.Frame(self.root)
self.sprFrame.pack()
self.spriteCanvas = tk.Canvas(self.sprFrame, height=288, width=288, bg="white")
self.spriteCanvas.pack(pady=5, padx=5)
self.pickColour = tk.Button(self.sprFrame, text="Pick colour", command=self.changeColour)
self.pickColour.pack(pady=5)
self.spriteGrid = []
for i in range(16):
self.spriteGrid.append([])
for j in range(16):
spot = Spot(i, j, 1, 16)
self.spriteGrid[i].append(spot)
self.image1 = PIL.Image.new("RGB", (16, 16), "white")
self.spriteCanvas.bind("<Button-1>", self.get_clicked_pos)
self.root.mainloop()
def get_clicked_pos(self, event):
y, x = event.y, event.x
gap = 288//16
row = y//gap # row and column are the wrong way but i found it easier to keep it that way instead of changing it all
col = x//gap
self.spriteGrid[row][col].tile = self.colour[1]
self.spriteDraw(gap)
self.image1.putpixel((col, row), self.colour[0])
def changeColour(self):
self.colour = askcolor(title="Sprite Colour")
print(self.colour)
def save_as_png(self):
self.image1.save("sprite.png")
def spriteDraw(self, gap):
for i in self.spriteGrid:
for j in i:
if not(j.tile==None):
self.spriteCanvas.create_rectangle(j.y*gap, j.x*gap, j.y*gap+gap, j.x*gap+gap, fill=j.tile)
class Spot:
def __init__(self, row, col, width, total_rows):
self.row = row
self.col = col
self.x = row * width
self.y = col * width
self.tile = None
self.width = width
self.total_rows = total_rows
DrawWindow()

Tkinter: How to get correct bounding box from a zoomed image?

I am writing a program in python tkinter that allows you to select certain areas in the image. This image can be zoomed and dragged inside a Canvas. But the problem is that I can't get correct coordinates of the selections and extract thumbnails from original images using the small one.
Here is my code and a screenshot:
from tkinter import *
from tkinter import ttk
from tkinter import messagebox as mb
import glob
import os
from PIL import Image, ImageTk
import cv2
from src.lsh_utils import get_lsh, query_lsh
from src.io_utils import get_config
COLORS = ['red', 'blue', 'olive', 'teal', 'cyan', 'green', 'black']
CONFIG = get_config('conf.json')
class AutoScrollbar(ttk.Scrollbar):
''' A scrollbar that hides itself if it not needed.
Works only if you use the grid geometry manager '''
def set(self, lo, hi):
if float(lo) <= 0.0 and float(hi) >= 1.0:
self.grid_remove()
else:
self.grid()
ttk.Scrollbar.set(self, lo, hi)
def pack(self, **kw):
raise TclError('Cannot use pack with this widget')
def place(self, **kw):
raise TclError('Cannot use place with this widget')
class Zoom_Advanced(ttk.Frame):
''' Advanced zoom of the image '''
def __init__(self, mainframe, path):
''' Initialize the main Frame '''
ttk.Frame.__init__(self, master=mainframe)
self.master.title('Zoom with mouse wheel')
# Vertical and horizontal scrollbars for canvas
vbar = AutoScrollbar(self.master, orient='vertical')
hbar = AutoScrollbar(self.master, orient='horizontal')
vbar.grid(row=0, column=1, sticky='ns', rowspan=4)
hbar.grid(row=1, column=0, sticky='we')
# Create canvas and put image on it
self.canvas = Canvas(self.master, highlightthickness=0,
xscrollcommand=hbar.set, yscrollcommand=vbar.set)
self.canvas.grid(row=0, column=0, sticky='nswe')
self.canvas.update() # wait till canvas is created
vbar.configure(command=self.scroll_y) # bind scrollbars to the canvas
hbar.configure(command=self.scroll_x)
# Make the canvas expandable
self.master.rowconfigure(0, weight=1)
self.master.columnconfigure(0, weight=1)
# Bind events to the Canvas
self.canvas.bind('<Configure>', self.show_image) # canvas is resized
self.canvas.bind('<Button-3>', self.move_from)
self.canvas.bind("<Button-1>", self.mouseClick)
self.canvas.bind('<B3-Motion>', self.move_to)
self.canvas.bind("<Motion>", self.mouseMove)
self.canvas.bind('<MouseWheel>', self.wheel) # with Windows and MacOS, but not Linux
self.canvas.bind('<Button-5>', self.wheel) # only with Linux, wheel scroll down
self.canvas.bind('<Button-4>', self.wheel) # only with Linux, wheel scroll up
self.master.bind("<Escape>", self.cancelBBox) # press <Espace> to cancel current bbox
self.master.bind("s", self.cancelBBox)
self.master.bind("a", self.prevImage) # press 'a' to go backforward
self.master.bind("d", self.nextImage) # press 'd' to go forward
self.side_container = Frame(self.master)
self.side_container.grid_rowconfigure(0, weight=1)
self.side_container.grid_columnconfigure(0, weight=1)
self.side_container.grid(row=0, column=2, sticky='nse')
self.imscale = 1.0 # scale for the canvaas image
self.delta = 1.3 # zoom magnitude
self.lb1 = Label(self.side_container, text='Bounding boxes:')
self.lb1.grid(row=0, column=0, sticky='nwe')
self.listbox = Listbox(self.side_container)
self.listbox.grid(row=1, column=0, sticky='nwe')
self.btnDel = Button(self.side_container, text='Delete', command=self.delBBox)
self.btnDel.grid(row=2, column=0, sticky='nwe')
self.btnClear = Button(self.side_container, text='ClearAll', command=self.clearBBox)
self.btnClear.grid(row=3, column=0, sticky='nwe')
# control panel for image navigation
self.ctrPanel = Frame(self.side_container)
self.ctrPanel.grid(row=4, column=0, columnspan=2, sticky='we')
self.prevBtn = Button(self.ctrPanel, text='<< Prev', width=10, command=self.prevImage)
self.prevBtn.pack(side=LEFT, padx=5, pady=3)
self.nextBtn = Button(self.ctrPanel, text='Next >>', width=10, command=self.nextImage)
self.nextBtn.pack(side=LEFT, padx=5, pady=3)
self.progLabel = Label(self.ctrPanel, text="Progress: / ")
self.progLabel.pack(side=LEFT, padx=5)
self.tmpLabel = Label(self.ctrPanel, text="Go to Image No.")
self.tmpLabel.pack(side=LEFT, padx=5)
self.idxEntry = Entry(self.ctrPanel, width=5)
self.idxEntry.pack(side=LEFT)
self.goBtn = Button(self.ctrPanel, text='Go', command=self.gotoImage)
self.goBtn.pack(side=LEFT)
self.runBtn = Button(self.ctrPanel, text='Run', command=self.run)
self.runBtn.pack(side=LEFT, padx=5, pady=3)
self.imageDir = ''
self.imageList = []
self.egDir = ''
self.egList = []
self.outDir = ''
self.cur = 0
self.total = 0
self.category = 0
self.imagename = ''
self.labelfilename = ''
self.tkimg = None
self.cla_can_temp = []
self.detection_images_path = CONFIG.get('DETECTIONS_PATH', 'data\\detections')
self.orig_images_path = CONFIG.get('IMAGE_DIR')
self.viewer = None
self._new_window = None
# initialize mouse state
self.STATE = {}
self.STATE['click'] = 0
self.STATE['x'], self.STATE['y'] = 0, 0
# reference to bbox
self.bboxIdList = []
self.bboxId = None
self.bboxList = []
self.hl = None
self.vl = None
self.disp = Label(self.ctrPanel, text='')
self.disp.pack(side=RIGHT)
self.loadDir()
# self.show_image()
def scroll_y(self, *args, **kwargs):
''' Scroll canvas vertically and redraw the image '''
self.canvas.yview(*args, **kwargs) # scroll vertically
self.show_image() # redraw the image
def scroll_x(self, *args, **kwargs):
''' Scroll canvas horizontally and redraw the image '''
self.canvas.xview(*args, **kwargs) # scroll horizontally
self.show_image() # redraw the image
def move_from(self, event):
''' Remember previous coordinates for scrolling with the mouse '''
self.canvas.scan_mark(event.x, event.y)
def move_to(self, event):
''' Drag (move) canvas to the new position '''
self.canvas.scan_dragto(event.x, event.y, gain=1)
self.show_image() # redraw the image
def wheel(self, event):
''' Zoom with mouse wheel '''
x = self.canvas.canvasx(event.x)
y = self.canvas.canvasy(event.y)
bbox = self.canvas.bbox(self.container) # get image area
if bbox[0] < x < bbox[2] and bbox[1] < y < bbox[3]: pass # Ok! Inside the image
else: return # zoom only inside image area
scale = 1.0
# Respond to Linux (event.num) or Windows (event.delta) wheel event
if event.num == 5 or event.delta == -120: # scroll down
i = min(self.width, self.height)
if int(i * self.imscale) < 30: return # image is less than 30 pixels
self.imscale /= self.delta
scale /= self.delta
if event.num == 4 or event.delta == 120: # scroll up
i = min(self.canvas.winfo_width(), self.canvas.winfo_height())
if i < self.imscale: return # 1 pixel is bigger than the visible area
self.imscale *= self.delta
scale *= self.delta
self.canvas.scale('all', x, y, scale, scale) # rescale all canvas objects
self.show_image()
def show_image(self, event=None):
''' Show image on the Canvas '''
bbox1 = self.canvas.bbox(self.container) # get image area
# Remove 1 pixel shift at the sides of the bbox1
bbox1 = (bbox1[0] + 1, bbox1[1] + 1, bbox1[2] - 1, bbox1[3] - 1)
bbox2 = (self.canvas.canvasx(0), # get visible area of the canvas
self.canvas.canvasy(0),
self.canvas.canvasx(self.canvas.winfo_width()),
self.canvas.canvasy(self.canvas.winfo_height()))
bbox = [min(bbox1[0], bbox2[0]), min(bbox1[1], bbox2[1]), # get scroll region box
max(bbox1[2], bbox2[2]), max(bbox1[3], bbox2[3])]
print(bbox)
if bbox[0] == bbox2[0] and bbox[2] == bbox2[2]: # whole image in the visible area
bbox[0] = bbox1[0]
bbox[2] = bbox1[2]
if bbox[1] == bbox2[1] and bbox[3] == bbox2[3]: # whole image in the visible area
bbox[1] = bbox1[1]
bbox[3] = bbox1[3]
print(bbox2, bbox1)
self.canvas.configure(scrollregion=bbox) # set scroll region
x1 = max(bbox2[0] - bbox1[0], 0) # get coordinates (x1,y1,x2,y2) of the image tile
y1 = max(bbox2[1] - bbox1[1], 0)
x2 = min(bbox2[2], bbox1[2]) - bbox1[0]
y2 = min(bbox2[3], bbox1[3]) - bbox1[1]
print(x1, y1, x2, y2)
if int(x2 - x1) > 0 and int(y2 - y1) > 0: # show image if it in the visible area
x = min(int(x2 / self.imscale), self.width) # sometimes it is larger on 1 pixel...
y = min(int(y2 / self.imscale), self.height) # ...and sometimes not
print(x, y)
image = self.image.crop((int(x1 / self.imscale), int(y1 / self.imscale), x, y))
self.imagetk = ImageTk.PhotoImage(image.resize((int(x2 - x1), int(y2 - y1))))
imageid = self.canvas.create_image(max(bbox2[0], bbox1[0]), max(bbox2[1], bbox1[1]),
anchor='nw', image=self.imagetk)
self.canvas.lower(imageid) # set image into background
self.canvas.imagetk = self.imagetk # keep an extra reference to prevent garbage-collection
def run(self):
labels = self._compare_labels()
lsh = get_lsh(CONFIG.get('LSH_DATASET'))
if not labels:
return
for file, bboxes in labels.items():
im = cv2.imread(file)
h, w, _ = im.shape
print(w, h)
rois = []
for box in bboxes:
x1 = int(int(box[0])/600*w)
y1 = int(int(box[1])/800*h)
x2 = int(int(box[2])/600*w)
y2 = int(int(box[3])/800*h)
roi = im[y1:y2, x1:x2].copy()
print(file, [n[0][1] for n in query_lsh(lsh, roi)])
# cv2.imshow(str(box), roi)
# cv2.waitKey()
# def _update_labels_boxes(self, w, h):
# for i, bbox in enumerate(self.listbox):
# x1 = int(int(bbox[0]) / 768 * w)
# y1 = int(int(bbox[1]) / 768 * h)
# x2 = int(int(bbox[2]) / 768 * w)
# y2 = int(int(bbox[3]) / 768 * h)
#
def _compare_labels(self):
label_dict = dict()
if not self.imageDir:
return None
for label_file in glob.glob(os.path.join(self.imageDir, '*.txt')):
with open(label_file, 'r') as f:
filename = ''.join([os.path.splitext(label_file)[0], '.jpg'])
label_dict[filename] = [tuple(l.split()) for l in f.readlines()]
return label_dict
def loadDir(self, dbg=False):
# get image list
self.imageDir = self.orig_images_path
# print self.imageDir
# print self.category
self.imageList = glob.glob(os.path.join(self.imageDir, '*.JPG'))
print(self.imageList)
if len(self.imageList) == 0:
print('No .JPG images found in the specified dir!')
return
# default to the 1st image in the collection
self.cur = 1
self.total = len(self.imageList)
# set up output dir
self.outDir = self.imageDir
if not os.path.exists(self.outDir):
os.mkdir(self.outDir)
self.loadImage()
print('%d images loaded from %s' % (self.total, self.orig_images_path))
def new_window(self, path):
self.newWindow = Toplevel(self.master)
# frame = Frame(self.newWindow)
self.new_panel = Canvas(self.newWindow, cursor='tcross')
self.new_img = ImageTk.PhotoImage(Image.open(path).resize((600, 800)))
self.new_panel.pack()
self.new_panel.config(width=max(self.new_img.width(), 400), height=max(self.new_img.height(), 400))
self.new_panel.create_image(0, 0, image=self.new_img, anchor=NW)
return self.new_panel
def loadImage(self):
# load image
imagepath = self.imageList[self.cur - 1]
self.img = Image.open(imagepath).resize((600, 800))
detect_image_path = os.path.join(self.detection_images_path, 'detect_'+os.path.basename(imagepath))
if not os.path.exists(detect_image_path):
mb.showerror(f'image {detect_image_path} doesn\' exists')
self.nextImage(save=False)
self.new_window(detect_image_path)
# cv2.imshow(f'{detect_image_path}', detect_image_path)
self.image = Image.open(path) # open image
self.width, self.height = self.image.size
# Put image into container rectangle and use it to set proper coordinates to the image
self.container = self.canvas.create_rectangle(0, 0, self.width, self.height, width=0)
self.show_image()
# load labels
self.clearBBox()
self.imagename = os.path.split(imagepath)[-1].split('.')[0]
labelname = self.imagename + '.txt'
self.labelfilename = os.path.join(self.outDir, labelname)
if os.path.exists(self.labelfilename):
with open(self.labelfilename) as f:
for (i, line) in enumerate(f):
# tmp = [int(t.strip()) for t in line.split()]
tmp = line.split()
self.bboxList.append(tuple(tmp))
tmpId = self.canvas.create_rectangle(int(tmp[0]), int(tmp[1]),
int(tmp[2]), int(tmp[3]),
width=2,
outline=COLORS[(len(self.bboxList) - 1) % len(COLORS)])
# print tmpId
self.bboxIdList.append(tmpId)
self.listbox.insert(END, '(%d, %d) -> (%d, %d)' % (int(tmp[0]), int(tmp[1]),
int(tmp[2]), int(tmp[3])))
self.listbox.itemconfig(len(self.bboxIdList) - 1,
fg=COLORS[(len(self.bboxIdList) - 1) % len(COLORS)])
def saveImage(self):
if self.newWindow:
self.newWindow.destroy()
with open(self.labelfilename, 'w') as f:
for bbox in self.bboxList:
f.write(' '.join(map(str, bbox)) + '\n')
print('Image No. %d saved' % (self.cur))
def mouseClick(self, event):
new_x, new_y = self.canvas.canvasx(event.x), self.canvas.canvasy(event.y)
if self.STATE['click'] == 0:
self.STATE['x'], self.STATE['y'] = new_x, new_y
else:
x1, x2 = min(self.STATE['x'], new_x), max(self.STATE['x'], new_x)
y1, y2 = min(self.STATE['y'], new_y), max(self.STATE['y'], new_y)
x, y = int(x2 / self.imscale), int(y2 / self.imscale)
print(self.imscale, (int(x1 / self.imscale), int(y1 / self.imscale), x, y))
im = self.image.crop((int(x1 / self.imscale), int(y1 / self.imscale), x, y))
im.show()
self.bboxList.append((int(x1 / self.imscale), int(y1 / self.imscale), x, y))
self.bboxIdList.append(self.bboxId)
self.bboxId = None
self.listbox.insert(END, '(%d, %d) -> (%d, %d)' % (x1, y1, x2, y2))
self.listbox.itemconfig(len(self.bboxIdList) - 1, fg=COLORS[(len(self.bboxIdList) - 1) % len(COLORS)])
self.STATE['click'] = 1 - self.STATE['click']
def mouseMove(self, event):
new_x, new_y = int(self.canvas.canvasx(event.x)), int(self.canvas.canvasy(event.y))
self.disp.config(text='x: %d, y: %d' % (new_x, new_y))
if self.canvas:
if self.hl:
self.canvas.delete(self.hl)
self.hl = self.canvas.create_line(0, new_y, self.canvas['width'], new_y, width=2)
if self.vl:
self.canvas.delete(self.vl)
self.vl = self.canvas.create_line(new_x, 0, new_x, self.canvas['height'], width=2)
if 1 == self.STATE['click']:
if self.bboxId:
self.canvas.delete(self.bboxId)
self.bboxId = self.canvas.create_rectangle(self.STATE['x'], self.STATE['y'],
new_x, new_y,
width=2,
outline=COLORS[len(self.bboxList) % len(COLORS)])
def cancelBBox(self, event):
if 1 == self.STATE['click']:
if self.bboxId:
self.canvas.delete(self.bboxId)
self.bboxId = None
self.STATE['click'] = 0
def delBBox(self):
sel = self.listbox.curselection()
if len(sel) != 1:
return
idx = int(sel[0])
self.canvas.delete(self.bboxIdList[idx])
self.bboxIdList.pop(idx)
self.bboxList.pop(idx)
self.listbox.delete(idx)
def clearBBox(self):
for idx in range(len(self.bboxIdList)):
self.canvas.delete(self.bboxIdList[idx])
self.listbox.delete(0, len(self.bboxList))
self.bboxIdList = []
self.bboxList = []
def prevImage(self, event=None):
self.saveImage()
if self.cur > 1:
self.cur -= 1
self.loadImage()
def nextImage(self, event=None, save=True):
if save:
self.saveImage()
if self.cur < self.total:
self.cur += 1
self.loadImage()
def gotoImage(self):
idx = int(self.idxEntry.get())
if 1 <= idx <= self.total:
self.saveImage()
self.cur = idx
self.loadImage()
path = 'data\\detections\\detect_1.jpg' # place path to your image here
root = Tk()
root.geometry('1280x720')
app = Zoom_Advanced(root, path=path)
root.mainloop()
I think the solution is in def MouseClick and in show_image. My thoughts is to use the same methods of extracting a tile (or a bbox) from original image as in show_image.
I tried to fo that but I didn't get any results. I don't understand how can I do that so I'm searching for the help here.
I did that with this code, where self.container is a rectangle around image with its width and height and self.imscale is scale of image.
...
new_x, new_y = self.canvas.canvasx(event.x), self.canvas.canvasy(event.y)
if self.STATE['click'] == 0:
self.STATE['x'], self.STATE['y'] = new_x, new_y
else:
x1, x2 = min(self.STATE['x'], new_x), max(self.STATE['x'], new_x)
y1, y2 = min(self.STATE['y'], new_y), max(self.STATE['y'], new_y)
self.canvas.create_text((x1 + x2) // 2, (y1 + y2) // 2, text=self.bbox_num)
bbox = self.canvas.bbox(self.container)
x1 = int((x1 - bbox[0]) / self.imscale)
y1 = int((y1 - bbox[1]) / self.imscale)
x2 = int((x2 - bbox[0]) / self.imscale)
y2 = int((y2 - bbox[1]) / self.imscale)

Creating a crop tool for tkinter: The cropping tool crops in other places

I'm creating in tkinter a Crop Tool that is similar in Photoshop. This code has a function that is supposed to crop a moveable image within the cropping box (2 in x 2 in, passport size, and so on). The problem is, the code often crops portions of the image outside the box.
For example, if I have a portrait and aimed the face at the rectangle, the code would crop the hat instead, or anywhere but the face.
I tried to use bbox, event objects, etc. but the measurements end up wrong. Please help me. Thanks.
Here is a partial code. Sorry if it's a quite lengthy.
from tkinter import *
from tkinter import ttk
import tkinter as tk
from tkinter import messagebox
from tkinter.filedialog import askopenfilename, asksaveasfilename
from PIL import Image, ImageTk
class PictureEditor:
# Quits when called
#staticmethod
# Opens an image
def open_app(self, event=None):
self.canvas.delete(ALL)
# Opens a window to choose a file=
self.openfile = askopenfilename(initialdir = # "Filename here")
if self.openfile:
with open(self.openfile) as _file:
# if file is selected by user, I'm going to delete
# the contents inside the canvas widget
self.canvas.delete(1.0, END)
self.im = Image.open(self.openfile)
self.image = ImageTk.PhotoImage(self.im)
self.a1 = self.canvas.create_image(0, 0, anchor=NW,
image=self.image, tags="image")
self.image_dim = self.canvas.bbox(self.a1)
self.imx = self.image_dim[0]
self.imy = self.image_dim[1]
# updating text widget
window.update_idletasks()
def on_drag(self, event):
# record the item and its location
self._drag_data["item"] = self.canvas.find_closest(event.x, event.y)[0]
self._drag_data["x"] = event.x
self._drag_data["y"] = event.y
self.origx = event.x
self.origy = event.y
def on_release(self, event):
# when I release the mouse, this happens
# reset the drag information
self._drag_data["item"] = None
self._drag_data["x"] = 0
self._drag_data["y"] = 0
self.newx = event.x
self.newy = event.y
# Measures mouse movement from one point to another
self.movex = self.origx - self.newx
self.movey = self.origy - self.newy
def on_motion(self, event):
# handles the dragging of an object
# compute how much the mouse has moved
delta_x = event.x - self._drag_data["x"]
delta_y = event.y - self._drag_data["y"]
# move the object the appropriate amount
self.canvas.move(self._drag_data["item"], delta_x, delta_y)
# record the new position
self._drag_data["x"] = event.x
self._drag_data["y"] = event.y
def draw(self, event, x1=None, y1=None,x2=None,y2=None):
# deleting contents of border, if any.
try:
self.canvas.delete(self.border)
except:
pass
# if an item is selected
selection = self.combo.get()
if selection == 'No Crop':
x1, y1, x2, y2 = None, None, None, None
if selection == '2 in x 2 in':
x1, y1, x2, y2 = self.imx, self.imy, self.imx + 200, self.imy + 200
if selection == '1 in x 1 in':
x1, y1, x2, y2 = self.imx, self.imy, self.imx + 100, self.imy + 100
if selection == 'Passport Size':
x1, y1, x2, y2 = self.imx, self.imy, self.imx + 132.28, self.imy
+170.079
if x1 != None or y1 != None or x2 != None or y2 != None:
self.dimensions = {"x1":x1, "y1":y1, "x2":x2, "y2":y2}
width = 5
self.border = self.canvas.create_rectangle(x1+ width, y1 +
width, x2 + width, y2 + width, width=width, outline="#ffffff", fill ="",
tags = "rectangle")
else:
pass
def crop(self, event=None):
# cropping the image
try:
self.crop_image = self.im.crop((self.dimensions["x1"] +
self.movex,
self.dimensions["y1"] + self.movey,
self.dimensions["x2"] + self.movex,
self.dimensions["y2"] + self.movey))
except:
print("cropping failed")
return 1
self.newly_cropped = ImageTk.PhotoImage(self.crop_image)
try:
new_image = self.canvas.create_image(120, 120,
image=self.newly_cropped)
print("Image is cropped")
except:
print("Cropping failed")
def __init__(self,window):
frame1 = Frame(bg='red')
frame1.pack(side=TOP, fill=X)
frame2height = 600
frame2width = 600
frame2 = Frame(window, bd=2, relief=SUNKEN)
frame2.pack(side=LEFT, fill=X)
frame3 = Frame(bg='green')
frame3.pack(side=LEFT, fill=X)
# Button that open pictures
open = Button(frame1, text='Open Pic', padx=20, command =
self.open_app)
open.pack(pady=5, padx=5, side=LEFT)
# Creating a canvas widget
self.canvas = tk.Canvas(frame2, height=frame2height,
width=frame2width,
bg='gray')
self.xsb = Scrollbar(frame2, orient="horizontal",
command=self.canvas.xview)
self.ysb = Scrollbar(frame2, orient="vertical",
command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.ysb.set,
xscrollcommand=self.xsb.set)
self.canvas.configure(scrollregion=(0, 0, 1000, 1000))
# keeps track of data being dragged
self._drag_data = {"x": 0, "y": 0, "item": None}
# creating image and crop border
self.canvas.tag_bind("image","<ButtonPress-1>", self.on_drag)
self.canvas.tag_bind("image","<ButtonRelease-1>", self.on_release)
self.canvas.tag_bind("image","<B1-Motion>", self.on_motion)
# widget positions in frame2
self.xsb.pack(side=BOTTOM, fill=X)
self.canvas.pack(side=LEFT)
self.ysb.pack(side=LEFT, fill=Y)
self.combo = ttk.Combobox(frame1)
# Combobox selections
self.combo['values'] = ('No Crop', '2 in x 2 in', '1 in x 1 in',
'Passport Size')
self.combo.current(0)
self.combo.pack(pady=5, padx=5, side=LEFT)
self.combo.bind("<Button-1>", self.draw)
# Button that crops picture
self.crop = Button(frame1, text='Crop Pic', padx=20,
command=self.crop)
self.crop.pack(pady=5, padx=5, side=LEFT)
# this window has all the properties of tkinter.
# .Tk() declares this variable as the frame
window = tk.Tk()
# .title() will input whatever title you want for the app
window.title("ID Picture Generator")
# .geometry() sets the size in pixels of what the window will be
window.geometry("800x600")
app = PictureEditor(window)
# runs everything inside the window
window.mainloop()

Basic image editing on Python tkinter working but unwanted image scrolling course when dragging mouse

The following code produces a nice Canvas with an image and I can draw a square on top of it. However:
a) I can't get the Canvas to not scroll.
b) I only want the image to appear and nothing else and can't get the sizes right
As you will see, I have even tried to stop the scrolling but it does not work all the time. In addition the image is never fully aligned with the Canvas nor the window even though I set the sizes to be the same for the three (root, canvas and image).
Here is the code (partly taken already from another example with some portions commented out):
try:
from PIL import Image
except ImportError:
import Image
from PIL import ImageTk
try:
import Tkinter as tk # Python2
except ImportError:
import tkinter as tk # Python3s
import Tkinter
from Tkinter import *
import PIL as PILAll
class ExampleApp(Frame):
def __init__(self,master):
Frame.__init__(self,master=None)
self.x = 0
self.y = 0
self.canvas = Canvas(self, cursor="cross", width=640, height=480, confine=True, scrollregion=(10, 10, 10, 10), relief="groove", bg="blue")# and I have experimented with a few other options
#self.sbarv=Scrollbar(self,orient=VERTICAL)
#self.sbarh=Scrollbar(self,orient=HORIZONTAL)
#self.sbarv.config(command=self.canvas.yview)
#self.sbarh.config(command=self.canvas.xview)
self.canvas.config()#yscrollcommand=self.sbarv.set)
self.canvas.config()#xscrollcommand=self.sbarh.set)
self.canvas.config(scrollregion=self.canvas.bbox(ALL))
self.canvas.grid(row=0,column=0,sticky=N+S+E+W)
#self.sbarv.grid(row=0,column=1,stick=N+S)
#self.sbarh.grid(row=1,column=0,sticky=E+W)
self.canvas.bind("<ButtonPress-1>", self.on_button_press)
self.canvas.bind("<B1-Motion>", self.on_move_press)
self.canvas.bind("<ButtonRelease-1>", self.on_button_release)
self.canvas.bind("<Leave>", self.on_button_leave)
self.canvas.bind("<Enter>", self.on_button_enter)
self.canvas.bind("<Double-Button-1>", self.on_double_click)
self.canvas.create_line(0, 0, 200, 100)
self.canvas.create_line(0, 100, 200, 0, fill="red", dash=(4, 4))
self.canvas.create_rectangle(50, 25, 150, 75, fill="blue")
self.rect = None
self.text = None
self.start_x = None
self.start_y = None
self.im = PILAll.Image.open("../../" + "image6.JPG")
self.wazil,self.lard=self.im.size
self.canvas.config() #scrollregion=(0,0,self.wazil,self.lard))
self.tk_im = ImageTk.PhotoImage(self.im)
self.canvas.create_image(0,0,anchor="nw",image=self.tk_im)
out_of_scope = 1
def on_button_leave(self, event):
self.out_of_scope = 2
print "out_of_scope....", self.out_of_scope
def on_button_enter(self, event):
print("entering...")
self.out_of_scope = 1
def on_double_click(self, event):
print("double click")
def on_button_press(self, event):
# save mouse drag start position
self.start_x = self.canvas.canvasx(event.x)
self.start_y = self.canvas.canvasy(event.y)
# create rectangle if not yet exist
if not self.rect:
if self.out_of_scope == 1:
self.rect = self.canvas.create_rectangle(self.x, self.y, 1, 1, outline='blue', fill='yellow') #since it's only created once it always remains at the bottom
def get_out_of_scope(self, x, y):
return self.out_of_scope
def on_move_press(self, event):
curX = self.canvas.canvasx(event.x)
curY = self.canvas.canvasy(event.y)
var=self.get_out_of_scope(event.x, event.y)
print(var, event.x, event.y)
if var == 1:
w, h = self.canvas.winfo_width(), self.canvas.winfo_height()
if event.x > 0.9*w:
self.canvas.xview_scroll(1, 'units')
elif event.x < 0.1*w:
self.canvas.xview_scroll(-1, 'units')
if event.y > 0.9*h:
self.canvas.yview_scroll(1, 'units')
elif event.y < 0.1*h:
self.canvas.yview_scroll(-1, 'units')
# expand rectangle as you drag the mouse
self.canvas.coords(self.rect, self.start_x, self.start_y, curX, curY)
def on_button_release(self, event):
print(event.x, event.y)
pass
root=Tk()
root.geometry("640x480")
app = ExampleApp(root)
app.grid()
root.mainloop()
I think your code would benefit from beingreviewed but I will try to limit myself to the question...
If the canvas needs to be the same size as the image why is it constructed with width=640, height=480? You figure out the width and height of the image further down:
self.im = PILAll.Image.open("../../" + "image6.JPG")
self.wazil,self.lard=self.im.size
(interesting variable name choice btw) so if self.wazil and self.lard represent the width and height of the image why don't you make that the width and height of the canvas?
self.im = PILAll.Image.open("../../" + "image6.JPG")
self.wazil,self.lard=self.im.size
self.canvas = Canvas(self, width=self.wazil, height=self.lard) #, ...)
then the canvas will be the correct size but the root window is still forcing itself to be 640x480 from:
root.geometry("640x480")
but since widgets will automatically scale themselves to the contents you can just comment that line out and it should be the correct size.
#root.geometry("640x480")
I should note that I was experiencing some very odd behaviour about the position of the image being 3 pixels too high and 3 pixels to the left, drawing the image with:
self.canvas.create_image(3,3,anchor="nw",image=self.tk_im)
fixed it for me but I have no idea why...
As for the scrolling you removed the parts about the scroll bars but you left in this in on_move_press:
w, h = self.canvas.winfo_width(), self.canvas.winfo_height()
if event.x > 0.9*w:
self.canvas.xview_scroll(1, 'units')
elif event.x < 0.1*w:
self.canvas.xview_scroll(-1, 'units')
if event.y > 0.9*h:
self.canvas.yview_scroll(1, 'units')
elif event.y < 0.1*h:
self.canvas.yview_scroll(-1, 'units')
This is the section of code that is occasionally scrolling the canvas (happened when I tried to resize the window) so again you can comment that section out and it shouldn't scroll anymore.
Last note which is unrelated to question, you have:
def __init__(self,master):
Frame.__init__(self,master=None)
But I'm pretty sure you mean to have:
def __init__(self,master=None):
Frame.__init__(self,master)
since the first way you require a master argument but do not pass it to Frame.__init__. When Frame.__init__ receives a master of None it just uses the Tk instance which in your case is the same thing but if you used any other master it would cause very odd issues.

The item configure method didn't work in Tkinter

I tried to use the Tkinter library for my small project in python. I create a 500 by 500 square with 10000 small square in it.
And I want each small square turns black when user click on it. Can someone please tell me why, I would really appreciate it. Here is the graphics code:
from Tkinter import *
from button import *
class AppFrame(Frame):
def __init__(self):
self.root = Tk()
self.root.geometry = ("1000x1000")
self.f = Frame(self.root, relief = 'sunken', width = 600, height = 600)
self.w = Canvas(self.f,width = 505, height =505)
##get the x, y value whenever the user make a mouse click
self.w.bind("<Button-1>", self.xy)
self.bolist = []
for k in range(1,101):
for i in range(1, 101):
button = Buttons(self.w, i * 5, k * 5, i * 5 + 5, k * 5 + 5)
self.bolist.append(button)
self.f.grid(column =0, columnspan = 4)
self.w.grid(column = 0)
self.root.mainloop()
def xy (self, event):
self.x, self.y = event.x, event.y
print (self.x, self.y)
##check each button if it's clicked
for hb in self.bolist:
if hb.clicked(self.x, self.y):
print ("hurry")
hb.activate()
And
##button.py
from Tkinter import *
class Buttons:
def __init__(self,canvas,bx,by,tx,ty):
self.canvas = canvas
self.rec = canvas.create_rectangle((bx,by,tx,ty),fill = "lightgray",
activefill= 'black', outline = 'lightgray')
self.xmin = bx
self.xmax = tx
self.ymin = by
self.ymax = ty
##print (bx, by, tx, ty)
def clicked(self, px, py):
return (self.active and self.xmin <= px <= self.xmax and
self.ymin <= py <= self.ymax)
def activate(self):
self.canvas.itemconfigure(slef.rec, fill = 'black')
self.active = True
The problem is that you don't initialize the active attribute, so it doesn't exist until the cell becomes active. To fix that, add self.active = False inside the __init__ method of Buttons.
You also have a typo in this line (notice you use slef rather than self):
self.canvas.itemconfigure(slef.rec, fill = 'black')
Instead of a global binding on the canvas, it would be more efficient to set a binding on each individual rectangle. You can then use the binding to pass the instance of the Buttons class to the callback. This way you don't have to iterate over several thousand widgets looking for the one that was clicked on.
To do this, use the tag_bind method of the canvas. You can make it so that your main program passes in a reference to a function to call when the rectangle is clicked, then the binding can call that method and pass it a reference to itself.
For example:
class Buttons:
def __init__(self,canvas,bx,by,tx,ty, callback):
...
self.rec = canvas.create_rectangle(...)
self.canvas.tag_bind(self.rec, "<1>",
lambda event: callback(self))
...
class AppFrame(Frame):
def __init__(...):
...
button = Buttons(..., self.callback)
...
def callback(self, b):
b.activate()
Here, I looked at your code, debugged it, and made some adjustments. It works now.
Just keep both the scripts in one folder and run your AppFrame script (the second one in this answer)
##button.py
from Tkinter import *
class Buttons:
def __init__(self,canvas,bx,by,tx,ty):
self.canvas = canvas
self.rec = canvas.create_rectangle((bx,by,tx,ty),fill = "lightgray", activefill= 'black', outline = 'lightgray')
self.xmin = bx
self.xmax = tx
self.ymin = by
self.ymax = ty
##print (bx, by, tx, ty)
def clicked(self, px, py):
return (self.xmin <= px <= self.xmax and
self.ymin <= py <= self.ymax)
def activate(self):
self.canvas.itemconfigure(self.rec, fill = 'black')
AND
from Tkinter import *
from button import *
class AppFrame(Frame):
def __init__(self):
self.root = Tk()
self.root.geometry = ("1000x1000")
self.f = Frame(self.root, relief = 'sunken', width = 600, height = 600)
self.w = Canvas(self.f,width = 505, height =505)
##get the x, y value whenever the user make a mouse click
self.w.bind("<Button-1>", self.xy)
self.bolist = []
for k in range(1,101):
for i in range(1, 101):
button = Buttons(self.w, i * 5, k * 5, i * 5 + 5, k * 5 + 5)
self.bolist.append(button)
self.f.grid(column =0, columnspan = 4)
self.w.grid(column = 0)
self.root.mainloop()
def xy (self, event):
self.x, self.y = event.x, event.y
print (self.x, self.y)
##check each button if it's clicked
for hb in self.bolist:
if hb.clicked(self.x, self.y):
print ("hurry")
hb.activate()
newApp = AppFrame()

Categories