Matplotlib und Bottel

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.
Benutzeravatar
mathman
User
Beiträge: 92
Registriert: Mittwoch 19. November 2008, 08:27
Wohnort: Magdeburg
Kontaktdaten:

Guten Morgen :D

aus meiner Berechnung kann ich bis jetzt einen Plot mittels mathplotlib erzeugen und der wird bis jetzt in einem eigenen Fenster (ist scheinbar bei mathplotlib dabei) ausgegeben.
Jetzt möchte ich den Plot als PNG in meine html Seite die ich mittels Bottel erzeuge einbauen. Leider bekomme ich es nicht hin die Ausgabe dort einzubauen.

Code: Alles auswählen

  import numpy
  import matplotlib.pyplot as plt

  ...

  #Ausgabe als Plot
  #Plot  x,y
  plt.plot(ecz[:,0] , (ecz[:,2])*-1)
  plt.ylabel('Moment [kN/m]')
  plt.xlabel('Laenge [m]')
  plt.title('Momentenverlauf')
  plt.grid() # Gitternetz zeichnen
  plt.show()
Jetzt habe ich mir das Buch "Matplotlib for Python Developers" gekauft um ein wenig mehr zu erfahren. Leider hilft mir das Kapitel über Webframework nicht weiter, weil da u.A. auf Django eingegangen wird. Gibt es irgendwo ein Tutorial oder kann mir jemand einen Ansatz geben wie man so einen Plot in die Webseite einbauen kann?

Gruß
Mathman
BlackJack

@mathman: Du müsstest halt die Grafik nicht anzeigen -- da hat ein Benutzer ja nichts von, wenn das Diagramm auf dem Server angezeigt wird -- sondern über Bottle eine URL zu einem PNG mit dem Diagramm routen. Mit `pyplot.savefig()` kann man das Diagramm in verschiedenen Formaten speichern. Dabei muss man nicht zwingend in eine Datei speichern, sondern man kann auch ein dateiähnliches Objekt angeben. Also zum Beispiel `StringIO` aus der Standardbibliothek. Dessen Inhalt müsste man dann nur noch mit dem passenden MIME-Type an den Browser ausliefern.
Benutzeravatar
mathman
User
Beiträge: 92
Registriert: Mittwoch 19. November 2008, 08:27
Wohnort: Magdeburg
Kontaktdaten:

Vielen Dank für deine Antwort.

ich habe nun versucht mir was zusammen zu reimen aus deiner Aussage und meinem Buch.

Code: Alles auswählen

def schnittkraefte(traegertyp, I, Ecm, l1, l2, l3, g, q1, q2, q3):
  import sys
  import numpy
  import matplotlib
  matplotlib.use('Agg')
  import matplotlib.pyplot as plt

...
  #Ausgabe als Plot
  #Plot  x,y
  plt.plot(ecz[:,0] , (ecz[:,2])*-1)
  plt.ylabel('Moment [kN/m]')
  plt.xlabel('Laenge [m]')
  plt.title('Momentenverlauf')
  plt.grid() # Gitternetz zeichnen
  #plt.show()
  
  print "Content-Type: image/png\n"
  plt.savefig(sys.stdout, format='png')

In der Datei (bzw. Funktion) generiere ich den Plot. In einer weiteren Datei lasse ich Bottel
laufen.

Code: Alles auswählen

from bottle import route, run, post, request
from schnitt_test import schnittkraefte


@route('/')
@route('/index.html')
def index():
    
@post('/berechnung')
def berechnung():
    
    from schnitt_test import schnittkraefte
      
    return schnittkraefte(3, 2.25, 26700000., 5, 10, 30, 10, 20, 20, 20)

run(host='localhost', port=8080)
Wenn ich nun im Webbrowser auf die URL "http://localhost:8080/berechnung" zugreife, passiert erst einmal nichts. Danach gucke ich in meine Konsole (siehe Bildschirmfoto) und dann wird da ein kryptischer Text ausgegeben was wahrscheinlich den Plot darstellt. Wie bekomme ich nun den Plot in den Browser bzw. ich möchte ja später den Plot und Text dort auf der Seite haben?

