Spaces:
Sleeping
Sleeping
File size: 5,523 Bytes
8fcc1ae | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | import cv2
import numpy as np
def intersect_lines(line_a, line_b):
"""Intersect two lines given as (x1, y1, x2, y2), or None if parallel."""
x1, y1, x2, y2 = line_a
x3, y3, x4, y4 = line_b
p1 = np.array([x1, y1, 1.0])
p2 = np.array([x2, y2, 1.0])
p3 = np.array([x3, y3, 1.0])
p4 = np.array([x4, y4, 1.0])
l1 = np.cross(p1, p2)
l2 = np.cross(p3, p4)
x = np.cross(l1, l2)
if abs(x[2]) < 1e-8:
return None
return x[0] / x[2], x[1] / x[2]
def build_point_map(landmarks):
"""Index a list of landmark dicts (each with a 'name' key) by name."""
return {lm["name"]: lm for lm in landmarks}
def set_midpoint(pt_map, name, a, b):
"""Store the midpoint of two named points in pt_map under `name`."""
if (a in pt_map) and (b in pt_map):
pt_map[name] = {
"orig_x": (pt_map[a]["orig_x"] + pt_map[b]["orig_x"]) / 2,
"orig_y": (pt_map[a]["orig_y"] + pt_map[b]["orig_y"]) / 2,
}
return pt_map
def get_angle(pt_map, p1, p2, p3):
"""Angle in degrees at vertex p2, between rays p2->p1 and p2->p3."""
if (p1 not in pt_map) or (p2 not in pt_map) or (p3 not in pt_map):
return None
v21 = np.array([
pt_map[p1]["orig_x"] - pt_map[p2]["orig_x"],
pt_map[p1]["orig_y"] - pt_map[p2]["orig_y"],
])
v23 = np.array([
pt_map[p3]["orig_x"] - pt_map[p2]["orig_x"],
pt_map[p3]["orig_y"] - pt_map[p2]["orig_y"],
])
dot_product = np.dot(v21, v23)
cross_product_z = abs(v21[0] * v23[1] - v21[1] * v23[0])
angle_radians = np.arctan2(cross_product_z, dot_product)
angle_degrees = np.degrees(angle_radians)
if angle_degrees < 0:
angle_degrees += 360
return angle_degrees
def plot_line(image, pt_map, a, b, color, skip_display=False):
"""Draw a line between two named points in pt_map; returns (image, line)."""
if (a not in pt_map) or (b not in pt_map):
return image, None
px1, py1 = pt_map[a]["orig_x"], pt_map[a]["orig_y"]
px2, py2 = pt_map[b]["orig_x"], pt_map[b]["orig_y"]
if not skip_display:
cv2.line(
image,
(int(round(px1)), int(round(py1))),
(int(round(px2)), int(round(py2))),
color, 2,
)
return image, [px1, py1, px2, py2]
def plot_angle(image, pt_map, a, b, c, color):
"""Draw the angle arc at vertex B formed by A-B and B-C."""
if (a not in pt_map) or (b not in pt_map) or (c not in pt_map):
return image
xA, yA = pt_map[a]["orig_x"], pt_map[a]["orig_y"]
xB, yB = pt_map[b]["orig_x"], pt_map[b]["orig_y"]
xC, yC = pt_map[c]["orig_x"], pt_map[c]["orig_y"]
m_ba = (yB - yA) / (xB - xA)
b_ba = yB - m_ba * xB
ba = np.linspace(xB, xA, 10)
nx_a = ba[1]
ny_a = m_ba * nx_a + b_ba
pts = np.array([[
[round(nx_a), round(ny_a)],
[round(xB), round(yB)],
[round(xC), round(yC)],
]]).astype(int)
return cv2.polylines(image, [pts], isClosed=True, color=color, thickness=2)
def plot_landmarks(image, landmarks, color=(255, 255, 0), angle_color=(255, 0, 0)):
"""Draw landmark points plus the derived axes/joint-lines/angles used
for CPAK measurement on top of image (modified in place, also returned).
"""
pt_map = {}
for pt in landmarks:
pt_map[pt["name"]] = pt
if pt["name"] in ("FR", "FL"):
continue
cv2.circle(image, (int(pt["orig_x"]), int(pt["orig_y"])), 5, color, -1)
# FHR-FR, LFR-MFR LTR-MTR [FTR]-AR
# FHL-FL, LFL-MFL LTL-MTL [FTL]-AL
# FTR = (LTR+MTR)/2
# FTL = (LTL+MTL)/2
# Right femur: mechanical axis (FHR-FR) x joint line (LFR-MFR) -> FFR
image, femoral_axis_r = plot_line(image, pt_map, "FHR", "FR", color, skip_display=True)
image, joint_line_distal_femur_r = plot_line(image, pt_map, "LFR", "MFR", color)
if femoral_axis_r is not None and joint_line_distal_femur_r is not None:
intersection = intersect_lines(femoral_axis_r, joint_line_distal_femur_r)
if intersection is not None:
pt_map["FFR"] = {"orig_x": intersection[0], "orig_y": intersection[1]}
image, _ = plot_line(image, pt_map, "FHR", "FFR", color)
# Right tibia: joint line (LTR-MTR), midpoint FTR -> ankle center (AR)
image, _ = plot_line(image, pt_map, "LTR", "MTR", color)
set_midpoint(pt_map, "FTR", "LTR", "MTR")
image, _ = plot_line(image, pt_map, "FTR", "AR", color)
image = plot_angle(image, pt_map, "FHR", "FFR", "LFR", angle_color)
image = plot_angle(image, pt_map, "AR", "FTR", "MTR", angle_color)
# Left femur
image, femoral_axis_l = plot_line(image, pt_map, "FHL", "FL", color, skip_display=True)
image, joint_line_distal_femur_l = plot_line(image, pt_map, "LFL", "MFL", color)
if femoral_axis_l is not None and joint_line_distal_femur_l is not None:
intersection = intersect_lines(femoral_axis_l, joint_line_distal_femur_l)
if intersection is not None:
pt_map["FFL"] = {"orig_x": intersection[0], "orig_y": intersection[1]}
image, _ = plot_line(image, pt_map, "FHL", "FFL", color)
# Left tibia
image, _ = plot_line(image, pt_map, "LTL", "MTL", color)
set_midpoint(pt_map, "FTL", "LTL", "MTL")
image, _ = plot_line(image, pt_map, "FTL", "AL", color)
image = plot_angle(image, pt_map, "FHL", "FFL", "LFL", angle_color)
image = plot_angle(image, pt_map, "AL", "FTL", "MTL", angle_color)
return image
|