Skip to content

Commit

Permalink
#15 Fixed attendance time + VideoFeed support added for multiple devi…
Browse files Browse the repository at this point in the history
…ce cameras + Improved training
  • Loading branch information
codeglitchz committed Sep 27, 2020
1 parent bdb8d8b commit 1ddd96d
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 66 deletions.
19 changes: 6 additions & 13 deletions backend/src/libs/cli_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,6 @@ def recognize_n_attendance(self):
# store input video stream in cap variable
cap = cv2.VideoCapture(self.input_video)

# find if today's attendance exists in the database
attendance = AttendanceModel.find_by_date(date=dt.today())
# if not
if attendance is None:
# create new instance for today's attendance
attendance = AttendanceModel()

# create in dictionary for known students from database to avoid multiple queries
known_students = {}

Expand Down Expand Up @@ -163,12 +156,12 @@ def recognize_n_attendance(self):
# find matched student in the database by id
student = StudentModel.find_by_id(_id)
known_students[_id] = student
# if student's attendance is not marked
if not attendance.is_marked(student):
# then mark student's attendance
attendance.students.append(student)
# commit changes to database
attendance.save_to_db()
# if student's attendance is not marked
if not AttendanceModel.is_marked(dt.today(), student):
# then mark student's attendance
student_attendance = AttendanceModel(student=student)
# commit changes to database
student_attendance.save_to_db()
# update displayed name to student's name
display_name = student.name
# append the name to be displayed in names list
Expand Down
23 changes: 8 additions & 15 deletions backend/src/libs/web_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,15 @@ def frames(cls):
data = pickle.loads(open(ENCODINGS_FILE, "rb").read())
# print(len(data['encodings']) == len(data['ids']))

# find if today's attendance exists in the database
attendance = AttendanceModel.find_by_date(date=dt.today())
# if not
if attendance is None:
# create new instance for today's attendance
attendance = AttendanceModel()

# create in dictionary for known students from database to avoid multiple queries
known_students = {}
while True:
# read current frame
_, img = camera.read()
yield cls.recognize_n_attendance(img, attendance, data, known_students)
yield cls.recognize_n_attendance(img, data, known_students)

