def process_wrapper(self, **kwargs): """ Keep countours near ROIs: Keep big contours inside a series of ROIs. Small contours inside ROIs may be added on conditions. Contours outsside ROIs may be added to root contours if close enough Real time: False Keyword Arguments (in parentheses, argument name): * Activate tool (enabled): Toggle whether or not tool is active * Name of ROI to be used (roi_names): Operation will only be applied inside of ROI * ROI selection mode (roi_selection_mode): * Maximum distance to ROI (init_max_distance): Maximum distance to add a contour on initialization phase. If distance is >0, ROIs will dilated with a kernel of size distance. * Minimum contour size (init_min_size): Minimum accepted size for a contour on initialization phase * Delete all contours smaller than (delete_all_bellow): All contours below this size will be permanently ignored. The more smaller contours are delete, the faster the algorithm * Merge distance for root contours (root_merge_distance): * Aggregate small contours inside ROIs distance (small_contours_distance_tolerance): Aggregate small contours inside ROIs if closer than x to any root contour. Any aggregated contour is considered as a root one. * Aggregate unknown contours distance (unk_contours_distance_tolerance): Aggregate unknown contours if closer than x to any root contour. Any aggregated contour is considered as a root one. """ wrapper = self.init_wrapper(**kwargs) if wrapper is None: return False res = False try: if self.get_value_of("enabled") == 1: img = wrapper.current_image mask = self.get_mask() if mask is None: logger.error(f"FAIL {self.name}: mask must be initialized") return # Get ROIs as mask rois = self.get_ipt_roi( wrapper=wrapper, roi_names=self.get_value_of("roi_names").replace( " ", "").split(","), selection_mode=self.get_value_of("roi_selection_mode"), ) if rois: rois_mask = np.zeros_like(mask) for roi in rois: rois_mask = roi.draw_to(dst_img=rois_mask, line_width=-1, color=255) else: self.result = mask logger.error( f"Warning {self.name}: must have at least one ROI") res = True return wrapper.store_image(rois_mask, "rois_as_mask") # Get source contours contours = ipc.get_contours( mask=mask, retrieve_mode=cv2.RETR_LIST, method=cv2.CHAIN_APPROX_SIMPLE, ) # Dilate ROIs init_max_distance = self.get_value_of("init_max_distance") if init_max_distance > 0: rois_mask = wrapper.dilate( image=rois_mask, kernel_size=ipc.ensure_odd(i=init_max_distance, min_val=3), ) wrapper.store_image(rois_mask, "dilated_rois") # Remove smaller contours delete_all_bellow = self.get_value_of("delete_all_bellow") if delete_all_bellow > 0: fnt = (cv2.FONT_HERSHEY_SIMPLEX, 0.6) small_img = mask.copy() small_img = np.dstack((small_img, small_img, small_img)) for cnt in contours: area_ = cv2.contourArea(cnt) if area_ < delete_all_bellow: # Delete cv2.drawContours(mask, [cnt], 0, (0, 0, 0), -1) # Print debug image x, y, w, h = cv2.boundingRect(cnt) x += w // 2 - 10 y += h // 2 cv2.drawContours(small_img, [cnt], -1, ipc.C_RED, -1) cv2.putText( small_img, f"Area: {area_}", (x, y), fnt[0], fnt[1], ipc.C_FUCHSIA, 2, ) else: cv2.drawContours(small_img, [cnt], -1, ipc.C_GREEN, -1) wrapper.store_image(small_img, "small_removed_mask") # Find root contours root_cnts = [] small_cnts = [] unk_cnts = [] init_min_size = self.get_value_of("init_min_size") tmp_img = np.zeros_like(mask) for i, cnt in enumerate(contours): cv2.drawContours( image=tmp_img, contours=[cnt], contourIdx=0, color=255, thickness=-1, ) intersection = cv2.bitwise_and(tmp_img, rois_mask) cv2.drawContours( image=tmp_img, contours=[cnt], contourIdx=0, color=0, thickness=-1, ) if np.sum(intersection[intersection != 0]) > 0: if cv2.contourArea(cnt) > init_min_size: root_cnts.append(cnt) else: small_cnts.append(cnt) else: unk_cnts.append(cnt) # approx_eps = self.get_value_of("approx_eps") / 10000 cnt_approx = { "root": [{ "cnt": cnt, "label": i } for i, cnt in enumerate(root_cnts)], "small": [{ "cnt": cnt, "label": i + len(root_cnts) } for i, cnt in enumerate(small_cnts)], "unk": [{ "cnt": cnt, "label": i + len(root_cnts) + len(small_cnts) } for i, cnt in enumerate(unk_cnts)], "discarded": [], } cnt_data = [ { "name": "root", "start_color": (0, 50, 0), "stop_color": (0, 255, 0), "print_label": True, }, { "name": "small", "start_color": (50, 0, 0), "stop_color": (255, 0, 0), "print_label": False, }, { "name": "unk", "start_color": (0, 0, 50), "stop_color": (0, 0, 255), "print_label": False, }, ] bck_img = wrapper.current_image for roi in rois: bck_img = roi.draw_to(dst_img=bck_img, line_width=2, color=ipc.C_WHITE) self.draw_contours( canvas=bck_img, contours=cnt_approx, image_name="contours_after_init", contours_data=cnt_data, ) # Merge root contours by label root_merge_distance = self.get_value_of("root_merge_distance") stable = False step = 1 while not stable and step < 100: stable = True for left_index, left in enumerate(cnt_approx["root"]): for right_index, right in enumerate( cnt_approx["root"]): if left_index == right_index: continue if (left["label"] != right["label"] and wrapper.contours_min_distance( left["cnt"], right["cnt"]) < root_merge_distance): right["label"] = left["label"] stable = False self.draw_contours( canvas=wrapper.current_image, contours=cnt_approx, image_name=f"merging_root_{step}", contours_data=cnt_data, ) step += 1 self.draw_contours( canvas=wrapper.current_image, contours=cnt_approx, image_name="root_labels_merged", contours_data=cnt_data, ) # Merge small contours small_contours_distance_tolerance = self.get_value_of( "small_contours_distance_tolerance") stable = False step = 1 while not stable and step < 100: stable = True for root in cnt_approx["root"]: i = 0 while i < len(cnt_approx["small"]): small = cnt_approx["small"][i] if (wrapper.contours_min_distance( root["cnt"], small["cnt"]) < small_contours_distance_tolerance): new_root = cnt_approx["small"].pop(i) new_root["label"] = root["label"] cnt_approx["root"].append(new_root) stable = False else: i += 1 if not stable: self.draw_contours( canvas=wrapper.current_image, contours=cnt_approx, image_name=f"merging_small_{step}", contours_data=cnt_data, ) step += 1 self.draw_contours( canvas=wrapper.current_image, contours=cnt_approx, image_name="small_labels_merged", contours_data=cnt_data, ) # Merge unknown contours unk_contours_distance_tolerance = self.get_value_of( "unk_contours_distance_tolerance") stable = False step = 1 while not stable and step < 100: stable = True for root in cnt_approx["root"]: i = 0 while i < len(cnt_approx["unk"]): unk = cnt_approx["unk"][i] if (wrapper.contours_min_distance( root["cnt"], unk["cnt"]) < unk_contours_distance_tolerance): new_root = cnt_approx["unk"].pop(i) new_root["label"] = root["label"] cnt_approx["root"].append(new_root) stable = False else: i += 1 if not stable: self.draw_contours( canvas=wrapper.current_image, contours=cnt_approx, image_name=f"merging_unk_{step}", contours_data=cnt_data, ) step += 1 self.demo_image = self.draw_contours( canvas=wrapper.current_image, contours=cnt_approx, image_name="unk_labels_merged", contours_data=cnt_data, ) # Clean mask contours = ipc.get_contours( mask=mask, retrieve_mode=cv2.RETR_LIST, method=cv2.CHAIN_APPROX_SIMPLE, ) src_image = wrapper.current_image for cnt in contours: is_good_one = False for root in cnt_approx["root"]: if wrapper.contours_min_distance(root["cnt"], cnt) <= 0: is_good_one = True break if is_good_one: cv2.drawContours(src_image, [cnt], 0, (0, 255, 0), 2) else: cv2.drawContours(src_image, [cnt], 0, (0, 0, 255), 2) cv2.drawContours(mask, [cnt], 0, 0, -1) wrapper.store_image(src_image, "kcnr_img_wth_tagged_cnt") wrapper.store_image(mask, "kcnr_final") self.result = mask res = True else: wrapper.store_image(wrapper.current_image, "current_image") res = True except Exception as e: res = False wrapper.error_holder.add_error( new_error_text=f'Failed to process {self. name}: "{repr(e)}"', new_error_level=35, target_logger=logger, ) else: pass finally: return res
def process_wrapper(self, **kwargs): """ Filter contour by size: 'Keep or descard contours according to their size Real time: False Keyword Arguments (in parentheses, argument name): * Activate tool (enabled): Toggle whether or not tool is active * Lower bound limit (min_threshold): Only contours bigger than lower limit bound will be kept * Upper bound limit (max_threshold): Only contours smaller than lower limit bound will be kept * Name of ROI to be used (roi_names): Operation will only be applied inside of ROI * ROI selection mode (roi_selection_mode): """ wrapper = self.init_wrapper(**kwargs) if wrapper is None: return False res = False try: if self.get_value_of("enabled") == 1: mask = self.get_mask() if mask is None: logger.error(f"FAIL {self.name}: mask must be initialized") return lt, ut = self.get_value_of("min_threshold"), self.get_value_of( "max_threshold") # Get source contours contours = [ c for c in ipc.get_contours( mask=mask, retrieve_mode=cv2.RETR_LIST, method=cv2.CHAIN_APPROX_SIMPLE, ) if cv2.contourArea(c, True) < 0 ] contours.sort(key=lambda x: cv2.contourArea(x), reverse=True) colors = ipc.build_color_steps(step_count=len(contours)) dbg_img = np.dstack((np.zeros_like(mask), np.zeros_like(mask), np.zeros_like(mask))) for clr, cnt in zip(colors, contours): cv2.drawContours(dbg_img, [cnt], 0, clr, -1) dbg_img = np.dstack(( cv2.bitwise_and(dbg_img[:, :, 0], mask), cv2.bitwise_and(dbg_img[:, :, 1], mask), cv2.bitwise_and(dbg_img[:, :, 2], mask), )) wrapper.store_image( image=dbg_img, text="all_contours", ) fnt = (cv2.FONT_HERSHEY_SIMPLEX, 0.6) for cnt in contours: area_ = cv2.contourArea(cnt) x, y, w, h = cv2.boundingRect(cnt) x += w // 2 - 10 y += h // 2 if area_ > 0: cv2.putText( dbg_img, f"{area_}", (x, y), fnt[0], fnt[1], ipc.C_WHITE, 2, ) wrapper.store_image( image=dbg_img, text="all_contours_with_sizes", ) dbg_img = np.dstack((np.zeros_like(mask), np.zeros_like(mask), np.zeros_like(mask))) out_mask = np.zeros_like(mask) # Discarded contours size_cnts = np.dstack( (np.zeros_like(mask), np.zeros_like(mask), np.zeros_like(mask))) for cnt in contours: area_ = cv2.contourArea(cnt) if area_ < lt: cv2.drawContours(size_cnts, [cnt], 0, ipc.C_RED, -1) elif area_ > ut: cv2.drawContours(size_cnts, [cnt], 0, ipc.C_BLUE, -1) else: cv2.drawContours(size_cnts, [cnt], 0, ipc.C_WHITE, -1) wrapper.store_image(image=size_cnts, text="cnts_by_size") # Discarded contours size_cnts = np.dstack( (np.zeros_like(mask), np.zeros_like(mask), np.zeros_like(mask))) for cnt in sorted(contours, key=lambda x: cv2.contourArea(x), reverse=True): area_ = cv2.contourArea(cnt) if area_ < lt: cv2.drawContours(size_cnts, [cnt], 0, ipc.C_RED, -1) elif area_ > ut: cv2.drawContours(size_cnts, [cnt], 0, ipc.C_BLUE, -1) else: cv2.drawContours(size_cnts, [cnt], 0, ipc.C_WHITE, -1) wrapper.store_image(image=size_cnts, text="cnts_by_size_reversed") for cnt in contours: area_ = cv2.contourArea(cnt) if not (lt < area_ < ut): cv2.drawContours(dbg_img, [cnt], 0, ipc.C_RED, -1) # Discarded contours borders for cnt in contours: area_ = cv2.contourArea(cnt) if not (lt < area_ < ut): cv2.drawContours(dbg_img, [cnt], 0, ipc.C_MAROON, 4) # Kept contours for cnt in contours: area_ = cv2.contourArea(cnt) if lt < area_ < ut: cv2.drawContours(out_mask, [cnt], 0, 255, -1) cv2.drawContours(dbg_img, [cnt], 0, ipc.C_GREEN, -1) else: cv2.drawContours(out_mask, [cnt], 0, 0, -1) cv2.drawContours(dbg_img, [cnt], 0, ipc.C_RED, -1) dbg_img = np.dstack(( cv2.bitwise_and(dbg_img[:, :, 0], mask), cv2.bitwise_and(dbg_img[:, :, 1], mask), cv2.bitwise_and(dbg_img[:, :, 2], mask), )) # Discarded sizes for cnt in contours: area_ = cv2.contourArea(cnt) if not (lt < area_ < ut): x, y, w, h = cv2.boundingRect(cnt) x += w // 2 - 10 y += h // 2 cv2.putText( dbg_img, f"{area_}", (x, y), fnt[0], fnt[1], ipc.C_BLUE, thickness=2, ) # Kept sizes for cnt in contours: area_ = cv2.contourArea(cnt) if lt < area_ < ut: x, y, w, h = cv2.boundingRect(cnt) x += w // 2 - 10 y += h // 2 cv2.putText( dbg_img, f"{area_}", (x, y), fnt[0], fnt[1], ipc.C_BLUE, thickness=2, ) out_mask = cv2.bitwise_and( out_mask, mask, ) # Apply ROIs if needed rois = self.get_ipt_roi( wrapper=wrapper, roi_names=self.get_value_of("roi_names").replace( " ", "").split(","), selection_mode=self.get_value_of("roi_selection_mode"), ) if rois: untouched_mask = regions.delete_rois(rois=rois, image=self.get_mask()) self.result = cv2.bitwise_or( untouched_mask, regions.keep_rois(rois=rois, image=out_mask)) self.demo_image = cv2.bitwise_or( dbg_img, np.dstack( (untouched_mask, untouched_mask, untouched_mask)), ) else: self.result = out_mask self.demo_image = dbg_img wrapper.store_image(image=self.result, text="filtered_contours") wrapper.store_image(image=self.demo_image, text="tagged_contours") res = True else: wrapper.store_image(wrapper.current_image, "current_image") res = True except Exception as e: res = False logger.exception( f"Filter contour by size FAILED, exception: {repr(e)}") else: pass finally: return res
def process_wrapper(self, **kwargs): """ Smooth contours: 'Smooth contours Real time: True Keyword Arguments (in parentheses, argument name): * Activate tool (enabled): Toggle whether or not tool is active * Degree of spline curve (spline_degree): Degree of the spline. Cubic splines are recommended. Even values of k should be avoided especially with a small s-value. 1 <= k <= 5, default is 3. * Smoothing condition (smoothing): Value will be divide by 10""" wrapper = self.init_wrapper(**kwargs) if wrapper is None: return False res = False try: if self.get_value_of("enabled") == 1: img = wrapper.current_image mask = self.get_mask() if mask is None: logger.error( "Failure Smooth contours: mask must be initialized") return # Get source contours contours = [ c for c in ipc.get_contours( mask=mask, retrieve_mode=cv2.RETR_LIST, method=cv2.CHAIN_APPROX_SIMPLE, ) if (cv2.contourArea(c, True) < 0) ] contours.sort(key=lambda x: cv2.contourArea(x), reverse=True) output = mask.copy() for contour in contours: x, y = contour.T # Convert from np arrays to normal arrays tck, u, = splprep( [x.tolist()[0], y.tolist()[0]], u=None, k=ipc.ensure_odd(self.get_value_of("spline_degree")), s=self.get_value_of("smoothing") / 10, per=1, ) u_new = np.linspace(u.min(), u.max(), 1000) x_new, y_new = splev(u_new, tck, der=0) cv2.drawContours( output, [ np.asarray( [[[int(i[0]), int(i[1])]] for i in zip(x_new, y_new)], dtype=np.int32, ) ], 0, 255, -1, ) self.result = output # Write your code here wrapper.store_image(output, "current_image") res = True else: wrapper.store_image(wrapper.current_image, "current_image") res = True except Exception as e: res = False logger.error(f"Smooth contours FAILED, exception: {repr(e)}") else: pass finally: return res
def process_wrapper(self, **kwargs): """ Split overlapped ellipses: Split overlapped ellipses. Three methods are available: Concave points detection, Hough circle detection and extrapolation from median area. Real time: True Keyword Arguments (in parentheses, argument name): * Activate tool (enabled): Toggle whether or not tool is active * Use concave detection method (concave): * Use Hough detection method (hough): * Use median area detection method (median): * Approximation factor (approx_factor): * Debug font scale (dbg_font_scale): * Debug font thickness (dbg_font_thickness): * Split object if size is over (min_size_to_split): * Residue size (residue_size): Consider all object below this size as noise when analysing objects * Minimal radius to consider (min_radius): All circles smaller than this will be ignored * Maximal radius to consider (max_radius): All circles bigger than this will be ignored * Minimum distance between two circles (min_distance): Remove circles that are too close * Radius granularity (step_radius): Steps for scanning radius --------------""" wrapper = self.init_wrapper(**kwargs) if wrapper is None: return False res = False try: if self.get_value_of("enabled") == 1: method = self.get_value_of("method") mask = self.get_mask() if mask is None: logger.error( "Failure Split overlapped ellipses: mask must be initialized" ) return # Get source contours contours = [ c for c in ipc.get_contours( mask=mask, retrieve_mode=cv2.RETR_LIST, method=cv2.CHAIN_APPROX_SIMPLE, ) if (cv2.contourArea(c, True) < 0) ] contours.sort(key=lambda x: cv2.contourArea(x), reverse=True) demo_images = [] if self.get_value_of("concave") == 1: res = self.concave_detection( wrapper=wrapper, mask=mask, contours=contours, ) demo_images.append(res["demo_image"]) self.add_value( key="ellipses_concave_method", value=res["count"], force_add=True, ) if self.get_value_of("hough") == 1: res = self.hough_detection( wrapper=wrapper, mask=mask, contours=contours, ) demo_images.append(res["demo_image"]) self.add_value( key="ellipses_hough_method", value=res["count"], force_add=True, ) if self.get_value_of("median") == 1: res = self.median_area_detection( wrapper=wrapper, mask=mask, contours=contours, ) demo_images.append(res["demo_image"]) self.add_value( key="ellipses_median_method", value=res["count"], force_add=True, ) if len(demo_images) == 3: demo_images.append(wrapper.current_image) self.demo_image = wrapper.auto_mosaic(demo_images) res = True else: wrapper.store_image(wrapper.current_image, "current_image") res = True except Exception as e: res = False logger.error( f"Split overlapped ellipses FAILED, exception: {repr(e)}") else: pass finally: return res
def concave_detection(self, wrapper, mask, contours): min_size_to_split = self.get_value_of("min_size_to_split") dbg_font_scale = self.get_value_of("dbg_font_scale") dbg_font_thickness = self.get_value_of("dbg_font_thickness") residue_size = self.get_value_of("residue_size") total_ellipses = 0 dbg_img = np.dstack(( np.zeros_like(mask), np.zeros_like(mask), np.zeros_like(mask), )) for cnt in contours: x, y, w, h = cv2.boundingRect(cnt) clr = ( random.randint(50, 200), random.randint(50, 200), random.randint(50, 200), ) handled_ = cv2.contourArea(cnt) > min_size_to_split if handled_ is True: work_img = dbg_img.copy() cv2.drawContours( image=work_img, contours=[cv2.convexHull(cnt)], contourIdx=0, color=ipc.C_WHITE, thickness=-1, ) cv2.drawContours( image=work_img, contours=[cnt], contourIdx=0, color=ipc.C_BLACK, thickness=-1, ) work_img = work_img[y:y + h, x:x + w].copy() count_ = 0 for c in ipc.get_contours( mask=work_img[:, :, 0], retrieve_mode=cv2.RETR_LIST, method=cv2.CHAIN_APPROX_SIMPLE, ): if (cv2.contourArea(c, True) < 0) and (cv2.contourArea(c) >= residue_size): cv2.drawContours( image=work_img, contours=[c], contourIdx=0, color=ipc.C_GREEN, thickness=-1, ) count_ += 1 else: cv2.drawContours( image=work_img, contours=[c], contourIdx=0, color=ipc.C_RED, thickness=-1, ) dbg_img[y:y + h, x:x + w] = work_img if count_ < 2: count_ += 1 else: count_ = 1 cv2.drawContours(dbg_img, [cnt], 0, ipc.C_WHITE, -1) total_ellipses += count_ cx = x + w // 2 - 10 cy = y + h // 2 cv2.putText( img=dbg_img, text=str(count_), org=(cx, cy), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=dbg_font_scale, color=(0, 255, 0), thickness=dbg_font_thickness, ) if handled_ is True: cv2.putText( img=dbg_img, text=f"{x}, {y}", org=(x + w, y + h), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=dbg_font_scale / 2, color=(0, 0, 255), thickness=dbg_font_thickness // 2, ) cv2.putText( img=dbg_img, text=f"{cv2.contourArea(cnt)}", org=(x + w, y), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=dbg_font_scale / 2, color=(0, 0, 255), thickness=dbg_font_thickness // 2, ) return { "count": total_ellipses, "demo_image": self.write_result( image=dbg_img, method="concave detection", value=total_ellipses, ), }
def process_wrapper(self, **kwargs): """ Fix perspective: Fixes perspective using four dots to detect rectangle boundary. Use the included threshold utility to detect the dots. Real time: True Keyword Arguments (in parentheses, argument name): * Activate tool (enabled): Toggle whether or not tool is active * Module mode (mode): * Channel 1 (c1): * Min threshold for channel 1 (c1_low): * Max threshold for channel 1 (c1_high): * Channel 2 (c2): * Min threshold for channel 2 (c2_low): * Max threshold for channel 2 (c2_high): * Channel 3 (c3): * Min threshold for channel 3 (c3_low): * Max threshold for channel 3 (c3_high): * How to merge thresholds (merge_mode): * Morphology operator (morph_op): * Kernel size (kernel_size): * Kernel shape (kernel_shape): * Iterations (proc_times): * Minimal dot size (surface) (min_dot_size): * Maximal dot size (surface) (max_dot_size): * Destination width (dst_width): * Destination height (dst_height): """ wrapper = self.init_wrapper(**kwargs) if wrapper is None: return False res = False try: if self.get_value_of("enabled") == 1: img = wrapper.current_image pm = self.get_value_of("mode") channels = [] stored_names = [] for i in [1, 2, 3]: c = self.get_value_of(f"c{i}") if c == "none": continue msk, stored_name = wrapper.get_mask( src_img=img, channel=c, min_t=self.get_value_of(f"c{i}_low"), max_t=self.get_value_of(f"c{i}_high"), ) channels.append(msk) stored_names.append(stored_name) wrapper.store_image(image=msk, text=stored_name) wrapper.store_image( image=wrapper.build_mosaic( shape=(wrapper.height // len(stored_names), wrapper.width, 3), image_names=np.array(stored_names), ), text="partial_thresholds", ) func = getattr(wrapper, self.get_value_of("merge_mode"), None) if func: mask = self.apply_morphology_from_params( func([mask for mask in channels if mask is not None]) ) wrapper.store_image(image=mask, text="merged_mask") else: logger.error("Unable to merge partial masks") res = False return if pm == "threshold": self.result = mask res = True return # Clean mask if needed labels = measure.label(input=mask, neighbors=8, background=0) dots_mask = np.zeros(mask.shape, dtype="uint8") min_dot_size = self.get_value_of("min_dot_size") max_dot_size = self.get_value_of("max_dot_size") # loop over the unique components for label in np.unique(labels): # if this is the background label, ignore it if label == 0: continue # otherwise, construct the label mask and count the # number of pixels labelMask = np.zeros(mask.shape, dtype="uint8") labelMask[labels == label] = 255 numPixels = cv2.countNonZero(labelMask) # if the number of pixels in the component is sufficiently # large, then add it to our mask of "large blobs" if min_dot_size <= numPixels <= max_dot_size: dots_mask = cv2.add(dots_mask, labelMask) wrapper.store_image(image=dots_mask, text="mask_cleaned") # Find dots' positions mask = dots_mask # find the contours in the mask, then sort them from left to # right cnts = ipc.get_contours( mask=mask, retrieve_mode=cv2.RETR_EXTERNAL, method=cv2.CHAIN_APPROX_SIMPLE, ) cnts = sort_contours(cnts)[0] # loop over the contours dots = [] for (i, c) in enumerate(cnts): # draw the bright spot on the image (x, y, w, h) = cv2.boundingRect(c) ((cX, cY), radius) = cv2.minEnclosingCircle(c) dots.append((int(cX), int(cY))) # Reorder dots top_left = min_distance(origin=(0, 0), points=dots) cv2.circle(img, top_left, 20, (0, 0, 255), 3) cv2.putText( img, f"top_left - {top_left}", top_left, cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 0, 255), 6, ) top_right = min_distance(origin=(wrapper.width, 0), points=dots) cv2.circle(img, top_right, 20, (0, 0, 255), 3) cv2.putText( img, f"top_right - {top_right}", top_right, cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 0, 255), 6, ) bottom_left = min_distance(origin=(0, wrapper.height), points=dots) cv2.circle(img, bottom_left, 20, (0, 0, 255), 3) cv2.putText( img, f"bottom_left - {bottom_left}", bottom_left, cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 0, 255), 6, ) bottom_right = min_distance( origin=(wrapper.width, wrapper.height), points=dots ) cv2.circle(img, bottom_right, 20, (0, 0, 255), 3) cv2.putText( img, f"bottom_right - {bottom_right}", bottom_right, cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 0, 255), 6, ) wrapper.store_image(image=img, text="dotted_image") if pm == "dot_detection": self.result = img res = True return # pad_hor = self.get_value_of('pad_hor') # pad_ver = self.get_value_of('pad_ver') # top_left = (top_left[0] - pad_hor, top_left[1] - pad_ver) # top_right = (top_right[0] + pad_hor, top_right[1] - pad_ver) # bottom_left = (bottom_left[0] - pad_hor, bottom_left[1] + pad_ver) # bottom_right = (bottom_right[0] + pad_hor, bottom_right[1] + pad_ver) # Transform the image mat = cv2.getPerspectiveTransform( src=np.array( [top_left, top_right, bottom_right, bottom_left], dtype="float32", ), dst=np.array( [ [0, 0], [self.get_value_of("dst_width") - 1, 0], [ self.get_value_of("dst_width") - 1, self.get_value_of("dst_height") - 1, ], [0, self.get_value_of("dst_height") - 1], ], dtype="float32", ), ) self.result = cv2.warpPerspective( src=wrapper.current_image, M=mat, dsize=( self.get_value_of("dst_width"), self.get_value_of("dst_height"), ), ) wrapper.store_image(image=self.result, text="warped_image") res = True else: wrapper.store_image(wrapper.current_image, "current_image") res = True except Exception as e: res = False logger.error(f'Failed to process {self. name}: "{repr(e)}"') else: pass finally: return res