Skip to content

Commit

Permalink
[Chore] Merge development into master (#449)
Browse files Browse the repository at this point in the history
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Francisco Perella-Holfeld <68356602+fperellaholfeld@users.noreply.github.com>
Co-authored-by: fperellaholfeld <fperellaholfeld@gmail.com>
Co-authored-by: Paula <paula.wong-chung@alumni.ubc.ca>
Co-authored-by: Ishika Agarwal <105883848+ishikaubc@users.noreply.github.com>
Co-authored-by: Bennett Chang <bv2cq_b@outlook.com>
Co-authored-by: BennettChang <108947000+BennettChang@users.noreply.github.com>
Co-authored-by: Ishika Agarwal <ishikaagarwal@Ishikas-MBP.lan>
Co-authored-by: Ishika Agarwal <ishikaagarwal@Ishikas-MacBook-Pro.local>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
  • Loading branch information
10 people authored Aug 9, 2024
1 parent 250db81 commit 5bfea5e
Show file tree
Hide file tree
Showing 13 changed files with 152 additions and 84 deletions.
2 changes: 1 addition & 1 deletion backend/src/modules/course/course.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -875,7 +875,7 @@ describe('CourseService', () => {

expect(result).toBeDefined();
expect(result).toMatchSnapshot();
});
}, 10_000);

