BLOB String aus SQLite DB wieder als Image anzeigen

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
bigbonzo
User
Beiträge: 21
Registriert: Samstag 30. November 2019, 10:23

@Sirius; mit b'data' kommt die Fehlermeldung "AttributeError: 'Image' object has no attribute 'open'" , mit 'data' kommt die Fehlermeldung "TypeError: a bytes-like object is required, not 'str'"
Wie muss der Programmcode lauten, um mit dem Datenbankwert ein Image in der GUI anzuzeigen ?
data = selection[12] ## string aus der Datenbank
f = io.BytesIO(b'data')
photo=image.open(f)

Gibt es da eine Lösung zu ?
Danke.
bigbonzo
User
Beiträge: 21
Registriert: Samstag 30. November 2019, 10:23

@blackjack; der Hinweis ist richtig, hier aber leider nicht zielführend.
Ich habe seit 25 Jahren mit relationalen Datenbanken und -modellierungen zu tun und kann bestätigen, dass eine Änderung des Datenmodells und Datenbankschemas u.U. viel Aufwand bedeutet. In diesem Fall geht es aber nur um eine einzige Tabelle mit 14 Attributen ;-) und ein klein wenig Funktionalität drum herum. Mein Problem ist, dass ich Python Laie bin und nur für mich ein kleines Progrämmchen schreiben möchte.
Ich weiß, dass ich da bestimmt einiges durcheinander werfe und falsch ansetze, aber ich glaube, dass ihr Fachleute, die paar Zeilen Code, die zum Ergebnis führen, parat habt.
Wie muss der Code aussehen, um mit den Informationen in der Variable data ein Tkinter Image in der GUI anzuzeigen?
Danke.
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du sollst

data

benutzen. OHNE ANFÜHRUNGSZEICHEN!

Wenn du so ein doller SQL Experte bist, dann sollte dir doch der Unterschied zwischen einem Namen einer Spalte und einem String-Literal in Anführungszeichen bekannt sein. Wieso denkst du das wäre in Python anders?
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@bigbonzo: Du machst hier den Fehler das Du folgendes hast:

Code: Alles auswählen

name = "Peter"
print("name")
Und Dich fragst warum da nicht Peter ausgegeben wird. Namen/Variablen und literale Werte und den Unterschied dazwischen zu verstehen ist eigentlich recht grundlegend. Und in SQL ist das doch genau so, das hier wird ja auch nicht den Wert der Spalte Nachname liefern sondern den Wert 'Nachname':

Code: Alles auswählen

SELECT 'Nachname' FROM personen WHERE id=1;
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
bigbonzo
User
Beiträge: 21
Registriert: Samstag 30. November 2019, 10:23

@deets, will mich hier nicht als SQL Experte darstellen, bin ich nicht, suche nur eine Antwort für den Programmcode.
Natürlich hatte ich das auch ohne Anführungszeichen ausprobiert, dann kommt folgende Fehlermeldung "TypeError: a bytes-like object is required, not 'str'"
bigbonzo
User
Beiträge: 21
Registriert: Samstag 30. November 2019, 10:23

@blackjack; danke für die Erläuterung, das sehe ich auch so. Leider funktioniert das ohne Anführungszeichen (als Variable) auch nicht, hatte ich deets schon geschrieben.

data = selection[12] ## string aus der Datenbank
f = io.BytesIO(data)
photo=image.open(f)

Wo ist denn da der Fehler ? Wie funktioniert es?
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Was heisst denn ”funktioniert nicht” und was ist `image`?
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Kannst du mal den Code für die Datenbankanfrage zeigen, und das Tabellenschema? Du bekommst ja offensichtlich statt der Bytes ein Stringobjekt.
bigbonzo
User
Beiträge: 21
Registriert: Samstag 30. November 2019, 10:23

Moin, danke, dass ihr dran bleibt ;-). Ich habe das DB Lesestatement mal direkt vor die Ausgabe geschoben und bekomme jetzt eine andere Meldung:

lfdnr="LfdNR"
connection = sqlite3.connect(datenbank)
cursor = connection.cursor()
cursor.execute(f"SELECT LfdNr, Datei_klein FROM Inventardaten Where {'lfdnr'} = {selection[0]}")
result = cursor.fetchall()
for r in result:
print (r[0], r[1]) # nur für Test
data = r[1]
connection.close()

# Konvertieren
f = io.BytesIO(data)
photo=Image.open(f)

# Foto in TopFrame ausgeben
foto_label=tk.Label(topFrame,anchor='e', image=photo)
foto_label.image=photo
foto_label.config(image=photo)
foto_label.grid(row=1, column=12, columnspan=3, rowspan=3, padx='5', pady='5', sticky='es')

