def run_analysis(contours, filename, message): filename = filename.replace("images\\", "").replace("images/", "") if message != "": results = message sys.stdout.write(str(results)) sys.stdout.flush() else: properties = imgpheno.contour_properties(contours, ( 'Area', 'MajorAxisLength', )) if properties is None: pass else: properties = imgpheno.contour_properties(contours, ( 'Area', 'MajorAxisLength', )) major_axes = [i['MajorAxisLength'] for i in properties] if yml.detailed_size_classes is True: b_0_1 = [i for i in major_axes if i < 4] b_1_4 = [i for i in major_axes if 4 <= i < 15] b_4_7 = [i for i in major_axes if 15 <= i < 26] b_7_12 = [i for i in major_axes if 26 <= i < 45] larger_12 = [i for i in major_axes if i >= 45] areas = [i['Area'] for i in properties] average_area = np.mean(areas) number_of_insects = (len(b_0_1) + len(b_1_4) + len(b_4_7) + len(b_7_12) + len(larger_12)) results = """%s \t %s \t %f \t %s \t %s \t %s \t %s \t %s """ % (filename, number_of_insects, (average_area / 4), len(b_0_1), len(b_1_4), len(b_4_7), len(b_7_12), len(larger_12)) sys.stdout.write(str(results.replace(" ", ""))) sys.stdout.flush() else: smaller_than_4 = [i for i in major_axes if 4 <= i < 15] between_4_and_10 = [i for i in major_axes if 15 <= i < 38] larger_than_10 = [i for i in major_axes if 38 <= i < 45] areas = [i['Area'] for i in properties] average_area = np.mean(areas) number_of_insects = (len(smaller_than_4) + len(between_4_and_10) + len(larger_than_10)) results = """%s \t %s \t %d \t %s \t %s \t %s """ % (filename, number_of_insects, (average_area / 4), len(smaller_than_4), len(between_4_and_10), len(larger_than_10)) sys.stdout.write(str(results.replace(" ", ""))) sys.stdout.flush()
def process_image(args, path): global img, img_src img = cv2.imread(path) if img == None or img.size == 0: logging.error("Failed to read %s" % path) exit(1) logging.info("Processing %s..." % path) # Scale the image down if its perimeter exceeds the maximum (if set). img = common.scale_max_perimeter(img, args.max_size) img_src = img.copy() # Perform segmentation logging.info("- Segmenting...") mask = common.grabcut(img, args.iters, None, args.margin) bin_mask = np.where((mask==cv2.GC_FGD) + (mask==cv2.GC_PR_FGD), 255, 0).astype('uint8') contours, hierarchy = cv2.findContours(bin_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # Calculate contour properties. logging.info("- Computing contour properties...") props = ft.contour_properties(contours, 'all') # Print properties. pprint.pprint(props) # Draw some properties. draw_props(props)
def process_image(args, path): global img, img_src, outline, box, bin_mask img = cv2.imread(path) if img == None or img.size == 0: logging.error("Failed to read %s" % path) exit(1) logging.info("Processing %s..." % path) # Scale the image down if its perimeter exceeds the maximum (if set). img = common.scale_max_perimeter(img, args.max_size) img_src = img.copy() # Perform segmentation. logging.info("- Segmenting...") mask = common.grabcut(img, args.iters, None, args.margin) bin_mask = np.where((mask == cv2.GC_FGD) + (mask == cv2.GC_PR_FGD), 255, 0).astype('uint8') # Obtain contours (all points) from the mask. contour = ft.get_largest_contour(bin_mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # Get bounding rectange of the largest contour. props = ft.contour_properties([contour], 'BoundingRect') box = props[0]['BoundingRect'] # And draw it. logging.info("- Done") draw_sections(0, args.bins)
def process_image(args, path): global img, img_src, outline, box, bin_mask img = cv2.imread(path) if img == None or img.size == 0: logging.error("Failed to read %s" % path) exit(1) logging.info("Processing %s..." % path) # Scale the image down if its perimeter exceeds the maximum (if set). img = common.scale_max_perimeter(img, args.max_size) img_src = img.copy() # Perform segmentation. logging.info("- Segmenting...") mask = common.grabcut(img, args.iters, None, args.margin) bin_mask = np.where((mask==cv2.GC_FGD) + (mask==cv2.GC_PR_FGD), 255, 0).astype('uint8') # Obtain contours (all points) from the mask. contour = ft.get_largest_contour(bin_mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # Get bounding rectange of the largest contour. props = ft.contour_properties([contour], 'BoundingRect') box = props[0]['BoundingRect'] # And draw it. logging.info("- Done") draw_sections(0, args.bins)
def shape_360_v2(contour, rotation=0, step=1, t=8): """Returns a shape feature from a contour. The rotation in degrees of the contour can be set with `rotation`, which must be a value between 0 and 179 inclusive. A rotation above 90 degrees is interpreted as a rotation to the left (e.g. a rotation of 120 is interpreted as 60 degrees to the left). The returned shape is shifted by the rotation. The step size for the angle can be set with `step`. Argument `t` is passed to :meth:`weighted_points_nearest`. The returned shape is rotation invariant provided that the rotation argument is set properly and the rotation is no more than 90 degrees left or right. Shape is returned as a tuple ``(intersects, center)``, where ``intersects`` is a dict where the keys are 0 based angles and the values are lists of intersecting points. If `step` is set to 1, then the dict will contain the intersections for 360 degrees. ``center`` specifies the center of the contour and can be used to calculate distances from center to contour. """ if len(contour) < 6: raise ValueError("Contour must have at least 6 points, found %d" % len(contour)) if not 0 <= rotation <= 179: raise ValueError("The rotation must be between 0 and 179 inclusive, found %d" % rotation) # Get the center. props = ft.contour_properties([contour], 'Centroid') center = props[0]['Centroid'] # If the rotation is more than 90 degrees, assume the object is rotated to # the left. if rotation <= 90: a = 0 b = 180 else: a = 180 b = 0 # Define the slope for each point and group points by slope. slopes = {} for p in contour: p = tuple(p[0]) x = p[0] - center[0] y = p[1] - center[1] if x == 0: s = float('inf') else: s = float(y) / x s = round(s, 4) if s in slopes: slopes[s].append(p) else: slopes[s] = [p] # Get the intersecting points with the contour for every degree from the # symmetry axis. intersects = {} for angle in range(0, 180, step): # Get the slope for the linear function of this angle and account for # the rotation of the object. slope = ft.slope_from_angle(angle + rotation, inverse=True) # Since pixel points can only be defined with natural numbers, # resulting in gaps in between two points, means the maximum # gap is equal to the slope of the linear function. gap_max = math.ceil(abs(slope)) # Dmax set empirically. dmax = gap_max * 0.20 # Make a selection of the contour points which somewhat fit the # slope for this angle. candidates = [] for s in slopes: if math.isinf(slope): if math.isinf(s): d = 0 else: continue else: d = abs(slope - s) if d == 0 or d <= dmax: for p in slopes[s]: candidates.append(p) # Find the contour points from the list of candidate points that # closely fit the angle's linear function. # Only save points for which the distance to the expected point is # no more than the maximum gap. Save each point with a weight value, # which is used for clustering. weighted_points = [] for p in candidates: x = p[0] - center[0] y = p[1] - center[1] if math.isinf(slope): if x == 0: # Save points that are on the vertical axis. weighted_points.append( (1, tuple(p)) ) else: y_exp = slope * x d = abs(y - y_exp) if d <= gap_max: w = 1 / (d+1) weighted_points.append( (w, tuple(p)) ) assert len(weighted_points) > 0, "No intersections found for angle %d" % angle # Cluster the points. weighted_points = ft.weighted_points_nearest(weighted_points, t) _, points = zip(*weighted_points) # Figure out on which side of the symmetry the points lie. # Create a line that separates points on the left from points on # the right. if (angle + rotation) != 0: division_line = ft.angled_line(center, angle + rotation + 90, 100) else: # Points cannot be separated when the division line is horizontal. # So make a division line that is rotated 45 degrees to the left # instead, so that the points are properly separated. division_line = ft.angled_line(center, angle + rotation - 45, 100) intersects[angle] = [] intersects[angle+180] = [] for p in points: side = ft.side_of_line(division_line, p) if side < 0: intersects[angle+a].append(p) elif side > 0: intersects[angle+b].append(p) else: assert side != 0, "A point cannot be on the division line" return (intersects, center)
def run_analysis(contours, filename, message): filename = filename.replace("images/sticky-traps\\", "").replace("images/sticky-traps/", "") if message != "": results = message if yml.result_file == "": pass else: resultfile = open(yml.result_file, "a+") resultfile.write(str(results)) resultfile.close() else: properties = imgpheno.contour_properties(contours, ('Area', 'MajorAxisLength',)) if properties is None: pass else: properties = imgpheno.contour_properties(contours, ('Area', 'MajorAxisLength',)) major_axes = [i['MajorAxisLength'] for i in properties] if yml.detailed_size_classes is True: b_0_1 = [i for i in major_axes if i < 4] b_1_4 = [i for i in major_axes if 4 <= i < 15] b_4_7 = [i for i in major_axes if 15 <= i < 26] b_7_12 = [i for i in major_axes if 26 <= i < 45] larger_12 = [i for i in major_axes if i >= 45] areas = [i['Area'] for i in properties] average_area = np.mean(areas) number_of_insects = (len(b_0_1) + len(b_1_4) + len(b_4_7) + len(b_7_12) + len(larger_12)) print """There are %s insects on the trap in %s. The average area of the insects in %s is %f mm square. The number of insects between 0 and 1 mm is %s The number of insects between 1 and 4 mm is %s The number of insects between 4 and 7 mm is %s The number of insects between 7 and 12 mm is %s The number of insects larger than 12 mm is %s """ % (number_of_insects, filename, filename, (average_area / 4), len(b_0_1), len(b_1_4), len(b_4_7), len(b_7_12), len(larger_12)) results = """%s \t %s \t %f \t %s \t %s \t %s \t %s \t %s """ % (filename, number_of_insects, (average_area / 4), len(b_0_1), len(b_1_4), len(b_4_7), len(b_7_12), len(larger_12)) if yml.result_file == "": pass else: resultfile = open(yml.result_file, "a+") resultfile.write(str(results.replace(" ", ""))) resultfile.close() else: smaller_than_4 = [i for i in major_axes if 4 <= i < 15] between_4_and_10 = [i for i in major_axes if 15 <= i < 38] larger_than_10 = [i for i in major_axes if 38 <= i < 45] areas = [i['Area'] for i in properties] average_area = np.mean(areas) number_of_insects = (len(smaller_than_4) + len(between_4_and_10) + len(larger_than_10)) print """There are %s insects on the trap in %s. The average area of the insects in %s is %d mm square. The number of insects smaller than 4 mm is %s The number of insects between 4 and 10 mm is %s The number of insects larger than 10 mm is %s """ % (number_of_insects, filename, filename, (average_area / 4), len(smaller_than_4), len(between_4_and_10), len(larger_than_10)) results = """%s \t %s \t %d \t %s \t %s \t %s """ % (filename, number_of_insects, (average_area / 4), len(smaller_than_4), len(between_4_and_10), len(larger_than_10)) if yml.result_file == "": pass else: resultfile = open(yml.result_file, "a+") resultfile.write(str(results.replace(" ", ""))) resultfile.close()
def shape_360_v2(contour, rotation=0, step=1, t=8): """Returns a shape feature from a contour. The rotation in degrees of the contour can be set with `rotation`, which must be a value between 0 and 179 inclusive. A rotation above 90 degrees is interpreted as a rotation to the left (e.g. a rotation of 120 is interpreted as 60 degrees to the left). The returned shape is shifted by the rotation. The step size for the angle can be set with `step`. Argument `t` is passed to :meth:`weighted_points_nearest`. The returned shape is rotation invariant provided that the rotation argument is set properly and the rotation is no more than 90 degrees left or right. Shape is returned as a tuple ``(intersects, center)``, where ``intersects`` is a dict where the keys are 0 based angles and the values are lists of intersecting points. If `step` is set to 1, then the dict will contain the intersections for 360 degrees. ``center`` specifies the center of the contour and can be used to calculate distances from center to contour. """ if len(contour) < 6: raise ValueError("Contour must have at least 6 points, found %d" % len(contour)) if not 0 <= rotation <= 179: raise ValueError( "The rotation must be between 0 and 179 inclusive, found %d" % rotation) # Get the center. props = ft.contour_properties([contour], 'Centroid') center = props[0]['Centroid'] # If the rotation is more than 90 degrees, assume the object is rotated to # the left. if rotation <= 90: a = 0 b = 180 else: a = 180 b = 0 # Define the slope for each point and group points by slope. slopes = {} for p in contour: p = tuple(p[0]) x = p[0] - center[0] y = p[1] - center[1] if x == 0: s = float('inf') else: s = float(y) / x s = round(s, 4) if s in slopes: slopes[s].append(p) else: slopes[s] = [p] # Get the intersecting points with the contour for every degree from the # symmetry axis. intersects = {} for angle in range(0, 180, step): # Get the slope for the linear function of this angle and account for # the rotation of the object. slope = ft.slope_from_angle(angle + rotation, inverse=True) # Since pixel points can only be defined with natural numbers, # resulting in gaps in between two points, means the maximum # gap is equal to the slope of the linear function. gap_max = math.ceil(abs(slope)) # Dmax set empirically. dmax = gap_max * 0.20 # Make a selection of the contour points which somewhat fit the # slope for this angle. candidates = [] for s in slopes: if math.isinf(slope): if math.isinf(s): d = 0 else: continue else: d = abs(slope - s) if d == 0 or d <= dmax: for p in slopes[s]: candidates.append(p) # Find the contour points from the list of candidate points that # closely fit the angle's linear function. # Only save points for which the distance to the expected point is # no more than the maximum gap. Save each point with a weight value, # which is used for clustering. weighted_points = [] for p in candidates: x = p[0] - center[0] y = p[1] - center[1] if math.isinf(slope): if x == 0: # Save points that are on the vertical axis. weighted_points.append((1, tuple(p))) else: y_exp = slope * x d = abs(y - y_exp) if d <= gap_max: w = 1 / (d + 1) weighted_points.append((w, tuple(p))) assert len( weighted_points) > 0, "No intersections found for angle %d" % angle # Cluster the points. weighted_points = ft.weighted_points_nearest(weighted_points, t) _, points = zip(*weighted_points) # Figure out on which side of the symmetry the points lie. # Create a line that separates points on the left from points on # the right. if (angle + rotation) != 0: division_line = ft.angled_line(center, angle + rotation + 90, 100) else: # Points cannot be separated when the division line is horizontal. # So make a division line that is rotated 45 degrees to the left # instead, so that the points are properly separated. division_line = ft.angled_line(center, angle + rotation - 45, 100) intersects[angle] = [] intersects[angle + 180] = [] for p in points: side = ft.side_of_line(division_line, p) if side < 0: intersects[angle + a].append(p) elif side > 0: intersects[angle + b].append(p) else: assert side != 0, "A point cannot be on the division line" return (intersects, center)