it('should return 0 courseMembersSize, examCount, and submissionCount if course has no submissions', async () => {
const course = await CourseModel.create({
Expand Down
28 changes: 19 additions & 9 deletions backend/src/modules/exam/exam.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,17 @@ export class ExamService {
public async getSubmissionsByExamId(
examId: number,
): Promise<SubmissionModel[]> {
const queryBuilder = SubmissionModel.createQueryBuilder('submission')
.leftJoin('submission.exam', 'exam')
.leftJoinAndSelect('submission.student', 'student')
.leftJoinAndSelect('student.user', 'user')
.where('exam.id = :examId', { examId })
.orderBy('submission.score', 'DESC');

const submissions = await queryBuilder.getMany();
const submissions = await SubmissionModel.find({
where: {
exam: {
id: examId,
},
},
relations: ['student', 'student.user', 'exam'],
order: {
score: 'DESC',
},
});

// Modify the submissions to only include the necessary fields
const modifiedSubmissions: SubmissionModel[] = submissions.map(
Expand Down Expand Up @@ -844,12 +847,20 @@ export class ExamService {
courseId: number,
submissionId: number,
): Promise<UserSubmissionExamInterface> {
const submission = await SubmissionModel.findOne({
where: {
id: submissionId,
},
relations: ['exam'],
});

const exam = await ExamModel.findOne({
where: {
course: {
id: courseId,
is_archived: false,
},
id: submission?.exam?.id,
},
order: {
submissions: {
Expand Down Expand Up @@ -893,7 +904,6 @@ export class ExamService {
),
answers: currentSubmission[0].answers,
};

return modifiedResponse;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import ExamOverviewPopover from "@/app/(users)/components/examOverviewPopover";
const InstructorSubmissionPage = async ({
params,
}: {
params: { courseId: string; submissionId: string; examId: number };
params: { courseId: string; examId: number; submissionId: string };
}) => {
const cid = Number(params.courseId);
const submissionId = Number(params.submissionId);
Expand All @@ -42,6 +42,7 @@ const InstructorSubmissionPage = async ({
submissionId,
);
const submission: StudentSubmission = submissionResponse.data;

if (!submissionResponse || !submission) {
throw new Error("Submission does not exist");
}
Expand Down
42 changes: 26 additions & 16 deletions frontend/src/app/(users)/instructor/faq/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,54 @@ import FAQComponent from "../../components/Accordion";

const generalfaqItems = [
{
question: "How to create an exam for students?",
question: "What is the process for creating an exam?",
answer:
"Select a course and then click on the Create Exam button. Enter the exam credentials and publish the exam",
"First, select a course and click the 'Create Exam' button. Next, enter the exam information and publish the exam.",
},
{
question: "How to edit the course information?",
question: "How do I update course details?",
answer:
"Select course settings button on the course page and edit the course information as required",
"On the course page, click the 'Course Settings' button and make the necessary changes to the course information.",
},
{
question: "How to release grades for students?",
question: "How do I release student grades?",
answer:
"Select the analytics button on the course page and click on the release grades button.",
"Click on the action button associated with an exam. This will bring you to a page with the option to release/hide grades.",
},
];
const coursefaqItems = [
{
question: "How to view courses I am enrolled in?",
question: "How do I view the courses I am enrolled in?",
answer:
"Instructors will need to sign in with their credentials to view the list of courses on the Course Dashboard page.",
"Once signed in, the courses will be visible on the dashboard page.",
},
{
question: "How to edit the course information?",
question: "How do I update course details?",
answer:
"Select course settings button on the course page and edit the course information as required",
"On the course page, click the 'Course Settings' button and make the necessary changes to the course information.",
},
];
const examfaqItems = [
{
question: "How to create an exam for students?",
question: "What is the process for creating an exam?",
answer:
"Select a course and then click on the Create Exam button. Enter the exam credentials and publish the exam",
"First, select a course and click the 'Create Exam' button. Next, enter the exam information and publish the exam.",
},
{
question:
"Is it mandatory to create a custom bubble sheet when creating an exam?",
answer:
"No, this step is completely optional.",
answer: "No, this step is completely optional.",
},
{
question:
"Is it mandatory to create a custom bubble sheet when creating an exam?",
answer: "No, this step is completely optional.",
},

{
question:
"How many questions am I allowed to add when setting up a custom bubble sheet exam?",
answer: "You may include between 0 and 200 questions, inclusive.",
},

{
Expand All @@ -53,9 +63,9 @@ const examfaqItems = [
},

{
question: "How to release grades for students?",
question: "How do I release student grades?",
answer:
"Select the analytics button on the course page and click on the release grades button.",
"Click on the action button associated with an exam. This will bring you to a page with the option to release/hide grades.",
},
];

Expand Down
20 changes: 10 additions & 10 deletions frontend/src/app/(users)/student/faq/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,39 @@ import FAQComponent from "../../components/Accordion";
const generalfaqItems = [
{
id: 1,
question: "How to sign in to the account?",
question: "How do I sign in to my account?",
answer:
"Students can create an account and login with the credentials or sign in with google. They will be required to provide their student id to verify their account",
"Students can either create an account and login with an email and password combination, or sign in using a Google account.",
},
{
id: 2,
question: "How to view the graded exams?",
question: "How can I view my graded exams?",
answer:
"Select a course from the course dashboard. Click on the view exam button under the Graded Exams tab on the course page.",
"Select a course from the course dashboard. Click on the view exam button under the 'Graded Exams' tab on the course page.",
},
];

const courseFaqItems = [
{
id: 1,
question: "How to join a course?",
question: "How do I join a course?",
answer:
"The Course Invite link will be sent to you by your professor. Click on the invite link and you will be able to access the course",
"Your instructor will provide you with a course invite link. Click on the link to register yourself in the course.",
},
];

const examFaqItems = [
{
id: 1,
question: "How to view upcoming exams in a particular course?",
question: "How can I view upcoming exams for a particular course?",
answer:
"Select a particular course from the dashboard. Click on the upcoming exams on the course page to view the list of exams.",
"Select a course from the dashboard. Click on the upcoming exams tab on the course page to view the list of upcoming exams.",
},
{
id: 2,
question: "How can I view my graded exam?",
question: "How can I view my graded exams?",
answer:
"Select a course from the course dashboard. Click on the view exam button under the Graded Exams tab on the course page.",
"Select a course from the course dashboard. Click on the view exam button under the 'Graded Exams' tab on the course page.",
},
];

Expand Down
32 changes: 22 additions & 10 deletions tasks/OMR-tool/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# OMR Tool Backend Service

## Overview

This Python app completes all the tasks needed for grading submitted bubble sheets. The app completes the following steps when a job is created for it:

1. Receives the answer key and submission PDFs from the backend.
2. Converts the PDFs into image lists that can be used by OpenCV and the inferencer.
3. The answer key images are first processed by the inferencer, detecting the question obejects, from there, contour detection and thresholding is done on each question to populate an answer key list.
Expand All @@ -10,9 +13,10 @@ This Python app completes all the tasks needed for grading submitted bubble shee
7. Finally, the pages are compiled into a PDF file again, and the results and PDF file for each submission are sent as a payload to be stored in the backend, and subsequently relayed to the frontend for the professor to view.

## Installation

To run this app, you must have Poetry installed. [You may view the installation instructions for Poetry here](https://python-poetry.org/docs/)

Once you have poetry installed, it is as simple as running
Once you have poetry installed, it is as simple as running
``` poetry install ``` to install all the necessary dependencies.

You can then run the app within the poetry environment by running it within the poetry shell.
Expand All @@ -32,19 +36,27 @@ The code used to train the model is found under the submodule ```model_training/

# Developing Guidelines

- [Tools](#tools)
- [Setup and Installation](#setup-and-installation)
- [Running locally](#running-locally)
- [Running the bubble sheet generator outside of a container](#running-the-bubble-sheet-generator-outside-of-a-container)
## Developing Guidelines

- [OMR Tool Backend Service](#omr-tool-backend-service)
- [Overview](#overview)
- [Installation](#installation)
- [The Object Detection Model](#the-object-detection-model)
- [Developing Guidelines](#developing-guidelines)
- [Developing Guidelines](#developing-guidelines-1)
- [Tools](#tools)
- [Setup and Installation](#setup-and-installation)
- [Running locally](#running-locally)
- [Running the bubble sheet generator outside of a container](#running-the-bubble-sheet-generator-outside-of-a-container)

## Tools
### Tools

You will need to install the following command line tools and applications to run the application:

1. [Docker](https://docs.docker.com/get-docker/)
2. [Poetry](https://python-poetry.org/docs/)

## Setup and Installation
### Setup and Installation

1. Make sure you are in the `OMR-tool` folder by running in your CLI:

Expand All @@ -64,7 +76,7 @@ Confirm that the output follows the following format:
poetry install
```

3. Now, move to the `omr_tool` folder.
3. Now, move to the `omr_tool` folder.

```bash
cd omr_tool/
Expand All @@ -82,9 +94,9 @@ Confirm that you are in the right folder using `pwd`. The output should follow t
cp .env.example .env
```

## Running locally
### Running locally

### Running the bubble sheet generator outside of a container
#### Running the bubble sheet generator outside of a container

The following steps assume that PostgreSQL and Redis are running (for example, in a Docker container).

Expand Down
4 changes: 4 additions & 0 deletions tasks/OMR-tool/omr_tool/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ def send_grades(backend_url, exam_id, submission_results):

student_id = submission_results["student_id"]

# Check if student_id is valid as a last resort
if not student_id:
student_id = "-1"

request = requests.post(
f"{backend_url}/exam/{exam_id}/{student_id}",
headers={"x-worker-auth-token": os.getenv("WORKER_TOKEN")},
Expand Down
2 changes: 1 addition & 1 deletion tasks/OMR-tool/omr_tool/omr_pipeline/identify_student.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def extract_and_highlight_student_id(prepped_image, student_num_section, output_
sid_roi = extract_roi(prepped_image, (x1, y1, x2, y2))
student_id, id_filled, sid_issue = extract_student_num(sid_roi)
if student_id == "-1":
logging.warning(f"Invalid student ID detected in image.")
# logging.warning(f"Invalid student ID detected in image.")
output_image = highlight_error_region(
output_image, map(int, student_num_section), sid_issue
)
Expand Down
10 changes: 5 additions & 5 deletions tasks/OMR-tool/omr_tool/omr_pipeline/omr_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ def populate_answer_key(image: Image, flat_list: list[dict], first_q_in_page: in
question_num = first_q_in_page + i
question_roi = extract_roi(image, question_bounds)
bubble_contours = generate_bubble_contours(question_roi)
bubbled = find_filled_bubbles(question_roi, bubble_contours, 0.8)
bubbled = find_filled_bubbles(question_roi, bubble_contours)
answer_key.append(
{"question_num": question_num, "correct_answer_indices": bubbled}
)
Expand Down Expand Up @@ -338,10 +338,10 @@ def to_PIL_images(images: list[np.ndarray]) -> list[Image]:
from pathlib import Path
from omr_tool.utils.pdf_to_images import convert_to_images

# sheet_path = Path(__file__).resolve().parents[2] / "fixtures" / "custom.jpg"
sheet_path = (
Path(__file__).resolve().parents[2] / "fixtures" / "ubc_submission_100.pdf"
)
sheet_path = Path(__file__).resolve().parents[2] / "fixtures" / "custom.jpg"
# sheet_path = (
# Path(__file__).resolve().parents[2] / "fixtures" / "ubc_submission_200.pdf"
# )

images = convert_to_images(sheet_path)

Expand Down
2 changes: 1 addition & 1 deletion tasks/OMR-tool/omr_tool/omr_pipeline/read_bubbles.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def generate_bubble_contours(image: np.ndarray) -> list:
return sorted_contours


def find_filled_bubbles(img, bubble_contours, threshold=0.7):
def find_filled_bubbles(img, bubble_contours, threshold=0.8):
"""
Finds the indices of filled bubbles in an image based on the given bubble contours.
Expand Down
Loading

0 comments on commit 5bfea5e

Please sign in to comment.