retuve.hip_us.handlers.bad_data

Handles bad Hip Data Objects by removing outliers and empty frames.

  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"""
 16Handles bad Hip Data Objects by removing outliers and empty frames.
 17"""
 18
 19from typing import List
 20
 21import numpy as np
 22
 23from retuve.hip_us.classes.general import HipDatasUS, HipDataUS
 24from retuve.hip_us.metrics.alpha import bad_alpha
 25from retuve.hip_us.metrics.coverage import bad_coverage
 26from retuve.keyphrases.config import Config
 27from retuve.keyphrases.enums import HipMode
 28
 29
 30def remove_outliers(hip_datas: HipDatasUS, config: Config) -> List[bool]:
 31    """
 32    Remove outliers from the HipDatasUS object.
 33
 34    :param hip_datas: HipDatasUS object.
 35    :param config: Config object.
 36
 37    :return: List of booleans indicating which frames to keep.
 38    """
 39    pred_made = [(True if hip.marked() else False) for hip in hip_datas]
 40
 41    # Use this as a sliding window to find the position where
 42    # the most Trues fit in the window
 43    # Get total number of True values
 44    total_true = sum(pred_made)
 45
 46    # Sliding window to find the position where the most Trues fit in the window
 47    max_true = 0
 48    max_true_index = 0
 49
 50    for i in range(len(pred_made) - total_true + 1):
 51        current_window_true = sum(pred_made[i : i + total_true])
 52        if current_window_true > max_true:
 53            max_true = current_window_true
 54            max_true_index = i
 55
 56    # Create a list of Trues and Falses
 57    keep = [False] * len(pred_made)
 58    for i in range(max_true_index, max_true_index + total_true):
 59        if pred_made[i]:
 60            keep[i] = True
 61
 62    return keep
 63
 64
 65def left_apex_line_flat(hip: HipDataUS) -> bool:
 66    """
 67    Check if the left apex line is flat.
 68
 69    :param hip: HipDataUS object.
 70
 71    :return: Boolean indicating if the left apex line is flat.
 72    """
 73    if hip.landmarks.left is None or hip.landmarks.apex is None:
 74        return True
 75
 76    C, A, B = (
 77        np.array(hip.landmarks.left),
 78        np.array(hip.landmarks.apex),
 79        np.array((hip.landmarks.apex[0], hip.landmarks.left[1])),
 80    )
 81
 82    a = np.linalg.norm(C - B)
 83    b = np.linalg.norm(C - A)
 84
 85    angle = np.arccos((a**2 + b**2 - np.linalg.norm(A - B) ** 2) / (2 * a * b))
 86    angle = np.degrees(angle)
 87
 88    return abs(angle) < 10
 89
 90
 91def apex_right_points_too_close(hip: HipDataUS) -> bool:
 92    """
 93    Check if the apex and right points are too close.
 94
 95    :param hip: HipDataUS object.
 96
 97    :return: Boolean indicating if the apex and right points are too close.
 98    """
 99    if hip.landmarks.right is None or hip.landmarks.apex is None:
100        return True
101
102    return (
103        np.linalg.norm(
104            np.array(hip.landmarks.right) - np.array(hip.landmarks.apex)
105        )
106        < 30
107    )
108
109
110def handle_bad_frames(hip_datas: HipDatasUS, config: Config) -> HipDatasUS:
111    """
112    Handle bad frames by removing outliers and empty frames.
113
114    :param hip_datas: HipDatasUS object.
115    :param config: Config object.
116
117    :return: HipDatasUS object.
118    """
119
120    if config.batch.hip_mode == HipMode.US2DSW:
121        keep = [(True if hip.marked() else False) for hip in hip_datas]
122    else:
123        keep = remove_outliers(hip_datas, config)
124
125    bad_frame_reasons = {}
126
127    for i, (hip, rejection_reasons) in enumerate(
128        zip(hip_datas, hip_datas.all_seg_rejection_reasons)
129    ):
130
131        empty_hip = HipDataUS(
132            frame_no=hip.frame_no,
133        )
134
135        if not keep[i]:
136            hip_datas[i] = empty_hip
137            if config.batch.hip_mode == HipMode.US2DSW:
138                bad_frame_reasons[i] = " ".join(rejection_reasons)
139                if not rejection_reasons:
140                    bad_frame_reasons[i] = "Not Enough Data"
141            continue
142
143        if (not hip.metrics) or all(
144            metric.value == 0 for metric in hip.metrics
145        ):
146            hip_datas[i] = empty_hip
147            bad_frame_reasons[i] = "No Metrics"
148            continue
149
150        if hip.landmarks is None:
151            hip_datas[i] = empty_hip
152            bad_frame_reasons[i] = "No Landmarks"
153            continue
154
155        if bad_alpha(hip):
156            hip_datas[i] = empty_hip
157            bad_frame_reasons[i] = "Alpha Angle Non-Sensical"
158            continue
159
160        if not left_apex_line_flat(hip):
161            hip_datas[i] = empty_hip
162            bad_frame_reasons[i] = "Ilium Line not Flat"
163            continue
164
165        if bad_coverage(hip):
166            hip_datas[i] = empty_hip
167            bad_frame_reasons[i] = "Coverage Value Non-Sensical"
168            continue
169
170        if apex_right_points_too_close(hip):
171            hip_datas[i] = empty_hip
172            bad_frame_reasons[i] = "Apex and Right Too Close"
173            continue
174
175    hip_datas.bad_frame_reasons = bad_frame_reasons
176    return hip_datas
def remove_outliers( hip_datas: retuve.hip_us.classes.general.HipDatasUS, config: retuve.keyphrases.config.Config) -> List[bool]:
31def remove_outliers(hip_datas: HipDatasUS, config: Config) -> List[bool]:
32    """
33    Remove outliers from the HipDatasUS object.
34
35    :param hip_datas: HipDatasUS object.
36    :param config: Config object.
37
38    :return: List of booleans indicating which frames to keep.
39    """
40    pred_made = [(True if hip.marked() else False) for hip in hip_datas]
41
42    # Use this as a sliding window to find the position where
43    # the most Trues fit in the window
44    # Get total number of True values
45    total_true = sum(pred_made)
46
47    # Sliding window to find the position where the most Trues fit in the window
48    max_true = 0
49    max_true_index = 0
50
51    for i in range(len(pred_made) - total_true + 1):
52        current_window_true = sum(pred_made[i : i + total_true])
53        if current_window_true > max_true:
54            max_true = current_window_true
55            max_true_index = i
56
57    # Create a list of Trues and Falses
58    keep = [False] * len(pred_made)
59    for i in range(max_true_index, max_true_index + total_true):
60        if pred_made[i]:
61            keep[i] = True
62
63    return keep

