How to classify an image from Azure Storage in Django using a Tensorflow model - django

I am developing a django application where the user chooses a machine learning model from a drop down list and uploads an image for classification. This image was initially saved in the project directory (bad, I know) so that I can use it in the classification.
Now I save these images in Azure Storage, but at the moment I can't find a way to access them without having to save them locally to classify them, so I think I'll have to temporarily save them in the project directory and once I use them in the ml model, then I remove the images.
I would like to deploy this application to Azure web service, so I consider it is a bad idea to save and delete images in the project directory.
You can see app's form here
models.py
image is saved in Azure Storage, the other fields in Azure Database for PostgreSQL.
class UploadedImage(models.Model):
image = models.ImageField(upload_to='%Y/%m/%d/')
uploaded = models.DateTimeField(auto_now_add=False, auto_now=True)
title = models.CharField(max_length=50)
prediction = models.FloatField(null=True, blank=True)
def __str__(self):
return self.title
forms.py
class UploadImageForm(forms.ModelForm):
EXTRA_CHOICES = [
('MOB', 'MobileNetV2'),
('VGG', 'VGG-19'),
('CNN', 'CNN 3BI'),
]
predicted_with = forms.ChoiceField(label="Modelo Predictivo",
choices=EXTRA_CHOICES, required=True,
widget=forms.Select(attrs={'class': 'form-control'})
)
class Meta:
model = UploadedImage
fields = [
'image',
]
widgets = {
'image': forms.FileInput(attrs={'class':'custom-file-input'}),
}
views.py
def make_prediction(image_to_predict, model='MOB'):
tf.keras.backend.reset_uids()
folders = {'VGG': 'vgg', 'MOB': 'mobilenet', 'CNN': 'cnn3', 'MSG': 'mobile_sin_gpu'}
model_as_json = 'upload_images/model/%s/modelo.json' % (folders[model])
weights = 'upload_images/model/%s/modelo.h5' % (folders[model])
json_file = open(model_as_json, 'r')
loaded_json_model = json_file.read()
json_file.close()
model = tf.keras.models.model_from_json(loaded_json_model)
model.load_weights(weights)
image = [image_to_predict]
data = img_preprocessing.create_data_batches(image)
return model.predict(data)
def upload_image_view(request):
if request.method == 'POST':
form = forms.UploadImageForm(request.POST, request.FILES)
if form.is_valid():
m = form.save(commit=False)
try:
pred = make_prediction(m.image.path, form.cleaned_data['predicted_with'])[0][0]
if pred > 0.5:
# Code continue...
if status == 200:
m.prediction = pred
m.title = m.image.path
m.save()
# Code continue...
The above snippet worked when I initially saved the images in the project directory but when I started saving the images in Azure Storage I started getting this error:
This backend doesn't support absolute paths.
So I changed the following line: pred = make_prediction(m.image.name, form.cleaned_data['predicted_with'])[0][0]
However now I have this error: NewRandomAccessFile failed to Create/Open: image-100.png : The system cannot find the file specified. ; No such file or directory [[{{node ReadFile}}]] [[IteratorGetNext]] [Op:__inference_predict_function_845] Function call stack: predict_function
For this reason I think my solution would be to temporarily save the image in the project directory, use it in the model and then delete it, however, I do not think it is ideal.
What approach is appropriate to follow in this case?

Related

ValueError when uploading resized django images to google cloud

i have this model which works fine when uploading resized images to the media file on my django project
class ItemImage(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
item = models.ForeignKey(Item, on_delete=models.CASCADE)
image = models.ImageField(null=True, blank=True,upload_to='item_img/')
created = models.DateTimeField(auto_now_add=True)
def save(self):
im = Image.open(self.image)
im_name = uuid.uuid4()
im = im.convert('RGB')
output = BytesIO()
# Resize/modify the image
im = im.resize((700, 700))
# 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, 'image/jpeg',
sys.getsizeof(output), None)
super(ItemImage, self).save()
def __str__(self):
return self.item.title
when i changed the file storage to google cloud i faced this error when uploading the images
ValueError at /ar/dashboard/my_items/edit_item/add_item_image/2/
Size 120495 was specified but the file-like object only had 120373 bytes remaining.
note that the images are uploaded successfully when i remove the save method that is added so is there anything that i need to change in that save method when dealing with gcloud?
i found a similar problem on github and he explained the error as follow "I think this is an error in the end user code which GCS rejects and the other services are more liberal about. The call sys.getsizeof(fi_io) yields the size of the BytesIO object, not the size of the buffer"
so i changed sys.getsizeof(output) to len(output.getbuffer())
and that's it it works with both google cloud and local media files

