Tkinter - Bild in canvas wird nicht immer angezeigt

Fragen zu Tkinter.
Antworten
Der_Kaiser
User
Beiträge: 3
Registriert: Donnerstag 22. Juli 2021, 22:14

Hallo zusammen,
ich habe ein kleines Problem mit dem Anzeigen von einem Bild in einem Canvas bei Tkinter. Generell bin ich ziemlich neu bei Tkinter und habe generell wenig praktische Erfahrung mit GUI-Programmierung, was man wahrscheinlich auch am Code sehen wird.
Kurz zum Programm, bei dem es generell ersteinmal um eine einfache Kontrastoptimierung geht. Ich will eine Bitmap (Grauwerte) einlesen die unbegrenzte (theoretisch, praktisch 16Bit) Werte annehmen kann, dann mithilfe von zwei Schiebereglern den minimalen und maximalen Wert definieren, zwischen denen dann linear auf 8bit umgewandelt wird und alle anderen Werte auf die beiden Randwerte gesetzt werden.
Das hab ich so weit auch alles hinbekommen und mein Testbild wird am Anfang auch richtig angezeigt, doch sobald ich meine Schieberegler verwende, verschwindet das Bild und es wird gar nichts mehr angezeigt.
Wenn ich jetzt aber in meiner Funktion update_image_contrast() die auskommentierte Zeile dazunehme, funktioniert alles so wie ich es haben will, es treten bloß immer AttributeError auf.
Hat jemand eine Idee woran das liegt und wie man das Programm umschreiben kann dass nicht die ganze Zeit AttributeError auftritt?

Code: Alles auswählen

import cv2
import tkinter as tk
import numpy as np
from tkinter import ttk
from PIL import Image, ImageTk


def get_current_max_value():
    return int(current_value_max.get())

def get_current_min_value():
    return int(current_value_min.get())
    
def rescale_img(image, definedMin, definedMax):
    image = (255.0/(float(definedMax) - float(definedMin))) * np.subtract(image, definedMin)
    image[image < 0.0] = 0.0
    image[image > 255.0] = 255.0
    return image

def update_image_contrast(max_value, min_value, from_min = True):
    if from_min and min_value > max_value:
        slider_max.set(min_value)
    if not(from_min) and max_value < min_value:
        slider_min.set(max_value)
    var_array = rescale_img(np.copy(test_array), min_value - 0.1, max_value)
    var_img = ImageTk.PhotoImage(image=Image.fromarray(var_array), master=Canvas_Frame)
    canvas.itemconfig(img_on_canvas, image = var_img)
    #Canvas_Frame.itemconfig(width=canvas_width, height=canvas_height)


def slider_max_changed(event):
    input_max.delete(0,tk.END)
    input_max.insert(0,str(get_current_max_value()))
    update_image_contrast(get_current_max_value(), get_current_min_value(), from_min = False)
    
def slider_min_changed(event):
    input_min.delete(0,tk.END)
    input_min.insert(0,str(get_current_min_value()))
    update_image_contrast(get_current_max_value(), get_current_min_value(), from_min = True)
    
    

window_app = tk.Tk()
window_app.title("Test Anordnung")
window_app.resizable(0,0)

# BMP size
current_value_max = tk.DoubleVar()
current_value_min = tk.DoubleVar()

# Canvas Frame
canvas_width = 975
canvas_height = 643
Canvas_Frame = tk.Frame(window_app, width=canvas_width, height=canvas_height, bg="white")
Canvas_Frame.grid(row=0, column=0)

# load testpicture 
test_array = cv2.imread("./test_01.bmp", -1)
max_img_return = np.max(test_array)
min_img_return = np.min(test_array)

# canvas 
canvas = tk.Canvas(Canvas_Frame, width=canvas_width, height=canvas_height)
test_img = ImageTk.PhotoImage(image=Image.fromarray(test_array), master=Canvas_Frame)
img_on_canvas = canvas.create_image((canvas_width / 2, canvas_height / 2), image=test_img)
canvas.pack(fill=tk.BOTH, expand=True)

# Configuration Frame
Config_Frame = tk.Frame(window_app, width=300)
Config_Frame.grid(row=0, column=1)

#### max widgets #############################################################

# slider label max
slider_max_label = ttk.Label(Config_Frame, text='Maximum:')
slider_max_label.grid(row=0,column=0, sticky='ne')

# input max
input_max = tk.Entry(Config_Frame, bd=5, width=10, justify="right")
input_max.grid(row=0,column=2,sticky='n')
input_max.insert(0,str(max_img_return))

# slider max
slider_max = ttk.Scale(Config_Frame, from_=min_img_return, to=max_img_return, orient='horizontal', 
                       command=slider_max_changed, variable=current_value_max)
slider_max.grid(row=0,column=1, sticky='ne')
#slider_max.set(max_img_return)

#### min widgets #############################################################

# slider label min
slider_min_label = ttk.Label(Config_Frame, text='Minium:')
slider_min_label.grid(row=1,column=0, sticky='ne')

# input min
input_min = tk.Entry(Config_Frame, bd=5, width=10, justify="right")
input_min.grid(row=1,column=2,sticky='n')
input_min.insert(0,str(min_img_return))

# slider min
slider_min = ttk.Scale(Config_Frame, from_=min_img_return, to=max_img_return, orient='horizontal', 
                       command=slider_min_changed, variable=current_value_min)