@classmethod
def recognize_n_attendance(cls, frame: np.ndarray, attendance: AttendanceModel,
def recognize_n_attendance(cls, frame: np.ndarray,
data: Dict, known_students: Dict) -> bytes:
# convert the input frame from BGR to RGB then resize it to have
# a width of 750px (to speedup processing)
Expand Down Expand Up @@ -106,12 +99,12 @@ def recognize_n_attendance(cls, frame: np.ndarray, attendance: AttendanceModel,
# find matched student in the database by id
student = StudentModel.find_by_id(_id)
known_students[_id] = student
# if student's attendance is not marked
if not attendance.is_marked(student):
# then mark student's attendance
attendance.students.append(student)
# commit changes to database
attendance.save_to_db()
# if student's attendance is not marked
if not AttendanceModel.is_marked(dt.today(), student):
# then mark student's attendance
student_attendance = AttendanceModel(student=student)
# commit changes to database
student_attendance.save_to_db()
# update displayed name to student's name
display_name = student.name
# append the name to be displayed in names list
Expand Down
57 changes: 31 additions & 26 deletions backend/src/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
from uuid import uuid4
from datetime import date as dt, datetime as dtime

from sqlalchemy import Column, Integer, String, Boolean, Date, TIMESTAMP, ForeignKey
from sqlalchemy import Column, Integer, String, Boolean, DateTime, TIMESTAMP, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, backref
from sqlalchemy.sql import func


from src.db import Session
Expand Down Expand Up @@ -37,22 +38,15 @@ def delete_from_db(self) -> None:
Session.commit()


class StudentAttendances(Base):
__tablename__ = "student_attendances"
student_id = Column(Integer, ForeignKey("students.id"), primary_key=True)
date = Column(Date, ForeignKey("attendances.date"), primary_key=True)


class StudentModel(Base):
__tablename__ = "students"

id = Column(Integer, primary_key=True)
name = Column(String(80), unique=True, nullable=False)
# attendances = relationship(
# "AttendanceModel",
# secondary="student_attendances",
# # backref=backref("students", lazy="dynamic")
# )
attendances = relationship(
"AttendanceModel",
backref=backref("student")
)

@classmethod
def find_by_name(cls, name: str) -> "StudentModel":
Expand All @@ -76,26 +70,31 @@ def delete_from_db(self) -> None:


class AttendanceModel(Base):
# Many students have many attendances
# Many to Many Relationship
# One student has many attendances
# One to Many Relationship
__tablename__ = "attendances"

# id = Column(String(50), default=uuid4().hex, primary_key=True)
date = Column(Date, default=dt.today, primary_key=True)
# TODO: change time to show each student's time rather than 1 static time
time = Column(TIMESTAMP(timezone=False), default=dtime.now)
# time = Column(TIMESTAMP(timezone=False), default=dtime.now)
date = Column(DateTime(timezone=True), default=dtime.now, primary_key=True) # default=func.now
student_id = Column(Integer, ForeignKey("students.id"))
# creates AttendanceModel.students as list and
# backref StudentModel.attendances as AppenderQuery object
# which can be accessed by StudentModel.attendances.all()
students = relationship(
"StudentModel",
secondary="student_attendances",
backref=backref("attendances", lazy="dynamic")
)
# students = relationship(
# "StudentModel",
# # secondary="student_attendances",
# foreign_keys="StudentModel.id",
# backref=backref("attendances", lazy="dynamic")
# )

@classmethod
def find_by_date(cls, date: dt, student: StudentModel) -> "AttendanceModel":
return Session.query(cls).filter_by(date=date, student=student).first()

@classmethod
def find_by_date(cls, date: dt) -> "AttendanceModel":
return Session.query(cls).filter_by(date=date).first()
def find_by_student(cls, student: StudentModel) -> "AttendanceModel":
return Session.query(cls).filter_by(student=student).first()

@classmethod
def find_by_time(cls, time: dtime) -> "AttendanceModel":
Expand All @@ -109,8 +108,14 @@ def find_all(cls) -> List["AttendanceModel"]:
# print(f"Date: {x.AttendanceModel.date} Name: {x.StudentModel.name} Time: {x.AttendanceModel.time}")
return Session.query(cls).all()

def is_marked(self, student: StudentModel) -> bool:
return student in self.students
@classmethod
def is_marked(cls, date: dt, student: StudentModel) -> bool:
marked = AttendanceModel.find_by_date(date, student)
if marked is None:
marked = False
else:
marked = True
return marked

def save_to_db(self) -> None:
Session.add(self)
Expand Down
9 changes: 9 additions & 0 deletions backend/src/resources/video_feed.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,17 @@ def get(cls, feed_id: str):
video_feed = VideoFeedModel.find_by_id(feed_id)
feed_url = video_feed.url
camera_stream = RecognitionCamera
# Camera Device Selection
if feed_url == "0":
feed_url = 0
elif feed_url == "1":
feed_url = 1
elif feed_url == "2":
feed_url = 2
elif feed_url == "3":
feed_url = 3
elif feed_url == "4":
feed_url = 4
camera_stream.set_video_source(feed_url)
if video_feed:
resp = Response(
Expand Down
8 changes: 3 additions & 5 deletions backend/src/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,10 @@ class AttendanceSchema(SQLAlchemyAutoSchema):
class Meta:
model = AttendanceModel
# load_only = () # during deserialization dictionary -> object
dump_only = ("date", "time", "students") # during serialization object -> dictionary
dump_only = ("date", "student") # during serialization object -> dictionary
load_instance = True # Optional: deserialize to object/model instances
# Override books field to use a nested representation rather than pks
students = Nested(
StudentSchema,
many=True
student = Nested(
StudentSchema
)


Expand Down
2 changes: 2 additions & 0 deletions frontend/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { TokenInterceptorService } from './services/token-interceptor.service';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { CommonModule } from '@angular/common';

@NgModule({
declarations: [
Expand All @@ -20,6 +21,7 @@ import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
],
imports: [
BrowserModule,
CommonModule,
AppRoutingModule,
ReactiveFormsModule,
HttpClientModule,
Expand Down
12 changes: 5 additions & 7 deletions frontend/src/app/components/attendance/attendance.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@
</tr>
</thead>
<tbody>
<ng-container *ngFor="let day of attendances; index as i">
<tr *ngFor="let student of day.students; index as j">
<th scope="row">{{ day.date | date:'shortDate'}}</th>
<td> {{ student.name }} </td>
<td> {{ day.time | date:'mediumTime' }} </td>
</tr>
</ng-container>
<tr *ngFor="let attendance of attendances">
<th scope="row">{{ attendance.date | date:'shortDate'}}</th>
<td> {{ attendance.student.name }} </td>
<td> {{ attendance.date | date:'mediumTime' }} </td>
</tr>
</tbody>
</table>

0 comments on commit 1ddd96d

Please sign in to comment.