Modul für GUI-Programmierung

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
barisoezcan
User
Beiträge: 73
Registriert: Freitag 15. März 2013, 19:38

Also minimaler hab ich's nicht geschafft :D

Code: Alles auswählen

import numpy
from PIL import Image
import matplotlib.pyplot as plt
from PIL import Image, ImageTk
import tkinter

#Bild einlesen
image = Image.open("Irgendein_Bild.jpg")
pixels = image.load()


#Pixelwerte als RGBRGBRGB usw ... abspeichern 
pix = []

for j in range(image.size[0]):
    for i in range(image.size[1]):
        #print(pixels[i,j])
        x_r = float(pixels[i,j][0])/255
        x_g = float(pixels[i,j][1])/255
        x_b = float(pixels[i,j][2])/255
        pix.append(x_r)
        pix.append(x_g)
        pix.append(x_b)

#Bild in die Dimensionen formen    
picture = numpy.array(pix).reshape(image.size[0] , image.size[1] , 3)    


#Array abspeichern
numpy.save("Test123.npy", picture)

#Array laden
picture = numpy.load("Test123.npy")


#Plotten (Funktioniert!!)
plot = plt.imshow(picture, interpolation = "nearest")
plt.show()


#Per GUI anzeigen (Funktioniert nicht :(( )
img = Image.fromarray(picture, "RGB")

root = tkinter.Tk()

image1 = ImageTk.PhotoImage(img)

label1 = tkinter.Label(root,image=image1)
label1.pack()

root.mainloop()
BlackJack

@barisoezcan: Okay, und jetzt bitte noch die von Sirius3 gezeigte Umwandlung in 8-Bit-Werte anwenden und auf wundersame Weise funktioniert es. :roll:
barisoezcan
User
Beiträge: 73
Registriert: Freitag 15. März 2013, 19:38

Code: Alles auswählen

import numpy
from PIL import Image
import matplotlib.pyplot as plt
from PIL import Image, ImageTk
import tkinter

#Bild einlesen
image = Image.open("Irgendein_Bild.jpg")
pixels = image.load()


#Pixelwerte als RGBRGBRGB usw ... abspeichern
pix = []

for j in range(image.size[0]):
    for i in range(image.size[1]):
        #print(pixels[i,j])
        x_r = float(pixels[i,j][0])/255
        x_g = float(pixels[i,j][1])/255
        x_b = float(pixels[i,j][2])/255
        
        pix.append(x_r)
        pix.append(x_g)
        pix.append(x_b)


#Bild in die Dimensionen formen    
picture = numpy.array(pix).reshape(image.size[0] , image.size[1] , 3)    

#Array abspeichern
numpy.save("Test123.npy", picture)

#Array laden
picture = numpy.load("Test123.npy")


#DIESE FOR-SCHLEIFEN SIND NEU EINGEFUEGT FUER DIE UMFORMUNG VON FLOAT NACH UINT8, ABER ES KLAPPT NICHT =(
for i in range(len(picture)):
    for j in range(len(picture[0])):
        for z in range(len(picture[0][0])):
            picture[i][j][z] = (255 * picture[i][j][z]).astype(numpy.uint8)


#Plotten (Funktioniert!!)
plot = plt.imshow(picture, interpolation = "nearest")
plt.show()


#Per GUI anzeigen (Funktioniert nicht :(( )
img = Image.fromarray(picture, "RGB")

root = tkinter.Tk()

image1 = ImageTk.PhotoImage(img)

label1 = tkinter.Label(root,image=image1)
label1.pack()

root.mainloop()
BlackJack

@barisoezcan: Natürlich klappt das nicht. Du nimmst die Werte aus einem `float64`-Array wandelst die in 8 Bit um und schreibst sie dann wieder in das `float64`-Array wodurch sie dann natürlich wieder zu Fliesskommawerten werden. So ein Array hat *einen* Datentyp, der gilt für *jeden* Wert in dem Array. Man kann die nicht einzeln in andere Typen umwandeln. Die Umwandlung musst Du auf das ganze Array anwenden, ohne diese Schleifen. Eben genau so wie Sirius3 das gezeigt hat. Genau diese eine Zeile.
barisoezcan
User
Beiträge: 73
Registriert: Freitag 15. März 2013, 19:38

