Commit
·
b2d0a56
1
Parent(s):
ca13a3e
fix index.html
Browse files- main.py +74 -3
- templates/index.html +42 -40
main.py
CHANGED
@@ -279,6 +279,44 @@ def compute_answers():
|
|
279 |
error_msg = str(e).encode('ascii', 'ignore').decode('ascii')
|
280 |
return jsonify({"error": error_msg}), 500
|
281 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
282 |
@app.route('/compute_marks', methods=['POST'])
|
283 |
def compute_marks():
|
284 |
try:
|
@@ -307,8 +345,19 @@ def compute_marks():
|
|
307 |
"message": "No files uploaded"
|
308 |
}), 400
|
309 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
310 |
# Create a temporary directory for processing
|
311 |
base_temp_dir = tempfile.mkdtemp()
|
|
|
312 |
|
313 |
# Dictionary to store results by student folder
|
314 |
results = {}
|
@@ -320,31 +369,48 @@ def compute_marks():
|
|
320 |
try:
|
321 |
# Validate file
|
322 |
if not file or not file.filename:
|
|
|
323 |
continue
|
324 |
|
325 |
# Get folder structure from file path
|
326 |
path_parts = file.filename.split('/')
|
327 |
if len(path_parts) < 2:
|
|
|
328 |
continue
|
329 |
|
330 |
student_folder = path_parts[-2]
|
331 |
filename = path_parts[-1]
|
332 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
333 |
if student_folder not in results:
|
334 |
results[student_folder] = [0] * len(correct_answers)
|
|
|
335 |
|
336 |
# Save and process file
|
337 |
student_dir = os.path.join(base_temp_dir, student_folder)
|
338 |
os.makedirs(student_dir, exist_ok=True)
|
339 |
filepath = os.path.join(student_dir, filename)
|
|
|
|
|
|
|
340 |
file.save(filepath)
|
|
|
341 |
|
342 |
# Extract text
|
343 |
extracted_text = extract_text_from_image(filepath)
|
344 |
if not extracted_text:
|
|
|
345 |
failed_files.append({
|
346 |
"file": file.filename,
|
347 |
-
"error": "No text could be extracted"
|
348 |
})
|
349 |
continue
|
350 |
|
@@ -381,6 +447,7 @@ def compute_marks():
|
|
381 |
|
382 |
except Exception as score_error:
|
383 |
error_msg = str(score_error).encode('ascii', 'ignore').decode('ascii')
|
|
|
384 |
failed_files.append({
|
385 |
"file": file.filename,
|
386 |
"error": f"Error calculating scores: {error_msg}"
|
@@ -389,9 +456,11 @@ def compute_marks():
|
|
389 |
|
390 |
marks = new_value(best_score, 0, 1, 0, 5)
|
391 |
results[student_folder][best_answer_index] = round(marks, 2)
|
|
|
392 |
|
393 |
except Exception as e:
|
394 |
error_msg = str(e).encode('ascii', 'ignore').decode('ascii')
|
|
|
395 |
failed_files.append({
|
396 |
"file": file.filename,
|
397 |
"error": error_msg
|
@@ -402,8 +471,9 @@ def compute_marks():
|
|
402 |
# Clean up temp directory
|
403 |
try:
|
404 |
shutil.rmtree(base_temp_dir)
|
405 |
-
|
406 |
-
|
|
|
407 |
|
408 |
if not results:
|
409 |
return jsonify({
|
@@ -429,6 +499,7 @@ def compute_marks():
|
|
429 |
|
430 |
except Exception as e:
|
431 |
error_msg = str(e).encode('ascii', 'ignore').decode('ascii')
|
|
|
432 |
return jsonify({
|
433 |
"error": "Server error",
|
434 |
"message": f"Error computing marks: {error_msg}"
|
|
|
279 |
error_msg = str(e).encode('ascii', 'ignore').decode('ascii')
|
280 |
return jsonify({"error": error_msg}), 500
|
281 |
|
282 |
+
def validate_folder_structure(files):
|
283 |
+
"""Validate the folder structure of uploaded files"""
|
284 |
+
try:
|
285 |
+
# Get unique student folders
|
286 |
+
student_folders = set()
|
287 |
+
for file in files:
|
288 |
+
if not file or not file.filename:
|
289 |
+
continue
|
290 |
+
path_parts = file.filename.split('/')
|
291 |
+
if len(path_parts) >= 2:
|
292 |
+
student_folders.add(path_parts[-2])
|
293 |
+
|
294 |
+
if not student_folders:
|
295 |
+
return False, "No valid student folders found"
|
296 |
+
|
297 |
+
# Check if each student folder has the same number of files
|
298 |
+
file_counts = {}
|
299 |
+
for file in files:
|
300 |
+
if not file or not file.filename:
|
301 |
+
continue
|
302 |
+
path_parts = file.filename.split('/')
|
303 |
+
if len(path_parts) >= 2:
|
304 |
+
student = path_parts[-2]
|
305 |
+
file_counts[student] = file_counts.get(student, 0) + 1
|
306 |
+
|
307 |
+
if not file_counts:
|
308 |
+
return False, "No valid files found in student folders"
|
309 |
+
|
310 |
+
# Check if all students have the same number of files
|
311 |
+
counts = list(file_counts.values())
|
312 |
+
if len(set(counts)) > 1:
|
313 |
+
return False, "Inconsistent number of files across student folders"
|
314 |
+
|
315 |
+
return True, f"Valid folder structure with {len(student_folders)} students and {counts[0]} files each"
|
316 |
+
|
317 |
+
except Exception as e:
|
318 |
+
return False, f"Error validating folder structure: {str(e)}"
|
319 |
+
|
320 |
@app.route('/compute_marks', methods=['POST'])
|
321 |
def compute_marks():
|
322 |
try:
|
|
|
345 |
"message": "No files uploaded"
|
346 |
}), 400
|
347 |
|
348 |
+
# Validate folder structure
|
349 |
+
is_valid, message = validate_folder_structure(files)
|
350 |
+
if not is_valid:
|
351 |
+
return jsonify({
|
352 |
+
"error": "Invalid folder structure",
|
353 |
+
"message": message
|
354 |
+
}), 400
|
355 |
+
|
356 |
+
log_print(f"Folder structure validation: {message}")
|
357 |
+
|
358 |
# Create a temporary directory for processing
|
359 |
base_temp_dir = tempfile.mkdtemp()
|
360 |
+
log_print(f"Created temporary directory: {base_temp_dir}")
|
361 |
|
362 |
# Dictionary to store results by student folder
|
363 |
results = {}
|
|
|
369 |
try:
|
370 |
# Validate file
|
371 |
if not file or not file.filename:
|
372 |
+
log_print("Skipping invalid file", "WARNING")
|
373 |
continue
|
374 |
|
375 |
# Get folder structure from file path
|
376 |
path_parts = file.filename.split('/')
|
377 |
if len(path_parts) < 2:
|
378 |
+
log_print(f"Invalid file path structure: {file.filename}", "WARNING")
|
379 |
continue
|
380 |
|
381 |
student_folder = path_parts[-2]
|
382 |
filename = path_parts[-1]
|
383 |
|
384 |
+
# Validate file type
|
385 |
+
if not is_valid_image_file(filename):
|
386 |
+
log_print(f"Invalid file type: {filename}", "WARNING")
|
387 |
+
failed_files.append({
|
388 |
+
"file": file.filename,
|
389 |
+
"error": "Invalid file type. Only .jpg, .jpeg, and .png files are allowed."
|
390 |
+
})
|
391 |
+
continue
|
392 |
+
|
393 |
if student_folder not in results:
|
394 |
results[student_folder] = [0] * len(correct_answers)
|
395 |
+
log_print(f"Processing student folder: {student_folder}")
|
396 |
|
397 |
# Save and process file
|
398 |
student_dir = os.path.join(base_temp_dir, student_folder)
|
399 |
os.makedirs(student_dir, exist_ok=True)
|
400 |
filepath = os.path.join(student_dir, filename)
|
401 |
+
|
402 |
+
# Ensure filepath is secure
|
403 |
+
filepath = secure_filename(filepath)
|
404 |
file.save(filepath)
|
405 |
+
log_print(f"Saved file: {filepath}")
|
406 |
|
407 |
# Extract text
|
408 |
extracted_text = extract_text_from_image(filepath)
|
409 |
if not extracted_text:
|
410 |
+
log_print(f"No text extracted from: {filepath}", "WARNING")
|
411 |
failed_files.append({
|
412 |
"file": file.filename,
|
413 |
+
"error": "No text could be extracted from the image"
|
414 |
})
|
415 |
continue
|
416 |
|
|
|
447 |
|
448 |
except Exception as score_error:
|
449 |
error_msg = str(score_error).encode('ascii', 'ignore').decode('ascii')
|
450 |
+
log_print(f"Error calculating scores for {filepath}: {error_msg}", "ERROR")
|
451 |
failed_files.append({
|
452 |
"file": file.filename,
|
453 |
"error": f"Error calculating scores: {error_msg}"
|
|
|
456 |
|
457 |
marks = new_value(best_score, 0, 1, 0, 5)
|
458 |
results[student_folder][best_answer_index] = round(marks, 2)
|
459 |
+
log_print(f"Processed {filename} for {student_folder}: {marks} marks")
|
460 |
|
461 |
except Exception as e:
|
462 |
error_msg = str(e).encode('ascii', 'ignore').decode('ascii')
|
463 |
+
log_print(f"Error processing file {file.filename}: {error_msg}", "ERROR")
|
464 |
failed_files.append({
|
465 |
"file": file.filename,
|
466 |
"error": error_msg
|
|
|
471 |
# Clean up temp directory
|
472 |
try:
|
473 |
shutil.rmtree(base_temp_dir)
|
474 |
+
log_print("Cleaned up temporary directory")
|
475 |
+
except Exception as e:
|
476 |
+
log_print(f"Warning: Could not clean up temporary directory: {e}", "WARNING")
|
477 |
|
478 |
if not results:
|
479 |
return jsonify({
|
|
|
499 |
|
500 |
except Exception as e:
|
501 |
error_msg = str(e).encode('ascii', 'ignore').decode('ascii')
|
502 |
+
log_print(f"Error in compute_marks: {error_msg}", "ERROR")
|
503 |
return jsonify({
|
504 |
"error": "Server error",
|
505 |
"message": f"Error computing marks: {error_msg}"
|
templates/index.html
CHANGED
@@ -879,41 +879,49 @@
|
|
879 |
|
880 |
// Clear previous files
|
881 |
selectedFiles.clear();
|
882 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
883 |
|
884 |
// Update the visual tree
|
885 |
const treeContainer = document.getElementById('folder-tree');
|
886 |
treeContainer.innerHTML = '';
|
887 |
|
888 |
-
if (
|
889 |
-
treeContainer.innerHTML = '<div class="no-files-message">No files uploaded yet</div>';
|
890 |
return;
|
891 |
}
|
892 |
|
893 |
renderFolderStructure(folderStructure, treeContainer);
|
894 |
|
895 |
-
// Count total image files
|
896 |
-
let imageCount = 0;
|
897 |
-
for (let file of files) {
|
898 |
-
if (file.type.startsWith('image/')) {
|
899 |
-
imageCount++;
|
900 |
-
const pathParts = file.webkitRelativePath.split('/');
|
901 |
-
selectedFiles.set(file.webkitRelativePath, {
|
902 |
-
file: file,
|
903 |
-
mainFolder: pathParts[0],
|
904 |
-
studentFolder: pathParts[1],
|
905 |
-
fileName: pathParts[pathParts.length - 1],
|
906 |
-
fullPath: file.webkitRelativePath
|
907 |
-
});
|
908 |
-
}
|
909 |
-
}
|
910 |
-
|
911 |
// Show immediate feedback
|
912 |
-
|
913 |
-
alert(`Successfully loaded ${imageCount} image files`);
|
914 |
-
} else {
|
915 |
-
alert('No valid image files found in the selected folder');
|
916 |
-
}
|
917 |
});
|
918 |
|
919 |
function removeFile(path) {
|
@@ -974,11 +982,13 @@
|
|
974 |
formData.append('correct_answers[]', answer);
|
975 |
});
|
976 |
|
977 |
-
// Add files with their
|
978 |
let validFiles = 0;
|
979 |
selectedFiles.forEach((fileInfo, path) => {
|
980 |
if (fileInfo.file.type.startsWith('image/')) {
|
981 |
-
|
|
|
|
|
982 |
validFiles++;
|
983 |
}
|
984 |
});
|
@@ -996,23 +1006,15 @@
|
|
996 |
body: formData
|
997 |
});
|
998 |
|
999 |
-
|
1000 |
-
|
1001 |
-
|
1002 |
-
|
1003 |
-
const contentType = response.headers.get('content-type');
|
1004 |
-
if (!contentType || !contentType.includes('application/json')) {
|
1005 |
-
console.error('Non-JSON response:', await response.text());
|
1006 |
-
throw new Error('Server did not return JSON');
|
1007 |
}
|
1008 |
|
1009 |
const result = await response.json();
|
1010 |
console.log('Parsed response:', result);
|
1011 |
|
1012 |
-
if (!response.ok) {
|
1013 |
-
throw new Error(result.message || result.error || `Server returned error ${response.status}`);
|
1014 |
-
}
|
1015 |
-
|
1016 |
if (result.error) {
|
1017 |
throw new Error(result.message || result.error);
|
1018 |
}
|
@@ -1055,7 +1057,7 @@
|
|
1055 |
<thead>
|
1056 |
<tr>
|
1057 |
<th>Student Folder</th>
|
1058 |
-
<th>
|
1059 |
<th>Marks</th>
|
1060 |
</tr>
|
1061 |
</thead>
|
@@ -1073,7 +1075,7 @@
|
|
1073 |
const row = document.createElement('tr');
|
1074 |
row.innerHTML = `
|
1075 |
<td>${student}</td>
|
1076 |
-
<td>
|
1077 |
<td>${mark}</td>
|
1078 |
`;
|
1079 |
tbody.appendChild(row);
|
|
|
879 |
|
880 |
// Clear previous files
|
881 |
selectedFiles.clear();
|
882 |
+
|
883 |
+
// Validate file types
|
884 |
+
const validImageTypes = ['image/jpeg', 'image/jpg', 'image/png'];
|
885 |
+
let invalidFiles = [];
|
886 |
+
|
887 |
+
for (let file of files) {
|
888 |
+
if (file.type.startsWith('image/')) {
|
889 |
+
if (!validImageTypes.includes(file.type)) {
|
890 |
+
invalidFiles.push(file.name);
|
891 |
+
} else {
|
892 |
+
const pathParts = file.webkitRelativePath.split('/');
|
893 |
+
// Store the full relative path
|
894 |
+
const fullPath = file.webkitRelativePath;
|
895 |
+
selectedFiles.set(fullPath, {
|
896 |
+
file: file,
|
897 |
+
mainFolder: pathParts[0],
|
898 |
+
studentFolder: pathParts[1],
|
899 |
+
fileName: pathParts[pathParts.length - 1],
|
900 |
+
fullPath: fullPath
|
901 |
+
});
|
902 |
+
}
|
903 |
+
}
|
904 |
+
}
|
905 |
+
|
906 |
+
if (invalidFiles.length > 0) {
|
907 |
+
alert(`Warning: The following files are not valid image files (only .jpg, .jpeg, and .png are allowed):\n${invalidFiles.join('\n')}`);
|
908 |
+
}
|
909 |
+
|
910 |
+
folderStructure = createFolderStructure(Array.from(selectedFiles.values()).map(info => info.file));
|
911 |
|
912 |
// Update the visual tree
|
913 |
const treeContainer = document.getElementById('folder-tree');
|
914 |
treeContainer.innerHTML = '';
|
915 |
|
916 |
+
if (selectedFiles.size === 0) {
|
917 |
+
treeContainer.innerHTML = '<div class="no-files-message">No valid image files uploaded yet</div>';
|
918 |
return;
|
919 |
}
|
920 |
|
921 |
renderFolderStructure(folderStructure, treeContainer);
|
922 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
923 |
// Show immediate feedback
|
924 |
+
alert(`Successfully loaded ${selectedFiles.size} valid image files`);
|
|
|
|
|
|
|
|
|
925 |
});
|
926 |
|
927 |
function removeFile(path) {
|
|
|
982 |
formData.append('correct_answers[]', answer);
|
983 |
});
|
984 |
|
985 |
+
// Add files with their folder structure
|
986 |
let validFiles = 0;
|
987 |
selectedFiles.forEach((fileInfo, path) => {
|
988 |
if (fileInfo.file.type.startsWith('image/')) {
|
989 |
+
// Maintain the folder structure in the file path
|
990 |
+
const relativePath = fileInfo.fullPath;
|
991 |
+
formData.append('file', fileInfo.file, relativePath);
|
992 |
validFiles++;
|
993 |
}
|
994 |
});
|
|
|
1006 |
body: formData
|
1007 |
});
|
1008 |
|
1009 |
+
if (!response.ok) {
|
1010 |
+
const errorText = await response.text();
|
1011 |
+
console.error('Server error:', errorText);
|
1012 |
+
throw new Error(`Server returned error ${response.status}: ${errorText}`);
|
|
|
|
|
|
|
|
|
1013 |
}
|
1014 |
|
1015 |
const result = await response.json();
|
1016 |
console.log('Parsed response:', result);
|
1017 |
|
|
|
|
|
|
|
|
|
1018 |
if (result.error) {
|
1019 |
throw new Error(result.message || result.error);
|
1020 |
}
|
|
|
1057 |
<thead>
|
1058 |
<tr>
|
1059 |
<th>Student Folder</th>
|
1060 |
+
<th>Image Name</th>
|
1061 |
<th>Marks</th>
|
1062 |
</tr>
|
1063 |
</thead>
|
|
|
1075 |
const row = document.createElement('tr');
|
1076 |
row.innerHTML = `
|
1077 |
<td>${student}</td>
|
1078 |
+
<td>answer${index + 1}.jpg</td>
|
1079 |
<td>${mark}</td>
|
1080 |
`;
|
1081 |
tbody.appendChild(row);
|