I am pretty familiar with python, however right now I am creating my first program with user interface using tkinter. I have a grid layout in my scenario in column 3, row 1 (it is the second row and fourth column I am including row/column 0 here) is a list with items. Initially column 4, row 1 is empty. At later stages in the program it is filled with data (in my scenario coordinates that correspond with the labels from column 3)
below both grid items mentioned above I have a button with a columnspan of 2. however the button breaks when the data is inserted to column 4, row 1.
I thought I could resolve the issue by displaying some empty text (" ") in column 4 in the beginning, however the problem persists. I also tried to limit row 1 growth with using rowconfigure, this approach also failed.
Any ideas how I can avoid the row from growing? I tried breaking down the problem and added the code causing the problem below:
import os
import tkinter as tk
from tkinter import font,messagebox
from PIL import Image, ImageTk, ExifTags
import math
class GUIApp:
def __init__(self, root):
self.root = root
self.root.title("Winkelmessung")
# Define the custom font
self.helvetica_font = font.Font(family="Helvetica", size=16, weight="bold")
# Create a dictionary to store body parts and their coordinates
self.selected_body_parts = {}
# Create a list of body parts
self.body_parts = ["Fußgelenk", "Knie", "Hüfte", "Schulter", "Ohr", "Ellenbogen", "Handgelenk"]
self.current_body_part_index = 0 # To keep track of the current body part index
# Create a list of joint angles
self.joint_angles = ["Knie", "Hüfte", "Schulter", "Nacken", "Ellenbogen"]
# Add the coordinates selected instance variable
self.coordinates_selected = 0
# Initialize the list to store marked coordinates for undo functionality
self.marked_coordinates = []
# Load and display the oldest image
self.display_oldest_image()
self.create_grid()
self.create_buttons()
def create_grid(self):
# Create custom font
helvetica_font = font.Font(family="Helvetica", size=16, weight="bold")
screen_height=self.root.winfo_screenheight()
new_height = int(screen_height -210)
image_width = int(new_height*0.666)
column1u2width= image_width/2
row3_height= (new_height-275)
# Create labels with different background colors
label1 = tk.Label(self.root, text="Manuelle Bildbearbeitung", font=helvetica_font)
label2 = tk.Label(self.root, text="KI-basierte Bearbeitung", font=helvetica_font)
# Set column weights
self.root.columnconfigure(1, minsize=column1u2width)
self.root.columnconfigure(2, minsize=column1u2width)
self.root.columnconfigure(3, minsize=125)
self.root.columnconfigure(4, minsize=125)
# Set row weights
self.root.rowconfigure(1, minsize=150)
self.root.rowconfigure(2, minsize=125)
self.root.rowconfigure(3, minsize=row3_height)
# Create a label to display the list of body parts
body_parts_label = tk.Label(self.root, text="\n".join(self.body_parts), anchor="nw", justify="left")
body_parts_label.grid(row=1, column=3, rowspan=len(self.body_parts), sticky="nsew", padx=5, pady=10)
# Create a label to display the list of joint angles in row 2, column 3
joint_angles_label = tk.Label(self.root, text="\n".join(self.joint_angles), anchor="nw", justify="left")
joint_angles_label.grid(row=2, column=3, rowspan=len(self.joint_angles), sticky="nsew", padx=5, pady=10)
def display_oldest_image(self):
# Define the path to the "Unbearbeitet" folder
folder_path = "Unbearbeitet"
# Get a list of image files in the folder
image_files = [f for f in os.listdir(folder_path) if f.lower().endswith((".jpg", ".png"))]
# Load the oldest image using Pillow
oldest_image_path = os.path.join(folder_path, image_files[0])
oldest_image = Image.open(oldest_image_path)
# Rotate the image based on Exif orientation
for orientation in ExifTags.TAGS.keys():
if ExifTags.TAGS[orientation] == 'Orientation':
break
exif = oldest_image._getexif()
if exif is not None:
exif = dict(exif.items())
if orientation in exif:
if exif[orientation] == 3:
oldest_image = oldest_image.rotate(180, expand=True)
elif exif[orientation] == 6:
oldest_image = oldest_image.rotate(270, expand=True)
elif exif[orientation] == 8:
oldest_image = oldest_image.rotate(90, expand=True)
# Calculate the dimensions for resizing while maintaining aspect ratio
screen_height = self.root.winfo_screenheight()
new_height = int(screen_height -210)
aspect_ratio = oldest_image.width / oldest_image.height
new_width = int(new_height * aspect_ratio)
# Resize the image to fit the calculated dimensions
resized_image = oldest_image.resize((new_width, new_height), Image.LANCZOS)
# Create a Canvas widget to display the image
self.canvas = tk.Canvas(self.root)
self.canvas.grid(row=1, column=1, rowspan=4, columnspan=2, sticky="nsew") # Span 4 rows
# Display the image on the Canvas
self.photo = ImageTk.PhotoImage(resized_image)
self.canvas.create_image(0, 0, anchor="nw", image=self.photo)
# Bind click event to the Canvas
self.canvas.bind("<Button-1>", self.image_click_handler)
def image_click_handler(self, event):
# Check if all 7 coordinates have been selected
if self.coordinates_selected >= len(self.body_parts):
return
# Get the clicked coordinates
x, y = event.x, event.y
# Get the current body part from the list
current_body_part = self.body_parts[self.current_body_part_index]
# Store the body part and its coordinates in the dictionary
self.selected_body_parts[(x, y)] = current_body_part
# Mark the selected body part with a dot and label on the canvas
dot_radius = 5
dot_color = "red"
dot_id = self.canvas.create_oval(x - dot_radius, y - dot_radius, x + dot_radius, y + dot_radius, fill=dot_color)
label_id = self.canvas.create_text(x, y + dot_radius + 10, text=current_body_part, fill=dot_color)
# Update the current body part index
self.current_body_part_index += 1
if self.current_body_part_index >= len(self.body_parts):
self.current_body_part_index = 0
# Update the list of body parts in column 4, row 1 (coordinates)
coordinates_list = [f"({coord[0]}, {coord[1]})" for coord, part in self.selected_body_parts.items()]
coordinates_label = tk.Label(self.root, text="\n".join(coordinates_list), anchor="nw", justify="left")
coordinates_label.grid(row=1, column=4, rowspan=len(coordinates_list), sticky="nsew", padx=5, pady=10)
# Append the marked coordinates to the list for undo functionality
self.marked_coordinates.append((dot_id, label_id, (x, y), current_body_part))
# Update the number of coordinates selected
self.coordinates_selected += 1
# Check if all coordinates have been selected
if self.coordinates_selected >= len(self.body_parts):
# Calculate joint angles
self.calculate_joint_angles()
# Disable further clicks
self.canvas.unbind("<Button-1>")
def calculate_joint_angles(self):
# Check if all required body parts are marked
required_parts = ["Knie", "Hüfte", "Schulter", "Ohr", "Ellenbogen", "Handgelenk"]
missing_parts = [part for part in required_parts if part not in self.selected_body_parts.values()]
if missing_parts:
# Display a message indicating missing body parts
joint_angles_label = tk.Label(self.root, text=f"Mark the missing body parts: {', '.join(missing_parts)}", anchor="nw", justify="left")
joint_angles_label.grid(row=2, column=4, sticky="nsew", padx=5, pady=10)
else:
# Retrieve the coordinates of the body parts for calculation
foot_coords = next(coord for coord, part in self.selected_body_parts.items() if part == "Fußgelenk")
knee_coords = next(coord for coord, part in self.selected_body_parts.items() if part == "Knie")
hip_coords = next(coord for coord, part in self.selected_body_parts.items() if part == "Hüfte")
shoulder_coords = next(coord for coord, part in self.selected_body_parts.items() if part == "Schulter")
ear_coords = next(coord for coord, part in self.selected_body_parts.items() if part == "Ohr")
elbow_coords = next(coord for coord, part in self.selected_body_parts.items() if part == "Ellenbogen")
wrist_coords = next(coord for coord, part in self.selected_body_parts.items() if part == "Handgelenk")
# Calculate the joint angles based on the coordinates
angles = {
"Knie": self.calculate_angle(foot_coords, knee_coords, hip_coords),
"Hüfte": self.calculate_angle(knee_coords, hip_coords, shoulder_coords),
"Schulter": self.calculate_angle(hip_coords, shoulder_coords, elbow_coords),
"Nacken": self.calculate_angle(hip_coords, shoulder_coords, ear_coords),
"Ellenbogen": self.calculate_angle(shoulder_coords, elbow_coords, wrist_coords),
}
# Update the joint angles list
self.joint_angles = [f"{angle:.2f}°" for part, angle in angles.items()]
# Update the label in row 2, column 4
joint_angles_label = tk.Label(self.root, text="\n".join(self.joint_angles), anchor="nw", justify="left")
joint_angles_label.grid(row=2, column=4, rowspan=len(self.joint_angles), sticky="nsew", padx=5, pady=10)
def calculate_angle(self, start_coord, middle_coord, end_coord):
# Calculate the vectors representing the lines between the points
vector1 = (middle_coord[0] - start_coord[0], middle_coord[1] - start_coord[1])
vector2 = (middle_coord[0] - end_coord[0], middle_coord[1] - end_coord[1])
# Calculate the dot product of the vectors
dot_product = vector1[0] * vector2[0] + vector1[1] * vector2[1]
# Calculate the magnitudes of the vectors
magnitude1 = math.sqrt(vector1[0] ** 2 + vector1[1] ** 2)
magnitude2 = math.sqrt(vector2[0] ** 2 + vector2[1] ** 2)
# Calculate the angle in radians using the arccosine of the dot product divided by the product of magnitudes
angle_rad = math.acos(dot_product / (magnitude1 * magnitude2))
# Convert the angle from radians to degrees
angle_deg = math.degrees(angle_rad)
return angle_deg
def create_buttons(self):
# Button to reset
reset_button = tk.Button(self.root, text="Zurücksetzen", command=self.reset_function, cursor="hand")
reset_button.grid(row=5, column=1, sticky="ew", padx=5, pady=10)
# Button to undo
undo_button = tk.Button(self.root, text="Rückgängig", command=self.undo_function, cursor="hand")
undo_button.grid(row=5, column=2, sticky="ew", padx=5, pady=10)
# Button for "Speichern und weiter"
save_and_continue_button = tk.Button(self.root, text="Speichern und weiter", command=self.save_and_continue, cursor="hand")
save_and_continue_button.grid(row=3, column=3, columnspan=2, sticky="new", padx=5, pady=0, ipady=10)
def ki_analysis_function(self):
# Implement the behavior of the KI-basierte Bildanalyse button here
pass
def live_analysis_function(self):
# Implement the behavior of the Live-Analyse (KI-basiert) button here
pass
def reset_function(self):
# Clear the marked coordinates and selected body parts
self.selected_body_parts.clear()
self.coordinates_selected = 0
# Remove all dots and labels added to the canvas
for dot_id, label_id, _, _ in self.marked_coordinates:
self.canvas.delete(dot_id)
self.canvas.delete(label_id)
# Clear the coordinates list label in column 4, row 1 (coordinates)
coordinates_label = tk.Label(self.root, text="", anchor="nw", justify="left")
coordinates_label.grid(row=1, column=4, rowspan=1, sticky="nsew", padx=5, pady=10)
# Clear the joint angles label in column 4, row 2 (remove this part)
joint_angles_label = tk.Label(self.root, text="", anchor="nw", justify="left")
joint_angles_label.grid(row=2, column=4, rowspan=1, sticky="nsew", padx=5, pady=10)
# Re-enable clicking on the canvas
self.canvas.bind("<Button-1>", self.image_click_handler)
# Clear the marked coordinates list for undo functionality
self.marked_coordinates = []
# Reset the current body part index to 0
self.current_body_part_index = 0
def undo_function(self):
if self.marked_coordinates:
# Get the last marked coordinates and canvas element IDs
dot_id, label_id, (x, y), body_part = self.marked_coordinates.pop()
# Remove the marked dot and label from the canvas
self.canvas.delete(dot_id)
self.canvas.delete(label_id)
# Remove the coordinates from the selected body parts dictionary
for coord in self.selected_body_parts.copy():
if coord == (x, y):
del self.selected_body_parts[coord]
# Update the list of coordinates in column 4, row 1 (coordinates)
coordinates_list = [f"({coord[0]}, {coord[1]})" for coord, part in self.selected_body_parts.items()]
# Adjust rowspan to be at least 1 to avoid Tkinter error
coordinates_label = tk.Label(self.root, text="\n".join(coordinates_list), anchor="nw", justify="left")
coordinates_label.grid(row=1, column=4, rowspan=max(1, len(coordinates_list)), sticky="nsew", padx=5, pady=10)
# Decrement the number of coordinates selected
self.coordinates_selected -= 1
# Set the current body part index to the index of the undone coordinate
if self.current_body_part_index == 0:
self.current_body_part_index = len(self.body_parts) - 1
else:
self.current_body_part_index -= 1
# Re-enable clicking on the canvas
self.canvas.bind("<Button-1>", self.image_click_handler)
# Add this method to handle the "Speichern und weiter" button click
def save_and_continue(self):
if self.coordinates_selected < len(self.body_parts):
# If less than 7 points have been marked, show a popup
messagebox.showwarning("Information", "Bitte markieren Sie zuerst 7 Punkte.")
else:
# If 7 points have been marked, proceed with the specified functions
self.save_excel()
self.move_image()
self.next_image()
def save_excel(self):
# Implement the save_excel function here
pass
def move_image(self):
# Implement the move_image function here
pass
def next_image(self):
# Implement the next_image function here
pass
if __name__ == "__main__":
root = tk.Tk()
app = GUIApp(root)
root.mainloop()