Es klappt endlich :lol: :lol: :lol: :lol:
Vielen Dank Leute! :D
barisoezcan
User
Beiträge: 73
Registriert: Freitag 15. März 2013, 19:38

Neue Frage:

Wie kann ich denn Bilder in der GUI per

Code: Alles auswählen

label = tkinter.Label(root, image=imgs[i]).pack(side=ausrichtung)
in der schönen Reihenfolge darstellen:
Bild


Ich habe es in verschiedenen Variationen mit

Code: Alles auswählen

side = "left" 
side = "top"
usw.. 
ausprobiert.
Die Bilder sind immer verschieden angeordnet, aber leider nicht wie im Beispielbild...

So sieht es bei mir aus:
Bild
Oder so:
Bild


Minimal lauffähiger Code:

Code: Alles auswählen

import tkinter
import numpy
from PIL import Image, ImageTk
from Plot import Plot


# Node einlesen
nodes_float = numpy.load("nodes.npy")

# Array-Werte in Integer umwandeln
nodes = (nodes_float * 255).astype(numpy.uint8)

# Plotten
plot = Plot()
plot.plotFromLocal(nodes)


# Per GUI anzeigen
root = tkinter.Tk()

#GUI-anzeigefähige Bilder erzeugen
image_new = []
for i in range(len(nodes)):
    for j in range(len(nodes[0])):
        image_new.append(ImageTk.PhotoImage(Image.fromarray(nodes[i][j], "RGB")))


#Einer meiner gescheiterten Loesungsansaetze zur Darstellung der Bilder auf der GUI
s = ["left", "top"]
n = 0
for i in image_new:
    n = n + 1
    label = tkinter.Label(root, image=i)
    if(n%5 != 0):
        label.pack(side = s[0])
    else:
        label.pack(side = s[1])

root.mainloop()
BlackJack

@barisoezcan: Für die Ausrichtung ist `grid()` offensichtlich wesentlich besser geeignet als `pack()`. Bei `pack()` müsste man zum Beispiel für jede Zeile einen zusätzlichen `Frame` verwenden wo die Bilder einer Zeile zum Beispiel mit 'LEFT' platziert werden und diese Zeilen-`Frame`\s dann mit 'TOP' im übergeordneten Containerwidget platziert werden.

Den Rückgabewert der `pack()`-Methode an einen Namen zu binden macht übrigens keinen Sinn — der ist nämlich `None`.
barisoezcan
User
Beiträge: 73
Registriert: Freitag 15. März 2013, 19:38

BlackJack hat geschrieben:@barisoezcan: Für die Ausrichtung ist `grid()` offensichtlich wesentlich besser geeignet als `pack()`. Bei `pack()` müsste man zum Beispiel für jede Zeile einen zusätzlichen `Frame` verwenden wo die Bilder einer Zeile zum Beispiel mit 'LEFT' platziert werden und diese Zeilen-`Frame`\s dann mit 'TOP' im übergeordneten Containerwidget platziert werden.

Den Rückgabewert der `pack()`-Methode an einen Namen zu binden macht übrigens keinen Sinn — der ist nämlich `None`.
Danke, ich probier's gleich mal aus.
Wo binde ich denn den Rückgabewert der 'pack()'-Methode an einen Namen?
BlackJack

Gleich im ersten Beispiel in Deinem Beitrag:

Code: Alles auswählen

label = tkinter.Label(root, image=imgs[i]).pack(side=ausrichtung)
barisoezcan
User
Beiträge: 73
Registriert: Freitag 15. März 2013, 19:38

BlackJack hat geschrieben:Gleich im ersten Beispiel in Deinem Beitrag:

Code: Alles auswählen

label = tkinter.Label(root, image=imgs[i]).pack(side=ausrichtung)
Stimmt, hab ich glatt übersehen.. Und mit grid() klappt es.
Dankeschööön

Code: Alles auswählen

z = 0
for i in image_new:
    label = tkinter.Label(root, image=i).grid(row=int(z/4),column=z%4)
    z = z + 1
Aber ist es so überhaupt "schön" mit dem "aufwendigen" Zähler?
BlackJack

