How to retrieve an image in flutter from flask api - python

I have a post request in Flask that accepts an image file, and I want to return another image to retrieve it in Flutter and put it on screen.
In Flutter, I can send the image through the post request, but I don't know how to retrieve an image and put it on screen.
I know I can save the image in the static folder at Flask, and retrieve the URL from Flutter, and it works, but I think this is too inefficient for what I'm doing.
So I want to send the image directly without saving it.
This was my last attempt but didn't work.
#app.route("/send-image", methods=['POST'])
def send_image():
if request.method == 'POST':
user_image = request.files["image"]
image = cv2.imdecode(np.frombuffer(
user_image.read(), np.uint8), cv2.IMREAD_COLOR)
#data is a NumPy array returned by the predict function. This numpy array it's an image
data = predict(image)
data_object = {}
data = data.reshape(data.shape[0], data.shape[1], 1)
data2 = array_to_img(data)
b = BytesIO()
data2.save(b, format="jpeg")
b.seek(0)
data_object["img"] = str(b.read())
return json.dumps(data_object)
Here I returned a Uint8List because I read from the internet that I can put that into an Image.memory() to put the image on the screen.
Future<Uint8List> makePrediction(File photo) async {
const url = "http://192.168.0.11:5000/send-image";
try {
FormData data = new FormData.fromMap({
"image": await MultipartFile.fromFile(photo.path),
});
final response = await dio.post(url, data: data);
String jsonResponse = json.decode(response.data)["img"].toString();
List<int> bytes =
utf8.encode(jsonResponse.substring(2, jsonResponse.length - 1));
Uint8List dataResponse = Uint8List.fromList(bytes);
return dataResponse;
} catch (error) {
print("ERRORRR: " + error.toString());
}
}
Sorry if what I did here doesn't make sense, but after trying a lot of things I wasn't thinking properly.
I really need your help

You can convert the image to base64 and display it with Flutter.
On server:
import base64
...
data_object["img"] = base64.b64encode(b.read()).decode('ascii')
...
On client:
...
String imageStr = json.decode(response.data)["img"].toString();
Image.memory(base64Decode(imageStr));
...
The problem with your server-side code is it tries to coerce a bytes to str object by using function str().
However, in Python 3, bytes.__repr__ is called by str() since bytes.__str__ is not defined. This results in something like this:
str(b'\xf9\xf3') == "b'\\xf9\\xf3'"
It makes the JSON response looks like:
{"img": "b'\\xf9\\xf3'"}
Without writing a dedicated parser, you can not read this format of image data in Flutter. However, base64 is a well known format of encoding binary data and we do have a parser base64Decode in Flutter.

Related

How do I return an image in fastAPI?