How to display an avatar?

I made a function that generates avatars for users when they create a profile:
users/models.py
def random_image():
directory = os.path.join(settings.BASE_DIR, 'media')
files = os.listdir(directory)
images = [file for file in files if os.path.isfile(os.path.join(directory, file))]
rand = choice(images)
return rand
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
avatar_new = models.ImageField (default=random_image)
def __str__(self):
return f'{self.user.username} Profile'
For changing avatar i am using django-avatar
There is a line like this (settings):
DEFAULT_URL = ("avatar/img/default.jpg")
I would like to display not a static image (default.jpg), but the result of the random function, which assigned the user an avatar. How can I make sure that the default url contains not a static image, but the result of the field avatar_new?
Tried itDEFAULT_URL = Profile.avatar_new error. Need help pls
django-avatar has its own model for storing Avatars. You can try to add your own AVATAR_PROVIDER as they describe in their documentation.
Something like this might work:-
from avatar.models import Avatar
from django.core.files import File
class RandomAvatarProvider(object):
#classmethod
def get_avatar_url(cls, user, size):
if not user or not user.is_authenticated:
return
avatar = Avatar(user=user, primary=True)
random_image = cls.random_image()
image_file = File(open(random_image, 'rb'))
avatar.avatar.save(image_file.name, image_file)
avatar.save()
return avatar.avatar_url(size)
#staticmethod
def random_image():
directory = os.path.join(settings.BASE_DIR, 'media')
files = os.listdir(directory)
images = [file for file in files if os.path.isfile(os.path.join(directory, file))]
rand = choice(images)
return os.path.join(directory, rand)
Also you would have to add this into your settings:-
AVATAR_PROVIDERS = (
'avatar.providers.PrimaryAvatarProvider',
'avatar.providers.GravatarAvatarProvider',
'app_name.where_provider_is.RandomAvatarProvider',
)
The random avatar provider must be last while you can chose which ones you want to keep above in which order.
Given your usecase perhaps you might want to remove GravatarAvatarProvider from AVATAR_PROVIDERS as it would always return some url.
Edit: add 'avatar.providers.DefaultAvatarProvider' at the end of AVATAR_PROVIDERS and a AVATAR_DEFAULT_URL for unauthenticated users, or write some logic for them in the space where I am returning nothing.

saving image/doc or media files dynamicallly created folder from input form in django

I want to make a Notes Platform where teachers can upload Notes and others can access it . The problem is whenever I am uploading the image it is going to the same folder and I am not able to categorise it according to year,branch,subject, and unit-wise.
Since all the media files are uploaded to the same folder I am unable to get the logic how will i able to fetch the media/image/doc file when a user query for the notes.
my model form is :-
class Note(models.Model):
year_choices = (
( 1 , 'First' ),
( 2 , 'Second'),
( 3 , 'Third' ),
( 4 , 'Fourth')
)
branch_choices = (
( 'IT','IT' ),
( 'EE','EE' ),
( 'CSE','CSE'),
( 'EC','EC' ),
( 'ME','ME' ),
( 'CE','CE' ),
)
unit_choices = ((1,'1'),(2,'2'),(3,'3'),(4,'4'),(5,'5'),
(6,'6'),(7,'7'),(8,'8'),(9,'9'),(10,'10'))
branch = models.CharField(max_length=55,choices=branch_choices)
year = models.IntegerField(choices = year_choices)
subject_name = models.CharField(max_length = 15)
unit = models.IntegerField(choices=unit_choices)
location = 'images'
picture = models.ImageField(upload_to = location)
My notes uploading form and searchform(for searching) is field is :-
class notesform(forms.ModelForm):
class Meta:
model = Note
fields = [ 'year','branch','subject_name','unit','picture' ]
class searchform(forms.ModelForm):
class Meta:
model = Note
fields = [ 'year','branch','subject_name','unit' ]
My notes adding function logic is :-
def addNotes(request):
if request.user.is_authenticated():
if request.method == "POST":
form = notesform(request.POST,request.FILES)
if form.is_valid():
profile = Note()
profile.year = form.cleaned_data["year"]
profile.branch = form.cleaned_data["branch"]
profile.subject_name = form.cleaned_data["subject_name"]
profile.picture = form.cleaned_data["picture"]
post = form.save(commit=False)
post.save()
return redirect('Notes', pk=post.pk)
else:
form = notesform()
return render(request, 'newNotes.html', {'form': form})
else:
return redirect('/accounts/login/')
I want to make upload in such a way that when the teacher fill the form and send upload the media the files upload according to the fields data he will be filling in. For ex:- Teacher fill the form as "First year" then "CSE branch" then "Data Structure" ad "Unit 1" , then it goes to the dynamic media folder "media/images/1/CSE/DATA_STRUCTURE/UNIT_1/".So that i can easily implement the search query .
If it can not applied in this way please suggest some other ways.
The problem is whenever I am uploading the image it is going to the same folder and I am not able to categorise it according to year,branch,subject, and unit-wise.
Instead of creating a new directory for very unit (which is ultimately going to result in a huge directory tree), you can provide a well structured name to uploaded picture and save it in the images directory itself.
Define a function to generate new name for picture:
def generate_picture_name(instance, filename):
url = "images/{0}_{1}_{2}_{3}.jpg".format(
instance.branch, instance.year, instance.subject_name, instance.unit)
return url
And update picture field to use generate_picture_name for saving image file.
picture = models.ImageField(upload_to = generate_picture_name)
Now the image file will be saved as:
media/images/CSE_1_DATA_STRUCTURE_UNIT_1.jpg