Bild

Gruß

***Nachtrag***

wahrscheinlich muss man das Bild noch in html verknüpfen mit
<img src="/img/test.png" /> aber wie ist der pfad dann zur Datei die eigentlich im Speicher ist ?
frabron
User
Beiträge: 306
Registriert: Dienstag 31. März 2009, 14:36

Na, in deiner Bottle-Anwendung sind ja gleich mehrere Sachen drin, die für den Fehlschlag verantwortlich sein können. Deine IDE lasse ich jetzt mal aussen vor, bemerke nur am Rande, dass die auch zu Problemen mit der Ausführung von Python Skripten führen können.

Deine index-Route ist keine gültige Python-Funktion, da gehört afaik noch mindestens ein pass drunter.

Desweiteren benutzt du für deine Berechnungsroute den POST-Dekorator, d.h. die Route ist nur über Post verfügbar, und nicht über GET, so wie du es probiert hast. Wahrscheinlich musst du auch noch die passenden Header mitschicken, damit der Browser erkennt, dass da ein Bild kommt und kein Html/Text. Der doppelte Import von schnittkraefte ist auch nicht notwendig.

Gruß

Frank
Benutzeravatar
mathman
User
Beiträge: 92
Registriert: Mittwoch 19. November 2008, 08:27
Wohnort: Magdeburg
Kontaktdaten:

Vielen Dank :)

habe die Bottel Anwendung angepasst

Code: Alles auswählen

from bottle import route, run, post, request

@route('/')
@route('/index.html')
def index():
    from schnitt_test import schnittkraefte
    return schnittkraefte(3, 2.25, 26700000., 5, 10, 30, 10, 20, 20, 20)
    
run(host='localhost', port=8080)
Danach habe ich dies dann im Terminal gestartet und das gleich Problem ist aufgetreten, es wird der Plot in der Konsole "angezeigt" und nicht mit geschickt.

Wenn ich in der Funktion ein print "Content-Type: image/png\n" mit schicke, sollte der Browser doch eigentlich wissen das da nen Bild kommt ?

gruß
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

mathman hat geschrieben: habe die Bottel Anwendung angepasst
Es heißt bottle :mrgreen:
(SCNR, weil Du es wiederholt falsch geschrieben hast, sonst wäre ich von einem Flüchtgkeitsfehler ausgegangen!)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
frabron
User
Beiträge: 306
Registriert: Dienstag 31. März 2009, 14:36

Deine schnittkraefte-Funktion darf schon mal nix mit print ausgeben, ist ja kein Wunder, dass du eine Ausgabe in deiner Konsole siehst. Desweiteren empfehle ich http://bottle.paws.de/docs/dev/tutorial ... ng-content zum Einlesen.

Der Header der in der schnittkraefte-Funktion steht, gehört in den View, und (ohne Kenntnis von Matplotlib zu haben) musst du wahrscheinlich plt.savefig(sys.stdout, format='png') als Returnwert der Funktion behandeln.
Benutzeravatar
mathman
User
Beiträge: 92
Registriert: Mittwoch 19. November 2008, 08:27
Wohnort: Magdeburg
Kontaktdaten:

frabron hat geschrieben:Deine schnittkraefte-Funktion darf schon mal nix mit print ausgeben, ist ja kein Wunder, dass du eine Ausgabe in deiner Konsole siehst. Desweiteren empfehle ich http://bottle.paws.de/docs/dev/tutorial ... ng-content zum Einlesen.

Der Header der in der schnittkraefte-Funktion steht, gehört in den View, und (ohne Kenntnis von Matplotlib zu haben) musst du wahrscheinlich plt.savefig(sys.stdout, format='png') als Returnwert der Funktion behandeln.
Was ist denn der View?