Remove outliers from the HipDatasUS object.

Parameters
  • hip_datas: HipDatasUS object.
  • config: Config object.
Returns

List of booleans indicating which frames to keep.

def left_apex_line_flat(hip: retuve.hip_us.classes.general.HipDataUS) -> bool:
66def left_apex_line_flat(hip: HipDataUS) -> bool:
67    """
68    Check if the left apex line is flat.
69
70    :param hip: HipDataUS object.
71
72    :return: Boolean indicating if the left apex line is flat.
73    """
74    if hip.landmarks.left is None or hip.landmarks.apex is None:
75        return True
76
77    C, A, B = (
78        np.array(hip.landmarks.left),
79        np.array(hip.landmarks.apex),
80        np.array((hip.landmarks.apex[0], hip.landmarks.left[1])),
81    )
82
83    a = np.linalg.norm(C - B)
84    b = np.linalg.norm(C - A)
85
86    angle = np.arccos((a**2 + b**2 - np.linalg.norm(A - B) ** 2) / (2 * a * b))
87    angle = np.degrees(angle)
88
89    return abs(angle) < 10

Check if the left apex line is flat.

Parameters
  • hip: HipDataUS object.
Returns

Boolean indicating if the left apex line is flat.

def apex_right_points_too_close(hip: retuve.hip_us.classes.general.HipDataUS) -> bool:
 92def apex_right_points_too_close(hip: HipDataUS) -> bool:
 93    """
 94    Check if the apex and right points are too close.
 95
 96    :param hip: HipDataUS object.
 97
 98    :return: Boolean indicating if the apex and right points are too close.
 99    """
100    if hip.landmarks.right is None or hip.landmarks.apex is None:
101        return True
102
103    return (
104        np.linalg.norm(
105            np.array(hip.landmarks.right) - np.array(hip.landmarks.apex)
106        )
107        < 30
108    )

Check if the apex and right points are too close.

Parameters
  • hip: HipDataUS object.
Returns

Boolean indicating if the apex and right points are too close.

111def handle_bad_frames(hip_datas: HipDatasUS, config: Config) -> HipDatasUS:
112    """
113    Handle bad frames by removing outliers and empty frames.
114
115    :param hip_datas: HipDatasUS object.
116    :param config: Config object.
117
118    :return: HipDatasUS object.
119    """
120
121    if config.batch.hip_mode == HipMode.US2DSW:
122        keep = [(True if hip.marked() else False) for hip in hip_datas]
123    else:
124        keep = remove_outliers(hip_datas, config)
125
126    bad_frame_reasons = {}
127
128    for i, (hip, rejection_reasons) in enumerate(
129        zip(hip_datas, hip_datas.all_seg_rejection_reasons)
130    ):
131
132        empty_hip = HipDataUS(
133            frame_no=hip.frame_no,
134        )
135
136        if not keep[i]:
137            hip_datas[i] = empty_hip
138            if config.batch.hip_mode == HipMode.US2DSW:
139                bad_frame_reasons[i] = " ".join(rejection_reasons)
140                if not rejection_reasons:
141                    bad_frame_reasons[i] = "Not Enough Data"
142            continue
143
144        if (not hip.metrics) or all(
145            metric.value == 0 for metric in hip.metrics
146        ):
147            hip_datas[i] = empty_hip
148            bad_frame_reasons[i] = "No Metrics"
149            continue
150
151        if hip.landmarks is None:
152            hip_datas[i] = empty_hip
153            bad_frame_reasons[i] = "No Landmarks"
154            continue
155
156        if bad_alpha(hip):
157            hip_datas[i] = empty_hip
158            bad_frame_reasons[i] = "Alpha Angle Non-Sensical"
159            continue
160
161        if not left_apex_line_flat(hip):
162            hip_datas[i] = empty_hip
163            bad_frame_reasons[i] = "Ilium Line not Flat"
164            continue
165
166        if bad_coverage(hip):
167            hip_datas[i] = empty_hip
168            bad_frame_reasons[i] = "Coverage Value Non-Sensical"
169            continue
170
171        if apex_right_points_too_close(hip):
172            hip_datas[i] = empty_hip
173            bad_frame_reasons[i] = "Apex and Right Too Close"
174            continue
175
176    hip_datas.bad_frame_reasons = bad_frame_reasons
177    return hip_datas

Handle bad frames by removing outliers and empty frames.

Parameters
  • hip_datas: HipDatasUS object.
  • config: Config object.
Returns

HipDatasUS object.