Populate model with metadata of file uploaded through django admin

I have two models,Foto and FotoMetadata. Foto just has one property called upload, that is an upload field. FotoMetadata has a few properties and should receive metadata from the foto uploaded at Foto. This can be done manually at the admin interface, but I want to do it automatically, i.e: when a photo is uploaded through admin interface, the FotoMetadata is automatically filled.
In my model.py I have a few classes, including Foto and FotoMetadata:
class Foto(models.Model):
upload = models.FileField(upload_to="fotos")
def __str__(self):
return '%s' %(self.upload)
class FotoMetadata(models.Model):
image_formats = (
('RAW', 'RAW'),
('JPG', 'JPG'),
)
date = models.DateTimeField()
camera = models.ForeignKey(Camera, on_delete=models.PROTECT)
format = models.CharField(max_length=8, choices=image_formats)
exposure = models.CharField(max_length=8)
fnumber = models.CharField(max_length=8)
iso = models.IntegerField()
foto = models.OneToOneField(
Foto,
on_delete=models.CASCADE,
primary_key=True,
)
When I login at the admin site, I have an upload form related to the Foto, and this is working fine. My problem is that I can't insert metadata at FotoMetadata on the go. I made a function that parse the photo and give me a dictionary with the info I need. This function is called GetExif is at a file called getexif.py. This will be a simplified version of it:
def GetExif(foto):
# Open image file for reading (binary mode)
f = open(foto, 'rb')
# Parse file
...
<parsing code>
...
f.close()
#create dictionary to receive data
meta={}
meta['date'] = str(tags['EXIF DateTimeOriginal'].values)
meta['fnumber'] = str(tags['EXIF FNumber'])
meta['exposure'] = str(tags['EXIF ExposureTime'])
meta['iso'] = str(tags['EXIF ISOSpeedRatings'])
meta['camera'] =str( tags['Image Model'].values)
return meta
So, basically, what I'm trying to do is use this function at admin.py to automatically populate the FotoMetadata when uploading a photo at Foto, but I really couldn't figure out how to make it. Does any one have a clue?
Edit 24/03/2016
Ok, after a lot more failures, I'm trying to use save_model in admin.py:
from django.contrib import admin
from .models import Autor, Camera, Lente, Foto, FotoMetadata
from fotomanager.local.getexif import GetExif
admin.site.register(Autor)
admin.site.register(Camera)
admin.site.register(Lente)
admin.site.register(FotoMetadata)
class FotoAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
# populate the model
obj.save()
# get metadata
metadados = GetExif(obj.upload.url)
# Create instance of FotoMetadata
fotometa = FotoMetadata()
# FotoMetadata.id = Foto.id
fotometa.foto = obj.pk
# save exposure
fotometa.exposure = metadados['exposure']
admin.site.register(Foto, FotoAdmin)
I thought it would work, or that I will have problems saving data to the model, but actually I got stucked before this. I got this error:
Exception Type: FileNotFoundError
Exception Value:
[Errno 2] No such file or directory: 'http://127.0.0.1:8000/media/fotos/IMG_8628.CR2'
Exception Location: /home/ricardo/Desenvolvimento/fotosite/fotomanager/local/getexif.py in GetExif, line 24
My GetExif function can't read the file, however, the file path is right! If I copy and paste it to my browser, it downloads the file. I'm trying to figure out a way to correct the address, or to pass the internal path, or to pass the real file to the function instead of its path. I'm also thinking about a diferent way to access the file at GetExif() function too. Any idea of how to solve it?
Solution
I solved the problem above! By reading the FileField source, I've found a property called path, which solve the problem. I also made a few other modifications and the code is working. The class FotoAdmin, at admin.py is like this now:
class FotoAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
# populate the model
obj.save()
# get metadata
metadados = GetExif(obj.upload.path)
# Create instance of FotoMetadata
fotometa = FotoMetadata()
# FotoMetadata.id = Foto.id
fotometa.foto = obj
# set and save exposure
fotometa.exposure = metadados['exposure']
fotometa.save()
I also had to set null=True at some properties in models.py and everything is working as it should.
I guess you want to enable post_save a signal
read : django signals
Activate the post_save signal - so after you save a FOTO you have a hook to do other stuff, in your case parse photometa and create a FotoMetadata instance.
More, if you want to save the foto only if fotometa succeed , or any other condition you may use , pre_save signal and save the foto only after meta foto was saved.

