Flask: display newly generated image on webpage while continuing with function? - python

I have a webpage where the user uploads an image and my python code will make some adjustments to it and do some analysis. I want the newly generated image to be displayed to the user on the webpage as soon as it's generated, and then continue doing the analysis that needs to be done, and then update the webpage with that information. However, I am not sure how to communicate from flask to the webpage halfway through the function (once the new image is generated) that the website can display the newly generated image, as using render_template can only be done at the end of the function.
My python (flask) code is as follows:
#app.route('/uploaded', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
filename = secure_filename(file.filename)
f = request.files['file']
f.save(secure_filename(f.filename))
image = cv2.imread(filename)
im = Image.fromarray(image)
# make some adjustments to the image (not shown here) and then save it...
im.save(os.path.join(app.config['UPLOAD_FOLDER'], 'your_file.jpg'))
# after the new image is generated, display it on the website at {{ place_for_generated_image }}
# do some more analysis, then finally:
return render_template('index.html', analysis = analysis)
HTML is straightforward:
<form action = "http://localhost/uploaded" method = "POST"
enctype = "multipart/form-data">
<input type = "file" name = "file" class="form-control-file">
<input type = "submit" class="btn btn-info" value="Upload Image" button id="uploadfile" onclick="uploadfile()">
</form>
{{ place_for_generated_image }}
{{ analysis }}

You need to use multiple ajax calls to achieve that. Something like the below:
First make a route to handle the image upload
#app.route('/uploaded', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
filename = secure_filename(file.filename)
f = request.files['file']
f.save(secure_filename(f.filename))
image = cv2.imread(filename)
im = Image.fromarray(image)
# make some adjustments to the image (not shown here) and then save it...
im.save(os.path.join(app.config['UPLOAD_FOLDER'], 'your_file.jpg'))
# Here you need to convert your image to a base64 string and return that string to your ajax call
# do some more analysis, then finally:
return 'YOUR BASE64 ENCODED IMAGE STRING'
This route will handle your analysis. Your second nested ajax call will communicate with this route
from flask import Response
#app.route('/analyze', methods=['GET', 'POST'])
def analyze():
# run some analysis
return Response(status=200, response="Anlysis Done")
This what your javascript code should look like. You can place it in a script tag in your template. If you place it in a separate JS file, make sure to look at my comment for the url_for in the second ajax call
$('form').submit(function(e){
e.preventDefault();
$.ajax({
url: $(this).attr('action'),
type: 'POST',
data: $(this).serialize()
}).done(function(res){
// Here you write code that will display the returned base64 image
// from the upload_file route Just update your img tag src to the
// base64 string you received as the response
// SECOND AJAX CALL TO RUN THE ANALYTICS
$.ajax({
url: "{{url_for('analysis')}}", // if this JS code in a separate JS file you will have to declare a URL variable in a script tag in your template as {{url_for}} is only accessible from your template
type: 'POST',
data: 'any data you want to send can be in JSON format'
}).done(function(res){
// analysis is done
})
})
})

Related

How to display a bytes type image in HTML/Jinja2 template using FastAPI?