slider_min.grid(row=1,column=1, sticky='ne')
#slider_min.set(min_img_return)

window_app.mainloop()
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@Der_Kaiser,

es ist immer besser die gesamte Fehlermeldung zu posten.

Code: Alles auswählen

Canvas_Frame = tk.Frame(window_app, width=canvas_width, height=canvas_height, bg="white")
Canvas_Frame ist ein tk.Frame und hat kein Attribut 'itemconfig'. Daher kommt wahrscheinlich der Fehler.

canvas is ein tk.Canvas

Code: Alles auswählen

canvas = tk.Canvas(Canvas_Frame, width=canvas_width, height=canvas_height)
Ein tk.Canvas hat eine Methode 'itemconfig()'. Daher dürfte das noch funktionieren.
Der_Kaiser
User
Beiträge: 3
Registriert: Donnerstag 22. Juli 2021, 22:14

@rogerb

Das mit 'itemconfig()' habe ich schon benutzt um mein angezeigtes 'image' zu aktualisieren, jetzt fehlt noch dass der Canvas_Frame dieses Bild richtig anzeigt.
Ich hätte glaube ich meine Frage besser stellen müssen, weil wenn ich 'config()' anstatt 'itemconfig()' verwende, dann ändert sich wirklich die Größe von meinem Canvas, was ich ja eigentlich gar nicht direkt haben will, und mein Bild wird nicht aktualisiert bzw. wird nur weiß angezeigt. Hier habe ich die Funktion eigentlich nur genutzt mit der Idee, dass sich dadurch der Canvas_Frame aktualisiert und dass Bild richtig angezeigt wird, was, wenn man es richtig definiert, wie gesagt nicht das gewünschte Resultat bringt.
Generell aktualisiert sich mein Bild nur richtig, wenn ich ein AttributError auslöse. Zudem muss der Canvas_Frame gar nicht existieren, dass das richtige Bild angezeigt wird, weil dann ja ein 'NameError: name 'Canvas_Frame_1' is not defined' ausgelöst wird, was anscheinend die gleiche Wirkung wie ein AttributError auslöst. In beiden Fällen funktioniert das Programm so wie es sollte, bloß mit den Fehlermeldungen.
Also zusammengefasst, warum benötige ich ein AttributError oder NameError dass mein Bild richtig aktualisiert wird?
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Alles was Funktionen brauchen, müssen sie auch über ihre Argumente bekommen. Ab einer bestimmten Komplexität von GUIs führt das zwangsläufig dazu, dass man Klassen definieren muß.
Für `rescale_img` muß man keine Kopie des Bildes übergeben, da eh ein neues erzeugt wird. Mit numpy.clip läßt sich die Funktion viel einfacher schreiben.
Das was in `update_image_contrast` mit from_min gemacht wird, gehört eigentlich gar nicht in diese Funktion, sondern in die changed-Funktionen.
Python muß sich eine Referenz auf das Bild merken, damit es nicht vom Garbage-Collector gelöscht wird. Mangels besseren Ortes kann man das z.B. an ein Attribut des canvas binden.

Code: Alles auswählen

def rescale_img(image, defined_min, defined_max):
    scaling = 255.0 / (defined_max - defined_min)
    return np.clip(scaling * (image - defined_min), 0, 255)

def update_image_contrast(canvas, img_on_canvas, image, max_value, min_value):
    var_array = rescale_img(image, min_value - 0.1, max_value)
    var_img = ImageTk.PhotoImage(image=Image.fromarray(var_array), master=Canvas_Frame)
    canvas.var_img = var_img
    canvas.itemconfig(img_on_canvas, image=var_img)

def slider_max_changed(event, input_max, current_value_min, current_value_max, canvas, img_on_canvas, image):
    min_value = int(current_value_min.get())
    max_value = int(current_value_max.get())
    if max_value < min_value:
        slider_min.set(max_value)
    input_max.delete(0, tk.END)
    input_max.insert(0, max_value)
    update_image_contrast(canvas, img_on_canvas, image, max_value, min_value)
    
def slider_min_changed(event, input_min, current_value_min, current_value_max, canvas, img_on_canvas, image):
    min_value = int(current_value_min.get())
    max_value = int(current_value_max.get())
    if min_value > max_value:
        slider_max.set(min_value)
    input_min.delete(0, tk.END)
    input_min.insert(0, min_value)
    update_image_contrast(canvas, img_on_canvas, image, max_value, min_value)
Hier merkt man schon, dass die Anzahl der Parameter viel zu groß ist, und man eigentlich eine Klasse braucht.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wenn es dir um Bildbearbeitung geht, und visuelle Inspektion, statt einer ausgefeilten GUI, dann würde ich zur opencv und ihrem highgui Modul raten. Das ist einfacher zu bedienen für Programmierneulinge, weil nicht Ereignis-basiert.
Der_Kaiser
User
Beiträge: 3
Registriert: Donnerstag 22. Juli 2021, 22:14

@Sirius

Also dass mit den vielen Parameterübergaben habe ich dann selber gemerkt wie unpraktisch sowas ist, habe mir jetzt Klassen in Python angeschaut und mein Programm umgeschrieben, jetzt funktioniert alles so wie ich es haben will.
Das mit numpy.clip hab ich auch in betracht gezogen aber bei meinen Laufzeitvergleichen war meine Methode komischerweise immer schneller als die numpy.clip Variante, warum auch immer.
Antworten