Code: Alles auswählen

  plt.plot(ecz[:,0] , (ecz[:,2])*-1)
  plt.ylabel('Moment [kN/m]')
  plt.xlabel('Laenge [m]')
  plt.title('Momentenverlauf')
  plt.grid() # Gitternetz zeichnen
  
  return "Content-Type: image/png\n"
  return plt.savefig(sys.stdout, format='png')
Jedenfalls wird nun der Plot in der Konsole nicht mehr ausgegeben, aber leider auch kein Plot auf der Seite. Das einzige was im Quellcode der Seite nun steht ist "Content-Type: image/png " :|

*nachtrag*
Habe mir eben den Abschnitt "Generating content" durchgelesen und davon trifft dann der "File objects" Teil zu. Wenn ich das richtig verstanden habe, wird der Dateityp nicht mitgesendet (was wahrscheinlich den View Teil darstellt). Im weiteren Teil wird dann auf die statischen Dateien hingewiesen. Da sich der Plot aber dynamisch erstellt hab ich leider keine Statische Datei womit ich das Problem lösen könnte oder ich habs nicht richtig verstanden :?
Benutzeravatar
/me
User
Beiträge: 3554
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

mathman hat geschrieben:

Code: Alles auswählen

  return "Content-Type: image/png\n"
  return plt.savefig(sys.stdout, format='png')
Ich weiß jetzt nicht genau wie bottle funktioniert, aber wenn du erwartest, dass nach einem return-Statement noch weiter folgender Code ausgeführt wird, dann hast du ein grundsätzliches Verständnisproblem.
Benutzeravatar
mathman
User
Beiträge: 92
Registriert: Mittwoch 19. November 2008, 08:27
Wohnort: Magdeburg
Kontaktdaten:

/me hat geschrieben:
mathman hat geschrieben:

Code: Alles auswählen

  return "Content-Type: image/png\n"
  return plt.savefig(sys.stdout, format='png')
Ich weiß jetzt nicht genau wie bottle funktioniert, aber wenn du erwartest, dass nach einem return-Statement noch weiter folgender Code ausgeführt wird, dann hast du ein grundsätzliches Verständnisproblem.
Gut das return nur einmal vorkommen darf, wusste ich nicht :?
Wenn ich nun den Content Type auskommentiere bekomme ich wieder den Plot in der Konsole :K


**Nachtrag**
ich habe die letzte Zeile wie folgt geschrieben
return plt.savefig('test.png', format='png')

dabei wird mir ein wunderschöner Plot auf die Festplatte geschrieben. Also bis dahin funktioniert es. Es muss lediglich das Bild nur im flüchtigen Speicher bleiben, in Bottle ankommen und beim Nutzer auf der Webseite angezeigt werden :K
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

mathman hat geschrieben:
/me hat geschrieben:
mathman hat geschrieben:

Code: Alles auswählen

  return "Content-Type: image/png\n"
  return plt.savefig(sys.stdout, format='png')
Ich weiß jetzt nicht genau wie bottle funktioniert, aber wenn du erwartest, dass nach einem return-Statement noch weiter folgender Code ausgeführt wird, dann hast du ein grundsätzliches Verständnisproblem.
Gut das return nur einmal vorkommen darf, wusste ich nicht :?
Das ist ja auch eine falsche Folgerung. Nach einem ``return`` kehrt die Funktion zurück, das heißt aber nicht dass ``return`` nur einmal Vorkommen darf:

Code: Alles auswählen

if farbe == "grün":
    return "Python"
    # hier bringt weiterer Code nichts
else:
    return "Nicht Python"
    # hier wird weiterer Code ebenfalls ignoriert
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
mathman
User
Beiträge: 92
Registriert: Mittwoch 19. November 2008, 08:27
Wohnort: Magdeburg
Kontaktdaten:

Ok, die return Anweisung gibt als zum Schluß den Rückgabewert zurück.
In meinem Buch womit ich arbeite (Python 3, Das umfassende Handbuch) ist sys.stdout nur angerissen, aber ich hoffe ich habe es halbwegs verstanden. In meiner Returnanweisung habe ich als Speicherort des Bildes sys.stdout an gegeben, somit müsste doch eigentlich das Bild im Speicher sein ? Wenn ich dafür einen Dateinamen rein schreibe wird es wunderbar auf die Festplatte geschrieben.

Code: Alles auswählen

def schnittkraefte(traegertyp, I, Ecm, l1, l2, l3, g, q1, q2, q3):
  import sys
  import numpy
  import matplotlib
  import matplotlib.pyplot as plt
...

  #Ausgabe als Plot
  #Plot  x,y
  plt.plot(ecz[:,0] , (ecz[:,2])*-1)
  plt.ylabel('Moment [kN/m]')
  plt.xlabel('Laenge [m]')
  plt.title('Momentenverlauf')
  plt.grid() # Gitternetz zeichnen
  
  return plt.savefig(sys.stdout, format='png')
Ich rufe die Funktion mittels meinem Testprogramm auf

Code: Alles auswählen

from bottle import route, run, post
@route('/')
@route('/index.html')
def index():
    from schnitt_test import schnittkraefte
    
    return schnittkraefte(3, 2.25, 26700000., 5, 10, 30, 10, 20, 20, 20)
    

run(host='localhost', port=8080)
Demnach sollte doch der Rückgabewert der Funktion direkt in der Webseite erscheinen, jedoch wird die Ausgabe in die Konsole geschrieben :K

Wie kann man denn das Bild direkt an die Webseite schicken ?

gruß
Benutzeravatar
Rebecca
User
Beiträge: 1662
Registriert: Freitag 3. Februar 2006, 12:28
Wohnort: DN, Heimat: HB
Kontaktdaten:

Die Ausgabe wird in die Konsole geschrieben, weil print und sys.stdout nunmal in die Konsole schreiben. BlackJack hat ja schon auf StringIO hingewiesen: Du musst dir solch ein Objekt anlegen, das an savefig uebergeben und es dann returnen.

Im Aufrufer der Funktion kannst du dann daraus den Inhalt als String lesen.
Offizielles Python-Tutorial (Deutsche Version)

Urheberrecht, Datenschutz, Informationsfreiheit: Piratenpartei
Zap
User
Beiträge: 533
Registriert: Freitag 13. Oktober 2006, 10:56

Hallo mathman. Ich habe bisher weder mit matplotlib noch mit bottle gearbeitet.
Aber was du da machst, macht meines Erachtens keinen Sinn.
Auf die Konsole schreiben heist nicht in den Speicher schreiben.

Versuche mal folgendes.

Code: Alles auswählen

from StringIO import StringIO

def schnittkraefte(...):
  #...
  io = StringIO()
  plt.savefig(io, format='png')
  return io.getvalue()
Nicht vergessen in der Doku von Bottle nachzugucken wie man den MIME-Type für den Response setzt.
Der client muss ja schließlich wissen das dort kein normaler Text kommt sondern ein Bild.

PS: Rebecca war schneller, ich wollte meinen Text aber einfach wieder wegschmeissen ;)
Benutzeravatar
mathman
User
Beiträge: 92
Registriert: Mittwoch 19. November 2008, 08:27
Wohnort: Magdeburg
Kontaktdaten:

Zap hat geschrieben:Hallo mathman. Ich habe bisher weder mit matplotlib noch mit bottle gearbeitet.
Aber was du da machst, macht meines Erachtens keinen Sinn.
Auf die Konsole schreiben heist nicht in den Speicher schreiben.

Versuche mal folgendes.

Code: Alles auswählen

from StringIO import StringIO

def schnittkraefte(...):
  #...
  io = StringIO()
  plt.savefig(io, format='png')
  return io.getvalue()