Using the python module fastAPI, I can't figure out how to return an image. In flask I would do something like this:
#app.route("/vector_image", methods=["POST"])
def image_endpoint():
# img = ... # Create the image here
return Response(img, mimetype="image/png")
what's the corresponding call in this module?
If you already have the bytes of the image in memory
Return a fastapi.responses.Response with your custom content and media_type.
You'll also need to muck with the endpoint decorator to get FastAPI to put the correct media type in the OpenAPI specification.
#app.get(
"/image",
# Set what the media type will be in the autogenerated OpenAPI specification.
# fastapi.tiangolo.com/advanced/additional-responses/#additional-media-types-for-the-main-response
responses = {
200: {
"content": {"image/png": {}}
}
}
# Prevent FastAPI from adding "application/json" as an additional
# response media type in the autogenerated OpenAPI specification.
# https://github.com/tiangolo/fastapi/issues/3258
response_class=Response,
)
def get_image()
image_bytes: bytes = generate_cat_picture()
# media_type here sets the media type of the actual response sent to the client.
return Response(content=image_bytes, media_type="image/png")
See the Response documentation.
If your image exists only on the filesystem
Return a fastapi.responses.FileResponse.
See the FileResponse documentation.
Be careful with StreamingResponse
Other answers suggest StreamingResponse. StreamingResponse is harder to use correctly, so I don't recommend it unless you're sure you can't use Response or FileResponse.
In particular, code like this is pointless. It will not "stream" the image in any useful way.
#app.get("/image")
def get_image()
image_bytes: bytes = generate_cat_picture()
# ❌ Don't do this.
image_stream = io.BytesIO(image_bytes)
return StreamingResponse(content=image_stream, media_type="image/png")
First of all, StreamingResponse(content=my_iterable) streams by iterating over the chunks provided by my_iterable. But when that iterable is a BytesIO, the chunks will be \n-terminated lines, which won't make sense for a binary image.
And even if the chunk divisions made sense, chunking is pointless here because we had the whole image_bytes bytes object available from the start. We may as well have just passed the whole thing into a Response from the beginning. We don't gain anything by holding data back from FastAPI.
Second, StreamingResponse corresponds to HTTP chunked transfer encoding. (This might depend on your ASGI server, but it's the case for Uvicorn, at least.) And this isn't a good use case for chunked transfer encoding.
Chunked transfer encoding makes sense when you don't know the size of your output ahead of time, and you don't want to wait to collect it all to find out before you start sending it to the client. That can apply to stuff like serving the results of slow database queries, but it doesn't generally apply to serving images.
Unnecessary chunked transfer encoding can be harmful. For example, it means clients can't show progress bars when they're downloading the file. See:
Content-Length header versus chunked encoding
Is it a good idea to use Transfer-Encoding: chunked on static files?
I had a similar issue but with a cv2 image. This may be useful for others. Uses the StreamingResponse.
import io
from starlette.responses import StreamingResponse
app = FastAPI()
#app.post("/vector_image")
def image_endpoint(*, vector):
# Returns a cv2 image array from the document vector
cv2img = my_function(vector)
res, im_png = cv2.imencode(".png", cv2img)
return StreamingResponse(io.BytesIO(im_png.tobytes()), media_type="image/png")
All the other answer(s) is on point, but now it's so easy to return an image
from fastapi.responses import FileResponse
#app.get("/")
async def main():
return FileResponse("your_image.jpeg")
It's not properly documented yet, but you can use anything from Starlette.
So, you can use a FileResponse if it's a file in disk with a path: https://www.starlette.io/responses/#fileresponse
If it's a file-like object created in your path operation, in the next stable release of Starlette (used internally by FastAPI) you will also be able to return it in a StreamingResponse.
Thanks to #biophetik's answer, with an important reminder that caused me confusion: If you're using BytesIO especially with PIL/skimage, make sure to also do img.seek(0) before returning!
#app.get("/generate")
def generate(data: str):
img = generate_image(data)
print('img=%s' % (img.shape,))
buf = BytesIO()
imsave(buf, img, format='JPEG', quality=100)
buf.seek(0) # important here!
return StreamingResponse(buf, media_type="image/jpeg",
headers={'Content-Disposition': 'inline; filename="%s.jpg"' %(data,)})
The answer from #SebastiánRamírez pointed me in the right direction, but for those looking to solve the problem, I needed a few lines of code to make it work. I needed to import FileResponse from starlette (not fastAPI?), add CORS support, and return from a temporary file. Perhaps there is a better way, but I couldn't get streaming to work:
from starlette.responses import FileResponse
from starlette.middleware.cors import CORSMiddleware
import tempfile
app = FastAPI()
app.add_middleware(
CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]
)
#app.post("/vector_image")
def image_endpoint(*, vector):
# Returns a raw PNG from the document vector (define here)
img = my_function(vector)
with tempfile.NamedTemporaryFile(mode="w+b", suffix=".png", delete=False) as FOUT:
FOUT.write(img)
return FileResponse(FOUT.name, media_type="image/png")
My needs weren't quite met from the above because my image was built with PIL. My fastapi endpoint takes an image file name, reads it as a PIL image, and generates a thumbnail jpeg in memory that can be used in HTML like:
<img src="http://localhost:8000/images/thumbnail/bigimage.jpg">
import io
from PIL import Image
from fastapi.responses import StreamingResponse
#app.get('/images/thumbnail/{filename}',
response_description="Returns a thumbnail image from a larger image",
response_class="StreamingResponse",
responses= {200: {"description": "an image", "content": {"image/jpeg": {}}}})
def thumbnail_image (filename: str):
# read the high-res image file
image = Image.open(filename)
# create a thumbnail image
image.thumbnail((100, 100))
imgio = io.BytesIO()
image.save(imgio, 'JPEG')
imgio.seek(0)
return StreamingResponse(content=imgio, media_type="image/jpeg")
You can use a FileResponse if it's a file in disk with a path:
import os
from fastapi import FastAPI
from fastapi.responses import FileResponse
app = FastAPI()
path = "/path/to/files"
#app.get("/")
def index():
return {"Hello": "World"}
#app.get("/vector_image", responses={200: {"description": "A picture of a vector image.", "content" : {"image/jpeg" : {"example" : "No example available. Just imagine a picture of a vector image."}}}})
def image_endpoint():
file_path = os.path.join(path, "files/vector_image.jpg")
if os.path.exists(file_path):
return FileResponse(file_path, media_type="image/jpeg", filename="vector_image_for_you.jpg")
return {"error" : "File not found!"}
If when following the top answer and you are attempting to return a BytesIO object like this in your Response
buffer = BytesIO(my_data)
# Return file
return Response(content=buffer, media_type="image/jpg")
You may receive an error that looks like this (as described in this comment)
AttributeError: '_io.BytesIO' object has no attribute 'encode'
This is caused by the render function in Response which explicitly checks for a bytes type here. Since BytesIO != bytes it attempts to encode the value and fails.
The solution is to get the bytes value from the BytesIO object with getvalue()
buffer = BytesIO(my_data)
# Return file
return Response(content=buffer.getvalue(), media_type="image/jpg")
You can do something very similar in FastAPI
from fastapi import FastAPI, Response
app = FastAPI()
#app.post("/vector_image/")
async def image_endpoint():
# img = ... # Create the image here
return Response(content=img, media_type="image/png")

