retuve.hip_xray.metrics.ace
Metric: Acentabular Index (ACE Index)
1# Copyright 2024 Adam McArthur 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15""" 16Metric: Acentabular Index (ACE Index) 17""" 18 19from typing import Literal 20 21import numpy as np 22from radstract.math import smart_find_intersection 23 24from retuve.draw import Overlay 25from retuve.hip_xray.classes import HipDataXray, LandmarksXRay 26from retuve.keyphrases.config import Config 27 28 29def find_ace(landmarks: LandmarksXRay) -> tuple[float, float]: 30 """ 31 Calculate the Acentabular Index (ACE Index) for both hips. 32 33 :param landmarks: The Landmarks of the Hip 34 :return: The ACE Index for both hips 35 """ 36 intersection_left = smart_find_intersection( 37 landmarks.pel_l_o, landmarks.pel_l_i, landmarks.fem_l, landmarks.fem_r 38 ) 39 intersection_right = smart_find_intersection( 40 landmarks.pel_r_o, landmarks.pel_r_i, landmarks.fem_l, landmarks.fem_r 41 ) 42 43 A, B, C = ( 44 np.array(landmarks.pel_l_o), 45 np.array(landmarks.pel_l_i), 46 np.array(intersection_right), 47 ) 48 AB = np.linalg.norm(A - B) 49 BC = np.linalg.norm(B - C) 50 AC = np.linalg.norm(A - C) 51 52 ace_index_left = np.arccos((BC**2 + AB**2 - AC**2) / (2 * BC * AB)) 53 54 ace_index_left = round(180 - np.degrees(ace_index_left), 1) 55 56 A, B, C = ( 57 np.array(landmarks.pel_r_o), 58 np.array(landmarks.pel_r_i), 59 np.array(intersection_left), 60 ) 61 AB = np.linalg.norm(A - B) 62 BC = np.linalg.norm(B - C) 63 AC = np.linalg.norm(A - C) 64 65 ace_index_right = np.arccos((BC**2 + AB**2 - AC**2) / (2 * BC * AB)) 66 67 ace_index_right = round(180 - np.degrees(ace_index_right), 1) 68 69 if not (5 < ace_index_left < 50): 70 ace_index_left = 0 71 72 if not (5 < ace_index_right < 50): 73 ace_index_right = 0 74 75 return ace_index_left, ace_index_right 76 77 78def extend_line( 79 p1: list, 80 p2: list, 81 scale: float = 1.2, 82 direction: Literal["both", "up", "down"] = "both", 83): 84 """ 85 Extend the line from p1 to p2 by a scale factor (default 10%) 86 in specified direction ('up', 'down', 'both'). 87 88 :param p1: The first point of the line 89 :param p2: The second point of the line 90 :param scale: The scale factor 91 :param direction: The direction to extend the lines 92 """ 93 direction_vector = np.array(p2) - np.array(p1) 94 left_point = ( 95 np.array(p1) - direction_vector * (scale - 1) 96 if direction in ["up", "both"] 97 else np.array(p1) 98 ) 99 right_point = ( 100 np.array(p2) + direction_vector * (scale - 1) 101 if direction in ["down", "both"] 102 else np.array(p2) 103 ) 104 105 return left_point, right_point 106 107 108def draw_ace(hip: HipDataXray, overlay: Overlay, config: Config): 109 """ 110 Draw the Acentabular Index (ACE Index) on the X-Ray. 111 112 :param hip: The Hip Data 113 :param overlay: The Overlay 114 :param config: The Config 115 116 :return: The Drawn Overlay 117 """ 118 landmarks = hip.landmarks 119 120 if not landmarks.fem_l: 121 return overlay 122 123 # Intersect and extend lines 124 intersection_left = smart_find_intersection( 125 landmarks.pel_l_o, landmarks.pel_l_i, landmarks.fem_l, landmarks.fem_r 126 ) 127 intersection_right = smart_find_intersection( 128 landmarks.pel_r_o, landmarks.pel_r_i, landmarks.fem_l, landmarks.fem_r 129 ) 130 131 new_H_line = extend_line(landmarks.fem_l, landmarks.fem_r, scale=1.2) 132 new_A_line = extend_line( 133 landmarks.pel_l_o, intersection_left, scale=1.3, direction="up" 134 ) 135 new_B_line = extend_line( 136 landmarks.pel_r_o, intersection_right, scale=1.3, direction="up" 137 ) 138 139 overlay.draw_lines([new_H_line, new_A_line, new_B_line]) 140 141 # Draw ace_index_left and ace_index_right 142 overlay.draw_text( 143 f"ACE Index: {hip.metrics[0].value}", 144 landmarks.fem_l[0] - 150, 145 (landmarks.fem_l[1] + 10), 146 ) 147 148 overlay.draw_text( 149 f"ACE Index: {hip.metrics[1].value}", 150 landmarks.fem_r[0] - 100, 151 (landmarks.fem_r[1] + 10), 152 ) 153 154 return overlay
30def find_ace(landmarks: LandmarksXRay) -> tuple[float, float]: 31 """ 32 Calculate the Acentabular Index (ACE Index) for both hips. 33 34 :param landmarks: The Landmarks of the Hip 35 :return: The ACE Index for both hips 36 """ 37 intersection_left = smart_find_intersection( 38 landmarks.pel_l_o, landmarks.pel_l_i, landmarks.fem_l, landmarks.fem_r 39 ) 40 intersection_right = smart_find_intersection( 41 landmarks.pel_r_o, landmarks.pel_r_i, landmarks.fem_l, landmarks.fem_r 42 ) 43 44 A, B, C = ( 45 np.array(landmarks.pel_l_o), 46 np.array(landmarks.pel_l_i), 47 np.array(intersection_right), 48 ) 49 AB = np.linalg.norm(A - B) 50 BC = np.linalg.norm(B - C) 51 AC = np.linalg.norm(A - C) 52 53 ace_index_left = np.arccos((BC**2 + AB**2 - AC**2) / (2 * BC * AB)) 54 55 ace_index_left = round(180 - np.degrees(ace_index_left), 1) 56 57 A, B, C = ( 58 np.array(landmarks.pel_r_o), 59 np.array(landmarks.pel_r_i), 60 np.array(intersection_left), 61 ) 62 AB = np.linalg.norm(A - B) 63 BC = np.linalg.norm(B - C) 64 AC = np.linalg.norm(A - C) 65 66 ace_index_right = np.arccos((BC**2 + AB**2 - AC**2) / (2 * BC * AB)) 67 68 ace_index_right = round(180 - np.degrees(ace_index_right), 1) 69 70 if not (5 < ace_index_left < 50): 71 ace_index_left = 0 72 73 if not (5 < ace_index_right < 50): 74 ace_index_right = 0 75 76 return ace_index_left, ace_index_right
Calculate the Acentabular Index (ACE Index) for both hips.
Parameters
- landmarks: The Landmarks of the Hip
Returns
The ACE Index for both hips
def
extend_line( p1: list, p2: list, scale: float = 1.2, direction: Literal['both', 'up', 'down'] = 'both'):
79def extend_line( 80 p1: list, 81 p2: list, 82 scale: float = 1.2, 83 direction: Literal["both", "up", "down"] = "both", 84): 85 """ 86 Extend the line from p1 to p2 by a scale factor (default 10%) 87 in specified direction ('up', 'down', 'both'). 88 89 :param p1: The first point of the line 90 :param p2: The second point of the line 91 :param scale: The scale factor 92 :param direction: The direction to extend the lines 93 """ 94 direction_vector = np.array(p2) - np.array(p1) 95 left_point = ( 96 np.array(p1) - direction_vector * (scale - 1) 97 if direction in ["up", "both"] 98 else np.array(p1) 99 ) 100 right_point = ( 101 np.array(p2) + direction_vector * (scale - 1) 102 if direction in ["down", "both"] 103 else np.array(p2) 104 ) 105 106 return left_point, right_point
Extend the line from p1 to p2 by a scale factor (default 10%) in specified direction ('up', 'down', 'both').
Parameters
- p1: The first point of the line
- p2: The second point of the line
- scale: The scale factor
- direction: The direction to extend the lines
def
draw_ace( hip: retuve.hip_xray.classes.HipDataXray, overlay: retuve.classes.draw.Overlay, config: retuve.keyphrases.config.Config):
109def draw_ace(hip: HipDataXray, overlay: Overlay, config: Config): 110 """ 111 Draw the Acentabular Index (ACE Index) on the X-Ray. 112 113 :param hip: The Hip Data 114 :param overlay: The Overlay 115 :param config: The Config 116 117 :return: The Drawn Overlay 118 """ 119 landmarks = hip.landmarks 120 121 if not landmarks.fem_l: 122 return overlay 123 124 # Intersect and extend lines 125 intersection_left = smart_find_intersection( 126 landmarks.pel_l_o, landmarks.pel_l_i, landmarks.fem_l, landmarks.fem_r 127 ) 128 intersection_right = smart_find_intersection( 129 landmarks.pel_r_o, landmarks.pel_r_i, landmarks.fem_l, landmarks.fem_r 130 ) 131 132 new_H_line = extend_line(landmarks.fem_l, landmarks.fem_r, scale=1.2) 133 new_A_line = extend_line( 134 landmarks.pel_l_o, intersection_left, scale=1.3, direction="up" 135 ) 136 new_B_line = extend_line( 137 landmarks.pel_r_o, intersection_right, scale=1.3, direction="up" 138 ) 139 140 overlay.draw_lines([new_H_line, new_A_line, new_B_line]) 141 142 # Draw ace_index_left and ace_index_right 143 overlay.draw_text( 144 f"ACE Index: {hip.metrics[0].value}", 145 landmarks.fem_l[0] - 150, 146 (landmarks.fem_l[1] + 10), 147 ) 148 149 overlay.draw_text( 150 f"ACE Index: {hip.metrics[1].value}", 151 landmarks.fem_r[0] - 100, 152 (landmarks.fem_r[1] + 10), 153 ) 154 155 return overlay
Draw the Acentabular Index (ACE Index) on the X-Ray.
Parameters
- hip: The Hip Data
- overlay: The Overlay
- config: The Config
Returns
The Drawn Overlay