Nicht vergessen in der Doku von Bottle nachzugucken wie man den MIME-Type für den Response setzt.
Der client muss ja schließlich wissen das dort kein normaler Text kommt sondern ein Bild.

PS: Rebecca war schneller, ich wollte meinen Text aber einfach wieder wegschmeissen ;)

Vielen Dank :)

ich hab mir in der Zwischenzeit ein weiteres Buch zu Gemüt geführt und wollte was zu StringIO erfahren. Stand zwar was drinne, aber war auch nicht die Welt ...
Jedenfalls hab ich mich in der Zwischenzeit mit StringIO versucht.
War auch fast so wie dein Lösungsvorschlag, außer das das ich den Rückgabewert nicht mit getvalue() gemacht hatte und dies von Bottel ausführen lassen wollte. Aufjedenfall kommt jetzt schon mal was außerhalb der Funktion an :mrgreen:

Code: Alles auswählen

def schnittkraefte(traegertyp, I, Ecm, l1, l2, l3, g, q1, q2, q3):
  import sys
  import numpy
  import matplotlib
  #soll Antigrain Enginge zum Rendern nutzen
  matplotlib.use('Agg')
  import matplotlib.pyplot as plt
  #Lib fuer Ramdatei
  from StringIO import StringIO

...

 #Ausgabe als Plot
  #Plot  x,y
  s = StringIO()
  plt.plot(ecz[:,0] , (ecz[:,2])*-1)
  plt.ylabel('Moment [kN/m]')
  plt.xlabel('Laenge [m]')
  plt.title('Momentenverlauf')
  plt.grid() # Gitternetz zeichnen
  

  #return plt.savefig(sys.stdout, format='png')
  plt.savefig(s, format='png')
  return s.getvalue()
Das sollte schon funktioniere, jetzt ist das Problem auf Bottle zu verlagern.

Code: Alles auswählen

from bottle import route, run, static_file, debug


@route('/')
@route('/index.html')
def index():
    from schnitt_test import schnittkraefte
    
    s = schnittkraefte(3, 2.25, 26700000., 5, 10, 30, 10, 20, 20, 20)
    return static_file(s, mimetype='image/png')
  
debug(True)
run(host='localhost', port=8080)
Da kommt es zu einem Fehler 500
Error 500: Internal Server Error

Sorry, the requested URL http://localhost:8080/ caused an error:

Unhandled exception

Exception:

TypeError('static_file() takes at least 2 non-keyword arguments (1 given)',)

Traceback:

Traceback (most recent call last):
File "/home/mkoenig/workspace/sb2brain/src/bottle.py", line 499, in handle
return handler(**args)
File "/home/mkoenig/workspace/sb2brain/src/web_test_plot.py", line 10, in index
return static_file(s, mimetype='image/png')
TypeError: static_file() takes at least 2 non-keyword arguments (1 given)
Im Tutorial von Bottle wird auf statische Dateien eingegangen die man mittels
der Funktion "static_file(filename, root='/path/to/image/files', mimetype='image/png')"
an den Browser verschicken kann. Das habe ich probiert, außer das ich den root Pfad weg gelassen habe, da es keinen gibt da das Bild ja in der Ramdisk ist.
Jedenfalls bin ich überfragt wie man das Bild jetzt in den Browser bekommt.
Benutzeravatar
Rebecca
User
Beiträge: 1662
Registriert: Freitag 3. Februar 2006, 12:28
Wohnort: DN, Heimat: HB
Kontaktdaten:

Warum kommst du jetzt auf static_file, wo du doch nun extra matplotlib dazu gebracht hast, KEINE Datei zu speichern?

sowas wie (ungetestet)

Code: Alles auswählen

def index():
    from schnitt_test import schnittkraefte
    
    s = schnittkraefte(3, 2.25, 26700000., 5, 10, 30, 10, 20, 20, 20)
    return "Content-Type: image/png\n" + s