@barisoezcan: Statt den Zähler „manuell” zu verwalten würde man eher die `enumerate()`-Funktion verwenden. Und bei `i` erwartet fast jeder Programmierer eine Zahl und kein Bildobjekt. Ein Name wie `image` wäre besser. Das `image_new` sollte auch besser in der Mehrzahl genannt werden. Also zum Beispiel `new_images`. Denn hinter dem Namen verbirgt sich ja mehr als nur ein Bild.
barisoezcan
User
Beiträge: 73
Registriert: Freitag 15. März 2013, 19:38

Code: Alles auswählen

from tkinter import *
import tkinter
import numpy
from PIL import Image, ImageTk


#Map einlesen
nodes_float = numpy.load("map.npy")

#Array-Werte in Integer umwandeln
nodes = (nodes_float * 255).astype(numpy.uint8)
     

def win1():
    
    def button_click(event):
        win2()
        
    root = Tk()
    root.bind("<Button>", button_click)
    
    photo_imgs = []
    for i in range(len(nodes)):
        for j in range(len(nodes[0])):
            img = Image.fromarray(nodes[i][j], "RGB")
            photo_imgs.append(ImageTk.PhotoImage(img))
    
    for i, im in enumerate(photo_imgs):
        l = Label(root, image=im).grid(column=int(i/4),row=i%4)
        l.bind("<Button>", button_click)
    
    root.mainloop()   
    

def win2():

    root = tkinter.Toplevel()

    photo_imgs = []
    for i in range(len(nodes)):
        for j in range(len(nodes[0])):
            img = Image.fromarray(nodes[i][j], "RGB")
            photo_imgs.append(ImageTk.PhotoImage(img))
    
    for i, im in enumerate(photo_imgs):
        Label(root, image=im).grid(column=int(i/4),row=i%4)
    
    root.mainloop()  
    
    
    
win1() 
Könnt ihr mir sagen, warum beim Binden mit dem Label folgende Fehlermeldung erscheint? (Mit dem "root" klappt's ja)
Bild

Im Grunde will ich erreichen, dass, wenn man auf ein bestimmtes Bild klickt, das neue Fenster erscheint. (Und nicht, wenn man irgendwo auf das erste Fenster klickt.)
-> Eventgebundenes Label(Bild)
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Die Meldung ist doch eindeutig! ``l.bind(..)`` wirft den NoneType-Fehler, ergo ist ``l`` zu diesem Zeitpunkt eben an ``None`` gebunden. Folglich muss der Fehler davor zu finden sein.

Code: Alles auswählen

l = Label(root, image=im).grid(column=int(i/4),row=i%4)
Die Methode ``grid()`` wird wohl ``None`` als Rückgabewert haben. Da Du diese aufrufst, wird ``l`` eben ``None`` zugewiesen.

Du wirst das Erstellen des Labels und die Geometrie-Anweisung wohl trennen müssen:

Code: Alles auswählen

l = Label(root, image=im)
l.grid(column=int(i/4),row=i%4)
# ... usw.
Übrigens musst Du Fehlermeldungen nicht als Bild posten - ein Textquote reicht da vollkommen aus. Wenn wir Deine Pfade nicht sehen sollen, dann kannst Du sie ja textuell auch leicht unkenntlich machen, indem Du sie löschst ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Sirius3
User
Beiträge: 18265
Registriert: Sonntag 21. Oktober 2012, 17:20

Hallo barisoezcan,

bitte keine Tracebacks oder sonstigen Text als Bilder posten.
Die Fehlermeldung ist doch eindeutig. »l« ist vom Typ NoneType, oder besser gesagt »l« ist None.
Und warum? Weil Du eine Zeile darüber »l« genau diese Wert zuweist.
Das Laden und erzeugen der Images würde ich in eine Funktion schreiben. Gleichzeitig kannst Du noch die Anzahl globaler Variablen reduzieren. Diese »range(len(...))«-Schleifen solltest Du in Python vermeiden und direkt über »nodes« iterieren. Der *-import von tkinter wurde glaube ich schon weiter oben bemängelt.
barisoezcan
User
Beiträge: 73
Registriert: Freitag 15. März 2013, 19:38

Warum bin ich nicht selber drauf kommen können? :oops:
Meinst du mit globalen Variablen "nodes_float" und "nodes"?
Indem ich beide Variablen in Funktionen einbaue, wäre das "globale Problem" beseitigt, oder?
JonnyDamnnox
User
Beiträge: 68
Registriert: Sonntag 10. März 2013, 21:14

Nimm doch PyQt4 oder PySide, da gibts den Qt-Designer. Damit kannst du ganz locker die Fenster selbst per drag&drop zusammenstellen(aber erst wenn du verstanden hast wie das code-technisch geht :D)

Gruß
barisoezcan
User
Beiträge: 73
Registriert: Freitag 15. März 2013, 19:38

@ JonnyDamnnox: Ich habe schon mit Tkinter angefangen und sehe es auch als Herausforderung mit Code zu arbeiten als mit Drag & Drop :)