Create image from string sent from iOS device

I have an iOS mobile application that sends an encoded image to a Python3 server.
static func prepareImageAndUpload(imageView: UIImageView) -> String?
{
if let image: UIImage? = imageView.image {
// You create a NSData from your image
let imageData = UIImageJPEGRepresentation(imageView.image!, 0.5)
// You create a base64 string
let base64String = imageData!.base64EncodedStringWithOptions([])
// And you encode it in order to delete any problem of specials char
let encodeImg = base64String.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet()) as String!
return encodeImg
}
return nil
}
And I am trying to receive that image using the following code:
imageName = "imageToSave.jpg"
fh = open(imageName, "wb")
imgDataBytes = bytes(imgData, encoding="ascii")
imgDataBytesDecoded = base64.b64decode(imgDataBytes)
fh.write(imgDataBytesDecoded)
fh.close()
I create the image file successfully and nothing breaks. And I can see that the filesize is correct, but the image is not correct, since it can't be opened and shows that it is broken.
I am not sure where the error can be, since the logic is as follows:
Encode image with base64 on iOS device
Send it
Decode image with base64 on Python3 server
Save image from decoded bytes
I have tried two new variants:
Remove stringByAddingPercentEncodingWithAllowedCharacters and
the result was the same
Add urldecode in Python3 server and the result was the same

How to send image with form data in test case with unittest in flask application?

I am newer to Python, I am making Flask application. so, I want to write Test Cases for my application using unittest, and I am doing like this:
def test_bucket_name(self):
self.test_app = app.test_client()
response = self.test_app.post('/add_item', data={'name':'test_item','user_id':'1','username':'admin'})
self.assertEquals(response.status, "200 OK")
It is all work well. But I am posting some data and image with POST in one URL. So, My Question is that : "How do i send image with that data?"
Read the image into a StringIO buffer. Pass the image as another item in the form data, where the value is a tuple of (image, filename).
def test_bucket_name(self):
self.test_app = app.test_client()
with open('/home/linux/Pictures/natural-scenery.jpg', 'rb') as img1:
imgStringIO1 = StringIO(img1.read())
response = self.test_app.post('/add_item',content_type='multipart/form-data',
data={'name':'test_item',
'user_id':'1',
'username':'admin',
'image': (imgStringIO1, 'img1.jpg')})
self.assertEquals(response.status, "200 OK")
The above answer is correct, except in my case I had to use BytesIO like the following:
def create_business(self, name):
with open('C:/Users/.../cart.jpg', 'rb') as img1:
imgStringIO1 = BytesIO(img1.read())
return self.app.post(
'/central-dashboard',
content_type='multipart/form-data',
data=dict(name=name, logo=(imgStringIO1, 'cart.jpg')),
follow_redirects=True
)

Android , Read in binary data and write it to file

