Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Udayraj123
GitHub Repository: Udayraj123/OMRChecker
Path: blob/master/src/core.py
214 views
1
import os
2
from collections import defaultdict
3
from typing import Any
4
5
import cv2
6
import matplotlib.pyplot as plt
7
import numpy as np
8
9
import src.constants as constants
10
from src.logger import logger
11
from src.utils.image import CLAHE_HELPER, ImageUtils
12
from src.utils.interaction import InteractionUtils
13
14
15
class ImageInstanceOps:
16
"""Class to hold fine-tuned utilities for a group of images. One instance for each processing directory."""
17
18
save_img_list: Any = defaultdict(list)
19
20
def __init__(self, tuning_config):
21
super().__init__()
22
self.tuning_config = tuning_config
23
self.save_image_level = tuning_config.outputs.save_image_level
24
25
def apply_preprocessors(self, file_path, in_omr, template):
26
tuning_config = self.tuning_config
27
# resize to conform to template
28
in_omr = ImageUtils.resize_util(
29
in_omr,
30
tuning_config.dimensions.processing_width,
31
tuning_config.dimensions.processing_height,
32
)
33
34
# run pre_processors in sequence
35
for pre_processor in template.pre_processors:
36
in_omr = pre_processor.apply_filter(in_omr, file_path)
37
return in_omr
38
39
def read_omr_response(self, template, image, name, save_dir=None):
40
config = self.tuning_config
41
auto_align = config.alignment_params.auto_align
42
try:
43
img = image.copy()
44
# origDim = img.shape[:2]
45
img = ImageUtils.resize_util(
46
img, template.page_dimensions[0], template.page_dimensions[1]
47
)
48
if img.max() > img.min():
49
img = ImageUtils.normalize_util(img)
50
# Processing copies
51
transp_layer = img.copy()
52
final_marked = img.copy()
53
54
morph = img.copy()
55
self.append_save_img(3, morph)
56
57
if auto_align:
58
# Note: clahe is good for morphology, bad for thresholding
59
morph = CLAHE_HELPER.apply(morph)
60
self.append_save_img(3, morph)
61
# Remove shadows further, make columns/boxes darker (less gamma)
62
morph = ImageUtils.adjust_gamma(
63
morph, config.threshold_params.GAMMA_LOW
64
)
65
# TODO: all numbers should come from either constants or config
66
_, morph = cv2.threshold(morph, 220, 220, cv2.THRESH_TRUNC)
67
morph = ImageUtils.normalize_util(morph)
68
self.append_save_img(3, morph)
69
if config.outputs.show_image_level >= 4:
70
InteractionUtils.show("morph1", morph, 0, 1, config)
71
72
# Move them to data class if needed
73
# Overlay Transparencies
74
alpha = 0.65
75
omr_response = {}
76
multi_marked, multi_roll = 0, 0
77
78
# TODO Make this part useful for visualizing status checks
79
# blackVals=[0]
80
# whiteVals=[255]
81
82
if config.outputs.show_image_level >= 5:
83
all_c_box_vals = {"int": [], "mcq": []}
84
# TODO: simplify this logic
85
q_nums = {"int": [], "mcq": []}
86
87
# Find Shifts for the field_blocks --> Before calculating threshold!
88
if auto_align:
89
# print("Begin Alignment")
90
# Open : erode then dilate
91
v_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 10))
92
morph_v = cv2.morphologyEx(
93
morph, cv2.MORPH_OPEN, v_kernel, iterations=3
94
)
95
_, morph_v = cv2.threshold(morph_v, 200, 200, cv2.THRESH_TRUNC)
96
morph_v = 255 - ImageUtils.normalize_util(morph_v)
97
98
if config.outputs.show_image_level >= 3:
99
InteractionUtils.show(
100
"morphed_vertical", morph_v, 0, 1, config=config
101
)
102
103
# InteractionUtils.show("morph1",morph,0,1,config=config)
104
# InteractionUtils.show("morphed_vertical",morph_v,0,1,config=config)
105
106
self.append_save_img(3, morph_v)
107
108
morph_thr = 60 # for Mobile images, 40 for scanned Images
109
_, morph_v = cv2.threshold(morph_v, morph_thr, 255, cv2.THRESH_BINARY)
110
# kernel best tuned to 5x5 now
111
morph_v = cv2.erode(morph_v, np.ones((5, 5), np.uint8), iterations=2)
112
113
self.append_save_img(3, morph_v)
114
# h_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (10, 2))
115
# morph_h = cv2.morphologyEx(morph, cv2.MORPH_OPEN, h_kernel, iterations=3)
116
# ret, morph_h = cv2.threshold(morph_h,200,200,cv2.THRESH_TRUNC)
117
# morph_h = 255 - normalize_util(morph_h)
118
# InteractionUtils.show("morph_h",morph_h,0,1,config=config)
119
# _, morph_h = cv2.threshold(morph_h,morph_thr,255,cv2.THRESH_BINARY)
120
# morph_h = cv2.erode(morph_h, np.ones((5,5),np.uint8), iterations = 2)
121
if config.outputs.show_image_level >= 3:
122
InteractionUtils.show(
123
"morph_thr_eroded", morph_v, 0, 1, config=config
124
)
125
126
self.append_save_img(6, morph_v)
127
128
# template relative alignment code
129
for field_block in template.field_blocks:
130
s, d = field_block.origin, field_block.dimensions
131
132
match_col, max_steps, align_stride, thk = map(
133
config.alignment_params.get,
134
[
135
"match_col",
136
"max_steps",
137
"stride",
138
"thickness",
139
],
140
)
141
shift, steps = 0, 0
142
while steps < max_steps:
143
left_mean = np.mean(
144
morph_v[
145
s[1] : s[1] + d[1],
146
s[0] + shift - thk : -thk + s[0] + shift + match_col,
147
]
148
)
149
right_mean = np.mean(
150
morph_v[
151
s[1] : s[1] + d[1],
152
s[0]
153
+ shift
154
- match_col
155
+ d[0]
156
+ thk : thk
157
+ s[0]
158
+ shift
159
+ d[0],
160
]
161
)
162
163
# For demonstration purposes-
164
# if(field_block.name == "int1"):
165
# ret = morph_v.copy()
166
# cv2.rectangle(ret,
167
# (s[0]+shift-thk,s[1]),
168
# (s[0]+shift+thk+d[0],s[1]+d[1]),
169
# constants.CLR_WHITE,
170
# 3)
171
# appendSaveImg(6,ret)
172
# print(shift, left_mean, right_mean)
173
left_shift, right_shift = left_mean > 100, right_mean > 100
174
if left_shift:
175
if right_shift:
176
break
177
else:
178
shift -= align_stride
179
else:
180
if right_shift:
181
shift += align_stride
182
else:
183
break
184
steps += 1
185
186
field_block.shift = shift
187
# print("Aligned field_block: ",field_block.name,"Corrected Shift:",
188
# field_block.shift,", dimensions:", field_block.dimensions,
189
# "origin:", field_block.origin,'\n')
190
# print("End Alignment")
191
192
final_align = None
193
if config.outputs.show_image_level >= 2:
194
initial_align = self.draw_template_layout(img, template, shifted=False)
195
final_align = self.draw_template_layout(
196
img, template, shifted=True, draw_qvals=True
197
)
198
# appendSaveImg(4,mean_vals)
199
self.append_save_img(2, initial_align)
200
self.append_save_img(2, final_align)
201
202
if auto_align:
203
final_align = np.hstack((initial_align, final_align))
204
self.append_save_img(5, img)
205
206
# Get mean bubbleValues n other stats
207
all_q_vals, all_q_strip_arrs, all_q_std_vals = [], [], []
208
total_q_strip_no = 0
209
for field_block in template.field_blocks:
210
box_w, box_h = field_block.bubble_dimensions
211
q_std_vals = []
212
for field_block_bubbles in field_block.traverse_bubbles:
213
q_strip_vals = []
214
for pt in field_block_bubbles:
215
# shifted
216
x, y = (pt.x + field_block.shift, pt.y)
217
rect = [y, y + box_h, x, x + box_w]
218
q_strip_vals.append(
219
cv2.mean(img[rect[0] : rect[1], rect[2] : rect[3]])[0]
220
# detectCross(img, rect) ? 100 : 0
221
)
222
q_std_vals.append(round(np.std(q_strip_vals), 2))
223
all_q_strip_arrs.append(q_strip_vals)
224
# _, _, _ = get_global_threshold(q_strip_vals, "QStrip Plot",
225
# plot_show=False, sort_in_plot=True)
226
# hist = getPlotImg()
227
# InteractionUtils.show("QStrip "+field_block_bubbles[0].field_label, hist, 0, 1,config=config)
228
all_q_vals.extend(q_strip_vals)
229
# print(total_q_strip_no, field_block_bubbles[0].field_label, q_std_vals[len(q_std_vals)-1])
230
total_q_strip_no += 1
231
all_q_std_vals.extend(q_std_vals)
232
233
global_std_thresh, _, _ = self.get_global_threshold(
234
all_q_std_vals
235
) # , "Q-wise Std-dev Plot", plot_show=True, sort_in_plot=True)
236
# plt.show()
237
# hist = getPlotImg()
238
# InteractionUtils.show("StdHist", hist, 0, 1,config=config)
239
240
# Note: Plotting takes Significant times here --> Change Plotting args
241
# to support show_image_level
242
# , "Mean Intensity Histogram",plot_show=True, sort_in_plot=True)
243
global_thr, _, _ = self.get_global_threshold(all_q_vals, looseness=4)
244
245
logger.info(
246
f"Thresholding: \tglobal_thr: {round(global_thr, 2)} \tglobal_std_THR: {round(global_std_thresh, 2)}\t{'(Looks like a Xeroxed OMR)' if (global_thr == 255) else ''}"
247
)
248
# plt.show()
249
# hist = getPlotImg()
250
# InteractionUtils.show("StdHist", hist, 0, 1,config=config)
251
252
# if(config.outputs.show_image_level>=1):
253
# hist = getPlotImg()
254
# InteractionUtils.show("Hist", hist, 0, 1,config=config)
255
# appendSaveImg(4,hist)
256
# appendSaveImg(5,hist)
257
# appendSaveImg(2,hist)
258
259
per_omr_threshold_avg, total_q_strip_no, total_q_box_no = 0, 0, 0
260
for field_block in template.field_blocks:
261
block_q_strip_no = 1
262
box_w, box_h = field_block.bubble_dimensions
263
shift = field_block.shift
264
s, d = field_block.origin, field_block.dimensions
265
key = field_block.name[:3]
266
# cv2.rectangle(final_marked,(s[0]+shift,s[1]),(s[0]+shift+d[0],
267
# s[1]+d[1]),CLR_BLACK,3)
268
for field_block_bubbles in field_block.traverse_bubbles:
269
# All Black or All White case
270
no_outliers = all_q_std_vals[total_q_strip_no] < global_std_thresh
271
# print(total_q_strip_no, field_block_bubbles[0].field_label,
272
# all_q_std_vals[total_q_strip_no], "no_outliers:", no_outliers)
273
per_q_strip_threshold = self.get_local_threshold(
274
all_q_strip_arrs[total_q_strip_no],
275
global_thr,
276
no_outliers,
277
f"Mean Intensity Histogram for {key}.{field_block_bubbles[0].field_label}.{block_q_strip_no}",
278
config.outputs.show_image_level >= 6,
279
)
280
# print(field_block_bubbles[0].field_label,key,block_q_strip_no, "THR: ",
281
# round(per_q_strip_threshold,2))
282
per_omr_threshold_avg += per_q_strip_threshold
283
284
# Note: Little debugging visualization - view the particular Qstrip
285
# if(
286
# 0
287
# # or "q17" in (field_block_bubbles[0].field_label)
288
# # or (field_block_bubbles[0].field_label+str(block_q_strip_no))=="q15"
289
# ):
290
# st, end = qStrip
291
# InteractionUtils.show("QStrip: "+key+"-"+str(block_q_strip_no),
292
# img[st[1] : end[1], st[0]+shift : end[0]+shift],0,config=config)
293
294
# TODO: get rid of total_q_box_no
295
detected_bubbles = []
296
for bubble in field_block_bubbles:
297
bubble_is_marked = (
298
per_q_strip_threshold > all_q_vals[total_q_box_no]
299
)
300
total_q_box_no += 1
301
if bubble_is_marked:
302
detected_bubbles.append(bubble)
303
x, y, field_value = (
304
bubble.x + field_block.shift,
305
bubble.y,
306
bubble.field_value,
307
)
308
cv2.rectangle(
309
final_marked,
310
(int(x + box_w / 12), int(y + box_h / 12)),
311
(
312
int(x + box_w - box_w / 12),
313
int(y + box_h - box_h / 12),
314
),
315
constants.CLR_DARK_GRAY,
316
3,
317
)
318
319
cv2.putText(
320
final_marked,
321
str(field_value),
322
(x, y),
323
cv2.FONT_HERSHEY_SIMPLEX,
324
constants.TEXT_SIZE,
325
(20, 20, 10),
326
int(1 + 3.5 * constants.TEXT_SIZE),
327
)
328
else:
329
cv2.rectangle(
330
final_marked,
331
(int(x + box_w / 10), int(y + box_h / 10)),
332
(
333
int(x + box_w - box_w / 10),
334
int(y + box_h - box_h / 10),
335
),
336
constants.CLR_GRAY,
337
-1,
338
)
339
340
for bubble in detected_bubbles:
341
field_label, field_value = (
342
bubble.field_label,
343
bubble.field_value,
344
)
345
# Only send rolls multi-marked in the directory
346
multi_marked_local = field_label in omr_response
347
omr_response[field_label] = (
348
(omr_response[field_label] + field_value)
349
if multi_marked_local
350
else field_value
351
)
352
# TODO: generalize this into identifier
353
# multi_roll = multi_marked_local and "Roll" in str(q)
354
multi_marked = multi_marked or multi_marked_local
355
356
if len(detected_bubbles) == 0:
357
field_label = field_block_bubbles[0].field_label
358
omr_response[field_label] = field_block.empty_val
359
360
if config.outputs.show_image_level >= 5:
361
if key in all_c_box_vals:
362
q_nums[key].append(f"{key[:2]}_c{str(block_q_strip_no)}")
363
all_c_box_vals[key].append(
364
all_q_strip_arrs[total_q_strip_no]
365
)
366
367
block_q_strip_no += 1
368
total_q_strip_no += 1
369
# /for field_block
370
371
per_omr_threshold_avg /= total_q_strip_no
372
per_omr_threshold_avg = round(per_omr_threshold_avg, 2)
373
# Translucent
374
cv2.addWeighted(
375
final_marked, alpha, transp_layer, 1 - alpha, 0, final_marked
376
)
377
# Box types
378
if config.outputs.show_image_level >= 6:
379
# plt.draw()
380
f, axes = plt.subplots(len(all_c_box_vals), sharey=True)
381
f.canvas.manager.set_window_title(name)
382
ctr = 0
383
type_name = {
384
"int": "Integer",
385
"mcq": "MCQ",
386
"med": "MED",
387
"rol": "Roll",
388
}
389
for k, boxvals in all_c_box_vals.items():
390
axes[ctr].title.set_text(type_name[k] + " Type")
391
axes[ctr].boxplot(boxvals)
392
# thrline=axes[ctr].axhline(per_omr_threshold_avg,color='red',ls='--')
393
# thrline.set_label("Average THR")
394
axes[ctr].set_ylabel("Intensity")
395
axes[ctr].set_xticklabels(q_nums[k])
396
# axes[ctr].legend()
397
ctr += 1
398
# imshow will do the waiting
399
plt.tight_layout(pad=0.5)
400
plt.show()
401
402
if config.outputs.show_image_level >= 3 and final_align is not None:
403
final_align = ImageUtils.resize_util_h(
404
final_align, int(config.dimensions.display_height)
405
)
406
# [final_align.shape[1],0])
407
InteractionUtils.show(
408
"Template Alignment Adjustment", final_align, 0, 0, config=config
409
)
410
411
if config.outputs.save_detections and save_dir is not None:
412
if multi_roll:
413
save_dir = save_dir.joinpath("_MULTI_")
414
image_path = str(save_dir.joinpath(name))
415
ImageUtils.save_img(image_path, final_marked)
416
417
self.append_save_img(2, final_marked)
418
419
if save_dir is not None:
420
for i in range(config.outputs.save_image_level):
421
self.save_image_stacks(i + 1, name, save_dir)
422
423
return omr_response, final_marked, multi_marked, multi_roll
424
425
except Exception as e:
426
raise e
427
428
@staticmethod
429
def draw_template_layout(img, template, shifted=True, draw_qvals=False, border=-1):
430
img = ImageUtils.resize_util(
431
img, template.page_dimensions[0], template.page_dimensions[1]
432
)
433
final_align = img.copy()
434
for field_block in template.field_blocks:
435
s, d = field_block.origin, field_block.dimensions
436
box_w, box_h = field_block.bubble_dimensions
437
shift = field_block.shift
438
if shifted:
439
cv2.rectangle(
440
final_align,
441
(s[0] + shift, s[1]),
442
(s[0] + shift + d[0], s[1] + d[1]),
443
constants.CLR_BLACK,
444
3,
445
)
446
else:
447
cv2.rectangle(
448
final_align,
449
(s[0], s[1]),
450
(s[0] + d[0], s[1] + d[1]),
451
constants.CLR_BLACK,
452
3,
453
)
454
for field_block_bubbles in field_block.traverse_bubbles:
455
for pt in field_block_bubbles:
456
x, y = (pt.x + field_block.shift, pt.y) if shifted else (pt.x, pt.y)
457
cv2.rectangle(
458
final_align,
459
(int(x + box_w / 10), int(y + box_h / 10)),
460
(int(x + box_w - box_w / 10), int(y + box_h - box_h / 10)),
461
constants.CLR_GRAY,
462
border,
463
)
464
if draw_qvals:
465
rect = [y, y + box_h, x, x + box_w]
466
cv2.putText(
467
final_align,
468
f"{int(cv2.mean(img[rect[0] : rect[1], rect[2] : rect[3]])[0])}",
469
(rect[2] + 2, rect[0] + (box_h * 2) // 3),
470
cv2.FONT_HERSHEY_SIMPLEX,
471
0.6,
472
constants.CLR_BLACK,
473
2,
474
)
475
if shifted:
476
text_in_px = cv2.getTextSize(
477
field_block.name, cv2.FONT_HERSHEY_SIMPLEX, constants.TEXT_SIZE, 4
478
)
479
cv2.putText(
480
final_align,
481
field_block.name,
482
(int(s[0] + d[0] - text_in_px[0][0]), int(s[1] - text_in_px[0][1])),
483
cv2.FONT_HERSHEY_SIMPLEX,
484
constants.TEXT_SIZE,
485
constants.CLR_BLACK,
486
4,
487
)
488
return final_align
489
490
def get_global_threshold(
491
self,
492
q_vals_orig,
493
plot_title=None,
494
plot_show=True,
495
sort_in_plot=True,
496
looseness=1,
497
):
498
"""
499
Note: Cannot assume qStrip has only-gray or only-white bg
500
(in which case there is only one jump).
501
So there will be either 1 or 2 jumps.
502
1 Jump :
503
......
504
||||||
505
|||||| <-- risky THR
506
|||||| <-- safe THR
507
....||||||
508
||||||||||
509
510
2 Jumps :
511
......
512
|||||| <-- wrong THR
513
....||||||
514
|||||||||| <-- safe THR
515
..||||||||||
516
||||||||||||
517
518
The abstract "First LARGE GAP" is perfect for this.
519
Current code is considering ONLY TOP 2 jumps(>= MIN_GAP) to be big,
520
gives the smaller one
521
522
"""
523
config = self.tuning_config
524
PAGE_TYPE_FOR_THRESHOLD, MIN_JUMP, JUMP_DELTA = map(
525
config.threshold_params.get,
526
[
527
"PAGE_TYPE_FOR_THRESHOLD",
528
"MIN_JUMP",
529
"JUMP_DELTA",
530
],
531
)
532
533
global_default_threshold = (
534
constants.GLOBAL_PAGE_THRESHOLD_WHITE
535
if PAGE_TYPE_FOR_THRESHOLD == "white"
536
else constants.GLOBAL_PAGE_THRESHOLD_BLACK
537
)
538
539
# Sort the Q bubbleValues
540
# TODO: Change var name of q_vals
541
q_vals = sorted(q_vals_orig)
542
# Find the FIRST LARGE GAP and set it as threshold:
543
ls = (looseness + 1) // 2
544
l = len(q_vals) - ls
545
max1, thr1 = MIN_JUMP, global_default_threshold
546
for i in range(ls, l):
547
jump = q_vals[i + ls] - q_vals[i - ls]
548
if jump > max1:
549
max1 = jump
550
thr1 = q_vals[i - ls] + jump / 2
551
552
# NOTE: thr2 is deprecated, thus is JUMP_DELTA
553
# Make use of the fact that the JUMP_DELTA(Vertical gap ofc) between
554
# values at detected jumps would be atleast 20
555
max2, thr2 = MIN_JUMP, global_default_threshold
556
# Requires atleast 1 gray box to be present (Roll field will ensure this)
557
for i in range(ls, l):
558
jump = q_vals[i + ls] - q_vals[i - ls]
559
new_thr = q_vals[i - ls] + jump / 2
560
if jump > max2 and abs(thr1 - new_thr) > JUMP_DELTA:
561
max2 = jump
562
thr2 = new_thr
563
# global_thr = min(thr1,thr2)
564
global_thr, j_low, j_high = thr1, thr1 - max1 // 2, thr1 + max1 // 2
565
566
# # For normal images
567
# thresholdRead = 116
568
# if(thr1 > thr2 and thr2 > thresholdRead):
569
# print("Note: taking safer thr line.")
570
# global_thr, j_low, j_high = thr2, thr2 - max2//2, thr2 + max2//2
571
572
if plot_title:
573
_, ax = plt.subplots()
574
ax.bar(range(len(q_vals_orig)), q_vals if sort_in_plot else q_vals_orig)
575
ax.set_title(plot_title)
576
thrline = ax.axhline(global_thr, color="green", ls="--", linewidth=5)
577
thrline.set_label("Global Threshold")
578
thrline = ax.axhline(thr2, color="red", ls=":", linewidth=3)
579
thrline.set_label("THR2 Line")
580
# thrline=ax.axhline(j_low,color='red',ls='-.', linewidth=3)
581
# thrline=ax.axhline(j_high,color='red',ls='-.', linewidth=3)
582
# thrline.set_label("Boundary Line")
583
# ax.set_ylabel("Mean Intensity")
584
ax.set_ylabel("Values")
585
ax.set_xlabel("Position")
586
ax.legend()
587
if plot_show:
588
plt.title(plot_title)
589
plt.show()
590
591
return global_thr, j_low, j_high
592
593
def get_local_threshold(
594
self, q_vals, global_thr, no_outliers, plot_title=None, plot_show=True
595
):
596
"""
597
TODO: Update this documentation too-
598
//No more - Assumption : Colwise background color is uniformly gray or white,
599
but not alternating. In this case there is atmost one jump.
600
601
0 Jump :
602
<-- safe THR?
603
.......
604
...|||||||
605
|||||||||| <-- safe THR?
606
// How to decide given range is above or below gray?
607
-> global q_vals shall absolutely help here. Just run same function
608
on total q_vals instead of colwise _//
609
How to decide it is this case of 0 jumps
610
611
1 Jump :
612
......
613
||||||
614
|||||| <-- risky THR
615
|||||| <-- safe THR
616
....||||||
617
||||||||||
618
619
"""
620
config = self.tuning_config
621
# Sort the Q bubbleValues
622
q_vals = sorted(q_vals)
623
624
# Small no of pts cases:
625
# base case: 1 or 2 pts
626
if len(q_vals) < 3:
627
thr1 = (
628
global_thr
629
if np.max(q_vals) - np.min(q_vals) < config.threshold_params.MIN_GAP
630
else np.mean(q_vals)
631
)
632
else:
633
# qmin, qmax, qmean, qstd = round(np.min(q_vals),2), round(np.max(q_vals),2),
634
# round(np.mean(q_vals),2), round(np.std(q_vals),2)
635
# GVals = [round(abs(q-qmean),2) for q in q_vals]
636
# gmean, gstd = round(np.mean(GVals),2), round(np.std(GVals),2)
637
# # DISCRETION: Pretty critical factor in reading response
638
# # Doesn't work well for small number of values.
639
# DISCRETION = 2.7 # 2.59 was closest hit, 3.0 is too far
640
# L2MaxGap = round(max([abs(g-gmean) for g in GVals]),2)
641
# if(L2MaxGap > DISCRETION*gstd):
642
# no_outliers = False
643
644
# # ^Stackoverflow method
645
# print(field_label, no_outliers,"qstd",round(np.std(q_vals),2), "gstd", gstd,
646
# "Gaps in gvals",sorted([round(abs(g-gmean),2) for g in GVals],reverse=True),
647
# '\t',round(DISCRETION*gstd,2), L2MaxGap)
648
649
# else:
650
# Find the LARGEST GAP and set it as threshold: //(FIRST LARGE GAP)
651
l = len(q_vals) - 1
652
max1, thr1 = config.threshold_params.MIN_JUMP, 255
653
for i in range(1, l):
654
jump = q_vals[i + 1] - q_vals[i - 1]
655
if jump > max1:
656
max1 = jump
657
thr1 = q_vals[i - 1] + jump / 2
658
# print(field_label,q_vals,max1)
659
660
confident_jump = (
661
config.threshold_params.MIN_JUMP
662
+ config.threshold_params.CONFIDENT_SURPLUS
663
)
664
# If not confident, then only take help of global_thr
665
if max1 < confident_jump:
666
if no_outliers:
667
# All Black or All White case
668
thr1 = global_thr
669
else:
670
# TODO: Low confidence parameters here
671
pass
672
673
# if(thr1 == 255):
674
# print("Warning: threshold is unexpectedly 255! (Outlier Delta issue?)",plot_title)
675
676
# Make a common plot function to show local and global thresholds
677
if plot_show and plot_title is not None:
678
_, ax = plt.subplots()
679
ax.bar(range(len(q_vals)), q_vals)
680
thrline = ax.axhline(thr1, color="green", ls=("-."), linewidth=3)
681
thrline.set_label("Local Threshold")
682
thrline = ax.axhline(global_thr, color="red", ls=":", linewidth=5)
683
thrline.set_label("Global Threshold")
684
ax.set_title(plot_title)
685
ax.set_ylabel("Bubble Mean Intensity")
686
ax.set_xlabel("Bubble Number(sorted)")
687
ax.legend()
688
# TODO append QStrip to this plot-
689
# appendSaveImg(6,getPlotImg())
690
if plot_show:
691
plt.show()
692
return thr1
693
694
def append_save_img(self, key, img):
695
if self.save_image_level >= int(key):
696
self.save_img_list[key].append(img.copy())
697
698
def save_image_stacks(self, key, filename, save_dir):
699
config = self.tuning_config
700
if self.save_image_level >= int(key) and self.save_img_list[key] != []:
701
name = os.path.splitext(filename)[0]
702
result = np.hstack(
703
tuple(
704
[
705
ImageUtils.resize_util_h(img, config.dimensions.display_height)
706
for img in self.save_img_list[key]
707
]
708
)
709
)
710
result = ImageUtils.resize_util(
711
result,
712
min(
713
len(self.save_img_list[key]) * config.dimensions.display_width // 3,
714
int(config.dimensions.display_width * 2.5),
715
),
716
)
717
ImageUtils.save_img(f"{save_dir}stack/{name}_{str(key)}_stack.jpg", result)
718
719
def reset_all_save_img(self):
720
for i in range(self.save_image_level):
721
self.save_img_list[i + 1] = []
722
723