Skip to content

Commit 27289fe

Browse files
committed
fixes
1 parent 3413029 commit 27289fe

13 files changed

+62
-148
lines changed

README.md

-7
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,6 @@ A face recognition based attendance system.
77
[![views-counter](https://github.com/Husseinfo/views-counter/blob/master/svg/90946301/badge.svg)](https://github.com/Husseinfo/views-counter/blob/master/readme/90946301/year.md)
88
[![GitHub stars](https://img.shields.io/github/stars/husseinfo/tracker.svg)](https://github.com/husseinfo/tracker/stargazers)
99

10-
## ***UPDATE***
11-
12-
- [OpenCV](https://github.com/opencv/opencv) replaced
13-
with [face_recognition](https://github.com/ageitgey/face_recognition/)
14-
- Upgraded to [Django 4](https://github.com/django/django/releases/tag/4.0.6)
15-
- UI with [django-bootstrap5](https://github.com/zostera/django-bootstrap5)
16-
1710
![homepage](https://github.com/Husseinfo/tracker/blob/main/static/images/homepage.png?raw=true)
1811

1912
## Running

templates/form.html

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
{% load django_bootstrap5 %}
44

55
{% block content %}
6+
{% block extra %}{% endblock %}
7+
68
<div class="col-lg-4"></div>
79
<div class="col-lg-4">
810
<div class="card card-primary">

templates/home.html

+7-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,13 @@ <h5 class="card-title">{{ users }}</h5>
3030
<div class="col-4">
3131
<div class="card">
3232
<div class="card-body">
33-
<h5 class="card-title">{{ last_training|timesince }}</h5>
33+
<h5 class="card-title">
34+
{% if last_training %}
35+
{{ last_training|timesince }}
36+
{% else %}
37+
N/A
38+
{% endif %}
39+
</h5>
3440
<p class="card-text">
3541
Last Training
3642
</p>

templates/profile.html

+2-3
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,12 @@ <h5 class="card-title">
4141
<dt>Address:</dt>
4242
<dd>{{ user.address }}</dd>
4343
</dl>
44-
4544
</div>
4645
<div class="tab-pane fade" id="images-tab-pane" role="tabpanel" aria-labelledby="images-tab"
4746
tabindex="0">
48-
{% for image in images %}
47+
{% for image in user.image_set.all %}
4948
{% with 'photos/'|add:image as image_static %}
50-
<img src="{% static image_static %}" alt="Image {{ image }}" class="w-100">
49+
<img src="/{{ image.Image }}" alt="{{ image.Image }}" class="w-100">
5150
{% endwith %}
5251
{% endfor %}
5352
</div>

templates/userdetails.html

+16
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,19 @@
33
{% load django_bootstrap5 %}
44

55
{% block title %}User{% endblock %}
6+
7+
{% block extra %}
8+
<div class="container-fluid">
9+
<div class="row">
10+
{% if request.GET.status == 'empty' %}
11+
<div class="col-lg-4"></div>
12+
<div class="col-lg-4">
13+
<div class="alert alert-dismissible alert-warning fade show">
14+
<strong>No enough data! </strong>You need to add new users and photos to train model.
15+
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
16+
</div>
17+
</div>
18+
{% endif %}
19+
</div>
20+
</div>
21+
{% endblock %}

tests/test_recognition.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
from django.test import TestCase
55

6+
from tracker.engine import train, predict
67
from tracker.models import User
7-
from tracker.recognition import get_nbr_photos, train, predict
88

99

1010
class RecognitionTestCase(TestCase):
@@ -15,10 +15,7 @@ def setUp(self):
1515
User.objects.create(first_name="Barack", last_name="Obama")
1616
User.objects.create(first_name="Cristiano", last_name="Ronaldo")
1717

18-
def test_get_nbr_photos(self):
19-
self.assertEqual(get_nbr_photos(self.photos_train_path), 2)
20-
21-
def test_train(self):
18+
def test_training(self):
2219
if isfile(self.model_path):
2320
remove(self.model_path)
2421
train(self.model_path, self.photos_train_path)

tracker/recognition.py tracker/engine.py

-7
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,6 @@
1010
knn_clf: KNeighborsClassifier | None = None
1111

1212

13-
def get_nbr_photos(photos=photos_path) -> int:
14-
try:
15-
return len(listdir(photos))
16-
except Exception as e:
17-
return 0
18-
19-
2013
def get_dataset(photos=photos_path):
2114
dataset = [[], []]
2215
for photo in listdir(photos):

tracker/migrations/0001_initial.py

+2-22
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,16 @@
1-
# Generated by Django 4.0.6 on 2022-07-28 18:08
2-
31
import datetime
4-
from django.db import migrations, models
2+
53
import django.db.models.deletion
4+
from django.db import migrations, models
65

76

87
class Migration(migrations.Migration):
9-
108
initial = True
119

1210
dependencies = [
1311
]
1412

1513
operations = [
16-
migrations.CreateModel(
17-
name='Task',
18-
fields=[
19-
('id', models.AutoField(primary_key=True, serialize=False)),
20-
('name', models.CharField(max_length=32)),
21-
],
22-
),
2314
migrations.CreateModel(
2415
name='User',
2516
fields=[
@@ -50,15 +41,4 @@ class Migration(migrations.Migration):
5041
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tracker.user')),
5142
],
5243
),
53-
migrations.CreateModel(
54-
name='UserTask',
55-
fields=[
56-
('id', models.AutoField(primary_key=True, serialize=False)),
57-
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tracker.task')),
58-
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tracker.user')),
59-
],
60-
options={
61-
'unique_together': {('task', 'user')},
62-
},
63-
),
6444
]

tracker/models.py

-14
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,3 @@ class Attendance(models.Model):
3232
user = models.ForeignKey(User, on_delete=models.CASCADE)
3333
date = models.DateTimeField("date")
3434
inout = models.BooleanField("inout", null=True)
35-
36-
37-
class Task(models.Model):
38-
id = models.AutoField(name='id', primary_key=True)
39-
name = models.CharField(max_length=32)
40-
41-
42-
class UserTask(models.Model):
43-
id = models.AutoField(name='id', primary_key=True)
44-
task = models.ForeignKey(Task, on_delete=models.CASCADE)
45-
user = models.ForeignKey(User, on_delete=models.CASCADE)
46-
47-
class Meta:
48-
unique_together = (("task", "user"),)

tracker/settings.py

+1-5
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
'django.contrib.messages',
3737
'django.contrib.staticfiles',
3838
'tracker.apps.TrackerConfig',
39-
'rest_framework',
4039
'django_bootstrap5',
4140
]
4241

@@ -113,13 +112,10 @@
113112
LANGUAGE_CODE = 'en-us'
114113

115114
TIME_ZONE = 'UTC'
116-
115+
USE_TZ = True
117116
USE_I18N = True
118-
119117
USE_L10N = True
120118

121-
USE_TZ = True
122-
123119
# Static files (CSS, JavaScript, Images)
124120
# https://docs.djangoproject.com/en/1.11/howto/static-files/
125121

tracker/urls.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@
1111
path(r'capture', view=views.capture, name='capture'),
1212
path(r'upload', view=views.upload, name='upload'),
1313
path(r'train/', view=views.train, name='train'),
14-
path(r'sendimage/', view=views.receive_images, name='sendimage'),
1514

16-
path(r'users', view=views.display_users, name='users'),
15+
path(r'users', view=views.users, name='users'),
1716
path(r'profile/<user_id>', view=views.profile, name="profile"),
1817
path(r'adduser/', view=views.add_user, name='adduser'),
1918
path(r'edituser/<user_id>', view=views.edit_user, name="edituser"),

tracker/utility.py

-46
This file was deleted.

tracker/views.py

+29-36
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
11
from base64 import b64decode
22
from datetime import datetime
33
from math import ceil
4-
from os import listdir, remove
4+
from os import remove
5+
from os.path import isfile, getmtime
56
from time import time
67

78
from django.contrib.auth.decorators import login_required
8-
from django.shortcuts import render, redirect, HttpResponse
9+
from django.shortcuts import render, redirect
910

10-
from tracker import photos_path, utility, temp_path
11+
from tracker import model_filename, temp_path
12+
from tracker.engine import predict, train as do_train
1113
from tracker.forms import UserForm, ImageForm
1214
from tracker.models import Attendance, User, Image
13-
from tracker.recognition import predict, get_nbr_photos, train as do_train
1415

1516

1617
@login_required
1718
def home(request):
18-
return render(request, "home.html",
19-
{'photos': get_nbr_photos(),
20-
'users': User.objects.count(),
21-
'last_training': utility.last_training()}, )
19+
return render(request, "home.html", {
20+
'photos': Image.objects.count(),
21+
'users': User.objects.count(),
22+
'last_training': datetime.fromtimestamp(getmtime(model_filename)) if isfile(model_filename) else None
23+
})
2224

2325

2426
@login_required
2527
def add_user(request):
2628
if request.method == 'POST':
27-
form = UserForm(request.POST, request.FILES)
28-
instance = form.save(commit=False)
29-
instance.save()
30-
return redirect(home)
29+
UserForm(request.POST, request.FILES).save()
30+
return redirect('users')
3131
return render(request, 'userdetails.html', {'formset': UserForm()})
3232

3333

@@ -62,14 +62,14 @@ def upload(request):
6262

6363

6464
@login_required
65-
def display_users(request):
65+
def users(request):
6666
return render(request, 'user.html', {'users': User.objects.all()})
6767

6868

6969
@login_required
7070
def train(request):
71-
if not utility.are_there_photos():
72-
return redirect('/capture/?status=empty')
71+
if not Image.objects.count():
72+
return redirect('/capture?status=empty')
7373
if request.method == 'GET':
7474
return render(request, 'train.html')
7575
start = time()
@@ -78,51 +78,44 @@ def train(request):
7878
return render(request, 'train.html', {'duration': duration})
7979

8080

81-
@login_required
82-
def receive_images(request):
83-
label = request.POST.get('label')
84-
photos = request.POST.getlist('photos[]')
85-
utility.save_base64_photos(label, photos)
86-
return HttpResponse()
87-
88-
8981
@login_required
9082
def profile(request, user_id):
91-
user_data = User.objects.get(pk=user_id)
92-
images = [filename for filename in listdir(photos_path) if filename.split('_')[0] == str(user_id)]
93-
return render(request, 'profile.html', {'user': user_data, 'images': images})
83+
return render(request, 'profile.html', {'user': User.objects.get(pk=user_id)})
9484

9585

9686
@login_required
9787
def delete_user(request, user_id):
98-
User.objects.filter(id=user_id).delete()
99-
images = [filename for filename in listdir(photos_path) if filename.split('_')[0] == str(user_id)]
100-
for image in images:
101-
remove('static/photos/' + image)
88+
user = User.objects.get(id=user_id)
89+
for image in user.image_set.all():
90+
try:
91+
remove(image.Image.path)
92+
except (FileNotFoundError, ValueError):
93+
pass
94+
user.delete()
10295
return redirect('users')
10396

10497

10598
@login_required
10699
def recognize(request):
107-
if not utility.is_model_trained():
100+
if not isfile(model_filename):
108101
return redirect('/train/?status=untrained')
109102
if request.method == 'POST':
110-
paths = []
103+
files = []
111104
if frame := request.POST.get('frame'):
112105
ext, img = frame.split(';base64,')
113106
ext = ext.split('/')[-1]
114107
name = f'{temp_path}/rec_frame.{ext}'
115108
with open(name, 'wb') as fh:
116109
fh.write(b64decode(img))
117-
paths.append(name)
110+
files.append(name)
118111
if images := request.FILES.getlist('images'):
119112
for i, image in enumerate(images):
120113
name = f'{temp_path}/rec{i}.jpg'
121114
with open(name, 'wb') as fh:
122115
fh.write(image.file.read())
123-
paths.append(name)
124-
if paths:
125-
if predictions := predict(paths):
116+
files.append(name)
117+
if files:
118+
if predictions := predict(files):
126119
user_id, confidence = predictions[0][:2]
127120
user = User.objects.get(id=user_id)
128121
name = 'Unknown' if user_id in (-1, None) else user.first_name + ' ' + user.last_name

0 commit comments

Comments
 (0)