I am trying to figure out how to use flask to just show a simple image and update with a new image. I have the following that seems to work just fine for images (pretty much right from MPL website):
#app.route("/image")
def show_image():
to_show = do_stuff(os.listdir( current_dir )[-1] ) #turn file into 2d np.array
fig, ax = plt.subplots()
ax.imshow( to_show, **plot_pars)
buf = BytesIO()
fig.savefig(buf, format = "png")
plot_url = base64.b64encode(buf.getvalue().decode("utf8")
return render_template("index.html", plot_url = plot_url)
The index.html file is currently setup to refresh every 15 seconds
<!DOCTYPE html>
<html>
<head>
<title> Plot</title>
<meta http-equiv='refresh' content="15>
</head>
<body>
<img src="data:image/png;base64, {{ plot_url }}">
</body>
</html>
I am curious if I can set something up that will basically run show_image if the length of my current_dir changes. If I try it in my show_image function (if os.listdir( current_dir) != nfiles ), but nothing changes, so I am trying to figure out the best way to start with this "trigger".
Related
Is it possible to have a user upload an arbitrary Python script (that uses built-ins only, no external packages) and then execute it using PyScript?
You can use an HTML <input> element to upload the file, and use await file.txt() to get the source. Then there are a couple ways to execute the code.
Using Python's exec() method:
<script defer src="https://pyscript.net/releases/2022.12.1/pyscript.js"></script>
<link rel="stylesheet" href="https://pyscript.net/releases/2022.12.1/pyscript.css">
<input type="file" id="file-upload">
<py-script>
import js
from pyodide.ffi.wrappers import add_event_listener
async def upload_and_run(event):
file_list = event.target.files
first_item = file_list.item(0)
src = await first_item.text()
exec(src)
input_elem = js.document.getElementById("file-upload")
add_event_listener(input_elem, "change", upload_and_run);
</py-script>
Or, if you want the script to behave like a <py-script> tag, with pretty error handling and such, you could add source to a new <py-script> tag:
<script defer src="https://pyscript.net/releases/2022.12.1/pyscript.js"></script>
<link rel="stylesheet" href="https://pyscript.net/releases/2022.12.1/pyscript.css">
<input type="file" id="file-upload">
<py-script>
import js
from pyodide.ffi.wrappers import add_event_listener
async def upload_as_script_tag(event):
file_list = event.target.files
first_item = file_list.item(0)
src = await first_item.text()
newTag = js.document.createElement("py-script")
newTag.innerText = src
js.document.body.appendChild(newTag)
input_elem = js.document.getElementById("file-upload")
add_event_listener(input_elem, "change", upload_as_script_tag);
</py-script>
</body>
I have developed a web-based tool, and currently trying to make it python-launchable. I figured using CEFpython is probably the way to do it. I followed the tutorial here and wrote the following code:
from cefpython3 import cefpython as cef
import base64
import platform
import sys
import threading
import os
HTML_code = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link href="static/css/main.css" rel="stylesheet" />
</head>
<body>
<div id="UI">
</div>
<div id="container"></div>
<script src="static/main.js"></script>
<script type="text/javascript">
function defineData(datainput){
console.log("start")
data = datainput;
var loc = window.location.pathname;
var dir = loc.substring(0, loc.lastIndexOf('/'));
console.log(loc);
console.log(dir);
Main();
}
</script>
</body>
</html>
"""
def html_to_data_uri(html):
html = html.encode("utf-8", "replace")
b64 = base64.b64encode(html).decode("utf-8", "replace")
ret = "data:text/html;base64,{data}".format(data=b64)
return ret
def main(config):
sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error
settings = {}
cef.Initialize(settings=settings)
browser = cef.CreateBrowserSync(url=html_to_data_uri(HTML_code),window_title="Test")
browser.SetClientHandler(LoadHandler(config))
cef.MessageLoop()
cef.Shutdown()
return
class LoadHandler(object):
def __init__(self, config):
self.config = config
def OnLoadingStateChange(self, browser, is_loading, **_):
"""Called when the loading state has changed."""
if not is_loading:
# Loading is complete. DOM is ready.
browser.ExecuteFunction("defineData", self.config)
unfortunately, unlike in the tutorial, my tool has to load a local .js file where the main function is defined (), and it seems if I code the html file this way, my working directory is not actually the directory where I call the script, but some strange place
the output of these lines are:
var loc = window.location.pathname;
var dir = loc.substring(0, loc.lastIndexOf('/'));
console.log(loc);
console.log(dir);
output:
text/html;base64,CjwhRE9DVFlQRSBodG1sPgo8aHRtbCBsYW5nPSJlbiI+Cgk8aGVhZD4KCQk8bWV0YSBjaGFyc2V0PSJ1dGYtOCI+CgkJPG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCwgdXNlci1zY2FsYWJsZT1ubywgbWluaW11bS1zY2FsZT0xLjAsIG1heGltdW0tc2NhbGU9MS4wIj4KCQk8bGluayBocmVmPSJzdGF0aWMvY3NzL21haW4uY3NzIiByZWw9InN0eWxlc2hlZXQiIC8+CgkJPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCQkJKiB7CgkJCQkuYm9yZGV....
text
Could you help me finding the correct way of hard coding html code in python with the correct path? maybe I need to somehow set the path?
PS: I did try including the html code in a separate .html file, and it worked on Windows machines, but it seems MacOS doesn't like it. Since this tutorial did work on MAC, I'm trying to hard code the html part into the python script and hope it would work on both Windows and Mac
Well, the HTML document has been converted to the body of a data URI by html_to_data_uri, so the U[niversal]R[esource]L[ocator] (window.location) of the document isn't a location on a server, but the data URI itself (the "strange place" you mention).
Remember that URLs are a subset of URIs, and you passed the URI as a URL to CEF with:
browser = cef.CreateBrowserSync(url=html_to_data_uri(HTML_code),window_title="Test")
So, as long as you are using a data URI/URL, I don't think that window.location will be helpful. Instead, you could extract the HTML code into a separate .html file, and change that line to:
browser = cef.CreateBrowserSync(url="/path/to/that_html_file.html", window_title="Test")
I have recently come across the Pyodide project.
I have built a little demo using Pyodide, but although I've spent a lot of time looking at the source, it is not obvious (yet) to me how to redirect print output from python (other than modifying the CPython source), and also, how to redirect output from matplotlib.pyplot to the browser.
From the source code, FigureCanvasWasm does have a show() method with the appropriate backend for plotting to the browser canvas - however, it is not clear to me how to instantiate this class and invoke it's show() method or indeed, if there is another more obvious way of redirecting plots to canvas.
My questions therefore are:
How do I redirect print() messages
How do I force pyodide to plot matplotlib figures in the browser?
Here is my test page:
<!doctype html>
<meta charset="utf-8">
<html lang="en">
<html>
<head>
<title>Demo</title>
<script src="../../pyodide/build/pyodide.js"></script>
</head>
<body>
</body>
<script type="text/javascript">
languagePluginLoader.then(() => {
pyodide.loadPackage(['matplotlib']).then(() => {
pyodide.runPython(`
import matplotlib.pyplot as plt
plt.plot([1, 2, 3, 4])
plt.ylabel('some numbers')
#fig = plt.gcf()
#fig.savefig(imgdata, format='png')
print('Done from python!')`
);
//var image = pyodide.pyimport('imgdata');
//console.log(image);
});});
</script>
<html>
First of all let's see if we can get just anything to show up in the browser; e.g. a normal string. Python variables are stored in the pyodide.globals attribute. Hence we can take the python object from there and place it into a <div> element on the page.
<!doctype html>
<meta charset="utf-8">
<html>
<head>
<title>Demo</title>
<script src="../pyodide/pyodide.js"></script>
</head>
<body>
</body>
<script type="text/javascript">
languagePluginLoader.then(() => {
pyodide.runPython(`my_string = "This is a python string." `);
document.getElementById("textfield").innerText = pyodide.globals.my_string;
});
</script>
<div id="textfield"></div>
<html>
Now I guess we can do the same with a matplotlib figure. The following would show a saved png image in the document.
<!doctype html>
<meta charset="utf-8">
<html lang="en">
<html>
<head>
<title>Demo</title>
<script src="../pyodide/pyodide.js"></script>
</head>
<body>
</body>
<script type="text/javascript">
languagePluginLoader.then(() => {
pyodide.loadPackage(['matplotlib']).then(() => {
pyodide.runPython(`
import matplotlib.pyplot as plt
import io, base64
fig, ax = plt.subplots()
ax.plot([1,3,2])
buf = io.BytesIO()
fig.savefig(buf, format='png')
buf.seek(0)
img_str = 'data:image/png;base64,' + base64.b64encode(buf.read()).decode('UTF-8')`
);
document.getElementById("pyplotfigure").src=pyodide.globals.img_str
});});
</script>
<div id="textfield">A matplotlib figure:</div>
<div id="pyplotdiv"><img id="pyplotfigure"/></div>
<html>
I haven't looked into the backends.wasm_backend yet, so that may allow for a more automated way of the above.
When using the wasm backend, the canvas property of a figure is an instance of FigureCanvasWasm. Calling the show() method of the canvas should be sufficient to display the figure in the browser. Unfortunately a minor bug in the create_root_element() method of the canvas prevents the figure from being displayed. This method creates a div element that will contain the figure. It tries first to create an iodide output div element. If that fails a plain HTML div element is created. This element however is never appended to the document and remains therefore invisible.
Below are the lines of code from FigureCanvasWasm were it happens
def create_root_element(self):
# Designed to be overridden by subclasses for use in contexts other
# than iodide.
try:
from js import iodide
return iodide.output.element('div')
except ImportError:
return document.createElement('div')
The comment suggests the non-iodide code is a stub that needs to be extended, by overriding the method. This would require subclassing FigureCanvasWasm, installing it as a pyodide module and configuring matplotlib to use that backend.
There is a shortcut however, because python allows overriding a method of an instance, without modifying the class, as per question 394770. Putting the following code in your HTML document gives a figure in the browser
import numpy as np
from matplotlib import pyplot as plt
from js import document
x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x)
f = plt.figure()
plt.plot(x,y)
# ordinary function to create a div
def create_root_element1(self):
div = document.createElement('div')
document.body.appendChild(div)
return div
#ordinary function to find an existing div
#you'll need to put a div with appropriate id somewhere in the document
def create_root_element2(self):
return document.getElementById('figure1')
#override create_root_element method of canvas by one of the functions above
f.canvas.create_root_element = create_root_element1.__get__(
create_root_element1, f.canvas.__class__)
f.canvas.show()
Initially the toolbar did not show icons. I had to download, unzip and install fontawesome alongside pyodide and include the following line in the header to get those
<link rel="stylesheet" href="font-awesome-4.7.0/css/font-awesome.min.css">
Edit:
About the first part of your question, redirecting the output stream to the browser, you could take a look at how it is done in pyodide's console.html.
It replaces sys.stdout by a StringIO object
pyodide.runPython(`
import sys
import io
sys.stdout = io.StringIO()
`);
Then run the python code (that can be completely oblivious to the fact that it is running in a wasm context)
pyodide.runPython(`
print("Hello, world!")
`);
Finally, send the contents of the stdout buffer to an output element
var stdout = pyodide.runPython("sys.stdout.getvalue()")
var div = document.createElement('div');
div.innerText = stdout;
document.body.appendChild(div);
To show print() calls form pyodide you can use the parameters on loadPyodide to redirect stdout:
var paragraph = document.getElementById("p");
pyodide = await loadPyodide({
indexURL : "https://cdn.jsdelivr.net/pyodide/v0.18.1/full/",
stdin: window.prompt,
stdout: (text) => {paragraph.textContent += text;},
stderr: (text) => {paragraph.textContent += text;}
});
https://github.com/pyodide/pyodide/blob/main/src/js/pyodide.js
I created a simple interactive shell for Python. Read my tutorial if you need more detailed information.
const output = document.getElementById("output")
const code = document.getElementById("code")
code.addEventListener("keydown", function (event) {
if (event.ctrlKey && event.key === "Enter") {
evaluatePython()
}
})
function addToOutput(s) {
output.value += `>>>${code.value}\n${s}\n`
output.scrollTop = output.scrollHeight
code.value=''
}
output.value = 'Initializing...\n'
// init pyodide
languagePluginLoader.then(() => { output.value += 'Ready!\n' })
function evaluatePython() {
pyodide.runPythonAsync(code.value)
.then(output => addToOutput(output))
.catch((err) => { addToOutput(err) })
}
<!DOCTYPE html>
<head>
<script type="text/javascript">
// this variable should be changed if you load pyodide from different source
window.languagePluginUrl = 'https://pyodide-cdn2.iodide.io/v0.15.0/full/';
</script>
<script src="https://pyodide-cdn2.iodide.io/v0.15.0/full/pyodide.js"></script>
</head>
<body>
Output:
</div>
<textarea id='output' style='width: 100%;' rows='10' disabled></textarea>
<textarea id='code' value='' rows='2'></textarea>
<button id='run' onclick='evaluatePython()'>Run</button>
<p>You can execute any Python code. Just enter something in the box above and click the button (or Ctrl+Enter).</p>
<div><a href='https://github.com/karray/truepyxel/demo.html'>Source code</a></div>
</body>
</html>
Here is the example for matplotlib. Note that this will load a bunch of dependencies which will take up to several minutes.
let python_code = `
from js import document
import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt
import io, base64
def generate_plot_img():
# get values from inputs
mu = int(document.getElementById('mu').value)
sigma = int(document.getElementById('sigma').value)
# generate an interval
x = np.linspace(mu - 3*sigma, mu + 3*sigma, 100)
# calculate PDF for each value in the x given mu and sigma and plot a line
plt.plot(x, stats.norm.pdf(x, mu, sigma))
# create buffer for an image
buf = io.BytesIO()
# copy the plot into the buffer
plt.savefig(buf, format='png')
buf.seek(0)
# encode the image as Base64 string
img_str = 'data:image/png;base64,' + base64.b64encode(buf.read()).decode('UTF-8')
# show the image
img_tag = document.getElementById('fig')
img_tag.src = img_str
buf.close()
`
languagePluginLoader.then(()=>pyodide.runPythonAsync(python_code).then(()=>document.getElementById('status').innerHTML='Done!'))
<!DOCTYPE html>
<head>
<script type="text/javascript">
// this variable should be changed if you load pyodide from different source
window.languagePluginUrl = 'https://pyodide-cdn2.iodide.io/v0.15.0/full/';
</script>
<script src="https://pyodide-cdn2.iodide.io/v0.15.0/full/pyodide.js"></script>
</head>
<body>
Status: <strong id='status'>Initializing...</strong>
<br><br>
mu:
<input id='mu' value='1' type="number">
<br><br>
sigma:
<input id='sigma' value='1' type="number">
<br><br>
<button onclick='pyodide.globals.generate_plot_img()'>Plot</button>
<br>
<img id="fig" />
</body>
</html>
Recently, I have a project is to implement the dynamic plotting on a web page.
Here is my python code
from flask import Flask
#from flask import render_template
import matplotlib.pyplot as plt
import io
import base64
app = Flask(__name__)
#app.route('/plot')
def build_plot():
img = io.BytesIO()
y = [1,2,3,4,5]
x = [0,2,1,3,4]
plt.plot(x,y)
plt.savefig(img, format='png')
img.seek(0)
plot_url = base64.b64encode(img.getvalue()).decode()
return '<img src="data:image/png;base64,{}">'.format(plot_url)
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
Here is my HTML code
<!DOCTYPE html>
<html>
<head>
<title> Plot</title>
<meta content='5; url=http://127.0.0.1:5000/plot' http-equiv='refresh'>
</head>
<body>
<img src="data:image/png;base64, {{ plot_url }}">
</body>
</html>
Although I add <meta content='5; url=http://127.0.0.1:5000/plot' http-equiv='refresh'> into my HTML code, it still can not refresh automatically.
If I want to observe the change of the plotting, I still need to refresh it manually.
Can anyone help me to solve this problem? Thanks a lot
You do not need to include the URL. Just use only the content attribute of meta tag.
<meta http-equiv="refresh" content="5" >
But to see the real time changes in your graph, you might want to look at sockets in flask. This will help server push changes to all clients using broadcasting. Using sockets is way better approach than to refresh your page at regular interval.
I have created a dashboard using python. I have a requirement to make some cosmetic changes in the html page.
Change the font and size
Changing the background color
Putting one company logo.
I researched with turtle and tkinter,and installed the same but the system is not recogonizing the modules. Is there a way to achieve the above functionality.
Source code is as below
#!/usr/local/bin/python
import requests
import json
import datetime
import sys
import os
from html import HTML
todayDate=datetime.date.today().strftime("%Y-%m-%d")
h=HTML('html','')
p=h.p('DETAILS for ',' ', todayDate)
t=h.table(border='1')
r=t.tr()
r.td('Import Timestamp')
r.td('JobId')
r.td('Status')
r.td('RecordsProcessed')
r.td('RecordsFailed')
r.td('FileName')
r.td('Duration')
r.td('Throughput')
print '\n'
def genHTMLforImportSuccess():
responseurl = requests.get(url)
if(responseurl.ok):
jData = json.loads(responseurl.content)
if jData > 0:
for responseurl in jData['response']:
starttime=responseurl['statistics']['startTime']
jobId= responseurl['jobId']
status = responseurl['status']
recordsProcessed=responseurl['statistics']['recordsProcessed']
recordsFailed=responseurl['statistics']['recordsFailed']
fileName=responseurl['fileName']
duration=responseurl['statistics']['duration']
throughput=responseurl['statistics']['throughput']
print '\n'
r=t.tr()
r.td(str(starttime))
r.td(str(jobId))
r.td(str(status))
r.td(str(recordsProcessed))
r.td(str(recordsFailed))
r.td(str(fileName))
r.td(str(duration))
r.td(str(throughput))
print '\n'
else:
print "No data feed"
else:
responseurl.raise_for_status()
genHTMLforImportSuccess()
print h
Any help is highly appreciated
I added the below code after the
from html import HTML
html = """
<!doctype html>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<title>testpage </title>
<style>
body {
background-color: aqua;
}
</style>
</head>
<body>
</body>
</html>
"""
but could not get the desired result.how can i fix this