I am currently developing a web interface where you can draw a digit and upload it. The image goes through a machine learning model of digit recognition and return the digit that has been transcribed by the user.
HTML Code :
<canvas id="myCanvas">
Sorry, your browser does not support HTML5 canvas technology.
</canvas>
<button onclick="clickonbutton()">Upload</button>
Canvas is used to draw digit and button to send it to a flask web server.
Javascript Code :
<script type="text/javascript">
function clickonbutton() {
var canvas = document.getElementById('myCanvas');
//Convert HTML5 Canvas to bytes-like object then File object
canvas.toBlob(function(blob) {
var file = new File([blob], "image.png", { type: "image/png", lastModified : new Date()});
//Prepare a XMLHttpRequest to send the data with POST method to the server
var request = new XMLHttpRequest();
request.open("POST", "/");
request.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
//Sending file...
request.send(file);
});
}
</script>
When the user click on the button "Upload" after editing the HTML5 canvas, it run this function which send the file to the server.
Python script (server-side) :
#app.route('/', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
#Convert MultiDict to Dict object then string
data = str(request.form.to_dict())
#Format string to remove Dict elements
data = data.replace("{","")
data = data.replace("}","")
data = data.replace("'","")
data = data.replace(": ","")
#Convert string to bytes-like object
img_bytes = data.encode()
#Predict the digit of the user
_, predicted_class, probabilities = get_prediction(image_bytes=img_bytes)
predicted_class = int(predicted_class)
classify(img=_, ps=probabilities)
#Return the result of the digit prediction
return render_template('result.html',class_name=predicted_class)
return render_template('index.html')
On server-side, i get the data from request.form but these are in a ImmutableMultiDict object so i converted the data to string then bytes-like object. Unfortunately, conversion using the .replace() method corrupts the PNG file and there's no tool to do that conversion properly...
Is there a solution to get the data directly in a File object without using an ImmutableMultiDict object ?
So with help from this thread on using the Fetch API and this answer on HTML5 canvas drawing I figured this could be done with something like:
<div id='sketch'>
<canvas id="myCanvas">
</canvas>
</div>
<button id='btn'>Upload</button>
<div id='result' style='font-size:2em;'></div>
Notice I've added a div to output the results (the class name) on the same page.
Then in the Javascript use a FormData object: (I've missed out the drawing function for clarity):
// Define the dom elements
const canvas = document.getElementById('myCanvas');
const btn = document.getElementById('btn');
const result = document.getElementById('result');
// This will handle the upload
const upload = (file) => {
fetch('/', { // Your POST endpoint
method: 'POST',
body: file // This is your file object
}).then(
response => response.json() // if the response is a JSON object
).then(
success => { result.innerText = success['class_name']; } // Write class_name into results div.
).catch(
error => console.log(error) // Handle the error response object
);
};
// Listener which handles the button click:
btn.addEventListener('click', function() {
canvas.toBlob(function(blob) {
var data = new FormData();
data.append('file', blob);
upload(data);
});
}, false);
Now on the server side, this can be handled like a regular Flask file upload:
#app.route('/', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
file = request.files['file']
#Convert string to bytes-like object
img_bytes = file.read()
#Predict the digit of the user
_, predicted_class, probabilities = get_prediction(image_bytes=img_bytes)
predicted_class = int(predicted_class)
classify(img=_, ps=probabilities)
#Return the result of the digit prediction
return {'class_name': predicted_class}
return render_template('index.html')
Any post request now returns a jsonified dictionary, which is in turn written to the results div on the page.
There's probably room for improvment here, like some validation on the server-side aswell as rate limiting and error handling, but the basic concept works.
Related
I am trying to send a pdf file from javascript to a rest wcf service.
The service expects an array of byte with the following signature
The trick is in the byte array parameter, all the others are working fine
[OperationContract]
[WebInvoke(UriTemplate = "rest/{sessionToken}/ImportNewTemplate?commit={commit}&createApplication={createApplication}&templateName={templateName}&option={option}")]
[CloudMethod(Group = "02. Templates", Description = "Import a new template in the platform.", HelpFile = "ListPaperTemplate.aspx")]
[CloudParameter(Name = "sessionToken", Description = "session token", HelpFile = "ServiceAPIDoc.aspx?q=sessionToken")]
[CloudParameter(Name = "createApplication", Description = "Create a standalone application linked to this template.")]
[CloudParameter(Name = "commit", Description = "Commit the upload ? if true, the template will be imported, else the return just allow you to preview template description.")]
[CloudParameter(Name = "templateName", Description = "Name of the new template. Only valid for single pdf upload. If the files are zipped, the file name in the zip will be used instead")]
[CloudParameter(Name = "templateFile", Description = "Can be a PDF file, or a zip file containing a flat pdf + xml definition", HelpFile = "ServiceAPIDoc.aspx?q=templateFile")]
CloudObjects.TemplateImportation ImportNewTemplate(string sessionToken, bool commit, bool createApplication, byte[] templateFile, string templateName, string option);
this is what I use from the javascript end to send the pdf file
const file = e.target.files[0];
// Encode the file using the FileReader API
const reader = new FileReader();
var fileByteArray = [];
reader.onloadend = async (e) => {
const arrayBuffer = e.target.result,
array = new Uint8Array(arrayBuffer);
for (const a of array) {
console.log(a);
fileByteArray.push(a);
}
let ret = await dispatch('createTemplate', {name: this.newForm.name, pdf:fileByteArray, save:false});
await this.$store.dispatch('hideLoadingScreen')
// Logs data:<type>;base64,wL2dvYWwgbW9yZ...
};
reader.onerror = async () => {
await this.$store.dispatch('hideLoadingScreen')
}
reader.onabort = async () => {
await this.$store.dispatch('hideLoadingScreen')
}
await this.$store.dispatch('showLoadingScreen');
reader.readAsArrayBuffer(file);
And here is the code to send it to the rest service
let url = `${getters.getServiceUrl}ImportNewTemplate?templateName=${name}&commit=${save || true}`
const xhr = new XMLHttpRequest;
xhr.open("POST", url, false);
xhr.setRequestHeader('Content-Type', 'application/json');
let response = await xhr.send(pdf);
However every time I get an error from the service when it tries to deserialise the byte array.
The exception message is 'There was an error deserializing the object of type System.Byte[]. End element 'root' from namespace '' expected.
I have tried a lot of alternatives but nothing works.
Any suggestions are welcome !
Thanks
For those interested, the trick was to add JSON.stringify to the returned array.
So: xhr.send(JSON.stringify(pdf))
would do the trick
I'm working on a REST web application that manages documents between users and uploaders. The backend is written in Java and my Document entity contains, besides various attributes, a byte[] content. I was able to send a file created at server side by
#GET
...
document.setContent(Files.readAllBytes(Paths.get("WEB-INF/testFile.txt")));
return Response.ok(document).build();
and retrieve it at front-end (vueJs) through
async function download(file) {
const fileURL = window.URL.createObjectURL(new Blob([atob(file.content)]));
const fileLink = document.createElement("a");
fileLink.href = fileURL;
fileLink.setAttribute("download",`${file.name}.${file.type}`);
document.body.appendChild(fileLink);
fileLink.click();
fileLink.remove;
window.URL.revokeObjectURL(fileURL);
}
the problem is that when I try to upload a file and then download it, its content is not parsed correctly (is shown undefined, string in Base64 or numbers depending on how I try to solve it). The file is sent by a post request and is retrieved through an input form bound to an onFileSelected function.
function onFileSelected(e) {
var reader = new FileReader();
reader.readAsArrayBuffer(e.target.files[0]);
reader.onloadend = (evt) => {
if (evt.target.readyState === FileReader.DONE) {
var arrayBuffer = evt.target.result;
this.file.content = new Uint8Array(arrayBuffer);
//this.file.content = arrayBuffer;
}
};
}
axios.post(...,document,...)
and I have tried using atob and btoa as well before assigning the value to this.file.content. If I print the file on server Welcome.txt it gives B#ae3b74d and if I use Arrays.toString(welcome.getContent()) it gives an array of numbers but as soon as it passed to the frontend its content become in Base64 welcome: { ... content: IFRoaXMgaXMgYSB0ZXN0IGZpbGUhIAo...}. Any idea? Thank you a lot!
Hi I currently have a form to select a file for upload to an endpoint that requires the payload to be encoded as JSON.
Currently, I have the following form but it is multipart/form-data. I do not think it is possible to encode a form submission as application/json without resorting to something like ajax....
<form action = "/upload" method = "POST"
enctype = "multipart/form-data">
<input type = "file" name = "file" />
<input type = "submit"/>
</form>
Is it possible using ajax to read the file selected in the form and send the contents as a json payload to the POST url endpoint? Can anyone help?
It's possible by using the FileReader API to parse the file to a string and send the string to the server as encoded JSON.
<form id="upload-form" action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit"/>
</form>
First of we'll need a function that takes in a File and parses it to a string with the FileReader. The readFileAsText function returns a Promise which will resolve once the file has been parsed as text.
The result should be the content of the file as a string. That string is the JSON that we'll send to the server.
const readFileAsText = file => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = ({ target }) => {
resolve(target.result);
};
reader.readAsText(file);
});
Edit: nowadays you can perform this same process of reading the file as string with the Blob.text() method. A File instance has this method as well and is used like this:
/**
* In this context the file variable is an instance of File.
*/
// In an async context
const text = await file.text();
// Outside an async context.
file.text().then(text => {
// handle the text.
});
Next will be the AJAX part of sending your payload to the server. We do this by using the Fetch API. Here we specify the content type in the headers and create a POST request with a payload that contains JSON.
After the request is made it checks the response from the server and expects a JSON response from the server. Be sure to return a JSON response from the server side.
const sendJSON = async json => {
const headers = {
'Content-Type': 'application/json'
};
const response = await fetch('/upload', {
headers,
method: 'POST',
body: json
});
if (!response.ok) {
throw new Error(`Sending file caused an error - ${response.statusText}`);
}
return response.json();
};
Now we'll try to piece it together. In the following snippet we listen for the submit event on the form. When the form submits, stop the default behavior and overwrite what we need to do on submit.
Get the values from the form with the FormData API. This enables us to extract the values from the form without having to select a specific element.
Select the file from the formData object and pass it to the readFileAsText function (or use the text() method on the file). The result should be the JSON in the file.
Now pass the json from the file into the sendJSON function and off it goes.
const form = document.querySelector('#upload-form');
form.addEventListener('submit', async event => {
event.preventDefault();
const formData = new FormData(event.target);
const file = formData.get('file');
// No file size means no file.
if (file.size === 0) {
return; // Do nothing.
}
try {
const json = await readFileAsText(file);
const response = await sendJSON(json);
console.log(response);
} catch(error) {
console.log(error);
}
});
I am using javascript and the Flask framework
I would like to retrieve in Flask in python the bytes of one or more files that the user will have chosen in my HTML page. To do this, when the user has chosen the files and has clicked on the send button, it triggers a function that uses the FileReader API to retrieve the base64 content of the file(s) that the user has selected.
After that, I would like to send the base64 data to flask with ajax.
But here is my problem, when I get the base64 string in python and compare it with the one in javascript, I notice the number of characters is exactly the same, but some characters are different as you can see on the screenshots below
size of character strings
And when I decode the base64 string, I get different bytes :
bytes variables
This makes me think that the problem is with the use of ajax
Code Python :
#app.route('/test', methods=['POST'])
def test():
if request.method == "POST":
files = eval(request.form.get("files"))
python_data = file.get("data")
javascript_data = "" # Copy from chrome console
len_python_data = len(python_data)
len_javascript_data = len(javascript_data)
base64_bytes_javascript = base64.b64decode(javascript_data)
base64_bytes_python = base64.b64decode(python_data)
Code Javascript :
let array_files = [];
let files_input = document.getElementById("files_input");
let files = files_input.files;
let reader = new FileReader();
function readFile(index) {
if( index >= files.length ) {
let data = "files=" + JSON.stringify(array_files);
$.ajax({
url: '/test',
type: 'POST',
data: data,
success: function (msg) {
console.log(msg)
}
});
return;
}
let file = files[index];
reader.name = file.name;
reader.onload = function() {
let file_info = {};
// get file content
let bin = this.result;
console.log(bin)
let data = bin.split(";")[1].replace("base64,", "");
file_info.name = reader.name;
file_info.data = data;
array_files.push(file_info);
readFile(index + 1)
}
reader.readAsDataURL(file);
}
readFile(0);
The problem is solved
In the base64 character string, the "+" were replaced by spaces after sending it to ajax.
Purpose: show different images on my html page by using AngularJS and just one Servlet (images will change depending on request parameters["id" variable])
Code explanation:
I send request to the Servlet by passing id=13 as params (see JS
code)
Servlet will retrieve the photo stored in the DB having id=13
The response value will be passed to the variable "image" on $scope
Problem: The image doesn't appear on my view.
Error Chrome console:
http://localhost:8080/MyProject/%7B%7Bimage%7D%7D 404 (Not Found)
Console.log(response) result:
Object {data: "����..., status:200, config:Object, statusText:"OK"}
Additional info:
If I change "id" assignment from
"Integer.parseInt(request.getParameter("id"))" to "13", my image will be properly shown on the Servlet URL, which means the file is correctly retrieved and encoded.
If I add System.out.println(id) after "int
id=Integer.parseInt(request.getParameter("id"))", it will be shown 13
as value, which means params are correctly passed from JS file to Servlet
The photos I want to show on my view are stored on MySQL as longBlob
-
MY HTML CODE:
...
<img class="images" src="{{image}}"/>
...
-
MY ANGULAR JS CODE:
...
$http.get('./ImagesServlet', {params: {id:13}})
.then(function(response) {
console.log(response)
$scope.image = response.data;
});
...
-
ImagesServlet CODE:
...
int id = Integer.parseInt(request.getParameter("id"));
PoiDao fetcher = new PoiDao();
try {
List<PoiImg> image = fetcher.ImgById(id);
response.setContentType("image/png");
Blob img = image.get(0).getPhoto();
InputStream in = img.getBinaryStream();
int length = (int) img.length();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
while ((length = in.read(buffer)) != -1) {
response.getOutputStream().write(buffer, 0, length);
}
} catch (ClassNotFoundException | SQLException e1) {
e1.printStackTrace();
}
...
JavaScript
If you don't want to change your $http call to expect a Blob directly,
let blob = new Blob([response.data], {type: 'image/jpeg'});
Otherwise, pass responseType: 'blob' into it so response.data will start as a Blob
Then convert this to a URL you can access from the HTML page
let url = URL.createObjectURL(blob); // note this will prevent GC of blob
Finally if you want to be clean, when you're done with this image
URL.revokeObjectURL(url); // frees blob to be GCd