sollte es doch tun.
Offizielles Python-Tutorial (Deutsche Version)

Urheberrecht, Datenschutz, Informationsfreiheit: Piratenpartei
Benutzeravatar
mathman
User
Beiträge: 92
Registriert: Mittwoch 19. November 2008, 08:27
Wohnort: Magdeburg
Kontaktdaten:

Rebecca hat geschrieben:Warum kommst du jetzt auf static_file, wo du doch nun extra matplotlib dazu gebracht hast, KEINE Datei zu speichern?

sowas wie (ungetestet)

Code: Alles auswählen

def index():
    from schnitt_test import schnittkraefte
    
    s = schnittkraefte(3, 2.25, 26700000., 5, 10, 30, 10, 20, 20, 20)
    return "Content-Type: image/png\n" + s
sollte es doch tun.
Ich hab gedacht, dass man so den Type übergeben kann.

Wenn ich das so probiere wie du das gesagt hast, bekomme ich das Bild kryptisch ausgegeben (so wies sonst in der Konsole war).

Bild

Ich hab dann doch noch eine weitere Frage zu der return Anweisung.
Wenn man eine komplette Seite mit Bildern etc. ausgeben will, muss man sich den String mit Bildern vorher zusammen bauen und dann diesen zurück geben ? Wie würde ich dann in diesem Fall das dynamisch generierte Bild dort unterbringen?

gruß
Benutzeravatar
Rebecca
User
Beiträge: 1662
Registriert: Freitag 3. Februar 2006, 12:28
Wohnort: DN, Heimat: HB
Kontaktdaten:

Naja, das ist ja schonmal einen Schritt weiter. OK, den Content-Type an den String zu klatschen war offensichtlich Bloedsinn, aber schau dir mal das Response-Objekt an...
Offizielles Python-Tutorial (Deutsche Version)

Urheberrecht, Datenschutz, Informationsfreiheit: Piratenpartei
Benutzeravatar
mathman
User
Beiträge: 92
Registriert: Mittwoch 19. November 2008, 08:27
Wohnort: Magdeburg
Kontaktdaten:

Rebecca hat geschrieben:Naja, das ist ja schonmal einen Schritt weiter. OK, den Content-Type an den String zu klatschen war offensichtlich Bloedsinn, aber schau dir mal das Response-Objekt an...
Vielen Dank für den Hinweis, jetzt funktioniert es endlich Bild

Code: Alles auswählen

from bottle import route, run, static_file, debug, response

@route('/')
@route('/index.html')
def index():
    from schnitt_test import schnittkraefte
    
    s = schnittkraefte(3, 2.25, 26700000., 5, 10, 30, 10, 20, 20, 20)
    response.headers['Content-Type'] = 'image/png'
    return s
  
debug(True)
run(host='localhost', port=8080)
Jetzt hab ich aber noch weitere Fragen :mrgreen:
Wenn das Bild ausgegeben wurde, bleibt das dann im Arbeitsspeicher oder wird das dann automatisch gelöscht? Weil wenn das Programm auf einem Server ständig läuft, wäre ja irgendwann der Arbeitsspeicher voll :lol:

Ein weiteres Anliegen von mir, dass ich nicht nur Bilder ausgeben möchte, sondern halt eine gesamte Webseite. Jetzt habe ich den Header verändert, so dass der Browser annimmt dass da nur ein Bild kommt. Wie kann ich denn Bilder und Text verschicken?

gruß
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

mathman hat geschrieben:Wenn das Bild ausgegeben wurde, bleibt das dann im Arbeitsspeicher oder wird das dann automatisch gelöscht? Weil wenn das Programm auf einem Server ständig läuft, wäre ja irgendwann der Arbeitsspeicher voll :lol:
Das Objekt bleibt so lange im Speicher wie lange es noch Referenzen drauf gibt. Wie jedes Objekt in Python eben :roll:
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Antworten