File size: 9,170 Bytes
e920ad7
a401535
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e920ad7
 
a401535
e920ad7
 
ffd7fc4
a401535
e920ad7
 
a401535
e920ad7
 
a401535
 
e920ad7
a401535
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e920ad7
a401535
 
 
 
 
 
5e281e2
 
e920ad7
a401535
e920ad7
a401535
 
 
 
 
 
 
 
ffd7fc4
 
a401535
 
 
 
 
 
e920ad7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5e281e2
a401535
 
 
e920ad7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
import os
import cv2
import pandas as pd
import gradio as gr
from ultralytics import YOLO
from datetime import datetime, timedelta
import numpy as np

class AttendanceSystem:
    def __init__(self, model_path, csv_path):
        self.model_path = model_path
        self.csv_path = csv_path
        self.model = None
        self.class_names_list = []
        self.time_list = []
        self.load_previous_attendance()

    def load_model(self):
        """Load the YOLO model"""
        if self.model is None:
            try:
                self.model = YOLO(self.model_path)
                return True, "Model loaded successfully!"
            except Exception as e:
                return False, f"Error loading model: {str(e)}"
        return True, "Model already loaded"

    def load_previous_attendance(self):
        """Load previous attendance data if CSV exists"""
        if os.path.exists(self.csv_path):
            try:
                df = pd.read_csv(self.csv_path)
                if not df.empty:
                    self.class_names_list = df["Class Name"].tolist()
                    self.time_list = df["Time"].tolist()
                    return True, f"Loaded {len(self.class_names_list)} previous attendance records"
            except Exception as e:
                return False, f"Error loading previous attendance: {str(e)}"
        return False, "No previous attendance data found"

    def process_frame(self, frame):
        """Process a single frame and update attendance"""
        if self.model is None:
            success, message = self.load_model()
            if not success:
                return frame, message, [], []

        # Create a copy of the frame to draw on
        display_frame = frame.copy()

        # Store detected names in this frame
        detected_names = []

        # Detect objects
        results = self.model(frame)

        # Check if any results are detected
        if results:
            for result in results:
                boxes = result.boxes
                for box in boxes:
                    x1, y1, x2, y2 = box.xyxy[0].cpu().numpy().astype(int)
                    class_id = int(box.cls[0])
                    confidence = float(box.conf[0])

                    # Get the class name from YOLO class names
                    class_name = self.model.names[class_id]
                    detected_names.append(class_name)

                    # Draw rectangle around detected object
                    cv2.rectangle(display_frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
                    cv2.putText(display_frame, f"{class_name}: {confidence:.2f}",
                                (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2)

                    # Get the current time
                    current_time = datetime.now()
                    current_time_str = current_time.strftime("%Y-%m-%d %H:%M:%S")

                    # Check if the class has been recorded already
                    person_already_recorded = False
                    for idx, name in enumerate(self.class_names_list):
                        if name == class_name:
                            last_recorded_time = datetime.strptime(self.time_list[idx], "%Y-%m-%d %H:%M:%S")
                            # If less than 24 hours have passed since the last recording
                            if (current_time - last_recorded_time) < timedelta(hours=24):
                                person_already_recorded = True
                                break

                    # Record attendance if not already recorded in the last 24 hours
                    if not person_already_recorded:
                        self.class_names_list.append(class_name)
                        self.time_list.append(current_time_str)
                        self.save_attendance()

        # Create attendance list for display
        attendance_data = []
        for name, time_str in zip(self.class_names_list, self.time_list):
            attendance_data.append(f"{name} - {time_str}")

        return display_frame, f"Detected: {', '.join(detected_names) if detected_names else 'None'}", attendance_data, detected_names

    def save_attendance(self):
        """Save attendance data to CSV"""
        if self.class_names_list and self.time_list:
            df = pd.DataFrame({
                "Class Name": self.class_names_list,
                "Time": self.time_list
            })
            try:
                df.to_csv(self.csv_path, index=False)
                return True, "Attendance saved to CSV"
            except Exception as e:
                return False, f"Error saving to CSV: {str(e)}"
        return False, "No attendance data to save"

    def clear_attendance(self):
        """Clear attendance records"""
        self.class_names_list = []
        self.time_list = []
        if os.path.exists(self.csv_path):
            try:
                os.remove(self.csv_path)
                return True, "Attendance records cleared"
            except Exception as e:
                return False, f"Error clearing records: {str(e)}"
        return True, "No records to clear"


# Function for Gradio interface - process images from webcam
def process_webcam_image(image, state):
    if state is None:
        # Default paths - update these to match your Hugging Face deployment
        model_path = "best(attendance).pt"  # Make sure to upload this model to your Space
        csv_path = "attendance_data.csv"
        state = AttendanceSystem(model_path, csv_path)
        # Load model immediately
        state.load_model()

    if image is None:
        return None, "No image received", "", state

    # Process the frame
    processed_frame, message, attendance_data, detected_names = state.process_frame(image)

    # Format attendance as HTML table for better display
    if attendance_data:
        attendance_html = "<table style='width:100%; border-collapse: collapse;'>"
        attendance_html += "<tr><th style='border:1px solid black; padding:8px;'>Name</th><th style='border:1px solid black; padding:8px;'>Time</th></tr>"

        for record in attendance_data:
            name, time_str = record.split(" - ", 1)
            attendance_html += f"<tr><td style='border:1px solid black; padding:8px;'>{name}</td><td style='border:1px solid black; padding:8px;'>{time_str}</td></tr>"

        attendance_html += "</table>"
    else:
        attendance_html = "No attendance records."

    return processed_frame, message, attendance_html, state


def clear_attendance_records(state):
    if state is not None:
        success, message = state.clear_attendance()
        return message, "<table></table>", state
    return "System not initialized", "<table></table>", None


def change_model_path(model_path, csv_path, state):
    if not model_path or not csv_path:
        return "Please provide both paths", state

    state = AttendanceSystem(model_path, csv_path)
    success, message = state.load_model()
    return message, state


# Create Gradio interface that works on Hugging Face
with gr.Blocks(title="Attendance System") as app:
    gr.Markdown("# Automated Attendance System")
    gr.Markdown("This system uses YOLO to detect and record attendance of individuals.")

    with gr.Row():
        with gr.Column(scale=2):
            # Fixed: Use sources instead of source for the webcam component
            input_image = gr.Image(label="Webcam Feed", sources=["webcam"])
            process_button = gr.Button("Process Image")
            output_image = gr.Image(label="Processed Feed")
            status_text = gr.Textbox(label="Status", value="Capture an image and click 'Process Image'")

        with gr.Column(scale=1):
            # Attendance records and controls
            attendance_display = gr.HTML(label="Attendance Records", value="No records yet.")
            clear_button = gr.Button("Clear Attendance Records")

            # Configuration options
            with gr.Accordion("Configuration", open=False):
                model_path_input = gr.Textbox(label="Model Path", value="best(attendance).pt")
                csv_path_input = gr.Textbox(label="CSV Output Path", value="attendance_data.csv")
                update_paths_button = gr.Button("Update Paths")

    # State for storing the attendance system object
    state = gr.State(None)

    # Set up event handlers
    process_button.click(
        process_webcam_image, 
        inputs=[input_image, state],
        outputs=[output_image, status_text, attendance_display, state]
    )

    clear_button.click(
        clear_attendance_records, 
        inputs=[state],
        outputs=[status_text, attendance_display, state]
    )

    update_paths_button.click(
        change_model_path, 
        inputs=[model_path_input, csv_path_input, state],
        outputs=[status_text, state]
    )

# Create a requirements.txt file for Hugging Face
with open('requirements.txt', 'w') as f:
    f.write('opencv-python-headless\npandas\ngradio>=3.32.0\nultralytics')

# Launch the app
if __name__ == "__main__":
    app.launch()  # Remove share=True for Hugging Face deployment