Spaces:
Build error
Build error
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Facility Management System</title> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css"> | |
| <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> | |
| <style> | |
| :root { | |
| --primary-color: #2563eb; /* Royal Blue */ | |
| --secondary-color: #4b5563; /* Dark Gray */ | |
| --success-color: #059669; /* Emerald */ | |
| --warning-color: #d97706; /* Dark Amber */ | |
| --danger-color: #dc2626; /* Red */ | |
| --background-color: #1e293b; /* Dark Blue Gray */ | |
| --card-background: #334155; /* Lighter Blue Gray */ | |
| --sidebar-background: #0f172a; /* Darkest Blue */ | |
| --text-primary: #f8fafc; /* Almost White */ | |
| --text-secondary: #cbd5e1; /* Light Gray */ | |
| --input-background: #475569; /* Medium Gray Blue */ | |
| --border-color: #64748b; /* Border Gray */ | |
| --hover-color: #1d4ed8; /* Darker Blue */ | |
| --border-radius: 0.75rem; | |
| } | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background-color: var(--background-color); | |
| color: var(--text-primary); | |
| line-height: 1.6; | |
| } | |
| .navbar { | |
| background-color: var(--card-background); | |
| box-shadow: 0 1px 3px rgba(0,0,0,0.1); | |
| padding: 1rem 0; | |
| } | |
| .navbar-brand { | |
| font-weight: 700; | |
| color: var(--primary-color); | |
| } | |
| .main-container { | |
| max-width: 1400px; | |
| margin: 2rem auto; | |
| padding: 0 1rem; | |
| } | |
| .sidebar { | |
| background-color: var(--sidebar-background); | |
| border-radius: var(--border-radius); | |
| padding: 1.5rem; | |
| box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); | |
| } | |
| .form-control { | |
| background-color: #d2dae8; | |
| color: var(--text-primary); | |
| border-radius: 0.5rem; | |
| border: 1px solid var(--border-color); | |
| padding: 0.75rem; | |
| transition: all 0.3s ease; | |
| } | |
| .form-control:focus { | |
| background-color: var(--input-background); | |
| color: var(--text-primary); | |
| border-color: var(--primary-color); | |
| box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2); | |
| } | |
| .btn { | |
| padding: 0.75rem 1.5rem; | |
| border-radius: 0.5rem; | |
| font-weight: 600; | |
| transition: all 0.3s ease; | |
| } | |
| .btn-primary { | |
| background-color: var(--primary-color); | |
| border: none; | |
| } | |
| .btn-primary:hover { | |
| background-color: var(--hover-color); | |
| transform: translateY(-1px); | |
| } | |
| .facility-card { | |
| background-color: var(--card-background); | |
| border-radius: var(--border-radius); | |
| border: none; | |
| box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); | |
| margin-bottom: 1.5rem; | |
| overflow: hidden; | |
| } | |
| .facility-card .card-header { | |
| background-color: var(--sidebar-background); | |
| color: var(--text-primary); | |
| padding: 1rem; | |
| font-weight: 600; | |
| border-bottom: none; | |
| } | |
| .facility-card .card-body { | |
| padding: 1.5rem; | |
| } | |
| .upload-section { | |
| background-color: var(--card-background); | |
| border: 2px dashed var(--border-color); | |
| border-radius: 0.5rem; | |
| padding: 1.5rem; | |
| text-align: center; | |
| transition: all 0.3s ease; | |
| } | |
| .upload-section:hover { | |
| border-color: var(--primary-color); | |
| } | |
| .grade-container { | |
| background-color: var(--card-background); | |
| border-radius: var(--border-radius); | |
| padding: 2rem; | |
| text-align: center; | |
| margin-top: 2rem; | |
| } | |
| .grade-display { | |
| font-size: 3rem; | |
| font-weight: 700; | |
| margin: 1rem 0; | |
| color: var(--primary-color); | |
| } | |
| .stats-card { | |
| background-color: var(--card-background); | |
| border-radius: var(--border-radius); | |
| padding: 1.5rem; | |
| margin-bottom: 1rem; | |
| } | |
| .stats-value { | |
| font-size: 1.5rem; | |
| font-weight: 700; | |
| color: var(--primary-color); | |
| } | |
| /* Add more modern styling for tables */ | |
| .table { | |
| background-color: var(--card-background); | |
| border-radius: var(--border-radius); | |
| overflow: hidden; | |
| color: var(--text-primary); | |
| } | |
| .table thead th { | |
| background-color: var(--sidebar-background); | |
| border-bottom: 2px solid #e2e8f0; | |
| color: var(--text-secondary); | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| font-size: 0.875rem; | |
| border-bottom-color: #475569; | |
| } | |
| /* Toast notifications */ | |
| .toast { | |
| position: fixed; | |
| top: 1rem; | |
| right: 1rem; | |
| padding: 1rem 1.5rem; | |
| border-radius: 0.5rem; | |
| background-color: var(--card-background); | |
| box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); | |
| z-index: 1000; | |
| color: var(--text-primary); | |
| } | |
| /* Loading animation */ | |
| .loading-spinner { | |
| width: 2.5rem; | |
| height: 2.5rem; | |
| border: 3px solid #f3f3f3; | |
| border-top: 3px solid var(--primary-color); | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .container { | |
| background-color: var(--card-background); | |
| border-radius: var(--border-radius); | |
| padding: 20px; | |
| margin-top: 20px; | |
| } | |
| input[type="text"], textarea { | |
| background-color: #cbd5e1; | |
| color: var(--text-primary); | |
| border: 3px solid #141111; | |
| } | |
| button { | |
| background-color: #4a4a4a; | |
| color: #ffffff; | |
| border: none; | |
| padding: 8px 16px; | |
| border-radius: 4px; | |
| } | |
| button:hover { | |
| background-color: #5a5a5a; | |
| } | |
| .card { | |
| background-color: var(--card-background); | |
| border: 1px solid var(--border-color); | |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); | |
| color: var(--text-primary); | |
| } | |
| .card-header { | |
| background-color: var(--sidebar-background); | |
| color: white; | |
| border-bottom: none; | |
| font-weight: 600; | |
| } | |
| .card-body { | |
| background-color: var(--card-background); | |
| color: var(--text-primary); | |
| } | |
| /* Update input styles */ | |
| input[type="number"] { | |
| background-color: var(--input-background); | |
| color: var(--text-primary); | |
| border-color: #475569; | |
| } | |
| input[type="number"]:focus { | |
| background-color: var(--input-background); | |
| color: var(--text-primary); | |
| border-color: var(--primary-color); | |
| box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2); | |
| } | |
| /* Update button styles to match theme */ | |
| button { | |
| background-color: var(--primary-color); | |
| color: var(--text-primary); | |
| border: none; | |
| padding: 8px 16px; | |
| border-radius: var(--border-radius); | |
| transition: all 0.3s ease; | |
| } | |
| button:hover { | |
| background-color: var(--hover-color); | |
| transform: translateY(-1px); | |
| } | |
| /* Update input group styling */ | |
| .input-group-text { | |
| background-color: var(--input-background); | |
| color: var(--text-secondary); | |
| border: 1px solid var(--border-color); | |
| } | |
| /* Update verification result styling */ | |
| .uploaded-image { | |
| background-color: var(--card-background); | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--border-radius); | |
| padding: 1rem; | |
| margin-bottom: 1rem; | |
| } | |
| .img-thumbnail { | |
| background-color: var(--input-background); | |
| border-color: #475569; | |
| } | |
| /* Update form label color */ | |
| .form-label { | |
| color: var(--text-primary); | |
| font-weight: 500; | |
| } | |
| /* Add styles for grade colors with better visibility */ | |
| .grade-a { color: #34d399; } /* Light Green */ | |
| .grade-b { color: #60a5fa; } /* Light Blue */ | |
| .grade-c { color: #fbbf24; } /* Light Yellow */ | |
| .grade-d { color: #fb923c; } /* Light Orange */ | |
| .grade-f { color: #f87171; } /* Light Red */ | |
| /* Update table borders */ | |
| .table-bordered > :not(caption) > * { | |
| border-color: #475569; | |
| } | |
| .table-hover tbody tr:hover { | |
| background-color: var(--input-background); | |
| } | |
| /* Add these styles for the facility cards */ | |
| .facility-type-cards { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | |
| gap: 1.5rem; | |
| margin-bottom: 2rem; | |
| } | |
| .facility-type-card { | |
| background-color: var(--card-background); | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--border-radius); | |
| padding: 1.5rem; | |
| transition: all 0.3s ease; | |
| } | |
| .facility-type-card:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| } | |
| .facility-type-card h3 { | |
| color: var(--text-primary); | |
| margin-bottom: 1rem; | |
| font-size: 1.25rem; | |
| } | |
| .facility-type-card .icon { | |
| font-size: 2rem; | |
| margin-bottom: 1rem; | |
| color: var(--primary-color); | |
| } | |
| .model-info { | |
| color: var(--text-secondary); | |
| font-size: 0.875rem; | |
| margin-bottom: 1rem; | |
| padding: 0.5rem; | |
| background-color: var(--input-background); | |
| border-radius: var(--border-radius); | |
| text-align: center; | |
| } | |
| /* Add/update these styles */ | |
| .upload-grid { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1.5rem; | |
| width: 100%; | |
| max-width: 800px; | |
| margin: 0 auto; | |
| } | |
| .upload-card { | |
| background-color: var(--card-background); | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--border-radius); | |
| padding: 1.5rem; | |
| width: 100%; | |
| } | |
| .results-grid { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1.5rem; | |
| width: 100%; | |
| } | |
| .result-card { | |
| background-color: var(--card-background); | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--border-radius); | |
| padding: 1.5rem; | |
| width: 100%; | |
| } | |
| .detections-list { | |
| margin-top: 1rem; | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--border-radius); | |
| overflow: hidden; | |
| } | |
| .detection-item { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 0.75rem 1rem; | |
| border-bottom: 1px solid var(--border-color); | |
| } | |
| .detection-item:last-child { | |
| border-bottom: none; | |
| } | |
| /* Add these styles for improved object detection UI */ | |
| .detection-section { | |
| background-color: var(--card-background); | |
| border-radius: var(--border-radius); | |
| padding: 2rem; | |
| margin-bottom: 2rem; | |
| } | |
| .detection-header { | |
| text-align: center; | |
| margin-bottom: 2rem; | |
| color: var(--text-primary); | |
| } | |
| .detection-header h2 { | |
| font-size: 2rem; | |
| margin-bottom: 1rem; | |
| } | |
| .upload-grid { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 2rem; | |
| } | |
| .upload-card { | |
| background-color: var(--sidebar-background); | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--border-radius); | |
| padding: 2rem; | |
| transition: all 0.3s ease; | |
| } | |
| .upload-card:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | |
| } | |
| .upload-card h3 { | |
| color: var(--text-primary); | |
| font-size: 1.5rem; | |
| margin-bottom: 1rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .upload-card h3 i { | |
| color: var(--primary-color); | |
| } | |
| .custom-file-upload { | |
| background-color: var(--input-background); | |
| border: 2px dashed var(--border-color); | |
| border-radius: var(--border-radius); | |
| padding: 2rem; | |
| text-align: center; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| display: block; | |
| margin-bottom: 1rem; | |
| } | |
| .custom-file-upload:hover { | |
| border-color: var(--primary-color); | |
| background-color: rgba(99, 102, 241, 0.1); | |
| } | |
| .custom-file-upload i { | |
| font-size: 2rem; | |
| color: var(--primary-color); | |
| margin-bottom: 1rem; | |
| display: block; | |
| } | |
| .detection-results { | |
| background-color: var(--card-background); | |
| border-radius: var(--border-radius); | |
| padding: 1.5rem; | |
| margin-top: 2rem; | |
| } | |
| .detection-results h4 { | |
| color: var(--text-primary); | |
| margin-bottom: 1rem; | |
| padding-bottom: 0.5rem; | |
| border-bottom: 1px solid var(--border-color); | |
| } | |
| .table { | |
| margin-bottom: 0; | |
| } | |
| .table thead th { | |
| background-color: var(--input-background); | |
| color: var(--text-primary); | |
| font-weight: 600; | |
| padding: 1rem; | |
| } | |
| .table tbody td { | |
| padding: 1rem; | |
| color: var(--text-secondary); | |
| } | |
| .confidence-badge { | |
| background-color: var(--primary-color); | |
| color: white; | |
| padding: 0.25rem 0.75rem; | |
| border-radius: 9999px; | |
| font-size: 0.875rem; | |
| } | |
| #detect-all { | |
| background-color: var(--primary-color); | |
| color: white; | |
| padding: 1rem 2rem; | |
| font-size: 1.1rem; | |
| font-weight: 600; | |
| margin-top: 2rem; | |
| transition: all 0.3s ease; | |
| } | |
| #detect-all:hover { | |
| background-color: var(--hover-color); | |
| transform: translateY(-2px); | |
| } | |
| .loading { | |
| text-align: center; | |
| padding: 2rem; | |
| } | |
| .loading-spinner { | |
| margin: 0 auto 1rem; | |
| } | |
| .stats-panel { | |
| background-color: var(--sidebar-background); | |
| border-radius: var(--border-radius); | |
| padding: 2rem; | |
| margin-top: 2rem; | |
| } | |
| .stats-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
| gap: 1.5rem; | |
| } | |
| .stats-item { | |
| text-align: center; | |
| padding: 1.5rem; | |
| background-color: var(--card-background); | |
| border-radius: var(--border-radius); | |
| } | |
| .stats-value { | |
| font-size: 2rem; | |
| font-weight: 700; | |
| color: var(--primary-color); | |
| display: block; | |
| margin-top: 0.5rem; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container mt-5"> | |
| <h1 class="text-center mb-4">Facility Management System</h1> | |
| <div class="row"> | |
| <!-- Sidebar --> | |
| <div class="col-md-4 sidebar"> | |
| <h4>Input Form</h4> | |
| <form id="facility-form"> | |
| <div class="mb-3"> | |
| <label for="num_students" class="form-label">Number of Students:</label> | |
| <input type="number" id="num_students" class="form-control" required> | |
| </div> | |
| <div class="mb-3"> | |
| <label for="num_divisions" class="form-label">Number of Divisions:</label> | |
| <input type="number" id="num_divisions" class="form-control" required> | |
| </div> | |
| <div class="mb-3"> | |
| <label for="num_courses" class="form-label">Number of Courses:</label> | |
| <input type="number" id="num_courses" class="form-control" required> | |
| </div> | |
| <div class="mb-3"> | |
| <label for="course_duration" class="form-label">Course Duration (Years):</label> | |
| <input type="number" id="course_duration" class="form-control" required> | |
| </div> | |
| <button type="submit" class="btn btn-primary w-100">Calculate</button> | |
| </form> | |
| </div> | |
| <!-- Facilities Section --> | |
| <div class="col-md-8"> | |
| <div id="facilities-section"> | |
| {% for facility in facilities %} | |
| <div class="card"> | |
| <div class="card-header">{{ facility }}</div> | |
| <div class="card-body"> | |
| <!-- Facility Requirements --> | |
| <div class="card-body"> | |
| <!-- Facility Requirements --> | |
| <h5>Requirements:</h5> | |
| <div class="input-group mb-3"> | |
| <input | |
| type="text" | |
| id="requirement-{{ facility | replace(' ', '_') | lower }}" | |
| class="form-control text-center" | |
| value="0" | |
| readonly> | |
| <span class="input-group-text">/</span> | |
| <input | |
| type="number" | |
| id="actual-{{ facility | replace(' ', '_') | lower }}" | |
| class="form-control text-center" | |
| placeholder="Enter Actual Count"> | |
| </div> | |
| </div> | |
| <!-- Upload Images --> | |
| <h5 class="mt-4">Upload Images:</h5> | |
| <form enctype="multipart/form-data"> | |
| <div class="input-group"> | |
| <input type="file" class="form-control" id="files-{{ facility | replace(' ', '_') | lower }}" name="images" multiple> | |
| <button type="button" class="btn btn-success upload-btn" data-facility="{{ facility | replace(' ', '_') | lower }}">Verify Images</button> | |
| </div> | |
| <div id="verification-result-{{ facility | replace(' ', '_') | lower }}" class="mt-3"> | |
| <!-- Uploaded images and verification results will be displayed here --> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| {% endfor %} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1>Object Detection</h1> | |
| </div> | |
| <div class="toast" id="toast"></div> | |
| <div class="debug-panel" id="debug-panel"> | |
| <div class="debug-content"></div> | |
| </div> | |
| <div class="main-content"> | |
| <div class="detection-section"> | |
| <div class="detection-header"> | |
| <h2>Object Detection</h2> | |
| <p>Upload images for each facility to detect and count objects</p> | |
| </div> | |
| <form id="upload-form"> | |
| <div class="upload-grid"> | |
| <!-- Classroom Section --> | |
| <div class="upload-card"> | |
| <h3><i class="fas fa-chalkboard"></i> Classroom</h3> | |
| <label class="custom-file-upload"> | |
| <input type="file" id="image-classroom" name="image_classroom" accept="image/*" style="display: none;"> | |
| <i class="fas fa-cloud-upload-alt"></i> | |
| <span>Drop classroom image here or click to upload</span> | |
| </label> | |
| <p id="file-name-classroom" class="file-name"></p> | |
| <div class="detection-results"> | |
| <h4>Detected Objects</h4> | |
| <table class="table"> | |
| <thead> | |
| <tr> | |
| <th>Object</th> | |
| <th>Confidence</th> | |
| <th>Count</th> | |
| </tr> | |
| </thead> | |
| <tbody id="classroom-detections"></tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <!-- Chemical Lab Section --> | |
| <div class="upload-card"> | |
| <h3><i class="fas fa-flask"></i> Chemical Lab</h3> | |
| <label class="custom-file-upload"> | |
| <input type="file" id="image-chemical-lab" name="image_chemical_lab" accept="image/*" style="display: none;"> | |
| <i class="fas fa-cloud-upload-alt"></i> | |
| <span>Drop lab image here or click to upload</span> | |
| </label> | |
| <p id="file-name-chemical-lab" class="file-name"></p> | |
| <div class="detection-results"> | |
| <h4>Detected Objects</h4> | |
| <table class="table"> | |
| <thead> | |
| <tr> | |
| <th>Object</th> | |
| <th>Confidence</th> | |
| <th>Count</th> | |
| </tr> | |
| </thead> | |
| <tbody id="chemical-lab-detections"></tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <!-- Mechanical Workshop Section --> | |
| <div class="upload-card"> | |
| <h3><i class="fas fa-tools"></i> Mechanical Workshop</h3> | |
| <label class="custom-file-upload"> | |
| <input type="file" id="image-mechanical-workshop" name="image_mechanical_workshop" accept="image/*" style="display: none;"> | |
| <i class="fas fa-cloud-upload-alt"></i> | |
| <span>Drop workshop image here or click to upload</span> | |
| </label> | |
| <p id="file-name-mechanical-workshop" class="file-name"></p> | |
| <div class="detection-results"> | |
| <h4>Detected Objects</h4> | |
| <table class="table"> | |
| <thead> | |
| <tr> | |
| <th>Object</th> | |
| <th>Confidence</th> | |
| <th>Count</th> | |
| </tr> | |
| </thead> | |
| <tbody id="mechanical-workshop-detections"></tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <!-- Computer Lab Section --> | |
| <div class="upload-card"> | |
| <h3><i class="fas fa-desktop"></i> Computer Lab</h3> | |
| <label class="custom-file-upload"> | |
| <input type="file" id="image-computer-lab" name="image_computer_lab" accept="image/*" style="display: none;"> | |
| <i class="fas fa-cloud-upload-alt"></i> | |
| <span>Drop computer lab image here or click to upload</span> | |
| </label> | |
| <p id="file-name-computer-lab" class="file-name"></p> | |
| <div class="detection-results"> | |
| <h4>Detected Objects</h4> | |
| <table class="table"> | |
| <thead> | |
| <tr> | |
| <th>Object</th> | |
| <th>Confidence</th> | |
| <th>Count</th> | |
| </tr> | |
| </thead> | |
| <tbody id="computer-lab-detections"></tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <!-- CCTV Section --> | |
| <div class="upload-card"> | |
| <h3><i class="fas fa-video"></i> CCTV Detection</h3> | |
| <label class="custom-file-upload"> | |
| <input type="file" id="image-cctv" name="image_cctv" accept="image/*" style="display: none;"> | |
| <i class="fas fa-cloud-upload-alt"></i> | |
| <span>Drop CCTV image here or click to upload</span> | |
| </label> | |
| <p id="file-name-cctv" class="file-name"></p> | |
| <div class="detection-results"> | |
| <h4>Detected Objects</h4> | |
| <table class="table"> | |
| <thead> | |
| <tr> | |
| <th>Object</th> | |
| <th>Confidence</th> | |
| <th>Count</th> | |
| </tr> | |
| </thead> | |
| <tbody id="cctv-detections"></tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <!-- Notice Board Section --> | |
| <div class="upload-card"> | |
| <h3><i class="fas fa-clipboard"></i> Notice Board Detection</h3> | |
| <label class="custom-file-upload"> | |
| <input type="file" id="image-notice-board" name="image_notice_board" accept="image/*" style="display: none;"> | |
| <i class="fas fa-cloud-upload-alt"></i> | |
| <span>Drop notice board image here or click to upload</span> | |
| </label> | |
| <p id="file-name-notice-board" class="file-name"></p> | |
| <div class="detection-results"> | |
| <h4>Detected Objects</h4> | |
| <table class="table"> | |
| <thead> | |
| <tr> | |
| <th>Object</th> | |
| <th>Confidence</th> | |
| <th>Count</th> | |
| </tr> | |
| </thead> | |
| <tbody id="notice-board-detections"></tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <!-- Bench Section --> | |
| <div class="upload-card"> | |
| <h3><i class="fas fa-chair"></i> Bench Detection</h3> | |
| <label class="custom-file-upload"> | |
| <input type="file" id="image-bench" name="image_bench" accept="image/*" style="display: none;"> | |
| <i class="fas fa-cloud-upload-alt"></i> | |
| <span>Drop bench image here or click to upload</span> | |
| </label> | |
| <p id="file-name-bench" class="file-name"></p> | |
| <div class="detection-results"> | |
| <h4>Detected Objects</h4> | |
| <table class="table"> | |
| <thead> | |
| <tr> | |
| <th>Object</th> | |
| <th>Confidence</th> | |
| <th>Count</th> | |
| </tr> | |
| </thead> | |
| <tbody id="bench-detections"></tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| <button type="submit" class="btn btn-primary w-100" id="detect-all"> | |
| <i class="fas fa-search"></i> | |
| Detect Objects in All Images | |
| </button> | |
| </form> | |
| </div> | |
| <div class="error-message" id="error-message"></div> | |
| <div class="loading" id="loading"> | |
| <div class="loading-spinner"></div> | |
| <p>Analyzing image...</p> | |
| </div> | |
| <div class="stats-panel" id="stats-panel" style="display: none;"> | |
| <h3>Analysis Summary</h3> | |
| <div class="stats-grid"> | |
| <div class="stats-item"> | |
| <span>Total Objects Detected</span> | |
| <span id="total-detections" class="stats-value">0</span> | |
| </div> | |
| <div class="stats-item"> | |
| <span>Processing Duration</span> | |
| <span id="processing-time" class="stats-value">0ms</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="image-container"> | |
| <div id="result"> | |
| <img id="result-image" alt="Detection result"> | |
| <div class="detection-count" id="detection-count" style="display: none;"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="sidebar"> | |
| <h2 class="detections-title">Detected Objects</h2> | |
| <div id="detections"></div> | |
| </div> | |
| </div> | |
| <div class="card mt-5"> | |
| <div class="card-header"> | |
| Deficiency Table | |
| </div> | |
| <div class="card-body"> | |
| <table class="table table-bordered table-hover text-center" id="deficiency-table"> | |
| <thead class="table-primary"> | |
| <tr> | |
| <th>Facility Name</th> | |
| <th>Required Count</th> | |
| <th>Actual Count</th> | |
| <th>Number of Images Verified</th> | |
| <th>Status</th> | |
| </tr> | |
| </thead> | |
| <tbody id="deficiency-table-body"> | |
| <!-- Dynamic rows will be added here --> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <div class="grade-display-section text-center mt-4"> | |
| <h3>Your Grade:</h3> | |
| <div id="grade-display" class="grade-display"></div> | |
| </div> | |
| <div class="text-center mt-4"> | |
| <h3>Download Reports</h3> | |
| <button id="download-pdf" class="btn btn-primary">Download PDF Report</button> | |
| <button id="download-excel" class="btn btn-success">Download Excel Report</button> | |
| </div> | |
| <script> | |
| $('#facility-form').on('submit', function (e) { | |
| e.preventDefault(); | |
| const data = { | |
| num_students: $('#num_students').val(), | |
| num_divisions: $('#num_divisions').val(), | |
| num_courses: $('#num_courses').val(), | |
| course_duration: $('#course_duration').val(), | |
| }; | |
| $.ajax({ | |
| url: '/calculate', | |
| type: 'POST', | |
| contentType: 'application/json', | |
| data: JSON.stringify(data), | |
| success: function (response) { | |
| for (const [facility, value] of Object.entries(response)) { | |
| const facilityId = facility.replace(/ /g, '_').toLowerCase(); | |
| $(`#requirement-${facilityId}`).val(value); | |
| // Optionally, clear or prefill the "Actual Count" input box | |
| $(`#actual-${facilityId}`).val(''); | |
| } | |
| }, | |
| error: function () { | |
| alert('Failed to calculate facilities. Please try again.'); | |
| } | |
| }); | |
| }); | |
| // Capture "Actual Count" values for further processing | |
| function getActualCounts() { | |
| const actualCounts = {}; | |
| $('.card').each(function () { | |
| const facility = $(this).find('.card-header').text().trim(); | |
| const facilityId = facility.replace(/ /g, '_').toLowerCase(); | |
| const actualCount = $(`#actual-${facilityId}`).val(); | |
| actualCounts[facility] = actualCount ? parseInt(actualCount, 10) : 0; | |
| }); | |
| console.log(actualCounts); | |
| // Perform additional processing with `actualCounts` as needed | |
| } | |
| // Handle Multiple File Upload | |
| $('.upload-btn').on('click', function () { | |
| const facility = $(this).data('facility'); | |
| const fileInput = $(`#files-${facility}`)[0]; | |
| const files = fileInput.files; | |
| if (files.length === 0) { | |
| alert('Please select at least one image.'); | |
| return; | |
| } | |
| const formData = new FormData(); | |
| Array.from(files).forEach((file) => { | |
| formData.append('images', file); | |
| }); | |
| $.ajax({ | |
| url: `/upload/${facility.replace(/_/g, ' ')}`, | |
| type: 'POST', | |
| processData: false, | |
| contentType: false, | |
| data: formData, | |
| success: function (response) { | |
| let resultsHtml = ''; | |
| response.forEach((result, index) => { | |
| const fileReader = new FileReader(); | |
| // Render each image | |
| fileReader.onload = function (e) { | |
| resultsHtml += ` | |
| <div class="uploaded-image"> | |
| <img src="${e.target.result}" alt="Uploaded Image" class="img-thumbnail" style="max-width: 200px;"> | |
| <p class="text-center mt-2"> | |
| <strong>Label:</strong> ${result.label}<br> | |
| <strong>Confidence:</strong> ${result.confidence.toFixed(2)} | |
| </p> | |
| <p class="text-center ${result.confidence >= 0.8 ? 'text-success' : 'text-danger'}"> | |
| ${result.confidence >= 0.8 ? 'Verified' : 'Not Verified'} | |
| </p> | |
| </div> | |
| `; | |
| // Update the results section for the facility after all images are processed | |
| if (index === response.length - 1) { | |
| $(`#verification-result-${facility}`).html(resultsHtml); | |
| } | |
| }; | |
| // Read the file to get its data URL | |
| fileReader.readAsDataURL(files[index]); | |
| }); | |
| }, | |
| error: function () { | |
| $(`#verification-result-${facility}`).html(` | |
| <div class="alert alert-danger">Failed to verify images.</div> | |
| `); | |
| } | |
| }); | |
| }); | |
| // Handle Answer Submission | |
| $('.submit-answers').on('click', function () { | |
| const facility = $(this).data('facility'); | |
| const answers = []; | |
| $(this).closest('.question-form').find('.answer-box').each(function () { | |
| answers.push({ | |
| question: $(this).data('question'), | |
| answer: $(this).val() | |
| }); | |
| }); | |
| $.ajax({ | |
| url: '/submit_answers', | |
| type: 'POST', | |
| contentType: 'application/json', | |
| data: JSON.stringify({ facility: facility, answers: answers }), | |
| success: function (response) { | |
| alert(response.message); | |
| }, | |
| error: function () { | |
| alert('Failed to submit answers.'); | |
| } | |
| }); | |
| }); | |
| const loadingDiv = document.getElementById('loading'); | |
| const errorMessage = document.getElementById('error-message'); | |
| const form = document.getElementById('upload-form'); | |
| const detectButton = form.querySelector('button[type="submit"]'); | |
| const imageInputs = { | |
| classroom: document.getElementById('image-classroom'), | |
| chemical_lab: document.getElementById('image-chemical-lab'), | |
| mechanical_workshop: document.getElementById('image-mechanical-workshop'), | |
| computer_lab: document.getElementById('image-computer-lab'), | |
| cctv: document.getElementById('image-cctv'), | |
| notice_board: document.getElementById('image-notice-board'), | |
| bench: document.getElementById('image-bench') | |
| }; | |
| // Handle file selection for each input | |
| Object.entries(imageInputs).forEach(([key, input]) => { | |
| input.addEventListener('change', (e) => { | |
| const file = e.target.files[0]; | |
| const fileNameElement = document.getElementById(`file-name-${key.replace('_', '-')}`); | |
| if (file) { | |
| fileNameElement.textContent = `Selected: ${file.name}`; | |
| updateDetectButton(); | |
| } else { | |
| fileNameElement.textContent = ''; | |
| } | |
| // Clear previous results when new file is selected | |
| clearResults(); | |
| }); | |
| }); | |
| function clearResults() { | |
| const resultContainer = document.getElementById('result'); | |
| resultContainer.innerHTML = '<img id="result-image" alt="Detection result" style="display: none;">'; | |
| document.getElementById('stats-panel').style.display = 'none'; | |
| errorMessage.style.display = 'none'; | |
| } | |
| function updateDetectButton() { | |
| const allFilesSelected = Object.values(imageInputs).every(input => input.files.length > 0); | |
| detectButton.disabled = !allFilesSelected; | |
| } | |
| form.addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| const formData = new FormData(); | |
| let allFilesValid = true; | |
| // Clear previous results | |
| clearResults(); | |
| Object.entries(imageInputs).forEach(([key, input]) => { | |
| const file = input.files[0]; | |
| if (!file) { | |
| showToast(`Please select an image for ${key.replace('_', ' ')}`, 'error'); | |
| allFilesValid = false; | |
| return; | |
| } | |
| formData.append(`image_${key}`, file); | |
| }); | |
| if (!allFilesValid) return; | |
| const startTime = performance.now(); | |
| loadingDiv.style.display = 'block'; | |
| detectButton.disabled = true; | |
| errorMessage.style.display = 'none'; | |
| try { | |
| const response = await fetch('/detect', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| const data = await response.json(); | |
| if (data.success) { | |
| const processingTime = Math.round(performance.now() - startTime); | |
| displayResults(data.results, processingTime); | |
| } else { | |
| errorMessage.textContent = data.error || 'Error processing images'; | |
| errorMessage.style.display = 'block'; | |
| showToast(data.error || 'Error processing images', 'error'); | |
| } | |
| } catch (error) { | |
| errorMessage.textContent = 'Network error or server not responding'; | |
| errorMessage.style.display = 'block'; | |
| showToast('Network error or server not responding', 'error'); | |
| console.error('Error:', error); | |
| } finally { | |
| loadingDiv.style.display = 'none'; | |
| detectButton.disabled = false; | |
| } | |
| }); | |
| function displayResults(results, processingTime) { | |
| const resultContainer = document.getElementById('result'); | |
| resultContainer.innerHTML = '<div class="results-grid"></div>'; | |
| const grid = resultContainer.querySelector('.results-grid'); | |
| let totalDetections = 0; | |
| Object.entries(results).forEach(([environment, data]) => { | |
| const card = document.createElement('div'); | |
| card.className = 'result-card'; | |
| // Format the environment name for display | |
| const envName = environment.split('_') | |
| .map(word => word.charAt(0).toUpperCase() + word.slice(1)) | |
| .join(' '); | |
| card.innerHTML = ` | |
| <h3>${envName}</h3> | |
| <img src="${data.image}" alt="${environment}" style="width: 100%"> | |
| <div class="detections-count">${data.detections.length} objects detected</div> | |
| <div class="detections-list"> | |
| ${data.detections.map(det => ` | |
| <div class="detection-item"> | |
| <span>${det.class}</span> | |
| <span class="confidence-badge">${(det.confidence * 100).toFixed(1)}%</span> | |
| </div> | |
| `).join('')} | |
| </div> | |
| `; | |
| grid.appendChild(card); | |
| totalDetections += data.detections.length; | |
| }); | |
| // Update stats panel | |
| document.getElementById('total-detections').textContent = totalDetections; | |
| document.getElementById('processing-time').textContent = `${processingTime}ms`; | |
| document.getElementById('stats-panel').style.display = 'block'; | |
| showToast(`Processing completed in ${processingTime}ms`); | |
| } | |
| function showToast(message, type = 'success') { | |
| const toast = document.getElementById('toast'); | |
| toast.textContent = message; | |
| toast.className = `toast ${type}`; | |
| toast.style.display = 'block'; | |
| setTimeout(() => { | |
| toast.style.display = 'none'; | |
| }, 3000); | |
| } | |
| function debugLog(message) { | |
| const debugPanel = document.getElementById('debug-panel'); | |
| const content = debugPanel.querySelector('.debug-content'); | |
| const timestamp = new Date().toLocaleTimeString(); | |
| content.innerHTML += `[${timestamp}] ${message}<br>`; | |
| content.scrollTop = content.scrollHeight; | |
| } | |
| // Handle window resize | |
| let resizeTimeout; | |
| window.addEventListener('resize', () => { | |
| clearTimeout(resizeTimeout); | |
| resizeTimeout = setTimeout(() => { | |
| const highlightBoxes = document.querySelectorAll('.highlight-box'); | |
| const labels = document.querySelectorAll('.detection-label'); | |
| highlightBoxes.forEach(box => box.style.display = 'none'); | |
| labels.forEach(label => label.style.display = 'none'); | |
| }, 250); | |
| }); | |
| // Add debug panel toggle | |
| document.addEventListener('keypress', (e) => { | |
| if (e.key === 'd') { | |
| const debugPanel = document.getElementById('debug-panel'); | |
| debugPanel.classList.toggle('show'); | |
| } | |
| }); | |
| function updateDeficiencyTableAndGrade() { | |
| const tableBody = $('#deficiency-table-body'); | |
| tableBody.empty(); // Clear existing rows | |
| let totalDeficiencies = 0; | |
| let totalVerifiedImages = 0; | |
| const totalFacilities = $('.card').length; | |
| $('.card').each(function () { | |
| const facilityName = $(this).find('.card-header').text().trim(); | |
| const facilityId = facilityName.replace(/ /g, '_').toLowerCase(); | |
| const requiredCount = parseInt($(`#requirement-${facilityId}`).val(), 10) || 0; | |
| const actualCount = parseInt($(`#actual-${facilityId}`).val(), 10) || 0; | |
| // Count verified images | |
| const verifiedImages = $(`#verification-result-${facilityId} .uploaded-image .text-success`).length; | |
| totalVerifiedImages += verifiedImages; | |
| // Determine deficiency | |
| const deficiency = requiredCount > actualCount; | |
| if (deficiency) totalDeficiencies++; | |
| const status = deficiency | |
| ? `<span class="text-danger">✘ Deficiency</span>` | |
| : `<span class="text-success">✔ No Deficiency</span>`; | |
| // Add a new row to the table | |
| tableBody.append(` | |
| <tr> | |
| <td>${facilityName}</td> | |
| <td>${requiredCount}</td> | |
| <td>${actualCount}</td> | |
| <td>${verifiedImages}</td> | |
| <td>${status}</td> | |
| </tr> | |
| `); | |
| }); | |
| // Calculate grade | |
| const grade = calculateGrade(totalDeficiencies, totalVerifiedImages, totalFacilities); | |
| // Update grade display | |
| const gradeDisplay = $('#grade-display'); | |
| gradeDisplay | |
| .removeClass('grade-a grade-b grade-c grade-d grade-f') | |
| .addClass(`grade-${grade.toLowerCase()}`) | |
| .text(grade); | |
| } | |
| function calculateGrade(totalDeficiencies, totalVerifiedImages, totalFacilities) { | |
| const deficiencyRate = totalDeficiencies / totalFacilities; | |
| const verificationBonus = totalVerifiedImages / totalFacilities; | |
| if (deficiencyRate <= 0.1 && verificationBonus >= 0.8) return 'A'; | |
| if (deficiencyRate <= 0.2 && verificationBonus >= 0.6) return 'B'; | |
| if (deficiencyRate <= 0.3 && verificationBonus >= 0.4) return 'C'; | |
| if (deficiencyRate <= 0.4 && verificationBonus >= 0.2) return 'D'; | |
| return 'F'; | |
| } | |
| // Attach event listeners | |
| $('#facility-form').on('submit', function (e) { | |
| e.preventDefault(); | |
| updateDeficiencyTableAndGrade(); | |
| }); | |
| $('.form-control').on('input', function () { | |
| updateDeficiencyTableAndGrade(); | |
| }); | |
| $('.upload-btn').on('click', function () { | |
| setTimeout(() => { | |
| updateDeficiencyTableAndGrade(); | |
| }, 1000); // Allow time for verification results to render | |
| }); | |
| // Handle PDF download | |
| $('#download-pdf').on('click', function () { | |
| window.location.href = '/download_report'; | |
| }); | |
| // Handle Excel download | |
| $('#download-excel').on('click', function () { | |
| window.location.href = '/download_excel'; | |
| }); | |
| // Add FontAwesome for icons | |
| document.head.innerHTML += '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">'; | |
| </script> | |
| </body> | |
| </html> | |