Transcode video using celery and ffmpeg in django

I would like to transcode user uploaded videos using celery. I think first I should upload the video, and spawn a celery task for transcoding.
Maybe something like this in the tasks.py:
subprocess.call('ffmpeg -i path/.../original path/.../output')
Just completed First steps with celery, so confused how to do so in the views.py and tasks.py. Also is it a good solution? I would really appreciate your help and advice. Thank you.
models.py:
class Video(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=100)
original = models.FileField(upload_to=get_upload_file_name)
mp4_480 = models.FileField(upload_to=get_upload_file_name, blank=True, null=True)
mp4_720 = models.FileField(upload_to=get_upload_file_name, blank=True, null=True)
privacy = models.CharField(max_length=1,choices=PRIVACY, default='F')
pub_date = models.DateTimeField(auto_now_add=True, auto_now=False)
my incomplete views.py:
#login_required
def upload_video(request):
if request.method == 'POST':
form = VideoForm(request.POST, request.FILES)
if form.is_valid():
if form.cleaned_data:
user = request.user
#
#
# No IDEA WHAT TO DO NEXT
#
#
return HttpResponseRedirect('/')
else:
form = VideoForm()
return render(request, 'upload_video.html', {
'form':form
})
I guess you already have solved the problem but I will provide a bit more information to what already said GwynBleidD because I had the same issue.
So as GwynBleidD you need to call Celery tasks, but how to code those tasks ? here is the structure :
the task get the video from the database
it encodes it with ffmepg and outputs it anywhere you want
when done with the encoding, it sets the corresponding attribute to the model and saves it (be careful, if you run various tasks on the same video, do not save with the old instance, as you may lose information from other tasks running)
First, set a FFMPEG_PATH variable in your settings, then:
import os, subprocess
from .models import Video
#app.task
def encode_mp4(video_id, height):
try:
video = Video.objects.get(id = video_id)
input_file_path = video.original.path
input_file_name = video.original.name
#get the filename (without extension)
filename = os.path.basename(input_file_path)
# path to the new file, change it according to where you want to put it
output_file_name = os.path.join('videos', 'mp4', '{}.mp4'.format(filename))
output_file_path = os.path.join(settings.MEDIA_ROOT, output_file_name)
# 2-pass encoding
for i in range(1):
subprocess.call([FFMPEG_PATH, '-i', input_file_path, '-s', '{}x{}'.format(height * 16 /9, height), '-vcodec', 'mpeg4', '-acodec', 'libvo_aacenc', '-b', '10000k', '-pass', i, '-r', '30', output_file_path])
# Save the new file in the database
video.mp4_720.name = output_file_name
video.save(update_fields=['mp4_720'])
Modify your model so you can save original (uploaded) video without transcoded version(s) and maybe add some flag into your model that will save state if video was transcoded (and based on that flag you can display to user that video transcoding is still in progress).
After uploading video and saving it's model to database, run celery task passing ID of your video into it. In celery task retrieve video from database, transcode it and save it into database with changed flag.