I have a FastAPI app that gets an image from an API. This image is stored in a variable with type: bytes.
I want to display the image in HTML/Jinja2 template (without having to download it). I followed many tutorials but couldn't find the solution.
Here is what I came up with so far:
#app.get("/{id}")
async def root(request: Request, id: str):
picture = await get_online_person()
data = base64.b64encode(picture) # convert to base64 as bytes
data = data.decode() # convert bytes to string
# str_equivalent_image = base64.b64encode(img_buffer.getvalue()).decode()
img_tag = '<img src="data:image/png;base64,{}">'.format(data)
return templates.TemplateResponse(
"index.html", {"request": request, "img": img_tag}
)
All I get in the HTML is this: (as text on the page, not from source code)
<img src="data:image/png;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQICAQECAQEBAgICAgICAgICAQICAgICAgICAgL/2wBDAQEBAQEBAQEBAQECAQEBAgICAgI
CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgL/wgARCAQABAADASIAA
hEBAxEB/8QAHgAAAQUBAQEBAQAAAAAAAAAABQIDBAYHAQgACQr/xAAcAQACAwEBAQEAAAAAAAAAAAACAwABBAUGBwj/2gAMAwEAAhADEAAAAfEpwSR+a+9IPR3c7347iwscmWyYchEIJjn+MbJj/c4FFbbb9J5....................
Note: For people who are marking my question to a duplicate talking about urllib, I cannot use urllib because the image I'm getting is from ana API, and using their direct url will result in a 403 Forbidden, so I should use their python API to get the image.
On server side—as shown in the last section of this answer—you should return only the base64-encoded string in the context of the TemplateResponse (without using the <img> tag, as shown in your question):
# ...
base64_encoded_image = base64.b64encode(image_bytes).decode("utf-8")
return templates.TemplateResponse("index.html", {"request": request, "myImage": base64_encoded_image})
On client side, you could display the image as follows:
<img src="data:image/jpeg;base64,{{ myImage | safe }}">
Alternative approaches can be found here.

Python Flask Output to Webpage while processing continues [duplicate]

