phyloforfun's picture
Add application file
87c3140
raw
history blame
93.8 kB
import os, math, cv2, random
import numpy as np
from itertools import combinations
from PIL import Image
from dataclasses import dataclass, field
from typing import List, Dict
@dataclass()
class LeafSkeleton:
cfg: str
Dirs: str
leaf_type: str
all_points: list
dir_temp: str
file_name: str
width: int
height: int
logger: object
do_show_QC_images: bool = False
do_save_QC_images: bool = False
classes: float = None
points_list: float = None
image: float = None
ordered_midvein: float = None
midvein_fit: float = None
midvein_fit_points: float = None
ordered_midvein_length: float = None
has_midvein = False
is_split = False
ordered_petiole: float = None
ordered_petiole_length: float = None
has_ordered_petiole = False
has_apex: bool = False
apex_left: float = None
apex_right: float = None
apex_center: float = None
apex_angle_type: str = 'NA'
apex_angle_degrees: float = None
has_base: bool = False
base_left: float = None
base_right: float = None
base_center: float = None
base_angle_type: str = 'NA'
base_angle_degrees: float = None
has_lamina_tip: bool = False
lamina_tip: float = None
has_lamina_base: bool = False
lamina_base: float = None
has_lamina_length: bool = False
lamina_fit: float = None
lamina_length: float = None
has_width: bool = False
lamina_width: float = None
width_left: float = None
width_right: float = None
has_lobes: bool = False
lobe_count: float = None
lobes: float = None
def __init__(self, cfg, logger, Dirs, leaf_type, all_points, height, width, dir_temp, file_name) -> None:
# Store the necessary arguments as instance attributes
self.cfg = cfg
self.Dirs = Dirs
self.leaf_type = leaf_type
self.all_points = all_points
self.height = height
self.width = width
self.dir_temp = dir_temp
self.file_name = file_name
logger.name = f'[{leaf_type} - {file_name}]'
self.logger = logger
self.init_lists_dicts()
# Setup
self.set_cfg_values()
self.define_landmark_classes()
self.setup_QC_image()
self.setup_final_image()
self.parse_all_points()
self.convert_YOLO_bbox_to_point()
# Start with ordering the midvein and petiole
self.order_midvein()
self.order_petiole()
# print(self.ordered_midvein)
# Split the image using the midvein IF has_midvein == True
self.split_image_by_midvein()
# Process angles IF is_split == True. Need orientation to pick the appropriate pts for angle calcs
self.determine_apex()
self.determine_base()
self.determine_lamina_tip()
self.determine_lamina_base()
self.determine_lamina_length('QC')
self.determine_width()
self.determine_lobes()
self.determine_petiole() # straight length of petiole vs. ordered_petiole length which is tracing the petiole
self.restrictions()
# creates self.is_complete_leaf = False and self.is_leaf_no_width = False
# can add less restrictive options later, but for now only very complete leaves will pass
self.redo_measurements()
self.create_final_image()
self.translate_measurements_to_full_image()
self.show_QC_image()
self.show_final_image()
# self.save_QC_image()
# print('hi')
def get(self, attribute, default=None):
return getattr(self, attribute, default)
def split_image_by_midvein(self):
if self.has_midvein:
n_fit = 1
# Convert the points to a numpy array
points_arr = np.array(self.ordered_midvein)
# Fit a line to the points
self.midvein_fit = np.polyfit(points_arr[:, 0], points_arr[:, 1], n_fit)
if len(self.midvein_fit) < 1:
self.midvein_fit = None
else:
# Plot a sample of points from along the line
max_dim = max(self.height, self.width)
if max_dim < 400:
num_points = 40
elif max_dim < 1000:
num_points = 80
else:
num_points = 120
# Get the endpoints of the line segment that lies within the bounds of the image
x1 = 0
y1 = int(self.midvein_fit[0] * x1 + self.midvein_fit[1])
x2 = self.width - 1
y2 = int(self.midvein_fit[0] * x2 + self.midvein_fit[1])
denom = self.midvein_fit[0]
if denom == 0:
denom = 0.0000000001
if y1 < 0:
y1 = 0
x1 = int((y1 - self.midvein_fit[1]) / denom)
if y2 >= self.height:
y2 = self.height - 1
x2 = int((y2 - self.midvein_fit[1]) / denom)
# Sample num_points points along the line segment within the bounds of the image
x_vals = np.linspace(x1, x2, num_points)
y_vals = self.midvein_fit[0] * x_vals + self.midvein_fit[1]
# Remove any points that are outside the bounds of the image
indices = np.where((y_vals >= 0) & (y_vals < self.height))[0]
x_vals = x_vals[indices]
y_vals = y_vals[indices]
# Recompute y-values using the line equation and updated x-values
y_vals = self.midvein_fit[0] * x_vals + self.midvein_fit[1]
self.midvein_fit_points = np.column_stack((x_vals, y_vals))
self.is_split = True
# Draw line of fit
for point in self.midvein_fit_points:
cv2.circle(self.image, tuple(point.astype(int)), radius=1, color=(255, 255, 255), thickness=-1)
'''def split_image_by_midvein(self): # cubic
if self.file_name == 'B_774373024_Ebenaceae_Diospyros_glutinifera__L__469-164-888-632':
print('hi')
if self.has_midvein:
n_fit = 3
# Convert the points to a numpy array
points_arr = np.array(self.ordered_midvein)
# Fit a curve to the points
self.midvein_fit = np.polyfit(points_arr[:, 0], points_arr[:, 1], n_fit)
# Plot a sample of points from along the curve
max_dim = max(self.height, self.width)
if max_dim < 400:
num_points = 40
elif max_dim < 1000:
num_points = 80
else:
num_points = 120
# Get the endpoints of the curve segment that lies within the bounds of the image
x1 = 0
y1 = int(self.midvein_fit[0] * x1**3 + self.midvein_fit[1] * x1**2 + self.midvein_fit[2] * x1 + self.midvein_fit[3])
x2 = self.width - 1
y2 = int(self.midvein_fit[0] * x2**3 + self.midvein_fit[1] * x2**2 + self.midvein_fit[2] * x2 + self.midvein_fit[3])
# Sample num_points y-values that are evenly spaced within the bounds of the image
y_vals = np.linspace(0, self.height - 1, num_points)
# Compute the corresponding x-values using the polynomial
p = np.poly1d(self.midvein_fit)
x_vals = np.zeros(num_points)
for i, y in enumerate(y_vals):
roots = p - y
real_roots = roots.r[np.isreal(roots.r)].real
x_val = real_roots[(real_roots >= 0) & (real_roots < self.width)]
if len(x_val) > 0:
x_vals[i] = x_val[0]
# Remove any points that are outside the bounds of the image
indices = np.where((y_vals > 0) & (y_vals < self.height-1))[0]
x_vals = x_vals[indices]
y_vals = y_vals[indices]
# Recompute y-values using the polynomial and updated x-values
y_vals = self.midvein_fit[0] * x_vals**3 + self.midvein_fit[1] * x_vals**2 + self.midvein_fit[2] * x_vals + self.midvein_fit[3]
self.midvein_fit_points = np.column_stack((x_vals, y_vals))
self.is_split = True'''
def determine_apex(self):
if self.is_split:
can_get_angle = False
if 'apex_angle' in self.points_list:
if 'lamina_tip' in self.points_list:
self.apex_center, self.points_list['apex_angle'] = self.get_closest_point_to_sampled_points(self.points_list['apex_angle'], self.points_list['lamina_tip'])
can_get_angle = True
elif self.midvein_fit_points.shape[0] > 0:
self.apex_center, self.points_list['apex_angle'] = self.get_closest_point_to_sampled_points(self.points_list['apex_angle'], self.midvein_fit_points)
can_get_angle = True
if can_get_angle:
left = []
right = []
for point in self.points_list['apex_angle']:
loc = self.point_position_relative_to_line(point, self.midvein_fit)
if loc == 'right':
right.append(point)
elif loc == 'left':
left.append(point)
if (left == []) or (right == []):
self.has_apex = False
if (left == []) and (right != []):
self.apex_right, right = self.get_far_point(right, self.apex_center)
self.apex_left = None
elif (right == []) and (left != []):
self.apex_left, left = self.get_far_point(left, self.apex_center)
self.apex_right = None
else:
self.apex_left = None
self.apex_right = None
else:
self.has_apex = True
self.apex_left, left = self.get_far_point(left, self.apex_center)
self.apex_right, right = self.get_far_point(right, self.apex_center)
# print(self.points_list['apex_angle'])
# print(f'apex_center: {self.apex_center} apex_left: {self.apex_left} apex_right: {self.apex_right}')
self.logger.debug(f"[apex_angle_list] {self.points_list['apex_angle']}")
self.logger.debug(f"[apex_center] {self.apex_center} [apex_left] {self.apex_left} [apex_right] {self.apex_right}")
if self.has_apex:
self.apex_angle_type, self.apex_angle_degrees = self.determine_reflex(self.apex_left, self.apex_right, self.apex_center)
# print(f'angle_type {self.apex_angle_type} angle {self.apex_angle_degrees}')
self.logger.debug(f"[angle_type] {self.apex_angle_type} [angle] {self.apex_angle_degrees}")
else:
self.apex_angle_type = 'NA'
self.apex_angle_degrees = None
self.logger.debug(f"[angle_type] {self.apex_angle_type} [angle] {self.apex_angle_degrees}")
if self.has_apex:
if self.apex_center is not None:
cv2.circle(self.image, self.apex_center, radius=3, color=(0, 255, 0), thickness=-1)
if self.apex_left is not None:
cv2.circle(self.image, self.apex_left, radius=3, color=(255, 0, 0), thickness=-1)
if self.apex_right is not None:
cv2.circle(self.image, self.apex_right, radius=3, color=(0, 0, 255), thickness=-1)
def determine_apex_redo(self):
self.logger.debug(f"[apex_angle_list REDO] ")
self.logger.debug(f"[apex_center REDO] {self.apex_center} [apex_left] {self.apex_left} [apex_right] {self.apex_right}")
if self.has_apex:
self.apex_angle_type, self.apex_angle_degrees = self.determine_reflex(self.apex_left, self.apex_right, self.apex_center)
self.logger.debug(f"[angle_type REDO] {self.apex_angle_type} [angle] {self.apex_angle_degrees}")
else:
self.apex_angle_type = 'NA'
self.apex_angle_degrees = None
self.logger.debug(f"[angle_type REDO] {self.apex_angle_type} [angle] {self.apex_angle_degrees}")
if self.has_apex:
if self.apex_center is not None:
cv2.circle(self.image, self.apex_center, radius=11, color=(0, 255, 0), thickness=2)
if self.apex_left is not None:
cv2.circle(self.image, self.apex_left, radius=3, color=(255, 0, 0), thickness=-1)
if self.apex_right is not None:
cv2.circle(self.image, self.apex_right, radius=3, color=(0, 0, 255), thickness=-1)
def determine_base_redo(self):
self.logger.debug(f"[base_angle_list REDO] ")
self.logger.debug(f"[base_center REDO] {self.base_center} [base_left] {self.base_left} [base_right] {self.base_right}")
if self.has_base:
self.base_angle_type, self.base_angle_degrees = self.determine_reflex(self.base_left, self.base_right, self.base_center)
self.logger.debug(f"[angle_type REDO] {self.base_angle_type} [angle] {self.base_angle_degrees}")
else:
self.base_angle_type = 'NA'
self.base_angle_degrees = None
self.logger.debug(f"[angle_type REDO] {self.base_angle_type} [angle] {self.base_angle_degrees}")
if self.has_base:
if self.base_center is not None:
cv2.circle(self.image, self.base_center, radius=11, color=(0, 255, 0), thickness=2)
if self.base_left is not None:
cv2.circle(self.image, self.base_left, radius=3, color=(255, 0, 0), thickness=-1)
if self.base_right is not None:
cv2.circle(self.image, self.base_right, radius=3, color=(0, 0, 255), thickness=-1)
def determine_base(self):
if self.is_split:
can_get_angle = False
if 'base_angle' in self.points_list:
if 'lamina_base' in self.points_list:
self.base_center, self.points_list['base_angle'] = self.get_closest_point_to_sampled_points(self.points_list['base_angle'], self.points_list['lamina_base'])
can_get_angle = True
elif self.midvein_fit_points.shape[0] > 0:
self.base_center, self.points_list['base_angle'] = self.get_closest_point_to_sampled_points(self.points_list['base_angle'], self.midvein_fit_points)
can_get_angle = True
if can_get_angle:
left = []
right = []
for point in self.points_list['base_angle']:
loc = self.point_position_relative_to_line(point, self.midvein_fit)
if loc == 'right':
right.append(point)
elif loc == 'left':
left.append(point)
if (left == []) or (right == []):
self.has_base = False
if (left == []) and (right != []):
self.base_right, right = self.get_far_point(right, self.base_center)
self.base_left = None
elif (right == []) and (left != []):
self.base_left, left = self.get_far_point(left, self.base_center)
self.base_right = None
else:
self.base_left = None
self.base_right = None
else:
self.has_base = True
self.base_left, left = self.get_far_point(left, self.base_center)
self.base_right, right = self.get_far_point(right, self.base_center)
# print(self.points_list['base_angle'])
# print(f'base_center: {self.base_center} base_left: {self.base_left} base_right: {self.base_right}')
self.logger.debug(f"[base_angle_list] {self.points_list['base_angle']}")
self.logger.debug(f"[base_center] {self.base_center} [base_left] {self.base_left} [base_right] {self.base_right}")
if self.has_base:
self.base_angle_type, self.base_angle_degrees = self.determine_reflex(self.base_left, self.base_right, self.base_center)
# print(f'angle_type {self.base_angle_type} angle {self.base_angle_degrees}')
self.logger.debug(f"[angle_type] {self.base_angle_type} [angle] {self.base_angle_degrees}")
else:
self.base_angle_type = 'NA'
self.base_angle_degrees = None
self.logger.debug(f"[angle_type] {self.base_angle_type} [angle] {self.base_angle_degrees}")
if self.has_base:
if self.base_center:
cv2.circle(self.image, self.base_center, radius=3, color=(0, 255, 0), thickness=-1)
if self.base_left:
cv2.circle(self.image, self.base_left, radius=3, color=(255, 0, 0), thickness=-1)
if self.base_right:
cv2.circle(self.image, self.base_right, radius=3, color=(0, 0, 255), thickness=-1)
def determine_lamina_tip(self):
if 'lamina_tip' in self.points_list:
self.has_lamina_tip = True
if self.apex_center:
self.lamina_tip, self.lamina_tip_alternate = self.get_closest_point_to_sampled_points(self.points_list['lamina_tip'], self.apex_center)
elif len(self.midvein_fit_points) > 0:
self.lamina_tip, self.lamina_tip_alternate = self.get_closest_point_to_sampled_points(self.points_list['lamina_tip'], self.midvein_fit_points)
else:
if len(self.points_list['lamina_tip']) == 1:
self.lamina_tip = self.points_list['lamina_tip'][0]
self.lamina_tip_alternate = None
else: # blindly choose the most "central points"
centroid = tuple(np.mean(self.points_list['lamina_tip'], axis=0))
self.lamina_tip = min(self.points_list['lamina_tip'], key=lambda p: np.linalg.norm(np.array(p) - np.array(centroid)))
self.lamina_tip_alternate = None # TODO finish this
# if lamina_tip is closer to midvein_fit_points, then apex_center = lamina_tip
if self.apex_center and (len(self.midvein_fit_points) > 0):
d_apex = self.calc_min_distance(self.apex_center, self.midvein_fit_points)
d_lamina = self.calc_min_distance(self.lamina_tip, self.midvein_fit_points)
if d_lamina < d_apex:
cv2.circle(self.image, self.apex_center, radius=5, color=(255, 255, 255), thickness=3) # white hollow, indicates switch
cv2.circle(self.image, self.lamina_tip, radius=3, color=(0, 255, 0), thickness=-1) # repaint the point, indicates switch
self.apex_center = self.lamina_tip
if self.has_apex:
self.apex_angle_type, self.apex_angle_degrees = self.determine_reflex(self.apex_left, self.apex_right, self.apex_center)
else:
if self.apex_center:
self.has_lamina_tip = True
self.lamina_tip = self.apex_center
self.lamina_tip_alternate = None
if self.lamina_tip:
cv2.circle(self.image, self.lamina_tip, radius=5, color=(255, 0, 230), thickness=2) # pink solid
if self.lamina_tip_alternate:
for pt in self.lamina_tip_alternate:
cv2.circle(self.image, pt, radius=3, color=(255, 0, 230), thickness=-1) # pink hollow
def determine_lamina_base(self):
if 'lamina_base' in self.points_list:
self.has_lamina_base = True
if self.base_center:
self.lamina_base, self.lamina_base_alternate = self.get_closest_point_to_sampled_points(self.points_list['lamina_base'], self.base_center)
elif len(self.midvein_fit_points) > 0:
self.lamina_base, self.lamina_base_alternate = self.get_closest_point_to_sampled_points(self.points_list['lamina_base'], self.midvein_fit_points)
else:
if len(self.points_list['lamina_base']) == 1:
self.lamina_base = self.points_list['lamina_base'][0]
self.lamina_base_alternate = None
else: # blindly choose the most "central points"
centroid = tuple(np.mean(self.points_list['lamina_base'], axis=0))
self.lamina_base = min(self.points_list['lamina_base'], key=lambda p: np.linalg.norm(np.array(p) - np.array(centroid)))
self.lamina_base_alternate = None
# if has_lamina_tip is closer to midvein_fit_points, then base_center = has_lamina_tip
if self.base_center and (len(self.midvein_fit_points) > 0):
d_base = self.calc_min_distance(self.base_center, self.midvein_fit_points)
d_lamina = self.calc_min_distance(self.lamina_base, self.midvein_fit_points)
if d_lamina < d_base:
cv2.circle(self.image, self.base_center, radius=5, color=(255, 255, 255), thickness=3) # white hollow, indicates switch
cv2.circle(self.image, self.lamina_base, radius=3, color=(0, 255, 0), thickness=-1) # repaint the point, indicates switch
self.base_center = self.lamina_base
if self.has_base:
self.base_angle_type, self.base_angle_degrees = self.determine_reflex(self.base_left, self.base_right, self.base_center)
else:
if self.base_center:
self.has_lamina_base = True
self.lamina_base = self.base_center
self.lamina_base_alternate = None
if self.lamina_base:
cv2.circle(self.image, self.lamina_base, radius=5, color=(0, 100, 255), thickness=2) # orange
if self.lamina_base_alternate:
for pt in self.lamina_base_alternate:
cv2.circle(self.image, pt, radius=3, color=(0, 100, 255), thickness=-1) # orange hollow
def determine_lamina_length(self, QC_or_final):
if self.has_lamina_base and self.has_lamina_tip:
self.lamina_length = self.distance(self.lamina_base, self.lamina_tip)
ends = np.array([self.lamina_base, self.lamina_tip])
self.lamina_fit = np.polyfit(ends[:, 0], ends[:, 1], 1)
self.has_lamina_length = True
# r_base = 0
r_base = 16
# col = (0, 100, 0)
col = (0, 0, 0)
if QC_or_final == 'QC':
cv2.line(self.image, self.lamina_base, self.lamina_tip, col, 2 + r_base)
else:
cv2.line(self.image_final, self.lamina_base, self.lamina_tip, col, 2 + r_base)
else:
col = (0, 0, 0)
r_base = 16
if self.has_lamina_base and (not self.has_lamina_tip) and self.has_apex: # lamina base and apex center
self.lamina_length = self.distance(self.lamina_base, self.apex_center)
ends = np.array([self.lamina_base, self.apex_center])
self.lamina_fit = np.polyfit(ends[:, 0], ends[:, 1], 1)
self.has_lamina_length = True
if QC_or_final == 'QC':
cv2.line(self.image, self.lamina_base, self.apex_center, col, 2 + r_base)
else:
cv2.line(self.image, self.lamina_base, self.apex_center, col, 2 + r_base)
elif self.has_lamina_tip and (not self.has_lamina_base) and self.has_base: # lamina tip and base center
self.lamina_length = self.distance(self.lamina_tip, self.base_center)
ends = np.array([self.lamina_tip, self.apex_center])
self.lamina_fit = np.polyfit(ends[:, 0], ends[:, 1], 1)
self.has_lamina_length = True
if QC_or_final == 'QC':
cv2.line(self.image, self.lamina_tip, self.apex_center, col, 2 + r_base)
else:
cv2.line(self.image, self.lamina_tip, self.apex_center, col, 2 + r_base)
elif (not self.has_lamina_tip) and (not self.has_lamina_base) and self.has_apex and self.has_base: # apex center and base center
self.lamina_length = self.distance(self.apex_center, self.base_center)
ends = np.array([self.base_center, self.apex_center])
self.lamina_fit = np.polyfit(ends[:, 0], ends[:, 1], 1)
self.has_lamina_length = True
if QC_or_final == 'QC':
cv2.line(self.image, self.base_center, self.apex_center, col, 2 + r_base)
else:
cv2.line(self.image, self.base_center, self.apex_center, col, 2 + r_base) # 0, 175, 200
else:
self.lamina_length = None
self.lamina_fit = None
self.has_lamina_length = False
def determine_width(self):
if (('lamina_width' in self.points_list) and ((self.midvein_fit is not None and len(self.midvein_fit) > 0) or (self.lamina_fit is not None))):
left = []
right = []
if len(self.midvein_fit) > 0: # try using the midvein as a reference first
for point in self.points_list['lamina_width']:
loc = self.point_position_relative_to_line(point, self.midvein_fit)
if loc == 'right':
right.append(point)
elif loc == 'left':
left.append(point)
elif len(self.lamina_fit) > 0: # then try just the lamina tip/base
for point in self.points_list['lamina_width']:
loc = self.point_position_relative_to_line(point, self.lamina_fit)
if loc == 'right':
right.append(point)
elif loc == 'left':
left.append(point)
else:
self.has_width = False
self.width_left = None
self.width_right = None
self.lamina_width = None
if (left == []) or (right == []) or not self.has_width:
self.has_width = False
self.width_left = None
self.width_right = None
self.lamina_width = None
else:
self.has_width = True
if len(self.midvein_fit) > 0:
self.width_left, self.width_right = self.find_most_orthogonal_vectors(left, right, self.midvein_fit)
self.lamina_width = self.distance(self.width_left, self.width_right)
self.order_points_plot([self.width_left, self.width_right], 'lamina_width', 'QC')
else: # get shortest width if the nidvein is absent for comparison
self.width_left, self.width_right = self.find_min_width(left, right)
self.lamina_width = self.distance(self.width_left, self.width_right)
self.order_points_plot([self.width_left, self.width_right], 'lamina_width_alt', 'QC')
else:
self.has_width = False
self.width_left = None
self.width_right = None
self.lamina_width = None
def determine_lobes(self):
if 'lobe_tip' in self.points_list:
self.has_lobes = True
self.lobe_count = len(self.points_list['lobe_tip'])
self.lobes = self.points_list['lobe_tip']
for lobe in self.lobes:
cv2.circle(self.image, tuple(lobe), radius=6, color=(0, 255, 255), thickness=3)
def determine_petiole(self):
if 'petiole_tip' in self.points_list:
self.has_petiole_tip = True
if len(self.points_list['petiole_tip']) == 1:
self.petiole_tip = self.points_list['petiole_tip'][0]
self.petiole_tip_alternate = None
else: # blindly choose the most "central points"
centroid = tuple(np.mean(self.points_list['petiole_tip'], axis=0))
self.petiole_tip = min(self.points_list['petiole_tip'], key=lambda p: np.linalg.norm(np.array(p) - np.array(centroid)))
self.petiole_tip_alternate = None
# Straight length of petiole points
if self.has_ordered_petiole:
self.petiole_tip_opposite, self.petiole_tip_alternate = self.get_far_point(self.ordered_petiole, self.petiole_tip)
self.petiole_length = self.distance(self.petiole_tip_opposite, self.petiole_tip)
self.order_points_plot([self.petiole_tip_opposite, self.petiole_tip], 'petiole_tip', 'QC')
else:
self.petiole_tip_opposite = None
self.petiole_length = None
# Straight length of petiole tip to lamina base
if self.lamina_base is not None:
self.petiole_length_to_lamina_base = self.distance(self.lamina_base, self.petiole_tip)
self.petiole_tip_opposite_alternate = self.lamina_base
self.order_points_plot([self.petiole_tip_opposite_alternate, self.petiole_tip], 'petiole_tip_alt', 'QC')
elif self.base_center:
self.petiole_length_to_lamina_base = self.distance(self.base_center, self.petiole_tip)
self.petiole_tip_opposite_alternate = self.base_center
self.order_points_plot([self.petiole_tip_opposite_alternate, self.petiole_tip], 'petiole_tip_alt', 'QC')
else:
self.petiole_length_to_lamina_base = None
self.petiole_tip_opposite_alternate = None
def redo_measurements(self):
if self.has_width:
self.lamina_width = self.distance(self.width_left, self.width_right)
if self.has_ordered_petiole:
self.ordered_petiole_length, self.ordered_petiole = self.get_length_of_ordered_points(self.ordered_petiole, 'petiole_trace')
if self.has_midvein:
self.ordered_midvein_length, self.ordered_midvein = self.get_length_of_ordered_points(self.ordered_midvein, 'midvein_trace')
if self.has_apex:
self.apex_angle_type, self.apex_angle_degrees = self.determine_reflex(self.apex_left, self.apex_right, self.apex_center)
if self.has_base:
self.base_angle_type, self.base_angle_degrees = self.determine_reflex(self.base_left, self.base_right, self.base_center)
self.determine_lamina_length('final') # Calling just in case, should already be updated
def translate_measurements_to_full_image(self):
loc = self.file_name.split('__')[-1]
self.add_x = int(loc.split('-')[0])
self.add_y = int(loc.split('-')[1])
if self.has_base:
self.t_base_center = [self.base_center[0] + self.add_x, self.base_center[1] + self.add_y]
self.t_base_left = [self.base_left[0] + self.add_x, self.base_left[1] + self.add_y]
self.t_base_right = [self.base_right[0] + self.add_x, self.base_right[1] + self.add_y]
if self.has_apex:
self.t_apex_center = [self.apex_center[0] + self.add_x, self.apex_center[1] + self.add_y]
self.t_apex_left = [self.apex_left[0] + self.add_x, self.apex_left[1] + self.add_y]
self.t_apex_right = [self.apex_right[0] + self.add_x, self.apex_right[1] + self.add_y]
if self.has_lamina_base:
self.t_lamina_base = [self.lamina_base[0] + self.add_x, self.lamina_base[1] + self.add_y]
if self.has_lamina_tip:
self.t_lamina_tip = [self.lamina_tip[0] + self.add_x, self.lamina_tip[1] + self.add_y]
if self.has_lobes:
self.t_lobes = []
for point in self.lobes:
new_x = int(point[0]) + self.add_x
new_y = int(point[1]) + self.add_y
new_point = [new_x, new_y]
self.t_lobes.append(new_point)
if self.has_midvein:
self.t_midvein_fit_points = []
for point in self.midvein_fit_points:
new_x = int(point[0]) + self.add_x
new_y = int(point[1]) + self.add_y
new_point = [new_x, new_y]
self.t_midvein_fit_points.append(new_point)
self.t_midvein = []
for point in self.ordered_midvein:
new_x = int(point[0]) + self.add_x
new_y = int(point[1]) + self.add_y
new_point = [new_x, new_y]
self.t_midvein.append(new_point)
if self.has_ordered_petiole:
self.t_petiole = []
for point in self.ordered_petiole:
new_x = int(point[0]) + self.add_x
new_y = int(point[1]) + self.add_y
new_point = [new_x, new_y]
self.t_petiole.append(new_point)
if self.has_width:
self.t_width_left = [self.width_left[0] + self.add_x, self.width_left[1] + self.add_y]
self.t_width_right = [self.width_right[0] + self.add_x, self.width_right[1] + self.add_y]
if self.width_infer is not None:
self.t_width_infer = []
for point in self.width_infer:
new_x = int(point[0]) + self.add_x
new_y = int(point[1]) + self.add_y
new_point = [new_x, new_y]
self.t_width_infer.append(new_point)
def create_final_image(self):
self.is_complete_leaf = False ###########################################################################################################################################################
self.is_leaf_no_width = False
# r_base = 0
r_base = 16
if (self.has_apex and self.has_base and self.has_ordered_petiole and self.has_midvein and self.has_width):
self.is_complete_leaf = True
self.order_points_plot([self.width_left, self.width_right], 'lamina_width', 'final')
self.order_points_plot(self.ordered_midvein, 'midvein_trace', 'final')
self.order_points_plot(self.ordered_petiole, 'petiole_trace', 'final')
self.order_points_plot([self.apex_left, self.apex_center, self.apex_right], self.apex_angle_type, 'final')
self.order_points_plot([self.base_left, self.base_center, self.base_right], self.base_angle_type, 'final')
self.determine_lamina_length('final') # try
# Lamina tip and base
if self.has_lamina_tip:
cv2.circle(self.image_final, self.lamina_tip, radius=4 + r_base, color=(0, 255, 0), thickness=2)
cv2.circle(self.image_final, self.lamina_tip, radius=2 + r_base, color=(255, 255, 255), thickness=-1)
if self.has_lamina_base:
cv2.circle(self.image_final, self.lamina_base, radius=4 + r_base, color=(255, 0, 0), thickness=2)
cv2.circle(self.image_final, self.lamina_base, radius=2 + r_base, color=(255, 255, 255), thickness=-1)
# Apex angle
# if self.apex_center != []:
# cv2.circle(self.image_final, self.apex_center, radius=3, color=(0, 255, 0), thickness=-1)
if self.apex_left is not None:
cv2.circle(self.image_final, self.apex_left, radius=3 + r_base, color=(255, 0, 0), thickness=-1)
if self.apex_right is not None:
cv2.circle(self.image_final, self.apex_right, radius=3 + r_base, color=(0, 0, 255), thickness=-1)
# Base angle
# if self.base_center:
# cv2.circle(self.image_final, self.base_center, radius=3, color=(0, 255, 0), thickness=-1)
if self.base_left:
cv2.circle(self.image_final, self.base_left, radius=3 + r_base, color=(255, 0, 0), thickness=-1)
if self.base_right:
cv2.circle(self.image_final, self.base_right, radius=3 + r_base, color=(0, 0, 255), thickness=-1)
# Lobes
if self.has_lobes:
for lobe in self.lobes:
cv2.circle(self.image, tuple(lobe), radius=6 + r_base, color=(0, 255, 255), thickness=3)
elif self.has_apex and self.has_base and self.has_ordered_petiole and self.has_midvein and (not self.has_width):
self.is_leaf_no_width = True
self.order_points_plot(self.ordered_midvein, 'midvein_trace', 'final')
self.order_points_plot(self.ordered_petiole, 'petiole_trace', 'final')
self.order_points_plot([self.apex_left, self.apex_center, self.apex_right], self.apex_angle_type, 'final')
self.order_points_plot([self.base_left, self.base_center, self.base_right], self.base_angle_type, 'final')
self.determine_lamina_length('final')
# Lamina tip and base
if self.has_lamina_tip:
cv2.circle(self.image_final, self.lamina_tip, radius=4 + r_base, color=(0, 255, 0), thickness=2)
cv2.circle(self.image_final, self.lamina_tip, radius=2 + r_base, color=(255, 255, 255), thickness=-1)
if self.has_lamina_base:
cv2.circle(self.image_final, self.lamina_base, radius=4 + r_base, color=(255, 0, 0), thickness=2)
cv2.circle(self.image_final, self.lamina_base, radius=2 + r_base, color=(255, 255, 255), thickness=-1)
# Apex angle
# if self.apex_center != []:
# cv2.circle(self.image_final, self.apex_center, radius=3, color=(0, 255, 0), thickness=-1)
if self.apex_left is not None:
cv2.circle(self.image_final, self.apex_left, radius=3 + r_base, color=(255, 0, 0), thickness=-1)
if self.apex_right is not None:
cv2.circle(self.image_final, self.apex_right, radius=3 + r_base, color=(0, 0, 255), thickness=-1)
# Base angle
# if self.base_center:
# cv2.circle(self.image_final, self.base_center, radius=3, color=(0, 255, 0), thickness=-1)
if self.base_left:
cv2.circle(self.image_final, self.base_left, radius=3 + r_base, color=(255, 0, 0), thickness=-1)
if self.base_right:
cv2.circle(self.image_final, self.base_right, radius=3 + r_base, color=(0, 0, 255), thickness=-1)
# Draw line of fit
for point in self.width_infer:
point[0] = np.clip(point[0], 0, self.width - 1)
point[1] = np.clip(point[1], 0, self.height - 1)
cv2.circle(self.image_final, tuple(point.astype(int)), radius=4 + r_base, color=(0, 0, 255), thickness=-1)
# Lobes
if self.has_lobes:
for lobe in self.lobes:
cv2.circle(self.image, tuple(lobe), radius=6 + r_base, color=(0, 255, 255), thickness=3)
def restrictions(self):
# self.check_tips()
self.connect_midvein_to_tips()
self.connect_petiole_to_midvein()
self.check_crossing_width()
def check_tips(self): # TODO need to check the sides to prevent base from ending up on the tip side. just need to check which side of the oredered list to pull from
if max([self.height, self.width]) < 200:
scale_factor = 0.25
elif max([self.height, self.width]) < 500:
scale_factor = 0.5
else:
scale_factor = 1
if self.has_lamina_base:
second_last_dir = np.array(self.ordered_midvein[-1]) - np.array(self.lamina_base)
end_vector_mag = np.linalg.norm(second_last_dir)
avg_dist = np.mean([np.linalg.norm(np.array(self.ordered_midvein[i])-np.array(self.ordered_midvein[i-1])) for i in range(1, len(self.ordered_midvein))])
if (end_vector_mag > (scale_factor * 0.01 * avg_dist * len(self.ordered_midvein))):
self.lamina_base = self.ordered_midvein[-1]
cv2.circle(self.image, self.lamina_base, radius=4, color=(0, 0, 0), thickness=-1)
cv2.circle(self.image, self.lamina_base, radius=8, color=(0, 0, 255), thickness=2)
self.logger.debug(f'Check Tips - lamina base - made lamina base the last midvein point')
else:
self.logger.debug(f'Check Tips - lamina base - kept lamina base')
if self.has_lamina_tip:
second_last_dir = np.array(self.ordered_midvein[0]) - np.array(self.lamina_tip)
end_vector_mag = np.linalg.norm(second_last_dir)
avg_dist = np.mean([np.linalg.norm(np.array(self.ordered_midvein[i])-np.array(self.ordered_midvein[i-1])) for i in range(1, len(self.ordered_midvein))])
if (end_vector_mag > (scale_factor * 0.01 * avg_dist * len(self.ordered_midvein))):
self.lamina_tip = self.ordered_midvein[-1]
cv2.circle(self.image, self.lamina_tip, radius=4, color=(0, 0, 0), thickness=-1)
cv2.circle(self.image, self.lamina_tip, radius=8, color=(0, 0, 255), thickness=2)
self.logger.debug(f'Check Tips - lamina tip - made lamina tip the first midvein point')
else:
self.logger.debug(f'Check Tips - lamina tip - kept lamina tip')
def connect_midvein_to_tips(self):
self.logger.debug(f'Restrictions [Midvein Connect] - connect_midvein_to_tips()')
if self.has_midvein:
if self.has_lamina_tip:
original_lamina_tip = self.lamina_tip
start_or_end = self.add_tip(self.lamina_tip)
self.logger.debug(f'Restrictions [Midvein Connect] - Lamina tip [{self.lamina_tip}]')
self.ordered_midvein, move_midvein = self.check_momentum_complex(self.ordered_midvein, True, start_or_end)
if move_midvein: # the tip changed the momentum too much
self.logger.debug(f'Restrictions [Midvein Connect] - REDO APEX ANGLE - SWAP LAMINA TIP FOR FIRST MIDVEIN POINT')
# get midvein point cloases to tip
# new_endpoint_side, _ = self.get_closest_point_to_sampled_points(self.ordered_midvein, original_lamina_tip)
# new_endpoint, _ = self.get_closest_point_to_sampled_points([self.ordered_midvein[0], self.ordered_midvein[-1]], new_endpoint_side)
# change the apex to new endpoint
self.lamina_tip = self.ordered_midvein[0]
self.apex_center = self.ordered_midvein[0]
self.determine_lamina_length('QC')
self.determine_apex_redo()
# cv2.imshow('img', self.image)
# cv2.waitKey(0)
# self.order_points_plot(self.ordered_midvein, 'midvein_trace')
self.logger.debug(f'Restrictions [Midvein Connect] - connected lamina tip to midvein')
else:
self.logger.debug(f'Restrictions [Midvein Connect] - lacks lamina tip')
if self.has_lamina_base:
original_lamina_base = self.lamina_base
start_or_end = self.add_tip(self.lamina_base)
self.logger.debug(f'Restrictions [Midvein Connect] - Lamina base [{self.lamina_base}]')
self.ordered_midvein, move_midvein = self.check_momentum_complex(self.ordered_midvein, True, start_or_end)
if move_midvein: # the tip changed the momentum too much
self.logger.debug(f'Restrictions [Midvein Connect] - REDO BASE ANGLE - SWAP LAMINA BASE FOR LAST MIDVEIN POINT')
# get midvein point cloases to tip
# new_endpoint_side, _ = self.get_closest_point_to_sampled_points(self.ordered_midvein, original_lamina_base)
# new_endpoint, _ = self.get_closest_point_to_sampled_points([self.ordered_midvein[0], self.ordered_midvein[-1]], new_endpoint_side)
# change the apex to new endpoint
self.lamina_base = self.ordered_midvein[-1]
self.base_center = self.ordered_midvein[-1]
self.determine_lamina_length('QC')
self.determine_base_redo()
# self.order_points_plot(self.ordered_midvein, 'midvein_trace')
self.logger.debug(f'Restrictions [Midvein Connect] - connected lamina base to midvein')
else:
self.logger.debug(f'Restrictions [Midvein Connect] - lacks lamina base')
def connect_petiole_to_midvein(self):
if self.has_ordered_petiole and self.has_midvein:
if len(self.ordered_petiole) > 0 and len(self.ordered_midvein) > 0:
# Find the closest pair of points between ordered_petiole and ordered_midvein
min_dist = np.inf
closest_petiole_idx = None
closest_midvein_idx = None
for i, petiole_point in enumerate(self.ordered_petiole):
for j, midvein_point in enumerate(self.ordered_midvein):
# Convert petiole_point and midvein_point to NumPy arrays
petiole_point = np.array(petiole_point)
midvein_point = np.array(midvein_point)
# Calculate the distance between the two points
dist = np.linalg.norm(petiole_point - midvein_point)
if dist < min_dist:
min_dist = dist
closest_petiole_idx = i
closest_midvein_idx = j
# Calculate the midpoint between the closest points
petiole_point = self.ordered_petiole[closest_petiole_idx]
midvein_point = self.ordered_midvein[closest_midvein_idx]
midpoint = (int((petiole_point[0] + midvein_point[0]) / 2), int((petiole_point[1] + midvein_point[1]) / 2))
# Determine whether the midpoint should be added to the beginning or end of each list
petiole_dist_to_end = np.linalg.norm(np.array(self.ordered_petiole[closest_petiole_idx]) - np.array(self.ordered_petiole[-1]))
midvein_dist_to_end = np.linalg.norm(np.array(self.ordered_midvein[closest_midvein_idx]) - np.array(self.ordered_midvein[-1]))
if (petiole_dist_to_end < midvein_dist_to_end):
# Add the midpoint to the end of the petiole list and the beginning of the midvein list
self.ordered_midvein.insert(0, midpoint)
self.ordered_petiole.append(midpoint)
self.lamina_base = midpoint
cv2.circle(self.image, self.lamina_base, radius=4, color=(0, 255, 0), thickness=-1)
cv2.circle(self.image, self.lamina_base, radius=6, color=(0, 0, 0), thickness=2)
else:
# Add the midpoint to the end of the midvein list and the beginning of the petiole list
self.ordered_petiole.insert(0, midpoint)
self.ordered_midvein.append(midpoint)
self.lamina_base = midpoint
cv2.circle(self.image, self.lamina_base, radius=4, color=(0, 255, 0), thickness=-1)
cv2.circle(self.image, self.lamina_base, radius=6, color=(0, 0, 0), thickness=2)
# If the momentum changed, then move the apex/base centers to the begninning/end of the new midvein.
# self.ordered_midvein, move_midvein = self.check_momentum(self.ordered_midvein, True)
# self.ordered_petiole, move_petiole = self.check_momentum(self.ordered_petiole, True)
# if move_midvein or move_petiole:
# self.logger.debug(f'')
self.order_points_plot(self.ordered_midvein, 'midvein_trace', 'QC')
self.order_points_plot(self.ordered_petiole, 'petiole_trace', 'QC')
def check_crossing_width(self):
self.logger.debug(f'Restrictions [Crossing Width Line] - check_crossing_width()')
self.width_infer = None
if self.has_width:
self.logger.debug(f'Restrictions [Crossing Width Line] - has width')
# Given two points
x1, y1 = self.width_left
x2, y2 = self.width_right
# Calculate the slope and y-intercept
denom = (x2 - x1)
if denom == 0:
denom = 0.00000000001
m = (y2 - y1) / denom
b = y1 - m * x1
line_params = [m, b]
self.restrict_by_width_relation(line_params)
elif not self.has_width:
# generate approximate width line
self.logger.debug(f'Restrictions [Crossing Width Line] - infer width')
if self.has_apex and self.has_base:
line_params = self.infer_width_relation()
self.restrict_by_width_relation(line_params)
else:
self.has_ordered_petiole = False
self.has_apex = False
self.has_base = False
self.has_valid_apex_loc = False
self.has_valid_base_loc = False
self.logger.debug(f'Restrictions [Crossing Width Line] - CANNOT VALIDATE APEX, BASE, PETIOLE LOCATIONS')
else:
self.logger.debug(f'Restrictions [Crossing Width Line] - width fail *** ERROR ***')
def infer_width_relation(self):
top = [np.array((self.apex_center[0], self.apex_center[1])), np.array((self.apex_left[0], self.apex_left[1])), np.array((self.apex_right[0], self.apex_right[1]))]
bottom = [np.array((self.base_center[0], self.base_center[1])), np.array((self.base_left[0], self.base_left[1])), np.array((self.base_right[0], self.base_right[1]))]
if self.has_ordered_petiole:
bottom = bottom + [np.array(pt) for pt in self.ordered_petiole]
if self.has_midvein:
midvein = np.array(self.ordered_midvein)
self.logger.debug(f'Restrictions [Crossing Width Line] - infer width - using midvein points')
else:
self.logger.debug(f'Restrictions [Crossing Width Line] - infer width - estimating midvein points')
x_increment = (centroid2[0] - centroid1[0]) / 11
y_increment = (centroid2[1] - centroid1[1]) / 11
midvein = []
for i in range(1, 11):
x = centroid1[0] + i * x_increment
y = centroid1[1] + i * y_increment
midvein.append([x, y])
# find the centroids of each group of points
centroid1 = np.mean(top, axis=0)
centroid2 = np.mean(bottom, axis=0)
# calculate the midpoint between the centroids
midpoint = (centroid1 + centroid2) / 2
# calculate the vector between the centroids
centroid_vector = centroid2 - centroid1
# calculate the vector perpendicular to the centroid vector
perp_vector = np.array([-centroid_vector[1], centroid_vector[0]])
# normalize the perpendicular vector
perp_unit_vector = perp_vector / np.linalg.norm(perp_vector)
# define the length of the line segment
# line_segment_length = np.linalg.norm(centroid_vector) / 2
# calculate the maximum length of the line segment that can be drawn inside the image
max_line_segment_length = min(midpoint[0], midpoint[1], self.width - midpoint[0], self.height - midpoint[1])
# calculate the step size
step_size = max_line_segment_length / 5
# generate 10 points along the line that is perpendicular to the centroid vector and goes through the midpoint
points = []
for i in range(-5, 6):
point = midpoint + i * step_size * perp_unit_vector
points.append(point)
# find the equation of the line passing through the midpoint and with the perpendicular unit vector as the slope
b = midpoint[1] - perp_unit_vector[1] * midpoint[0]
if perp_unit_vector[0] == 0:
denom = 0.0000000001
else:
denom = perp_unit_vector[0]
m = perp_unit_vector[1] / denom
self.width_infer = points
# Draw line of fit
for point in points:
point[0] = np.clip(point[0], 0, self.width - 1)
point[1] = np.clip(point[1], 0, self.height - 1)
cv2.circle(self.image, tuple(point.astype(int)), radius=2, color=(0, 0, 255), thickness=-1)
return [m, b]
def restrict_by_width_relation(self, line_params):
'''
Are the tips on the same side
'''
if self.has_lamina_base and self.has_lamina_tip:
loc_tip = self.point_position_relative_to_line(self.lamina_tip, line_params)
loc_base = self.point_position_relative_to_line(self.lamina_base, line_params)
if loc_tip == loc_base:
self.has_lamina_base = False
self.has_lamina_tip = False
cv2.circle(self.image, self.lamina_tip, radius=5, color=(0, 0, 0), thickness=2) # pink solid
cv2.circle(self.image, self.lamina_base, radius=5, color=(0, 0, 0), thickness=2) # purple
self.logger.debug(f'Restrictions [Lamina Tip/Base] - fail - Lamina tip and base are on same side')
else:
self.logger.debug(f'Restrictions [Lamina Tip/Base] - pass - Lamina tip and base are on opposite side')
'''
are all apex and base values on their respecitive sides?
'''
self.has_valid_apex_loc = False
self.has_valid_base_loc = False
apex_side = 'NA'
base_side = 'NA'
if self.has_apex:
loc_left = self.point_position_relative_to_line(self.apex_left, line_params)
loc_right = self.point_position_relative_to_line(self.apex_right, line_params)
loc_center = self.point_position_relative_to_line(self.apex_center, line_params)
if loc_left == loc_right == loc_center: # all the same
apex_side = loc_center
self.has_valid_apex_loc = True
else:
self.has_valid_apex_loc = False
self.logger.debug(f'Restrictions [Angles] - has_valid_apex_loc = False, apex loc crosses width')
else:
self.logger.debug(f'Restrictions [Angles] - has_valid_apex_loc = False, no apex')
if self.has_base:
loc_left_b = self.point_position_relative_to_line(self.base_left, line_params)
loc_right_b = self.point_position_relative_to_line(self.base_right, line_params)
loc_center_b = self.point_position_relative_to_line(self.base_center, line_params)
if loc_left_b == loc_right_b == loc_center_b: # all the same
base_side = loc_center_b
self.has_valid_base_loc = True
else:
self.logger.debug(f'Restrictions [Angles] - has_valid_base_loc = False, base loc crosses width')
else:
self.logger.debug(f'Restrictions [Angles] - has_valid_base_loc = False')
if self.has_valid_apex_loc and self.has_valid_base_loc and (base_side != apex_side):
self.logger.debug(f'Restrictions [Angles] - pass - apex and base')
elif (base_side == apex_side) and (self.has_apex) and (self.has_base):
self.has_valid_apex_loc = False
self.has_valid_base_loc = False
### This is most restrictive
self.has_apex = False
self.has_base = False
self.order_points_plot([self.apex_left, self.apex_center, self.apex_right], 'failed_angle', 'QC')
self.order_points_plot([self.base_left, self.base_center, self.base_right], 'failed_angle', 'QC')
self.logger.debug(f'Restrictions [Angles] - fail - apex and base')
elif (not self.has_valid_apex_loc) and (self.has_apex):
self.has_apex = False
self.order_points_plot([self.apex_left, self.apex_center, self.apex_right], 'failed_angle', 'QC')
self.logger.debug(f'Restrictions [Angles] - fail - apex')
elif (not self.has_valid_base_loc) and (self.has_base):
self.has_base = False
self.order_points_plot([self.base_left, self.base_center, self.base_right], 'failed_angle', 'QC')
self.logger.debug(f'Restrictions [Angles] - fail - base')
else:
self.logger.debug(f'Restrictions [Angles] - no change')
'''
does the petiole cross the width loc?
'''
if self.has_ordered_petiole:
petiole_check = []
for point in self.ordered_petiole:
check_val = self.point_position_relative_to_line(point, line_params)
petiole_check.append(check_val)
petiole_check = list(set(petiole_check))
self.logger.debug(f'Restrictions [Petiole] - petiole set = {petiole_check}')
if len(petiole_check) == 1:
self.has_ordered_petiole = True # Keep the petiole
petiole_check = petiole_check[0]
self.logger.debug(f'Restrictions [Petiole] - petiole does not cross width - pass')
else:
self.has_ordered_petiole = False # Reject the petiole, it crossed the center
self.logger.debug(f'Restrictions [Petiole] - petiole does cross width - fail')
else:
self.logger.debug(f'Restrictions [Petiole] - has_ordered_petiole = False')
'''
Is the lamina base on the same side as the petiole?
happens after the other checks...
'''
if self.has_lamina_base and self.has_lamina_tip and self.has_ordered_petiole:
# base is not on the same side as petiole, swap IF base and tip are already opposite
if loc_base != petiole_check:
if loc_base != loc_tip: # make sure that the tips are on opposite sides, if yes, swap the base and tip
hold_data = self.lamina_tip
self.lamina_tip = self.lamina_base
self.lamina_base = hold_data
cv2.circle(self.image, self.lamina_tip, radius=9, color=(255, 0, 230), thickness=2) # pink solid
cv2.circle(self.image, self.lamina_base, radius=9, color=(0, 100, 255), thickness=2) # purple
self.logger.debug(f'Restrictions [Petiole/Lamina Tip Same Side] - pass - swapped lamina tip and lamina base')
else:
self.has_lamina_base = False
self.has_lamina_tip = False
self.logger.debug(f'Restrictions [Petiole/Lamina Tip Same Side] - fail - lamina base not on same side as petiole, base and tip are on same side')
else: # base is on correct side
if loc_base == loc_tip: # base and tip are on the same side. error
self.has_lamina_base = False
self.has_lamina_tip = False
self.logger.debug(f'Restrictions [Petiole/Lamina Tip Same Side] - fail - base and tip are on the same side, but base and petiole are ok')
else:
self.logger.debug(f'Restrictions [Petiole/Lamina Tip Same Side] - pass - no swap')
def add_tip(self, tip):
# Calculate the distances between the first and last points in midvein and the new point
dist_start = math.dist(self.ordered_midvein[0], tip)
dist_end = math.dist(self.ordered_midvein[-1], tip)
# Append tip to the beginning of the list if it's closer to the first point, otherwise append it to the end of the list
if dist_start < dist_end:
self.ordered_midvein.insert(0, tip)
start_or_end = 'start'
self.logger.debug(f'Restrictions [Midvein Connect] - tip added to beginning of ordered_midvein')
else:
self.ordered_midvein.append(tip)
start_or_end = 'end'
self.logger.debug(f'Restrictions [Midvein Connect] - tip added to end of ordered_midvein')
return start_or_end
def find_min_width(self, left, right):
left_vectors = np.array(left)[:, np.newaxis, :]
right_vectors = np.array(right)[np.newaxis, :, :]
distances = np.linalg.norm(left_vectors - right_vectors, axis=2)
indices = np.unravel_index(np.argmin(distances), distances.shape)
return left[indices[0]], right[indices[1]]
def find_most_orthogonal_vectors(self, left, right, midvein_fit):
left_vectors = np.array(left)[:, np.newaxis, :] - np.array(right)[np.newaxis, :, :]
right_vectors = -left_vectors
midvein_vector = np.array(midvein_fit[-1]) - np.array(midvein_fit[0])
midvein_vector /= np.linalg.norm(midvein_vector)
dot_products = np.abs(np.sum(left_vectors * midvein_vector, axis=2)) + np.abs(np.sum(right_vectors * midvein_vector, axis=2))
indices = np.unravel_index(np.argmax(dot_products), dot_products.shape)
return left[indices[0]], right[indices[1]]
def determine_reflex(self, apex_left, apex_right, apex_center):
vector_left_to_center = np.array([apex_center[0] - apex_left[0], apex_center[1] - apex_left[1]])
vector_right_to_center = np.array([apex_center[0] - apex_right[0], apex_center[1] - apex_right[1]])
# Calculate the vector pointing to the average midvein trace value
midvein_trace_arr = np.array([(x, y) for x, y in self.ordered_midvein])
midvein_trace_avg = midvein_trace_arr.mean(axis=0)
vector_to_midvein_trace = midvein_trace_avg - np.array(apex_center)
# Determine whether the angle is reflex or not
if np.dot(vector_left_to_center, vector_to_midvein_trace) > 0 and np.dot(vector_right_to_center, vector_to_midvein_trace) > 0:
angle_type = 'reflex'
else:
angle_type = 'not_reflex'
angle = self.calculate_angle(apex_left, apex_center, apex_right)
if angle_type == 'reflex':
angle = 360 - angle
self.order_points_plot([apex_left, apex_center, apex_right], angle_type, 'QC')
return angle_type, angle
def calculate_angle(self, p1, p2, p3):
# Calculate the vectors between the points
v1 = (p1[0] - p2[0], p1[1] - p2[1])
v2 = (p3[0] - p2[0], p3[1] - p2[1])
# Calculate the dot product and magnitudes of the vectors
dot_product = v1[0]*v2[0] + v1[1]*v2[1]
mag_v1 = math.sqrt(v1[0]**2 + v1[1]**2)
mag_v2 = math.sqrt(v2[0]**2 + v2[1]**2)
if mag_v1 == 0:
mag_v1 = 0.000000001
if mag_v2 == 0:
mag_v2 = 0.000000001
# Calculate the cosine of the angle
denom = (mag_v1 * mag_v2)
if denom == 0:
denom = 0.000000001
cos_angle = dot_product / denom
# Calculate the angle in radians and degrees
angle_rad = math.acos(min(max(cos_angle, -1), 1))
angle_deg = math.degrees(angle_rad)
return angle_deg
def calc_min_distance(self, point, reference_points):
# Convert the points and reference points to numpy arrays
points_arr = np.atleast_2d(point)
reference_arr = np.array(reference_points)
# Calculate the distances between each point in "points" and each point in "reference_points"
dists = np.linalg.norm(points_arr[:, np.newaxis, :] - reference_arr, axis=2)
distance = np.min(dists, axis=1)
return distance
def get_closest_point_to_sampled_points(self, points, reference_points):
# Convert the points and reference points to numpy arrays
points_arr = np.array(points)
reference_arr = np.array(reference_points)
# Calculate the distances between each point in "points" and each point in "reference_points"
dists = np.linalg.norm(points_arr[:, np.newaxis, :] - reference_arr, axis=2)
distances = np.min(dists, axis=1)
# Get the index of the closest point
closest_idx = np.argmin(distances)
# Remove the closest point from the list of points
return points.pop(closest_idx), points
def get_far_point(self, points, reference_point):
# Calculate the distances between each point and the reference points
distances = [math.dist(point, reference_point) for point in points]
# Get the index of the closest point
closest_idx = distances.index(max(distances))
far_point = points.pop(closest_idx)
# Remove the closest point from the list of points
return far_point, points
'''def point_position_relative_to_line(self, point, line_params):
# Extract the cubic coefficients from the line parameters
a, b, c, d = line_params
# Determine the x-coordinate of the point where it intersects with the line
# We solve the cubic equation ax^3 + bx^2 + cx + d = y for x, given y = point[1]
f = lambda x: a*x**3 + b*x**2 + c*x + d - point[1]
roots = np.roots([a, b, c, d-point[1]])
real_roots = roots[np.isreal(roots)].real
if len(real_roots) == 0:
return "left" # point is below the curve
x_intersection = real_roots[0]
# Determine the midpoint of the line
mid_x = self.width / 2
mid_y = self.height / 2
# Determine if the point is to the left or right of the line
if self.height > self.width:
if point[0] < x_intersection:
return "left"
else:
return "right"
else:
if point[1] < a*mid_x**3 + b*mid_x**2 + c*mid_x + d:
return "left"
else:
return "right"'''
def point_position_relative_to_line(self, point, line_params):
# Extract the slope and y-intercept from the line parameters
slope, y_intercept = line_params
if (slope == 0.0) or (slope == 0):
slope = 0.00000000000001
# Determine the x-coordinate of the point where it intersects with the line
x_intersection = (point[1] - y_intercept) / slope
# Determine the midpoint of the line
mid_x = self.width / 2
mid_y = self.height / 2
# Determine if the point is to the left or right of the line
if self.height > self.width:
if point[0] < x_intersection:
return "left"
else:
return "right"
else:
if point[1] < slope * (point[0] - mid_x) + mid_y:
return "left" #below
else:
return "right" #above
def rotate_points(self, points, angle_cw):
# Calculate the center of the image
center_x = self.width / 2
center_y = self.height / 2
# Translate the points to the center
translated_points = [(point[0]-center_x, point[1]-center_y) for point in points]
# Convert the angle to radians
angle_cw = math.radians(angle_cw)
# Rotate the points
rotated_points = [(round(point[0]*math.cos(angle_cw)-point[1]*math.sin(angle_cw)), round(point[0]*math.sin(angle_cw)+point[1]*math.cos(angle_cw))) for point in translated_points]
# Translate the points back to the original origin
return [(point[0]+center_x, point[1]+center_y) for point in rotated_points]
def order_petiole(self):
if 'petiole_trace' in self.points_list:
if len(self.points_list['petiole_trace']) >= 5:
self.logger.debug(f"Ordered Petiole - Raw list contains {len(self.points_list['petiole_trace'])} points - using momentum")
self.ordered_petiole = self.order_points(self.points_list['petiole_trace'])
self.ordered_petiole = self.remove_duplicate_points(self.ordered_petiole)
self.ordered_petiole = self.check_momentum(self.ordered_petiole, False)
self.order_points_plot(self.ordered_petiole, 'petiole_trace', 'QC')
self.ordered_petiole_length, self.ordered_petiole = self.get_length_of_ordered_points(self.ordered_petiole, 'petiole_trace')
self.has_ordered_petiole = True
elif len(self.points_list['petiole_trace']) >= 2:
self.logger.debug(f"Ordered Petiole - Raw list contains {len(self.points_list['petiole_trace'])} points - SKIPPING momentum")
self.ordered_petiole = self.order_points(self.points_list['petiole_trace'])
self.ordered_petiole = self.remove_duplicate_points(self.ordered_petiole)
self.order_points_plot(self.ordered_petiole, 'petiole_trace', 'QC')
self.ordered_petiole_length, self.ordered_petiole = self.get_length_of_ordered_points(self.ordered_petiole, 'petiole_trace')
self.has_ordered_petiole = True
else:
self.logger.debug(f"Ordered Petiole - Raw list contains {len(self.points_list['petiole_trace'])} points - SKIPPING PETIOLE")
def order_midvein(self):
if 'midvein_trace' in self.points_list:
if len(self.points_list['midvein_trace']) >= 5:
self.logger.debug(f"Ordered Midvein - Raw list contains {len(self.points_list['midvein_trace'])} points - using momentum")
self.ordered_midvein = self.order_points(self.points_list['midvein_trace'])
self.ordered_midvein = self.remove_duplicate_points(self.ordered_midvein)
self.ordered_midvein = self.check_momentum(self.ordered_midvein, False)
self.order_points_plot(self.ordered_midvein, 'midvein_trace', 'QC')
self.ordered_midvein_length, self.ordered_midvein = self.get_length_of_ordered_points(self.ordered_midvein, 'midvein_trace')
self.has_midvein = True
else:
self.logger.debug(f"Ordered Midvein - Raw list contains {len(self.points_list['midvein_trace'])} points - SKIPPING MIDVEIN")
def check_momentum(self, coords, info):
original_coords = coords
# find middle index of coordinates
mid_idx = len(coords) // 2
# set up variables for running average
running_avg = np.array(coords[mid_idx-1])
avg_count = 1
# iterate over coordinates to check momentum change
prev_vec = np.array(coords[mid_idx-1]) - np.array(coords[mid_idx-2])
cur_idx = mid_idx - 1
while cur_idx >= 0:
cur_vec = np.array(coords[cur_idx]) - np.array(coords[cur_idx-1])
# add current point to running average
running_avg = (running_avg * avg_count + np.array(coords[cur_idx])) / (avg_count + 1)
avg_count += 1
# check for momentum change
if self.check_momentum_change(prev_vec, cur_vec):
break
prev_vec = cur_vec
cur_idx -= 1
# use running average to check for momentum change
cur_vec = np.array(coords[cur_idx]) - running_avg
if self.check_momentum_change(prev_vec, cur_vec):
cur_idx += 1
prev_vec = np.array(coords[mid_idx+1]) - np.array(coords[mid_idx])
cur_idx2 = mid_idx + 1
while cur_idx2 < len(coords):
# check if current index is out of range
if cur_idx2 >= len(coords):
break
cur_vec = np.array(coords[cur_idx2]) - np.array(coords[cur_idx2-1])
# add current point to running average
running_avg = (running_avg * avg_count + np.array(coords[cur_idx2])) / (avg_count + 1)
avg_count += 1
# check for momentum change
if self.check_momentum_change(prev_vec, cur_vec):
break
prev_vec = cur_vec
cur_idx2 += 1
# use running average to check for momentum change
if cur_idx2 < len(coords):
cur_vec = np.array(coords[cur_idx2]) - running_avg
if self.check_momentum_change(prev_vec, cur_vec):
cur_idx2 -= 1
# remove problematic points and subsequent points from list of coordinates
new_coords = coords[:cur_idx2] + coords[mid_idx:cur_idx2:-1]
if info:
return new_coords, len(original_coords) != len(new_coords)
else:
return new_coords
# define function to check for momentum change
def check_momentum_change(self, prev_vec, cur_vec):
dot_product = np.dot(prev_vec, cur_vec)
prev_norm = np.linalg.norm(prev_vec)
cur_norm = np.linalg.norm(cur_vec)
denom = (prev_norm * cur_norm)
if denom == 0:
denom = 0.0000000001
cos_theta = dot_product / denom
theta = np.arccos(cos_theta)
return abs(theta) > np.pi / 2
'''def check_momentum_complex(self, coords, info, start_or_end):
original_coords = coords
# find middle index of coordinates
mid_idx = len(coords) // 2
# get directional vectors for first-middle, middle-last, and second-first and second-last pairs of points
first_middle_dir = np.array(coords[1]) - np.array(coords[0])
middle_last_dir = np.array(coords[-1]) - np.array(coords[-2])
second_first_dir = np.array(coords[1]) - np.array(coords[2])
second_last_dir = np.array(coords[-1]) - np.array(coords[-3])
if start_or_end == 'end':
# check directional change for first-middle vector
cur_idx = 2
while cur_idx < len(coords):
cur_vec = np.array(coords[cur_idx]) - np.array(coords[cur_idx-1])
if self.check_momentum_change_complex(first_middle_dir, cur_vec):
break
cur_idx += 1
cur_idx2 = len(coords) - 2
elif start_or_end == 'start':
# check directional change for last-middle vector
cur_idx2 = len(coords)-3
while cur_idx2 >= 0:
cur_vec = np.array(coords[cur_idx2]) - np.array(coords[cur_idx2+1])
if self.check_momentum_change_complex(middle_last_dir, cur_vec):
break
cur_idx2 -= 1
cur_idx = 1
# check directional change for second-first and second-last vectors
second_first_change = self.check_momentum_change_complex(second_first_dir, first_middle_dir)
second_last_change = self.check_momentum_change_complex(second_last_dir, middle_last_dir)
# remove problematic points and subsequent points from list of coordinates
if cur_idx <= cur_idx2:
new_coords = coords[:cur_idx+1] + coords[cur_idx2:mid_idx:-1] + coords[cur_idx+1:cur_idx2+1]
else:
new_coords = coords[:mid_idx+1] + coords[cur_idx2:cur_idx:-1] + coords[mid_idx+1:cur_idx2+1]
self.logger.debug(f'Original midvein points - {self.ordered_midvein}')
self.logger.debug(f'Momentum midvein points - {new_coords}')
if info:
return new_coords, len(original_coords) != len(new_coords) or second_first_change or second_last_change
else:
return new_coords'''
def check_momentum_complex(self, coords, info, start_or_end): # Works, but removes ALL points after momentum change
original_coords = coords
if max([self.height, self.width]) < 200:
scale_factor = 0.25
elif max([self.height, self.width]) < 500:
scale_factor = 0.5
else:
scale_factor = 1
self.logger.debug(f'Scale factor - [{scale_factor}]')
# find middle index of coordinates
mid_idx = len(coords) // 2
# get directional vectors for first-middle, middle-last, and second-first and second-last pairs of points
first_middle_dir = np.array(coords[1]) - np.array(coords[mid_idx])
middle_last_dir = np.array(coords[mid_idx]) - np.array(coords[-2])
second_first_dir = np.array(coords[1]) - np.array(coords[0])
second_last_dir = np.array(coords[-1]) - np.array(coords[-2])
if start_or_end == 'end':
# check directional change for first-middle vector
cur_idx_list = []
cur_idx = 2
while cur_idx < len(coords):
cur_vec = np.array(coords[cur_idx]) - np.array(coords[cur_idx-1])
if self.check_momentum_change_complex(first_middle_dir, cur_vec):
# break
cur_idx_list.append(cur_idx)
cur_idx += 1
if len(cur_idx_list) > 0:
cur_idx = max(cur_idx_list)
else:
cur_idx = len(coords)
# remove problematic points and subsequent points from list of coordinates
end_vector_mag = np.linalg.norm(second_last_dir)
avg_dist = np.mean([np.linalg.norm(np.array(coords[i])-np.array(coords[i-1])) for i in range(1, len(coords))])
new_coords = coords
if (end_vector_mag > (scale_factor * 0.01 * avg_dist * len(new_coords))) and (len(cur_idx_list) > 0):
# new_coords = coords[:cur_idx+1] + coords[-2:cur_idx:-1][::-1] #coords[-2:cur_idx:-1]
new_coords = coords[:len(new_coords)-1]# + coords[-2:cur_idx:-1][::-1] #coords[-2:cur_idx:-1]
self.logger.debug(f'Momentum - removing last point')
else:
self.logger.debug(f'Momentum - change not detected, no change')
elif start_or_end == 'start':
# check directional change for last-middle vector
cur_idx2_list = []
cur_idx2 = len(coords)-3
while cur_idx2 >= 0:
cur_vec = np.array(coords[cur_idx2]) - np.array(coords[cur_idx2+1])
if self.check_momentum_change_complex(middle_last_dir, cur_vec):
# break
cur_idx2_list.append(cur_idx2)
cur_idx2 -= 1
if len(cur_idx2_list) > 0:
cur_idx2 = min(cur_idx2_list)
else:
cur_idx2 = 0
# remove problematic points and subsequent points from list of coordinates
new_coords = coords
start_vector_mag = np.linalg.norm(second_first_dir)
avg_dist = np.mean([np.linalg.norm(np.array(coords[i])-np.array(coords[i-1])) for i in range(1, len(coords))])
if (start_vector_mag > (scale_factor * 0.01 * avg_dist * len(new_coords))) and (len(cur_idx2_list) > 0):
# new_coords = coords[:mid_idx+1] + coords[cur_idx2:mid_idx:-1][::-1] # #coords[cur_idx2:mid_idx:-1]
new_coords = coords[1:]#ur_idx2-1] + coords[mid_idx+1:]
# new_coords = coords[cur_idx2:mid_idx+1][::-1] + coords[mid_idx+1:]
self.logger.debug(f'Momentum - removing first point')
else:
self.logger.debug(f'Momentum - change not detected, no change')
else:
print('hi')
# check directional change for second-first and second-last vectors
# second_first_change = self.check_momentum_change_complex(second_first_dir, first_middle_dir)
# second_last_change = self.check_momentum_change_complex(second_last_dir, middle_last_dir)
self.logger.debug(f'Original midvein points complex - {start_or_end} - {self.ordered_midvein}')
self.logger.debug(f'Momentum midvein points complex - {start_or_end} - {new_coords}')
if info:
return new_coords, len(original_coords) != len(new_coords) #or second_first_change or second_last_change
else:
return new_coords
'''def check_momentum_complex(self, coords, info, start_or_end): # does not seem to work
original_coords = coords
# get directional vectors for first-middle, middle-last, and second-first and second-last pairs of points
first_middle_dir = np.array(coords[1]) - np.array(coords[0])
middle_last_dir = np.array(coords[-1]) - np.array(coords[-2])
second_first_dir = np.array(coords[1]) - np.array(coords[2])
second_last_dir = np.array(coords[-1]) - np.array(coords[-3])
# calculate running average momentum and check if endpoints are different
if start_or_end == 'end':
end_vector_mag = np.linalg.norm(first_middle_dir)
avg_dist = np.mean([np.linalg.norm(np.array(coords[i])-np.array(coords[i-1])) for i in range(1, len(coords))])
running_avg_momentum = np.mean([np.linalg.norm(np.array(coords[i])-np.array(coords[i-1])) for i in range(len(coords)-10, len(coords))])
endpoint_diff = np.linalg.norm(np.array(coords[-1])-self.ordered_midvein[-1]) > 0.1*running_avg_momentum
elif start_or_end == 'start':
start_vector_mag = np.linalg.norm(middle_last_dir)
avg_dist = np.mean([np.linalg.norm(np.array(coords[i])-np.array(coords[i-1])) for i in range(1, len(coords))])
running_avg_momentum = np.mean([np.linalg.norm(np.array(coords[i])-np.array(coords[i-1])) for i in range(10)])
endpoint_diff = np.linalg.norm(np.array(coords[0])-self.ordered_midvein[0]) > 0.1*running_avg_momentum
# remove problematic points and subsequent points from list of coordinates
if start_or_end == 'end' and endpoint_diff:
cur_idx = 2
while cur_idx < len(coords):
cur_vec = np.array(coords[cur_idx]) - np.array(coords[cur_idx-1])
if self.check_momentum_change_complex(first_middle_dir, cur_vec):
break
cur_idx += 1
new_coords = coords[:cur_idx+1] + coords[-2:cur_idx:-1][::-1]
elif start_or_end == 'start' and endpoint_diff:
cur_idx2 = len(coords)-3
while cur_idx2 >= 0:
cur_vec = np.array(coords[cur_idx2]) - np.array(coords[cur_idx2+1])
if self.check_momentum_change_complex(middle_last_dir, cur_vec):
break
cur_idx2 -= 1
new_coords = coords[:1] + coords[cur_idx2:0:-1][::-1]
else:
new_coords = coords
# check directional change for second-first and second-last vectors
second_first_change = self.check_momentum_change_complex(second_first_dir, first_middle_dir)
second_last_change = self.check_momentum_change_complex(second_last_dir, middle_last_dir)
self.logger.debug(f'Original midvein points - {self.ordered_midvein}')
self.logger.debug(f'Momentum midvein points - {new_coords}')
if info:
return new_coords, len(original_coords) != len(new_coords) #or second_first_change or second_last_change or endpoint_diff
else:
return new_coords'''
# define function to check for momentum change
def check_momentum_change_complex(self, prev_vec, cur_vec):
dot_product = np.dot(prev_vec, cur_vec)
prev_norm = np.linalg.norm(prev_vec)
cur_norm = np.linalg.norm(cur_vec)
denom = (prev_norm * cur_norm)
if denom == 0:
denom = 0.0000000001
cos_theta = dot_product / denom
theta = np.arccos(cos_theta)
return abs(theta) > np.pi / 2
def remove_duplicate_points(self, points):
unique_set = set()
new_list = []
for item in points:
if item not in unique_set:
unique_set.add(item)
new_list.append(item)
return new_list
def order_points_plot(self, points, version, QC_or_final):
# thk_base = 0
thk_base = 16
if version == 'midvein_trace':
# color = (0, 255, 0)
color = (0, 255, 255) # yellow
thick = 2 + thk_base
elif version == 'petiole_trace':
color = (255, 255, 0)
thick = 2 + thk_base
elif version == 'lamina_width':
color = (0, 0, 255)
thick = 2 + thk_base
elif version == 'lamina_width_alt':
color = (100, 100, 255)
thick = 2 + thk_base
elif version == 'not_reflex':
color = (200, 0, 123)
thick = 3 + thk_base
elif version == 'reflex':
color = (0, 120, 200)
thick = 3 + thk_base
elif version == 'petiole_tip_alt':
color = (255, 55, 100)
thick = 1 + thk_base
elif version == 'petiole_tip':
color = (100, 255, 55)
thick = 1 + thk_base
elif version == 'failed_angle':
color = (0, 0, 0)
thick = 3 + thk_base
# Convert the points to a numpy array and round to integer values
points_arr = np.round(np.array(points)).astype(int)
# Draw a green line connecting all of the points
if QC_or_final == 'QC':
for i in range(len(points_arr) - 1):
cv2.line(self.image, tuple(points_arr[i]), tuple(points_arr[i+1]), color, thick)
else:
for i in range(len(points_arr) - 1):
cv2.line(self.image_final, tuple(points_arr[i]), tuple(points_arr[i+1]), color, thick)
def get_length_of_ordered_points(self, points, name):
# if self.file_name == 'B_774373631_Ebenaceae_Diospyros_buxifolia__L__438-687-578-774':
# print('hi')
total_length = 0
total_length_first_pass = 0
for i in range(len(points) - 1):
x1, y1 = points[i]
x2, y2 = points[i+1]
segment_length = math.sqrt((x2-x1)**2 + (y2-y1)**2)
total_length_first_pass += segment_length
cutoff = total_length_first_pass / 2
# print(f'Total length of {name}: {total_length_first_pass}')
# print(f'points length {len(points)}')
self.logger.debug(f"Total length of {name}: {total_length_first_pass}")
self.logger.debug(f"Points length {len(points)}")
# If there are more than 2 points, this will exclude extreme outliers, or
# misordered points that don't belong
if len(points) > 2:
pop_ind = []
for i in range(len(points) - 1):
x1, y1 = points[i]
x2, y2 = points[i+1]
segment_length = math.sqrt((x2-x1)**2 + (y2-y1)**2)
if segment_length < cutoff:
total_length += segment_length
else:
pop_ind.append(i)
for exclude in pop_ind:
points.pop(exclude)
# print(f'Total length of {name}: {total_length}')
# print(f'Excluded {len(pop_ind)} points')
# print(f'points length {len(points)}')
self.logger.debug(f"Total length of {name}: {total_length}")
self.logger.debug(f"Excluded {len(pop_ind)} points")
self.logger.debug(f"Points length {len(points)}")
else:
total_length = total_length_first_pass
return total_length, points
def convert_YOLO_bbox_to_point(self):
for point_type, bbox in self.points_list.items():
xy_points = []
for point in bbox:
x = point[0]
y = point[1]
w = point[2]
h = point[3]
x1 = int((x - w/2) * self.width)
y1 = int((y - h/2) * self.height)
x2 = int((x + w/2) * self.width)
y2 = int((y + h/2) * self.height)
xy_points.append((int((x1+x2)/2), int((y1+y2)/2)))
self.points_list[point_type] = xy_points
def parse_all_points(self):
points_list = {}
for sublist in self.all_points:
key = sublist[0]
value = sublist[1:]
key = self.swap_number_for_string(key)
if key not in points_list:
points_list[key] = []
points_list[key].append(value)
# print(points_list)
self.points_list = points_list
def swap_number_for_string(self, key):
for k, v in self.classes.items():
if v == key:
return k
return key
def distance(self, point1, point2):
x1, y1 = point1
x2, y2 = point2
return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
def order_points(self, points):
# height = max(points, key=lambda point: point[1])[1] - min(points, key=lambda point: point[1])[1]
# width = max(points, key=lambda point: point[0])[0] - min(points, key=lambda point: point[0])[0]
if self.height > self.width:
start_point = min(points, key=lambda point: point[1])
end_point = max(filter(lambda point: point[0] == max(points, key=lambda point: point[0])[0], points), key=lambda point: point[1])
else:
start_point = min(points, key=lambda point: point[0])
end_point = max(filter(lambda point: point[1] == max(points, key=lambda point: point[1])[1], points), key=lambda point: point[0])
tour = [start_point]
unvisited = set(points) - {start_point}
while unvisited:
nearest = min(unvisited, key=lambda point: self.distance(tour[-1], point))
tour.append(nearest)
unvisited.remove(nearest)
tour.append(end_point)
return tour
def define_landmark_classes(self):
self.classes = {
'apex_angle': 0,
'base_angle': 1,
'lamina_base': 2,
'lamina_tip': 3,
'lamina_width': 4,
'lobe_tip': 5,
'midvein_trace': 6,
'petiole_tip': 7,
'petiole_trace': 8
}
def set_cfg_values(self):
self.do_show_QC_images = self.cfg['leafmachine']['landmark_detector']['do_show_QC_images']
self.do_save_QC_images = self.cfg['leafmachine']['landmark_detector']['do_save_QC_images']
self.do_show_final_images = self.cfg['leafmachine']['landmark_detector']['do_show_final_images']
self.do_save_final_images = self.cfg['leafmachine']['landmark_detector']['do_save_final_images']
def setup_QC_image(self):
self.image = cv2.imread(os.path.join(self.dir_temp, '.'.join([self.file_name, 'jpg'])))
if self.leaf_type == 'Landmarks_Whole_Leaves':
self.path_QC_image = os.path.join(self.Dirs.landmarks_whole_leaves_overlay_QC, '.'.join([self.file_name, 'jpg']))
elif self.leaf_type == 'Landmarks_Partial_Leaves':
self.path_QC_image = os.path.join(self.Dirs.landmarks_partial_leaves_overlay_QC, '.'.join([self.file_name, 'jpg']))
def setup_final_image(self):
self.image_final = cv2.imread(os.path.join(self.dir_temp, '.'.join([self.file_name, 'jpg'])))
if self.leaf_type == 'Landmarks_Whole_Leaves':
self.path_image_final = os.path.join(self.Dirs.landmarks_whole_leaves_overlay_final, '.'.join([self.file_name, 'jpg']))
elif self.leaf_type == 'Landmarks_Partial_Leaves':
self.path_image_final = os.path.join(self.Dirs.landmarks_partial_leaves_overlay_final, '.'.join([self.file_name, 'jpg']))
def show_QC_image(self):
if self.do_show_QC_images:
cv2.imshow('QC image', self.image)
cv2.waitKey(0)
def show_final_image(self):
if self.do_show_final_images:
cv2.imshow('Final image', self.image_final)
cv2.waitKey(0)
def save_QC_image(self):
if self.do_save_QC_images:
cv2.imwrite(self.path_QC_image, self.image)
def get_QC(self):
return self.image
def get_final(self):
return self.image_final
def init_lists_dicts(self):
# Initialize all lists and dictionaries
self.classes = {}
self.points_list = []
self.image = []
self.ordered_midvein = []
self.midvein_fit = []
self.midvein_fit_points = []
self.ordered_petiole = []
self.apex_left = self.apex_left or None
self.apex_right = self.apex_right or None
self.apex_center = self.apex_center or None
self.base_left = self.base_left or None
self.base_right = self.base_right or None
self.base_center = self.base_center or None
self.lamina_tip = self.lamina_tip or None
self.lamina_base = self.lamina_base or None
self.width_left = self.width_left or None
self.width_right = self.width_right or None