I am loading some images into a Django model.
When I display these images through a Django template, portrait images are rotated. If I view these same images through Django Admin by clicking on the link they display as I would expect.
This is my view to load the images:
def upload_profile(request, user_id):
variables = {}
if request.POST:
user_form = UserForm(request.POST, instance=User.objects.get(id=request.user.id))
myuser_form = MyUserForm(request.POST, request.FILES, instance=MyUser.objects.get(user__id=request.user.id))
if user_form.is_valid() and myuser_form.is_valid():
user_form.save()
myuser = myuser_form.save()
myuser.photo = apply_orientation(myuser.photo)
myuser.save()
return game_selection(request)
variables['user_form'] = user_form
variables['myuser_form'] = myuser_form
return render(request, 'esc/profile.html', variables)
And this is how I am applying the rotation:
def flip_horizontal(im): return im.transpose(Image.FLIP_LEFT_RIGHT)
def flip_vertical(im): return im.transpose(Image.FLIP_TOP_BOTTOM)
def rotate_180(im): return im.transpose(Image.ROTATE_180)
def rotate_90(im): return im.transpose(Image.ROTATE_90)
def rotate_270(im): return im.transpose(Image.ROTATE_270)
def transpose(im): return rotate_90(flip_horizontal(im))
def transverse(im): return rotate_90(flip_vertical(im))
orientation_funcs = [None,
lambda x: x,
flip_horizontal,
rotate_180,
flip_vertical,
transpose,
rotate_270,
transverse,
rotate_90
]
def apply_orientation(im):
"""
Extract the oritentation EXIF tag from the image, which should be a PIL Image instance,
and if there is an orientation tag that would rotate the image, apply that rotation to
the Image instance given to do an in-place rotation.
:param Image im: Image instance to inspect
:return: A possibly transposed image instance
"""
try:
kOrientationEXIFTag = 0x0112
if hasattr(im, '_getexif'): # only present in JPEGs
e = im._getexif() # returns None if no EXIF data
if e is not None:
#log.info('EXIF data found: %r', e)
orientation = e[kOrientationEXIFTag]
f = orientation_funcs[orientation]
return f(im)
except:
# We'd be here with an invalid orientation value or some random error?
pass # log.exception("Error applying EXIF Orientation tag")
return im
My code never seems to pass the condition if hasattr(im, '_getexif'):
**** EDIT ****
So it's not getting through because no matter what image I load there is no '_getexif'.
If I add print getattr(im, '_getexif') I get the following error, with all images
'ImageFieldFile' object has no attribute '_getexif'
So I dropped all the code above and replaced it with the following
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill, Transpose, SmartResize
class MyUser(models.Model):
avatar = models.ImageField(upload_to='upload/avatars', max_length=255, blank=True, null=True)
avatar_thumbnail = ImageSpecField(
source='avatar',
processors = [Transpose(),SmartResize(200, 200)],
format = 'JPEG',
options = {'quality': 75}
)
It works
Related
I'm writing a Form Creation/Filling out forms app, and I'm to the point where I'm taking canvas data from the front-end and filled out fields of text input and draw them on a picture of the form with the pillow library. The problem is when I try to save the data for the form I get this error from "serializer.error" from django-rest-framework: "'FilledForm': [ErrorDetail(string='The submitted data was not a file. Check the encoding type on the form.', code='invalid')]}"
Here is the Api view:
#api_view(['POST'])
def postForm(request):
dataDict = request.data
FieldCoords = {}
# Define Formtype
selectedForm = dataDict["SelectedForm"]
del dataDict["SelectedForm"]
# Get Datalist for form creation
datalist = list(FormData.objects.filter(FormType=selectedForm).values())
#Set Signature Images on Form and Fill form with inputs
for field in datalist:
#Get Field Positions of Model
if field["FieldType"] == "SignatureField":
FieldCoords[field["FieldName"]] = (field["StartX"],field["StartY"])
elif field["FieldType"] == "TextField":
FieldCoords[field["FieldName"]] = (field["StartX"],field["StartY"],abs(field["height"]))
elif field["FieldType"] == "DateField":
FieldCoords[field["FieldName"]] = (field["StartX"],field["StartY"])
#print(FieldCoords)
#Place Signature Fields On FormImage
sigFields = json.loads(dataDict["Signatures"])
FormImage = Image.open(f"{MEDIA_ROOT}/{selectedForm}")
del dataDict["Signatures"]
for field in sigFields:
datauri = sigFields[field]
uri = datauri.split("base64,")
sNameselect = field.split(" - ")
bytes = base64.b64decode(uri[1])
img = Image.open(io.BytesIO(bytes))
for keyCoordSet in FieldCoords:
print(keyCoordSet)
print("Name" + sNameselect[0])
if sNameselect[0] == keyCoordSet:
print("Im here")
FormImage.paste(img, FieldCoords[keyCoordSet], mask=img)
#Place Text Fields On FormImage
d1 = ImageDraw.Draw(FormImage)
for field in dataDict:
for keyCoordSet in FieldCoords:
if field == keyCoordSet:
myFont = ImageFont.truetype("LiberationMono-Regular.ttf",size=FieldCoords[keyCoordSet][2] - 10)
d1.text((FieldCoords[keyCoordSet][0]+5,FieldCoords[keyCoordSet][1]+5), dataDict[field], fill =(255, 255, 255),font=myFont)
time = str(timezone.now()).split(" ")
image_bytes = io.BytesIO(FormImage.tobytes())
imageFile = ContentFile(image_bytes.getvalue())
print(imageFile.read())
data = { # Final data structure
"FormType": selectedForm,
"DateCreated": time[0],
"Data": json.dumps(dataDict, indent = 4),
"FilledForm": (str(selectedForm),imageFile)
}
serializer = FilledFormdataSerializer(data=data)
print(type(imageFile))
if serializer.is_valid():
print("Im Valid")
#serializer.save()
else:
print(serializer.errors)
return Response("Returned Data")
Here is the Model
class FilledForm(models.Model):
FormType = models.CharField(max_length=100)
DateCreated = models.DateField(default=timezone.now())
Data = models.JSONField()
FilledForm = models.ImageField()
Here is the serializer
class FilledFormdataSerializer(serializers.ModelSerializer):
class Meta:
model = FilledForm
fields = ["FormType", "DateCreated", "Data", "FilledForm"]
def create(self, validated_data):
print(validated_data)
return FilledForm.objects.create(**validated_data)
Heres the part of the Code where the issue is
#Place Text Fields On FormImage
d1 = ImageDraw.Draw(FormImage)
for field in dataDict:
for keyCoordSet in FieldCoords:
if field == keyCoordSet:
myFont = ImageFont.truetype("LiberationMono-Regular.ttf",size=FieldCoords[keyCoordSet][2] - 10)
d1.text((FieldCoords[keyCoordSet][0]+5,FieldCoords[keyCoordSet][1]+5), dataDict[field], fill =(255, 255, 255),font=myFont)
time = str(timezone.now()).split(" ")
image_bytes = io.BytesIO(FormImage.tobytes())
imageFile = ContentFile(image_bytes.getvalue())
print(imageFile.read())
data = { # Final data structure
"FormType": selectedForm,
"DateCreated": time[0],
"Data": json.dumps(dataDict, indent = 4),
"FilledForm": (str(selectedForm),imageFile)
}
serializer = FilledFormdataSerializer(data=data)
print(type(imageFile))
if serializer.is_valid():
print("Im Valid")
#serializer.save()
else:
print(serializer.errors)
Here's all the module imported
from rest_framework.response import Response
from rest_framework.decorators import api_view
from api.serializer import DataSerializer, FilledFormdataSerializer
from django.core.files import File
from django.utils import timezone
from backend.models import FormData
from django.core.files.base import ContentFile
import json
from PIL import Image, ImageDraw, ImageFont
import base64
import io
from FormApp.settings import BASE_DIR, MEDIA_ROOT
For some reason it's saying its not reading it as a file even though I'm converting it to bytes and rapping it in the ContentFile() object. Can I get some advice on why it's not saving to the image field?
I have a form that allows my to upload multiple image files. Everything works fine, when I select images to upload. If I try to submit the form without files, I get an error
OSError: cannot identify image file <FileStorage: '' ('application/octet-stream')>
I would like to be able to check if the field is empty and if so, bypass my save_images function.
Here is my form
class NewPortfolioProject(FlaskForm):
title = StringField('Project Name', validators=[DataRequired(), Length(max=50)])
description = TextAreaField('Description', validators=[
DataRequired(), Length(min=1, max=1000)])
tags = StringField('#Tags - space separated', validators=[DataRequired()])
link = StringField('Link to live project')
github_link = StringField('Github link', validators=[DataRequired()])
images = MultipleFileField('Add screenshots/wireframes', validators=[FileAllowed(['png', 'jpg'])])
submit = SubmitField('Save Project')
In my routes.py file I am trying to test if the MultipleFileField contains data
if form.validate_on_submit():
if not form.images.data:
portfolio.insert_one(new_doc)
else:
image_files = save_images(form.images.data)
new_doc['images'] = image_files
portfolio.insert_one(new_doc)
But this (and everything else I have tried) doesn't work.
Rather than being empty, form.images.data seems to be a filestorage object. How to I test if it is empty?
Apparently when you call form.images.data on MultipleFileField it returns a list that is never empty, which is why if form.images.data check always returns true.
I have eventually solved it using try/except in my image saving method, as in my case the error itself is raised when PIL tries to open the image and process it.
def save_image(form_picture):
random_hex = secrets.token_hex(8)
_, f_ext = os.path.splitext(form_picture.filename)
picture_fn = random_hex + f_ext
picture_path = os.path.join(current_app.root_path, 'static/images', picture_fn)
output_size = (600, 400)
try:
i = Image.open(form_picture)
i.thumbnail(output_size)
i.save(picture_path)
return picture_fn
except:
return False
I have a Django app that lets you upload a video or image, it sends it to an OpenCV function that returns a new image/video with some annotations on it and then shows it in the template.
For images it works just fine, but for video it doesn't.
The video is shown as unavailable both in the view (as HTML5 video) and when following the /media/[path_to_file], but when I'm opening it with VLC (or some other desktop player, locally), the video exists and is working as expected.
Moreover, I tried removing the OpenCV function and left only the upload functionality. When I uploaded a normal video, everything was fine, but when I tried to upload a video that was previously processed with OpenCV, it appears as unavailable again.
My question is: is opencv changing the video properties in any way comparing to the original so that Django doesn't recognise it anymore? What might be the problem? And how can I make it that opencv processed videos are visible in my view?
Also, the processed video has the same name and extension (and type) as the original.
If you have any idea it's more than helpful. Thanks a lot!
Edit (included the code)
views.py
class IndexView(View):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.context = dict()
def get(self, request, *args, **kwargs):
""" Action for GET requests """
upload_form = UploadForm()
self.context['upload_form'] = upload_form
return render(request, 'homepage/home.html', self.context)
def post(self, request, *args, **kwargs):
""" Action for POST requests """
upload_form = UploadForm(request.POST, request.FILES)
if upload_form.is_valid:
try:
uploaded_file = upload_form.save()
file_type = upload_form.cleaned_data['file'].content_type.split('/')[0]
# task = start_annotation_process.delay(uploaded_file.file.name, file_type)
task = add.delay(4, 5)
except ValidationError:
print('FAILED VALIDATION ERROR')
self.context['upload_status'] = 'failed'
return render(request, 'homepage/home.html', self.context)
except ValueError:
print('FAILED VALUE ERROR')
self.context['upload_status'] = 'failed'
return render(request, 'homepage/home.html', self.context)
self.context['upload_form'] = upload_form
self.context['task_id'] = task.task_id
self.context['uploaded_file'] = uploaded_file
self.context['upload_status'] = 'successfull'
self.context['content_type'] = upload_form.cleaned_data['file'].content_type
return render(request, 'homepage/home.html', self.context)
models.py
class UploadModel(models.Model):
""" Model for storing uploaded files """
file = models.FileField(upload_to='uploads/%Y/%m/%d/')
upload_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return 'UploadModel({file}, {date})'.format(file=self.file, date=self.upload_date)
class Meta:
db_table = 'UploadModel'
forms.py
class UploadForm(forms.ModelForm):
file = forms.FileField(widget=forms.ClearableFileInput(attrs={'class': "custom-file-input",
'id':"input-file",
'aria-describedby':"inputGroupFileAddon01"}))
def clean_file(self):
file = self.cleaned_data.get('file')
if file != None:
file = self.cleaned_data['file'] # try to delete
file_type = file.content_type.split('/')[0]
if file_type == settings.FILE_TYPES[0]: # image
self.__validate_size(file, settings.MAX_UPLOAD_IMAGE_SIZE)
elif file_type == settings.FILE_TYPES[1]: # video
self.__validate_size(file, settings.MAX_UPLOAD_VIDEO_SIZE)
else:
print('File type ', file.content_type)
raise forms.ValidationError(
'File type not supported. It has to be an image or a video.')
return file
def __validate_size(self, file, upload_size):
if file.size > int(upload_size):
raise forms.ValidationError('The file size exceeds {upload_size}. '
'Current file size is {file_size}'.format(upload_size=filesizeformat(upload_size),
file_size=filesizeformat(file.size)))
class Meta:
model = UploadModel
exclude = ('upload_date',)
fields = ('file',)
urls.py (homepage)
urlpatterns = [
path('', homepage_views.IndexView.as_view(), name='homepage'),
path('celery-progress', include('celery_progress.urls')), # the endpoint is configurable
path('download/<str:content_type>/<path:path>', homepage_views.download_file, name='download'),
]
urls.py (root app)
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('homepage.urls')),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
tasks.py (celery)
#shared_task(bind=True)
def start_annotation_process(self, file_path, file_type):
""" Celery task for starting the annotation process """
print('start task')
media_src_path = path.join(settings.MEDIA_ROOT, file_path)
media_dst_path = path.join(settings.MEDIA_ROOT, 'results', file_path)
media_res_path = path.join(settings.MEDIA_ROOT, 'results', path.dirname(file_path))
if not path.exists(media_res_path):
makedirs(media_res_path)
if file_type == 'image':
start_process_image(media_src_path, media_dst_path)
elif file_type == 'video':
start_process_video(media_src_path, media_dst_path)
move(media_dst_path, media_src_path)
print('finished task')
Opencv function (FastObjectDetector is a class that will predict the annotations and return the processed frame with predict_img() method)
def start_process_video(source_path, dest_path, process_offline=True, rotate=False):
""" Start annotation process for video """
if not process_offline:
cfod = FastObjectDetector(score_threshold=0.5)
vstrm = VideoCameraStream(logger=cfod.logger,
process_func=cfod.predict_img,
info_func=cfod._DEBUG_INFO,
onclick_func=cfod.on_click,
hd=1,
camera=0)
if vstrm.video != None:
video_frame_shape = (vstrm.H, vstrm.W)
cfod.prepare(image_shape=video_frame_shape)
vstrm.play()
vstrm.shutdown()
if cfod.DEBUG:
cfod.show_fr_stats()
cfod.shutdown()
else:
cfod = FastObjectDetector(score_threshold=0.5)
FRAME_W, FRAME_H = 1280, 720
cfod.prepare(image_shape=(FRAME_H, FRAME_W))
video = cv2.VideoCapture(source_path)
fourcc = int(video.get(cv2.CAP_PROP_FOURCC))
fourcc = cv2.VideoWriter_fourcc(*'XVID')
fps = video.get(cv2.CAP_PROP_FPS)
frame_size = (FRAME_W, FRAME_H)
source_path, _ = os.path.splitext(source_path)
new_video = cv2.VideoWriter(dest_path, fourcc, fps, frame_size)
while (video.isOpened()):
ret, frame = video.read()
if ret:
frame = cv2.resize(frame, (FRAME_W, FRAME_H))
if rotate:
(cW, cH) = (FRAME_W // 2, FRAME_H // 2)
m = cv2.getRotationMatrix2D((cW, cH), -90, 1)
frame = cv2.warpAffine(frame, m, (FRAME_W, FRAME_H))
frame = cfod.predict_img(frame)
new_video.write(frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
else:
break
video.release()
new_video.release()
cv2.destroyAllWindows()
template.html
<video id="result-video" controls style="width:45vw; height:auto; max-height:40vh">
<p> view this video please enable JavaScript, and consider upgrading to a web browser that supports HTML5 video </p>
</video>
<script type="text/javascript">
{% if task_id and upload_status == "successfull" %}
var progressUrl = "{% url 'celery_progress:task_status' task_id %}";
$('#btn-upload').addClass('disabled');
$(function () {
let options = {
onProgress: function(progressBarElement, progressBarMessageElement, progress){
console.log('ON PROCESS');
$('#div-progress').show();
$('#div-results').hide();
},
onSuccess: function(progressBarElement, progressBarMessageElement){
console.log('ON SUCCESS');
var d = new Date();
{% get_content_type content_type as type %}
{% if type == 'image' %}
$('.result-img').attr('src', '{{ uploaded_file.file.url }}?' + d.getTime());
{% elif type == 'video' %}
$('#result-video').html('<source src="{{ uploaded_file.file.url }}" type="{{ content_type }}">');
$('#result-video')[0].load();
{% endif %}
$('#div-progress').hide();
$('#div-results').show();
$('#btn-upload').removeClass('disabled')
}
};
CeleryProgressBar.initProgressBar(progressUrl, options);
});
{% endif %}
</script>
For the template I used celery_progress's callback functions to add the video source after the celery task is finished. The final HTML file will have the correct source included in the video tag. But the video is unavailable following the /media/ url too.
I hope this helps. Thanks.
I solved the problem myself, so if anyone has the same problem as me, check your codecs inside cv2.VideoWriter_fourcc(*'XVID'). I was trying to upload an .mp4 file while the codec was set for .avi.
This is even more embarrassing as the previous line (fourcc = int(video.get(cv2.CAP_PROP_FOURCC))) was getting the codec from the uploaded file and was about to use it, but the next line was overwriting it.
I ended up using AVC1 codec and changed the code to make any uploaded video into a .mp4 file, just to be sure that this problem will not repeat.
I hope this helps someone.
I am working on an application where a user can upload an image. I want to reduce the size of the image in 200-500kb.
This is my models.py file
class Report_item(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL)
title = models.CharField(max_length=255, help_text='*Title for the post e.g. item identity')
image = models.ImageField(default="add Item image",
upload_to=get_uplaod_file_name)
def __str__(self):
return self.title + " " + str(self.publish)
And this is my views.py file
class ReportCreate(generic.CreateView):
model = Report_item
fields = ['title','image']
def get_form(self, form_class=None):
if form_class is None:
form_class = self.get_form_class()
form = super(ReportCreate, self).get_form(form_class)
form.fields['title'].widget = TextInput(
attrs={'placeholder': '*Enter UID e.g. CBSE Marksheet Roll nunber 0506***'})
return form
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.owner = self.request.user
self.object.save()
return FormMixin.form_valid(self, form)
I am using Django 1.11 and S3 storage. Kindly help me to compress the image before uploading to s3.
So we need to define a save method in models in order to compress the image before save. Following code help me what I want to achieve for my problem.
from io import BytesIO
import sys
from PIL import Image
from django.core.files.uploadedfile import InMemoryUploadedFile
class Report_item(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL)
title = models.CharField(max_length=255, help_text='*Title for the post e.g. item identity')
image = models.ImageField(default="add Item image",
upload_to=get_uplaod_file_name)
def save(self):
# Opening the uploaded image
im = Image.open(self.image)
output = BytesIO()
# Resize/modify the image
im = im.resize((100, 100))
# after modifications, save it to the output
im.save(output, format='JPEG', quality=90)
output.seek(0)
# change the imagefield value to be the newley modifed image value
self.image = InMemoryUploadedFile(output, 'ImageField', "%s.jpg" % self.image.name.split('.')[0], 'image/jpeg',
sys.getsizeof(output), None)
super(Report_item, self).save()
WIth the help of this 5 Mb image compress to 4 kb approx.
Suppose you want to upload an image after resizing it. You can use below code, just pass the image object and you will get resized image in return.
def GetThumbnail(f):
try:
name = str(f).split('.')[0]
image = Image.open(f)
image.thumbnail((400, 400), Image.ANTIALIAS)
thumbnail = BytesIO()
# Default quality is quality=75
image.save(thumbnail, format='JPEG', quality=50)
thumbnail.seek(0)
newImage = InMemoryUploadedFile(thumbnail,
None,
name + ".jpg",
'image/jpeg',
thumbnail.tell(),
None)
return newImage
except Exception as e:
return e
#jitesh2796, to retain the height to width ratio, modify the accepted answer to include the following:
def save(self):
...
original_width, original_height = im.size
aspect_ratio = round(original_width / original_height)
desired_height = 100 # Edit to add your desired height in pixels
desired_width = desired_height * aspect_ratio
# Resize the image
im = im.resize((desired_width, desired_height))
...
Note you may also need to use round() to ensure that the aspect ratio is an integer.
I have a simple model representing a female that a user can view, edit and create:
class Female(models.Model):
firstName = models.CharField(max_length=255)
lastName = models.CharField(max_length=255)
profileImage = models.ImageField()
bio = models.TextField()
fantasticBio = models.TextField()
I am using a multi-part form to send the data for a create via an Angular service. This works fine. The django view that handles the creation is:
#api_view(['POST'])
def createFemale(request):
serializedFemale = FemaleSerializer(data=request.data)
if serializedFemale.is_valid():
serializedFemale.save()
return Response(serializedFemale.data)
else:
return Response(serializedFemale.errors, status=status.HTTP_400_BAD_REQUEST)
My problem is that I am unable to fully unit test this view. I am having trouble creating an image that I can use to test the view via the test client. My knowledge of image generation is limited so I may be generating the image wrong, however it seems to be accepted as a Django ImageField when I write it directly to the database in my set up. The relevant test code is as follows:
def createImageFile():
"""
Convinience function to create a test image
"""
image = Image.new('RGBA', size=(50, 50), color=(256, 0, 0))
image_file = BytesIO()
image.save(image_file, 'PNG')
img_str = base64.b64encode(image_file.getvalue())
img_str = str.encode("data:image/png;base64,") + img_str
return ImageFile(img_str)
def test_createFemale(self):
"""
A valid Female created with the API should be in the database
"""
profileImage = SimpleUploadedFile("test.png", createImageFile().file, content_type="image/png")
femaleToCreate = {}
femaleToCreate['firstName'] = "Test"
femaleToCreate['lastName'] = "Female"
femaleToCreate['profileImage'] = profileImage
femaleToCreate['bio'] = "Test bio"
femaleToCreate['fantasticBio'] = "Fantastic test bio"
response = self.client.post(url, femaleToCreate)
self.assertEquals(response.status_code, 200) # Make sure valid request returns success response
The response I receive from the server is:
{'profileImage': ['Upload a valid image. The file you uploaded was either not an image or a corrupted image.']}
How can I create an image that will be accepted by my API from within my unit test?
Please refrain from suggesting a file read of an existing image, I have considered this option and chosen not to pursue it as I believe it is a bad practice.
Thank you for any help.
Ok so I've got round this as follows:
def test_images_by_component_id_update(self):
image = PIL.Image.new('RGB', size=(1, 1))
file = tempfile.NamedTemporaryFile(suffix='.jpg')
image.save(file)
with open(file.name, 'rb') as f:
data = {'image': f}
response = self.client.patch(reverse('foo', args=(1, 1)), data=data, format='multipart')
self.assertEqual(200, response.status_code)
Notice I have to reopen the file, this took a while to debug. For whatever reason it doesn't like the NamedTemporaryFile.file attribute so I had to reopen.