Code: Alles auswählen

from tkinter import *
import tkinter
import numpy
from PIL import Image, ImageTk

class GUI:
    
    def getNodes(self):
        
        nodes_float = numpy.load("map.npy")
        nodes = (nodes_float * 255).astype(numpy.uint8)
        
        return nodes
        
        
    def getNode(self, number):
        
        nodes = self.getNodes()
        
        row = int(number/4)
        column = number%4
        
        return numpy.array(nodes[row][column])
        
        
    def array2PhotoImages(self):
        
        nodes = self.getNodes()
        
        photo_imgs = []
        
        for row in nodes:
            for node in row:
                photo_imgs.append(ImageTk.PhotoImage(Image.fromarray(node, "RGB"))) 
                 
        return photo_imgs
    
    
    def array2PhotoImage(self, node):
        
        photo_img = ImageTk.PhotoImage(Image.fromarray(node, "RGB"))
        
        return photo_img
     
     
    def win1(self):
        
        def do_bind(eventname):
            def button_click(event):
                self.win2(eventname) 
            l.bind("<Button-1>", button_click)
            
        root = Tk()
        
        photo_imgs = self.array2PhotoImages()
        
        for i, im in enumerate(photo_imgs):
            l = Label(root, image=im)
            l.grid(column=int(i/4),row=i%4)
            do_bind(i)
        
        root.mainloop()   
        
    
    def win2(self, number):
    
        root = tkinter.Toplevel()
    
        node = self.getNode(number)
        photo_img = ImageTk.PhotoImage(Image.fromarray(node, "RGB"))
    
        Label(root, image=photo_img).grid()
        
        root.mainloop()  
Wenn ich ein Instanz dieser Klasse erzeugen will, kommt die Fehlermeldung:

Traceback (most recent call last):
File "C:\xxxxxxx\Start.py", line 31, in <module>
g.win1()
File "C:\xxxxxxx\GUI.py", line 58, in win1
l = Label(root, image=im)
File "C:\Python32\lib\tkinter\__init__.py", line 2459, in __init__
Widget.__init__(self, master, 'label', cnf, kw)
File "C:\Python32\lib\tkinter\__init__.py", line 1958, in __init__
(widgetName, self._w) + extra + self._options(cnf))
_tkinter.TclError: image "pyimage21" doesn't exist


Wenn ich aber "class GUI:" und die "self"'s entferne und dann diesen Code laufen lasse, funktioniert es.
Kann es vllt sein, dass man von "GUI"'s keine Instanzen erzeugen kann?

PS: Wie ist denn von tkinter zu importieren, wenn ich nur das nötigste will UND nicht jedesmal auf die Objekte referenzieren will?
Also kein:

Code: Alles auswählen

tkinter.Label()
tkinter.Tk()
usw..
BlackJack

@barisoezcan: Die Klasse macht hinten und vorne keinen Sinn. Da sind einfach ein paar Funktionen als ”Methoden” in eine Klasse gesteckt worden. Es gibt keinen gemeinsamen Zustand, keine der ”Methoden” benutzt `self` tatsächlich, ausser das es halt nötig ist um die unnötigerweise in einer Klasse steckenden Funktionen aufzurufen.

Das ganze ist auch nicht besonders effizient. Überleg mal unter welchen Umständen `getNodes()` aufgerufen wird um alle Daten zu laden und dann doch nur einen Ausschnitt davon zu verwenden, obwohl alle Daten schon längst in den Speicher geladen sind, also auch der Ausschnitt den `getNode()` holt/verwendet.

Es sollte nicht nötig sei `numpy.array()` in `getNode()` aufzurufen, denn `nodes` ist ja schon so ein Array, also auch dar Teilbereich den Du mit den Indexzugriffen selektierst.

