jhOMR-webInterface 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.2
2
+ Name: jhOMR_webInterface
3
+ Version: 0.1.0
4
+ Summary: OMR format to be used in KUET. This program supports using of Web Interface making use of fastAPI
5
+ Author: Md Nur Kutubul Alam
6
+ Author-email: alamjhilam@gmail.com
7
+ Requires-Python: >=3.7
8
+ Requires-Dist: numpy
9
+ Requires-Dist: pandas
10
+ Requires-Dist: opencv-python
11
+ Requires-Dist: openpyxl
12
+ Requires-Dist: fastapi
13
+ Requires-Dist: uvicorn
14
+ Requires-Dist: requests
15
+ Requires-Dist: Jinja2
16
+ Requires-Dist: python-multipart
17
+ Dynamic: author
18
+ Dynamic: author-email
19
+ Dynamic: requires-dist
20
+ Dynamic: requires-python
21
+ Dynamic: summary
@@ -0,0 +1,787 @@
1
+ import cv2
2
+ import pandas as pd
3
+ import numpy as np
4
+ widthImg=800
5
+ heightImg=800
6
+ RectangleScore_dict={}
7
+ DisplayLocation={'Roll':1,'TF':2,'QA':3}
8
+ DisplayLocation_3img={'Roll':0,'TF':1,'QA':2}
9
+
10
+ #Rectangle_Shape_Dict={'QA':(14,8),'TF':(5,10),'Roll':(8,10)}
11
+ #Rectangle_Shape_Dict={'QA':(14,9),'TF':(11,4),'Roll':(11,7)}
12
+ Rectangle_Shape_Dict={'QA':(14,9),'TF':(12,4),'Roll':(11,7)}
13
+
14
+ T='T';F='F';
15
+ a, b, c, d, e, f = 'a', 'b', 'c', 'd', 'e', 'f'
16
+
17
+
18
+ Rectangle_no_dict={'QA':0,'Roll':1,'TF':2}
19
+
20
+
21
+ def imgPreProcess(img, widthImg, heightImg,rectContours,TakeRectangle,threshold_value=0.5):
22
+ #imgCanny = getImgCanny(img)
23
+ # imgBlank = np.zeros_like(img)
24
+ # contours, hierarchy = cv2.findContours(imgCanny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
25
+ #rectContours, imgContours = getRectangles_fromImage(img, widthImg, heightImg)
26
+
27
+ rectangle_no=Rectangle_no_dict[TakeRectangle]
28
+ selectedContour = getCornerPoints(rectContours[rectangle_no])
29
+
30
+ imgWarpColored = getWarpPerspective(img, selectedContour, widthImg, heightImg)
31
+ imgWarpGray = cv2.cvtColor(imgWarpColored, cv2.COLOR_BGR2GRAY)
32
+
33
+
34
+ # if threshold_value is None:
35
+ # threshold_value=(int(imgWarpGray.min())+int(imgWarpGray.max()))/2
36
+ # else:
37
+ threshold_value=int(imgWarpGray.min()*threshold_value)+int(imgWarpGray.max()*(1-threshold_value))
38
+
39
+ imgThreshold = cv2.threshold(imgWarpGray, threshold_value, 255, cv2.THRESH_BINARY_INV)[1]
40
+ return imgThreshold,imgWarpColored
41
+
42
+ def getWarpPerspective(img,selectedContour,widthImg,heightImg):
43
+ selectedContour = reorder(selectedContour)
44
+ pt1 = np.float32(selectedContour)
45
+ pt2 = np.float32([[0, 0], [widthImg, 0], [0, heightImg], [widthImg, heightImg]])
46
+ matrix = cv2.getPerspectiveTransform(pt1, pt2)
47
+ # Transform the image into bird-eye-view using the warp transformation matrix
48
+ imgWarpColored = cv2.warpPerspective(img, matrix, (widthImg, heightImg))
49
+ return imgWarpColored
50
+
51
+ def getRectangles_fromImage(img,widthImg, heightImg):
52
+ img = cv2.resize(img, (widthImg, heightImg))
53
+ imgContours = img.copy()
54
+
55
+ imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
56
+ imgBlur = cv2.GaussianBlur(imgGray, (5, 5), 1)
57
+ imgCanny = cv2.Canny(imgBlur, 10, 50) # Canny is edge detector. Here we are able to detect the edge of the rectengles
58
+
59
+ ##### FINDING ALL CONTOURS
60
+ contours, hierarchy = cv2.findContours(imgCanny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
61
+ cv2.drawContours(imgContours, contours, -1, (0, 255, 0), 10) # Draw all the contours with green line
62
+
63
+ #### FIND RECTANGLES
64
+ rectContours = rectContour(contours) # Find the rectangular contours and sort them according to their area
65
+ return rectContours,imgContours
66
+
67
+ def rectContour(contours):
68
+ rectCon=[]
69
+ for i in contours:
70
+ area=cv2.contourArea(i)
71
+ #print(area)
72
+ if area>50:
73
+ perimeter=cv2.arcLength(i,True) #Closed perimeter=True
74
+ approx=cv2.approxPolyDP(i,0.02*perimeter,True) #Finds the number of corner points
75
+ #print("Corner Points",len(approx))
76
+ if len(approx)==4: #taking the rectangles only
77
+ rectCon.append(i)
78
+ rectCon=sorted(rectCon,key=cv2.contourArea,reverse=True)
79
+ return rectCon
80
+
81
+ def reorder(myPoints):
82
+ #This function takes the corner points of a rectangle as input.
83
+ # Then x+y=minimu, its the origin. If x+y=maximum, it is (w,h)
84
+ #If x-y is minimum, it is (0,h). if x-y is maximu, it is the remaining point (h,0)
85
+
86
+ myPoints=myPoints.reshape((4,2))
87
+ myPointsNew_likeVideo=np.zeros((4,1,2),np.int32)
88
+ add=myPoints.sum(1) #
89
+
90
+ myPointsNew_likeVideo[0]=myPoints[np.argmin(add)] #[0,0]
91
+ myPointsNew_likeVideo[3]=myPoints[np.argmax(add)] #[w,h]
92
+ diff=np.diff(myPoints,axis=1)
93
+ myPointsNew_likeVideo[1]=myPoints[np.argmin(diff)] #[w,0]
94
+ myPointsNew_likeVideo[2]=myPoints[np.argmax(diff)] #[0,h]
95
+ return myPointsNew_likeVideo
96
+ def getCornerPoints(contour):
97
+ perimeter = cv2.arcLength(contour, True) # Closed perimeter=True
98
+ approx = cv2.approxPolyDP(contour, 0.02 * perimeter,True) # Finds the number of corner points
99
+ return approx
100
+
101
+
102
+ def getImgCanny(img):
103
+ imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
104
+ imgBlur = cv2.GaussianBlur(imgGray, (5, 5), 1)
105
+ imgCanny = cv2.Canny(imgBlur, 10,50) # Canny is edge detector. Here we are able to detect the edge of the rectengles
106
+ return imgCanny
107
+
108
+
109
+ def fillFraction(box):
110
+ n_pixel=cv2.countNonZero(box)
111
+ total_pixel=np.shape(box)[0]*np.shape(box)[1]
112
+ return round(n_pixel / total_pixel,4)
113
+
114
+ def get_Fillups(imgThreshold,n_rows,n_cols,n_skipPixels=30,w=0.5):
115
+ # This function splits a rectangle into individual boxes. Then calculates box fill fraction
116
+ # Note that sometimes box can be significantly larger than the OMR circle. In that case we skip few pixels along
117
+ # the horizontal direction. In other words, we crop the image horizontally
118
+ imgThreshold_modified = cv2.resize(imgThreshold, (n_cols * 100, n_rows * 100))
119
+ rows = np.vsplit(imgThreshold_modified, n_rows)
120
+ ############################
121
+ boxes=[]
122
+ rgbBoxes=[]
123
+ boxFillFractions=[]
124
+ for row in rows:
125
+ cols=np.hsplit(row, n_cols)
126
+ for box in cols:
127
+ grayBox = box.copy()
128
+ rgb_box = cv2.cvtColor(grayBox, cv2.COLOR_GRAY2RGB)
129
+
130
+ box=box[:,n_skipPixels:100-n_skipPixels]
131
+ boxes.append(box)
132
+ fill = fillFraction(box)
133
+ boxFillFractions.append(fill)
134
+ #Mark the cropped region with "Blue gray"
135
+ rgb_box[:, 0:n_skipPixels, 1] = 54;rgb_box[:, -n_skipPixels:, 1] = 54
136
+ rgb_box[:, 0:n_skipPixels, 1] = 147;rgb_box[:, -n_skipPixels:, 1] = 147
137
+ rgb_box[:, 0:n_skipPixels, 2] = 179;rgb_box[:, -n_skipPixels:, 2] = 179
138
+ rgbBoxes.append(rgb_box)
139
+
140
+ boxes = np.reshape(boxes, (n_rows, n_cols, np.shape(boxes)[1], np.shape(boxes)[2]))
141
+ rgbBoxes=np.reshape(rgbBoxes,(n_rows, n_cols, np.shape(rgbBoxes)[1], np.shape(rgbBoxes)[2],np.shape(rgbBoxes)[3]))
142
+ boxFillFractions = np.reshape(boxFillFractions, (n_rows, n_cols))
143
+
144
+
145
+ # threshold_row=boxFillFractions[0,:]
146
+ # threshold=w*threshold_row.max()+(1-w)*threshold_row.min()
147
+ threshold=w*boxFillFractions.max()+(1-w)*boxFillFractions.min()
148
+
149
+ ind=np.where(boxFillFractions>threshold)
150
+ fillupMatrix=np.zeros(np.shape(boxFillFractions))
151
+ fillupMatrix[ind]=1
152
+
153
+ return boxes,rgbBoxes,boxFillFractions,fillupMatrix
154
+
155
+
156
+
157
+
158
+
159
+ #answer_loc_dict={'a':1, 'b':2, 'c':4, 'd':5, 'e':7, 'f':8}
160
+
161
+ answer_loc_dict={'a':1, 'b':2, 'c':4, 'd':5, 'e':7, 'f':8,'T':1,'F':3}
162
+
163
+ def GenerateCorrectAnswerMatrix(Correct_answer_dict,Rectangle_Shape,ignore_nrows=2):
164
+
165
+ correct_answer_matrix = np.zeros(Rectangle_Shape)
166
+ for key in Correct_answer_dict:
167
+ correct_answer=Correct_answer_dict[key] #a, b, c etc.
168
+ column=answer_loc_dict[correct_answer]
169
+ #print(key,column)
170
+ correct_answer_matrix[int(key)+int(ignore_nrows-1), column] = 1
171
+ return correct_answer_matrix
172
+
173
+ # def GenerateCorrectTF_Matrix(Correct_TrueFalse_indices,Rectangle_Shape):
174
+ # correct_TF_matrix = np.zeros(Rectangle_Shape)
175
+ # for key in Correct_TrueFalse_indices:
176
+ # if Correct_TrueFalse_indices[key]=='T':
177
+ # correct_TF_matrix[ key,1] = 1
178
+ # elif Correct_TrueFalse_indices[key]=='F':
179
+ # correct_TF_matrix[ key,3] = 1
180
+ # else:
181
+ # print("Correct_TrueFalse_indices is not correctly defined")
182
+ # return correct_TF_matrix
183
+
184
+
185
+
186
+
187
+
188
+ def RotateClockwise(*Matrices):
189
+ retList=[]
190
+ for matrix in Matrices:
191
+ matrix=cv2.rotate(matrix, cv2.ROTATE_90_CLOCKWISE)
192
+ retList.append(matrix)
193
+ return retList
194
+
195
+
196
+
197
+
198
+ def Nullify_DoubleAnswers(fillups,TakeRectangle):
199
+ def ApplyNullification(fillups_innder):
200
+ fillupNullified = fillups_innder.copy()
201
+ for nn in range(0, np.shape(fillups)[0]):
202
+ ind = np.where(fillups[nn,] == 1)
203
+ if len(ind[0])> 1:
204
+ fillupNullified[nn,ind]='Nan'
205
+ return fillupNullified
206
+
207
+ fillups[0, :] = 0
208
+ if TakeRectangle=='TF':
209
+ fillupProcessed=ApplyNullification(fillups)
210
+ elif TakeRectangle=='QA':
211
+ if np.shape(fillups)[0]<np.shape(fillups)[1]:
212
+ print("Nullify_DoubleAnswers: Wrong fillup matrix supplied!!! Expecting for 'QA' box...")
213
+ #print(colored("Nullify_DoubleAnswers: Wrong fillup matrix supplied!!! Expecting for 'QA' box..."),'red')
214
+ fillupProcessed = ApplyNullification(fillups)
215
+ else:
216
+ fillupProcessed = ApplyNullification(fillups)
217
+ # fillups[0, :] = 'Nan'
218
+ # fillupProcessed=fillups
219
+ return fillupProcessed
220
+
221
+
222
+
223
+ def computeRoll(fillupMatrix,TakeRectangle):
224
+ fillupMatrix=fillupMatrix[1:,:] #Skipping 1st row
225
+
226
+ if TakeRectangle=='Roll':
227
+ roll=0
228
+ for nn in range(0,7):
229
+ col=fillupMatrix[:,nn]
230
+ ind=np.where(col==1)[0]
231
+ if len(ind)==0:
232
+ print("Roll: No fillup in col ",nn,'\t',col)
233
+ return -1
234
+ elif len(ind)>1:
235
+ print("Roll: Multiple fillup in col ",nn,'\t',col)
236
+ return -1
237
+ else:
238
+ roll=roll+10**(6-nn)*ind[0]
239
+ else:
240
+ roll=-1
241
+ return '{:07.0f}'.format(roll)
242
+
243
+ def computeRoll_v2(boxFillFractions,TakeRectangle,ignore_nrows=2,w=0.5):
244
+ n_cols=np.shape(boxFillFractions)[1]
245
+ fillupMatrix=np.zeros(np.shape(boxFillFractions))
246
+
247
+ threshold=w*boxFillFractions[0,:].max()+(1-w)*boxFillFractions[0,:].min()
248
+ ind=np.where(boxFillFractions[0,:]>threshold)
249
+ fillupMatrix[0,ind]=1
250
+
251
+ for nn in range(n_cols):
252
+ ind=np.where(boxFillFractions[ignore_nrows:,nn]==max(boxFillFractions[ignore_nrows:,nn]))[0]
253
+ fillupMatrix[ind+ignore_nrows,nn]=1 #Skipping 1st "ignore_nrows"
254
+
255
+
256
+ if TakeRectangle=='Roll':
257
+ roll=0
258
+ for nn in range(0,7):
259
+ col=fillupMatrix[ignore_nrows:,nn]
260
+ ind=np.where(col==1)[0]
261
+ if len(ind)==0:
262
+ print("Roll: No fillup in col ",nn,'\t',col)
263
+ return -1
264
+ elif len(ind)>1:
265
+ print("Roll: Multiple fillup in col ",nn,'\t',col)
266
+ return -1
267
+ else:
268
+ roll=roll+10**(6-nn)*ind[0]
269
+ else:
270
+ roll=-1
271
+
272
+ return fillupMatrix,'{:07.0f}'.format(roll)
273
+
274
+
275
+
276
+ import itertools
277
+
278
+ def showRoll(imgWarpColored, fillupMatrix_doubleAns_nullified):
279
+ imgAnswers = imgWarpColored.copy()
280
+
281
+ n_rows = np.shape(fillupMatrix_doubleAns_nullified)[0]
282
+ n_cols = np.shape(fillupMatrix_doubleAns_nullified)[1]
283
+
284
+
285
+ secW = int(imgAnswers.shape[1] / n_cols)
286
+ secH = int(imgAnswers.shape[0] / n_rows)
287
+ thickness = 8
288
+
289
+ for row, col in itertools.product(range(1,n_rows), range(n_cols)):
290
+ if np.isnan(fillupMatrix_doubleAns_nullified[row,]).any(): # If row contains a 'nan' (multiple answer),continue
291
+ cX=int(secW / 2 + 0 * secW)
292
+ cY = int(secH / 2 + row * secH)
293
+ cv2.rectangle(imgAnswers, (int(cX - secW / 2), int(cY+ secH / 2)),
294
+ (int(cX+n_rows* secW + secW / 2), int(cY - secH / 2)), (94, 48, 201), thickness)
295
+ continue
296
+
297
+ Entry_Student_answerMatrix = fillupMatrix_doubleAns_nullified[row, col]
298
+
299
+ if Entry_Student_answerMatrix: #Mark the answer only if student answered it
300
+ cX_student = int(secW / 2 + col * secW)
301
+ cY_student = int(secH / 2 + row * secH)
302
+
303
+ cv2.rectangle(imgAnswers, (int(cX_student-secW / 2),int(cY_student+secH/2)), (int(cX_student+secW / 2),int(cY_student-secH/2)), (255, 255, 0), thickness)
304
+ return imgAnswers
305
+
306
+
307
+ def showAnswers(imgWarpColored, correct_answer_matrix,fillupMatrix_doubleAns_nullified,TakeRectangle,Mark_dict,ignore_nrows=2):
308
+ text_x_offset_dict={'TF':2,'QA':4}
309
+ mark_x_offset_dict={'TF':0.6,'QA':3}
310
+
311
+ Mark_correctAnswer_dict,Mark_wrongAnswer_dict=Mark_dict[TakeRectangle]
312
+ text_x_offset=text_x_offset_dict[TakeRectangle]
313
+ mark_x_offset_dict=mark_x_offset_dict[TakeRectangle]
314
+
315
+ dictLen=np.shape(correct_answer_matrix)[0]
316
+ Mark_correctAnswer_dict, Mark_wrongAnswer_dict = Convert_integer_mark_into_dictionary(Mark_correctAnswer_dict,Mark_wrongAnswer_dict,dictLen)
317
+
318
+ imgAnswers = imgWarpColored.copy()
319
+ n_rows = np.shape(correct_answer_matrix)[0]
320
+ n_cols = np.shape(correct_answer_matrix)[1]
321
+
322
+
323
+ secW = int(imgAnswers.shape[1] / n_cols)
324
+ secH = int(imgAnswers.shape[0] / n_rows)
325
+
326
+ angle = 0;
327
+ startAngle = 0;
328
+ endAngle = 360;
329
+ axesLength = (int(secW / 2.5), int(secH / 2));
330
+ thickness = 8
331
+
332
+ # for row, col in itertools.product(range(n_rows), range(n_cols)):
333
+ for row, col in itertools.product(range(ignore_nrows,n_rows), range(n_cols)):
334
+ Dict_key=row-(ignore_nrows-1)
335
+
336
+ cX_student = int(secW / 2 + col * secW)
337
+ cY_student = int(secH / 2 + row * secH)
338
+ center_coordinates = (cX_student, cY_student);
339
+ Mark_text_coordinate=(int(secW * mark_x_offset_dict), cY_student)
340
+ # Mark_text_coordinate=(int(secW / 2), cY_student)
341
+ text_coordinate = (int(secW * text_x_offset), cY_student)
342
+
343
+
344
+ if np.isnan(fillupMatrix_doubleAns_nullified[row,]).any(): # If row contains a 'nan' (multiple answer),continue
345
+ cX=int(secW / 2 + 0 * secW)
346
+ cY = int(secH / 2 + row * secH)
347
+ cv2.rectangle(imgAnswers, (int(cX - secW / 2), int(cY+ secH / 2)),
348
+ (int(cX+n_rows* secW + secW / 2), int(cY - secH / 2)), (94, 48, 201), thickness)
349
+ cv2.putText(imgAnswers, "Null", text_coordinate, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255),
350
+ 4, cv2.LINE_AA)
351
+ continue
352
+
353
+ if Dict_key not in Mark_correctAnswer_dict and Dict_key not in Mark_wrongAnswer_dict:
354
+ cv2.putText(imgAnswers, "CA & WA Mark Not provided", text_coordinate, cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 255), 4,
355
+ cv2.LINE_AA)
356
+ elif Dict_key not in Mark_correctAnswer_dict:
357
+ cv2.putText(imgAnswers, "CA Mark Not provided", text_coordinate, cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 255), 4,
358
+ cv2.LINE_AA)
359
+ elif Dict_key not in Mark_wrongAnswer_dict:
360
+ cv2.putText(imgAnswers, "WA Mark Not provided", text_coordinate, cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 255), 4,
361
+ cv2.LINE_AA)
362
+
363
+
364
+ Entry_Student_answerMatrix = fillupMatrix_doubleAns_nullified[row, col]
365
+ Entry_corr_answerMatrix = correct_answer_matrix[row, col]
366
+
367
+ if Entry_Student_answerMatrix: #Mark the answer only if student answered it
368
+ #Correct answer for this question is not provided, though student answered it.
369
+ #In that case, mark the answer with a square.
370
+ if not np.any(correct_answer_matrix[row,]): # If there is no "correct answer" of a given question
371
+ cv2.rectangle(imgAnswers, (int(cX_student-secW / 2),int(cY_student+secH/2)), (int(cX_student+secW / 2),int(cY_student-secH/2)), (255, 255, 0), thickness)
372
+ cv2.putText(imgAnswers,"Ans. Not Provided",text_coordinate,cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,255),4,cv2.LINE_AA)
373
+ continue
374
+
375
+ #Answer is correct
376
+ elif Entry_corr_answerMatrix == Entry_Student_answerMatrix:
377
+ cv2.ellipse(imgAnswers, center_coordinates, axesLength, angle, startAngle, endAngle, (0, 255, 0),
378
+ thickness)
379
+ isKeyPresent = Dict_key in Mark_correctAnswer_dict
380
+ if isKeyPresent:
381
+ Mark_CA=Mark_correctAnswer_dict[row-(ignore_nrows-1)]
382
+ cv2.putText(imgAnswers,str(Mark_CA),Mark_text_coordinate,cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,255),4,cv2.LINE_AA)
383
+
384
+ else:
385
+ cv2.ellipse(imgAnswers, center_coordinates, axesLength, angle, startAngle, endAngle, (0, 0, 255),
386
+ thickness)
387
+ col = np.where(correct_answer_matrix[row,] == 1)[0]
388
+
389
+ cX_correct = int(secW / 2 + col * secW)
390
+ cY_correct = int(secH / 2 + row * secH)
391
+ cv2.ellipse(imgAnswers, (cX_correct, cY_correct), axesLength, angle, startAngle, endAngle,
392
+ (255, 255, 0), thickness)
393
+
394
+ isKeyPresent = Dict_key in Mark_wrongAnswer_dict
395
+ if isKeyPresent:
396
+ Mark_WA=Mark_wrongAnswer_dict[row-(ignore_nrows-1)]
397
+ cv2.putText(imgAnswers,str(Mark_WA),Mark_text_coordinate,cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,255),4,cv2.LINE_AA)
398
+
399
+ return imgAnswers
400
+
401
+
402
+
403
+
404
+
405
+ def Convert_integer_mark_into_dictionary(Mark_correctAns_dict,Mark_wrongAns_dict,dictLen):
406
+ if type(Mark_correctAns_dict) is not dict:
407
+ tmp_Mark_correctAnswer = {}
408
+ tmr_Mark_wrongAnswer = {}
409
+ for row in range(0, dictLen):
410
+ tmp_Mark_correctAnswer[row] = Mark_correctAns_dict
411
+ tmr_Mark_wrongAnswer[row] = Mark_wrongAns_dict
412
+ Mark_correctAns_dict = tmp_Mark_correctAnswer
413
+ Mark_wrongAns_dict = tmr_Mark_wrongAnswer
414
+ return Mark_correctAns_dict,Mark_wrongAns_dict
415
+
416
+
417
+ def GenerateScore(TakeRectangle,Mark_dict,correct_answer_matrix,fillupMatrix_doubleAns_nullified,imgWarpColored,ignore_nrows=2):
418
+ def _GenerateScore(correct_answer_matrix,fillupMatrix_doubleAns_nullified,Mark_correctAnswer_dict,Mark_wrongAnswer_dict,imgWarpColored,ignore_nrows):
419
+ n_rows = np.shape(correct_answer_matrix)[0]
420
+ n_cols = np.shape(correct_answer_matrix)[1]
421
+
422
+ text_x_offset_dict = {'TF': 2, 'QA': 4}
423
+ mark_x_offset_dict = {'TF': 0.6, 'QA': 3}
424
+ text_x_offset = text_x_offset_dict[TakeRectangle]
425
+ mark_x_offset_dict = mark_x_offset_dict[TakeRectangle]
426
+ imgAnswers = imgWarpColored.copy()
427
+ secW = int(imgAnswers.shape[1] / n_cols)
428
+ secH = int(imgAnswers.shape[0] / n_rows)
429
+ angle = 0;
430
+ startAngle = 0;
431
+ endAngle = 360;
432
+ axesLength = (int(secW / 2.5), int(secH / 2));
433
+ thickness = 8
434
+
435
+ score_positive = 0
436
+ score_negative = 0
437
+
438
+ for row in range(ignore_nrows, n_rows):
439
+ # if Mark_correctAnswer is None or Mark_wrongAnswer is None: #If the mark is not given
440
+ # continue
441
+
442
+ Row_studentAnswerMatrix = fillupMatrix_doubleAns_nullified[row, :]
443
+ Row_CorrectAnswerMatrix = correct_answer_matrix[row, :]
444
+ Dict_key = row - (ignore_nrows - 1)
445
+
446
+ if not np.any(Row_studentAnswerMatrix): #Student did not answer this question.
447
+ continue
448
+ if np.isnan(Row_studentAnswerMatrix).any(): # Multiple answering nullifies the answer.
449
+ cX = int(secW / 2 + 0 * secW)
450
+ cY = int(secH / 2 + row * secH)
451
+ cv2.rectangle(imgAnswers, (int(cX - secW / 2), int(cY + secH / 2)),(int(cX + n_rows * secW + secW / 2), int(cY - secH / 2)), (94, 48, 201), thickness)
452
+ text_coordinate = (int(secW * text_x_offset), cY)
453
+ cv2.putText(imgAnswers, "Null", text_coordinate, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 4, cv2.LINE_AA)
454
+ continue
455
+
456
+
457
+ studentAnsIndex = np.where(Row_studentAnswerMatrix == 1)[0]
458
+ correctAnsIndex = np.where(Row_CorrectAnswerMatrix == 1)[0]
459
+
460
+ cX_student = int(secW / 2 + studentAnsIndex * secW)
461
+ cY_student = int(secH / 2 + row * secH)
462
+ student_fillup_coordinate = (cX_student, cY_student);
463
+ Mark_text_coordinate = (int(secW * mark_x_offset_dict), cY_student)
464
+ # Mark_text_coordinate=(int(secW / 2), cY_student)
465
+ text_coordinate = (int(secW * text_x_offset), cY_student)
466
+
467
+
468
+ if not np.any(Row_CorrectAnswerMatrix): # If there is no "correct answer" of a given question, though student answered it.
469
+ # In that case, assume this question does not exist, and continue.
470
+ cv2.rectangle(imgAnswers, (int(cX_student-secW / 2),int(cY_student+secH/2)), (int(cX_student+secW / 2),int(cY_student-secH/2)), (255, 255, 0), thickness)
471
+ cv2.putText(imgAnswers,"Ans. Not Provided",text_coordinate,cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,255),4,cv2.LINE_AA)
472
+ continue
473
+
474
+
475
+
476
+ elif studentAnsIndex == correctAnsIndex: #Student filled the correct circle
477
+ cv2.ellipse(imgAnswers, student_fillup_coordinate, axesLength, angle, startAngle, endAngle, (0, 255, 0),thickness)
478
+ if Mark_correctAnswer_dict.get(Dict_key) is not None:
479
+ Mark_correctAnswer = Mark_correctAnswer_dict.get(Dict_key)
480
+ score_positive = score_positive + Mark_correctAnswer
481
+ cv2.putText(imgAnswers,str(Mark_correctAnswer),Mark_text_coordinate,cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,255),4,cv2.LINE_AA)
482
+ else:
483
+ cv2.putText(imgAnswers, "CA Mark Not provided", text_coordinate, cv2.FONT_HERSHEY_SIMPLEX, 1,(255, 0, 255), 4,cv2.LINE_AA)
484
+
485
+
486
+ else: #student filled a wrong circle
487
+ cv2.ellipse(imgAnswers, student_fillup_coordinate, axesLength, angle, startAngle, endAngle, (0, 0, 255),thickness)
488
+ col = np.where(correct_answer_matrix[row,] == 1)[0]
489
+ cX_correct = int(secW / 2 + col * secW); cY_correct = int(secH / 2 + row * secH)
490
+ cv2.ellipse(imgAnswers, (cX_correct, cY_correct), axesLength, angle, startAngle, endAngle, (255, 255, 0), thickness)
491
+
492
+ if Mark_wrongAnswer_dict.get(Dict_key) is not None :
493
+ Mark_wrongAnswer = Mark_wrongAnswer_dict.get(Dict_key)
494
+ score_negative = score_negative + Mark_wrongAnswer
495
+ cv2.putText(imgAnswers,str(Mark_wrongAnswer),Mark_text_coordinate,cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,255),4,cv2.LINE_AA)
496
+ else:
497
+ cv2.putText(imgAnswers, "WA Mark Not provided", text_coordinate, cv2.FONT_HERSHEY_SIMPLEX, 1,(255, 0, 255), 4, cv2.LINE_AA)
498
+
499
+ return score_positive,score_negative,imgAnswers
500
+ ################################## showAnswers works from Here ###############################################
501
+
502
+
503
+ Mark_correctAnswer_dict,Mark_wrongAnswer_dict=Mark_dict[TakeRectangle]
504
+ dictLen=np.shape(correct_answer_matrix)[0]
505
+ Mark_correctAnswer_dict, Mark_wrongAnswer_dict = Convert_integer_mark_into_dictionary(Mark_correctAnswer_dict,Mark_wrongAnswer_dict,dictLen)
506
+
507
+ score_positive,score_negative,imgAnswers=_GenerateScore(correct_answer_matrix,fillupMatrix_doubleAns_nullified,Mark_correctAnswer_dict, Mark_wrongAnswer_dict,imgWarpColored,ignore_nrows)
508
+
509
+ return score_positive,score_negative,imgAnswers
510
+
511
+ # def GenerateScore(TakeRectangle,Mark_dict,correct_answer_matrix,fillupMatrix_doubleAns_nullified,ignore_nrows=2):
512
+ # def _GenerateScore(correct_answer_matrix,fillupMatrix_doubleAns_nullified,Mark_correctAnswer_dict,Mark_wrongAnswer_dict,ignore_nrows):
513
+ # n_rows = np.shape(correct_answer_matrix)[0]
514
+ # score = 0
515
+ #
516
+ # for row in range(0, n_rows):
517
+ #
518
+ # # if Mark_correctAnswer is None or Mark_wrongAnswer is None: #If the mark is not given
519
+ # # continue
520
+ #
521
+ # Row_studentAnswerMatrix = fillupMatrix_doubleAns_nullified[row, :]
522
+ # Row_CorrectAnswerMatrix = correct_answer_matrix[row, :]
523
+ # Dict_key = row - (ignore_nrows - 1)
524
+ #
525
+ # # Correct answer for this question is not provided, though student answered it.
526
+ # # In that case, assume this question does not exist, and continue.
527
+ # if not np.any(Row_CorrectAnswerMatrix): # If there is no "correct answer" of a given question
528
+ # continue
529
+ # if not np.any(Row_studentAnswerMatrix): #Student did not answer this question.
530
+ # continue
531
+ #
532
+ # studentAnsIndex = np.where(Row_studentAnswerMatrix == 1)[0]
533
+ # correctAnsIndex = np.where(Row_CorrectAnswerMatrix == 1)[0]
534
+ #
535
+ # if np.isnan(Row_studentAnswerMatrix).any(): # Multiple answering nullifies the answer.
536
+ # continue
537
+ # elif studentAnsIndex == correctAnsIndex and Mark_correctAnswer_dict.get(Dict_key) is not None:
538
+ # Mark_correctAnswer = Mark_correctAnswer_dict.get(Dict_key)
539
+ # score = score + Mark_correctAnswer
540
+ # elif Mark_wrongAnswer_dict.get(Dict_key) is not None :
541
+ # Mark_wrongAnswer = Mark_wrongAnswer_dict.get(Dict_key)
542
+ # score = score + Mark_wrongAnswer
543
+ #
544
+ # return score
545
+ # ################################## showAnswers works from Here ###############################################
546
+ #
547
+ #
548
+ # Mark_correctAnswer_dict,Mark_wrongAnswer_dict=Mark_dict[TakeRectangle]
549
+ # dictLen=np.shape(correct_answer_matrix)[0]
550
+ # Mark_correctAnswer_dict, Mark_wrongAnswer_dict = Convert_integer_mark_into_dictionary(Mark_correctAnswer_dict,Mark_wrongAnswer_dict,dictLen)
551
+ #
552
+ #
553
+ # score=_GenerateScore(correct_answer_matrix,fillupMatrix_doubleAns_nullified,Mark_correctAnswer_dict, Mark_wrongAnswer_dict,ignore_nrows)
554
+ #
555
+ # return score
556
+
557
+
558
+
559
+ # def GenerateScore(TakeRectangle,Mark_dict,correct_answer_matrix,fillupMatrix_doubleAns_nullified):
560
+ # def _GenerateScore(correct_answer_matrix,fillupMatrix_doubleAns_nullified,Mark_correctAnswer_dict,Mark_wrongAnswer_dict):
561
+ # n_rows = np.shape(correct_answer_matrix)[0]
562
+ # score = 0
563
+ #
564
+ # for row in range(0, n_rows):
565
+ #
566
+ # Mark_correctAnswer = Mark_correctAnswer_dict.get(row)
567
+ # Mark_wrongAnswer=Mark_wrongAnswer_dict.get(row)
568
+ #
569
+ # if Mark_correctAnswer is None or Mark_wrongAnswer is None: #If the mark is not given
570
+ # continue
571
+ #
572
+ # Row_studentAnswerMatrix = fillupMatrix_doubleAns_nullified[row, :]
573
+ # Row_CorrectAnswerMatrix = correct_answer_matrix[row, :]
574
+ #
575
+ # # Correct answer for this question is not provided, though student answered it.
576
+ # # In that case, assume this question does not exist, and continue.
577
+ # if not np.any(Row_CorrectAnswerMatrix): # If there is no "correct answer" of a given question
578
+ # continue
579
+ # if not np.any(Row_studentAnswerMatrix): #Student did not answer this question.
580
+ # continue
581
+ #
582
+ # studentAnsIndex = np.where(Row_studentAnswerMatrix == 1)[0]
583
+ # correctAnsIndex = np.where(Row_CorrectAnswerMatrix == 1)[0]
584
+ #
585
+ # if np.isnan(Row_studentAnswerMatrix).any(): # Multiple answering nullifies the answer.
586
+ # continue
587
+ # elif studentAnsIndex == correctAnsIndex:
588
+ # score = score + Mark_correctAnswer
589
+ # else:
590
+ # score = score + Mark_wrongAnswer
591
+ # return score
592
+ # ################################## showAnswers works from Here ###############################################
593
+ #
594
+ #
595
+ # Mark_correctAnswer_dict,Mark_wrongAnswer_dict=Mark_dict[TakeRectangle]
596
+ # dictLen=np.shape(correct_answer_matrix)[0]
597
+ # Mark_correctAnswer_dict, Mark_wrongAnswer_dict = Convert_integer_mark_into_dictionary(Mark_correctAnswer_dict,Mark_wrongAnswer_dict,dictLen)
598
+ #
599
+ #
600
+ # score=_GenerateScore(correct_answer_matrix,fillupMatrix_doubleAns_nullified,Mark_correctAnswer_dict, Mark_wrongAnswer_dict)
601
+ #
602
+ # return score
603
+
604
+ def Conv_FirstRow_into_Binary(a):
605
+ b = 0
606
+ for i in range(len(a)):
607
+ b=b+10**i*a[len(a)-i-1]
608
+ return int(b)
609
+
610
+ def ReadCorrectAnswer(xlsxFN, nrows=12, usecols='A:Q', Print=False):
611
+ print("\n\t\tReading "+str(xlsxFN)+'...')
612
+ AllAnswer_df = pd.read_excel(xlsxFN, nrows=nrows, usecols=usecols)
613
+
614
+ TF_df = AllAnswer_df[
615
+ ['True/False no', 'T/F Set 1', 'T/F Set 2', 'T/F Set 3', 'Correct T/F mark', 'Wrong T/F mark']].copy()
616
+ QA_df = AllAnswer_df[
617
+ ['Question no', 'Q/A Set 1', 'Q/A Set 2', 'Q/A Set 3', 'Correct Q/A mark', 'Wrong Q/A mark']].copy()
618
+
619
+ Course = list(AllAnswer_df['Course'])[0]
620
+
621
+ binaryCode=AllAnswer_df['Binary code'].dropna()
622
+ binaryCode=list(binaryCode.astype(int))
623
+ equivalentSetCode=AllAnswer_df['Equivalent Set Code'].dropna()
624
+ equivalentSetCode =list(equivalentSetCode.astype(int))
625
+ SetCode = pd.Series(equivalentSetCode,index=binaryCode)
626
+ GetSetCode_from_Binary=SetCode.to_dict()
627
+
628
+
629
+ TF_df = TF_df.dropna(subset=['T/F Set 1', 'T/F Set 2', 'T/F Set 3', 'Correct T/F mark', 'Wrong T/F mark'])
630
+ QA_df = QA_df.dropna(subset=['Q/A Set 1', 'Q/A Set 2', 'Q/A Set 3', 'Correct Q/A mark', 'Wrong Q/A mark'])
631
+
632
+ if Print:
633
+ print('\n\t Verify the content of '+str(xlsxFN)+':\n')
634
+ print('\t\tCourse= ',Course)
635
+ for key in GetSetCode_from_Binary:
636
+ print("\t\tBinary:", key, '\t\tSet Code:', GetSetCode_from_Binary[key])
637
+ print('\n')
638
+ print(TF_df.to_string(index=False), '\n', QA_df.to_string(index=False))
639
+
640
+ TF_question_no = TF_df['True/False no']
641
+ TF_set1 = TF_df['T/F Set 1']
642
+ TF_set2 = TF_df['T/F Set 2']
643
+ TF_set3 = TF_df['T/F Set 3']
644
+ C_TF_Mark_Arr = TF_df['Correct T/F mark']
645
+ W_TF_Mark_Arr = TF_df['Wrong T/F mark']
646
+
647
+ QA_question_no = QA_df['Question no']
648
+ QA_set1 = QA_df['Q/A Set 1']
649
+ QA_set2 = QA_df['Q/A Set 2']
650
+ QA_set3 = QA_df['Q/A Set 3']
651
+ C_QA_Mark_Arr = QA_df['Correct Q/A mark']
652
+ W_QA_Mark_Arr = QA_df['Wrong Q/A mark']
653
+
654
+ TF_ind_set1={}
655
+ TF_ind_set2 = {}
656
+ TF_ind_set3 = {}
657
+ Mark_correctAnswer_TF={}
658
+ Mark_wrongAnswer_TF={}
659
+
660
+
661
+ for i in range(len(TF_question_no)):
662
+ TF_ind_set1[TF_question_no[i]] = TF_set1[i].strip()
663
+ TF_ind_set2[TF_question_no[i]] = TF_set2[i].strip()
664
+ TF_ind_set3[TF_question_no[i]] = TF_set3[i].strip()
665
+ Mark_correctAnswer_TF[TF_question_no[i]] = C_TF_Mark_Arr[i]
666
+ Mark_wrongAnswer_TF[TF_question_no[i]] = W_TF_Mark_Arr[i]
667
+
668
+ QA_ind_set1={}
669
+ QA_ind_set2 = {}
670
+ QA_ind_set3 = {}
671
+ Mark_CorrectAnswer_QA={}
672
+ Mark_wrongAnswer_QA={}
673
+ for i in range(len(QA_question_no)):
674
+ QA_ind_set1[QA_question_no[i]] = QA_set1[i].strip()
675
+ QA_ind_set2[QA_question_no[i]] = QA_set2[i].strip()
676
+ QA_ind_set3[QA_question_no[i]] = QA_set3[i].strip()
677
+ Mark_CorrectAnswer_QA[QA_question_no[i]] = C_QA_Mark_Arr[i]
678
+ Mark_wrongAnswer_QA[QA_question_no[i]] = W_QA_Mark_Arr[i]
679
+
680
+ return Course,GetSetCode_from_Binary,TF_ind_set1, TF_ind_set2, TF_ind_set3, QA_ind_set1, QA_ind_set2, QA_ind_set3, Mark_correctAnswer_TF, Mark_wrongAnswer_TF, Mark_CorrectAnswer_QA, Mark_wrongAnswer_QA
681
+
682
+
683
+ def ReadCorrectAnswer_dict_from_webread_df(AllAnswer_df, Print=False):
684
+
685
+ TF_df = AllAnswer_df[
686
+ ['True/False no', 'T/F Set 1', 'T/F Set 2', 'T/F Set 3', 'Correct T/F mark', 'Wrong T/F mark']].copy()
687
+ QA_df = AllAnswer_df[
688
+ ['Question no', 'Q/A Set 1', 'Q/A Set 2', 'Q/A Set 3', 'Correct Q/A mark', 'Wrong Q/A mark']].copy()
689
+
690
+ Course = list(AllAnswer_df['Course'])[0]
691
+
692
+ binaryCode=AllAnswer_df['Binary code'].dropna()
693
+ binaryCode=list(binaryCode.astype(int))
694
+ equivalentSetCode=AllAnswer_df['Equivalent Set Code'].dropna()
695
+ equivalentSetCode =list(equivalentSetCode.astype(int))
696
+ SetCode = pd.Series(equivalentSetCode,index=binaryCode)
697
+ GetSetCode_from_Binary=SetCode.to_dict()
698
+
699
+
700
+ TF_df = TF_df.dropna(subset=['T/F Set 1', 'T/F Set 2', 'T/F Set 3', 'Correct T/F mark', 'Wrong T/F mark'])
701
+ QA_df = QA_df.dropna(subset=['Q/A Set 1', 'Q/A Set 2', 'Q/A Set 3', 'Correct Q/A mark', 'Wrong Q/A mark'])
702
+
703
+ if Print:
704
+ print('\t\tCourse= ',Course)
705
+ for key in GetSetCode_from_Binary:
706
+ print("\t\tBinary:", key, '\t\tSet Code:', GetSetCode_from_Binary[key])
707
+ print('\n')
708
+ print(TF_df.to_string(index=False), '\n', QA_df.to_string(index=False))
709
+
710
+ TF_question_no = TF_df['True/False no']
711
+ TF_set1 = TF_df['T/F Set 1']
712
+ TF_set2 = TF_df['T/F Set 2']
713
+ TF_set3 = TF_df['T/F Set 3']
714
+ C_TF_Mark_Arr = TF_df['Correct T/F mark']
715
+ W_TF_Mark_Arr = TF_df['Wrong T/F mark']
716
+
717
+ QA_question_no = QA_df['Question no']
718
+ QA_set1 = QA_df['Q/A Set 1']
719
+ QA_set2 = QA_df['Q/A Set 2']
720
+ QA_set3 = QA_df['Q/A Set 3']
721
+ C_QA_Mark_Arr = QA_df['Correct Q/A mark']
722
+ W_QA_Mark_Arr = QA_df['Wrong Q/A mark']
723
+
724
+ TF_ind_set1={}
725
+ TF_ind_set2 = {}
726
+ TF_ind_set3 = {}
727
+ Mark_correctAnswer_TF={}
728
+ Mark_wrongAnswer_TF={}
729
+
730
+
731
+ for i in range(len(TF_question_no)):
732
+ TF_ind_set1[TF_question_no[i]] = TF_set1[i].strip()
733
+ TF_ind_set2[TF_question_no[i]] = TF_set2[i].strip()
734
+ TF_ind_set3[TF_question_no[i]] = TF_set3[i].strip()
735
+ Mark_correctAnswer_TF[TF_question_no[i]] = C_TF_Mark_Arr[i]
736
+ Mark_wrongAnswer_TF[TF_question_no[i]] = W_TF_Mark_Arr[i]
737
+
738
+ QA_ind_set1={}
739
+ QA_ind_set2 = {}
740
+ QA_ind_set3 = {}
741
+ Mark_CorrectAnswer_QA={}
742
+ Mark_wrongAnswer_QA={}
743
+ for i in range(len(QA_question_no)):
744
+ QA_ind_set1[QA_question_no[i]] = QA_set1[i].strip()
745
+ QA_ind_set2[QA_question_no[i]] = QA_set2[i].strip()
746
+ QA_ind_set3[QA_question_no[i]] = QA_set3[i].strip()
747
+ Mark_CorrectAnswer_QA[QA_question_no[i]] = C_QA_Mark_Arr[i]
748
+ Mark_wrongAnswer_QA[QA_question_no[i]] = W_QA_Mark_Arr[i]
749
+
750
+ return Course,GetSetCode_from_Binary,TF_ind_set1, TF_ind_set2, TF_ind_set3, QA_ind_set1, QA_ind_set2, QA_ind_set3, Mark_correctAnswer_TF, Mark_wrongAnswer_TF, Mark_CorrectAnswer_QA, Mark_wrongAnswer_QA
751
+
752
+
753
+
754
+
755
+ def stackImages(imgArray,scale):
756
+ rows = len(imgArray)
757
+ cols = len(imgArray[0])
758
+
759
+ rowsAvailable = isinstance(imgArray[0], (list,np.ndarray))
760
+ width = imgArray[0][0].shape[1]
761
+ height = imgArray[0][0].shape[0]
762
+
763
+ if rowsAvailable:
764
+ for x in range ( 0, rows):
765
+ for y in range(0, cols):
766
+ if imgArray[x][y].shape[:2] == imgArray[0][0].shape [:2]:
767
+ imgArray[x][y] = cv2.resize(imgArray[x][y], (0, 0), None, scale, scale)
768
+ else:
769
+ imgArray[x][y] = cv2.resize(imgArray[x][y], (imgArray[0][0].shape[1], imgArray[0][0].shape[0]), None, scale, scale)
770
+ if len(imgArray[x][y].shape) == 2: imgArray[x][y]= cv2.cvtColor( imgArray[x][y], cv2.COLOR_GRAY2BGR)
771
+ imageBlank = np.zeros((height, width, 3), np.uint8)
772
+ hor = [imageBlank]*rows
773
+ hor_con = [imageBlank]*rows
774
+ for x in range(0, rows):
775
+ hor[x] = np.hstack(imgArray[x])
776
+ ver = np.vstack(hor)
777
+
778
+ else:
779
+ for x in range(0, rows):
780
+ if imgArray[x].shape[:2] == imgArray[0].shape[:2]:
781
+ imgArray[x] = cv2.resize(imgArray[x], (0, 0), None, scale, scale)
782
+ else:
783
+ imgArray[x] = cv2.resize(imgArray[x], (imgArray[0].shape[1], imgArray[0].shape[0]), None,scale, scale)
784
+ if len(imgArray[x].shape) == 2: imgArray[x] = cv2.cvtColor(imgArray[x], cv2.COLOR_GRAY2BGR)
785
+ hor= np.hstack(imgArray)
786
+ ver = hor
787
+ return ver
@@ -0,0 +1,3 @@
1
+ from .OMRutils_v4 import *
2
+
3
+ print("Initializing the OMR package...\nAuthor:Md Nur Kutubul Alam\nemail:alamjhilam@gmail.com")
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.2
2
+ Name: jhOMR_webInterface
3
+ Version: 0.1.0
4
+ Summary: OMR format to be used in KUET. This program supports using of Web Interface making use of fastAPI
5
+ Author: Md Nur Kutubul Alam
6
+ Author-email: alamjhilam@gmail.com
7
+ Requires-Python: >=3.7
8
+ Requires-Dist: numpy
9
+ Requires-Dist: pandas
10
+ Requires-Dist: opencv-python
11
+ Requires-Dist: openpyxl
12
+ Requires-Dist: fastapi
13
+ Requires-Dist: uvicorn
14
+ Requires-Dist: requests
15
+ Requires-Dist: Jinja2
16
+ Requires-Dist: python-multipart
17
+ Dynamic: author
18
+ Dynamic: author-email
19
+ Dynamic: requires-dist
20
+ Dynamic: requires-python
21
+ Dynamic: summary
@@ -0,0 +1,8 @@
1
+ setup.py
2
+ jhOMR_webInterface/OMRutils_v4.py
3
+ jhOMR_webInterface/__init__.py
4
+ jhOMR_webInterface.egg-info/PKG-INFO
5
+ jhOMR_webInterface.egg-info/SOURCES.txt
6
+ jhOMR_webInterface.egg-info/dependency_links.txt
7
+ jhOMR_webInterface.egg-info/requires.txt
8
+ jhOMR_webInterface.egg-info/top_level.txt
@@ -0,0 +1,9 @@
1
+ numpy
2
+ pandas
3
+ opencv-python
4
+ openpyxl
5
+ fastapi
6
+ uvicorn
7
+ requests
8
+ Jinja2
9
+ python-multipart
@@ -0,0 +1 @@
1
+ jhOMR_webInterface
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,24 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name='jhOMR_webInterface',
5
+ version='0.1.0',
6
+ author='Md Nur Kutubul Alam',
7
+ author_email='alamjhilam@gmail.com',
8
+ description='OMR format to be used in KUET. This program supports using of Web Interface making use of fastAPI',
9
+
10
+ packages=find_packages(), # Automatically find the 'jhOMR_webInterface' package
11
+
12
+ install_requires=[
13
+ 'numpy',
14
+ 'pandas',
15
+ 'opencv-python',
16
+ 'openpyxl',
17
+ 'fastapi',
18
+ 'uvicorn',
19
+ 'requests',
20
+ 'Jinja2',
21
+ 'python-multipart'
22
+ ],
23
+ python_requires='>=3.7',
24
+ )