I have a view that generates data and streams it in real time. I can't figure out how to send this data to a variable that I can use in my HTML template. My current solution just outputs the data to a blank page as it arrives, which works, but I want to include it in a larger page with formatting. How do I update, format, and display the data as it is streamed to the page?
import flask
import time, math
app = flask.Flask(__name__)
#app.route('/')
def index():
def inner():
# simulate a long process to watch
for i in range(500):
j = math.sqrt(i)
time.sleep(1)
# this value should be inserted into an HTML template
yield str(i) + '<br/>\n'
return flask.Response(inner(), mimetype='text/html')
app.run(debug=True)
You can stream data in a response, but you can't dynamically update a template the way you describe. The template is rendered once on the server side, then sent to the client.
One solution is to use JavaScript to read the streamed response and output the data on the client side. Use XMLHttpRequest to make a request to the endpoint that will stream the data. Then periodically read from the stream until it's done.
This introduces complexity, but allows updating the page directly and gives complete control over what the output looks like. The following example demonstrates that by displaying both the current value and the log of all values.
This example assumes a very simple message format: a single line of data, followed by a newline. This can be as complex as needed, as long as there's a way to identify each message. For example, each loop could return a JSON object which the client decodes.
from math import sqrt
from time import sleep
from flask import Flask, render_template
app = Flask(__name__)
#app.route("/")
def index():
return render_template("index.html")
#app.route("/stream")
def stream():
def generate():
for i in range(500):
yield "{}\n".format(sqrt(i))
sleep(1)
return app.response_class(generate(), mimetype="text/plain")
<p>This is the latest output: <span id="latest"></span></p>
<p>This is all the output:</p>
<ul id="output"></ul>
<script>
var latest = document.getElementById('latest');
var output = document.getElementById('output');
var xhr = new XMLHttpRequest();
xhr.open('GET', '{{ url_for('stream') }}');
xhr.send();
var position = 0;
function handleNewData() {
// the response text include the entire response so far
// split the messages, then take the messages that haven't been handled yet
// position tracks how many messages have been handled
// messages end with a newline, so split will always show one extra empty message at the end
var messages = xhr.responseText.split('\n');
messages.slice(position, -1).forEach(function(value) {
latest.textContent = value; // update the latest value in place
// build and append a new item to a list to log all output
var item = document.createElement('li');
item.textContent = value;
output.appendChild(item);
});
position = messages.length - 1;
}
var timer;
timer = setInterval(function() {
// check the response for new data
handleNewData();
// stop checking once the response has ended
if (xhr.readyState == XMLHttpRequest.DONE) {
clearInterval(timer);
latest.textContent = 'Done';
}
}, 1000);
</script>
An <iframe> can be used to display streamed HTML output, but it has some downsides. The frame is a separate document, which increases resource usage. Since it's only displaying the streamed data, it might not be easy to style it like the rest of the page. It can only append data, so long output will render below the visible scroll area. It can't modify other parts of the page in response to each event.
index.html renders the page with a frame pointed at the stream endpoint. The frame has fairly small default dimensions, so you may want to to style it further. Use render_template_string, which knows to escape variables, to render the HTML for each item (or use render_template with a more complex template file). An initial line can be yielded to load CSS in the frame first.
from flask import render_template_string, stream_with_context
#app.route("/stream")
def stream():
#stream_with_context
def generate():
yield render_template_string('<link rel=stylesheet href="{{ url_for("static", filename="stream.css") }}">')
for i in range(500):
yield render_template_string("<p>{{ i }}: {{ s }}</p>\n", i=i, s=sqrt(i))
sleep(1)
return app.response_class(generate())
<p>This is all the output:</p>
<iframe src="{{ url_for("stream") }}"></iframe>
5 years late, but this actually can be done the way you were initially trying to do it, javascript is totally unnecessary (Edit: the author of the accepted answer added the iframe section after I wrote this). You just have to include embed the output as an <iframe>:
from flask import Flask, render_template, Response
import time, math
app = Flask(__name__)
#app.route('/content')
def content():
"""
Render the content a url different from index
"""
def inner():
# simulate a long process to watch
for i in range(500):
j = math.sqrt(i)
time.sleep(1)
# this value should be inserted into an HTML template
yield str(i) + '<br/>\n'
return Response(inner(), mimetype='text/html')
#app.route('/')
def index():
"""
Render a template at the index. The content will be embedded in this template
"""
return render_template('index.html.jinja')
app.run(debug=True)
Then the 'index.html.jinja' file will include an <iframe> with the content url as the src, which would something like:
<!doctype html>
<head>
<title>Title</title>
</head>
<body>
<div>
<iframe frameborder="0"
onresize="noresize"
style='background: transparent; width: 100%; height:100%;'
src="{{ url_for('content')}}">
</iframe>
</div>
</body>
When rendering user-provided data render_template_string() should be used to render the content to avoid injection attacks. However, I left this out of the example because it adds additional complexity, is outside the scope of the question, isn't relevant to the OP since he isn't streaming user-provided data, and won't be relevant for the vast majority of people seeing this post since streaming user-provided data is a far edge case that few if any people will ever have to do.
Originally I had a similar problem to the one posted here where a model is being trained and the update should be stationary and formatted in Html. The following answer is for future reference or people trying to solve the same problem and need inspiration.
A good solution to achieve this is to use an EventSource in Javascript, as described here. This listener can be started using a context variable, such as from a form or other source. The listener is stopped by sending a stop command. A sleep command is used for visualization without doing any real work in this example. Lastly, Html formatting can be achieved using Javascript DOM-Manipulation.
Flask Application
import flask
import time
app = flask.Flask(__name__)
#app.route('/learn')
def learn():
def update():
yield 'data: Prepare for learning\n\n'
# Preapre model
time.sleep(1.0)
for i in range(1, 101):
# Perform update
time.sleep(0.1)
yield f'data: {i}%\n\n'
yield 'data: close\n\n'
return flask.Response(update(), mimetype='text/event-stream')
#app.route('/', methods=['GET', 'POST'])
def index():
train_model = False
if flask.request.method == 'POST':
if 'train_model' in list(flask.request.form):
train_model = True
return flask.render_template('index.html', train_model=train_model)
app.run(threaded=True)
HTML Template
<form action="/" method="post">
<input name="train_model" type="submit" value="Train Model" />
</form>
<p id="learn_output"></p>
{% if train_model %}
<script>
var target_output = document.getElementById("learn_output");
var learn_update = new EventSource("/learn");
learn_update.onmessage = function (e) {
if (e.data == "close") {
learn_update.close();
} else {
target_output.innerHTML = "Status: " + e.data;
}
};
</script>
{% endif %}

HTML form doesn't want to send an image