Hier die DDL zur DB:
CREATE TABLE Inventardaten (
LfdNR INTEGER PRIMARY KEY AUTOINCREMENT,
DB_Status CHAR,
Datei_klein BLOB,
Datei_original VARCHAR
);
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Wäre hilfreich, wenn du die andere Meldung auch verraten würdest. Was man niemals machen darf, ist Parameter in SQL-Statenents hineinzuformatieren. LfdNr steckst du rein, warum fragst du sie auch wieder ab? Wie bist du auf die Idee gekommen, einen literalen String als Formatangabe zu verwenden? Die for-Schleife ist komisch, da du alles wegwirfst, bis auf den letzten Eintrag.
bigbonzo
User
Beiträge: 21
Registriert: Samstag 30. November 2019, 10:23

oh, vergessen. Hier die Meldung "_tkinter.TclError: image "<PIL.JpegImagePlugin.JpegImageFile image mode=L size=250x200 at 0x523D530>" doesn't exist"
Ok, das Lesestatement ist nicht so gelungen, habe ich auch nur von anderer Stelle hier herein gebracht. LfdNr als Parameter und Ergebnis, einfach nur Ungläubigkeit bzw. für die Testausgabe.
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

(Einiges wurde zwischenzeitlich schon besprochen, bin zu faul den Beitrag jetzt noch mal zu überarbeiten, sorry.)

@bigbonzo: Was hast Du Dir denn bei f"… {'lfdnr'} …" gedacht? Das sieht doch schon wieder danach aus als wenn Du Namen und literale Zeichenketten durcheinander bringst, denn das ist das gleiche wie f"… lfdnr …". Funktioniert natürlich hier trotzdem wenn der *Name* `lfdnr` an "LfdNR" gebunden ist weil SQL nicht zwischen Gross- und Kleinschreibung unterscheidet.

Spaltennamen in SQL zu formatieren ist zumindest komisch – Werte in SQL zu formatieren ist *falsch*. Das ist potentiell gefährlich und kann bei Datenbanken/Datenbankanbindungen die SQL-Anweisungen cachen, dazu führen das der Cache nicht genutzt werden kann wenn die SQL-Anweisung immer anders aussieht. Für Werte gibt es Platzhalter und das zweite Argument von `execute()`.

Da über den Primärschlüssel selektiert wird kann da nur (maximal) ein Ergebnis bei heraus kommen, da macht eine Schleife über ”alle” Ergebnisse wenig Sinn.

`f` ist wie die meisten einbuchstabigen Namen kein sinnvoller, guter Name. Wenn man `file` meint, sollte man das auch schreiben. Wenn man `image_file` schreibt, weiss der Leser sogar dass die Datei ein Bild enthält. Andererseits muss man auch nicht jedes Zwischenergebnis an einen Namen binden.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase). Also `top_frame` statt `topFrame`.

Die `image`-Option vom Label wird unnötigerweise zweimal gesetzt.

Zwischenstand:

Code: Alles auswählen

        ...
        
        with sqlite3.connect(datenbank) as connection:
            cursor = connection.cursor()
            cursor.execute(
                "SELECT LfdNr, Datei_klein FROM Inventardaten WHERE lfdnr = ?",
                [selection[0]],
            )
            result = cursor.fetchone()

        if result:
            _, image_data = result

            photo = Image.open(io.BytesIO(image_data))

            foto_label = tk.Label(top_frame, anchor=tk.E, image=photo)
            foto_label.image = photo
            foto_label.grid(
                row=1,
                column=12,
                columnspan=3,
                rowspan=3,
                padx=5,
                pady=5,
                sticky=tk.SE,
            )
Hier dürfte jetzt das Problem sein das `Image` wahrscheinlich `PIL.Image` ist und das was `open()` da liefert nicht direkt als Bildobjekt für `tkinter` verwendet werden kann. Das hattest Du in einem der vorherigen Beiträge schon mal richtig gemacht mit `ImageTk` aus dem `PIL`-Package.

Ich verwende bei Datenbanken ja fast immer SQLAlchemy als Zwischenschicht. Selbst wenn man das ORM nicht verwendet, lässt sich so auch SQL dynamisch zusammenbauen ohne das man da mit SQL oder fehleranfälligen Zeichenketten hantieren muss. Und man ist unabhängiger von der am Ende verwendeten Datenbank.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
bigbonzo
User
Beiträge: 21
Registriert: Samstag 30. November 2019, 10:23

Hallo Leute, et löpp.
Habe mir die neue Fehlermeldung nochmal angesehen und im Netz gesucht. Dort den Hinweis "photo = ImageTk.PhotoImage(photo)" gefunden, läuft jetzt tadellos.
Habe gerade gelesen, dass du das auch so siehst, passt.
Euch vielen Dank für eure Hinweise und Geduld ;-)
bigbonzo
User
Beiträge: 21
Registriert: Samstag 30. November 2019, 10:23

@blackjack, extra Dank für den Programmcode, der sieht deutlich besser aus als meiner.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Jetzt ist es wahrscheinlich noch falsch, dass da mitten in einer Funktion ein neues Label erzeugt wird. GUI-Elemente sollten nur bei der Initialisierung erzeugt werden und im Nachhinein nur noch verändert werden.
Antworten