I am in the process of making my first ever flask/python web app. The app initially displays a form which the user is invited to fill in, then they click on a "submit" button, then the server runs the simulation and creates a PNG file with a graph showing the results, then finally the page is redrawn with the graph displayed. My python code is roughly of this form:
# flask_app.py
#app.route("/", methods=["POST", "GET"])
def home():
if request.method == 'POST':
# bunch of request.form things to scoop the contents of the form
if form_answers_all_good:
for i in range(huge_number):
# some maths
# create png file with results
return render_template("index.htm", foo=bar)
The program is working just fine, but the huge_number loop can take several tens of seconds. So what I would like is some sort of progress indicator - it doesn't have to be a slick animation - even a string readout of the percentage progress would be fine.
Presumably I can change my for loop to something like...
for i in range(huge_number):
# some maths
percentage_done = str(i * 100/huge_number)
and then somehow arrange on the client side to read (poll?) percentage_done so that I put something like:
Completed {% percentage_done %}% so far.
in my index.htm. BTW, my knowledge of things like Javascript, AJAX or come to think of it, almost anything on the client side (apart from HTML) is beginner level.
I have seen a lot of explanations of similar sounding problems but generally they are doing far more complex things than I actually need and I fail to understand them because of my lack of client side knowledge. So for example some solutions might include a snippet of code and I won't actually know where to put it, or I won't know that something else needs to be loaded first in order for the snippet to work.
EDIT: I am hosting my web app on pythonanywhere.com. The list of included modules is here.
EDIT: pythonanywhere.com does not allow streaming :-(
You mention you're new to flask, so I am assuming you're new to flask but comfortable with python and also you are very constrained on what you can use because you're using pythonanywhere. One of the main things is it is single threaded so it makes really hard to scale anything. Also it would be better sticking with pure python as managing the dependencies in python anywhere would be an extra problem to care about, and in the end it's doable to do it using only python builtins.
I focused to show a working solution that you simple copy and paste in pythonanywhere or in your local, rather then showing snippets of code. I will try to:
show the working solution
describe how to replicate it
breakdown the main components and briefly explain
(1) The Working solution
you can access it here(I made a lot of restrictions to try to avoid people from breaking it). The solution just involves two files, flask_app.py and index.html
(1.1) solution code
"./home/{username}/{flask_foldername}/flask_app.py"
from queue import Queue
import time
import random
import threading
from PIL import Image
import flask
from flask import request
import json
import io
import uuid
import base64
### You create a Queue and start a scheduler, Start flask after that
def run_scheduler(app):
sleep_time = 5
while True:
time.sleep(sleep_time)
print("\n"*5)
print(f'images Completed:{app.images_completed}')
print('-----'*20)
if(app.images_toBe_processed.qsize() > 0):
next_image_name = app.images_toBe_processed.get()
print(f"No Images being processed so scheduler will start processing the next image {next_image_name} from the queue")
app.function_to_process_image(next_image_name, app)
else:
pass
def function_to_process_image(image_name, app):
huge_number = 5
R = random.randint(0,256)
G = random.randint(0,256)
B = random.randint(0,256)
for i in range(huge_number):
# some maths
percentage_done = str((i+1)*100/huge_number)
app.images_processing_status[image_name] = percentage_done
time.sleep(1)
app.images_processing_status[image_name] = str(100.0)
img = Image.new('RGB', (60, 30), color =(R,G,B))
b=io.BytesIO()
img.save(b, "jpeg")
app.images_completed[image_name] = {"status":1,"file": b}
print(f"IC from function: {app.images_completed} **************************")
if app.images_processing_status.get("!!total!!",False): app.images_processing_status["!!total!!"]+= 1
else: app.images_processing_status["!!total!!"] = 1
del app.images_processing_status[image_name]
return 0 #process sucessful
class Webserver(flask.Flask):
def __init__(self,*args,**kwargs):
scheduler_func = kwargs["scheduler_func"]
function_to_process_image = kwargs["function_to_process_image"]
queue_MAXSIZE = kwargs["queue_MAXSIZE"]
del kwargs["function_to_process_image"], kwargs["scheduler_func"], kwargs["queue_MAXSIZE"]
super(Webserver, self).__init__(*args, **kwargs)
self.start_time = time.strftime("%d/%m/%Y %H:%M")
self.queue_MAXSIZE = queue_MAXSIZE
self.active_processing_threads = []
self.images_processing_status = {}
self.images_completed = {}
self.images_toBe_processed = Queue(maxsize=queue_MAXSIZE)
self.function_to_process_image = function_to_process_image
self.scheduler_thread = threading.Thread(target=scheduler_func, args=(self,))
app = Webserver(__name__,
template_folder="./templates",
static_folder="./",
static_url_path='',
scheduler_func = run_scheduler,
function_to_process_image = function_to_process_image,
queue_MAXSIZE = 20,
)
### You define a bunch of views
#app.route("/",methods=["GET"])
def send_index_view():
if not flask.current_app.scheduler_thread.isAlive():
flask.current_app.scheduler_thread.start()
return flask.render_template('index.html',queue_size = flask.current_app.images_toBe_processed.qsize(),
max_queue_size =flask.current_app.queue_MAXSIZE , being_processed=len(flask.current_app.active_processing_threads),
total=flask.current_app.images_processing_status.get("!!total!!",0), start_time=flask.current_app.start_time )
#app.route("/process_image",methods=["POST"])
def receive_imageProcessing_request_view():
image_name = json.loads(request.data)["image_name"]
if(flask.current_app.images_toBe_processed.qsize() >= flask.current_app.queue_MAXSIZE ):
while(not flask.current_app.images_toBe_processed.empty()):
flask.current_app.images_toBe_processed.get()
requestedImage_status = {"name":image_name, "id":uuid.uuid1()}
flask.current_app.images_toBe_processed.put(image_name)
return flask.jsonify(requestedImage_status)
#app.route("/check_image_progress",methods=["POST"])
def check_image_progress():
print(f'Current Image being processed: {flask.current_app.images_processing_status}')
print(f'Current Images completed: {flask.current_app.images_completed}')
image_name = json.loads(request.data)["image_name"]
is_finished = flask.current_app.images_completed \
.get(image_name,{"status":0,"file": ''})["status"]
requestedImage_status = {
"is_finished": is_finished,
"progress": flask.current_app.images_processing_status.get(image_name,"0")
}
return flask.jsonify(requestedImage_status) #images_processing_status[image_name]})
#app.route("/get_image",methods=["POST"])
def get_processed_image():
image_name = json.loads(request.data)["image_name"]
file_bytes = flask.current_app.images_completed[image_name]["file"] #open("binary_image.jpeg", 'rb').read()
file_bytes = base64.b64encode(file_bytes.getvalue()).decode()
flask.current_app.images_completed.clear()
return flask.jsonify({image_name:file_bytes}) #images_processing_status[image_name]})
"./home/{username}/{flask_foldername}/templates/index.html"
<html>
<head>
</head>
<body>
<h5> welcome to the index page, give some inputs and get a random RGB image back after some time</h5>
<h5> Wait 10 seconds to be able to send an image request to the server </h5>
<h5>When the page was loaded there were {{queue_size}} images on the queue to be processed, and {{being_processed}} images being processed</h5>
<h5> The max size of the queue is {{max_queue_size}}, and it will be reseted when reaches it</h5>
<h5>A total of {{total}} images were processed since the server was started at {{start_time}}</h5>
<form>
<label for="name">Image name:</label><br>
<input type="text" id="name" name="name" value="ImageName" required><br>
</form>
<button onclick="send();" disabled>Send request to process image </button>
<progress id="progressBar" value="0" max="100"></progress>
<img style="display:block" />
<script>
window.image_name = "";
window.requests = "";
function send(){
var formEl = document.getElementsByTagName("form")[0];
var input = formEl.getElementsByTagName("input")[0];
var RegEx = /^[a-zA-Z0-9]+$/;
var Valid = RegEx.test(input.value);
if(Valid){
window.image_name = input.value;
var xhttp = new XMLHttpRequest();
xhttp.onload = function() {
result=JSON.parse(xhttp.response)
window.requests = setTimeout(check_image_progress, 3000);
};
xhttp.open("POST", "/process_image", true);
xhttp.send(JSON.stringify({"image_name":input.value}));
var buttonEl = document.getElementsByTagName("button")[0];
buttonEl.disabled = true;
buttonEl.innerHTML = "Image sent to process;only one image per session allowed";
}
else{
alert("input not valid, only alphanumeric characters");
}
}
function check_image_progress(){
var xhttp = new XMLHttpRequest();
xhttp.onload = function() {
result=JSON.parse(xhttp.response)
var progressBarEl = document.getElementsByTagName("progress")[0];
if(progressBarEl.value < result["progress"]){
progressBarEl.value=result["progress"];
} else {}
if(result["is_finished"] == true){
clearTimeout(window.requests);
window.requests = setTimeout(get_image,5);
}
else {
window.requests = setTimeout(check_image_progress, 3000);
}
};
xhttp.open("POST", "/check_image_progress", true);
xhttp.send(JSON.stringify({"image_name":window.image_name}));
}
function get_image(){
var xhttp = new XMLHttpRequest();
xhttp.onload = function() {
result=JSON.parse(xhttp.response)
img_base64 = result[window.image_name];
var progressBarEl = document.getElementsByTagName("progress")[0];
progressBarEl.value=100;
clearTimeout(window.requests);
var imgEl = document.getElementsByTagName("img")[0];
console.log(result)
imgEl.src = 'data:image/jpeg;base64,'+img_base64;
};
xhttp.open("POST", "/get_image", true);
xhttp.send(JSON.stringify({"image_name":window.image_name}));
}
setTimeout(function(){document.getElementsByTagName("button")[0].disabled=false;},100);
function hexToBase64(str) {
return btoa(String.fromCharCode.apply(null, str.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" ")));
}
</script>
</body>
</html>
(2) How to replicate, and create your own webapp
go to your web app tab in web
scroll down to find the source directory link
click on the flask_app.py and include the flask_app.py code
click on the templates directory, or create if doesn't exists
click on the index.html file and include the index.html code
go back to the web app tab and reload your app
(3) Main components of the app
Some details about pythonanywhere:
Pythoanywhere run a wsgi to start your flask app, it will basically import your app from the flask_app.py and run it.
by default wsgi is run in your /home/{username} folder and not in the /home/{username}/{flask_folder}, you can change this if you want.
Pythonanywhere is single-threaded so you can't rely on sending jobs to background.
The main components to watch out for in the backend:
1) Threads, Flask will be in the main Thread run by wsgi and we will run a child thread scheduler that will keep track of the Queue and schedule the next image to be processed.
2) Flask class: app, the component which handles user requests and send processing requests to the Queue
3) Queue, a Queue that stores in order the request from users to process images
4) Scheduler, The component that decides if a new function process_image call can be run and if yes. It needs to be run in an independent Thread than flask.
5) Encapsulate all those in a custom class Webserver to be able to easily access then (pythonanywhere uses wsgi which makes keeping track of variables created locally hard)
So taking look in the big picture of the code
#lot of imports
+-- 14 lines: from queue import Queue-----------------------------------------------------------------------------------------
# this function will check periodically if there's no images being processed at the moment.
# if no images are being processed check in the queue if there's more images to be processd
# and start the first one in the queue
def run_scheduler(app):
+-- 12 lines: sleep_time = 5 -------------------------------------------------------------------------------------------------
# this function do the math and creates an random RGB image in the end.
def function_to_process_image(image_name, app):
+-- 21 lines: {---------------------------------------------------------------------------------------------------------------
# This class encapsulates all the data structures("state") from our application
# in order to easily access the progress and images information
class Webserver(flask.Flask):
def __init__(self,*args,**kwargs):
+-- 13 lines: scheduler_func = kwargs["scheduler_func"]-----------------------------------------------------------------------
# Here we're instatiating the class
app = Webserver(__name__,
+-- 5 lines: template_folder="./templates",----------------------------------------------------------------------------------
queue_MAXSIZE = 20,
)
### You define a bunch of views
+-- 39 lines: #app.route("/",methods=["GET"]) --------------------------------------------------------------------------------
the main components of the frontend:
send function which is triggered when user clicks the send request to process image button
check_progress function which is triggered by send function to recurrently request the check_progress view in flask to get info about progress. When processing is over we remove the recurrence.
get_image function which is triggered by check_progress when processing is over ('is_finished' = 1)
big picture of the frontend:
<html>
<head>
</head>
<body>
<!-- JUST THE INITIAL HTML elements -->
+-- 12 lines: <h5> welcome to the index page, give some inputs and get a random RGB image back after some time</h5>-----------
<script>
window.image_name = "";
window.requests = "";
function send(){
// SEND image process request when click button and set a timer to call periodically check_image_process
+-- 20 lines: var formEl = document.getElementsByTagName("form")[0];----------------------------------------------------------
}
function check_image_progress(){
// SEND a request to get processing status for a certain image_name
+-- 18 lines: var xhttp = new XMLHttpRequest();-------------------------------------------------------------------------------
}
function get_image(){
// SEND a request to get the image when image_status 'is_processed' = 1
+--- 13 lines: var xhttp = new XMLHttpRequest();------------------------------------------------------------------------------
}
setTimeout(function(){document.getElementsByTagName("button")[0].disabled=false;},100);
</script>
</body>
</html>
You can create a simple stream using Flask stream_with_context and then use yield to send some data about the progress to the client using JSON format. You'll read that data using XMLHttpRequest: progress event and display the progress to the client or update an element with the image when the script is done.
Python Flask code:
Please read inline comments
import time
from flask import Flask, stream_with_context, request, Response
app = Flask(__name__, static_url_path='',static_folder='static')
#app.route('/')
def hello_world():
return app.send_static_file('index.html')
#app.route('/compute-image')
def compute_image():
def generate():
huge_number = 100000
progress = 0
# Start by sending total JSON value to the client stream
yield "{\"total\": %d}" % huge_number
for i in range(huge_number):
# If there is a 100 step progress, send progress JSON value to the client stream
if progress >= 100:
progress = 0
yield "{\"progress\": %d}" % (i)
# Else increment progress by 1 and sleep a bit for demonstration
else:
progress = progress + 1
time.sleep(0.0005)
# Send the generated image location to the client when we're done
yield "{\"img_url\": \"/some/generated/image.png\"}"
# Return the stream context with out generator function
return Response(stream_with_context(generate()))
Client Javascript code:
Please read inline comments
// Compute button
const btnEl = document.getElementById('compute');
// Progress label
const progEl = document.getElementById('progress');
// Time elapsed label
const timeEl = document.getElementById('time');
btnEl.addEventListener('click', function(e) {
btnEl.disabled = true;
progEl.textContent = '';
timeEl.textContent = '';
// Track the AJAX response buffer length
let prevBufferEnd = 0;
// Save the total (huge_number)
let total = 0;
// Save the time we've started
const timeStart = new Date;
const xhr = new XMLHttpRequest();
xhr.addEventListener('progress', function(e) {
// Get the current progress JSON data
let currentProgress = e.currentTarget.responseText.substring(prevBufferEnd, e.currentTarget.responseText.length);
prevBufferEnd = e.currentTarget.responseText.length;
// Parse JSON data
const respData = JSON.parse(currentProgress);
if (respData) {
// If there is a total, save to the total variable
if (respData['total']) {
total = respData.total;
// If there is a progress, display progress to the client
} else if (respData['progress']) {
const { progress } = respData;
const percent = (progress / total) * 100;
progEl.textContent = `${respData['progress']} of ${total} (${percent.toFixed(1)}%)`;
const timeSpan = (new Date) - timeStart;
timeEl.textContent = `${timeSpan / 1000} s`;
// Elese if there is an img_url, we've finished
} else if (respData['img_url']) {
progEl.textContent = `Done! Displaying image: "${respData['img_url']}"`;
}
}
});
xhr.addEventListener('loadend', function(e) {
btnEl.disabled = false;
});
xhr.open("GET", '/compute-image');
xhr.send();
});
Demo client screen capture:
You can find the demo project in this Github repository: https://github.com/clytras/py-flask-ajax-progress
Related
I'm sending my servers microphone's audio to the browser (mostly like this post but with some modified options).
All works fine, until you head over to a mobile or safari, where it doesn't work at all. I've tried using something like howler to take care of the frontend but with not success (still works in chrome and on the computer but not on the phones Safari/Chrome/etc). <audio> ... </audio> works fine in chrome but only on the computer.
function play_audio() {
var sound = new Howl({
src: ['audio_feed'],
format: ['wav'],
html5: true,
autoplay: true
});
sound.play();
}
How does one send a wav-generated audio feed which is 'live' that works in any browser?
EDIT 230203:
I have narrowed the error down to headers (at least what I think is causing the errors).
What headers should one use to make the sound available in all browsers?
Take this simple app.py for example:
from flask import Flask, Response, render_template
import pyaudio
import time
app = Flask(__name__)
#app.route('/')
def index():
return render_template('index.html', headers={'Content-Type': 'text/html'})
def generate_wav_header(sampleRate, bitsPerSample, channels):
datasize = 2000*10**6
o = bytes("RIFF",'ascii')
o += (datasize + 36).to_bytes(4,'little')
o += bytes("WAVE",'ascii')
o += bytes("fmt ",'ascii')
o += (16).to_bytes(4,'little')
o += (1).to_bytes(2,'little')
o += (channels).to_bytes(2,'little')
o += (sampleRate).to_bytes(4,'little')
o += (sampleRate * channels * bitsPerSample // 8).to_bytes(4,'little')
o += (channels * bitsPerSample // 8).to_bytes(2,'little')
o += (bitsPerSample).to_bytes(2,'little')
o += bytes("data",'ascii')
o += (datasize).to_bytes(4,'little')
return o
def get_sound(InputAudio):
FORMAT = pyaudio.paInt16
CHANNELS = 2
CHUNK = 1024
SAMPLE_RATE = 44100
BITS_PER_SAMPLE = 16
wav_header = generate_wav_header(SAMPLE_RATE, BITS_PER_SAMPLE, CHANNELS)
stream = InputAudio.open(
format=FORMAT,
channels=CHANNELS,
rate=SAMPLE_RATE,
input=True,
input_device_index=1,
frames_per_buffer=CHUNK
)
first_run = True
while True:
if first_run:
data = wav_header + stream.read(CHUNK)
first_run = False
else:
data = stream.read(CHUNK)
yield(data)
#app.route('/audio_feed')
def audio_feed():
return Response(
get_sound(pyaudio.PyAudio()),
content_type = 'audio/wav',
)
if __name__ == '__main__':
app.run(debug=True)
With a index.html looking like this:
<html>
<head>
<title>Test audio</title>
</head>
<body>
<button onclick="play_audio()">
Play audio
</button>
<div id="audio-feed"></div>
</body>
<script>
function play_audio() {
var audio_div = document.getElementById('audio-feed');
const audio_url = "{{ url_for('audio_feed') }}"
audio_div.innerHTML = "<audio controls><source src="+audio_url+" type='audio/x-wav;codec=pcm'></audio>";
}
</script>
</html>
Fire upp the flask development server python app.py and test with chrome, if you have a microphone you will hear the input sound (headphones preferably, otherwise you'll get a sound loop). Firefox works fine too.
But If you try the same app with any browser on an iPhone you'll get no sound, and the same goes for safari on MacOS.
There's no errors and you can see that the byte stream of the audio is getting downloaded in safari, but still no sound.
What is causing this? I think I should use some kind of headers in the audio_feed response but with hours of debugging I cannot seem to find anything for this.
I guess apple demands the implementation of RFC7233 HTTP Range Request for data without given content length. It seems otherwise the download stops after the first chunk.
For this you can add response header information like:
Accept-Ranges: bytes
Content-Range: bytes <start>-<end>/*
and return HTTP code 206 PARTIAL CONTENT. This way the browser is able to request the chunks of your media stream as soon as it needs them. Also you should evaluate the Range-attribute of each request, because the audio controls can be used to navigate in the stream. For this it will be necessary that you keep a defined amount of audio data in your server to satisfy this requirement. Also, your app should allow multi-threading.
There are discussions about streaming video content on SO like here or here. You can try to implement the same for your audio stream.
I know that this isn't the whole solution but I hope it points you in the right direction.
The problem
I am creating an Electron-React frontend for my Python script.
Electron and Python communicate via Node.js child_process module, using the function spawn to call my Python script.
The script does the following task:
Gets path to a folder, which contains 15 pdf files.
Loads 1 pdf file at a time.
Skims through it to find the first word of the 5th page.
Saves the result in a list.
Continues until all pdf files have been read.
As you can see, this takes some time to process this script.
What I want to do: Once the process is called from Electron, I want the progress bar to appear, indicating how many pdf files are processed out of the 15 given. For example: 1/15 done, 2/15 done, and etc.
I tried googling the answer but after a week I'm at my wits end. I would very much appreciate any help I could get.
Disclaimer: I know of other good packages like Python Eel which does the job (sort of, in a hackish way). But I really want it to work with CLI.
The setup
I used Electron React Boilerplate for my purposes.
For simplicity I used a dummy function for python:
assets/python/main.py
import sys
import time
# Track the progress
progress = 0
def doStuff():
# Access the global variable
global progress
# Simulate time taken to do stuff. Set at 30 seconds per iteration.
for i in range(15):
progress += 1
time.sleep(30)
# The result
return "It is done!"
# Print is used to get the result for stdout.
print(doStuff())
src/main/main.ts
import { spawn } from 'child_process';
import path from 'path';
import { app, BrowserWindow, ipcMain, webContents } from 'electron';
...
let mainWindow: BrowserWindow | null = null;
...
app
.whenReady()
.then(() => {
ipcMain.handle('readPdfs', async (event, arg) => {
// Spawn a python instance
const python = spawn('python', [
path.join(__dirname, '..', '..', 'assets', 'python', 'main.py')
]);
// Get the result and pass it on to the frontend
python.stdout.on('data', (data) => {
const result = data.toString('utf8');
mainWindow.webContents.send('output', result);
});
});
...
src/main/preload.ts
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';
...
let mainWindow: BrowserWindow | null = null;
...
contextBridge.exposeInMainWorld('electron', {
...
// Just sending a command forward and getting a response.
readPdfs: async () => ipcRenderer.invoke('readPdfs'),
readPdfsOutput: (callback: any) => ipcRenderer.on('output', callback),
});
src/renderer/App.tsx
const Hello = () => {
const [text, setText] = useState('');
const handleReadPdfs = async (): Promise<void> => {
await window.electron.readPdfs();
await window.electron.output((event: any, response: string) => {
setText(response);
});
};
The question
What I have set up works. I manage to get the "It is done!" message all right. The problem is I want to get the value progress from main.py every time it increments, while the process is busy reading the pdfs.
Is there a way to get that value from the python script using child_process without interrupting a time consuming process?
I have an HTML page that lets you upload a picture and displays it (very simple). But how do I send that particular loaded image into my flask app so that I could, say, perform some image processing on it using python's openCV or PIL packages?
Disclaimer
I've done similar thing recently, It may not be a 100% solution for you, but using parts of this code will propably solve all of your problems.
Flask Code
This part gets all files uploaded from browser and saves them with their respected extensions.
if request.method=="PUT":
for f in request.files.getlist("Files"): #<---- note 1
f.save(os.path.join(Path_To_Folder, f.filename))
return jsonify({"result": "res"}), 200 #status code is only thing important here
HTML
This just allows you to select file(s) from your device
<input type="file" id="file_loader" multiple/>
JavaScript
This code
const file_loader = document.getElementById("file_loader");
file_loader.onchange = async function(e){
alert("Upload started")
let sending = new FormData(); //creates form data object
//this for loop adds all files you selected to a form object
for (let i = 0; i < file_loader.files.length; i++) {
sending.append("Files", file_loader.files[i]); //<---- note 1
}
//this part just sends all the files to server
const podaci = await fetch(url_filesistem, {
method: "PUT",
body: sending,
})
.then((response) => response.json())
.then((pod) => {return pod;});
//removing all selected files from an input, so every time you want to select files
//its just those files, not ones previously selected
while (file_loader.length > 0) {
file_loader.pop();
}
alert("Finnished uploading")
}
Note 1
String "Files" mentioned in both lines needs to be the same in order for this method to work.
Advice
First save all the files on the server and then do the processing. I don't know what "f" object from for loop in python contains, therefore I don't know whether you can process them immediately.
Feel free to ask me anything regarding my code!
Best regards!
I have a multi-threaded python script that converts an image to a NumPy array (occupancy grid). The python script then continuously changes the values of the array to simulate a dot moving in a map. This array is converted back to an image using Pillow and then encoded to base64. The NodeJS part has an express server running that also has a SocketIO connection with a mobile app.
What I am trying to do is:
send the encoded image to the mobile app from the server, so the option that came to mind was to run the python script from nodeJs, and as the python script transmits an encoded image the server will redirect it to the mobile app.
What I'm looking for is:
stream the python script output to nodeJS without stopping (until the simulation stops)
or use a better-purposed solution to get the data from the python to the mobile phone
Thanks in advance!
python script to run:
import matplotlib.pyplot as plt
import cv2
import threading
import base64
from PIL import Image
temp = 0
global im_bw
pixel = 0
def update_image():
global im_bw
im2 = Image.fromarray(im_bw)
image64 = base64.b64encode(im2.tobytes())
# print the base64 encoded image to javascript
print(image64)
threading.Timer(1, update_image).start()
print("initialization")
im_gray = cv2.imread(r'gridMapBlue.JPG', cv2.IMREAD_GRAYSCALE)
(thresh, im_bw) = cv2.threshold(im_gray, 128, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
thresh = 127
im_bw = cv2.threshold(im_gray, thresh, 255, cv2.THRESH_BINARY)[1]
# noinspection PyBroadException
try:
update_image()
except Exception as e:
print(e.with_traceback())
pass
# simulate the dot moving by modifying the
while True:
if pixel == 200:
break
temp = im_bw[150,pixel]
im_bw[150, pixel] = 127
pixel += 1
Javascript code:
const express = require("express");
const app = express();
const io = require("./api/services/SocketIoService")
const spawn = require('child_process').spawn;
const socketIO = new io(app);
socketIO.server.listen(6969, () => console.log("server started"))
py = spawn('python',["E:\\QU\\Senior\\robot_positioning\\robot_position_map.py"])
py.stdout.on('data', (data)=>console.log(data));
py.stderr.on("error", err =>console.log(err))
image used
edit: added the code for creating the occupancy grid and the javascript code that I tried (but didn't work)
As wished op, not the 100% solution he want, but a working adapted concept.
main file is index.js:
const { spawn } = require("child_process");
const WebSocket = require("ws");
// create websocket server
// open "client.html" in a browser and see how the images flaps between two
const wss = new WebSocket.Server({
port: 8080
});
// feedback
wss.on("connection", function connection(ws) {
console.log("Client conneted to websocket");
});
// spawn python child process
const py = spawn("python", ["image.py"]);
console.log("Python image manipulating process has pid:", py.pid)
// listen for the new image
py.stdout.on("data", (data) => {
// broadcast the new binary image to all clients
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(data);
}
});
});
py.stderr.on("data", (data) => {
console.error(data.toString());
});
this communicate between the image manipluating image.py and clients (over WebSockets, should be easy to change that to socket.io if needed):
import time
import sys
toogle = True
while True:
if(toogle):
f = open("image1.jpg", "r")
else:
f = open("image2.jpg", "r")
toogle = not toogle
sys.stdout.write(f.read())
sys.stdout.flush()
time.sleep(2)
That file create/manipluate a image and push the image binary over stdout to the parent index.js. For demo purpose we switch between 2 images that get send to the client: "image1.jpg" and "image2.jpg" (use any jpg image you want, if you need other mimetypes/extensions set theme in the html client too).
As client i used a simple html document client.html:
When you open file it just shows a white/blank page, because no image was initzial loaded.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<canvas id="image">unsupported browser</canvas>
<script>
var ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => {
console.log("Connected")
};
ws.onmessage = (evt) => {
// re-create(?) blob data from websocket
var blob = new Blob([evt.data], {
type: 'image/jpg' // -> MIME TYPES OF IMAGES GOES HERE
});
// create blob url
var url = URL.createObjectURL(blob);
var image = document.getElementById("image");
var img = new Image(0, 0);
img.onload = () => {
// get canvas context
let ctx = image.getContext("2d");
ctx.clearRect(0, 0, image.width, image.height);
ctx.drawImage(img, 0, 0);
};
img.src = url;
};
</script>
</body>
</html>
The client renders the received image buffer on the page and refresh/redraw it if the python scripts "says so".
Install the only needed dependencie: npm install ws
To start everything type: node index.js and open the client in a webrowser of your choice (tested with firefox on ubuntu 18.04 LTS)
You should see that the image changes every second. With that working you can start to write the python script that "animate" your moving pixel :)
The script was edited inline here: perhaps its run not on the first hit.
I try it by "copy and paste" and fix the found issues (typos, wrong names, etc..)
If you need any help or changes, let me now.
I am attempting to implement a flask application for uploading files. This file could be very large. For example, almost 2G in size.
I have finished the server side process function like this:
#app.route("/upload/<filename>", methods=["POST", "PUT"])
def upload_process(filename):
filename = secure_filename(filename)
fileFullPath = os.path.join(application.config['UPLOAD_FOLDER'], filename)
with open(fileFullPath, "wb") as f:
chunk_size = 4096
while True:
chunk = flask.request.stream.read(chunk_size)
if len(chunk) == 0:
return
f.write(chunk)
return jsonify({'filename': filename})
As for browser side, I should give users a from to submit the file. One file at a time. Show progressbar to indicate the uploading process.
But I have no idea about the browser side code. How can I use javascript code to start the uploading and show it status?
This will be a difficult task for your to figure out on your own. I would suggest a plugin like https://blueimp.github.io/jQuery-File-Upload/
You can see from this projects source code they use a method name which essentially looks at how large the file is and how much data has been transferred thus far and how much remains to show a percentage complete div.
code example from this project
progressall: function (e, data) {
var $this = $(this);
$this.find('.fileupload-progress')
.find('.progress').progressbar(
'option',
'value',
parseInt(data.loaded / data.total * 100, 10)
).end()
.find('.progress-extended').each(function () {
$(this).html(
($this.data('blueimp-fileupload') ||
$this.data('fileupload'))
._renderExtendedProgress(data)
);
});
}
https://github.com/blueimp/jQuery-File-Upload/blob/master/js/jquery.fileupload-jquery-ui.js
So if you do want to come up with your own solution, I would suggest you start by building a UI div rectangle which has a dynamic width which updates according to your percentage calculation based upon the file upload size and data uploaded... or just go with an already established solution.