I have a complicated task and for now I don't know reason why things don't work correctly.
So I have 2 services - my Django main server and OCR service. OCR service is built with FastAPI and only takes image, processes it and returns response as JSON with data from image. That's how my fastapi file looks like:
from fastapi import FastAPI, File, UploadFile, Request
import celery_launch
from cleaning_folders import cleaning_folder
from config import save_img_from_form
f_app = FastAPI()
def save_img_from_form(image):
ts = time.time() * 1000
digit = random.randint(0, 9)
file_name = "img_{digit}_{ts}.jpg".format(digit=digit, ts=ts)
with open(os.path.join('temp_img', file_name), 'wb') as buffer:
shutil.copyfileobj(image.file, buffer)
return buffer
#f_app.post("/api/ocr")
async def send_request(image: UploadFile = File(default='Any', media_type='multipart/form-data')):
buffer = save_img_from_form(image)
response = celery_launch.ocr_process(
selected_town='Templates/Chernomorsk',
raw_img_path=buffer.name,
selected_billing_type=1
)
json_response = response.get()
cleaning_folder('temp_img')
return json_response
so save_img_from_form() gets an image object from request and saves it to the disk for next processing. Then celery runs and does all OCR process and then returns a dict.
So when I use Swagger UI as interactive API testing service, it all works, so in Swagger UI I can load my image through html input and then click the button to run my endpoint. And then I get right JSON as response. I also checked the network activity (ctrl+shift+I in Chrome) and in network action, which is linked to my endpoint I see lots of stuff about my request and also form data (image ofc).
But the question is in another. I need to load an image at my Django server and then using requests library send request with this file to FastAPI service. But here I faced with lots of troubles. I have such an html form:
<form method="POST" action="{% url 'send_ocr_form' %}" enctype="multipart/form-data">
{% csrf_token %}
<input type="hidden" name="pk" value="{{pk}}">
<input type="file" id="ocr_image" name="ocr_image" accept=".jpg, .jpeg"><br>
<button type="submit">Recognize</button>
</form>
Hidden field with name="pk" is needed for some service stuff. So for loading an image I have a field with name="ocr_image".
My view for this form is:
def send_ocr_bill_form(request):
image = request.FILES['ocr_image'].temporary_file_path()
ocr_response = send_ocr_bill(image=image)
files = {'file': open(image, 'rb')}
ocr_response = requests.post('http://127.0.0.1:5005/api/ocr', files=files)
print(ocr_response.json())
return redirect(request.META.get('HTTP_REFERER', '/accounts/profile_page/'))
It's not the full functionality of this view, it just not ended because of current trouble.
So when I send my form with image FastAPI uses default='Any' as an image variable, so that means that there are no files in request. And when I check network I also don't see 'form data' block in my request. So it seems like I don't send image. But in my view I can easily get and print an object from request.FILES, see its name and temporary path, so that means that I load image and send it in my request.
And interesting fact. When I delete my file input and leave only csrf token and pk hidden input and then submit form, in my request in network I can see form data and then get my "pk" in a view. But when I return file input, form data block disappears.
So many hours spent to solve the issue and answer was so easy.
Well, my fail was with requests library.
Let's look at my FastAPI's endpoint first string:
async def send_request(image: UploadFile = File(default='Any', media_type='multipart/form-data')):
So i define 'image' as an UploadFile object, and of course my endpoint waits for a file in request, but when I send image in a Django view I do this:
files = {'file': open(image, 'rb')}
ocr_response = requests.post('http://127.0.0.1:5005/api/ocr', files=files)
So in 'files' variable I also use 'file' key to assign image to it.
And all I needed to change is:
files = {'image': open(image, 'rb')}
ocr_response = requests.post('http://127.0.0.1:5005/api/ocr', files=files)
Now when FastAPI endpoint gets a request, it takes image argument from the right place and then I can do all stuff with it.

Is it possible to provide the user with status reports from Flask during a looping python function? [duplicate]