`array2PhotoImages()` könnte `array2PhotoImage()` aufrufen um den gemeinsamen Codeanteil in den beiden Funktionen nur einmal schreiben und warten zu müssen.

`win1()` und `win2()` sind schlechte Namen — die sagen nichts darüber aus was der jeweilge Funktionsaufruf machen wird. Genau wie `eventname` ein schlechter Name für ganzzahlige Werte ist. Dieses `do_bind()` ist sowieso eigenartig. Das scheint ein neu erfinden der `functools.partial()`-Funktion zu sein, nur in schlechter lesbar und mit GUI-Code vermengt.

Die `mainloop()` braucht man nur einmal aufrufen, sprich das ist in der `win2()`-Funktion nicht nötig und eventuell sogar keine gute Idee.

Die Ausnahme bekommt man normalerweise wenn man keine Referenz auf das Bildobjekt auf der Python-Seite behält, weil Pythons Speicherverwaltung nicht feststellen kann ob Tk das Bild noch verwendet oder nicht und den Speicher einfach frei gibt, wenn es von Python aus keine Möglichkeit mehr gibt auf das Objekt zuzugreifen. Beim vorliegenden Quelltext kann ich mir das aber nicht erklären, denn solange die `win1()`-Funktion noch nicht abgearbeitet ist, existiert die Liste mit den Bildobjekten und die der `mainloop()`-Aufruf kehrt erst zurück wenn das Fenster geschlossen wurde. Und dann braucht Tk die Bilddaten auch nicht mehr.

Tkinter wird üblicherweise als ``import tkinter as tk`` importiert. Dann holt man sich nicht die ganzen Namen ins Modul, braucht aber auch nicht so viel zusätzlich tippen.
barisoezcan
User
Beiträge: 73
Registriert: Freitag 15. März 2013, 19:38

Aaaaalso:

Ich habe die einzelnen Funktionen in eine gemeinsame Klasse gepackt, weil ich eine Hauptklasse habe, wo ich diese GUI und weitere Klassen verwenden möchte.

"getNodes()" wird von "array2PhotoImages()" vollständig verwendet (nicht nur ein Ausschnitt) und wird auch vollständig an "win1()" übergeben, wo es vollständig zur Darstellung verwendet wird.

Mit "numpy.array()" in "getNode()" hattest du Recht. Das habe ich entfernt.

Habe "array2PhotoImages()" so umgeschrieben, dass es jetzt "array2PhotoImage()" aufruft.

Die schlechten Namen habe ich umbenannt.

An
"functools.partial()" statt "do_bind()",
"mainloop()"'s in "win1()" und "win2()"
und meinem eigentlichen Problem "_tkinter.TclError: image "pyimage21" doesn't exist" arbeite ich noch dran.

Vielen Dank für die vielen nützlichen Verbesserungsvorschläge!
BlackJack

@barisoezcan: Man packt keine einzelnen Funktionen in eine Klasse sondern man fasst *Daten* und Funktionen die auf diesen Daten operieren, sinnvoll in einer Klasse zu einem Datentypen zusammen. Wenn man einfach nur Funktionen zusammenfassen möchte, dann ist das Modul das Sprachmittel dazu. (Auch da sollten die Funktionen natürlich irgendwie zusammen gehören.)

Wenn eine Klasse keine `__init__()`-Methode besitzt, in welcher der Zustand des Objekts initialisiert wird, also die den Methoden gemeinsamen Daten, dann hat man keine echte, sinnvolle Klasse.

Die Funktionen, die dort zusammengefasst wurden, passen auch nicht alle zum Klassennamen, denn bei weitem nicht alles hat mit GUI zu tun. Ein Teil ist auch Anwendungslogik.

Von `array2PhotoImages()` wird das gesamte Ergebnis von `getNodes()` verwendet, aber der Aufruf aus `win2()` über `getNode()` erledigt ziemlich viel unnötige Arbeit. Da wird die gesamte Datei geladen um dann *einen* Node daraus zu holen, und das *nachdem* auf jeden Fall schon einmal alle Nodes über `array2PhotoImages()` geladen wurden. Davon hätte man sich das Ergebnis einfach merken können. Solange die Daten in der Datei sich zwischen den Ladevorgängen nicht ändern, macht das keinen Sinn alles immer wieder neu zu laden.
Antworten