Im trying to read in image file from a server , with the code below . It keeps going into the exception. I know the correct number of bytes are being sent as I print them out when received. Im sending the image file from python like so
#open the image file and read it into an object
imgfile = open (marked_image, 'rb')
obj = imgfile.read()
#get the no of bytes in the image and convert it to a string
bytes = str(len(obj))
#send the number of bytes
self.conn.send( bytes + '\n')
if self.conn.sendall(obj) == None:
imgfile.flush()
imgfile.close()
print 'Image Sent'
else:
print 'Error'
Here is the android part , this is where I'm having the problem. Any suggestions on the best way to go about receiving the image and writing it to a file ?
//read the number of bytes in the image
String noOfBytes = in.readLine();
Toast.makeText(this, noOfBytes, 5).show();
byte bytes [] = new byte [Integer.parseInt(noOfBytes)];
//create a file to store the retrieved image
File photo = new File(Environment.getExternalStorageDirectory(), "PostKey.jpg");
DataInputStream dis = new DataInputStream(link.getInputStream());
try{
os =new FileOutputStream(photo);
byte buf[]=new byte[1024];
int len;
while((len=dis.read(buf))>0)
os.write(buf,0,len);
Toast.makeText(this, "File recieved", 5).show();
os.close();
dis.close();
}catch(IOException e){
Toast.makeText(this, "An IO Error Occured", 5).show();
}
EDIT: I still cant seem to get it working. I have been at it since and the result of all my efforts have either resulted in a file that is not the full size or else the app crashing. I know the file is not corrupt before sending server side. As far as I can tell its definitely sending too as the send all method in python sends all or throws an exception in the event of an error and so far it has never thrown an exception. So the client side is messed up . I have to send the file from the server so I cant use the suggestion suggested by Brian .
The best way to get a bitmap from a server is to execute the following.
HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet("http://yoururl");
HttpResponse response = client.execute(get);
InputStream is = response.getEntity().getContent();
Bitmap image = BitmapFactory.decodeStream(is);
That will give you your bitmap, to save it to a file do something like the following.
FileOutputStream fos = new FileOutputStream("yourfilename");
image.compress(CompressFormat.PNG, 1, fos);
fos.close();
You can also combine the two and just write straight to disk
HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet("http://yoururl");
HttpResponse response = client.execute(get);
InputStream is = response.getEntity().getContent();
FileOutputStream fos = new FileOutputStream("yourfilename");
byte[] buffer = new byte[256];
int read = is.read(buffer);
while(read != -1){
fos.write(buffer, 0, read);
read = is.read(buffer);
}
fos.close();
is.close();
Hope this helps;
I'm not sure I understand your code. You are calling dis.readFully(bytes); to write the content of dis into your byte array. But then you don't do anything with the array, and then try to write the content of dis through a buffer into your FileOutputStream.
Try commenting out the line dis.readFully(bytes);.
As a side note, I would write to the log rather than popping up a toast for things like the number of bytes or when an exception occurs:
...
} catch (IOException e) {
Log.e("MyTagName","Exception caught " + e.toString());
e.printStackTrace();
}
You could look at these links for examples of writing a file to the SD card:
Android download binary file problems
Android write to sd card folder
I solved it with the help of a Ubuntu Forums member. It was the reading of the bytes that was the problem . It was cutting some of the bytes from the image. The solution was to just send the image whole and remove the sending of the bytes from the equation all together

passing blob parameter to django

I keep my images in the DB as blobs:
class MyClass(db.Model):
icon=db.BlobProperty()
Now, I want to send the blob to my HTML like this :
lets say I have myClass as an instance of MyClass
result = """<div img_attr=%s> Bla Bla </div>""" % (myClass.icon)
Some how it doesn't work. Do you have any idea?
You cannot just dump raw image data into your html page. You need to do this in two pieces:
In your html, you need to refer to an image file:
result = "<div>"
"<img src='{0}' />"
"</div>"
.format(MYSITE + MYIMAGEDIR + myClass.name)
Your browser reads the html page, finds out you want to include an image, and goes looking for the image file to include - so it asks your site for something like http://www.myexample.com/images/icon01.jpg
Now, separately, you respond to this second request with the image content, as #anand has shown.
Your code suggests that you are working on Google application engine with Django.
You just need to query the image in your view and return it as http response.
image = myclassObject.icon
response = HttpResponse(image)
response['Content-Type'] = 'image/jpg'
return response
The value stored in the the data store, and returned by appengine with a db.BlobProperty is not the actual bytes of the blob, but rather a BlobKey that is used to reference it. There are two ways you can use that key. You can create a BlobReader to load the bytes of the blob from the BlobStore into your app, or you can craft a response with ServeHandler.send_blob to transfer those bytes to the client.
Doing the second one in Django is a bit of a headache, because ServeHandler doesn't really fit in well with the Django request processing stack. Here's a view that will do it for you without too much trouble:
def get_image_data(request, key, filename):
"serve original uploaded image"
# verify the users' ability to get the requested image
key = urllib.unquote(key)
img = _load_metadata(request, key)
blob = img.data;
blobkey = blob.key()
# and tell google to serve it
response = http.HttpResponse(
content='',
content_type=blob.content_type)
response['X-AppEngine-BlobKey'] = str(blobkey)
return response

Categories