I have a view that generates data and streams it in real time. I can't figure out how to send this data to a variable that I can use in my HTML template. My current solution just outputs the data to a blank page as it arrives, which works, but I want to include it in a larger page with formatting. How do I update, format, and display the data as it is streamed to the page?
import flask
import time, math
app = flask.Flask(__name__)
#app.route('/')
def index():
def inner():
# simulate a long process to watch
for i in range(500):
j = math.sqrt(i)
time.sleep(1)
# this value should be inserted into an HTML template
yield str(i) + '<br/>\n'
return flask.Response(inner(), mimetype='text/html')
app.run(debug=True)
You can stream data in a response, but you can't dynamically update a template the way you describe. The template is rendered once on the server side, then sent to the client.
One solution is to use JavaScript to read the streamed response and output the data on the client side. Use XMLHttpRequest to make a request to the endpoint that will stream the data. Then periodically read from the stream until it's done.
This introduces complexity, but allows updating the page directly and gives complete control over what the output looks like. The following example demonstrates that by displaying both the current value and the log of all values.
This example assumes a very simple message format: a single line of data, followed by a newline. This can be as complex as needed, as long as there's a way to identify each message. For example, each loop could return a JSON object which the client decodes.
from math import sqrt
from time import sleep
from flask import Flask, render_template
app = Flask(__name__)
#app.route("/")
def index():
return render_template("index.html")
#app.route("/stream")
def stream():
def generate():
for i in range(500):
yield "{}\n".format(sqrt(i))
sleep(1)
return app.response_class(generate(), mimetype="text/plain")
<p>This is the latest output: <span id="latest"></span></p>
<p>This is all the output:</p>
<ul id="output"></ul>
<script>
var latest = document.getElementById('latest');
var output = document.getElementById('output');
var xhr = new XMLHttpRequest();
xhr.open('GET', '{{ url_for('stream') }}');
xhr.send();
var position = 0;
function handleNewData() {
// the response text include the entire response so far
// split the messages, then take the messages that haven't been handled yet
// position tracks how many messages have been handled
// messages end with a newline, so split will always show one extra empty message at the end
var messages = xhr.responseText.split('\n');
messages.slice(position, -1).forEach(function(value) {
latest.textContent = value; // update the latest value in place
// build and append a new item to a list to log all output
var item = document.createElement('li');
item.textContent = value;
output.appendChild(item);
});
position = messages.length - 1;
}
var timer;
timer = setInterval(function() {
// check the response for new data
handleNewData();
// stop checking once the response has ended
if (xhr.readyState == XMLHttpRequest.DONE) {
clearInterval(timer);
latest.textContent = 'Done';
}
}, 1000);
</script>
An <iframe> can be used to display streamed HTML output, but it has some downsides. The frame is a separate document, which increases resource usage. Since it's only displaying the streamed data, it might not be easy to style it like the rest of the page. It can only append data, so long output will render below the visible scroll area. It can't modify other parts of the page in response to each event.
index.html renders the page with a frame pointed at the stream endpoint. The frame has fairly small default dimensions, so you may want to to style it further. Use render_template_string, which knows to escape variables, to render the HTML for each item (or use render_template with a more complex template file). An initial line can be yielded to load CSS in the frame first.
from flask import render_template_string, stream_with_context
#app.route("/stream")
def stream():
#stream_with_context
def generate():
yield render_template_string('<link rel=stylesheet href="{{ url_for("static", filename="stream.css") }}">')
for i in range(500):
yield render_template_string("<p>{{ i }}: {{ s }}</p>\n", i=i, s=sqrt(i))
sleep(1)
return app.response_class(generate())
<p>This is all the output:</p>
<iframe src="{{ url_for("stream") }}"></iframe>
5 years late, but this actually can be done the way you were initially trying to do it, javascript is totally unnecessary (Edit: the author of the accepted answer added the iframe section after I wrote this). You just have to include embed the output as an <iframe>:
from flask import Flask, render_template, Response
import time, math
app = Flask(__name__)
#app.route('/content')
def content():
"""
Render the content a url different from index
"""
def inner():
# simulate a long process to watch
for i in range(500):
j = math.sqrt(i)
time.sleep(1)
# this value should be inserted into an HTML template
yield str(i) + '<br/>\n'
return Response(inner(), mimetype='text/html')
#app.route('/')
def index():
"""
Render a template at the index. The content will be embedded in this template
"""
return render_template('index.html.jinja')
app.run(debug=True)
Then the 'index.html.jinja' file will include an <iframe> with the content url as the src, which would something like:
<!doctype html>
<head>
<title>Title</title>
</head>
<body>
<div>
<iframe frameborder="0"
onresize="noresize"
style='background: transparent; width: 100%; height:100%;'
src="{{ url_for('content')}}">
</iframe>
</div>
</body>
When rendering user-provided data render_template_string() should be used to render the content to avoid injection attacks. However, I left this out of the example because it adds additional complexity, is outside the scope of the question, isn't relevant to the OP since he isn't streaming user-provided data, and won't be relevant for the vast majority of people seeing this post since streaming user-provided data is a far edge case that few if any people will ever have to do.
Originally I had a similar problem to the one posted here where a model is being trained and the update should be stationary and formatted in Html. The following answer is for future reference or people trying to solve the same problem and need inspiration.
A good solution to achieve this is to use an EventSource in Javascript, as described here. This listener can be started using a context variable, such as from a form or other source. The listener is stopped by sending a stop command. A sleep command is used for visualization without doing any real work in this example. Lastly, Html formatting can be achieved using Javascript DOM-Manipulation.
Flask Application
import flask
import time
app = flask.Flask(__name__)
#app.route('/learn')
def learn():
def update():
yield 'data: Prepare for learning\n\n'
# Preapre model
time.sleep(1.0)
for i in range(1, 101):
# Perform update
time.sleep(0.1)
yield f'data: {i}%\n\n'
yield 'data: close\n\n'
return flask.Response(update(), mimetype='text/event-stream')
#app.route('/', methods=['GET', 'POST'])
def index():
train_model = False
if flask.request.method == 'POST':
if 'train_model' in list(flask.request.form):
train_model = True
return flask.render_template('index.html', train_model=train_model)
app.run(threaded=True)
HTML Template
<form action="/" method="post">
<input name="train_model" type="submit" value="Train Model" />
</form>
<p id="learn_output"></p>
{% if train_model %}
<script>
var target_output = document.getElementById("learn_output");
var learn_update = new EventSource("/learn");
learn_update.onmessage = function (e) {
if (e.data == "close") {
learn_update.close();
} else {
target_output.innerHTML = "Status: " + e.data;
}
};
</script>
{% endif %}

Django download not working with browser, but works fine in machines where Internet Download Manager(IDM) is installed

My django project uses the following code to download the file. It works all good if the client machine has IDM installed but fails to work if IDM is not installed. I couldn't find any reason for this weirdness.
views.py
def somefunction():
something something
return render(request,
'something/download/download.html',
{'pdf_file_location': pdf_file_location})
def download(request):
if not request.user.is_authenticated():
return render(request, 'login/login/login.html')
else:
filename = request.POST.get('pdf_file_location')
if request.method == 'POST':
while os.path.exists(filename) is False:
time.sleep(2)
chunk_size = 8192
response = StreamingHttpResponse(FileWrapper(open(filename, 'rb'), chunk_size),
content_type=mimetypes.guess_type(filename)[0])
response['Content-Length'] = os.path.getsize(filename)
response['Content-Disposition'] = "attachment; filename=%s" % filename[filename.find("UserSessionDetails-")+19:]
return response
return render(request, 'something/something/index.html')
download.html
<canvas id="c-timer" width="300" height="300">
<input id="pdf_file_location" type="hidden" value={{ pdf_file_location }} name="pdf_file_location"/>
</canvas>
js for the download.html
var val = document.getElementById('pdf_file_location').value
data ={"pdf_file_location": val};
something something and then finishTime is called
var finishTime = function () {
$.post( "/book_publish/download/",data);
};
I don't have much knowledge about how IDM works, but reading this article tells me that it shouldn't give any upper hand apart from the fact that it opens multiple connections for the operation, and my code is sending data in chunks. Is it that the browser can't stitch the data when it is sent in small chunks?
PROBLEM: The problem was I was using JS to post a request for the download, and since I'm a newbie at web, so I couldn't handle the request sent back. Hence it got all messed up.
And somehow IDM was able to catch that response and initiate the download process.
SOLUTION: I used a simple form post and submit button in the HTML itself and not use the JS for post request.

Categories